From 7e2418b9579c1e952c32d2694f12dde1aca25991 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 10 Jul 2025 09:54:59 +0200 Subject: [PATCH 01/39] Held item refactor (#5656) * Introducing held items * Pokemon class can add held items * Example of applyHeldItemAttrs * Introducing a PokemonItemManager class * Moving away from HeldItemAttr * Attempt at incorporating the new framework in modifier-type * Changes * More changes * Splitting up methods in select-modifier-phase.ts * Newrefactors of reward-pool-manager.ts * New refactors of select-modifier-phase.ts * Extracted logic from modifierSelectCallback * Moved some files around, added many held item ids * Introduced HeldItemReward class * Introduced AttackBoosterReward * Introduced AttackBoosterRewardGenerator * Removed unused file * Select modifier phase tentatively working with new held items * Working leftovers in new style * Moved modifier-bar to its own file * Held items now show up in summary * Added some suggestions * Implemented more suggestions * Splitting up held-item file * Fixing various imports * Created items folder * Shell bell * Converted White Herb * HeldItem .apply() methods do not really need the stack as input, just the pokemon * Made heldItems public in heldItemManager * Update modifier bar is now called in the apply of consumable held items * Refactored parameters passed to .apply() methods; introduced generic .applyHeldItems() function; all HeldItems classes specify an ITEM_EFFECT * Lucky egg and Golden egg * Converted wild pokemon pool to held item rewards * Temporary stopgap on maxUpgradeCount to avoid game crashing on modifier select ui handler * Changed held-items.ts to held-item-id.ts and renamed id object accoridngly * Added reviver seed * Simplified HeldItemReward * Added effect of reviver seed (leveraging consumable logic) * Remove InstantReviveModifier * Added Stat Boost items; generic name and description in HeldItem class * Added Crit Boost held items, King's Rock, Focus Band and Quick Claw * Added Mystical Rock * Added Shell Bell, Soul Dew * Added multi lens and wide lens * Added Baton and Golden Punch * Baton switch logic in party ui handler now using held item * Partial implementation of item steal items * Using held items in some places * Using phaseManager * Tracking forms in held item manager * Shuckle Juice and Old Gateau * Using phaseManager * Removed a bunch of modifiers * Fixed shell bell in ability.ts (why is it here?) * Changed BattleScene.removeModifier and pokemon.loseHeldItem * Making some held items unstealable and unsuppressable * Refactored most of battle-scene.ts with held items * Added soundName to HeldItem (possibly useless) * Reworked various effects that steal items * Refactored Baton logic * Reworked most entries in Modifier Types * pokemon.getHeldItems now uses heldItemManager * Added Evolution Tracker as held item * MBE is always untransferable * Improved item transfer * Fixed types in held item manager * Various fixes * Fixed types in shuckle juice and old gateau * MBE achievement now tracks held items * Removed AttackTypeBoosterModifierRequirement for MEs * Fixed Pickup * Fixing (most) berry usage * Using Berry held items in move.ts * Split up vitamins from the rest of stat boosting items * Fixed form change trigger after merge conflicts * Added some utility functions to check if an item fits a list of items/categories, or to filter out which held items fit * Fixed delibirdy encounter * Various fixes * Reworked EnemyPokemonConfig to include a HeldItemProperty object (to feed to the heldItemManager). Updated Dark Deal ME * More various fixes; introduced isMaxStack(item) method to heldItemManager * Updated modifier-bar.ts * Converted Berries Abound encounter * Converted The Strong Stuff encounter * Fixed Slumbering Snorlax encounter, overrideItems of heldItemConfiguration can deal with items with 0 stack * Preliminary changes to some MEs * Changes to the summary ui handler * Converting to held item pools * Fixed evo tracker * Moved many data types for held items to a dedicated file; introduced held item pools for item generation * Fixed some MEs * Added function to assign items from a given configuration * Fixed held item evolution condition * Fixed some ui handlers * Fixed select-modifier-phase * Some changes to modifier files * Daily run items are generated within the new system * Held item generation for enemies follows the new scheme * Fixed init-modifier-pools.ts * Held item overrides now use the new system * Removed unused getOrInferTier function (was only used by thief and covet) * Fixed shady vitamin ME and some HeldItem files * Updated various MEs. HeldItemManager can now generate a configuration from its items. * Converted most MEs, introduced some more utility functions and methods * Replaced ModifierTier with RewardTier * Fixed some rogue inputs * Held item pools take a single pokemon as input, set weights to 0 for max stack items * Restored various changes to held item pool generation; distinguishing target of the generation from the party * Pools can now take a HaldItemConfigurationId without weights; set up table of item tiers; used custom pools in Clowning Around ME * Fixing some phases * Fixed several bugs related to accessing and visualizing held items * Fixed position of enemy modifier bar * Fixed berries, removed some debug messages * Vitamins max stack temporarily set to 30 * Luck upgrades work correctly again (maybe) * First steps to port tests to the new system * Form change items work correctly when selected in battle * Introducing helper functions for a modifier migrator; PokemonItemMap now uses pokemonId * Renamed file with item migrator functions * Restored missing strings * Shuckle juic and Old gateau are not instance dependent * Simplified HeldItemData * Migrator utils include shuckle juice * Introducing trainer items * Enemy tokens are now also trainer items * Converted most of modifierTypes * Fixed various MEs; removed findModifier from battle-scene * Removed most uses of globalScene.applyModifiers * Removed more functions from globalScene; changed lure weight function * Updated a variety of files * Split out X_accuracy, fixed trainer-item-manager * Fixed expert breeder ME * Replaced updateModifiers with updateItems * Removed modifiers from saveData * Trainer item rewards are generating during runs, added properly to the scene * Items (including held items) are saved and loaded correctly. * Fixed several trainer item names and descriptions, plus various issues with item generation * Restored override file * Added icons to tokens * Fixed Dire Hit starting stack * Using Pokemon.getMoveType() in generation of attack type boosters * Test for dire hit; ensuring that lapsing trainer items are added from config at full stack * Eviolite does not apply to G-Max forms * Changes to various item tests * Some still broken tests * More changes to tests but still failing * Fixed evo tracker item * Moved allHeldItems and allTrainerItems definitions to data-lists * Renamed modifier-bar and moved to ui files * Fixed held item when pokemon not passed * Remove `package-lock.json` added by merge issue * Add review comments as `TODO`s for now --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/@types/trainer-funcs.ts | 4 +- src/battle-scene.ts | 688 ++-- src/battle.ts | 73 +- src/data/abilities/ability.ts | 99 +- src/data/balance/pokemon-evolutions.ts | 12 +- src/data/balance/tms.ts | 636 ++-- src/data/challenge.ts | 24 +- src/data/data-lists.ts | 7 + src/data/moves/move.ts | 110 +- .../encounters/a-trainers-test-encounter.ts | 4 +- .../encounters/absolute-avarice-encounter.ts | 213 +- .../an-offer-you-cant-refuse-encounter.ts | 7 +- .../encounters/berries-abound-encounter.ts | 34 +- .../encounters/bug-type-superfan-encounter.ts | 66 +- .../encounters/clowning-around-encounter.ts | 177 +- .../encounters/dark-deal-encounter.ts | 16 +- .../encounters/delibirdy-encounter.ts | 142 +- .../encounters/fiery-fallout-encounter.ts | 20 +- .../encounters/fight-or-flight-encounter.ts | 10 +- .../encounters/fun-and-games-encounter.ts | 2 +- .../global-trade-system-encounter.ts | 80 +- .../mysterious-challengers-encounter.ts | 6 +- .../encounters/mysterious-chest-encounter.ts | 10 +- .../encounters/safari-zone-encounter.ts | 10 +- .../shady-vitamin-dealer-encounter.ts | 39 +- .../slumbering-snorlax-encounter.ts | 29 +- .../the-expert-pokemon-breeder-encounter.ts | 10 +- .../encounters/the-strong-stuff-encounter.ts | 32 +- .../the-winstrate-challenge-encounter.ts | 202 +- .../encounters/training-session-encounter.ts | 56 +- .../encounters/trash-to-treasure-encounter.ts | 113 +- .../encounters/uncommon-breed-encounter.ts | 32 +- .../encounters/weird-dream-encounter.ts | 68 +- .../mystery-encounter-requirements.ts | 135 +- .../utils/encounter-phase-utils.ts | 55 +- .../utils/encounter-pokemon-utils.ts | 72 +- src/data/pokeball.ts | 5 +- src/data/pokemon-forms.ts | 5 + .../pokemon-forms/form-change-triggers.ts | 17 +- src/data/trainers/trainer-config.ts | 18 +- src/enums/held-item-id.ts | 147 + src/enums/modifier-pool-type.ts | 10 +- .../{modifier-tier.ts => reward-tier.ts} | 2 +- src/enums/trainer-item-id.ts | 68 + src/events/battle-scene.ts | 14 +- src/field/arena.ts | 7 +- src/field/pokemon-held-item-manager.ts | 229 ++ src/field/pokemon.ts | 208 +- src/field/trainer.ts | 4 +- src/items/all-held-items.ts | 215 ++ src/items/all-trainer-items.ts | 114 + src/items/apply-trainer-items.ts | 47 + src/items/held-item-data-types.ts | 84 + src/items/held-item-pool.ts | 324 ++ src/items/held-item-tiers.ts | 47 + src/items/held-item.ts | 171 + src/items/held-items/accuracy-booster.ts | 47 + src/items/held-items/attack-type-booster.ts | 78 + src/items/held-items/base-stat-booster.ts | 81 + src/items/held-items/base-stat-flat.ts | 71 + src/items/held-items/base-stat-total.ts | 70 + src/items/held-items/baton.ts | 22 + src/items/held-items/berry.ts | 96 + src/items/held-items/bypass-speed-chance.ts | 62 + src/items/held-items/crit-booster.ts | 86 + src/items/held-items/damage-money-reward.ts | 33 + src/items/held-items/evo-tracker.ts | 57 + src/items/held-items/exp-booster.ts | 56 + src/items/held-items/field-effect.ts | 33 + src/items/held-items/flinch-chance.ts | 57 + src/items/held-items/friendship-booster.ts | 34 + src/items/held-items/hit-heal.ts | 53 + src/items/held-items/incrementing-stat.ts | 71 + src/items/held-items/instant-revive.ts | 68 + src/items/held-items/item-steal.ts | 168 + src/items/held-items/multi-hit.ts | 89 + src/items/held-items/nature-weight-booster.ts | 32 + .../held-items/reset-negative-stat-stage.ts | 66 + src/items/held-items/stat-booster.ts | 191 + src/items/held-items/survive-chance.ts | 57 + src/items/held-items/turn-end-heal.ts | 36 + src/items/held-items/turn-end-status.ts | 40 + src/items/init-held-item-pools.ts | 80 + src/items/init-trainer-item-pools.ts | 41 + src/items/item-utility.ts | 41 + src/items/modifier-to-item-migrator-utils.ts | 152 + src/items/trainer-item-data-types.ts | 44 + src/items/trainer-item-manager.ts | 158 + src/items/trainer-item-pool.ts | 62 + src/items/trainer-item.ts | 575 +++ src/loading-scene.ts | 8 + src/modifier/init-modifier-pools.ts | 266 +- src/modifier/modifier-pools.ts | 8 - src/modifier/modifier-type.ts | 1553 ++------ src/modifier/modifier.ts | 3364 +---------------- src/overrides.ts | 17 +- src/phases/add-enemy-buff-modifier-phase.ts | 25 +- src/phases/attempt-capture-phase.ts | 21 +- src/phases/attempt-run-phase.ts | 2 - src/phases/battle-end-phase.ts | 17 +- src/phases/berry-phase.ts | 23 +- src/phases/encounter-phase.ts | 38 +- src/phases/exp-phase.ts | 4 +- src/phases/faint-phase.ts | 15 +- src/phases/game-over-phase.ts | 11 +- src/phases/modifier-reward-phase.ts | 12 +- src/phases/money-reward-phase.ts | 4 +- src/phases/move-effect-phase.ts | 26 +- src/phases/mystery-encounter-phases.ts | 5 +- src/phases/pokemon-heal-phase.ts | 4 +- src/phases/select-biome-phase.ts | 5 +- src/phases/select-modifier-phase.ts | 73 +- src/phases/select-starter-phase.ts | 4 +- src/phases/show-party-exp-bar-phase.ts | 4 +- src/phases/stat-stage-change-phase.ts | 14 +- src/phases/summon-phase.ts | 4 +- src/phases/switch-summon-phase.ts | 26 +- src/phases/title-phase.ts | 27 +- src/phases/trainer-item-reward-phase.ts | 43 + src/phases/turn-end-phase.ts | 21 +- src/phases/turn-start-phase.ts | 5 +- src/system/achv.ts | 20 +- src/system/game-data.ts | 75 +- src/system/modifier-data.ts | 63 - src/system/pokemon-data.ts | 10 + src/timed-event-manager.ts | 16 +- src/ui/battle-flyout.ts | 4 +- src/ui/command-ui-handler.ts | 4 +- src/ui/item-bar-ui.ts | 103 + src/ui/modifier-select-ui-handler.ts | 24 +- src/ui/party-ui-handler.ts | 138 +- src/ui/run-info-ui-handler.ts | 87 +- src/ui/save-slot-select-ui-handler.ts | 11 +- src/ui/summary-ui-handler.ts | 39 +- src/ui/target-select-ui-handler.ts | 2 +- src/ui/text.ts | 24 +- src/utils/common.ts | 19 + src/utils/modifier-utils.ts | 16 +- test/abilities/cud_chew.test.ts | 9 +- test/abilities/harvest.test.ts | 41 +- test/abilities/unburden.test.ts | 33 +- test/items/dire_hit.test.ts | 19 +- .../double_battle_chance_booster.test.ts | 33 +- test/items/eviolite.test.ts | 22 +- test/items/exp_booster.test.ts | 8 +- test/items/grip_claw.test.ts | 65 +- test/items/leek.test.ts | 3 +- test/items/leftovers.test.ts | 9 +- test/items/light_ball.test.ts | 71 +- test/items/lock_capsule.test.ts | 7 +- test/items/mystical_rock.test.ts | 3 +- test/items/reviver_seed.test.ts | 6 +- test/items/scope_lens.test.ts | 3 +- test/items/temp_stat_stage_booster.test.ts | 1 - test/items/toxic_orb.test.ts | 9 +- test/moves/fusion_flare_bolt.test.ts | 4 +- .../clowning-around-encounter.test.ts | 8 +- .../encounters/delibirdy-encounter.test.ts | 24 +- .../global-trade-system-encounter.test.ts | 8 +- .../mysterious-challengers-encounter.test.ts | 18 +- .../trash-to-treasure-encounter.test.ts | 10 +- .../uncommon-breed-encounter.test.ts | 4 +- .../encounters/weird-dream-encounter.test.ts | 14 +- test/phases/select-modifier-phase.test.ts | 34 +- test/testUtils/gameManager.ts | 3 +- test/testUtils/helpers/overridesHelper.ts | 47 +- test/testUtils/helpers/reloadHelper.ts | 10 - test/testUtils/testFileInitialization.ts | 8 + 168 files changed, 6866 insertions(+), 7908 deletions(-) create mode 100644 src/enums/held-item-id.ts rename src/enums/{modifier-tier.ts => reward-tier.ts} (68%) create mode 100644 src/enums/trainer-item-id.ts create mode 100644 src/field/pokemon-held-item-manager.ts create mode 100644 src/items/all-held-items.ts create mode 100644 src/items/all-trainer-items.ts create mode 100644 src/items/apply-trainer-items.ts create mode 100644 src/items/held-item-data-types.ts create mode 100644 src/items/held-item-pool.ts create mode 100644 src/items/held-item-tiers.ts create mode 100644 src/items/held-item.ts create mode 100644 src/items/held-items/accuracy-booster.ts create mode 100644 src/items/held-items/attack-type-booster.ts create mode 100644 src/items/held-items/base-stat-booster.ts create mode 100644 src/items/held-items/base-stat-flat.ts create mode 100644 src/items/held-items/base-stat-total.ts create mode 100644 src/items/held-items/baton.ts create mode 100644 src/items/held-items/berry.ts create mode 100644 src/items/held-items/bypass-speed-chance.ts create mode 100644 src/items/held-items/crit-booster.ts create mode 100644 src/items/held-items/damage-money-reward.ts create mode 100644 src/items/held-items/evo-tracker.ts create mode 100644 src/items/held-items/exp-booster.ts create mode 100644 src/items/held-items/field-effect.ts create mode 100644 src/items/held-items/flinch-chance.ts create mode 100644 src/items/held-items/friendship-booster.ts create mode 100644 src/items/held-items/hit-heal.ts create mode 100644 src/items/held-items/incrementing-stat.ts create mode 100644 src/items/held-items/instant-revive.ts create mode 100644 src/items/held-items/item-steal.ts create mode 100644 src/items/held-items/multi-hit.ts create mode 100644 src/items/held-items/nature-weight-booster.ts create mode 100644 src/items/held-items/reset-negative-stat-stage.ts create mode 100644 src/items/held-items/stat-booster.ts create mode 100644 src/items/held-items/survive-chance.ts create mode 100644 src/items/held-items/turn-end-heal.ts create mode 100644 src/items/held-items/turn-end-status.ts create mode 100644 src/items/init-held-item-pools.ts create mode 100644 src/items/init-trainer-item-pools.ts create mode 100644 src/items/item-utility.ts create mode 100644 src/items/modifier-to-item-migrator-utils.ts create mode 100644 src/items/trainer-item-data-types.ts create mode 100644 src/items/trainer-item-manager.ts create mode 100644 src/items/trainer-item-pool.ts create mode 100644 src/items/trainer-item.ts create mode 100644 src/phases/trainer-item-reward-phase.ts delete mode 100644 src/system/modifier-data.ts create mode 100644 src/ui/item-bar-ui.ts diff --git a/src/@types/trainer-funcs.ts b/src/@types/trainer-funcs.ts index 0546dd53024..d2a6127819c 100644 --- a/src/@types/trainer-funcs.ts +++ b/src/@types/trainer-funcs.ts @@ -1,5 +1,5 @@ import type { EnemyPokemon } from "#app/field/pokemon"; -import type { PersistentModifier } from "#app/modifier/modifier"; +import type { TrainerItemConfiguration } from "#app/items/trainer-item-data-types"; import type { PartyMemberStrength } from "#enums/party-member-strength"; import type { SpeciesId } from "#enums/species-id"; import type { TrainerConfig } from "../data/trainers/trainer-config"; @@ -7,7 +7,7 @@ import type { TrainerPartyTemplate } from "../data/trainers/TrainerPartyTemplate export type PartyTemplateFunc = () => TrainerPartyTemplate; export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon; -export type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[]; +export type GenTrainerItemsFunc = (party: EnemyPokemon[]) => TrainerItemConfiguration; export type GenAIFunc = (party: EnemyPokemon[]) => void; export interface TrainerTierPools { diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 721b55020f9..5fa6df23051 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -5,7 +5,7 @@ import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type { PokemonSpeciesFilter } from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; -import { allSpecies } from "#app/data/data-lists"; +import { allHeldItems, allSpecies, allTrainerItems } from "#app/data/data-lists"; import { fixedInt, getIvsFromId, @@ -21,23 +21,12 @@ import { isBetween, } from "#app/utils/common"; import { deepMergeSpriteData } from "#app/utils/data"; -import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier"; +import type { Modifier } from "./modifier/modifier"; import { ConsumableModifier, ConsumablePokemonModifier, - DoubleBattleChanceBoosterModifier, - ExpBalanceModifier, - ExpShareModifier, FusePokemonModifier, - HealingBoosterModifier, - ModifierBar, - MultipleParticipantExpBonusModifier, - PersistentModifier, - PokemonExpBoosterModifier, - PokemonFormChangeItemModifier, - PokemonHeldItemModifier, PokemonHpRestoreModifier, - PokemonIncrementingStatModifier, RememberMoveModifier, } from "./modifier/modifier"; import { PokeballType } from "#enums/pokeball"; @@ -55,18 +44,9 @@ import { GameData } from "#app/system/game-data"; import { addTextObject, getTextColor, TextStyle } from "#app/ui/text"; import { allMoves } from "./data/data-lists"; import { MusicPreference } from "#app/system/settings/settings"; -import { - getDefaultModifierTypeForTier, - getEnemyModifierTypesForWave, - getLuckString, - getLuckTextTint, - getPartyLuckValue, - PokemonHeldItemModifierType, -} from "#app/modifier/modifier-type"; -import { getModifierType } from "./utils/modifier-utils"; -import { modifierTypes } from "./data/data-lists"; +import { getLuckString, getLuckTextTint, getPartyLuckValue } from "#app/modifier/modifier-type"; import { getModifierPoolForType } from "./utils/modifier-utils"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; +import { HeldItemPoolType, ModifierPoolType } from "#enums/modifier-pool-type"; import AbilityBar from "#app/ui/ability-bar"; import { applyAbAttrs } from "./data/abilities/apply-ab-attrs"; import { allAbilities } from "./data/data-lists"; @@ -89,7 +69,7 @@ import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import PokeballTray from "#app/ui/pokeball-tray"; import InvertPostFX from "#app/pipelines/invert"; import type { Achv } from "#app/system/achv"; -import { achvs, ModifierAchv, MoneyAchv } from "#app/system/achv"; +import { achvs, HeldItemAchv, ModifierAchv, MoneyAchv } from "#app/system/achv"; import type { Voucher } from "#app/system/voucher"; import { vouchers } from "#app/system/voucher"; import { Gender } from "#app/data/gender"; @@ -149,7 +129,6 @@ import { import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import type HeldModifierConfig from "#app/@types/held-modifier-config"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -161,7 +140,24 @@ import { hasExpSprite } from "./sprites/sprite-utils"; import { timedEventManager } from "./global-event-manager"; import { starterColors } from "./global-vars/starter-colors"; import { startingWave } from "./starting-wave"; +import { applyHeldItems } from "./items/all-held-items"; +import { HELD_ITEM_EFFECT } from "./items/held-item"; import { PhaseManager } from "./phase-manager"; +import { HeldItemId } from "#enums/held-item-id"; +import { assignEnemyHeldItemsForWave, assignItemsFromConfiguration } from "./items/held-item-pool"; +import type { HeldItemConfiguration } from "./items/held-item-data-types"; +import { TrainerItemManager } from "./items/trainer-item-manager"; +import { type EnemyAttackStatusEffectChanceTrainerItem, TRAINER_ITEM_EFFECT } from "./items/trainer-item"; +import { applyTrainerItems, type APPLY_TRAINER_ITEMS_PARAMS } from "./items/apply-trainer-items"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import { + isTrainerItemPool, + isTrainerItemSpecs, + type TrainerItemSaveData, + type TrainerItemConfiguration, +} from "./items/trainer-item-data-types"; +import { getNewTrainerItemFromPool } from "./items/trainer-item-pool"; +import { ItemBar } from "./ui/item-bar-ui"; const DEBUG_RNG = false; @@ -307,8 +303,8 @@ export default class BattleScene extends SceneBase { private scoreText: Phaser.GameObjects.Text; private luckLabelText: Phaser.GameObjects.Text; private luckText: Phaser.GameObjects.Text; - private modifierBar: ModifierBar; - private enemyModifierBar: ModifierBar; + private itemBar: ItemBar; + private enemyItemBar: ItemBar; public arenaFlyout: ArenaFlyout; private fieldOverlay: Phaser.GameObjects.Rectangle; @@ -316,8 +312,8 @@ export default class BattleScene extends SceneBase { private shopOverlayShown = false; private shopOverlayOpacity = 0.8; - public modifiers: PersistentModifier[]; - private enemyModifiers: PersistentModifier[]; + public trainerItems: TrainerItemManager; + public enemyTrainerItems: TrainerItemManager; public uiContainer: Phaser.GameObjects.Container; public ui: UI; @@ -498,18 +494,18 @@ export default class BattleScene extends SceneBase { this.shopOverlay.setAlpha(0); this.fieldUI.add(this.shopOverlay); - this.modifiers = []; - this.enemyModifiers = []; + this.trainerItems = new TrainerItemManager(); + this.enemyTrainerItems = new TrainerItemManager(); - this.modifierBar = new ModifierBar(); - this.modifierBar.setName("modifier-bar"); - this.add.existing(this.modifierBar); - uiContainer.add(this.modifierBar); + this.itemBar = new ItemBar(); + this.itemBar.setName("modifier-bar"); + this.add.existing(this.itemBar); + uiContainer.add(this.itemBar); - this.enemyModifierBar = new ModifierBar(true); - this.enemyModifierBar.setName("enemy-modifier-bar"); - this.add.existing(this.enemyModifierBar); - uiContainer.add(this.enemyModifierBar); + this.enemyItemBar = new ItemBar(true); + this.enemyItemBar.setName("enemy-modifier-bar"); + this.add.existing(this.enemyItemBar); + uiContainer.add(this.enemyItemBar); this.charSprite = new CharSprite(); this.charSprite.setName("sprite-char"); @@ -879,8 +875,8 @@ export default class BattleScene extends SceneBase { * @param isEnemy Whether to return the enemy's modifier bar * @returns {ModifierBar} */ - getModifierBar(isEnemy?: boolean): ModifierBar { - return isEnemy ? this.enemyModifierBar : this.modifierBar; + getItemBar(isEnemy?: boolean): ItemBar { + return isEnemy ? this.enemyItemBar : this.itemBar; } // store info toggles to be accessible by the ui @@ -918,6 +914,7 @@ export default class BattleScene extends SceneBase { variant?: Variant, ivs?: number[], nature?: Nature, + heldItemConfig?: HeldItemConfiguration, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void, ): PlayerPokemon { @@ -931,6 +928,7 @@ export default class BattleScene extends SceneBase { variant, ivs, nature, + heldItemConfig, dataSource, ); @@ -969,6 +967,7 @@ export default class BattleScene extends SceneBase { trainerSlot: TrainerSlot, boss = false, shinyLock = false, + heldItemConfig?: HeldItemConfiguration, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void, ): EnemyPokemon { @@ -981,7 +980,7 @@ export default class BattleScene extends SceneBase { boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1; } - const pokemon = new EnemyPokemon(species, level, trainerSlot, boss, shinyLock, dataSource); + const pokemon = new EnemyPokemon(species, level, trainerSlot, boss, shinyLock, heldItemConfig, dataSource); if (Overrides.OPP_FUSION_OVERRIDE) { pokemon.generateFusionSpecies(); } @@ -1025,6 +1024,7 @@ export default class BattleScene extends SceneBase { } pokemon.init(); + return pokemon; } @@ -1045,7 +1045,7 @@ export default class BattleScene extends SceneBase { this.field.remove(pokemon, true); pokemon.destroy(); } - this.updateModifiers(true); + this.updateItems(true); } addPokemonIcon( @@ -1196,10 +1196,10 @@ export default class BattleScene extends SceneBase { this.pokeballCounts = Overrides.POKEBALL_OVERRIDE.pokeballs; } - this.modifiers = []; - this.enemyModifiers = []; - this.modifierBar.removeAll(true); - this.enemyModifierBar.removeAll(true); + this.trainerItems.clearItems(); + this.enemyTrainerItems.clearItems(); + this.itemBar.removeAll(true); + this.enemyItemBar.removeAll(true); for (const p of this.getPlayerParty()) { p.destroy(); @@ -1301,7 +1301,7 @@ export default class BattleScene extends SceneBase { getDoubleBattleChance(newWaveIndex: number, playerField: PlayerPokemon[]) { const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8); - this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance); + this.applyPlayerItems(TRAINER_ITEM_EFFECT.DOUBLE_BATTLE_CHANCE_BOOSTER, { numberHolder: doubleChance }); for (const p of playerField) { applyAbAttrs("DoubleBattleChanceAbAttr", { pokemon: p, chance: doubleChance }); } @@ -2003,11 +2003,11 @@ export default class BattleScene extends SceneBase { } showEnemyModifierBar(): void { - this.enemyModifierBar.setVisible(true); + this.enemyItemBar.setVisible(true); } hideEnemyModifierBar(): void { - this.enemyModifierBar.setVisible(false); + this.enemyItemBar.setVisible(false); } updateBiomeWaveText(): void { @@ -2100,7 +2100,7 @@ export default class BattleScene extends SceneBase { } updateUIPositions(): void { - const enemyModifierCount = this.enemyModifiers.filter(m => m.isIconVisible()).length; + const enemyModifierCount = this.enemyItemBar.totalVisibleLength; const biomeWaveTextHeight = this.biomeWaveText.getBottomLeft().y - this.biomeWaveText.getTopLeft().y; this.biomeWaveText.setY( -(this.game.canvas.height / 6) + @@ -2132,9 +2132,7 @@ export default class BattleScene extends SceneBase { enemy.getSpeciesForm().getBaseExp() * (enemy.level / this.getMaxExpLevel()) * ((enemy.ivs.reduce((iv: number, total: number) => (total += iv), 0) / 93) * 0.2 + 0.8); - this.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemy.id, false).map( - m => (scoreIncrease *= (m as PokemonHeldItemModifier).getScoreMultiplier()), - ); + enemy.getHeldItems().map(m => (scoreIncrease *= allHeldItems[m].getScoreMultiplier())); if (enemy.isBoss()) { scoreIncrease *= Math.sqrt(enemy.bossSegments); } @@ -2652,14 +2650,11 @@ export default class BattleScene extends SceneBase { return Math.floor(moneyValue / 10) * 10; } - addModifier( - modifier: Modifier | null, - ignoreUpdate?: boolean, - playSound?: boolean, - virtual?: boolean, - instant?: boolean, - cost?: number, - ): boolean { + applyPlayerItems(effect: T, params: APPLY_TRAINER_ITEMS_PARAMS[T]) { + applyTrainerItems(effect, this.trainerItems, params); + } + + addModifier(modifier: Modifier | null, playSound?: boolean, instant?: boolean, cost?: number): boolean { // We check against modifier.type to stop a bug related to loading in a pokemon that has a form change item, which prior to some patch // that changed form change modifiers worked, had previously set the `type` field to null. // TODO: This is not the right place to check for this; it should ideally go in a session migrator. @@ -2669,40 +2664,7 @@ export default class BattleScene extends SceneBase { let success = false; const soundName = modifier.type.soundName; this.validateAchvs(ModifierAchv, modifier); - const modifiersToRemove: PersistentModifier[] = []; - if (modifier instanceof PersistentModifier) { - if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) { - if (modifier instanceof PokemonFormChangeItemModifier) { - const pokemon = this.getPokemonById(modifier.pokemonId); - if (pokemon) { - success = modifier.apply(pokemon, true); - } - } - if (playSound && !this.sound.get(soundName)) { - this.playSound(soundName); - } - } else if (!virtual) { - const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier); - this.phaseManager.queueMessage( - i18next.t("battle:itemStackFull", { - fullItemName: modifier.type.name, - itemName: defaultModifierType.name, - }), - undefined, - false, - 3000, - ); - return this.addModifier(defaultModifierType.newModifier(), ignoreUpdate, playSound, false, instant); - } - - for (const rm of modifiersToRemove) { - this.removeModifier(rm); - } - - if (!ignoreUpdate && !virtual) { - this.updateModifiers(true, instant); - } - } else if (modifier instanceof ConsumableModifier) { + if (modifier instanceof ConsumableModifier) { if (playSound && !this.sound.get(soundName)) { this.playSound(soundName); } @@ -2715,7 +2677,7 @@ export default class BattleScene extends SceneBase { if (modifier instanceof PokemonHpRestoreModifier) { if (!(modifier as PokemonHpRestoreModifier).fainted) { const hpRestoreMultiplier = new NumberHolder(1); - this.applyModifiers(HealingBoosterModifier, true, hpRestoreMultiplier); + this.applyPlayerItems(TRAINER_ITEM_EFFECT.HEALING_BOOSTER, { numberHolder: hpRestoreMultiplier }); args.push(hpRestoreMultiplier.value); } else { args.push(1); @@ -2744,35 +2706,28 @@ export default class BattleScene extends SceneBase { return success; } - addEnemyModifier(modifier: PersistentModifier, ignoreUpdate?: boolean, instant?: boolean): Promise { - return new Promise(resolve => { - const modifiersToRemove: PersistentModifier[] = []; - if ((modifier as PersistentModifier).add(this.enemyModifiers, false)) { - if (modifier instanceof PokemonFormChangeItemModifier) { - const pokemon = this.getPokemonById(modifier.pokemonId); - if (pokemon) { - modifier.apply(pokemon, true); - } - } - for (const rm of modifiersToRemove) { - this.removeModifier(rm, true); - } - } - if (!ignoreUpdate) { - this.updateModifiers(false, instant); - resolve(); - } else { - resolve(); - } - }); + addHeldItem(heldItemId: HeldItemId, pokemon: Pokemon, amount = 1, playSound?: boolean, ignoreUpdate?: boolean) { + pokemon.heldItemManager.add(heldItemId, amount); + if (!ignoreUpdate) { + this.updateItems(pokemon.isPlayer()); + } + const soundName = allHeldItems[heldItemId].soundName; + if (playSound && !this.sound.get(soundName)) { + this.playSound(soundName); + } + + if (pokemon.isPlayer()) { + this.validateAchvs(HeldItemAchv, pokemon); + } } /** - * Try to transfer a held item to another pokemon. + * Try to transfer a held item from source to target. * If the recepient already has the maximum amount allowed for this item, the transfer is cancelled. * The quantity to transfer is automatically capped at how much the recepient can take before reaching the maximum stack size for the item. * A transfer that moves a quantity smaller than what is specified in the transferQuantity parameter is still considered successful. - * @param itemModifier {@linkcode PokemonHeldItemModifier} item to transfer (represents the whole stack) + * @param heldItemId {@linkcode HeldItemId} item to transfer + * @param source {@linkcode Pokemon} giver in this transfer * @param target {@linkcode Pokemon} recepient in this transfer * @param playSound `true` to play a sound when transferring the item * @param transferQuantity How many items of the stack to transfer. Optional, defaults to `1` @@ -2781,16 +2736,15 @@ export default class BattleScene extends SceneBase { * @param itemLost If `true`, treat the item's current holder as losing the item (for now, this simply enables Unburden). Default is `true`. * @returns `true` if the transfer was successful */ - tryTransferHeldItemModifier( - itemModifier: PokemonHeldItemModifier, + tryTransferHeldItem( + heldItemId: HeldItemId, + source: Pokemon, target: Pokemon, playSound: boolean, transferQuantity = 1, - instant?: boolean, ignoreUpdate?: boolean, itemLost = true, ): boolean { - const source = itemModifier.pokemonId ? itemModifier.getPokemon() : null; const cancelled = new BooleanHolder(false); if (source && source.isPlayer() !== target.isPlayer()) { @@ -2801,112 +2755,94 @@ export default class BattleScene extends SceneBase { return false; } - const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier; - newItemModifier.pokemonId = target.id; - const matchingModifier = this.findModifier( - m => m instanceof PokemonHeldItemModifier && m.matchType(itemModifier) && m.pokemonId === target.id, - target.isPlayer(), - ) as PokemonHeldItemModifier; + const itemStack = source.heldItemManager.getStack(heldItemId); + const matchingItemStack = target.heldItemManager.getStack(heldItemId); - if (matchingModifier) { - const maxStackCount = matchingModifier.getMaxStackCount(); - if (matchingModifier.stackCount >= maxStackCount) { - return false; - } - const countTaken = Math.min( - transferQuantity, - itemModifier.stackCount, - maxStackCount - matchingModifier.stackCount, - ); - itemModifier.stackCount -= countTaken; - newItemModifier.stackCount = matchingModifier.stackCount + countTaken; - } else { - const countTaken = Math.min(transferQuantity, itemModifier.stackCount); - itemModifier.stackCount -= countTaken; - newItemModifier.stackCount = countTaken; + const maxStackCount = allHeldItems[heldItemId].getMaxStackCount(); + if (matchingItemStack >= maxStackCount) { + return false; + } + const countTaken = Math.min(transferQuantity, itemStack, maxStackCount - matchingItemStack); + + const itemSpecs = source.heldItemManager.getItemSpecs(heldItemId); + if (!itemSpecs) { + return false; + } + source.heldItemManager.remove(heldItemId, countTaken); + target.heldItemManager.add(itemSpecs); + + if (source.heldItemManager.getStack(heldItemId) === 0 && itemLost) { + applyAbAttrs("PostItemLostAbAttr", { pokemon: source }); } - const removeOld = itemModifier.stackCount === 0; + if (source.isPlayer() !== target.isPlayer() && !ignoreUpdate) { + this.updateItems(source.isPlayer()); + } - if (!removeOld || !source || this.removeModifier(itemModifier, source.isEnemy())) { - const addModifier = () => { - if (!matchingModifier || this.removeModifier(matchingModifier, target.isEnemy())) { - if (target.isPlayer()) { - this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant); - if (source && itemLost) { - applyAbAttrs("PostItemLostAbAttr", { pokemon: source }); - } - return true; + const soundName = allHeldItems[heldItemId].soundName; + if (playSound && !this.sound.get(soundName)) { + this.playSound(soundName); + } + + return true; + } + + canTransferHeldItem(heldItemId: HeldItemId, source: Pokemon, target: Pokemon, transferQuantity = 1): boolean { + const cancelled = new BooleanHolder(false); + + if (source && source.isPlayer() !== target.isPlayer()) { + applyAbAttrs("BlockItemTheftAbAttr", { pokemon: source, cancelled }); + } + + if (cancelled.value) { + return false; + } + + const itemStack = source.heldItemManager.getStack(heldItemId); + const matchingItemStack = target.heldItemManager.getStack(heldItemId); + + const maxStackCount = allHeldItems[heldItemId].getMaxStackCount(); + if (matchingItemStack >= maxStackCount) { + return false; + } + const countTaken = Math.min(transferQuantity, itemStack, maxStackCount - matchingItemStack); + + return countTaken > 0; + } + + assignTrainerItemsFromConfiguration(config: TrainerItemConfiguration, isPlayer: boolean) { + const manager = isPlayer ? this.trainerItems : this.enemyTrainerItems; + config.forEach(item => { + const { entry, count } = item; + const actualCount = typeof count === "function" ? count() : count; + + if (typeof entry === "number") { + manager.add(entry, actualCount); + } + + if (isTrainerItemSpecs(entry)) { + manager.add(entry); + } + + if (isTrainerItemPool(entry)) { + for (let i = 1; i <= (actualCount ?? 1); i++) { + const newItem = getNewTrainerItemFromPool(entry, manager); + if (newItem) { + manager.add(newItem); } - this.addEnemyModifier(newItemModifier, ignoreUpdate, instant); - if (source && itemLost) { - applyAbAttrs("PostItemLostAbAttr", { pokemon: source }); - } - return true; } - return false; - }; - if (source && source.isPlayer() !== target.isPlayer() && !ignoreUpdate) { - this.updateModifiers(source.isPlayer(), instant); - addModifier(); - } else { - addModifier(); } - return true; - } - return false; - } - - canTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, transferQuantity = 1): boolean { - const mod = itemModifier.clone() as PokemonHeldItemModifier; - const source = mod.pokemonId ? mod.getPokemon() : null; - const cancelled = new BooleanHolder(false); - - if (source && source.isPlayer() !== target.isPlayer()) { - applyAbAttrs("BlockItemTheftAbAttr", { pokemon: source, cancelled }); - } - - if (cancelled.value) { - return false; - } - - const matchingModifier = this.findModifier( - m => m instanceof PokemonHeldItemModifier && m.matchType(mod) && m.pokemonId === target.id, - target.isPlayer(), - ) as PokemonHeldItemModifier; - - if (matchingModifier) { - const maxStackCount = matchingModifier.getMaxStackCount(); - if (matchingModifier.stackCount >= maxStackCount) { - return false; - } - const countTaken = Math.min(transferQuantity, mod.stackCount, maxStackCount - matchingModifier.stackCount); - mod.stackCount -= countTaken; - } else { - const countTaken = Math.min(transferQuantity, mod.stackCount); - mod.stackCount -= countTaken; - } - - const removeOld = mod.stackCount === 0; - - return !removeOld || !source || this.hasModifier(itemModifier, !source.isPlayer()); - } - - removePartyMemberModifiers(partyMemberIndex: number): Promise { - return new Promise(resolve => { - const pokemonId = this.getPlayerParty()[partyMemberIndex].id; - const modifiersToRemove = this.modifiers.filter( - m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === pokemonId, - ); - for (const m of modifiersToRemove) { - this.modifiers.splice(this.modifiers.indexOf(m), 1); - } - this.updateModifiers(); - resolve(); }); } - generateEnemyModifiers(heldModifiersConfigs?: HeldModifierConfig[][]): Promise { + assignTrainerItemsFromSaveData(saveData: TrainerItemSaveData, isPlayer: boolean) { + const manager = isPlayer ? this.trainerItems : this.enemyTrainerItems; + for (const item of saveData) { + manager.add(item); + } + } + + generateEnemyItems(heldItemConfigs?: HeldItemConfiguration[]): Promise { return new Promise(resolve => { if (this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { return resolve(); @@ -2921,26 +2857,13 @@ export default class BattleScene extends SceneBase { const party = this.getEnemyParty(); if (this.currentBattle.trainer) { - const modifiers = this.currentBattle.trainer.genModifiers(party); - for (const modifier of modifiers) { - this.addEnemyModifier(modifier, true, true); - } + const trainerItemConfig = this.currentBattle.trainer.genTrainerItems(party); + this.assignTrainerItemsFromConfiguration(trainerItemConfig, false); } party.forEach((enemyPokemon: EnemyPokemon, i: number) => { - if (heldModifiersConfigs && i < heldModifiersConfigs.length && heldModifiersConfigs[i]) { - for (const mt of heldModifiersConfigs[i]) { - let modifier: PokemonHeldItemModifier; - if (mt.modifier instanceof PokemonHeldItemModifierType) { - modifier = mt.modifier.newModifier(enemyPokemon); - } else { - modifier = mt.modifier as PokemonHeldItemModifier; - modifier.pokemonId = enemyPokemon.id; - } - modifier.stackCount = mt.stackCount ?? 1; - modifier.isTransferable = mt.isTransferable ?? modifier.isTransferable; - this.addEnemyModifier(modifier, true); - } + if (heldItemConfigs && i < heldItemConfigs.length && heldItemConfigs[i]) { + assignItemsFromConfiguration(heldItemConfigs[i], enemyPokemon); } else { const isBoss = enemyPokemon.isBoss() || @@ -2961,17 +2884,17 @@ export default class BattleScene extends SceneBase { if (isBoss) { count = Math.max(count, Math.floor(chances / 2)); } - getEnemyModifierTypesForWave( + assignEnemyHeldItemsForWave( difficultyWaveIndex, count, - [enemyPokemon], - this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, + enemyPokemon, + this.currentBattle.battleType === BattleType.TRAINER ? HeldItemPoolType.TRAINER : HeldItemPoolType.WILD, upgradeChance, - ).map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false)); + ); } return true; }); - this.updateModifiers(false); + this.updateItems(false); resolve(); }); } @@ -2979,74 +2902,41 @@ export default class BattleScene extends SceneBase { /** * Removes all modifiers from enemy pokemon of {@linkcode PersistentModifier} type */ - clearEnemyModifiers(): void { - const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PersistentModifier); - for (const m of modifiersToRemove) { - this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1); + clearEnemyItems(): void { + this.enemyTrainerItems.clearItems(); + for (const p of this.getEnemyParty()) { + p.heldItemManager.clearItems(); } - this.updateModifiers(false); - this.updateUIPositions(); - } - - /** - * Removes all modifiers from enemy pokemon of {@linkcode PokemonHeldItemModifier} type - * @param pokemon - If specified, only removes held items from that {@linkcode Pokemon} - */ - clearEnemyHeldItemModifiers(pokemon?: Pokemon): void { - const modifiersToRemove = this.enemyModifiers.filter( - m => m instanceof PokemonHeldItemModifier && (!pokemon || m.getPokemon() === pokemon), - ); - for (const m of modifiersToRemove) { - this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1); - } - this.updateModifiers(false); + this.updateItems(false); this.updateUIPositions(); } setModifiersVisible(visible: boolean) { - [this.modifierBar, this.enemyModifierBar].map(m => m.setVisible(visible)); + [this.itemBar, this.enemyItemBar].map(m => m.setVisible(visible)); } // TODO: Document this - updateModifiers(player = true, instant?: boolean): void { - const modifiers = player ? this.modifiers : (this.enemyModifiers as PersistentModifier[]); - for (let m = 0; m < modifiers.length; m++) { - const modifier = modifiers[m]; - if ( - modifier instanceof PokemonHeldItemModifier && - !this.getPokemonById((modifier as PokemonHeldItemModifier).pokemonId) - ) { - modifiers.splice(m--, 1); - } - if ( - modifier instanceof PokemonHeldItemModifier && - !isNullOrUndefined(modifier.getSpecies()) && - !this.getPokemonById(modifier.pokemonId)?.hasSpecies(modifier.getSpecies()!) - ) { - modifiers.splice(m--, 1); - } - } - for (const modifier of modifiers) { - if (modifier instanceof PersistentModifier) { - (modifier as PersistentModifier).virtualStackCount = 0; - } + updateItems(player = true, showHeldItems = true): void { + const trainerItems = player ? this.trainerItems : this.enemyTrainerItems; + + this.updateParty(player ? this.getPlayerParty() : this.getEnemyParty(), true); + + const pokemonA = player ? this.getPlayerParty()[0] : this.getEnemyParty()[0]; + + const bar = player ? this.itemBar : this.enemyItemBar; + + if (showHeldItems) { + bar.updateItems(trainerItems, pokemonA); + } else { + bar.updateItems(trainerItems); } - const modifiersClone = modifiers.slice(0); - for (const modifier of modifiersClone) { - if (!modifier.getStackCount()) { - modifiers.splice(modifiers.indexOf(modifier), 1); - } - } - - this.updatePartyForModifiers(player ? this.getPlayerParty() : this.getEnemyParty(), instant); - (player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers); if (!player) { this.updateUIPositions(); } } - updatePartyForModifiers(party: Pokemon[], instant?: boolean): Promise { + updateParty(party: Pokemon[], instant?: boolean): Promise { return new Promise(resolve => { Promise.allSettled( party.map(p => { @@ -3057,156 +2947,33 @@ export default class BattleScene extends SceneBase { }); } - hasModifier(modifier: PersistentModifier, enemy = false): boolean { - const modifiers = !enemy ? this.modifiers : this.enemyModifiers; - return modifiers.indexOf(modifier) > -1; - } + applyShuffledStatusTokens(pokemon: Pokemon) { + let tokens = [ + TrainerItemId.ENEMY_ATTACK_BURN_CHANCE, + TrainerItemId.ENEMY_ATTACK_PARALYZE_CHANCE, + TrainerItemId.ENEMY_ATTACK_POISON_CHANCE, + ].filter(t => this.enemyTrainerItems.hasItem(t)); - /** - * Removes a currently owned item. If the item is stacked, the entire item stack - * gets removed. This function does NOT apply in-battle effects, such as Unburden. - * If in-battle effects are needed, use {@linkcode Pokemon.loseHeldItem} instead. - * @param modifier The item to be removed. - * @param enemy `true` to remove an item owned by the enemy rather than the player; default `false`. - * @returns `true` if the item exists and was successfully removed, `false` otherwise - */ - removeModifier(modifier: PersistentModifier, enemy = false): boolean { - const modifiers = !enemy ? this.modifiers : this.enemyModifiers; - const modifierIndex = modifiers.indexOf(modifier); - if (modifierIndex > -1) { - modifiers.splice(modifierIndex, 1); - if (modifier instanceof PokemonFormChangeItemModifier) { - const pokemon = this.getPokemonById(modifier.pokemonId); - if (pokemon) { - modifier.apply(pokemon, false); - } - } - return true; - } - - return false; - } - - /** - * Get all of the 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` - * @returns the list of all modifiers that matched `modifierType`. - */ - getModifiers(modifierType: Constructor, player = true): T[] { - return (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType); - } - - /** - * 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 = 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 = true): PersistentModifier | undefined { - return (player ? this.modifiers : this.enemyModifiers).find(modifierFilter); - } - - /** - * Apply all modifiers that match `modifierType` in a random order - * @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( - modifierType: Constructor, - player = true, - ...args: Parameters - ): T[] { - let modifiers = (player ? this.modifiers : this.enemyModifiers).filter( - (m): m is T => m instanceof modifierType && m.shouldApply(...args), - ); this.executeWithSeedOffset( () => { - const shuffleModifiers = mods => { - if (mods.length < 1) { - return mods; + const shuffleTokens = toks => { + if (toks.length < 1) { + return toks; } - const rand = randSeedInt(mods.length); - return [mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand))]; + const rand = randSeedInt(toks.length); + return [toks[rand], ...shuffleTokens(toks.filter((_, i) => i !== rand))]; }; - modifiers = shuffleModifiers(modifiers); + tokens = shuffleTokens(tokens); }, this.currentBattle.turn << 4, this.waveSeed, ); - return this.applyModifiersInternal(modifiers, player, 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 = 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); - } - - /** 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)) { - console.log("Applied", modifier.type.name, !player ? "(enemy)" : ""); - appliedModifiers.push(modifier); - } + for (const t in tokens) { + (allTrainerItems[t] as EnemyAttackStatusEffectChanceTrainerItem).apply(this.enemyTrainerItems, { + pokemon: pokemon, + }); } - - return appliedModifiers; - } - - /** - * 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 = 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)) { - console.log("Applied", modifier.type.name, !player ? "(enemy)" : ""); - return modifier; - } - } - - return null; } triggerPokemonFormChange( @@ -3223,22 +2990,17 @@ export default class BattleScene extends SceneBase { let matchingFormChange: SpeciesFormChange | null; if (pokemon.species.speciesId === SpeciesId.NECROZMA && matchingFormChangeOpts.length > 1) { // Ultra Necrozma is changing its form back, so we need to figure out into which form it devolves. - const formChangeItemModifiers = ( - this.findModifiers( - m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id, - ) as PokemonFormChangeItemModifier[] - ) - .filter(m => m.active) - .map(m => m.formChangeItem); + const activeFormChangeItems = pokemon.heldItemManager.getActiveFormChangeItems(); - matchingFormChange = formChangeItemModifiers.includes(FormChangeItem.N_LUNARIZER) + matchingFormChange = activeFormChangeItems.includes(FormChangeItem.N_LUNARIZER) ? matchingFormChangeOpts[0] - : formChangeItemModifiers.includes(FormChangeItem.N_SOLARIZER) + : activeFormChangeItems.includes(FormChangeItem.N_SOLARIZER) ? matchingFormChangeOpts[1] : null; } else { matchingFormChange = matchingFormChangeOpts[0]; } + if (matchingFormChange) { let phase: Phase; if (pokemon.isPlayer() && !matchingFormChange.quiet) { @@ -3368,11 +3130,7 @@ export default class BattleScene extends SceneBase { pokemon.species.name, undefined, () => { - const finalBossMBH = getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier( - pokemon, - ) as TurnHeldItemTransferModifier; - finalBossMBH.setTransferrableFalse(); - this.addEnemyModifier(finalBossMBH, false, true); + pokemon.heldItemManager.add(HeldItemId.MINI_BLACK_HOLE); pokemon.generateAndPopulateMoveset(1); this.setFieldScale(0.75); this.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); @@ -3409,11 +3167,9 @@ export default class BattleScene extends SceneBase { ): void { const participantIds = pokemonParticipantIds ?? this.currentBattle.playerParticipantIds; const party = this.getPlayerParty(); - const expShareModifier = this.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier; - const expBalanceModifier = this.findModifier(m => m instanceof ExpBalanceModifier) as ExpBalanceModifier; - const multipleParticipantExpBonusModifier = this.findModifier( - m => m instanceof MultipleParticipantExpBonusModifier, - ) as MultipleParticipantExpBonusModifier; + const expShareStack = this.trainerItems.getStack(TrainerItemId.EXP_SHARE); + const expBalanceStack = this.trainerItems.getStack(TrainerItemId.EXP_BALANCE); + const ovalCharmStack = this.trainerItems.getStack(TrainerItemId.OVAL_CHARM); const nonFaintedPartyMembers = party.filter(p => p.hp); const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < this.getMaxExpLevel()); const partyMemberExp: number[] = []; @@ -3436,28 +3192,28 @@ export default class BattleScene extends SceneBase { const participated = participantIds.has(pId); if (participated && pokemonDefeated) { partyMember.addFriendship(FRIENDSHIP_GAIN_FROM_BATTLE); - const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier); - if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount()) { - machoBraceModifier.stackCount++; - this.updateModifiers(true, true); + const hasMachoBrace = partyMember.heldItemManager.hasItem(HeldItemId.MACHO_BRACE); + if (hasMachoBrace) { + partyMember.heldItemManager.add(HeldItemId.MACHO_BRACE); + this.updateItems(true); partyMember.updateInfo(); } } if (!expPartyMembers.includes(partyMember)) { continue; } - if (!participated && !expShareModifier) { + if (!participated && !expShareStack) { partyMemberExp.push(0); continue; } let expMultiplier = 0; if (participated) { expMultiplier += 1 / participantIds.size; - if (participantIds.size > 1 && multipleParticipantExpBonusModifier) { - expMultiplier += multipleParticipantExpBonusModifier.getStackCount() * 0.2; + if (participantIds.size > 1 && ovalCharmStack) { + expMultiplier += ovalCharmStack * 0.2; } - } else if (expShareModifier) { - expMultiplier += (expShareModifier.getStackCount() * 0.2) / participantIds.size; + } else if (expShareStack) { + expMultiplier += (expShareStack * 0.2) / participantIds.size; } if (partyMember.pokerus) { expMultiplier *= 1.5; @@ -3466,11 +3222,11 @@ export default class BattleScene extends SceneBase { expMultiplier = Overrides.XP_MULTIPLIER_OVERRIDE; } const pokemonExp = new NumberHolder(expValue * expMultiplier); - this.applyModifiers(PokemonExpBoosterModifier, true, partyMember, pokemonExp); + applyHeldItems(HELD_ITEM_EFFECT.EXP_BOOSTER, { pokemon: partyMember, expAmount: pokemonExp }); partyMemberExp.push(Math.floor(pokemonExp.value)); } - if (expBalanceModifier) { + if (expBalanceStack) { let totalLevel = 0; let totalExp = 0; expPartyMembers.forEach((expPartyMember, epm) => { @@ -3493,7 +3249,7 @@ export default class BattleScene extends SceneBase { partyMemberExp[pm] = Phaser.Math.Linear( partyMemberExp[pm], recipientExpPartyMemberIndexes.indexOf(pm) > -1 ? splitExp : 0, - 0.2 * expBalanceModifier.getStackCount(), + 0.2 * expBalanceStack, ); }); } diff --git a/src/battle.ts b/src/battle.ts index 49f5c39e7dd..7242c4b8650 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -13,7 +13,6 @@ import { import Trainer from "./field/trainer"; import { TrainerVariant } from "#enums/trainer-variant"; import type { GameMode } from "./game-mode"; -import { MoneyMultiplierModifier, type PokemonHeldItemModifier } from "./modifier/modifier"; import type { PokeballType } from "#enums/pokeball"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { SpeciesFormKey } from "#enums/species-form-key"; @@ -31,11 +30,13 @@ import i18next from "#app/plugins/i18n"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { BattleType } from "#enums/battle-type"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; +import type { HeldItemId } from "#enums/held-item-id"; import { BattlerIndex } from "#enums/battler-index"; +import { TRAINER_ITEM_EFFECT } from "./items/trainer-item"; export interface TurnCommand { command: Command; @@ -72,7 +73,7 @@ export default class Battle { public turnCommands: TurnCommands; public playerParticipantIds: Set = new Set(); public battleScore = 0; - public postBattleLoot: PokemonHeldItemModifier[] = []; + public postBattleLoot: HeldItemId[] = []; public escapeAttempts = 0; public lastMove: MoveId; public battleSeed: string = randomString(16, true); @@ -177,24 +178,12 @@ export default class Battle { } addPostBattleLoot(enemyPokemon: EnemyPokemon): void { - this.postBattleLoot.push( - ...globalScene - .findModifiers( - m => m.is("PokemonHeldItemModifier") && m.pokemonId === enemyPokemon.id && m.isTransferable, - false, - ) - .map(i => { - const ret = i as PokemonHeldItemModifier; - //@ts-expect-error - this is awful to fix/change - ret.pokemonId = null; - return ret; - }), - ); + this.postBattleLoot.push(...enemyPokemon.getHeldItems()); } pickUpScatteredMoney(): void { const moneyAmount = new NumberHolder(globalScene.currentBattle.moneyScattered); - globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); if (globalScene.arena.getTag(ArenaTagType.HAPPY_HOUR)) { moneyAmount.value *= 2; @@ -616,7 +605,7 @@ export const classicFixedBattles: FixedBattleConfigs = { ), ) .setCustomModifierRewards({ - guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], + guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.GREAT, RewardTier.GREAT], allowLuckUpgrades: false, }), [ClassicFixedBossWaves.EVIL_GRUNT_1]: new FixedBattleConfig() @@ -648,7 +637,7 @@ export const classicFixedBattles: FixedBattleConfigs = { ), ) .setCustomModifierRewards({ - guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], + guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.GREAT, RewardTier.GREAT], allowLuckUpgrades: false, }), [ClassicFixedBossWaves.EVIL_GRUNT_2]: new FixedBattleConfig() @@ -721,7 +710,7 @@ export const classicFixedBattles: FixedBattleConfigs = { ), ) .setCustomModifierRewards({ - guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA], + guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.ULTRA], allowLuckUpgrades: false, }), [ClassicFixedBossWaves.EVIL_GRUNT_4]: new FixedBattleConfig() @@ -784,11 +773,11 @@ export const classicFixedBattles: FixedBattleConfigs = { ) .setCustomModifierRewards({ guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.ULTRA, ], allowLuckUpgrades: false, }), @@ -803,11 +792,11 @@ export const classicFixedBattles: FixedBattleConfigs = { ) .setCustomModifierRewards({ guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, ], allowLuckUpgrades: false, }), @@ -830,12 +819,12 @@ export const classicFixedBattles: FixedBattleConfigs = { ) .setCustomModifierRewards({ guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.ULTRA, ], allowLuckUpgrades: false, }), @@ -934,12 +923,12 @@ export const classicFixedBattles: FixedBattleConfigs = { ) .setCustomModifierRewards({ guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.GREAT, - ModifierTier.GREAT, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.GREAT, + RewardTier.GREAT, ], allowLuckUpgrades: false, }), diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index fa4ee2286ff..8972c6e663d 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -22,7 +22,6 @@ import { Gender } from "#app/data/gender"; import { applyMoveAttrs } from "../moves/apply-attrs"; import { allMoves } from "../data-lists"; import { ArenaTagSide } from "#enums/arena-tag-side"; -import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { TerrainType } from "#app/data/terrain"; import { pokemonFormChanges } from "../pokemon-forms"; import { @@ -31,7 +30,6 @@ import { } from "../pokemon-forms/form-change-triggers"; import i18next from "i18next"; import { Command } from "#enums/command"; -import { BerryModifierType } from "#app/modifier/modifier-type"; import { getPokeballName } from "#app/data/pokeball"; import { BattleType } from "#enums/battle-type"; import { globalScene } from "#app/global-scene"; @@ -79,6 +77,9 @@ import type { BattlerIndex } from "#enums/battler-index"; import type Move from "#app/data/moves/move"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; import type { Constructor } from "#app/utils/common"; +import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id"; +import { allHeldItems } from "#app/data/data-lists"; +import { type BerryHeldItem, berryTypeToHeldItem } from "#app/items/held-items/berry"; import type { Localizable } from "#app/@types/locales"; import { applyAbAttrs } from "./apply-ab-attrs"; import type { Closed, Exact } from "#app/@types/type-helpers"; @@ -2045,7 +2046,7 @@ export abstract class PostAttackAbAttr extends AbAttr { export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { private stealCondition: PokemonAttackCondition | null; - private stolenItem?: PokemonHeldItemModifier; + private stolenItem?: HeldItemId; constructor(stealCondition?: PokemonAttackCondition) { super(); @@ -2064,11 +2065,11 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { hitResult < HitResult.NO_EFFECT && (!this.stealCondition || this.stealCondition(pokemon, opponent, move)) ) { - const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable); + const heldItems = opponent.heldItemManager.getTransferableHeldItems(); if (heldItems.length) { // Ensure that the stolen item in testing is the same as when the effect is applied this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; - if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) { + if (globalScene.canTransferHeldItem(this.stolenItem, opponent, pokemon)) { return true; } } @@ -2078,28 +2079,21 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { } override apply({ opponent, pokemon }: PostMoveInteractionAbAttrParams): void { - const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable); + const heldItems = opponent.heldItemManager.getTransferableHeldItems(); if (!this.stolenItem) { this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; } - if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) { + if (globalScene.tryTransferHeldItem(this.stolenItem, opponent, pokemon, false)) { globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:postAttackStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), defenderName: opponent.name, - stolenItemType: this.stolenItem.type.name, + stolenItemType: allHeldItems[this.stolenItem].name, }), ); } this.stolenItem = undefined; } - - getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id, - target.isPlayer(), - ) as PokemonHeldItemModifier[]; - } } export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { @@ -2190,7 +2184,7 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { private condition?: PokemonDefendCondition; - private stolenItem?: PokemonHeldItemModifier; + private stolenItem?: HeldItemId; constructor(condition?: PokemonDefendCondition) { super(); @@ -2200,10 +2194,10 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { override canApply({ simulated, pokemon, opponent, move, hitResult }: PostMoveInteractionAbAttrParams): boolean { if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, opponent, move))) { - const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable); + const heldItems = opponent.heldItemManager.getTransferableHeldItems(); if (heldItems.length) { this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; - if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) { + if (globalScene.canTransferHeldItem(this.stolenItem, opponent, pokemon)) { return true; } } @@ -2212,28 +2206,21 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { } override apply({ pokemon, opponent }: PostMoveInteractionAbAttrParams): void { - const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable); + const heldItems = opponent.heldItemManager.getTransferableHeldItems(); if (!this.stolenItem) { this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; } - if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) { + if (globalScene.tryTransferHeldItem(this.stolenItem, opponent, pokemon, false)) { globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:postDefendStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), attackerName: opponent.name, - stolenItemType: this.stolenItem.type.name, + stolenItemType: allHeldItems[this.stolenItem].name, }), ); } this.stolenItem = undefined; } - - getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id, - target.isPlayer(), - ) as PokemonHeldItemModifier[]; - } } /** @@ -4590,10 +4577,14 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { override canApply({ pokemon }: AbAttrBaseParams): boolean { // Ensure we have at least 1 recoverable berry (at least 1 berry in berriesEaten is not capped) const cappedBerries = new Set( - globalScene - .getModifiers(BerryModifier, pokemon.isPlayer()) - .filter(bm => bm.pokemonId === pokemon.id && bm.getCountUnderMax() < 1) - .map(bm => bm.berryType), + pokemon + .getHeldItems() + .filter( + bm => + isItemInCategory(bm, HeldItemCategoryId.BERRY) && + pokemon.heldItemManager.getStack(bm) < allHeldItems[bm].maxStackCount, + ) + .map(bm => (allHeldItems[bm] as BerryHeldItem).berryType), ); this.berriesUnderCap = pokemon.battleData.berriesEaten.filter(bt => !cappedBerries.has(bt)); @@ -4623,30 +4614,15 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { const randomIdx = randSeedInt(this.berriesUnderCap.length); const chosenBerryType = this.berriesUnderCap[randomIdx]; pokemon.battleData.berriesEaten.splice(randomIdx, 1); // Remove berry from memory - const chosenBerry = new BerryModifierType(chosenBerryType); + const chosenBerry = berryTypeToHeldItem[chosenBerryType]; - // Add the randomly chosen berry or update the existing one - const berryModifier = globalScene.findModifier( - m => m instanceof BerryModifier && m.berryType === chosenBerryType && m.pokemonId === pokemon.id, - pokemon.isPlayer(), - ) as BerryModifier | undefined; + pokemon.heldItemManager.add(chosenBerry); - if (berryModifier) { - berryModifier.stackCount++; - } else { - const newBerry = new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1); - if (pokemon.isPlayer()) { - globalScene.addModifier(newBerry); - } else { - globalScene.addEnemyModifier(newBerry); - } - } - - globalScene.updateModifiers(pokemon.isPlayer()); + globalScene.updateItems(pokemon.isPlayer()); globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:postTurnLootCreateEatenBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - berryName: chosenBerry.name, + berryName: allHeldItems[chosenBerry].name, }), ); return true; @@ -4680,8 +4656,7 @@ export class CudChewConsumeBerryAbAttr extends AbAttr { // This doesn't count as "eating" a berry (for unnerve/stuff cheeks/unburden) as no item is consumed. for (const berryType of pokemon.summonData.berriesEatenLast) { getBerryEffectFunc(berryType)(pokemon); - const bMod = new BerryModifier(new BerryModifierType(berryType), pokemon.id, berryType, 1); - globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(bMod)); // trigger message + globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(pokemon, berryType)); // trigger message } // uncomment to make cheek pouch work with cud chew @@ -5266,13 +5241,13 @@ export abstract class PostBattleAbAttr extends AbAttr { } export class PostBattleLootAbAttr extends PostBattleAbAttr { - private randItem?: PokemonHeldItemModifier; + private randItem?: HeldItemId; override canApply({ simulated, victory, pokemon }: PostBattleAbAttrParams): boolean { const postBattleLoot = globalScene.currentBattle.postBattleLoot; if (!simulated && postBattleLoot.length && victory) { this.randItem = randSeedItem(postBattleLoot); - return globalScene.canTransferHeldItemModifier(this.randItem, pokemon, 1); + return pokemon.heldItemManager.getStack(this.randItem) < allHeldItems[this.randItem].maxStackCount; } return false; } @@ -5283,12 +5258,12 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr { this.randItem = randSeedItem(postBattleLoot); } - if (globalScene.tryTransferHeldItemModifier(this.randItem, pokemon, true, 1, true, undefined, false)) { + if (pokemon.heldItemManager.add(this.randItem)) { postBattleLoot.splice(postBattleLoot.indexOf(this.randItem), 1); globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - itemName: this.randItem.type.name, + itemName: allHeldItems[this.randItem].name, }), ); } @@ -6187,8 +6162,6 @@ class ForceSwitchOutHelper { } if (!allyPokemon?.isActive(true)) { - globalScene.clearEnemyHeldItemModifiers(); - if (switchOutTarget.hp) { globalScene.phaseManager.pushNew("BattleEndPhase", false); @@ -6272,11 +6245,9 @@ class ForceSwitchOutHelper { * @returns The amount of health recovered by Shell Bell. */ function calculateShellBellRecovery(pokemon: Pokemon): number { - const shellBellModifier = pokemon.getHeldItems().find(m => m instanceof HitHealModifier); - if (shellBellModifier) { - return toDmgValue(pokemon.turnData.totalDamageDealt / 8) * shellBellModifier.stackCount; - } - return 0; + // Returns 0 if no Shell Bell is present + const shellBellStack = pokemon.heldItemManager.getStack(HeldItemId.SHELL_BELL); + return toDmgValue(pokemon.turnData.totalDamageDealt / 8) * shellBellStack; } export interface PostDamageAbAttrParams extends AbAttrBaseParams { diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index 8714d6c0c9a..fb3d0e119bf 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -11,11 +11,11 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { SpeciesFormKey } from "#enums/species-form-key"; import { TimeOfDay } from "#enums/time-of-day"; -import type { SpeciesStatBoosterItem, SpeciesStatBoosterModifierType } from "#app/modifier/modifier-type"; import { speciesStarterCosts } from "./starters"; import i18next from "i18next"; import { allMoves } from "#app/data/data-lists"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; +import { HeldItemId } from "#enums/held-item-id"; export enum SpeciesWildEvolutionDelay { NONE, @@ -99,7 +99,7 @@ const EvoCondKey = { SPECIES_CAUGHT: 12, GENDER: 13, NATURE: 14, - HELD_ITEM: 15, // Currently checks only for species stat booster items + HELD_ITEM: 15, } as const; type EvolutionConditionData = @@ -110,7 +110,7 @@ type EvolutionConditionData = {key: typeof EvoCondKey.GENDER, gender: Gender} | {key: typeof EvoCondKey.MOVE_TYPE | typeof EvoCondKey.PARTY_TYPE, pkmnType: PokemonType} | {key: typeof EvoCondKey.SPECIES_CAUGHT, speciesCaught: SpeciesId} | - {key: typeof EvoCondKey.HELD_ITEM, itemKey: SpeciesStatBoosterItem} | + {key: typeof EvoCondKey.HELD_ITEM, itemKey: HeldItemId} | {key: typeof EvoCondKey.NATURE, nature: Nature[]} | {key: typeof EvoCondKey.WEATHER, weather: WeatherType[]} | {key: typeof EvoCondKey.TYROGUE, move: TyrogueMove} | @@ -201,7 +201,7 @@ export class SpeciesEvolutionCondition { case EvoCondKey.SPECIES_CAUGHT: return !!globalScene.gameData.dexData[cond.speciesCaught].caughtAttr; case EvoCondKey.HELD_ITEM: - return pokemon.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === cond.itemKey) + return pokemon.heldItemManager.hasItem(cond.itemKey); } }); } @@ -1765,8 +1765,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.DUSKNOIR, 1, EvolutionItem.REAPER_CLOTH, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.CLAMPERL]: [ - new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: "DEEP_SEA_TOOTH"}, SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: "DEEP_SEA_SCALE"}, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: HeldItemId.DEEP_SEA_TOOTH}, SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, {key: EvoCondKey.HELD_ITEM, itemKey: HeldItemId.DEEP_SEA_SCALE}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.BOLDORE]: [ new SpeciesEvolution(SpeciesId.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) diff --git a/src/data/balance/tms.ts b/src/data/balance/tms.ts index e194dc4040c..b6317e8edd4 100644 --- a/src/data/balance/tms.ts +++ b/src/data/balance/tms.ts @@ -1,4 +1,4 @@ -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; @@ -68591,324 +68591,324 @@ function transposeTmSpecies(): SpeciesTmMoves { export const speciesTmMoves: SpeciesTmMoves = transposeTmSpecies(); interface TmPoolTiers { - [key: number]: ModifierTier + [key: number]: RewardTier } export const tmPoolTiers: TmPoolTiers = { - [MoveId.MEGA_PUNCH]: ModifierTier.GREAT, - [MoveId.PAY_DAY]: ModifierTier.ULTRA, - [MoveId.FIRE_PUNCH]: ModifierTier.GREAT, - [MoveId.ICE_PUNCH]: ModifierTier.GREAT, - [MoveId.THUNDER_PUNCH]: ModifierTier.GREAT, - [MoveId.SWORDS_DANCE]: ModifierTier.COMMON, - [MoveId.CUT]: ModifierTier.COMMON, - [MoveId.FLY]: ModifierTier.COMMON, - [MoveId.MEGA_KICK]: ModifierTier.GREAT, - [MoveId.BODY_SLAM]: ModifierTier.GREAT, - [MoveId.TAKE_DOWN]: ModifierTier.GREAT, - [MoveId.DOUBLE_EDGE]: ModifierTier.ULTRA, - [MoveId.PIN_MISSILE]: ModifierTier.COMMON, - [MoveId.ROAR]: ModifierTier.COMMON, - [MoveId.FLAMETHROWER]: ModifierTier.ULTRA, - [MoveId.HYDRO_PUMP]: ModifierTier.ULTRA, - [MoveId.SURF]: ModifierTier.ULTRA, - [MoveId.ICE_BEAM]: ModifierTier.ULTRA, - [MoveId.BLIZZARD]: ModifierTier.ULTRA, - [MoveId.PSYBEAM]: ModifierTier.GREAT, - [MoveId.HYPER_BEAM]: ModifierTier.ULTRA, - [MoveId.LOW_KICK]: ModifierTier.COMMON, - [MoveId.COUNTER]: ModifierTier.COMMON, - [MoveId.STRENGTH]: ModifierTier.GREAT, - [MoveId.SOLAR_BEAM]: ModifierTier.ULTRA, - [MoveId.FIRE_SPIN]: ModifierTier.COMMON, - [MoveId.THUNDERBOLT]: ModifierTier.ULTRA, - [MoveId.THUNDER_WAVE]: ModifierTier.COMMON, - [MoveId.THUNDER]: ModifierTier.ULTRA, - [MoveId.EARTHQUAKE]: ModifierTier.ULTRA, - [MoveId.DIG]: ModifierTier.GREAT, - [MoveId.TOXIC]: ModifierTier.GREAT, - [MoveId.PSYCHIC]: ModifierTier.ULTRA, - [MoveId.AGILITY]: ModifierTier.COMMON, - [MoveId.NIGHT_SHADE]: ModifierTier.COMMON, - [MoveId.SCREECH]: ModifierTier.COMMON, - [MoveId.DOUBLE_TEAM]: ModifierTier.COMMON, - [MoveId.CONFUSE_RAY]: ModifierTier.COMMON, - [MoveId.LIGHT_SCREEN]: ModifierTier.COMMON, - [MoveId.HAZE]: ModifierTier.COMMON, - [MoveId.REFLECT]: ModifierTier.COMMON, - [MoveId.FOCUS_ENERGY]: ModifierTier.COMMON, - [MoveId.METRONOME]: ModifierTier.COMMON, - [MoveId.SELF_DESTRUCT]: ModifierTier.GREAT, - [MoveId.FIRE_BLAST]: ModifierTier.ULTRA, - [MoveId.WATERFALL]: ModifierTier.GREAT, - [MoveId.SWIFT]: ModifierTier.COMMON, - [MoveId.AMNESIA]: ModifierTier.COMMON, - [MoveId.DREAM_EATER]: ModifierTier.GREAT, - [MoveId.LEECH_LIFE]: ModifierTier.ULTRA, - [MoveId.FLASH]: ModifierTier.COMMON, - [MoveId.EXPLOSION]: ModifierTier.GREAT, - [MoveId.REST]: ModifierTier.COMMON, - [MoveId.ROCK_SLIDE]: ModifierTier.GREAT, - [MoveId.TRI_ATTACK]: ModifierTier.ULTRA, - [MoveId.SUPER_FANG]: ModifierTier.COMMON, - [MoveId.SUBSTITUTE]: ModifierTier.COMMON, - [MoveId.THIEF]: ModifierTier.GREAT, - [MoveId.SNORE]: ModifierTier.COMMON, - [MoveId.CURSE]: ModifierTier.COMMON, - [MoveId.REVERSAL]: ModifierTier.COMMON, - [MoveId.SPITE]: ModifierTier.COMMON, - [MoveId.PROTECT]: ModifierTier.COMMON, - [MoveId.SCARY_FACE]: ModifierTier.COMMON, - [MoveId.SLUDGE_BOMB]: ModifierTier.GREAT, - [MoveId.MUD_SLAP]: ModifierTier.COMMON, - [MoveId.SPIKES]: ModifierTier.COMMON, - [MoveId.ICY_WIND]: ModifierTier.GREAT, - [MoveId.OUTRAGE]: ModifierTier.ULTRA, - [MoveId.SANDSTORM]: ModifierTier.COMMON, - [MoveId.GIGA_DRAIN]: ModifierTier.ULTRA, - [MoveId.ENDURE]: ModifierTier.COMMON, - [MoveId.CHARM]: ModifierTier.COMMON, - [MoveId.FALSE_SWIPE]: ModifierTier.COMMON, - [MoveId.SWAGGER]: ModifierTier.COMMON, - [MoveId.STEEL_WING]: ModifierTier.GREAT, - [MoveId.ATTRACT]: ModifierTier.COMMON, - [MoveId.SLEEP_TALK]: ModifierTier.COMMON, - [MoveId.HEAL_BELL]: ModifierTier.COMMON, - [MoveId.RETURN]: ModifierTier.ULTRA, - [MoveId.FRUSTRATION]: ModifierTier.COMMON, - [MoveId.SAFEGUARD]: ModifierTier.COMMON, - [MoveId.PAIN_SPLIT]: ModifierTier.COMMON, - [MoveId.MEGAHORN]: ModifierTier.ULTRA, - [MoveId.BATON_PASS]: ModifierTier.COMMON, - [MoveId.ENCORE]: ModifierTier.COMMON, - [MoveId.IRON_TAIL]: ModifierTier.GREAT, - [MoveId.METAL_CLAW]: ModifierTier.COMMON, - [MoveId.SYNTHESIS]: ModifierTier.GREAT, - [MoveId.HIDDEN_POWER]: ModifierTier.GREAT, - [MoveId.RAIN_DANCE]: ModifierTier.COMMON, - [MoveId.SUNNY_DAY]: ModifierTier.COMMON, - [MoveId.CRUNCH]: ModifierTier.GREAT, - [MoveId.PSYCH_UP]: ModifierTier.COMMON, - [MoveId.SHADOW_BALL]: ModifierTier.ULTRA, - [MoveId.FUTURE_SIGHT]: ModifierTier.GREAT, - [MoveId.ROCK_SMASH]: ModifierTier.COMMON, - [MoveId.WHIRLPOOL]: ModifierTier.COMMON, - [MoveId.BEAT_UP]: ModifierTier.COMMON, - [MoveId.UPROAR]: ModifierTier.GREAT, - [MoveId.HEAT_WAVE]: ModifierTier.ULTRA, - [MoveId.HAIL]: ModifierTier.COMMON, - [MoveId.TORMENT]: ModifierTier.COMMON, - [MoveId.WILL_O_WISP]: ModifierTier.COMMON, - [MoveId.FACADE]: ModifierTier.GREAT, - [MoveId.FOCUS_PUNCH]: ModifierTier.COMMON, - [MoveId.NATURE_POWER]: ModifierTier.COMMON, - [MoveId.CHARGE]: ModifierTier.COMMON, - [MoveId.TAUNT]: ModifierTier.COMMON, - [MoveId.HELPING_HAND]: ModifierTier.COMMON, - [MoveId.TRICK]: ModifierTier.COMMON, - [MoveId.SUPERPOWER]: ModifierTier.ULTRA, - [MoveId.RECYCLE]: ModifierTier.COMMON, - [MoveId.REVENGE]: ModifierTier.GREAT, - [MoveId.BRICK_BREAK]: ModifierTier.GREAT, - [MoveId.KNOCK_OFF]: ModifierTier.GREAT, - [MoveId.ENDEAVOR]: ModifierTier.COMMON, - [MoveId.SKILL_SWAP]: ModifierTier.COMMON, - [MoveId.IMPRISON]: ModifierTier.COMMON, - [MoveId.SECRET_POWER]: ModifierTier.COMMON, - [MoveId.DIVE]: ModifierTier.GREAT, - [MoveId.FEATHER_DANCE]: ModifierTier.COMMON, - [MoveId.BLAZE_KICK]: ModifierTier.GREAT, - [MoveId.HYPER_VOICE]: ModifierTier.ULTRA, - [MoveId.BLAST_BURN]: ModifierTier.ULTRA, - [MoveId.HYDRO_CANNON]: ModifierTier.ULTRA, - [MoveId.WEATHER_BALL]: ModifierTier.COMMON, - [MoveId.FAKE_TEARS]: ModifierTier.COMMON, - [MoveId.AIR_CUTTER]: ModifierTier.GREAT, - [MoveId.OVERHEAT]: ModifierTier.ULTRA, - [MoveId.ROCK_TOMB]: ModifierTier.GREAT, - [MoveId.METAL_SOUND]: ModifierTier.COMMON, - [MoveId.COSMIC_POWER]: ModifierTier.COMMON, - [MoveId.SIGNAL_BEAM]: ModifierTier.GREAT, - [MoveId.SAND_TOMB]: ModifierTier.COMMON, - [MoveId.MUDDY_WATER]: ModifierTier.GREAT, - [MoveId.BULLET_SEED]: ModifierTier.GREAT, - [MoveId.AERIAL_ACE]: ModifierTier.GREAT, - [MoveId.ICICLE_SPEAR]: ModifierTier.GREAT, - [MoveId.IRON_DEFENSE]: ModifierTier.GREAT, - [MoveId.DRAGON_CLAW]: ModifierTier.ULTRA, - [MoveId.FRENZY_PLANT]: ModifierTier.ULTRA, - [MoveId.BULK_UP]: ModifierTier.COMMON, - [MoveId.BOUNCE]: ModifierTier.GREAT, - [MoveId.MUD_SHOT]: ModifierTier.GREAT, - [MoveId.POISON_TAIL]: ModifierTier.GREAT, - [MoveId.COVET]: ModifierTier.GREAT, - [MoveId.MAGICAL_LEAF]: ModifierTier.GREAT, - [MoveId.CALM_MIND]: ModifierTier.GREAT, - [MoveId.LEAF_BLADE]: ModifierTier.ULTRA, - [MoveId.DRAGON_DANCE]: ModifierTier.GREAT, - [MoveId.ROCK_BLAST]: ModifierTier.GREAT, - [MoveId.WATER_PULSE]: ModifierTier.GREAT, - [MoveId.ROOST]: ModifierTier.GREAT, - [MoveId.GRAVITY]: ModifierTier.COMMON, - [MoveId.GYRO_BALL]: ModifierTier.COMMON, - [MoveId.BRINE]: ModifierTier.GREAT, - [MoveId.PLUCK]: ModifierTier.GREAT, - [MoveId.TAILWIND]: ModifierTier.GREAT, - [MoveId.U_TURN]: ModifierTier.GREAT, - [MoveId.CLOSE_COMBAT]: ModifierTier.ULTRA, - [MoveId.PAYBACK]: ModifierTier.COMMON, - [MoveId.ASSURANCE]: ModifierTier.COMMON, - [MoveId.EMBARGO]: ModifierTier.COMMON, - [MoveId.FLING]: ModifierTier.COMMON, - [MoveId.GASTRO_ACID]: ModifierTier.GREAT, - [MoveId.POWER_SWAP]: ModifierTier.COMMON, - [MoveId.GUARD_SWAP]: ModifierTier.COMMON, - [MoveId.WORRY_SEED]: ModifierTier.GREAT, - [MoveId.TOXIC_SPIKES]: ModifierTier.GREAT, - [MoveId.FLARE_BLITZ]: ModifierTier.ULTRA, - [MoveId.AURA_SPHERE]: ModifierTier.GREAT, - [MoveId.ROCK_POLISH]: ModifierTier.COMMON, - [MoveId.POISON_JAB]: ModifierTier.GREAT, - [MoveId.DARK_PULSE]: ModifierTier.GREAT, - [MoveId.AQUA_TAIL]: ModifierTier.GREAT, - [MoveId.SEED_BOMB]: ModifierTier.GREAT, - [MoveId.AIR_SLASH]: ModifierTier.GREAT, - [MoveId.X_SCISSOR]: ModifierTier.GREAT, - [MoveId.BUG_BUZZ]: ModifierTier.GREAT, - [MoveId.DRAGON_PULSE]: ModifierTier.GREAT, - [MoveId.POWER_GEM]: ModifierTier.GREAT, - [MoveId.DRAIN_PUNCH]: ModifierTier.GREAT, - [MoveId.VACUUM_WAVE]: ModifierTier.COMMON, - [MoveId.FOCUS_BLAST]: ModifierTier.GREAT, - [MoveId.ENERGY_BALL]: ModifierTier.GREAT, - [MoveId.BRAVE_BIRD]: ModifierTier.ULTRA, - [MoveId.EARTH_POWER]: ModifierTier.ULTRA, - [MoveId.GIGA_IMPACT]: ModifierTier.GREAT, - [MoveId.NASTY_PLOT]: ModifierTier.COMMON, - [MoveId.AVALANCHE]: ModifierTier.GREAT, - [MoveId.SHADOW_CLAW]: ModifierTier.GREAT, - [MoveId.THUNDER_FANG]: ModifierTier.GREAT, - [MoveId.ICE_FANG]: ModifierTier.GREAT, - [MoveId.FIRE_FANG]: ModifierTier.GREAT, - [MoveId.PSYCHO_CUT]: ModifierTier.GREAT, - [MoveId.ZEN_HEADBUTT]: ModifierTier.GREAT, - [MoveId.FLASH_CANNON]: ModifierTier.GREAT, - [MoveId.ROCK_CLIMB]: ModifierTier.GREAT, - [MoveId.DEFOG]: ModifierTier.COMMON, - [MoveId.TRICK_ROOM]: ModifierTier.COMMON, - [MoveId.DRACO_METEOR]: ModifierTier.ULTRA, - [MoveId.LEAF_STORM]: ModifierTier.ULTRA, - [MoveId.POWER_WHIP]: ModifierTier.ULTRA, - [MoveId.CROSS_POISON]: ModifierTier.GREAT, - [MoveId.GUNK_SHOT]: ModifierTier.ULTRA, - [MoveId.IRON_HEAD]: ModifierTier.GREAT, - [MoveId.STONE_EDGE]: ModifierTier.ULTRA, - [MoveId.STEALTH_ROCK]: ModifierTier.COMMON, - [MoveId.GRASS_KNOT]: ModifierTier.ULTRA, - [MoveId.BUG_BITE]: ModifierTier.GREAT, - [MoveId.CHARGE_BEAM]: ModifierTier.GREAT, - [MoveId.HONE_CLAWS]: ModifierTier.COMMON, - [MoveId.WONDER_ROOM]: ModifierTier.COMMON, - [MoveId.PSYSHOCK]: ModifierTier.GREAT, - [MoveId.VENOSHOCK]: ModifierTier.GREAT, - [MoveId.MAGIC_ROOM]: ModifierTier.COMMON, - [MoveId.SMACK_DOWN]: ModifierTier.COMMON, - [MoveId.SLUDGE_WAVE]: ModifierTier.GREAT, - [MoveId.HEAVY_SLAM]: ModifierTier.GREAT, - [MoveId.ELECTRO_BALL]: ModifierTier.GREAT, - [MoveId.FLAME_CHARGE]: ModifierTier.GREAT, - [MoveId.LOW_SWEEP]: ModifierTier.GREAT, - [MoveId.ACID_SPRAY]: ModifierTier.COMMON, - [MoveId.FOUL_PLAY]: ModifierTier.ULTRA, - [MoveId.ROUND]: ModifierTier.COMMON, - [MoveId.ECHOED_VOICE]: ModifierTier.COMMON, - [MoveId.STORED_POWER]: ModifierTier.COMMON, - [MoveId.ALLY_SWITCH]: ModifierTier.COMMON, - [MoveId.SCALD]: ModifierTier.GREAT, - [MoveId.HEX]: ModifierTier.GREAT, - [MoveId.SKY_DROP]: ModifierTier.GREAT, - [MoveId.INCINERATE]: ModifierTier.GREAT, - [MoveId.QUASH]: ModifierTier.COMMON, - [MoveId.ACROBATICS]: ModifierTier.GREAT, - [MoveId.RETALIATE]: ModifierTier.GREAT, - [MoveId.WATER_PLEDGE]: ModifierTier.GREAT, - [MoveId.FIRE_PLEDGE]: ModifierTier.GREAT, - [MoveId.GRASS_PLEDGE]: ModifierTier.GREAT, - [MoveId.VOLT_SWITCH]: ModifierTier.GREAT, - [MoveId.STRUGGLE_BUG]: ModifierTier.COMMON, - [MoveId.BULLDOZE]: ModifierTier.GREAT, - [MoveId.FROST_BREATH]: ModifierTier.GREAT, - [MoveId.DRAGON_TAIL]: ModifierTier.GREAT, - [MoveId.WORK_UP]: ModifierTier.COMMON, - [MoveId.ELECTROWEB]: ModifierTier.GREAT, - [MoveId.WILD_CHARGE]: ModifierTier.GREAT, - [MoveId.DRILL_RUN]: ModifierTier.GREAT, - [MoveId.RAZOR_SHELL]: ModifierTier.GREAT, - [MoveId.HEAT_CRASH]: ModifierTier.GREAT, - [MoveId.TAIL_SLAP]: ModifierTier.GREAT, - [MoveId.HURRICANE]: ModifierTier.ULTRA, - [MoveId.SNARL]: ModifierTier.COMMON, - [MoveId.PHANTOM_FORCE]: ModifierTier.ULTRA, - [MoveId.PETAL_BLIZZARD]: ModifierTier.GREAT, - [MoveId.DISARMING_VOICE]: ModifierTier.GREAT, - [MoveId.DRAINING_KISS]: ModifierTier.GREAT, - [MoveId.GRASSY_TERRAIN]: ModifierTier.COMMON, - [MoveId.MISTY_TERRAIN]: ModifierTier.COMMON, - [MoveId.PLAY_ROUGH]: ModifierTier.GREAT, - [MoveId.CONFIDE]: ModifierTier.COMMON, - [MoveId.MYSTICAL_FIRE]: ModifierTier.GREAT, - [MoveId.EERIE_IMPULSE]: ModifierTier.COMMON, - [MoveId.VENOM_DRENCH]: ModifierTier.COMMON, - [MoveId.ELECTRIC_TERRAIN]: ModifierTier.COMMON, - [MoveId.DAZZLING_GLEAM]: ModifierTier.ULTRA, - [MoveId.INFESTATION]: ModifierTier.COMMON, - [MoveId.POWER_UP_PUNCH]: ModifierTier.GREAT, - [MoveId.DARKEST_LARIAT]: ModifierTier.GREAT, - [MoveId.HIGH_HORSEPOWER]: ModifierTier.ULTRA, - [MoveId.SOLAR_BLADE]: ModifierTier.GREAT, - [MoveId.THROAT_CHOP]: ModifierTier.GREAT, - [MoveId.POLLEN_PUFF]: ModifierTier.GREAT, - [MoveId.PSYCHIC_TERRAIN]: ModifierTier.COMMON, - [MoveId.LUNGE]: ModifierTier.GREAT, - [MoveId.SPEED_SWAP]: ModifierTier.COMMON, - [MoveId.SMART_STRIKE]: ModifierTier.GREAT, - [MoveId.BRUTAL_SWING]: ModifierTier.GREAT, - [MoveId.AURORA_VEIL]: ModifierTier.COMMON, - [MoveId.PSYCHIC_FANGS]: ModifierTier.GREAT, - [MoveId.STOMPING_TANTRUM]: ModifierTier.GREAT, - [MoveId.LIQUIDATION]: ModifierTier.ULTRA, - [MoveId.BODY_PRESS]: ModifierTier.ULTRA, - [MoveId.BREAKING_SWIPE]: ModifierTier.GREAT, - [MoveId.STEEL_BEAM]: ModifierTier.ULTRA, - [MoveId.EXPANDING_FORCE]: ModifierTier.GREAT, - [MoveId.STEEL_ROLLER]: ModifierTier.COMMON, - [MoveId.SCALE_SHOT]: ModifierTier.ULTRA, - [MoveId.METEOR_BEAM]: ModifierTier.GREAT, - [MoveId.MISTY_EXPLOSION]: ModifierTier.COMMON, - [MoveId.GRASSY_GLIDE]: ModifierTier.COMMON, - [MoveId.RISING_VOLTAGE]: ModifierTier.COMMON, - [MoveId.TERRAIN_PULSE]: ModifierTier.COMMON, - [MoveId.SKITTER_SMACK]: ModifierTier.GREAT, - [MoveId.BURNING_JEALOUSY]: ModifierTier.GREAT, - [MoveId.LASH_OUT]: ModifierTier.GREAT, - [MoveId.POLTERGEIST]: ModifierTier.ULTRA, - [MoveId.CORROSIVE_GAS]: ModifierTier.COMMON, - [MoveId.COACHING]: ModifierTier.COMMON, - [MoveId.FLIP_TURN]: ModifierTier.COMMON, - [MoveId.TRIPLE_AXEL]: ModifierTier.COMMON, - [MoveId.DUAL_WINGBEAT]: ModifierTier.COMMON, - [MoveId.SCORCHING_SANDS]: ModifierTier.GREAT, - [MoveId.TERA_BLAST]: ModifierTier.GREAT, - [MoveId.ICE_SPINNER]: ModifierTier.GREAT, - [MoveId.SNOWSCAPE]: ModifierTier.COMMON, - [MoveId.POUNCE]: ModifierTier.COMMON, - [MoveId.TRAILBLAZE]: ModifierTier.COMMON, - [MoveId.CHILLING_WATER]: ModifierTier.COMMON, - [MoveId.HARD_PRESS]: ModifierTier.GREAT, - [MoveId.DRAGON_CHEER]: ModifierTier.COMMON, - [MoveId.ALLURING_VOICE]: ModifierTier.GREAT, - [MoveId.TEMPER_FLARE]: ModifierTier.GREAT, - [MoveId.SUPERCELL_SLAM]: ModifierTier.GREAT, - [MoveId.PSYCHIC_NOISE]: ModifierTier.GREAT, - [MoveId.UPPER_HAND]: ModifierTier.COMMON, + [MoveId.MEGA_PUNCH]: RewardTier.GREAT, + [MoveId.PAY_DAY]: RewardTier.ULTRA, + [MoveId.FIRE_PUNCH]: RewardTier.GREAT, + [MoveId.ICE_PUNCH]: RewardTier.GREAT, + [MoveId.THUNDER_PUNCH]: RewardTier.GREAT, + [MoveId.SWORDS_DANCE]: RewardTier.COMMON, + [MoveId.CUT]: RewardTier.COMMON, + [MoveId.FLY]: RewardTier.COMMON, + [MoveId.MEGA_KICK]: RewardTier.GREAT, + [MoveId.BODY_SLAM]: RewardTier.GREAT, + [MoveId.TAKE_DOWN]: RewardTier.GREAT, + [MoveId.DOUBLE_EDGE]: RewardTier.ULTRA, + [MoveId.PIN_MISSILE]: RewardTier.COMMON, + [MoveId.ROAR]: RewardTier.COMMON, + [MoveId.FLAMETHROWER]: RewardTier.ULTRA, + [MoveId.HYDRO_PUMP]: RewardTier.ULTRA, + [MoveId.SURF]: RewardTier.ULTRA, + [MoveId.ICE_BEAM]: RewardTier.ULTRA, + [MoveId.BLIZZARD]: RewardTier.ULTRA, + [MoveId.PSYBEAM]: RewardTier.GREAT, + [MoveId.HYPER_BEAM]: RewardTier.ULTRA, + [MoveId.LOW_KICK]: RewardTier.COMMON, + [MoveId.COUNTER]: RewardTier.COMMON, + [MoveId.STRENGTH]: RewardTier.GREAT, + [MoveId.SOLAR_BEAM]: RewardTier.ULTRA, + [MoveId.FIRE_SPIN]: RewardTier.COMMON, + [MoveId.THUNDERBOLT]: RewardTier.ULTRA, + [MoveId.THUNDER_WAVE]: RewardTier.COMMON, + [MoveId.THUNDER]: RewardTier.ULTRA, + [MoveId.EARTHQUAKE]: RewardTier.ULTRA, + [MoveId.DIG]: RewardTier.GREAT, + [MoveId.TOXIC]: RewardTier.GREAT, + [MoveId.PSYCHIC]: RewardTier.ULTRA, + [MoveId.AGILITY]: RewardTier.COMMON, + [MoveId.NIGHT_SHADE]: RewardTier.COMMON, + [MoveId.SCREECH]: RewardTier.COMMON, + [MoveId.DOUBLE_TEAM]: RewardTier.COMMON, + [MoveId.CONFUSE_RAY]: RewardTier.COMMON, + [MoveId.LIGHT_SCREEN]: RewardTier.COMMON, + [MoveId.HAZE]: RewardTier.COMMON, + [MoveId.REFLECT]: RewardTier.COMMON, + [MoveId.FOCUS_ENERGY]: RewardTier.COMMON, + [MoveId.METRONOME]: RewardTier.COMMON, + [MoveId.SELF_DESTRUCT]: RewardTier.GREAT, + [MoveId.FIRE_BLAST]: RewardTier.ULTRA, + [MoveId.WATERFALL]: RewardTier.GREAT, + [MoveId.SWIFT]: RewardTier.COMMON, + [MoveId.AMNESIA]: RewardTier.COMMON, + [MoveId.DREAM_EATER]: RewardTier.GREAT, + [MoveId.LEECH_LIFE]: RewardTier.ULTRA, + [MoveId.FLASH]: RewardTier.COMMON, + [MoveId.EXPLOSION]: RewardTier.GREAT, + [MoveId.REST]: RewardTier.COMMON, + [MoveId.ROCK_SLIDE]: RewardTier.GREAT, + [MoveId.TRI_ATTACK]: RewardTier.ULTRA, + [MoveId.SUPER_FANG]: RewardTier.COMMON, + [MoveId.SUBSTITUTE]: RewardTier.COMMON, + [MoveId.THIEF]: RewardTier.GREAT, + [MoveId.SNORE]: RewardTier.COMMON, + [MoveId.CURSE]: RewardTier.COMMON, + [MoveId.REVERSAL]: RewardTier.COMMON, + [MoveId.SPITE]: RewardTier.COMMON, + [MoveId.PROTECT]: RewardTier.COMMON, + [MoveId.SCARY_FACE]: RewardTier.COMMON, + [MoveId.SLUDGE_BOMB]: RewardTier.GREAT, + [MoveId.MUD_SLAP]: RewardTier.COMMON, + [MoveId.SPIKES]: RewardTier.COMMON, + [MoveId.ICY_WIND]: RewardTier.GREAT, + [MoveId.OUTRAGE]: RewardTier.ULTRA, + [MoveId.SANDSTORM]: RewardTier.COMMON, + [MoveId.GIGA_DRAIN]: RewardTier.ULTRA, + [MoveId.ENDURE]: RewardTier.COMMON, + [MoveId.CHARM]: RewardTier.COMMON, + [MoveId.FALSE_SWIPE]: RewardTier.COMMON, + [MoveId.SWAGGER]: RewardTier.COMMON, + [MoveId.STEEL_WING]: RewardTier.GREAT, + [MoveId.ATTRACT]: RewardTier.COMMON, + [MoveId.SLEEP_TALK]: RewardTier.COMMON, + [MoveId.HEAL_BELL]: RewardTier.COMMON, + [MoveId.RETURN]: RewardTier.ULTRA, + [MoveId.FRUSTRATION]: RewardTier.COMMON, + [MoveId.SAFEGUARD]: RewardTier.COMMON, + [MoveId.PAIN_SPLIT]: RewardTier.COMMON, + [MoveId.MEGAHORN]: RewardTier.ULTRA, + [MoveId.BATON_PASS]: RewardTier.COMMON, + [MoveId.ENCORE]: RewardTier.COMMON, + [MoveId.IRON_TAIL]: RewardTier.GREAT, + [MoveId.METAL_CLAW]: RewardTier.COMMON, + [MoveId.SYNTHESIS]: RewardTier.GREAT, + [MoveId.HIDDEN_POWER]: RewardTier.GREAT, + [MoveId.RAIN_DANCE]: RewardTier.COMMON, + [MoveId.SUNNY_DAY]: RewardTier.COMMON, + [MoveId.CRUNCH]: RewardTier.GREAT, + [MoveId.PSYCH_UP]: RewardTier.COMMON, + [MoveId.SHADOW_BALL]: RewardTier.ULTRA, + [MoveId.FUTURE_SIGHT]: RewardTier.GREAT, + [MoveId.ROCK_SMASH]: RewardTier.COMMON, + [MoveId.WHIRLPOOL]: RewardTier.COMMON, + [MoveId.BEAT_UP]: RewardTier.COMMON, + [MoveId.UPROAR]: RewardTier.GREAT, + [MoveId.HEAT_WAVE]: RewardTier.ULTRA, + [MoveId.HAIL]: RewardTier.COMMON, + [MoveId.TORMENT]: RewardTier.COMMON, + [MoveId.WILL_O_WISP]: RewardTier.COMMON, + [MoveId.FACADE]: RewardTier.GREAT, + [MoveId.FOCUS_PUNCH]: RewardTier.COMMON, + [MoveId.NATURE_POWER]: RewardTier.COMMON, + [MoveId.CHARGE]: RewardTier.COMMON, + [MoveId.TAUNT]: RewardTier.COMMON, + [MoveId.HELPING_HAND]: RewardTier.COMMON, + [MoveId.TRICK]: RewardTier.COMMON, + [MoveId.SUPERPOWER]: RewardTier.ULTRA, + [MoveId.RECYCLE]: RewardTier.COMMON, + [MoveId.REVENGE]: RewardTier.GREAT, + [MoveId.BRICK_BREAK]: RewardTier.GREAT, + [MoveId.KNOCK_OFF]: RewardTier.GREAT, + [MoveId.ENDEAVOR]: RewardTier.COMMON, + [MoveId.SKILL_SWAP]: RewardTier.COMMON, + [MoveId.IMPRISON]: RewardTier.COMMON, + [MoveId.SECRET_POWER]: RewardTier.COMMON, + [MoveId.DIVE]: RewardTier.GREAT, + [MoveId.FEATHER_DANCE]: RewardTier.COMMON, + [MoveId.BLAZE_KICK]: RewardTier.GREAT, + [MoveId.HYPER_VOICE]: RewardTier.ULTRA, + [MoveId.BLAST_BURN]: RewardTier.ULTRA, + [MoveId.HYDRO_CANNON]: RewardTier.ULTRA, + [MoveId.WEATHER_BALL]: RewardTier.COMMON, + [MoveId.FAKE_TEARS]: RewardTier.COMMON, + [MoveId.AIR_CUTTER]: RewardTier.GREAT, + [MoveId.OVERHEAT]: RewardTier.ULTRA, + [MoveId.ROCK_TOMB]: RewardTier.GREAT, + [MoveId.METAL_SOUND]: RewardTier.COMMON, + [MoveId.COSMIC_POWER]: RewardTier.COMMON, + [MoveId.SIGNAL_BEAM]: RewardTier.GREAT, + [MoveId.SAND_TOMB]: RewardTier.COMMON, + [MoveId.MUDDY_WATER]: RewardTier.GREAT, + [MoveId.BULLET_SEED]: RewardTier.GREAT, + [MoveId.AERIAL_ACE]: RewardTier.GREAT, + [MoveId.ICICLE_SPEAR]: RewardTier.GREAT, + [MoveId.IRON_DEFENSE]: RewardTier.GREAT, + [MoveId.DRAGON_CLAW]: RewardTier.ULTRA, + [MoveId.FRENZY_PLANT]: RewardTier.ULTRA, + [MoveId.BULK_UP]: RewardTier.COMMON, + [MoveId.BOUNCE]: RewardTier.GREAT, + [MoveId.MUD_SHOT]: RewardTier.GREAT, + [MoveId.POISON_TAIL]: RewardTier.GREAT, + [MoveId.COVET]: RewardTier.GREAT, + [MoveId.MAGICAL_LEAF]: RewardTier.GREAT, + [MoveId.CALM_MIND]: RewardTier.GREAT, + [MoveId.LEAF_BLADE]: RewardTier.ULTRA, + [MoveId.DRAGON_DANCE]: RewardTier.GREAT, + [MoveId.ROCK_BLAST]: RewardTier.GREAT, + [MoveId.WATER_PULSE]: RewardTier.GREAT, + [MoveId.ROOST]: RewardTier.GREAT, + [MoveId.GRAVITY]: RewardTier.COMMON, + [MoveId.GYRO_BALL]: RewardTier.COMMON, + [MoveId.BRINE]: RewardTier.GREAT, + [MoveId.PLUCK]: RewardTier.GREAT, + [MoveId.TAILWIND]: RewardTier.GREAT, + [MoveId.U_TURN]: RewardTier.GREAT, + [MoveId.CLOSE_COMBAT]: RewardTier.ULTRA, + [MoveId.PAYBACK]: RewardTier.COMMON, + [MoveId.ASSURANCE]: RewardTier.COMMON, + [MoveId.EMBARGO]: RewardTier.COMMON, + [MoveId.FLING]: RewardTier.COMMON, + [MoveId.GASTRO_ACID]: RewardTier.GREAT, + [MoveId.POWER_SWAP]: RewardTier.COMMON, + [MoveId.GUARD_SWAP]: RewardTier.COMMON, + [MoveId.WORRY_SEED]: RewardTier.GREAT, + [MoveId.TOXIC_SPIKES]: RewardTier.GREAT, + [MoveId.FLARE_BLITZ]: RewardTier.ULTRA, + [MoveId.AURA_SPHERE]: RewardTier.GREAT, + [MoveId.ROCK_POLISH]: RewardTier.COMMON, + [MoveId.POISON_JAB]: RewardTier.GREAT, + [MoveId.DARK_PULSE]: RewardTier.GREAT, + [MoveId.AQUA_TAIL]: RewardTier.GREAT, + [MoveId.SEED_BOMB]: RewardTier.GREAT, + [MoveId.AIR_SLASH]: RewardTier.GREAT, + [MoveId.X_SCISSOR]: RewardTier.GREAT, + [MoveId.BUG_BUZZ]: RewardTier.GREAT, + [MoveId.DRAGON_PULSE]: RewardTier.GREAT, + [MoveId.POWER_GEM]: RewardTier.GREAT, + [MoveId.DRAIN_PUNCH]: RewardTier.GREAT, + [MoveId.VACUUM_WAVE]: RewardTier.COMMON, + [MoveId.FOCUS_BLAST]: RewardTier.GREAT, + [MoveId.ENERGY_BALL]: RewardTier.GREAT, + [MoveId.BRAVE_BIRD]: RewardTier.ULTRA, + [MoveId.EARTH_POWER]: RewardTier.ULTRA, + [MoveId.GIGA_IMPACT]: RewardTier.GREAT, + [MoveId.NASTY_PLOT]: RewardTier.COMMON, + [MoveId.AVALANCHE]: RewardTier.GREAT, + [MoveId.SHADOW_CLAW]: RewardTier.GREAT, + [MoveId.THUNDER_FANG]: RewardTier.GREAT, + [MoveId.ICE_FANG]: RewardTier.GREAT, + [MoveId.FIRE_FANG]: RewardTier.GREAT, + [MoveId.PSYCHO_CUT]: RewardTier.GREAT, + [MoveId.ZEN_HEADBUTT]: RewardTier.GREAT, + [MoveId.FLASH_CANNON]: RewardTier.GREAT, + [MoveId.ROCK_CLIMB]: RewardTier.GREAT, + [MoveId.DEFOG]: RewardTier.COMMON, + [MoveId.TRICK_ROOM]: RewardTier.COMMON, + [MoveId.DRACO_METEOR]: RewardTier.ULTRA, + [MoveId.LEAF_STORM]: RewardTier.ULTRA, + [MoveId.POWER_WHIP]: RewardTier.ULTRA, + [MoveId.CROSS_POISON]: RewardTier.GREAT, + [MoveId.GUNK_SHOT]: RewardTier.ULTRA, + [MoveId.IRON_HEAD]: RewardTier.GREAT, + [MoveId.STONE_EDGE]: RewardTier.ULTRA, + [MoveId.STEALTH_ROCK]: RewardTier.COMMON, + [MoveId.GRASS_KNOT]: RewardTier.ULTRA, + [MoveId.BUG_BITE]: RewardTier.GREAT, + [MoveId.CHARGE_BEAM]: RewardTier.GREAT, + [MoveId.HONE_CLAWS]: RewardTier.COMMON, + [MoveId.WONDER_ROOM]: RewardTier.COMMON, + [MoveId.PSYSHOCK]: RewardTier.GREAT, + [MoveId.VENOSHOCK]: RewardTier.GREAT, + [MoveId.MAGIC_ROOM]: RewardTier.COMMON, + [MoveId.SMACK_DOWN]: RewardTier.COMMON, + [MoveId.SLUDGE_WAVE]: RewardTier.GREAT, + [MoveId.HEAVY_SLAM]: RewardTier.GREAT, + [MoveId.ELECTRO_BALL]: RewardTier.GREAT, + [MoveId.FLAME_CHARGE]: RewardTier.GREAT, + [MoveId.LOW_SWEEP]: RewardTier.GREAT, + [MoveId.ACID_SPRAY]: RewardTier.COMMON, + [MoveId.FOUL_PLAY]: RewardTier.ULTRA, + [MoveId.ROUND]: RewardTier.COMMON, + [MoveId.ECHOED_VOICE]: RewardTier.COMMON, + [MoveId.STORED_POWER]: RewardTier.COMMON, + [MoveId.ALLY_SWITCH]: RewardTier.COMMON, + [MoveId.SCALD]: RewardTier.GREAT, + [MoveId.HEX]: RewardTier.GREAT, + [MoveId.SKY_DROP]: RewardTier.GREAT, + [MoveId.INCINERATE]: RewardTier.GREAT, + [MoveId.QUASH]: RewardTier.COMMON, + [MoveId.ACROBATICS]: RewardTier.GREAT, + [MoveId.RETALIATE]: RewardTier.GREAT, + [MoveId.WATER_PLEDGE]: RewardTier.GREAT, + [MoveId.FIRE_PLEDGE]: RewardTier.GREAT, + [MoveId.GRASS_PLEDGE]: RewardTier.GREAT, + [MoveId.VOLT_SWITCH]: RewardTier.GREAT, + [MoveId.STRUGGLE_BUG]: RewardTier.COMMON, + [MoveId.BULLDOZE]: RewardTier.GREAT, + [MoveId.FROST_BREATH]: RewardTier.GREAT, + [MoveId.DRAGON_TAIL]: RewardTier.GREAT, + [MoveId.WORK_UP]: RewardTier.COMMON, + [MoveId.ELECTROWEB]: RewardTier.GREAT, + [MoveId.WILD_CHARGE]: RewardTier.GREAT, + [MoveId.DRILL_RUN]: RewardTier.GREAT, + [MoveId.RAZOR_SHELL]: RewardTier.GREAT, + [MoveId.HEAT_CRASH]: RewardTier.GREAT, + [MoveId.TAIL_SLAP]: RewardTier.GREAT, + [MoveId.HURRICANE]: RewardTier.ULTRA, + [MoveId.SNARL]: RewardTier.COMMON, + [MoveId.PHANTOM_FORCE]: RewardTier.ULTRA, + [MoveId.PETAL_BLIZZARD]: RewardTier.GREAT, + [MoveId.DISARMING_VOICE]: RewardTier.GREAT, + [MoveId.DRAINING_KISS]: RewardTier.GREAT, + [MoveId.GRASSY_TERRAIN]: RewardTier.COMMON, + [MoveId.MISTY_TERRAIN]: RewardTier.COMMON, + [MoveId.PLAY_ROUGH]: RewardTier.GREAT, + [MoveId.CONFIDE]: RewardTier.COMMON, + [MoveId.MYSTICAL_FIRE]: RewardTier.GREAT, + [MoveId.EERIE_IMPULSE]: RewardTier.COMMON, + [MoveId.VENOM_DRENCH]: RewardTier.COMMON, + [MoveId.ELECTRIC_TERRAIN]: RewardTier.COMMON, + [MoveId.DAZZLING_GLEAM]: RewardTier.ULTRA, + [MoveId.INFESTATION]: RewardTier.COMMON, + [MoveId.POWER_UP_PUNCH]: RewardTier.GREAT, + [MoveId.DARKEST_LARIAT]: RewardTier.GREAT, + [MoveId.HIGH_HORSEPOWER]: RewardTier.ULTRA, + [MoveId.SOLAR_BLADE]: RewardTier.GREAT, + [MoveId.THROAT_CHOP]: RewardTier.GREAT, + [MoveId.POLLEN_PUFF]: RewardTier.GREAT, + [MoveId.PSYCHIC_TERRAIN]: RewardTier.COMMON, + [MoveId.LUNGE]: RewardTier.GREAT, + [MoveId.SPEED_SWAP]: RewardTier.COMMON, + [MoveId.SMART_STRIKE]: RewardTier.GREAT, + [MoveId.BRUTAL_SWING]: RewardTier.GREAT, + [MoveId.AURORA_VEIL]: RewardTier.COMMON, + [MoveId.PSYCHIC_FANGS]: RewardTier.GREAT, + [MoveId.STOMPING_TANTRUM]: RewardTier.GREAT, + [MoveId.LIQUIDATION]: RewardTier.ULTRA, + [MoveId.BODY_PRESS]: RewardTier.ULTRA, + [MoveId.BREAKING_SWIPE]: RewardTier.GREAT, + [MoveId.STEEL_BEAM]: RewardTier.ULTRA, + [MoveId.EXPANDING_FORCE]: RewardTier.GREAT, + [MoveId.STEEL_ROLLER]: RewardTier.COMMON, + [MoveId.SCALE_SHOT]: RewardTier.ULTRA, + [MoveId.METEOR_BEAM]: RewardTier.GREAT, + [MoveId.MISTY_EXPLOSION]: RewardTier.COMMON, + [MoveId.GRASSY_GLIDE]: RewardTier.COMMON, + [MoveId.RISING_VOLTAGE]: RewardTier.COMMON, + [MoveId.TERRAIN_PULSE]: RewardTier.COMMON, + [MoveId.SKITTER_SMACK]: RewardTier.GREAT, + [MoveId.BURNING_JEALOUSY]: RewardTier.GREAT, + [MoveId.LASH_OUT]: RewardTier.GREAT, + [MoveId.POLTERGEIST]: RewardTier.ULTRA, + [MoveId.CORROSIVE_GAS]: RewardTier.COMMON, + [MoveId.COACHING]: RewardTier.COMMON, + [MoveId.FLIP_TURN]: RewardTier.COMMON, + [MoveId.TRIPLE_AXEL]: RewardTier.COMMON, + [MoveId.DUAL_WINGBEAT]: RewardTier.COMMON, + [MoveId.SCORCHING_SANDS]: RewardTier.GREAT, + [MoveId.TERA_BLAST]: RewardTier.GREAT, + [MoveId.ICE_SPINNER]: RewardTier.GREAT, + [MoveId.SNOWSCAPE]: RewardTier.COMMON, + [MoveId.POUNCE]: RewardTier.COMMON, + [MoveId.TRAILBLAZE]: RewardTier.COMMON, + [MoveId.CHILLING_WATER]: RewardTier.COMMON, + [MoveId.HARD_PRESS]: RewardTier.GREAT, + [MoveId.DRAGON_CHEER]: RewardTier.COMMON, + [MoveId.ALLURING_VOICE]: RewardTier.GREAT, + [MoveId.TEMPER_FLARE]: RewardTier.GREAT, + [MoveId.SUPERCELL_SLAM]: RewardTier.GREAT, + [MoveId.PSYCHIC_NOISE]: RewardTier.GREAT, + [MoveId.UPPER_HAND]: RewardTier.COMMON, }; diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 3fdd83c185d..09b00f1af2c 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -22,7 +22,7 @@ import { TrainerType } from "#enums/trainer-type"; import { Nature } from "#enums/nature"; import type { MoveId } from "#enums/move-id"; import { TypeColor, TypeShadow } from "#enums/color"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { globalScene } from "#app/global-scene"; import { pokemonFormChanges } from "./pokemon-forms"; import { pokemonEvolutions } from "./balance/pokemon-evolutions"; @@ -459,11 +459,11 @@ export class SingleGenerationChallenge extends Challenge { .setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)) .setCustomModifierRewards({ guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.ULTRA, ], allowLuckUpgrades: false, }); @@ -476,12 +476,12 @@ export class SingleGenerationChallenge extends Challenge { .setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)) .setCustomModifierRewards({ guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.ULTRA, ], allowLuckUpgrades: false, }); diff --git a/src/data/data-lists.ts b/src/data/data-lists.ts index ed172846fe1..3cae3e25ffd 100644 --- a/src/data/data-lists.ts +++ b/src/data/data-lists.ts @@ -1,5 +1,9 @@ import type PokemonSpecies from "#app/data/pokemon-species"; +import type { HeldItem } from "#app/items/held-item"; +import type { TrainerItem } from "#app/items/trainer-item"; import type { ModifierTypes } from "#app/modifier/modifier-type"; +import type { HeldItemId } from "#enums/held-item-id"; +import type { TrainerItemId } from "#enums/trainer-item-id"; import type { Ability } from "./abilities/ability"; import type Move from "./moves/move"; @@ -7,5 +11,8 @@ export const allAbilities: Ability[] = []; export const allMoves: Move[] = []; export const allSpecies: PokemonSpecies[] = []; +export const allHeldItems: Record = {}; +export const allTrainerItems: Record = {}; + // TODO: Figure out what this is used for and provide an appropriate tsdoc comment export const modifierTypes = {} as ModifierTypes; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 0878ece2f01..04df60f0251 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -36,15 +36,7 @@ import { ArenaTagSide } from "#enums/arena-tag-side"; import { applyAbAttrs } from "../abilities/apply-ab-attrs"; -import { allAbilities, allMoves } from "../data-lists"; -import { - AttackTypeBoosterModifier, - BerryModifier, - PokemonHeldItemModifier, - PokemonMoveAccuracyBoosterModifier, - PokemonMultiHitModifier, - PreserveBerryModifier, -} from "../../modifier/modifier"; +import { allAbilities, allHeldItems, allMoves } from "../data-lists"; import type { BattlerIndex } from "#enums/battler-index"; import { BattleType } from "#enums/battle-type"; import { TerrainType } from "../terrain"; @@ -86,10 +78,15 @@ import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MultiHitType } from "#enums/MultiHitType"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves, invalidSketchMoves } from "./invalid-moves"; import { isVirtual, MoveUseMode } from "#enums/move-use-mode"; +import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { BerryHeldItem, berryTypeToHeldItem } from "#app/items/held-items/berry"; +import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id"; import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap } from "#app/@types/move-types"; import { applyMoveAttrs } from "./apply-attrs"; import { frenzyMissFunc, getMoveTargets } from "./move-utils"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; import { AbAttrBaseParams, AbAttrParamsWithCancel, PreAttackModifyPowerAbAttrParams } from "../abilities/ability"; +import { applyHeldItems } from "#app/items/all-held-items"; /** * A function used to conditionally determine execution of a given {@linkcode MoveAttr}. @@ -769,7 +766,7 @@ export default abstract class Move implements Localizable { const isOhko = this.hasAttr("OneHitKOAccuracyAttr"); if (!isOhko) { - globalScene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy); + applyHeldItems(HELD_ITEM_EFFECT.ACCURACY_BOOSTER, { pokemon: user, moveAccuracy: moveAccuracy }); } if (globalScene.arena.weather?.weatherType === WeatherType.FOG) { @@ -806,7 +803,7 @@ export default abstract class Move implements Localizable { applyAbAttrs("MoveTypeChangeAbAttr", {pokemon: source, opponent: target, move: this, simulated: true, moveType: typeChangeHolder, power: typeChangeMovePowerMultiplier}); const sourceTeraType = source.getTeraType(); - if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { + if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !source.heldItemManager.hasItem(HeldItemId.MULTI_LENS)) { power.value = 60; } @@ -851,7 +848,11 @@ export default abstract class Move implements Localizable { if (!this.hasAttr("TypelessAttr")) { globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power); - globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, typeChangeHolder.value, power); + applyHeldItems(HELD_ITEM_EFFECT.ATTACK_TYPE_BOOST, { + pokemon: source, + moveType: typeChangeHolder.value, + movePower: power, + }); } if (source.getTag(HelpingHandTag)) { @@ -914,7 +915,7 @@ export default abstract class Move implements Localizable { * Returns `true` if this move can be given additional strikes * by enhancing effects. * Currently used for {@link https://bulbapedia.bulbagarden.net/wiki/Parental_Bond_(Ability) | Parental Bond} - * and {@linkcode PokemonMultiHitModifier | Multi-Lens}. + * and {@linkcode MultiHitHeldItem | Multi-Lens}. * @param user The {@linkcode Pokemon} using the move * @param restrictSpread `true` if the enhancing effect * should not affect multi-target moves (default `false`) @@ -1565,7 +1566,7 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { // first, determine if the hit is coming from multi lens or not - const lensCount = user.getHeldItems().find(i => i instanceof PokemonMultiHitModifier)?.getStackCount() ?? 0; + const lensCount = user.heldItemManager.getStack(HeldItemId.MULTI_LENS); if (lensCount <= 0) { // no multi lenses; we can just halve the target's hp and call it a day (args[0] as NumberHolder).value = toDmgValue(target.hp / 2); @@ -2621,35 +2622,33 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { return false; } - const heldItems = this.getTargetHeldItems(target).filter((i) => i.isTransferable); + const heldItems = target.heldItemManager.getTransferableHeldItems(); if (!heldItems.length) { return false; } - const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; - const highestItemTier = heldItems.map((m) => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct? - const tierHeldItems = heldItems.filter((m) => m.type.getOrInferTier(poolType) === highestItemTier); - const stolenItem = tierHeldItems[user.randBattleSeedInt(tierHeldItems.length)]; - if (!globalScene.tryTransferHeldItemModifier(stolenItem, user, false)) { + const stolenItem = heldItems[user.randBattleSeedInt(heldItems.length)]; + + if (!globalScene.tryTransferHeldItem(stolenItem, target, user, false)) { return false; } - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name })); + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stoleItem", + { pokemonName: getPokemonNameWithAffix(user), + targetName: getPokemonNameWithAffix(target), + itemName: allHeldItems[stolenItem].name + } + )); return true; } - getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; - } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - const heldItems = this.getTargetHeldItems(target); + const heldItems = target.heldItemManager.getTransferableHeldItems(); return heldItems.length ? 5 : 0; } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - const heldItems = this.getTargetHeldItems(target); + const heldItems = target.heldItemManager.getTransferableHeldItems(); return heldItems.length ? -5 : 0; } } @@ -2695,10 +2694,10 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { // Considers entire transferrable item pool by default (Knock Off). // Otherwise only consider berries (Incinerate). - let heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable); + let heldItems = target.heldItemManager.getTransferableHeldItems(); if (this.berriesOnly) { - heldItems = heldItems.filter(m => m instanceof BerryModifier && m.pokemonId === target.id, target.isPlayer()); + heldItems = heldItems.filter(m => m in Object.values(berryTypeToHeldItem)); } if (!heldItems.length) { @@ -2709,29 +2708,26 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { // Decrease item amount and update icon target.loseHeldItem(removedItem); - globalScene.updateModifiers(target.isPlayer()); + globalScene.updateItems(target.isPlayer()); if (this.berriesOnly) { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:incineratedItem", + { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: allHeldItems[removedItem].name })); } else { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:knockedOffItem", + { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: allHeldItems[removedItem].name })); } return true; } - - getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; - } - + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - const heldItems = this.getTargetHeldItems(target); + const heldItems = target.getHeldItems(); return heldItems.length ? 5 : 0; } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - const heldItems = this.getTargetHeldItems(target); + const heldItems = target.getHeldItems(); return heldItems.length ? -5 : 0; } } @@ -2740,7 +2736,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { * Attribute that causes targets of the move to eat a berry. Used for Teatime, Stuff Cheeks */ export class EatBerryAttr extends MoveEffectAttr { - protected chosenBerry: BerryModifier; + protected chosenBerry: HeldItemId; constructor(selfTarget: boolean) { super(selfTarget); } @@ -2769,7 +2765,7 @@ export class EatBerryAttr extends MoveEffectAttr { this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)]; const preserve = new BooleanHolder(false); // check for berry pouch preservation - globalScene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.PRESERVE_BERRY, {pokemon: pokemon, doPreserve: preserve}); if (!preserve.value) { this.reduceBerryModifier(pokemon); } @@ -2780,16 +2776,15 @@ export class EatBerryAttr extends MoveEffectAttr { return true; } - getTargetHeldBerries(target: Pokemon): BerryModifier[] { - return globalScene.findModifiers(m => m instanceof BerryModifier - && (m as BerryModifier).pokemonId === target.id, target.isPlayer()) as BerryModifier[]; + getTargetHeldBerries(target: Pokemon): HeldItemId[] { + return target.getHeldItems().filter(m => isItemInCategory(m, HeldItemCategoryId.BERRY)); } reduceBerryModifier(target: Pokemon) { if (this.chosenBerry) { target.loseHeldItem(this.chosenBerry); } - globalScene.updateModifiers(target.isPlayer()); + globalScene.updateItems(target.isPlayer()); } @@ -2803,10 +2798,10 @@ export class EatBerryAttr extends MoveEffectAttr { */ protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) { // consumer eats berry, owner triggers unburden and similar effects - getBerryEffectFunc(this.chosenBerry.berryType)(consumer); + getBerryEffectFunc((allHeldItems[this.chosenBerry] as BerryHeldItem).berryType)(consumer); applyAbAttrs("PostItemLostAbAttr", {pokemon: berryOwner}); applyAbAttrs("HealFromBerryUseAbAttr", {pokemon: consumer}); - consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest); + consumer.recordEatenBerry((allHeldItems[this.chosenBerry] as BerryHeldItem).berryType, updateHarvest); } } @@ -2845,7 +2840,7 @@ export class StealEatBerryAttr extends EatBerryAttr { // pick a random berry and eat it this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)]; applyAbAttrs("PostItemLostAbAttr", {pokemon: target}); - const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name }); + const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: allHeldItems[this.chosenBerry].name }); globalScene.phaseManager.queueMessage(message); this.reduceBerryModifier(target); this.eatBerry(user, target); @@ -6424,9 +6419,6 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { } } - // clear out enemy held item modifiers of the switch out target - globalScene.clearEnemyHeldItemModifiers(switchOutTarget); - if (!allyPokemon?.isActive(true) && switchOutTarget.hp) { globalScene.phaseManager.pushNew("BattleEndPhase", false); @@ -8017,14 +8009,14 @@ const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Po const failIfGhostTypeCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => !target.isOfType(PokemonType.GHOST); -const failIfNoTargetHeldItemsCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.getHeldItems().filter(i => i.isTransferable)?.length > 0; +const failIfNoTargetHeldItemsCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.heldItemManager.getTransferableHeldItems().length > 0; const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { - const heldItems = target.getHeldItems().filter(i => i.isTransferable); + const heldItems = target.heldItemManager.getTransferableHeldItems(); if (heldItems.length === 0) { return ""; } - const itemName = heldItems[0]?.type?.name ?? "item"; + const itemName = allHeldItems[heldItems[0]].name ?? "item"; const message: string = i18next.t("moveTriggers:attackedByItem", { pokemonName: getPokemonNameWithAffix(target), itemName: itemName }); return message; }; @@ -9319,7 +9311,7 @@ export function initMoves() { .condition((user, target, move) => !target.status && !target.isSafeguarded(user)) .reflectable(), new AttackMove(MoveId.KNOCK_OFF, PokemonType.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3) - .attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferable).length > 0 ? 1.5 : 1) + .attr(MovePowerMultiplierAttr, (user, target, move) => target.heldItemManager.getTransferableHeldItems().length > 0 ? 1.5 : 1) .attr(RemoveHeldItemAttr, false) .edgeCase(), // Should not be able to remove held item if user faints due to Rough Skin, Iron Barbs, etc. @@ -10041,7 +10033,7 @@ export function initMoves() { .condition((user, target, move) => !target.turnData.acted) .attr(ForceLastAttr), new AttackMove(MoveId.ACROBATICS, PokemonType.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5) - .attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.isTransferable).reduce((v, m) => v + m.stackCount, 0))), + .attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.heldItemManager.getTransferableHeldItems().reduce((v, m) => v + user.heldItemManager.getStack(m), 0))), new StatusMove(MoveId.REFLECT_TYPE, PokemonType.NORMAL, -1, 15, -1, 0, 5) .ignoresSubstitute() .attr(CopyTypeAttr), @@ -10790,7 +10782,7 @@ export function initMoves() { .attr(EatBerryAttr, true) .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true) .condition((user) => { - const userBerries = globalScene.findModifiers(m => m instanceof BerryModifier, user.isPlayer()); + const userBerries = user.getHeldItems().filter(m => isItemInCategory(m, HeldItemCategoryId.BERRY)); return userBerries.length > 0; }) .edgeCase(), // Stuff Cheeks should not be selectable when the user does not have a berry, see wiki diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index 7a1c9821e89..4c88add526a 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -19,7 +19,7 @@ import i18next from "i18next"; import type { IEggOptions } from "#app/data/egg"; import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { modifierTypes } from "#app/data/data-lists"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; @@ -165,7 +165,7 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder. setEncounterRewards( { guaranteedModifierTypeFuncs: [modifierTypes.SACRED_ASH], - guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA], + guaranteedModifierTiers: [RewardTier.ROGUE, RewardTier.ULTRA], fillRemaining: true, }, [eggOptions], diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index bec75288837..e1d59b58670 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -1,6 +1,6 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - generateModifierType, + getPartyBerries, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, @@ -9,40 +9,47 @@ import { import type Pokemon from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; -import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; -import { PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { randInt } from "#app/utils/common"; import { BattlerIndex } from "#enums/battler-index"; -import { - applyModifierTypeToPlayerPokemon, - catchPokemon, - getHighestLevelPlayerPokemon, -} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { TrainerSlot } from "#enums/trainer-slot"; import { PokeballType } from "#enums/pokeball"; -import type HeldModifierConfig from "#app/@types/held-modifier-config"; -import type { BerryType } from "#enums/berry-type"; import { Stat } from "#enums/stat"; import i18next from "i18next"; +import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; import { MoveUseMode } from "#enums/move-use-mode"; +import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; +import { allHeldItems } from "#app/data/data-lists"; +import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { HeldItemRequirement } from "../mystery-encounter-requirements"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/absoluteAvarice"; +function berrySprite(spriteKey: string, x: number, y: number): MysteryEncounterSpriteConfig { + return { + spriteKey: spriteKey, + fileRoot: "items", + isItem: true, + x: x, + y: y, + hidden: true, + disableAnimation: true, + }; +} + /** * Absolute Avarice encounter. * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3805 | GitHub Issue #3805} @@ -53,7 +60,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde ) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(20, 180) - .withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 6)) // Must have at least 6 berries to spawn + .withSceneRequirement(new HeldItemRequirement(HeldItemCategoryId.BERRY, 6)) // Must have at least 6 berries to spawn .withFleeAllowed(false) .withIntroSpriteConfigs([ { @@ -74,105 +81,17 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde repeat: true, x: -5, }, - { - spriteKey: "lum_berry", - fileRoot: "items", - isItem: true, - x: 7, - y: -14, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "salac_berry", - fileRoot: "items", - isItem: true, - x: 2, - y: 4, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "lansat_berry", - fileRoot: "items", - isItem: true, - x: 32, - y: 5, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "liechi_berry", - fileRoot: "items", - isItem: true, - x: 6, - y: -5, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "sitrus_berry", - fileRoot: "items", - isItem: true, - x: 7, - y: 8, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "enigma_berry", - fileRoot: "items", - isItem: true, - x: 26, - y: -4, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "leppa_berry", - fileRoot: "items", - isItem: true, - x: 16, - y: -27, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "petaya_berry", - fileRoot: "items", - isItem: true, - x: 30, - y: -17, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "ganlon_berry", - fileRoot: "items", - isItem: true, - x: 16, - y: -11, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "apicot_berry", - fileRoot: "items", - isItem: true, - x: 14, - y: -2, - hidden: true, - disableAnimation: true, - }, - { - spriteKey: "starf_berry", - fileRoot: "items", - isItem: true, - x: 18, - y: 9, - hidden: true, - disableAnimation: true, - }, + berrySprite("lum_berry", 7, -14), + berrySprite("salac_berry", 2, 4), + berrySprite("lansat_berry", 32, 5), + berrySprite("liechi_berry", 6, -5), + berrySprite("sitrus_berry", 7, 8), + berrySprite("enigma_berry", 26, -4), + berrySprite("leppa_berry", 16, -27), + berrySprite("petaya_berry", 30, -17), + berrySprite("ganlon_berry", 16, -11), + berrySprite("apicot_berry", 14, -2), + berrySprite("starf_berry", 18, 9), ]) .withHideWildIntroMessage(true) .withAutoHideIntroVisuals(false) @@ -191,35 +110,17 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde globalScene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav"); globalScene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3"); - // Get all player berry items, remove from party, and store reference - const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; + // Get all berries in party, with references to the pokemon + const berryItems = getPartyBerries(); - // Sort berries by party member ID to more easily re-add later if necessary - const berryItemsMap = new Map(); - globalScene.getPlayerParty().forEach(pokemon => { - const pokemonBerries = berryItems.filter(b => b.pokemonId === pokemon.id); - if (pokemonBerries?.length > 0) { - berryItemsMap.set(pokemon.id, pokemonBerries); - } + encounter.misc.berryItemsMap = berryItems; + + // Adds stolen berries to the Greedent item configuration + const bossHeldItemConfig: HeldItemConfiguration = []; + berryItems.forEach(map => { + bossHeldItemConfig.push({ entry: map.item, count: 1 }); }); - encounter.misc = { berryItemsMap }; - - // Generates copies of the stolen berries to put on the Greedent - const bossModifierConfigs: HeldModifierConfig[] = []; - berryItems.forEach(berryMod => { - // Can't define stack count on a ModifierType, have to just create separate instances for each stack - // Overflow berries will be "lost" on the boss, but it's un-catchable anyway - for (let i = 0; i < berryMod.stackCount; i++) { - const modifierType = generateModifierType(modifierTypes.BERRY, [ - berryMod.berryType, - ]) as PokemonHeldItemModifierType; - bossModifierConfigs.push({ modifier: modifierType }); - } - }); - - // Do NOT remove the real berries yet or else it will be persisted in the session data - // +1 SpDef below wave 50, SpDef and Speed otherwise const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ? [Stat.SPDEF] : [Stat.SPDEF, Stat.SPD]; @@ -234,7 +135,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde bossSegments: 3, shiny: false, // Shiny lock because of consistency issues between the different options moveSet: [MoveId.THRASH, MoveId.CRUNCH, MoveId.BODY_PRESS, MoveId.SLACK_OFF], - modifierConfigs: bossModifierConfigs, + heldItemConfig: bossHeldItemConfig, tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(`${namespace}:option.1.boss_enraged`); @@ -261,12 +162,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde // Remove the berries from the party // Session has been safely saved at this point, so data won't be lost - const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; - berryItems.forEach(berryMod => { - globalScene.removeModifier(berryMod); + const berryItems = getPartyBerries(); + berryItems.forEach(map => { + globalScene.getPokemonById(map.pokemonId)?.heldItemManager.remove(map.item.id); }); - globalScene.updateModifiers(true); + globalScene.updateItems(true); return true; }) @@ -286,19 +187,14 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde const encounter = globalScene.currentBattle.mysteryEncounter!; // Provides 1x Reviver Seed to each party member at end of battle - const revSeed = generateModifierType(modifierTypes.REVIVER_SEED); encounter.setDialogueToken( "foodReward", - revSeed?.name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"), + allHeldItems[HeldItemId.REVIVER_SEED].name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"), ); const givePartyPokemonReviverSeeds = () => { const party = globalScene.getPlayerParty(); party.forEach(p => { - const heldItems = p.getHeldItems(); - if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) { - const seedModifier = revSeed.newModifier(p); - globalScene.addModifier(seedModifier, false, false, false, true); - } + p.heldItemManager.add(HeldItemId.REVIVER_SEED); }); queueEncounterMessage(`${namespace}:option.1.food_stash`); }; @@ -334,23 +230,20 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde // Returns 2/5 of the berries stolen to each Pokemon const party = globalScene.getPlayerParty(); party.forEach(pokemon => { - const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id); - const berryTypesAsArray: BerryType[] = []; - stolenBerries?.forEach(bMod => berryTypesAsArray.push(...new Array(bMod.stackCount).fill(bMod.berryType))); - const returnedBerryCount = Math.floor(((berryTypesAsArray.length ?? 0) * 2) / 5); + // TODO: is this check legal? + const stolenBerries = berryMap.filter(map => map.pokemon === pokemon); + const returnedBerryCount = Math.floor(((stolenBerries.length ?? 0) * 2) / 5); if (returnedBerryCount > 0) { for (let i = 0; i < returnedBerryCount; i++) { // Shuffle remaining berry types and pop - Phaser.Math.RND.shuffle(berryTypesAsArray); - const randBerryType = berryTypesAsArray.pop(); - - const berryModType = generateModifierType(modifierTypes.BERRY, [randBerryType]) as BerryModifierType; - applyModifierTypeToPlayerPokemon(pokemon, berryModType); + Phaser.Math.RND.shuffle(stolenBerries); + const randBerryType = stolenBerries.pop(); + pokemon.heldItemManager.add(randBerryType); } } }); - await globalScene.updateModifiers(true); + await globalScene.updateItems(true); await transitionMysteryEncounterIntroVisuals(true, true, 500); leaveEncounterWithoutBattle(true); 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 f8a904cdb45..37ddeaea3d2 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 @@ -1,5 +1,4 @@ import { - generateModifierType, leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, @@ -24,6 +23,8 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import i18next from "i18next"; +import { allTrainerItems } from "#app/data/data-lists"; +import { TrainerItemId } from "#enums/trainer-item-id"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/anOfferYouCantRefuse"; @@ -109,8 +110,8 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB } } - const shinyCharm = generateModifierType(modifierTypes.SHINY_CHARM); - encounter.setDialogueToken("itemName", shinyCharm?.name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name")); + const name = allTrainerItems[TrainerItemId.SHINY_CHARM].name; + encounter.setDialogueToken("itemName", name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name")); encounter.setDialogueToken("liepardName", getPokemonSpecies(SpeciesId.LIEPARD).getName()); return true; diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index d86a8439804..48bd27121d1 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -1,7 +1,6 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - generateModifierType, generateModifierTypeOption, getRandomEncounterSpecies, initBattleWithEnemyConfig, @@ -11,7 +10,7 @@ import { } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type"; +import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import { regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; import { ModifierPoolType } from "#enums/modifier-pool-type"; @@ -26,18 +25,17 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { - applyModifierTypeToPlayerPokemon, getEncounterPokemonLevelForWave, getHighestStatPlayerPokemon, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import PokemonData from "#app/system/pokemon-data"; -import { BerryModifier } from "#app/modifier/modifier"; import i18next from "#app/plugins/i18n"; import { BerryType } from "#enums/berry-type"; import { PERMANENT_STATS, Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; +import { berryTypeToHeldItem } from "#app/items/held-items/berry"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/berriesAbound"; @@ -312,35 +310,17 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder. function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) { const berryType = randSeedInt(Object.keys(BerryType).filter(s => !Number.isNaN(Number(s))).length) as BerryType; - const berry = generateModifierType(modifierTypes.BERRY, [berryType]) as BerryModifierType; + const berry = berryTypeToHeldItem[berryType]; const party = globalScene.getPlayerParty(); - // Will try to apply to prioritized pokemon first, then do normal application method if it fails - if (prioritizedPokemon) { - const heldBerriesOfType = globalScene.findModifier( - m => - m instanceof BerryModifier && - m.pokemonId === prioritizedPokemon.id && - (m as BerryModifier).berryType === berryType, - true, - ) as BerryModifier; - - if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) { - applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry); - return; - } + // Will give the berry to a Pokemon, starting from the prioritized one + if (prioritizedPokemon?.heldItemManager.add(berry)) { + return; } - // Iterate over the party until berry was successfully given for (const pokemon of party) { - const heldBerriesOfType = globalScene.findModifier( - m => m instanceof BerryModifier && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, - true, - ) as BerryModifier; - - if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) { - applyModifierTypeToPlayerPokemon(pokemon, berry); + if (pokemon.heldItemManager.add(berry)) { return; } } diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index df06f40c159..9bc3e293cbe 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -1,6 +1,5 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - generateModifierType, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, @@ -31,28 +30,22 @@ import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler" import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { - AttackTypeBoosterHeldItemTypeRequirement, CombinationPokemonRequirement, HeldItemRequirement, TypeRequirement, } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { PokemonType } from "#enums/pokemon-type"; -import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/modifier/modifier-type"; +import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; -import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; -import { - AttackTypeBoosterModifier, - BypassSpeedChanceModifier, - ContactHeldItemTransferChanceModifier, - GigantamaxAccessModifier, - MegaEvolutionAccessModifier, -} from "#app/modifier/modifier"; import i18next from "i18next"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; import { allMoves } from "#app/data/data-lists"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { HeldItemId } from "#enums/held-item-id"; +import { allHeldItems } from "#app/data/data-lists"; +import { TrainerItemId } from "#enums/trainer-item-id"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/bugTypeSuperfan"; @@ -143,6 +136,8 @@ const POOL_3_POKEMON: { species: SpeciesId; formIndex?: number }[] = [ const POOL_4_POKEMON = [SpeciesId.GENESECT, SpeciesId.SLITHER_WING, SpeciesId.BUZZWOLE, SpeciesId.PHEROMOSA]; +const REQUIRED_ITEMS = [HeldItemId.QUICK_CLAW, HeldItemId.GRIP_CLAW, HeldItemId.SILVER_POWDER]; + const PHYSICAL_TUTOR_MOVES = [ MoveId.MEGAHORN, MoveId.ATTACK_ORDER, @@ -186,8 +181,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde .withPrimaryPokemonRequirement( CombinationPokemonRequirement.Some( // Must have at least 1 Bug type on team, OR have a bug item somewhere on the team - new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1), - new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1), + new HeldItemRequirement(REQUIRED_ITEMS, 1), new TypeRequirement(PokemonType.BUG, false, 1), ), ) @@ -259,13 +253,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde }, ]; - const requiredItems = [ - generateModifierType(modifierTypes.QUICK_CLAW), - generateModifierType(modifierTypes.GRIP_CLAW), - generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.BUG]), - ]; - - const requiredItemString = requiredItems.map(m => m?.name ?? "unknown").join("/"); + const requiredItemString = REQUIRED_ITEMS.map(m => allHeldItems[m].name ?? "unknown").join("/"); encounter.setDialogueToken("requiredBugItems", requiredItemString); return true; @@ -366,10 +354,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde const modifierOptions: ModifierTypeOption[] = [generateModifierTypeOption(modifierTypes.MASTER_BALL)!]; const specialOptions: ModifierTypeOption[] = []; - if (!globalScene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) { + if (!globalScene.trainerItems.hasItem(TrainerItemId.MEGA_BRACELET)) { modifierOptions.push(generateModifierTypeOption(modifierTypes.MEGA_BRACELET)!); } - if (!globalScene.findModifier(m => m instanceof GigantamaxAccessModifier)) { + if (!globalScene.trainerItems.hasItem(TrainerItemId.DYNAMAX_BAND)) { modifierOptions.push(generateModifierTypeOption(modifierTypes.DYNAMAX_BAND)!); } const nonRareEvolutionModifier = generateModifierTypeOption(modifierTypes.EVOLUTION_ITEM); @@ -415,8 +403,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde .withPrimaryPokemonRequirement( CombinationPokemonRequirement.Some( // Meets one or both of the below reqs - new HeldItemRequirement(["BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier"], 1), - new AttackTypeBoosterHeldItemTypeRequirement(PokemonType.BUG, 1), + new HeldItemRequirement(REQUIRED_ITEMS, 1), ), ) .withDialogue({ @@ -439,25 +426,19 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones - const validItems = pokemon.getHeldItems().filter(item => { - return ( - (item instanceof BypassSpeedChanceModifier || - item instanceof ContactHeldItemTransferChanceModifier || - (item instanceof AttackTypeBoosterModifier && - (item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG)) && - item.isTransferable - ); + const validItems = pokemon.heldItemManager.getTransferableHeldItems().filter(item => { + item in REQUIRED_ITEMS; }); - return validItems.map((modifier: PokemonHeldItemModifier) => { + return validItems.map((item: HeldItemId) => { const option: OptionSelectItem = { - label: modifier.type.name, + label: allHeldItems[item].name, handler: () => { // Pokemon and item selected - encounter.setDialogueToken("selectedItem", modifier.type.name); + encounter.setDialogueToken("selectedItem", allHeldItems[item].name); encounter.misc = { chosenPokemon: pokemon, - chosenModifier: modifier, + chosenItem: item, }; return true; }, @@ -469,12 +450,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde const selectableFilter = (pokemon: Pokemon) => { // If pokemon has valid item, it can be selected const hasValidItem = pokemon.getHeldItems().some(item => { - return ( - item instanceof BypassSpeedChanceModifier || - item instanceof ContactHeldItemTransferChanceModifier || - (item instanceof AttackTypeBoosterModifier && - (item.type as AttackTypeBoosterModifierType).moveType === PokemonType.BUG) - ); + item in REQUIRED_ITEMS; }); if (!hasValidItem) { return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null; @@ -491,10 +467,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; chosenPokemon.loseHeldItem(modifier, false); - globalScene.updateModifiers(true, true); + globalScene.updateItems(true); const bugNet = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!; - bugNet.type.tier = ModifierTier.ROGUE; + bugNet.type.tier = RewardTier.ROGUE; setEncounterRewards({ guaranteedModifierTypeOptions: [bugNet], diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 553c4deb74e..8652ec2b022 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -1,6 +1,5 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, @@ -11,9 +10,7 @@ import { import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; -import { ModifierTier } from "#enums/modifier-tier"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; +import { RewardTier } from "#enums/reward-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { globalScene } from "#app/global-scene"; @@ -24,10 +21,7 @@ import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { AbilityId } from "#enums/ability-id"; -import { - applyAbilityOverrideToPokemon, - applyModifierTypeToPlayerPokemon, -} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { applyAbilityOverrideToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { PokemonType } from "#enums/pokemon-type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -38,8 +32,6 @@ import i18next from "i18next"; import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; import type { PlayerPokemon } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; -import { BerryModifier } from "#app/modifier/modifier"; -import { BerryType } from "#enums/berry-type"; import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { EncounterBattleAnim } from "#app/data/battle-anims"; @@ -49,7 +41,10 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { EncounterAnim } from "#enums/encounter-anims"; import { Challenges } from "#enums/challenges"; import { MoveUseMode } from "#enums/move-use-mode"; -import { allAbilities, modifierTypes } from "#app/data/data-lists"; +import { allAbilities } from "#app/data/data-lists"; +import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id"; +import { getHeldItemTier } from "#app/items/held-item-tiers"; +import { assignItemsFromConfiguration } from "#app/items/held-item-pool"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/clowningAround"; @@ -283,16 +278,16 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder const party = globalScene.getPlayerParty(); let mostHeldItemsPokemon = party[0]; - let count = mostHeldItemsPokemon - .getHeldItems() - .filter(m => m.isTransferable && !(m instanceof BerryModifier)) - .reduce((v, m) => v + m.stackCount, 0); + let count = mostHeldItemsPokemon.heldItemManager + .getTransferableHeldItems() + .filter(m => !isItemInCategory(m, HeldItemCategoryId.BERRY)) + .reduce((v, m) => v + mostHeldItemsPokemon.heldItemManager.getStack(m), 0); for (const pokemon of party) { - const nextCount = pokemon - .getHeldItems() - .filter(m => m.isTransferable && !(m instanceof BerryModifier)) - .reduce((v, m) => v + m.stackCount, 0); + const nextCount = pokemon.heldItemManager + .getTransferableHeldItems() + .filter(m => !isItemInCategory(m, HeldItemCategoryId.BERRY)) + .reduce((v, m) => v + pokemon.heldItemManager.getStack(m), 0); if (nextCount > count) { mostHeldItemsPokemon = pokemon; count = nextCount; @@ -301,16 +296,31 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender()); - const items = mostHeldItemsPokemon.getHeldItems(); + const items = mostHeldItemsPokemon.heldItemManager + .getTransferableHeldItems() + .filter(m => !isItemInCategory(m, HeldItemCategoryId.BERRY)); // Shuffles Berries (if they have any) + const oldBerries = mostHeldItemsPokemon.heldItemManager + .getHeldItems() + .filter(m => isItemInCategory(m, HeldItemCategoryId.BERRY)); + let numBerries = 0; - for (const m of items.filter(m => m instanceof BerryModifier)) { - numBerries += m.stackCount; - globalScene.removeModifier(m); + for (const berry of oldBerries) { + const stack = mostHeldItemsPokemon.heldItemManager.getStack(berry); + numBerries += stack; + mostHeldItemsPokemon.heldItemManager.remove(berry, stack); } - generateItemsOfTier(mostHeldItemsPokemon, numBerries, "Berries"); + assignItemsFromConfiguration( + [ + { + entry: HeldItemCategoryId.BERRY, + count: numBerries, + }, + ], + mostHeldItemsPokemon, + ); // Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm) // For the purpose of this ME, Soothe Bells and Lucky Eggs are counted as Ultra tier @@ -318,20 +328,36 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder let numUltra = 0; let numRogue = 0; - for (const m of items.filter(m => m.isTransferable && !(m instanceof BerryModifier))) { - const type = m.type.withTierFromPool(ModifierPoolType.PLAYER, party); - const tier = type.tier ?? ModifierTier.ULTRA; - if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) { - numRogue += m.stackCount; - globalScene.removeModifier(m); - } else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === ModifierTier.ULTRA) { - numUltra += m.stackCount; - globalScene.removeModifier(m); + for (const m of items) { + const tier = getHeldItemTier(m) ?? RewardTier.ULTRA; + const stack = mostHeldItemsPokemon.heldItemManager.getStack(m); + if (tier === RewardTier.ROGUE) { + numRogue += stack; + } else if (tier === RewardTier.ULTRA) { + numUltra += stack; } + mostHeldItemsPokemon.heldItemManager.remove(m, stack); } - generateItemsOfTier(mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA); - generateItemsOfTier(mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE); + assignItemsFromConfiguration( + [ + { + entry: ultraPool, + count: numUltra, + }, + ], + mostHeldItemsPokemon, + ); + + assignItemsFromConfiguration( + [ + { + entry: roguePool, + count: numRogue, + }, + ], + mostHeldItemsPokemon, + ); }) .withOptionPhase(async () => { leaveEncounterWithoutBattle(true); @@ -487,68 +513,21 @@ function onYesAbilitySwap(resolve) { selectPokemonForOption(onPokemonSelected, onPokemonNotSelected); } -function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: ModifierTier | "Berries") { - // These pools have to be defined at runtime so that modifierTypes exist - // Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon - // This is to prevent "over-generating" a random item of a certain type during item swaps - const ultraPool = [ - [modifierTypes.REVIVER_SEED, 1], - [modifierTypes.GOLDEN_PUNCH, 5], - [modifierTypes.ATTACK_TYPE_BOOSTER, 99], - [modifierTypes.QUICK_CLAW, 3], - [modifierTypes.WIDE_LENS, 3], - ]; +const ultraPool = [ + { entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 1 }, + { entry: HeldItemId.REVIVER_SEED, weight: 1 }, + { entry: HeldItemId.GOLDEN_PUNCH, weight: 1 }, + { entry: HeldItemId.QUICK_CLAW, weight: 1 }, + { entry: HeldItemId.WIDE_LENS, weight: 1 }, +]; - const roguePool = [ - [modifierTypes.LEFTOVERS, 4], - [modifierTypes.SHELL_BELL, 4], - [modifierTypes.SOUL_DEW, 10], - [modifierTypes.SCOPE_LENS, 1], - [modifierTypes.BATON, 1], - [modifierTypes.FOCUS_BAND, 5], - [modifierTypes.KINGS_ROCK, 3], - [modifierTypes.GRIP_CLAW, 5], - ]; - - const berryPool = [ - [BerryType.APICOT, 3], - [BerryType.ENIGMA, 2], - [BerryType.GANLON, 3], - [BerryType.LANSAT, 3], - [BerryType.LEPPA, 2], - [BerryType.LIECHI, 3], - [BerryType.LUM, 2], - [BerryType.PETAYA, 3], - [BerryType.SALAC, 2], - [BerryType.SITRUS, 2], - [BerryType.STARF, 3], - ]; - - let pool: any[]; - if (tier === "Berries") { - pool = berryPool; - } else { - pool = tier === ModifierTier.ULTRA ? ultraPool : roguePool; - } - - for (let i = 0; i < numItems; i++) { - if (pool.length === 0) { - // Stop generating new items if somehow runs out of items to spawn - return; - } - const randIndex = randSeedInt(pool.length); - const newItemType = pool[randIndex]; - let newMod: PokemonHeldItemModifierType; - if (tier === "Berries") { - newMod = generateModifierType(modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType; - } else { - newMod = generateModifierType(newItemType[0]) as PokemonHeldItemModifierType; - } - applyModifierTypeToPlayerPokemon(pokemon, newMod); - // Decrement max stacks and remove from pool if at max - newItemType[1]--; - if (newItemType[1] <= 0) { - pool.splice(randIndex, 1); - } - } -} +const roguePool = [ + { entry: HeldItemId.LEFTOVERS, weight: 1 }, + { entry: HeldItemId.SHELL_BELL, weight: 1 }, + { entry: HeldItemId.SOUL_DEW, weight: 1 }, + { entry: HeldItemId.SCOPE_LENS, weight: 1 }, + { entry: HeldItemId.BATON, weight: 1 }, + { entry: HeldItemId.FOCUS_BAND, weight: 1 }, + { entry: HeldItemId.KINGS_ROCK, weight: 1 }, + { entry: HeldItemId.GRIP_CLAW, weight: 1 }, +]; diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 4056ba3532e..236b4fad9d7 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -16,10 +16,9 @@ import { } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; -import { PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { Challenges } from "#enums/challenges"; +import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/darkDeal"; @@ -149,7 +148,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE const removedPokemon = getRandomPlayerPokemon(true, false, true); // Get all the pokemon's held items - const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier)); + const itemConfig = removedPokemon.heldItemManager.generateHeldItemConfiguration(); globalScene.removePokemonFromPlayerParty(removedPokemon); const encounter = globalScene.currentBattle.mysteryEncounter!; @@ -158,7 +157,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE // Store removed pokemon types encounter.misc = { removedTypes: removedPokemon.getTypes(), - modifiers, + itemConfig: itemConfig, }; }) .withOptionPhase(async () => { @@ -176,7 +175,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE bossTypes = singleTypeChallenges.map(c => (c.value - 1) as PokemonType); } - const bossModifiers: PokemonHeldItemModifier[] = encounter.misc.modifiers; + const bossItemConfig: HeldItemConfiguration = encounter.misc.itemConfig; // Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+ const roll = randSeedInt(100); const starterTier: number | [number, number] = roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [9, 10]; @@ -184,12 +183,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE const pokemonConfig: EnemyPokemonConfig = { species: bossSpecies, isBoss: true, - modifierConfigs: bossModifiers.map(m => { - return { - modifier: m, - stackCount: m.getStackCount(), - }; - }), + heldItemConfig: bossItemConfig, }; if (!isNullOrUndefined(bossSpecies.forms) && bossSpecies.forms.length > 0) { pokemonConfig.formIndex = 0; diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 842fc9d73bd..a516e3265bc 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -9,25 +9,14 @@ import { } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { - generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; -import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier"; -import { - BerryModifier, - HealingBoosterModifier, - LevelIncrementBoosterModifier, - MoneyMultiplierModifier, - PreserveBerryModifier, -} from "#app/modifier/modifier"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; import i18next from "#app/plugins/i18n"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; @@ -37,31 +26,41 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { timedEventManager } from "#app/global-event-manager"; +import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id"; +import { allHeldItems } from "#app/data/data-lists"; +import { TrainerItemId } from "#enums/trainer-item-id"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/delibirdy"; /** Berries only */ -const OPTION_2_ALLOWED_MODIFIERS = ["BerryModifier", "PokemonInstantReviveModifier"]; +const OPTION_2_ALLOWED_MODIFIERS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED]; /** Disallowed items are berries, Reviver Seeds, and Vitamins (form change items and fusion items are not PokemonHeldItemModifiers) */ -const OPTION_3_DISALLOWED_MODIFIERS = [ - "BerryModifier", - "PokemonInstantReviveModifier", - "TerastallizeModifier", - "PokemonBaseStatModifier", - "PokemonBaseStatTotalModifier", -]; +const OPTION_3_DISALLOWED_MODIFIERS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED]; const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2; +async function backupOption() { + globalScene.getPlayerPokemon()?.heldItemManager.add(HeldItemId.SHELL_BELL); + globalScene.playSound("item_fanfare"); + await showEncounterText( + i18next.t("battle:rewardGain", { + modifierName: allHeldItems[HeldItemId.SHELL_BELL].name, + }), + null, + undefined, + true, + ); + doEventReward(); +} + const doEventReward = () => { const event_buff = timedEventManager.getDelibirdyBuff(); if (event_buff.length > 0) { const candidates = event_buff.filter(c => { - const mtype = generateModifierType(modifierTypes[c]); - const existingCharm = globalScene.findModifier(m => m.type.id === mtype?.id); - return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount()); + const fullStack = globalScene.trainerItems.isMaxStack(c); + return !fullStack; }); if (candidates.length > 0) { globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes[randSeedItem(candidates)]); @@ -165,20 +164,11 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with .withOptionPhase(async () => { // Give the player an Amulet Coin // Check if the player has max stacks of that item already - const existing = globalScene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier; + const fullStack = globalScene.trainerItems.isMaxStack(TrainerItemId.AMULET_COIN); - if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { + if (fullStack) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell); - globalScene.playSound("item_fanfare"); - await showEncounterText( - i18next.t("battle:rewardGain", { modifierName: shellBell.name }), - null, - undefined, - true, - ); - doEventReward(); + backupOption(); } else { globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.AMULET_COIN); doEventReward(); @@ -205,19 +195,17 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones - const validItems = pokemon.getHeldItems().filter(it => { - return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable; - }); + const validItems = pokemon.heldItemManager.filterRequestedItems(OPTION_2_ALLOWED_MODIFIERS, true); - return validItems.map((modifier: PokemonHeldItemModifier) => { + return validItems.map((item: HeldItemId) => { const option: OptionSelectItem = { - label: modifier.type.name, + label: allHeldItems[item].name, handler: () => { // Pokemon and item selected - encounter.setDialogueToken("chosenItem", modifier.type.name); + encounter.setDialogueToken("chosenItem", allHeldItems[item].name); encounter.misc = { chosenPokemon: pokemon, - chosenModifier: modifier, + chosenItem: item, }; return true; }, @@ -240,59 +228,35 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with }) .withOptionPhase(async () => { const encounter = globalScene.currentBattle.mysteryEncounter!; - const modifier: BerryModifier | PokemonInstantReviveModifier = encounter.misc.chosenModifier; + const chosenItem: HeldItemId = encounter.misc.chosenItem; const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed - if (modifier instanceof BerryModifier) { + if (isItemInCategory(chosenItem, HeldItemCategoryId.BERRY)) { // Check if the player has max stacks of that Candy Jar already - const existing = globalScene.findModifier( - m => m instanceof LevelIncrementBoosterModifier, - ) as LevelIncrementBoosterModifier; + const fullStack = globalScene.trainerItems.isMaxStack(TrainerItemId.CANDY_JAR); - if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { + if (fullStack) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell); - globalScene.playSound("item_fanfare"); - await showEncounterText( - i18next.t("battle:rewardGain", { - modifierName: shellBell.name, - }), - null, - undefined, - true, - ); - doEventReward(); + backupOption(); } else { globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.CANDY_JAR); doEventReward(); } } else { // Check if the player has max stacks of that Berry Pouch already - const existing = globalScene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier; + const fullStack = globalScene.trainerItems.isMaxStack(TrainerItemId.BERRY_POUCH); - if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { + if (fullStack) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell); - globalScene.playSound("item_fanfare"); - await showEncounterText( - i18next.t("battle:rewardGain", { - modifierName: shellBell.name, - }), - null, - undefined, - true, - ); - doEventReward(); + backupOption(); } else { globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.BERRY_POUCH); doEventReward(); } } - chosenPokemon.loseHeldItem(modifier, false); + chosenPokemon.loseHeldItem(chosenItem, false); leaveEncounterWithoutBattle(true); }) @@ -315,21 +279,17 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones - const validItems = pokemon.getHeldItems().filter(it => { - return ( - !OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable - ); - }); + const validItems = pokemon.heldItemManager.filterRequestedItems(OPTION_3_DISALLOWED_MODIFIERS, true, true); - return validItems.map((modifier: PokemonHeldItemModifier) => { + return validItems.map((item: HeldItemId) => { const option: OptionSelectItem = { - label: modifier.type.name, + label: allHeldItems[item].name, handler: () => { // Pokemon and item selected - encounter.setDialogueToken("chosenItem", modifier.type.name); + encounter.setDialogueToken("chosenItem", allHeldItems[item].name); encounter.misc = { chosenPokemon: pokemon, - chosenModifier: modifier, + chosenItem: item, }; return true; }, @@ -356,20 +316,12 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Check if the player has max stacks of Healing Charm already - const existing = globalScene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier; - if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { + const fullStack = globalScene.trainerItems.isMaxStack(TrainerItemId.HEALING_CHARM); + + if (fullStack) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - await applyModifierTypeToPlayerPokemon(globalScene.getPlayerParty()[0], shellBell); - globalScene.playSound("item_fanfare"); - await showEncounterText( - i18next.t("battle:rewardGain", { modifierName: shellBell.name }), - null, - undefined, - true, - ); - doEventReward(); + backupOption(); } else { globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.HEALING_CHARM); doEventReward(); diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index e8900f8def4..8ad013be6fe 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -7,9 +7,7 @@ import { setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, - generateModifierType, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -35,7 +33,6 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun import { applyAbilityOverrideToPokemon, applyDamageToPokemon, - applyModifierTypeToPlayerPokemon, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -45,8 +42,11 @@ import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Stat } from "#enums/stat"; import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups"; +import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { getNewHeldItemFromCategory } from "#app/items/held-item-pool"; +import { allHeldItems } from "#app/data/data-lists"; import { MoveUseMode } from "#enums/move-use-mode"; -import { allAbilities, modifierTypes } from "#app/data/data-lists"; +import { allAbilities } from "#app/data/data-lists"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/fieryFallout"; @@ -302,16 +302,14 @@ function giveLeadPokemonAttackTypeBoostItem() { const leadPokemon = globalScene.getPlayerParty()?.[0]; if (leadPokemon) { // Generate type booster held item, default to Charcoal if item fails to generate - let boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType; - if (!boosterModifierType) { - boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ - PokemonType.FIRE, - ]) as AttackTypeBoosterModifierType; + let item = getNewHeldItemFromCategory(HeldItemCategoryId.TYPE_ATTACK_BOOSTER, leadPokemon); + if (!item) { + item = HeldItemId.CHARCOAL; } - applyModifierTypeToPlayerPokemon(leadPokemon, boosterModifierType); + leadPokemon.heldItemManager.add(item); const encounter = globalScene.currentBattle.mysteryEncounter!; - encounter.setDialogueToken("itemName", boosterModifierType.name); + encounter.setDialogueToken("itemName", allHeldItems[item].name); encounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender()); queueEncounterMessage(`${namespace}:found_item`); } diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index c53ff610c48..038b3e69de0 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -9,7 +9,7 @@ import { } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import type Pokemon from "#app/field/pokemon"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#enums/modifier-pool-type"; @@ -89,12 +89,12 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder. // Waves 10-40 GREAT, 60-120 ULTRA, 120-160 ROGUE, 160-180 MASTER const tier = globalScene.currentBattle.waveIndex > 160 - ? ModifierTier.MASTER + ? RewardTier.MASTER : globalScene.currentBattle.waveIndex > 120 - ? ModifierTier.ROGUE + ? RewardTier.ROGUE : globalScene.currentBattle.waveIndex > 40 - ? ModifierTier.ULTRA - : ModifierTier.GREAT; + ? RewardTier.ULTRA + : RewardTier.GREAT; regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0); let item: ModifierTypeOption | null = null; // TMs and Candy Jar excluded from possible rewards as they're too swingy in value for a singular item reward diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 9611560fe62..432cc3622fd 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -388,7 +388,7 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise { globalScene.add.existing(pokemon); globalScene.field.add(pokemon); addPokeballOpenParticles(pokemon.x, pokemon.y - 16, pokemon.pokeball); - globalScene.updateModifiers(true); + globalScene.updateItems(true); globalScene.updateFieldScale(); pokemon.showInfo(); pokemon.playAnim(); diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 6bbc1a68772..3c3a8d29475 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -4,7 +4,6 @@ import { setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { TrainerSlot } from "#enums/trainer-slot"; -import { ModifierTier } from "#enums/modifier-tier"; import { MusicPreference } from "#app/system/settings/settings"; import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; @@ -33,13 +32,6 @@ import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; -import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; -import { - HiddenAbilityRateBoosterModifier, - PokemonFormChangeItemModifier, - ShinyRateBoosterModifier, - SpeciesStatBoosterModifier, -} from "#app/modifier/modifier"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import PokemonData from "#app/system/pokemon-data"; import i18next from "i18next"; @@ -53,6 +45,11 @@ import type { PokeballType } from "#enums/pokeball"; import { doShinySparkleAnim } from "#app/field/anims"; import { TrainerType } from "#enums/trainer-type"; import { timedEventManager } from "#app/global-event-manager"; +import { HeldItemCategoryId, type HeldItemId, isItemInCategory } from "#enums/held-item-id"; +import { allHeldItems } from "#app/data/data-lists"; +import { RewardTier } from "#enums/reward-tier"; +import { getHeldItemTier } from "#app/items/held-item-tiers"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/globalTradeSystem"; @@ -215,9 +212,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil const encounter = globalScene.currentBattle.mysteryEncounter!; const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon; const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon; - const modifiers = tradedPokemon - .getHeldItems() - .filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier)); + const heldItemConfig = tradedPokemon.heldItemManager + .generateHeldItemConfiguration() + .filter(ic => !isItemInCategory(ic.entry as HeldItemId, HeldItemCategoryId.SPECIES_STAT_BOOSTER)); // Generate a trainer name const traderName = generateRandomTraderName(); @@ -241,16 +238,12 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil dataSource.variant, dataSource.ivs, dataSource.nature, + heldItemConfig, dataSource, ); globalScene.getPlayerParty().push(newPlayerPokemon); await newPlayerPokemon.loadAssets(); - for (const mod of modifiers) { - mod.pokemonId = newPlayerPokemon.id; - globalScene.addModifier(mod, true, false, false, true); - } - // Show the trade animation await showTradeBackground(); await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon); @@ -283,7 +276,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil if (timedEventManager.isEventActive()) { shinyThreshold.value *= timedEventManager.getShinyMultiplier(); } - globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold }); // Base shiny chance of 512/65536 -> 1/128, affected by events and Shiny Charms // Maximum shiny chance of 4096/65536 -> 1/16, cannot improve further after that @@ -297,7 +290,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil if (tradePokemon.species.abilityHidden) { if (tradePokemon.abilityIndex < hiddenIndex) { const hiddenAbilityChance = new NumberHolder(64); - globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER, { + numberHolder: hiddenAbilityChance, + }); const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); @@ -336,9 +331,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil const encounter = globalScene.currentBattle.mysteryEncounter!; const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon; const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon; - const modifiers = tradedPokemon - .getHeldItems() - .filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier)); + const heldItemConfig = tradedPokemon.heldItemManager + .generateHeldItemConfiguration() + .filter(ic => !isItemInCategory(ic.entry as HeldItemId, HeldItemCategoryId.SPECIES_STAT_BOOSTER)); // Generate a trainer name const traderName = generateRandomTraderName(); @@ -361,16 +356,12 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil dataSource.variant, dataSource.ivs, dataSource.nature, + heldItemConfig, dataSource, ); globalScene.getPlayerParty().push(newPlayerPokemon); await newPlayerPokemon.loadAssets(); - for (const mod of modifiers) { - mod.pokemonId = newPlayerPokemon.id; - globalScene.addModifier(mod, true, false, false, true); - } - // Show the trade animation await showTradeBackground(); await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon); @@ -395,17 +386,15 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones - const validItems = pokemon.getHeldItems().filter(it => { - return it.isTransferable; - }); + const validItems = pokemon.heldItemManager.getTransferableHeldItems(); - return validItems.map((modifier: PokemonHeldItemModifier) => { + return validItems.map((id: HeldItemId) => { const option: OptionSelectItem = { - label: modifier.type.name, + label: allHeldItems[id].name, handler: () => { // Pokemon and item selected - encounter.setDialogueToken("chosenItem", modifier.type.name); - encounter.misc.chosenModifier = modifier; + encounter.setDialogueToken("chosenItem", allHeldItems[id].name); + encounter.misc.chosenHeldItem = id; encounter.misc.chosenPokemon = pokemon; return true; }, @@ -416,10 +405,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil const selectableFilter = (pokemon: Pokemon) => { // If pokemon has items to trade - const meetsReqs = - pokemon.getHeldItems().filter(it => { - return it.isTransferable; - }).length > 0; + const meetsReqs = pokemon.heldItemManager.getTransferableHeldItems().length > 0; if (!meetsReqs) { return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null; } @@ -431,23 +417,15 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil }) .withOptionPhase(async () => { const encounter = globalScene.currentBattle.mysteryEncounter!; - const modifier = encounter.misc.chosenModifier as PokemonHeldItemModifier; + const heldItemId = encounter.misc.chosenHeldItem as HeldItemId; const party = globalScene.getPlayerParty(); const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Check tier of the traded item, the received item will be one tier up - const type = modifier.type.withTierFromPool(ModifierPoolType.PLAYER, party); - let tier = type.tier ?? ModifierTier.GREAT; - // Eggs and White Herb are not in the pool - if (type.id === "WHITE_HERB") { - tier = ModifierTier.GREAT; - } else if (type.id === "LUCKY_EGG") { - tier = ModifierTier.ULTRA; - } else if (type.id === "GOLDEN_EGG") { - tier = ModifierTier.ROGUE; - } + let tier = getHeldItemTier(heldItemId) ?? RewardTier.GREAT; + // Increment tier by 1 - if (tier < ModifierTier.MASTER) { + if (tier < RewardTier.MASTER) { tier++; } @@ -467,8 +445,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil fillRemaining: false, }); - chosenPokemon.loseHeldItem(modifier, false); - await globalScene.updateModifiers(true, true); + chosenPokemon.heldItemManager.remove(heldItemId); + await globalScene.updateItems(true); // Generate a trainer name const traderName = generateRandomTraderName(); diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index ae3d905cf91..f109d9ad7ca 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -7,7 +7,7 @@ import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerPartyTemplates } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; @@ -176,7 +176,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1]; setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], + guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.GREAT, RewardTier.GREAT], fillRemaining: true, }); @@ -207,7 +207,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter encounter.expMultiplier = 0.9; setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], + guaranteedModifierTiers: [RewardTier.ROGUE, RewardTier.ROGUE, RewardTier.ULTRA, RewardTier.GREAT], fillRemaining: true, }); diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 321e65d7008..ca3c3e5aafd 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -16,7 +16,7 @@ import { } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { randSeedInt } from "#app/utils/common"; import { MoveId } from "#enums/move-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -144,7 +144,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) { // Choose between 2 COMMON / 2 GREAT tier items (20%) setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.GREAT, ModifierTier.GREAT], + guaranteedModifierTiers: [RewardTier.COMMON, RewardTier.COMMON, RewardTier.GREAT, RewardTier.GREAT], }); // Display result message then proceed to rewards queueEncounterMessage(`${namespace}:option.1.normal`); @@ -152,7 +152,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) { // Choose between 3 ULTRA tier items (30%) setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA], + guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.ULTRA], }); // Display result message then proceed to rewards queueEncounterMessage(`${namespace}:option.1.good`); @@ -160,7 +160,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) { // Choose between 2 ROGUE tier items (10%) setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE], + guaranteedModifierTiers: [RewardTier.ROGUE, RewardTier.ROGUE], }); // Display result message then proceed to rewards queueEncounterMessage(`${namespace}:option.1.great`); @@ -171,7 +171,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde ) { // Choose 1 MASTER tier item (5%) setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.MASTER], + guaranteedModifierTiers: [RewardTier.MASTER], }); // Display result message then proceed to rewards queueEncounterMessage(`${namespace}:option.1.amazing`); diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index 207e6ca400d..6d47959caac 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -11,7 +11,6 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { TrainerSlot } from "#enums/trainer-slot"; -import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifier/modifier"; import type { EnemyPokemon } from "#app/field/pokemon"; import { PokeballType } from "#enums/pokeball"; import { PlayerGender } from "#enums/player-gender"; @@ -31,6 +30,8 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/safariZone"; @@ -297,7 +298,9 @@ async function summonSafariPokemon() { const hiddenIndex = pokemon.species.ability2 ? 2 : 1; if (pokemon.abilityIndex < hiddenIndex) { const hiddenAbilityChance = new NumberHolder(256); - globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER, { + numberHolder: hiddenAbilityChance, + }); const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); @@ -332,8 +335,7 @@ async function summonSafariPokemon() { // shows up and the IV scanner breaks. For now, we place the IV scanner code // separately so that at least the IV scanner works. - const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); - if (ivScannerModifier) { + if (globalScene.trainerItems.hasItem(TrainerItemId.IV_SCANNER)) { globalScene.phaseManager.pushNew("ScanIvsPhase", pokemon.getBattlerIndex()); } } diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index 967c105c740..cff759b5d60 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -1,5 +1,4 @@ import { - generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, @@ -7,7 +6,6 @@ import { } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { modifierTypes } from "#app/data/data-lists"; import { randSeedInt } from "#app/utils/common"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; @@ -19,7 +17,6 @@ import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { applyDamageToPokemon, - applyModifierTypeToPlayerPokemon, isPokemonValidForEncounterOptionSelection, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -28,6 +25,8 @@ import type { Nature } from "#enums/nature"; import { getNatureName } from "#app/data/nature"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import i18next from "i18next"; +import { getNewVitaminHeldItem } from "#app/items/held-item-pool"; +import { allHeldItems } from "#app/data/data-lists"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/shadyVitaminDealer"; @@ -97,15 +96,12 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui // Update money updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); // Calculate modifiers and dialogue tokens - const modifiers = [ - generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!, - generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!, - ]; - encounter.setDialogueToken("boost1", modifiers[0].name); - encounter.setDialogueToken("boost2", modifiers[1].name); + const items = [getNewVitaminHeldItem(), getNewVitaminHeldItem()]; + encounter.setDialogueToken("boost1", allHeldItems[items[0]].name); + encounter.setDialogueToken("boost2", allHeldItems[items[1]].name); encounter.misc = { chosenPokemon: pokemon, - modifiers: modifiers, + items: items, }; }; @@ -132,10 +128,10 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui // Choose Cheap Option const encounter = globalScene.currentBattle.mysteryEncounter!; const chosenPokemon = encounter.misc.chosenPokemon; - const modifiers = encounter.misc.modifiers; + const items = encounter.misc.items; - for (const modType of modifiers) { - await applyModifierTypeToPlayerPokemon(chosenPokemon, modType); + for (const item of items) { + chosenPokemon.heldItemManager.add(item); } leaveEncounterWithoutBattle(true); @@ -180,15 +176,12 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui // Update money updatePlayerMoney(-(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney); // Calculate modifiers and dialogue tokens - const modifiers = [ - generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!, - generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!, - ]; - encounter.setDialogueToken("boost1", modifiers[0].name); - encounter.setDialogueToken("boost2", modifiers[1].name); + const items = [getNewVitaminHeldItem(), getNewVitaminHeldItem()]; + encounter.setDialogueToken("boost1", allHeldItems[items[0]].name); + encounter.setDialogueToken("boost2", allHeldItems[items[1]].name); encounter.misc = { chosenPokemon: pokemon, - modifiers: modifiers, + items: items, }; }; @@ -203,10 +196,10 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui // Choose Expensive Option const encounter = globalScene.currentBattle.mysteryEncounter!; const chosenPokemon = encounter.misc.chosenPokemon; - const modifiers = encounter.misc.modifiers; + const items = encounter.misc.items; - for (const modType of modifiers) { - await applyModifierTypeToPlayerPokemon(chosenPokemon, modType); + for (const item of items) { + chosenPokemon.heldItemManager.add(item); } leaveEncounterWithoutBattle(true); diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index d6a85dee119..55cefeed695 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -1,5 +1,4 @@ import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; @@ -11,7 +10,6 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils"; import { - generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, @@ -27,10 +25,9 @@ import { AiType } from "#enums/ai-type"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { BerryType } from "#enums/berry-type"; -import { Stat } from "#enums/stat"; import { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; import { randSeedInt } from "#app/utils/common"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveUseMode } from "#enums/move-use-mode"; /** i18n namespace for the encounter */ @@ -78,24 +75,12 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil status: [StatusEffect.SLEEP, 6], // Extra turns on timer for Snorlax's start of fight moves nature: Nature.DOCILE, moveSet: [MoveId.BODY_SLAM, MoveId.CRUNCH, MoveId.SLEEP_TALK, MoveId.REST], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType, - stackCount: randSeedInt(2, 0), - }, - { - modifier: generateModifierType(modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType, - stackCount: randSeedInt(2, 0), - }, + heldItemConfig: [ + { entry: HeldItemId.SITRUS_BERRY, count: 1 }, + { entry: HeldItemId.ENIGMA_BERRY, count: 1 }, + { entry: HeldItemId.HP_UP, count: 1 }, + { entry: HeldItemId.SOOTHE_BELL, count: randSeedInt(2, 0) }, + { entry: HeldItemId.LUCKY_EGG, count: randSeedInt(2, 0) }, ], customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), aiType: AiType.SMART, // Required to ensure Snorlax uses Sleep Talk while it is asleep 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 03c09f6918e..92469964853 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 @@ -614,7 +614,6 @@ function removePokemonFromPartyAndStoreHeldItems(encounter: MysteryEncounter, ch party[chosenIndex] = party[0]; party[0] = chosenPokemon; encounter.misc.originalParty = globalScene.getPlayerParty().slice(1); - encounter.misc.originalPartyHeldItems = encounter.misc.originalParty.map(p => p.getHeldItems()); globalScene["party"] = [chosenPokemon]; } @@ -623,14 +622,7 @@ function restorePartyAndHeldItems() { // Restore original party globalScene.getPlayerParty().push(...encounter.misc.originalParty); - // Restore held items - const originalHeldItems = encounter.misc.originalPartyHeldItems; - for (const pokemonHeldItemsList of originalHeldItems) { - for (const heldItem of pokemonHeldItemsList) { - globalScene.addModifier(heldItem, true, false, false, true); - } - } - globalScene.updateModifiers(true); + globalScene.updateItems(true); } function onGameOver() { diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index f85206bcbc6..670cd76f534 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -5,9 +5,7 @@ import { leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, - generateModifierType, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; @@ -19,15 +17,14 @@ import { Nature } from "#enums/nature"; import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MoveId } from "#enums/move-id"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { BerryType } from "#enums/berry-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveUseMode } from "#enums/move-use-mode"; /** the i18n namespace for the encounter */ @@ -95,23 +92,12 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), nature: Nature.HARDY, moveSet: [MoveId.INFESTATION, MoveId.SALT_CURE, MoveId.GASTRO_ACID, MoveId.HEAL_ORDER], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType, - stackCount: 2, - }, + heldItemConfig: [ + { entry: HeldItemId.SITRUS_BERRY, count: 1 }, + { entry: HeldItemId.ENIGMA_BERRY, count: 1 }, + { entry: HeldItemId.APICOT_BERRY, count: 1 }, + { entry: HeldItemId.GANLON_BERRY, count: 1 }, + { entry: HeldItemId.LUM_BERRY, count: 2 }, ], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { @@ -171,11 +157,11 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder sortedParty.forEach((pokemon, index) => { if (index < 2) { // -15 to the two highest BST mons - modifyPlayerPokemonBST(pokemon, false); + pokemon.heldItemManager.add(HeldItemId.SHUCKLE_JUICE_BAD); encounter.setDialogueToken("highBstPokemon" + (index + 1), pokemon.getNameToRender()); } else { // +10 for the rest - modifyPlayerPokemonBST(pokemon, true); + pokemon.heldItemManager.add(HeldItemId.SHUCKLE_JUICE_GOOD); } }); diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 6d28a710953..a19335dd643 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -1,14 +1,11 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - generateModifierType, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -20,17 +17,18 @@ import { AbilityId } from "#enums/ability-id"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { Nature } from "#enums/nature"; -import { PokemonType } from "#enums/pokemon-type"; -import { BerryType } from "#enums/berry-type"; -import { Stat } from "#enums/stat"; import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import i18next from "i18next"; -import { ModifierTier } from "#enums/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { BattlerTagType } from "#enums/battler-tag-type"; +import { modifierTypes } from "#app/data/data-lists"; +import { RewardTier } from "#enums/reward-tier"; +import { HeldItemId } from "#enums/held-item-id"; + +//TODO: make all items unstealable /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theWinstrateChallenge"; @@ -166,7 +164,7 @@ async function spawnNextTrainerOrEndEncounter() { await showEncounterDialogue(`${namespace}:victory_2`, `${namespace}:speaker`); globalScene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in const machoBrace = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!; - machoBrace.type.tier = ModifierTier.MASTER; + machoBrace.type.tier = RewardTier.MASTER; setEncounterRewards({ guaranteedModifierTypeOptions: [machoBrace], fillRemaining: false, @@ -258,16 +256,9 @@ function getVictorTrainerConfig(): EnemyPartyConfig { abilityIndex: 0, // Guts nature: Nature.ADAMANT, moveSet: [MoveId.FACADE, MoveId.BRAVE_BIRD, MoveId.PROTECT, MoveId.QUICK_ATTACK], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType, - isTransferable: false, - }, - { - modifier: generateModifierType(modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, + heldItemConfig: [ + { entry: HeldItemId.FLAME_ORB, count: 1 }, + { entry: HeldItemId.FOCUS_BAND, count: 2 }, ], }, { @@ -276,16 +267,9 @@ function getVictorTrainerConfig(): EnemyPartyConfig { abilityIndex: 1, // Guts nature: Nature.ADAMANT, moveSet: [MoveId.FACADE, MoveId.OBSTRUCT, MoveId.NIGHT_SLASH, MoveId.FIRE_PUNCH], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType, - isTransferable: false, - }, - { - modifier: generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, + heldItemConfig: [ + { entry: HeldItemId.FLAME_ORB, count: 1 }, + { entry: HeldItemId.LEFTOVERS, count: 2 }, ], }, ], @@ -302,16 +286,9 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig { abilityIndex: 0, // Natural Cure nature: Nature.CALM, moveSet: [MoveId.SYNTHESIS, MoveId.SLUDGE_BOMB, MoveId.GIGA_DRAIN, MoveId.SLEEP_POWDER], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType, - isTransferable: false, - }, - { - modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, + heldItemConfig: [ + { entry: HeldItemId.SOUL_DEW, count: 1 }, + { entry: HeldItemId.QUICK_CLAW, count: 2 }, ], }, { @@ -320,21 +297,9 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig { formIndex: 1, nature: Nature.TIMID, moveSet: [MoveId.PSYSHOCK, MoveId.MOONBLAST, MoveId.SHADOW_BALL, MoveId.WILL_O_WISP], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ - PokemonType.PSYCHIC, - ]) as PokemonHeldItemModifierType, - stackCount: 1, - isTransferable: false, - }, - { - modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ - PokemonType.FAIRY, - ]) as PokemonHeldItemModifierType, - stackCount: 1, - isTransferable: false, - }, + heldItemConfig: [ + { entry: HeldItemId.TWISTED_SPOON, count: 1 }, + { entry: HeldItemId.FAIRY_FEATHER, count: 1 }, ], }, ], @@ -351,17 +316,9 @@ function getViviTrainerConfig(): EnemyPartyConfig { abilityIndex: 3, // Lightning Rod nature: Nature.ADAMANT, moveSet: [MoveId.WATERFALL, MoveId.MEGAHORN, MoveId.KNOCK_OFF, MoveId.REST], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType, - stackCount: 4, - isTransferable: false, - }, + heldItemConfig: [ + { entry: HeldItemId.LUM_BERRY, count: 2 }, + { entry: HeldItemId.HP_UP, count: 4 }, ], }, { @@ -370,16 +327,9 @@ function getViviTrainerConfig(): EnemyPartyConfig { abilityIndex: 1, // Poison Heal nature: Nature.JOLLY, moveSet: [MoveId.SPORE, MoveId.SWORDS_DANCE, MoveId.SEED_BOMB, MoveId.DRAIN_PUNCH], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType, - stackCount: 4, - isTransferable: false, - }, - { - modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType, - isTransferable: false, - }, + heldItemConfig: [ + { entry: HeldItemId.HP_UP, count: 4 }, + { entry: HeldItemId.TOXIC_ORB, count: 1 }, ], }, { @@ -388,13 +338,7 @@ function getViviTrainerConfig(): EnemyPartyConfig { formIndex: 1, nature: Nature.CALM, moveSet: [MoveId.EARTH_POWER, MoveId.FIRE_BLAST, MoveId.YAWN, MoveId.PROTECT], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, - stackCount: 3, - isTransferable: false, - }, - ], + heldItemConfig: [{ entry: HeldItemId.QUICK_CLAW, count: 3 }], }, ], }; @@ -410,12 +354,7 @@ function getVickyTrainerConfig(): EnemyPartyConfig { formIndex: 1, nature: Nature.IMPISH, moveSet: [MoveId.AXE_KICK, MoveId.ICE_PUNCH, MoveId.ZEN_HEADBUTT, MoveId.BULLET_PUNCH], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType, - isTransferable: false, - }, - ], + heldItemConfig: [{ entry: HeldItemId.SHELL_BELL, count: 1 }], }, ], }; @@ -431,13 +370,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig { abilityIndex: 0, // Soundproof nature: Nature.MODEST, moveSet: [MoveId.THUNDERBOLT, MoveId.GIGA_DRAIN, MoveId.FOUL_PLAY, MoveId.THUNDER_WAVE], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, - ], + heldItemConfig: [{ entry: HeldItemId.ZINC, count: 2 }], }, { species: getPokemonSpecies(SpeciesId.SWALOT), @@ -445,51 +378,18 @@ function getVitoTrainerConfig(): EnemyPartyConfig { abilityIndex: 2, // Gluttony nature: Nature.QUIET, moveSet: [MoveId.SLUDGE_BOMB, MoveId.GIGA_DRAIN, MoveId.ICE_BEAM, MoveId.EARTHQUAKE], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.STARF]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SALAC]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LANSAT]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LIECHI]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.PETAYA]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType, - stackCount: 2, - }, - { - modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LEPPA]) as PokemonHeldItemModifierType, - stackCount: 2, - }, + heldItemConfig: [ + { entry: HeldItemId.SITRUS_BERRY, count: 2 }, + { entry: HeldItemId.APICOT_BERRY, count: 2 }, + { entry: HeldItemId.GANLON_BERRY, count: 2 }, + { entry: HeldItemId.STARF_BERRY, count: 2 }, + { entry: HeldItemId.SALAC_BERRY, count: 2 }, + { entry: HeldItemId.LUM_BERRY, count: 2 }, + { entry: HeldItemId.LANSAT_BERRY, count: 2 }, + { entry: HeldItemId.LIECHI_BERRY, count: 2 }, + { entry: HeldItemId.PETAYA_BERRY, count: 2 }, + { entry: HeldItemId.ENIGMA_BERRY, count: 2 }, + { entry: HeldItemId.LEPPA_BERRY, count: 2 }, ], }, { @@ -498,13 +398,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig { abilityIndex: 2, // Tangled Feet nature: Nature.JOLLY, moveSet: [MoveId.DRILL_PECK, MoveId.QUICK_ATTACK, MoveId.THRASH, MoveId.KNOCK_OFF], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, - ], + heldItemConfig: [{ entry: HeldItemId.KINGS_ROCK, count: 2 }], }, { species: getPokemonSpecies(SpeciesId.ALAKAZAM), @@ -512,13 +406,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig { formIndex: 1, nature: Nature.BOLD, moveSet: [MoveId.PSYCHIC, MoveId.SHADOW_BALL, MoveId.FOCUS_BLAST, MoveId.THUNDERBOLT], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, - ], + heldItemConfig: [{ entry: HeldItemId.WIDE_LENS, count: 2 }], }, { species: getPokemonSpecies(SpeciesId.DARMANITAN), @@ -526,13 +414,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig { abilityIndex: 0, // Sheer Force nature: Nature.IMPISH, moveSet: [MoveId.EARTHQUAKE, MoveId.U_TURN, MoveId.FLARE_BLITZ, MoveId.ROCK_SLIDE], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, - stackCount: 2, - isTransferable: false, - }, - ], + heldItemConfig: [{ entry: HeldItemId.QUICK_CLAW, count: 2 }], }, ], }; diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index f76bd66151f..1f86cf31f8f 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -11,7 +11,6 @@ import { getNatureName } from "#app/data/nature"; import { speciesStarterCosts } from "#app/data/balance/starters"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { AbilityAttr } from "#enums/ability-attr"; import PokemonData from "#app/system/pokemon-data"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; @@ -25,7 +24,6 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import type HeldModifierConfig from "#app/@types/held-modifier-config"; import i18next from "i18next"; import { getStatKey } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; @@ -102,8 +100,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde // Spawn light training session with chosen pokemon // Every 50 waves, add +1 boss segment, capping at 5 const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 50), 5); - const modifiers = new ModifiersHolder(); - const config = getEnemyConfig(playerPokemon, segments, modifiers); + const config = getEnemyConfig(playerPokemon, segments); globalScene.removePokemonFromPlayerParty(playerPokemon, false); const onBeforeRewardsPhase = () => { @@ -152,13 +149,8 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde globalScene.gameData.setPokemonCaught(playerPokemon, false); } - // Add pokemon and mods back - globalScene.getPlayerParty().push(playerPokemon); - for (const mod of modifiers.value) { - mod.pokemonId = playerPokemon.id; - globalScene.addModifier(mod, true, false, false, true); - } - globalScene.updateModifiers(true); + // Make held items show up again + globalScene.updateItems(true); queueEncounterMessage(`${namespace}:option.1.finished`); }; @@ -217,8 +209,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde // Spawn medium training session with chosen pokemon // Every 40 waves, add +1 boss segment, capping at 6 const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 40), 6); - const modifiers = new ModifiersHolder(); - const config = getEnemyConfig(playerPokemon, segments, modifiers); + const config = getEnemyConfig(playerPokemon, segments); globalScene.removePokemonFromPlayerParty(playerPokemon, false); const onBeforeRewardsPhase = () => { @@ -227,13 +218,8 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde playerPokemon.setCustomNature(encounter.misc.chosenNature); globalScene.gameData.unlockSpeciesNature(playerPokemon.species, encounter.misc.chosenNature); - // Add pokemon and modifiers back - globalScene.getPlayerParty().push(playerPokemon); - for (const mod of modifiers.value) { - mod.pokemonId = playerPokemon.id; - globalScene.addModifier(mod, true, false, false, true); - } - globalScene.updateModifiers(true); + // Make held items show up again + globalScene.updateItems(true); }; setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase); @@ -308,8 +294,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde // Every 30 waves, add +1 boss segment, capping at 6 // Also starts with +1 to all stats const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 30), 6); - const modifiers = new ModifiersHolder(); - const config = getEnemyConfig(playerPokemon, segments, modifiers); + const config = getEnemyConfig(playerPokemon, segments); config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON]; globalScene.removePokemonFromPlayerParty(playerPokemon, false); @@ -340,13 +325,8 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde playerPokemon.calculateStats(); globalScene.gameData.setPokemonCaught(playerPokemon, false); - // Add pokemon and mods back - globalScene.getPlayerParty().push(playerPokemon); - for (const mod of modifiers.value) { - mod.pokemonId = playerPokemon.id; - globalScene.addModifier(mod, true, false, false, true); - } - globalScene.updateModifiers(true); + // Make held items show up again + globalScene.updateItems(true); }; setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase); @@ -373,18 +353,12 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde ) .build(); -function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifiers: ModifiersHolder): EnemyPartyConfig { +function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number): EnemyPartyConfig { playerPokemon.resetSummonData(); // Passes modifiers by reference - modifiers.value = playerPokemon.getHeldItems(); - const modifierConfigs = modifiers.value.map(mod => { - return { - modifier: mod.clone(), - isTransferable: false, - stackCount: mod.stackCount, - }; - }) as HeldModifierConfig[]; + // TODO: fix various things, like make enemy items untransferable, make sure form change items can come back + const config = playerPokemon.heldItemManager.generateHeldItemConfiguration(); const data = new PokemonData(playerPokemon); return { @@ -396,12 +370,8 @@ function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifier formIndex: playerPokemon.formIndex, level: playerPokemon.level, dataSource: data, - modifierConfigs: modifierConfigs, + heldItemConfig: config, }, ], }; } - -class ModifiersHolder { - public value: PokemonHeldItemModifier[] = []; -} diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index e2a740c4900..34bdaa86249 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -1,14 +1,12 @@ import type { EnemyPartyConfig, EnemyPokemonConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - generateModifierType, + assignItemToFirstFreePokemon, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/data/data-lists"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -17,11 +15,9 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { SpeciesId } from "#enums/species-id"; -import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#app/modifier/modifier"; -import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import i18next from "#app/plugins/i18n"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { BattlerIndex } from "#enums/battler-index"; @@ -29,6 +25,10 @@ import { PokemonMove } from "#app/data/moves/pokemon-move"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { randSeedInt } from "#app/utils/common"; import { MoveUseMode } from "#enums/move-use-mode"; +import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { allHeldItems } from "#app/data/data-lists"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import { allTrainerItems } from "#app/data/data-lists"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/trashToTreasure"; @@ -83,41 +83,13 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde formIndex: 1, // Gmax bossSegmentModifier: 1, // +1 Segment from normal moveSet: [MoveId.GUNK_SHOT, MoveId.STOMPING_TANTRUM, MoveId.HAMMER_ARM, MoveId.PAYBACK], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType, - stackCount: randSeedInt(2, 0), - }, - { - modifier: generateModifierType(modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType, - stackCount: randSeedInt(2, 1), - }, - { - modifier: generateModifierType(modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType, - stackCount: randSeedInt(3, 1), - }, - { - modifier: generateModifierType(modifierTypes.GOLDEN_EGG) as PokemonHeldItemModifierType, - stackCount: randSeedInt(2, 0), - }, + heldItemConfig: [ + { entry: HeldItemCategoryId.BERRY, count: 4 }, + { entry: HeldItemCategoryId.BASE_STAT_BOOST, count: 2 }, + { entry: HeldItemId.TOXIC_ORB, count: randSeedInt(2, 0) }, + { entry: HeldItemId.SOOTHE_BELL, count: randSeedInt(2, 1) }, + { entry: HeldItemId.LUCKY_EGG, count: randSeedInt(3, 1) }, + { entry: HeldItemId.GOLDEN_EGG, count: randSeedInt(2, 0) }, ], }; const config: EnemyPartyConfig = { @@ -157,18 +129,14 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde await transitionMysteryEncounterIntroVisuals(); await tryApplyDigRewardItems(); - const blackSludge = generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [ - SHOP_ITEM_COST_MULTIPLIER, - ]); - const modifier = blackSludge?.newModifier(); - if (modifier) { - await globalScene.addModifier(modifier, false, false, false, true); + const blackSludge = globalScene.trainerItems.add(TrainerItemId.BLACK_SLUDGE); + if (blackSludge) { globalScene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2, }); await showEncounterText( i18next.t("battle:rewardGain", { - modifierName: modifier.type.name, + modifierName: allTrainerItems[TrainerItemId.BLACK_SLUDGE].name, }), null, undefined, @@ -200,7 +168,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde const encounter = globalScene.currentBattle.mysteryEncounter!; setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], + guaranteedModifierTiers: [RewardTier.ROGUE, RewardTier.ROGUE, RewardTier.ULTRA, RewardTier.GREAT], fillRemaining: true, }); encounter.startOfBattleEffects.push( @@ -224,44 +192,18 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde .build(); async function tryApplyDigRewardItems() { - const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - const leftovers = generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType; - const party = globalScene.getPlayerParty(); - // Iterate over the party until an item was successfully given // First leftovers - for (const pokemon of party) { - const heldItems = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, - true, - ) as PokemonHeldItemModifier[]; - const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier; - - if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) { - await applyModifierTypeToPlayerPokemon(pokemon, leftovers); - break; - } - } + assignItemToFirstFreePokemon(HeldItemId.LEFTOVERS, party); // Second leftovers - for (const pokemon of party) { - const heldItems = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, - true, - ) as PokemonHeldItemModifier[]; - const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier; - - if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) { - await applyModifierTypeToPlayerPokemon(pokemon, leftovers); - break; - } - } + assignItemToFirstFreePokemon(HeldItemId.LEFTOVERS, party); globalScene.playSound("item_fanfare"); await showEncounterText( i18next.t("battle:rewardGainCount", { - modifierName: leftovers.name, + modifierName: allHeldItems[HeldItemId.LEFTOVERS].name, count: 2, }), null, @@ -270,23 +212,12 @@ async function tryApplyDigRewardItems() { ); // Only Shell bell - for (const pokemon of party) { - const heldItems = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, - true, - ) as PokemonHeldItemModifier[]; - const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier; - - if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) { - await applyModifierTypeToPlayerPokemon(pokemon, shellBell); - break; - } - } + assignItemToFirstFreePokemon(HeldItemId.SHELL_BELL, party); globalScene.playSound("item_fanfare"); await showEncounterText( i18next.t("battle:rewardGainCount", { - modifierName: shellBell.name, + modifierName: allHeldItems[HeldItemId.SHELL_BELL].name, count: 1, }), null, diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 44e578540dd..7e5cc9568b1 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -1,6 +1,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { + getPartyBerries, getRandomEncounterSpecies, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, @@ -15,10 +16,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; -import { - MoveRequirement, - PersistentModifierRequirement, -} from "#app/data/mystery-encounters/mystery-encounter-requirements"; +import { HeldItemRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { @@ -33,10 +31,11 @@ import { BattlerIndex } from "#enums/battler-index"; import { PokeballType } from "#enums/pokeball"; import { BattlerTagType } from "#enums/battler-tag-type"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import { BerryModifier } from "#app/modifier/modifier"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { MoveUseMode } from "#enums/move-use-mode"; +import type { PokemonItemMap } from "#app/items/held-item-data-types"; +import { HeldItemCategoryId } from "#enums/held-item-id"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/uncommonBreed"; @@ -191,7 +190,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder. ) .withOption( MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically + .withSceneRequirement(new HeldItemRequirement(HeldItemCategoryId.BERRY, 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withDialogue({ buttonLabel: `${namespace}:option.2.label`, buttonTooltip: `${namespace}:option.2.tooltip`, @@ -207,19 +206,18 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder. // Remove 4 random berries from player's party // Get all player berry items, remove from party, and store reference - const berryItems: BerryModifier[] = globalScene.findModifiers( - m => m instanceof BerryModifier, - ) as BerryModifier[]; + + const berryMap = getPartyBerries(); + const stolenBerryMap: PokemonItemMap[] = []; + for (let i = 0; i < 4; i++) { - const index = randSeedInt(berryItems.length); - const randBerry = berryItems[index]; - randBerry.stackCount--; - if (randBerry.stackCount === 0) { - globalScene.removeModifier(randBerry); - berryItems.splice(index, 1); - } + const index = randSeedInt(berryMap.length); + const randBerry = berryMap[index]; + globalScene.getPokemonById(randBerry.pokemonId)?.heldItemManager.remove(randBerry.item.id); + stolenBerryMap.push(randBerry); + berryMap.splice(index, 1); } - await globalScene.updateModifiers(true, true); + await globalScene.updateItems(true); // Pokemon joins the team, with 2 egg moves const encounter = globalScene.currentBattle.mysteryEncounter!; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 9739f71cb0f..68aac8d9d49 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -7,7 +7,6 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils"; import { - generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, @@ -21,11 +20,8 @@ import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "# import type PokemonSpecies from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { allSpecies } from "#app/data/data-lists"; -import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; -import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { achvs } from "#app/system/achv"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; import i18next from "#app/plugins/i18n"; import { @@ -34,15 +30,18 @@ import { } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence"; import { getLevelTotalExp } from "#app/data/exp"; import { Challenges } from "#enums/challenges"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; import PokemonData from "#app/system/pokemon-data"; import { Nature } from "#enums/nature"; -import type HeldModifierConfig from "#app/@types/held-modifier-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { PartyMemberStrength } from "#enums/party-member-strength"; +import type { HeldItemConfiguration, HeldItemSpecs } from "#app/items/held-item-data-types"; +import { assignItemsFromConfiguration } from "#app/items/held-item-pool"; +import { HeldItemId } from "#enums/held-item-id"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/weirdDream"; @@ -262,20 +261,14 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit dataSource.player = false; // Copy held items to new pokemon - const newPokemonHeldItemConfigs: HeldModifierConfig[] = []; - for (const item of transformation.heldItems) { - newPokemonHeldItemConfigs.push({ - modifier: item.clone() as PokemonHeldItemModifier, - stackCount: item.getStackCount(), - isTransferable: false, - }); - } + // TODO: Make items untransferable + const newPokemonHeldItemConfig = transformation.heldItems; + // Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats if (shouldGetOldGateau(newPokemon)) { - newPokemonHeldItemConfigs.push({ - modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU) as PokemonHeldItemModifierType, - stackCount: 1, - isTransferable: false, + newPokemonHeldItemConfig.push({ + entry: HeldItemId.OLD_GATEAU, + count: 1, }); } @@ -284,7 +277,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit isBoss: newPokemon.getSpeciesForm().getBaseStatTotal() > NON_LEGENDARY_BST_THRESHOLD, level: previousPokemon.level, dataSource: dataSource, - modifierConfigs: newPokemonHeldItemConfigs, + heldItemConfig: newPokemonHeldItemConfig, }; enemyPokemonConfigs.push(enemyConfig); @@ -316,12 +309,12 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit setEncounterRewards( { guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.GREAT, - ModifierTier.GREAT, + RewardTier.ROGUE, + RewardTier.ROGUE, + RewardTier.ULTRA, + RewardTier.ULTRA, + RewardTier.GREAT, + RewardTier.GREAT, ], fillRemaining: false, }, @@ -365,7 +358,7 @@ interface PokemonTransformation { previousPokemon: PlayerPokemon; newSpecies: PokemonSpecies; newPokemon: PlayerPokemon; - heldItems: PokemonHeldItemModifier[]; + heldItems: HeldItemConfiguration; } function getTeamTransformations(): PokemonTransformation[] { @@ -390,9 +383,7 @@ function getTeamTransformations(): PokemonTransformation[] { for (let i = 0; i < numPokemon; i++) { const removed = removedPokemon[i]; const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id); - pokemonTransformations[index].heldItems = removed - .getHeldItems() - .filter(m => !(m instanceof PokemonFormChangeItemModifier)); + pokemonTransformations[index].heldItems = removed.heldItemManager.generateHeldItemConfiguration(); const bst = removed.getSpeciesForm().getBaseStatTotal(); let newBstRange: [number, number]; @@ -448,17 +439,14 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) { } // Copy old items to new pokemon - for (const item of transformation.heldItems) { - item.pokemonId = newPokemon.id; - globalScene.addModifier(item, false, false, false, true); - } + const heldItemConfiguration = transformation.heldItems; + // Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats if (shouldGetOldGateau(newPokemon)) { - const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU(); - const modifier = modType?.newModifier(newPokemon); - if (modifier) { - globalScene.addModifier(modifier, false, false, false, true); - } + heldItemConfiguration.push({ + entry: HeldItemId.OLD_GATEAU, + count: 1, + }); } newPokemon.calculateStats(); @@ -499,7 +487,9 @@ async function postProcessTransformedPokemon( const hiddenIndex = newPokemon.species.ability2 ? 2 : 1; if (newPokemon.abilityIndex < hiddenIndex) { const hiddenAbilityChance = new NumberHolder(256); - globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER, { + numberHolder: hiddenAbilityChance, + }); const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index 07fd155b2b2..ae4ae7ee02c 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -8,14 +8,14 @@ import { StatusEffect } from "#enums/status-effect"; import { PokemonType } from "#enums/pokemon-type"; import { WeatherType } from "#enums/weather-type"; import type { PlayerPokemon } from "#app/field/pokemon"; -import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; -import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import { coerceArray, isNullOrUndefined } from "#app/utils/common"; import type { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { TimeOfDay } from "#enums/time-of-day"; +import type { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { allHeldItems } from "#app/data/data-lists"; export interface EncounterRequirement { meetsRequirement(): boolean; // Boolean to see if a requirement is met @@ -351,39 +351,6 @@ export class PartySizeRequirement extends EncounterSceneRequirement { } } -export class PersistentModifierRequirement extends EncounterSceneRequirement { - requiredHeldItemModifiers: string[]; - minNumberOfItems: number; - - constructor(heldItem: string | string[], minNumberOfItems = 1) { - super(); - this.minNumberOfItems = minNumberOfItems; - this.requiredHeldItemModifiers = coerceArray(heldItem); - } - - override meetsRequirement(): boolean { - const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon) || this.requiredHeldItemModifiers?.length < 0) { - return false; - } - let modifierCount = 0; - for (const modifier of this.requiredHeldItemModifiers) { - const matchingMods = globalScene.findModifiers(m => m.constructor.name === modifier); - if (matchingMods?.length > 0) { - for (const matchingMod of matchingMods) { - modifierCount += matchingMod.stackCount; - } - } - } - - return modifierCount >= this.minNumberOfItems; - } - - override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] { - return ["requiredItem", this.requiredHeldItemModifiers[0]]; - } -} - export class MoneyRequirement extends EncounterSceneRequirement { requiredMoney: number; // Static value scalingMultiplier: number; // Calculates required money based off wave index @@ -833,72 +800,13 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen } export class HeldItemRequirement extends EncounterPokemonRequirement { - requiredHeldItemModifiers: string[]; - minNumberOfPokemon: number; - invertQuery: boolean; - requireTransferable: boolean; - - constructor(heldItem: string | string[], minNumberOfPokemon = 1, invertQuery = false, requireTransferable = true) { - super(); - this.minNumberOfPokemon = minNumberOfPokemon; - this.invertQuery = invertQuery; - this.requiredHeldItemModifiers = coerceArray(heldItem); - this.requireTransferable = requireTransferable; - } - - override meetsRequirement(): boolean { - const partyPokemon = globalScene.getPlayerParty(); - if (isNullOrUndefined(partyPokemon)) { - return false; - } - return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; - } - - override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { - if (!this.invertQuery) { - return partyPokemon.filter(pokemon => - this.requiredHeldItemModifiers.some(heldItem => { - return pokemon.getHeldItems().some(it => { - return it.constructor.name === heldItem && (!this.requireTransferable || it.isTransferable); - }); - }), - ); - } - // for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers - // E.g. functions as a blacklist - return partyPokemon.filter( - pokemon => - pokemon.getHeldItems().filter(it => { - return ( - !this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) && - (!this.requireTransferable || it.isTransferable) - ); - }).length > 0, - ); - } - - override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { - const requiredItems = pokemon?.getHeldItems().filter(it => { - return ( - this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) && - (!this.requireTransferable || it.isTransferable) - ); - }); - if (requiredItems && requiredItems.length > 0) { - return ["heldItem", requiredItems[0].type.name]; - } - return ["heldItem", ""]; - } -} - -export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRequirement { - requiredHeldItemTypes: PokemonType[]; + requiredHeldItems: HeldItemId[] | HeldItemCategoryId[]; minNumberOfPokemon: number; invertQuery: boolean; requireTransferable: boolean; constructor( - heldItemTypes: PokemonType | PokemonType[], + heldItem: HeldItemId | HeldItemId[] | HeldItemCategoryId | HeldItemCategoryId[], minNumberOfPokemon = 1, invertQuery = false, requireTransferable = true, @@ -906,7 +814,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe super(); this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - this.requiredHeldItemTypes = coerceArray(heldItemTypes); + this.requiredHeldItems = coerceArray(heldItem); this.requireTransferable = requireTransferable; } @@ -921,14 +829,9 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { if (!this.invertQuery) { return partyPokemon.filter(pokemon => - this.requiredHeldItemTypes.some(heldItemType => { - return pokemon.getHeldItems().some(it => { - return ( - it instanceof AttackTypeBoosterModifier && - (it.type as AttackTypeBoosterModifierType).moveType === heldItemType && - (!this.requireTransferable || it.isTransferable) - ); - }); + this.requiredHeldItems.some(heldItem => { + (pokemon.heldItemManager.hasItem(heldItem) || pokemon.heldItemManager.hasItemCategory(heldItem)) && + (!this.requireTransferable || allHeldItems[heldItem].isTransferable); }), ); } @@ -936,30 +839,24 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe // E.g. functions as a blacklist return partyPokemon.filter( pokemon => - pokemon.getHeldItems().filter(it => { - return !this.requiredHeldItemTypes.some( - heldItemType => - it instanceof AttackTypeBoosterModifier && - (it.type as AttackTypeBoosterModifierType).moveType === heldItemType && - (!this.requireTransferable || it.isTransferable), + pokemon.getHeldItems().filter(item => { + return ( + !this.requiredHeldItems.some(heldItem => item === heldItem) && + (!this.requireTransferable || allHeldItems[item].isTransferable) ); }).length > 0, ); } override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { - const requiredItems = pokemon?.getHeldItems().filter(it => { + const requiredItems = pokemon?.getHeldItems().filter(item => { return ( - this.requiredHeldItemTypes.some( - heldItemType => - it instanceof AttackTypeBoosterModifier && - (it.type as AttackTypeBoosterModifierType).moveType === heldItemType, - ) && - (!this.requireTransferable || it.isTransferable) + this.requiredHeldItems.some(heldItem => item === heldItem) && + (!this.requireTransferable || allHeldItems[item].isTransferable) ); }); if (requiredItems && requiredItems.length > 0) { - return ["heldItem", requiredItems[0].type.name]; + return ["heldItem", allHeldItems[requiredItems[0]].name]; } return ["heldItem", ""]; } diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index bd2dfa998f4..71d02ced5d2 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -1,5 +1,4 @@ import type Battle from "#app/battle"; -import { BattleType } from "#enums/battle-type"; import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes"; import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/constants"; @@ -11,12 +10,7 @@ import { EnemyPokemon } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { FieldPosition } from "#enums/field-position"; import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type"; -import { - getPartyLuckValue, - ModifierTypeGenerator, - ModifierTypeOption, - regenerateModifierPoolThresholds, -} from "#app/modifier/modifier-type"; +import { getPartyLuckValue, ModifierTypeGenerator, ModifierTypeOption } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import type PokemonData from "#app/system/pokemon-data"; @@ -44,7 +38,6 @@ import type PokemonSpecies from "#app/data/pokemon-species"; import type { IEggOptions } from "#app/data/egg"; import { Egg } from "#app/data/egg"; import type { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; -import type HeldModifierConfig from "#app/@types/held-modifier-config"; import type { Variant } from "#app/sprites/variant"; import { StatusEffect } from "#enums/status-effect"; import { globalScene } from "#app/global-scene"; @@ -53,6 +46,9 @@ import { PokemonType } from "#enums/pokemon-type"; import { getNatureName } from "#app/data/nature"; import { getPokemonNameWithAffix } from "#app/messages"; import { timedEventManager } from "#app/global-event-manager"; +import type { HeldItemConfiguration, PokemonItemMap } from "#app/items/held-item-data-types"; +import { HeldItemCategoryId, type HeldItemId, isItemInCategory } from "#enums/held-item-id"; +import { allHeldItems } from "#app/data/data-lists"; /** * Animates exclamation sprite over trainer's head at start of encounter @@ -102,7 +98,7 @@ export interface EnemyPokemonConfig { /** Can set just the status, or pass a timer on the status turns */ status?: StatusEffect | [StatusEffect, number]; mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void; - modifierConfigs?: HeldModifierConfig[]; + heldItemConfig?: HeldItemConfiguration; tags?: BattlerTagType[]; dataSource?: PokemonData; tera?: PokemonType; @@ -200,6 +196,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): battle.enemyLevels.forEach((level, e) => { let enemySpecies: PokemonSpecies | undefined; + let heldItemConfig: HeldItemConfiguration = []; let dataSource: PokemonData | undefined; let isBoss = false; if (!loaded) { @@ -211,12 +208,14 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): dataSource = config.dataSource; enemySpecies = config.species; isBoss = config.isBoss; + heldItemConfig = config.heldItemConfig ?? []; battle.enemyParty[e] = globalScene.addEnemyPokemon( enemySpecies, level, TrainerSlot.TRAINER, isBoss, false, + heldItemConfig, dataSource, ); } else { @@ -226,6 +225,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): if (partyConfig?.pokemonConfigs && e < partyConfig.pokemonConfigs.length) { const config = partyConfig.pokemonConfigs[e]; level = config.level ? config.level : level; + heldItemConfig = config.heldItemConfig ?? []; dataSource = config.dataSource; enemySpecies = config.species; isBoss = config.isBoss; @@ -242,6 +242,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): TrainerSlot.NONE, isBoss, false, + heldItemConfig, dataSource, ); } @@ -428,16 +429,6 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): enemyPokemon_2.x += 300; } }); - if (!loaded) { - regenerateModifierPoolThresholds( - globalScene.getEnemyField(), - battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, - ); - const customModifierTypes = partyConfig?.pokemonConfigs - ?.filter(config => config?.modifierConfigs) - .map(config => config.modifierConfigs!); - globalScene.generateEnemyModifiers(customModifierTypes); - } } /** @@ -1305,3 +1296,29 @@ export function calculateRareSpawnAggregateStats(luckValue: number) { console.log(stats); } + +// Iterate over the party until an item is successfully given +export function assignItemToFirstFreePokemon(item: HeldItemId, party: Pokemon[]): void { + for (const pokemon of party) { + const stack = pokemon.heldItemManager.getStack(item); + if (stack < allHeldItems[item].getMaxStackCount()) { + pokemon.heldItemManager.add(item); + return; + } + } +} + +// Creates an item map of berries to pokemon, storing each berry separately (splitting up stacks) +export function getPartyBerries(): PokemonItemMap[] { + const pokemonItems: PokemonItemMap[] = []; + globalScene.getPlayerParty().forEach(pokemon => { + const berries = pokemon.getHeldItems().filter(item => isItemInCategory(item, HeldItemCategoryId.BERRY)); + berries.forEach(berryId => { + const berryStack = pokemon.heldItemManager.getStack(berryId); + for (let i = 1; i <= berryStack; i++) { + pokemonItems.push({ item: { id: berryId, stack: 1 }, pokemonId: pokemon.id }); + } + }); + }); + return pokemonItems; +} diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index 53088043777..92628b46314 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -1,7 +1,6 @@ import { globalScene } from "#app/global-scene"; import i18next from "i18next"; import { isNullOrUndefined, randSeedInt } from "#app/utils/common"; -import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { @@ -28,8 +27,6 @@ import { showEncounterText, } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getPokemonNameWithAffix } from "#app/messages"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/data/data-lists"; import { Gender } from "#app/data/gender"; import type { PermanentStat } from "#enums/stat"; import { SummaryUiMode } from "#app/ui/summary-ui-handler"; @@ -37,6 +34,7 @@ import { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; import type { AbilityId } from "#enums/ability-id"; import type { PokeballType } from "#enums/pokeball"; import { StatusEffect } from "#enums/status-effect"; +import type { HeldItemId } from "#enums/held-item-id"; /** Will give +1 level every 10 waves */ export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1; @@ -369,60 +367,13 @@ export function applyHealToPokemon(pokemon: PlayerPokemon, heal: number) { applyHpChangeToPokemon(pokemon, heal); } -/** - * Will modify all of a Pokemon's base stats by a flat value - * Base stats can never go below 1 - * @param pokemon - * @param value - */ -export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, good: boolean) { - const modType = modifierTypes - .MYSTERY_ENCOUNTER_SHUCKLE_JUICE() - .generateType(globalScene.getPlayerParty(), [good ? 10 : -15]) - ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE); - const modifier = modType?.newModifier(pokemon); - if (modifier) { - globalScene.addModifier(modifier, false, false, false, true); - pokemon.calculateStats(); +export function applyHeldItemWithFallback(pokemon: Pokemon, item: HeldItemId, fallbackItem?: HeldItemId) { + const added = pokemon.heldItemManager.add(item); + if (!added && fallbackItem) { + pokemon.heldItemManager.add(fallbackItem); } } -/** - * Will attempt to add a new modifier to a Pokemon. - * If the Pokemon already has max stacks of that item, it will instead apply 'fallbackModifierType', if specified. - * @param scene - * @param pokemon - * @param modType - * @param fallbackModifierType - */ -export async function applyModifierTypeToPlayerPokemon( - pokemon: PlayerPokemon, - modType: PokemonHeldItemModifierType, - fallbackModifierType?: PokemonHeldItemModifierType, -) { - // Check if the Pokemon has max stacks of that item already - const modifier = modType.newModifier(pokemon); - const existing = globalScene.findModifier( - m => - m instanceof PokemonHeldItemModifier && - m.type.id === modType.id && - m.pokemonId === pokemon.id && - m.matchType(modifier), - ) as PokemonHeldItemModifier; - - // At max stacks - if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { - if (!fallbackModifierType) { - return; - } - - // Apply fallback - return applyModifierTypeToPlayerPokemon(pokemon, fallbackModifierType); - } - - globalScene.addModifier(modifier, false, false, false, true); -} - /** * Alternative to using AttemptCapturePhase * Assumes player sprite is visible on the screen (this is intended for non-combat uses) @@ -687,20 +638,10 @@ export async function catchPokemon( } }; const addToParty = (slotIndex?: number) => { - const newPokemon = pokemon.addToParty(pokeballType, slotIndex); - const modifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); + pokemon.addToParty(pokeballType, slotIndex); if (globalScene.getPlayerParty().filter(p => p.isShiny()).length === 6) { globalScene.validateAchv(achvs.SHINY_PARTY); } - Promise.all(modifiers.map(m => globalScene.addModifier(m, true))).then(() => { - globalScene.updateModifiers(true); - removePokemon(); - if (newPokemon) { - newPokemon.loadAssets().then(end); - } else { - end(); - } - }); }; Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => { if (globalScene.getPlayerParty().length === 6) { @@ -725,6 +666,7 @@ export async function catchPokemon( pokemon.variant, pokemon.ivs, pokemon.nature, + pokemon.heldItemManager.generateHeldItemConfiguration(), pokemon, ); globalScene.ui.setMode( diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index a479fd8068a..b39b29561b4 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -1,4 +1,5 @@ import { globalScene } from "#app/global-scene"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; import { NumberHolder } from "#app/utils/common"; import { PokeballType } from "#enums/pokeball"; import i18next from "i18next"; @@ -93,7 +94,9 @@ export function getCriticalCaptureChance(modifiedCatchRate: number): number { } const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr); const catchingCharmMultiplier = new NumberHolder(1); - globalScene.findModifier(m => m.is("CriticalCatchChanceBoosterModifier"))?.apply(catchingCharmMultiplier); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.CRITICAL_CATCH_CHANCE_BOOSTER, { + numberHolder: catchingCharmMultiplier, + }); const dexMultiplier = globalScene.gameMode.isDaily || dexCount > 800 ? 2.5 diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index ea129454034..b21bf78a801 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -25,6 +25,11 @@ import { type SpeciesFormChangeTrigger, SpeciesFormChangeWeatherTrigger, } from "./pokemon-forms/form-change-triggers"; +import i18next from "i18next"; + +export function formChangeItemName(id: FormChangeItem) { + return i18next.t(`modifierType:FormChangeItem.${FormChangeItem[id]}`); +} export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean; export type SpeciesFormChangeConditionEnforceFunc = (p: Pokemon) => void; diff --git a/src/data/pokemon-forms/form-change-triggers.ts b/src/data/pokemon-forms/form-change-triggers.ts index 3726781d9e3..4c3f103f88f 100644 --- a/src/data/pokemon-forms/form-change-triggers.ts +++ b/src/data/pokemon-forms/form-change-triggers.ts @@ -3,7 +3,6 @@ import { coerceArray, type Constructor } from "#app/utils/common"; import type { TimeOfDay } from "#enums/time-of-day"; import type Pokemon from "#app/field/pokemon"; import type { SpeciesFormChange } from "#app/data/pokemon-forms"; -import type { PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { getPokemonNameWithAffix } from "#app/messages"; import { globalScene } from "#app/global-scene"; import { FormChangeItem } from "#enums/form-change-item"; @@ -77,16 +76,12 @@ export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger { } canChange(pokemon: Pokemon): boolean { - return !!globalScene.findModifier(r => { - // Assume that if m has the `formChangeItem` property, then it is a PokemonFormChangeItemModifier - const m = r as PokemonFormChangeItemModifier; - return ( - "formChangeItem" in m && - m.pokemonId === pokemon.id && - m.formChangeItem === this.item && - m.active === this.active - ); - }); + const matchItem = pokemon.heldItemManager.formChangeItems[this.item]; + if (!matchItem) { + return false; + } + console.log("CAN CHANGE FORMS:", matchItem.active === this.active); + return matchItem.active === this.active; } } diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 7c8b5b29fcd..0625ded7df0 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -49,7 +49,7 @@ import type { EnemyPokemon } from "#app/field/pokemon"; import type { EvilTeam } from "./evil-admin-trainer-pools"; import type { PartyMemberFunc, - GenModifiersFunc, + GenTrainerItemsFunc, GenAIFunc, PartyTemplateFunc, TrainerTierPools, @@ -118,7 +118,7 @@ export class TrainerConfig { public femaleEncounterBgm: string; public doubleEncounterBgm: string; public victoryBgm: string; - public genModifiersFunc: GenModifiersFunc; + public genModifiersFunc: GenTrainerItemsFunc; public genAIFuncs: GenAIFunc[] = []; public modifierRewardFuncs: ModifierTypeFunc[] = []; public partyTemplates: TrainerPartyTemplate[]; @@ -470,7 +470,7 @@ export class TrainerConfig { return this; } - setGenModifiersFunc(genModifiersFunc: GenModifiersFunc): TrainerConfig { + setGenModifiersFunc(genModifiersFunc: GenTrainerItemsFunc): TrainerConfig { this.genModifiersFunc = genModifiersFunc; return this; } @@ -1004,6 +1004,7 @@ export function getRandomPartyMemberFunc( undefined, false, undefined, + undefined, postProcess, ); }; @@ -1028,7 +1029,16 @@ function getSpeciesFilterRandomPartyMemberFunc( .getTrainerSpeciesForLevel(level, true, strength, waveIndex), ); - return globalScene.addEnemyPokemon(species, level, trainerSlot, undefined, false, undefined, postProcess); + return globalScene.addEnemyPokemon( + species, + level, + trainerSlot, + undefined, + false, + undefined, + undefined, + postProcess, + ); }; } diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts new file mode 100644 index 00000000000..944d76b0a0f --- /dev/null +++ b/src/enums/held-item-id.ts @@ -0,0 +1,147 @@ +// TODO: make category the lower 2 bytes +export const HeldItemId = { + NONE: 0x0000, + + // Berries + SITRUS_BERRY: 0x0101, + LUM_BERRY: 0x0102, + ENIGMA_BERRY: 0x0103, + LIECHI_BERRY: 0x0104, + GANLON_BERRY: 0x0105, + PETAYA_BERRY: 0x0106, + APICOT_BERRY: 0x0107, + SALAC_BERRY: 0x0108, + LANSAT_BERRY: 0x0109, + STARF_BERRY: 0x010A, + LEPPA_BERRY: 0x010B, + + // Other items that are consumed + REVIVER_SEED: 0x0201, + WHITE_HERB: 0x0202, + + // Type Boosters + SILK_SCARF: 0x0301, + BLACK_BELT: 0x0302, + SHARP_BEAK: 0x0303, + POISON_BARB: 0x0304, + SOFT_SAND: 0x0305, + HARD_STONE: 0x0306, + SILVER_POWDER: 0x0307, + SPELL_TAG: 0x0308, + METAL_COAT: 0x0309, + CHARCOAL: 0x030A, + MYSTIC_WATER: 0x030B, + MIRACLE_SEED: 0x030C, + MAGNET: 0x030D, + TWISTED_SPOON: 0x030E, + NEVER_MELT_ICE: 0x030F, + DRAGON_FANG: 0x0310, + BLACK_GLASSES: 0x0311, + FAIRY_FEATHER: 0x0312, + + // Species Stat Boosters + LIGHT_BALL: 0x0401, + THICK_CLUB: 0x0402, + METAL_POWDER: 0x0403, + QUICK_POWDER: 0x0404, + DEEP_SEA_SCALE: 0x0405, + DEEP_SEA_TOOTH: 0x0406, + + // Crit Boosters + SCOPE_LENS: 0x0501, + LEEK: 0x0502, + + // Items increasing gains + LUCKY_EGG: 0x0601, + GOLDEN_EGG: 0x0602, + SOOTHE_BELL: 0x0603, + + // Unique items + FOCUS_BAND: 0x0701, + QUICK_CLAW: 0x0702, + KINGS_ROCK: 0x0703, + LEFTOVERS: 0x0704, + SHELL_BELL: 0x0705, + MYSTICAL_ROCK: 0x0706, + WIDE_LENS: 0x0707, + MULTI_LENS: 0x0708, + GOLDEN_PUNCH: 0x0709, + GRIP_CLAW: 0x070A, + TOXIC_ORB: 0x070B, + FLAME_ORB: 0x070C, + SOUL_DEW: 0x070D, + BATON: 0x070E, + MINI_BLACK_HOLE: 0x070F, + EVIOLITE: 0x0710, + + // Vitamins + HP_UP: 0x0801, + PROTEIN: 0x0802, + IRON: 0x0803, + CALCIUM: 0x0804, + ZINC: 0x0805, + CARBOS: 0x0806, + + // Other stat boosting items + SHUCKLE_JUICE_GOOD: 0x0901, + SHUCKLE_JUICE_BAD: 0x0902, + OLD_GATEAU: 0x0903, + MACHO_BRACE: 0x0904, + + // Evo trackers + GIMMIGHOUL_EVO_TRACKER: 0x0A01, +}; + +export type HeldItemId = (typeof HeldItemId)[keyof typeof HeldItemId]; + +type HeldItemName = keyof typeof HeldItemId; +type HeldItemValue = typeof HeldItemId[HeldItemName]; + +// Use a type-safe reducer to force number keys and values +export const HeldItemNames: Record = Object.entries(HeldItemId).reduce( + (acc, [key, value]) => { + acc[value as HeldItemValue] = key as HeldItemName; + return acc; + }, + {} as Record +); + + +export const HeldItemCategoryId = { + NONE: 0x0000, + BERRY: 0x0100, + CONSUMABLE: 0x0200, + TYPE_ATTACK_BOOSTER: 0x0300, + SPECIES_STAT_BOOSTER: 0x0400, + CRIT_BOOSTER: 0x0500, + GAIN_INCREASE: 0x0600, + UNIQUE: 0x0700, + VITAMIN: 0x0800, + BASE_STAT_BOOST: 0x0900, + EVO_TRACKER: 0x0A00, +}; + +export type HeldItemCategoryId = (typeof HeldItemCategoryId)[keyof typeof HeldItemCategoryId]; + +const ITEM_CATEGORY_MASK = 0xFF00 + +export function getHeldItemCategory(itemId: HeldItemId): HeldItemCategoryId { + return itemId & ITEM_CATEGORY_MASK; +} + +export function isCategoryId(categoryId: HeldItemCategoryId): boolean { + return (categoryId & ITEM_CATEGORY_MASK) === categoryId; +} + +export function isItemInCategory(itemId: HeldItemId, category: HeldItemCategoryId): boolean { + return getHeldItemCategory(itemId) === category; +} + +export function isItemInRequested( + itemId: HeldItemId, + requestedItems: (HeldItemCategoryId | HeldItemId)[] +): boolean { + return requestedItems.some(entry => { + itemId === entry || (itemId & ITEM_CATEGORY_MASK) === entry + }); +} diff --git a/src/enums/modifier-pool-type.ts b/src/enums/modifier-pool-type.ts index 0d2b92ba80d..6513dc45ac1 100644 --- a/src/enums/modifier-pool-type.ts +++ b/src/enums/modifier-pool-type.ts @@ -1,7 +1,13 @@ export enum ModifierPoolType { PLAYER, +} + +export enum HeldItemPoolType { WILD, TRAINER, - ENEMY_BUFF, - DAILY_STARTER + DAILY_STARTER, } + +export enum TrainerItemPoolType { + ENEMY_BUFF, +} \ No newline at end of file diff --git a/src/enums/modifier-tier.ts b/src/enums/reward-tier.ts similarity index 68% rename from src/enums/modifier-tier.ts rename to src/enums/reward-tier.ts index d8a75e41b0a..e7ccc1d9166 100644 --- a/src/enums/modifier-tier.ts +++ b/src/enums/reward-tier.ts @@ -1,4 +1,4 @@ -export enum ModifierTier { +export enum RewardTier { COMMON, GREAT, ULTRA, diff --git a/src/enums/trainer-item-id.ts b/src/enums/trainer-item-id.ts new file mode 100644 index 00000000000..8069da2836e --- /dev/null +++ b/src/enums/trainer-item-id.ts @@ -0,0 +1,68 @@ +export const TrainerItemId = { + NONE: 0x0000, + + MAP: 0x0B01, + IV_SCANNER: 0x0B02, + LOCK_CAPSULE: 0x0B03, + MEGA_BRACELET: 0x0B04, + DYNAMAX_BAND: 0x0B05, + TERA_ORB: 0x0B06, + + GOLDEN_POKEBALL: 0x0B07, + + OVAL_CHARM: 0x0B08, + EXP_SHARE: 0x0B09, + EXP_BALANCE: 0x0B0A, + + CANDY_JAR: 0x0B0B, + BERRY_POUCH: 0x0B0C, + + HEALING_CHARM: 0x0B0D, + EXP_CHARM: 0x0B0E, + SUPER_EXP_CHARM: 0x0B0F, + GOLDEN_EXP_CHARM: 0x0B10, + AMULET_COIN: 0x0B11, + + ABILITY_CHARM: 0x0B12, + SHINY_CHARM: 0x0B13, + CATCHING_CHARM: 0x0B14, + + BLACK_SLUDGE: 0x0B15, + GOLDEN_BUG_NET: 0x0B16, + + LURE: 0x0C01, + SUPER_LURE: 0x0C02, + MAX_LURE: 0x0C03, + + X_ATTACK: 0x0D01, + X_DEFENSE: 0x0D02, + X_SP_ATK: 0x0D03, + X_SP_DEF: 0x0D04, + X_SPEED: 0x0D05, + X_ACCURACY: 0x0D06, + DIRE_HIT: 0x0D07, + + ENEMY_DAMAGE_BOOSTER: 0x0E01, + ENEMY_DAMAGE_REDUCTION: 0x0E02, + ENEMY_HEAL: 0x0E03, + ENEMY_ATTACK_POISON_CHANCE: 0x0E04, + ENEMY_ATTACK_PARALYZE_CHANCE: 0x0E05, + ENEMY_ATTACK_BURN_CHANCE: 0x0E06, + ENEMY_STATUS_EFFECT_HEAL_CHANCE: 0x0E07, + ENEMY_ENDURE_CHANCE: 0x0E08, + ENEMY_FUSED_CHANCE: 0x0E09, +}; + +export type TrainerItemId = (typeof TrainerItemId)[keyof typeof TrainerItemId]; + +type TrainerItemName = keyof typeof TrainerItemId; +type TrainerItemValue = typeof TrainerItemId[TrainerItemName]; + +// Use a type-safe reducer to force number keys and values +export const TrainerItemNames: Record = Object.entries(TrainerItemId).reduce( + (acc, [key, value]) => { + acc[value as TrainerItemValue] = key as TrainerItemName; + return acc; + }, + {} as Record +); \ No newline at end of file diff --git a/src/events/battle-scene.ts b/src/events/battle-scene.ts index 83d260bd7d2..7e454d813e2 100644 --- a/src/events/battle-scene.ts +++ b/src/events/battle-scene.ts @@ -1,5 +1,6 @@ +import type Pokemon from "#app/field/pokemon"; +import type { BerryType } from "#enums/berry-type"; import type Move from "../data/moves/move"; -import type { BerryModifier } from "../modifier/modifier"; /** Alias for all {@linkcode BattleScene} events */ export enum BattleSceneEventType { @@ -81,12 +82,13 @@ export class MoveUsedEvent extends Event { * @extends Event */ export class BerryUsedEvent extends Event { - /** The {@linkcode BerryModifier} being used */ - public berryModifier: BerryModifier; - constructor(berry: BerryModifier) { + /** The {@linkcode BerryType} being used */ + public pokemon: Pokemon; + public berryType: BerryType; + constructor(pokemon: Pokemon, berryType: BerryType) { super(BattleSceneEventType.BERRY_USED); - - this.berryModifier = berry; + this.pokemon = pokemon; + this.berryType = berryType; } } diff --git a/src/field/arena.ts b/src/field/arena.ts index c5e7edae4d8..08ed42ff72a 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -34,7 +34,8 @@ import { SpeciesFormChangeWeatherTrigger, } from "#app/data/pokemon-forms/form-change-triggers"; import { WeatherType } from "#enums/weather-type"; -import { FieldEffectModifier } from "#app/modifier/modifier"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { HELD_ITEM_EFFECT } from "#app/items/held-item"; export class Arena { public biomeType: BiomeId; @@ -338,7 +339,7 @@ export class Arena { if (!isNullOrUndefined(user)) { weatherDuration.value = 5; - globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, weatherDuration); + applyHeldItems(HELD_ITEM_EFFECT.FIELD_EFFECT, { pokemon: user, fieldDuration: weatherDuration }); } this.weather = weather ? new Weather(weather, weatherDuration.value) : null; @@ -425,7 +426,7 @@ export class Arena { if (!isNullOrUndefined(user)) { terrainDuration.value = 5; - globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, terrainDuration); + applyHeldItems(HELD_ITEM_EFFECT.FIELD_EFFECT, { pokemon: user, fieldDuration: terrainDuration }); } this.terrain = terrain ? new Terrain(terrain, terrainDuration.value) : null; diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts new file mode 100644 index 00000000000..dceaa7fae0c --- /dev/null +++ b/src/field/pokemon-held-item-manager.ts @@ -0,0 +1,229 @@ +import { allHeldItems } from "#app/data/data-lists"; +import { isItemInCategory, isItemInRequested, type HeldItemCategoryId, type HeldItemId } from "#app/enums/held-item-id"; +import type { FormChangeItem } from "#enums/form-change-item"; +import { + type HeldItemConfiguration, + isHeldItemSpecs, + type HeldItemDataMap, + type HeldItemSpecs, + type FormChangeItemPropertyMap, + type FormChangeItemSpecs, + type HeldItemSaveData, +} from "#app/items/held-item-data-types"; + +export class PokemonItemManager { + public heldItems: HeldItemDataMap; + public formChangeItems: FormChangeItemPropertyMap; + + constructor() { + this.heldItems = {}; + this.formChangeItems = {}; + } + + getItemSpecs(id: HeldItemId): HeldItemSpecs | undefined { + const item = this.heldItems[id]; + if (item) { + const itemSpecs: HeldItemSpecs = { + ...item, + id, + }; + return itemSpecs; + } + return undefined; + } + + generateHeldItemConfiguration(restrictedIds?: HeldItemId[]): HeldItemConfiguration { + const config: HeldItemConfiguration = []; + for (const [k, item] of Object.entries(this.heldItems)) { + const id = Number(k); + if (item && (!restrictedIds || id in restrictedIds)) { + const specs: HeldItemSpecs = { ...item, id }; + config.push({ entry: specs, count: 1 }); + } + } + for (const [k, item] of Object.entries(this.formChangeItems)) { + const id = Number(k); + const specs: FormChangeItemSpecs = { ...item, id }; + config.push({ entry: specs, count: 1 }); + } + return config; + } + + generateSaveData(): HeldItemSaveData { + const saveData: HeldItemSaveData = []; + for (const [k, item] of Object.entries(this.heldItems)) { + const id = Number(k); + if (item) { + const specs: HeldItemSpecs = { ...item, id }; + saveData.push(specs); + } + } + for (const [k, item] of Object.entries(this.formChangeItems)) { + const id = Number(k); + const specs: FormChangeItemSpecs = { ...item, id }; + saveData.push(specs); + } + return saveData; + } + + getHeldItems(): number[] { + return Object.keys(this.heldItems).map(k => Number(k)); + } + + getTransferableHeldItems(): number[] { + return Object.keys(this.heldItems) + .filter(k => allHeldItems[k].isTransferable) + .map(k => Number(k)); + } + + getStealableHeldItems(): number[] { + return Object.keys(this.heldItems) + .filter(k => allHeldItems[k].isStealable) + .map(k => Number(k)); + } + + getSuppressableHeldItems(): number[] { + return Object.keys(this.heldItems) + .filter(k => allHeldItems[k].isSuppressable) + .map(k => Number(k)); + } + + hasItem(itemType: HeldItemId): boolean { + return itemType in this.heldItems; + } + + hasItemCategory(categoryId: HeldItemCategoryId): boolean { + return Object.keys(this.heldItems).some(id => isItemInCategory(Number(id), categoryId)); + } + + getStack(itemType: HeldItemId): number { + const item = this.heldItems[itemType]; + return item ? item.stack : 0; + } + + // Use for tests if necessary to go over stack limit + setStack(itemType: HeldItemId, stack: number): void { + const item = this.heldItems[itemType]; + if (item) { + item.stack = stack; + } + } + + isMaxStack(itemType: HeldItemId): boolean { + const item = this.heldItems[itemType]; + return item ? item.stack >= allHeldItems[itemType].getMaxStackCount() : false; + } + + overrideItems(newItems: HeldItemDataMap) { + this.heldItems = newItems; + // The following is to allow randomly generated item configs to have stack 0 + for (const [item, properties] of Object.entries(this.heldItems)) { + if (!properties || properties.stack <= 0) { + delete this.heldItems[item]; + } + } + } + + add(itemType: HeldItemId | HeldItemSpecs, addStack = 1): boolean { + if (isHeldItemSpecs(itemType)) { + return this.addItemWithSpecs(itemType); + } + + const maxStack = allHeldItems[itemType].getMaxStackCount(); + const item = this.heldItems[itemType]; + + if (item) { + // TODO: We may want an error message of some kind instead + if (item.stack < maxStack) { + item.stack = Math.min(item.stack + addStack, maxStack); + return true; + } + } else { + this.heldItems[itemType] = { stack: Math.min(addStack, maxStack) }; + return true; + } + return false; + } + + addItemWithSpecs(itemSpecs: HeldItemSpecs): boolean { + const id = itemSpecs.id; + const maxStack = allHeldItems[id].getMaxStackCount(); + const item = this.heldItems[id]; + + const tempStack = item?.stack ?? 0; + + this.heldItems[id] = itemSpecs; + this.heldItems[id].stack = Math.min(itemSpecs.stack + tempStack, maxStack); + + return true; + } + + remove(itemType: HeldItemId, removeStack = 1, all = false) { + const item = this.heldItems[itemType]; + + if (item) { + item.stack -= removeStack; + + if (all || item.stack <= 0) { + delete this.heldItems[itemType]; + } + } + } + + filterRequestedItems(requestedItems: (HeldItemCategoryId | HeldItemId)[], transferableOnly = true, exclude = false) { + const currentItems = transferableOnly ? this.getTransferableHeldItems() : this.getHeldItems(); + return currentItems.filter(it => !exclude && isItemInRequested(it, requestedItems)); + } + + getHeldItemCount(): number { + let total = 0; + for (const properties of Object.values(this.heldItems)) { + total += properties?.stack ?? 0; + } + return total; + } + + addFormChangeItem(id: FormChangeItem) { + if (!(id in this.formChangeItems)) { + this.formChangeItems[id] = { active: false }; + } + } + + addFormChangeItemWithSpecs(item: FormChangeItemSpecs) { + if (!(item.id in this.formChangeItems)) { + this.formChangeItems[item.id] = { active: item.active }; + } + } + + hasFormChangeItem(id: FormChangeItem): boolean { + return id in this.formChangeItems; + } + + hasActiveFormChangeItem(id: FormChangeItem): boolean { + const item = this.formChangeItems[id]; + if (item) { + return item.active; + } + return false; + } + + getFormChangeItems(): FormChangeItem[] { + return Object.keys(this.formChangeItems).map(k => Number(k)); + } + + getActiveFormChangeItems(): FormChangeItem[] { + return this.getFormChangeItems().filter(m => this.formChangeItems[m]?.active); + } + + toggleActive(id: FormChangeItem) { + const item = this.formChangeItems[id]; + if (item) { + item.active = !item.active; + } + } + + clearItems() { + this.heldItems = {}; + this.formChangeItems = {}; + } +} diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0a8e8469115..aca18a02ef8 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -51,27 +51,6 @@ import { BATTLE_STATS, EFFECTIVE_STATS, } from "#enums/stat"; -import { - EnemyDamageBoosterModifier, - EnemyDamageReducerModifier, - EnemyFusionChanceModifier, - HiddenAbilityRateBoosterModifier, - BaseStatModifier, - PokemonFriendshipBoosterModifier, - PokemonHeldItemModifier, - PokemonNatureWeightModifier, - ShinyRateBoosterModifier, - SurviveDamageModifier, - TempStatStageBoosterModifier, - TempCritBoosterModifier, - StatBoosterModifier, - CritBoosterModifier, - PokemonBaseStatFlatModifier, - PokemonBaseStatTotalModifier, - PokemonIncrementingStatModifier, - EvoTrackerModifier, - PokemonMultiHitModifier, -} from "#app/modifier/modifier"; import { PokeballType } from "#enums/pokeball"; import { Gender } from "#app/data/gender"; import { Status, getRandomStatus } from "#app/data/status-effect"; @@ -135,7 +114,7 @@ import type { TrainerSlot } from "#enums/trainer-slot"; import Overrides from "#app/overrides"; import i18next from "i18next"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { applyChallenges } from "#app/data/challenge"; import { ChallengeType } from "#enums/challenge-type"; import { AbilityId } from "#enums/ability-id"; @@ -173,6 +152,10 @@ import { doShinySparkleAnim } from "#app/field/anims"; import { MoveFlags } from "#enums/MoveFlags"; import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; +import { PokemonItemManager } from "./pokemon-held-item-manager"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemId } from "#enums/held-item-id"; import { isVirtual, isIgnorePP, MoveUseMode } from "#enums/move-use-mode"; import { FieldPosition } from "#enums/field-position"; import { HitResult } from "#enums/hit-result"; @@ -182,6 +165,9 @@ import type { IllusionData } from "#app/@types/illusion-data"; import type { TurnMove } from "#app/@types/turn-move"; import type { DamageCalculationResult, DamageResult } from "#app/@types/damage-result"; import type { AbAttrMap, AbAttrString, TypeMultiplierAbAttrParams } from "#app/@types/ability-types"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; +import { assignItemsFromConfiguration } from "#app/items/held-item-pool"; import { getTerrainBlockMessage } from "#app/data/terrain"; import { LearnMoveSituation } from "#enums/learn-move-situation"; @@ -297,6 +283,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { private shinySparkle: Phaser.GameObjects.Sprite; + public heldItemManager: PokemonItemManager; + // TODO: Rework this eventually constructor( x: number, @@ -310,6 +298,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { variant?: Variant, ivs?: number[], nature?: Nature, + heldItemConfig?: HeldItemConfiguration, dataSource?: Pokemon | PokemonData, ) { super(globalScene, x, y); @@ -339,6 +328,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.exp = dataSource?.exp || getLevelTotalExp(this.level, species.growthRate); this.levelExp = dataSource?.levelExp || 0; + this.heldItemManager = new PokemonItemManager(); + if (heldItemConfig) { + assignItemsFromConfiguration(heldItemConfig, this); + } + if (dataSource) { this.id = dataSource.id; this.hp = dataSource.hp; @@ -416,7 +410,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (level > 1) { const fused = new BooleanHolder(globalScene.gameMode.isSplicedOnly); if (!fused.value && this.isEnemy() && !this.hasTrainer()) { - globalScene.applyModifier(EnemyFusionChanceModifier, false, fused); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.ENEMY_FUSED_CHANCE, { booleanHolder: fused }); } if (fused.value) { @@ -599,7 +593,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Roll for hidden ability chance, applying any ability charms for enemy mons const hiddenAbilityChance = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); if (!this.hasTrainer()) { - globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER, { + numberHolder: hiddenAbilityChance, + }); } // If the roll succeeded and we have one, use HA; otherwise pick a random ability @@ -1119,14 +1115,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.setScale(this.getSpriteScale()); } - getHeldItems(): PokemonHeldItemModifier[] { - if (!globalScene) { - return []; - } - return globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id, - this.isPlayer(), - ) as PokemonHeldItemModifier[]; + getHeldItems(): HeldItemId[] { + return this.heldItemManager.getHeldItems(); } updateScale(): void { @@ -1356,8 +1346,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getCritStage(source: Pokemon, move: Move): number { const critStage = new NumberHolder(0); applyMoveAttrs("HighCritAttr", source, this, move, critStage); - globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage); - globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage); + applyHeldItems(HELD_ITEM_EFFECT.CRIT_BOOST, { pokemon: source, critStage: critStage }); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.TEMP_CRIT_BOOSTER, { numberHolder: critStage }); applyAbAttrs("BonusCritAbAttr", { pokemon: source, critStage }); const critBoostTag = source.getTag(CritBoostTag); if (critBoostTag) { @@ -1411,7 +1401,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ): number { const statVal = new NumberHolder(this.getStat(stat, false)); if (!ignoreHeldItems) { - globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statVal); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: this, stat: stat, statValue: statVal }); } // The Ruin abilities here are never ignored, but they reveal themselves on summon anyway @@ -1519,7 +1509,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const statHolder = new NumberHolder(Math.floor((2 * baseStats[s] + this.ivs[s]) * this.level * 0.01)); if (s === Stat.HP) { statHolder.value = statHolder.value + this.level + 10; - globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); + applyHeldItems(HELD_ITEM_EFFECT.INCREMENTING_STAT, { pokemon: this, stat: s, statHolder: statHolder }); if (this.hasAbility(AbilityId.WONDER_GUARD, false, true)) { statHolder.value = 1; } @@ -1534,14 +1524,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } else { statHolder.value += 5; const natureStatMultiplier = new NumberHolder(getNatureStatMultiplier(this.getNature(), s)); - globalScene.applyModifier(PokemonNatureWeightModifier, this.isPlayer(), this, natureStatMultiplier); + applyHeldItems(HELD_ITEM_EFFECT.NATURE_WEIGHT_BOOSTER, { pokemon: this, multiplier: natureStatMultiplier }); if (natureStatMultiplier.value !== 1) { statHolder.value = Math.max( Math[natureStatMultiplier.value > 1 ? "ceil" : "floor"](statHolder.value * natureStatMultiplier.value), 1, ); } - globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); + applyHeldItems(HELD_ITEM_EFFECT.INCREMENTING_STAT, { pokemon: this, stat: s, statHolder: statHolder }); } statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER); @@ -1554,9 +1544,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const baseStats = this.getSpeciesForm(true).baseStats.slice(0); applyChallenges(ChallengeType.FLIP_STAT, this, baseStats); // Shuckle Juice - globalScene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats); + applyHeldItems(HELD_ITEM_EFFECT.BASE_STAT_TOTAL, { pokemon: this, baseStats: baseStats }); // Old Gateau - globalScene.applyModifiers(PokemonBaseStatFlatModifier, this.isPlayer(), this, baseStats); + applyHeldItems(HELD_ITEM_EFFECT.BASE_STAT_FLAT, { pokemon: this, baseStats: baseStats }); if (this.isFusion()) { const fusionBaseStats = this.getFusionSpeciesForm(true).baseStats; applyChallenges(ChallengeType.FLIP_STAT, this, fusionBaseStats); @@ -1570,7 +1560,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } // Vitamins - globalScene.applyModifiers(BaseStatModifier, this.isPlayer(), this, baseStats); + applyHeldItems(HELD_ITEM_EFFECT.BASE_STAT_BOOSTER, { pokemon: this, baseStats: baseStats }); return baseStats; } @@ -2180,8 +2170,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Gets the weight of the Pokemon with subtractive modifiers (Autotomize) happening first - * and then multiplicative modifiers happening after (Heavy Metal and Light Metal) + * Gets the weight of the Pokemon with subtractive abilities (Autotomize) happening first + * and then multiplicative abilities happening after (Heavy Metal and Light Metal) * @returns the kg of the Pokemon (minimum of 0.1) */ public getWeight(): number { @@ -2794,7 +2784,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } if (!this.hasTrainer()) { - globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold }); } } else { shinyThreshold.value = thresholdOverride; @@ -2826,7 +2816,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (timedEventManager.isEventActive()) { shinyThreshold.value *= timedEventManager.getShinyMultiplier(); } - globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold }); } this.shiny = randSeedInt(65536) < shinyThreshold.value; @@ -2898,7 +2888,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const haThreshold = new NumberHolder(thresholdOverride ?? BASE_HIDDEN_ABILITY_CHANCE); if (applyModifiersToOverride) { if (!this.hasTrainer()) { - globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER, { numberHolder: haThreshold }); } } @@ -2912,7 +2902,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public generateFusionSpecies(forStarter?: boolean): void { const hiddenAbilityChance = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); if (!this.hasTrainer()) { - globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER, { + numberHolder: hiddenAbilityChance, + }); } const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); @@ -3040,11 +3032,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } if (compatible && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) { - if (tmPoolTiers[moveId] === ModifierTier.COMMON && this.level >= 15) { + if (tmPoolTiers[moveId] === RewardTier.COMMON && this.level >= 15) { movePool.push([moveId, 4]); - } else if (tmPoolTiers[moveId] === ModifierTier.GREAT && this.level >= 30) { + } else if (tmPoolTiers[moveId] === RewardTier.GREAT && this.level >= 30) { movePool.push([moveId, 8]); - } else if (tmPoolTiers[moveId] === ModifierTier.ULTRA && this.level >= 50) { + } else if (tmPoolTiers[moveId] === RewardTier.ULTRA && this.level >= 50) { movePool.push([moveId, 14]); } } @@ -3426,7 +3418,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!ignoreStatStage.value) { const statStageMultiplier = new NumberHolder(Math.max(2, 2 + statStage.value) / Math.max(2, 2 - statStage.value)); if (!ignoreHeldItems) { - globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), stat, statStageMultiplier); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.TEMP_STAT_STAGE_BOOSTER, { + numberHolder: statStageMultiplier, + }); } return Math.min(statStageMultiplier.value, 4); } @@ -3460,7 +3454,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyAbAttrs("IgnoreOpponentStatStagesAbAttr", { pokemon: this, stat: Stat.EVA, ignored: ignoreEvaStatStage }); applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage); - globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.TEMP_ACCURACY_BOOSTER, { numberHolder: userAccStage }); userAccStage.value = ignoreAccStatStage.value ? 0 : Math.min(userAccStage.value, 6); targetEvaStage.value = ignoreEvaStatStage.value ? 0 : targetEvaStage.value; @@ -3713,14 +3707,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs("FixedDamageAttr", source, this, move, fixedDamage); if (fixedDamage.value) { const multiLensMultiplier = new NumberHolder(1); - globalScene.applyModifiers( - PokemonMultiHitModifier, - source.isPlayer(), - source, - move.id, - null, - multiLensMultiplier, - ); + applyHeldItems(HELD_ITEM_EFFECT.MULTI_HIT, { + pokemon: source, + moveId: move.id, + damageMultiplier: multiLensMultiplier, + }); fixedDamage.value = toDmgValue(fixedDamage.value * multiLensMultiplier.value); return { @@ -3764,14 +3755,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Multiplier for moves enhanced by Multi-Lens and/or Parental Bond */ const multiStrikeEnhancementMultiplier = new NumberHolder(1); - globalScene.applyModifiers( - PokemonMultiHitModifier, - source.isPlayer(), - source, - move.id, - null, - multiStrikeEnhancementMultiplier, - ); + applyHeldItems(HELD_ITEM_EFFECT.MULTI_HIT, { + pokemon: source, + moveId: move.id, + damageMultiplier: multiStrikeEnhancementMultiplier, + }); if (!ignoreSourceAbility) { applyAbAttrs("AddSecondStrikeAbAttr", { @@ -3890,10 +3878,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Apply the enemy's Damage and Resistance tokens */ if (!source.isPlayer()) { - globalScene.applyModifiers(EnemyDamageBoosterModifier, false, damage); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.ENEMY_DAMAGE_BOOSTER, { numberHolder: damage }); } if (!this.isPlayer()) { - globalScene.applyModifiers(EnemyDamageReducerModifier, false, damage); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.ENEMY_DAMAGE_REDUCER, { numberHolder: damage }); } const abAttrParams: PreAttackModifyDamageAbAttrParams = { @@ -3903,7 +3891,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { simulated, damage, }; - /** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */ + /** Apply this Pokemon's post-calc defensive attributes (e.g. Fur Coat) */ if (!ignoreAbility) { applyAbAttrs("ReceivedMoveDamageMultiplierAbAttr", abAttrParams); @@ -4005,7 +3993,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { surviveDamage.value = this.lapseTag(BattlerTagType.ENDURE_TOKEN); } if (!surviveDamage.value) { - globalScene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage); + applyHeldItems(HELD_ITEM_EFFECT.SURVIVE_CHANCE, { pokemon: this, surviveDamage: surviveDamage }); } if (surviveDamage.value) { damage = this.hp - 1; @@ -4447,7 +4435,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.setScale(this.getSpriteScale()); this.loadAssets().then(() => { this.calculateStats(); - globalScene.updateModifiers(this.isPlayer(), true); + globalScene.updateItems(this.isPlayer()); Promise.all([this.updateInfo(), globalScene.updateFieldScale()]).then(() => resolve()); }); }); @@ -5555,15 +5543,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param forBattle If `false`, do not trigger in-battle effects (such as Unburden) from losing the item. For example, set this to `false` if the Pokemon is giving away the held item for a Mystery Encounter. Default is `true`. * @returns `true` if the item was removed successfully, `false` otherwise. */ - public loseHeldItem(heldItem: PokemonHeldItemModifier, forBattle = true): boolean { - if (heldItem.pokemonId !== -1 && heldItem.pokemonId !== this.id) { + public loseHeldItem(heldItemId: HeldItemId, forBattle = true): boolean { + if (!this.heldItemManager.hasItem(heldItemId)) { return false; } - heldItem.stackCount--; - if (heldItem.stackCount <= 0) { - globalScene.removeModifier(heldItem, this.isEnemy()); - } + this.heldItemManager.remove(heldItemId); + if (forBattle) { applyAbAttrs("PostItemLostAbAttr", { pokemon: this }); } @@ -5585,13 +5571,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.turnData.berriesEaten.push(berryType); } - - getPersistentTreasureCount(): number { - return ( - this.getHeldItems().filter(m => m.is("DamageMoneyRewardModifier")).length + - globalScene.findModifiers(m => m.is("MoneyMultiplierModifier") || m.is("ExtraModifierModifier")).length - ); - } } export class PlayerPokemon extends Pokemon { @@ -5608,9 +5587,24 @@ export class PlayerPokemon extends Pokemon { variant?: Variant, ivs?: number[], nature?: Nature, + heldItemConfig?: HeldItemConfiguration, dataSource?: Pokemon | PokemonData, ) { - super(106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); + super( + 106, + 148, + species, + level, + abilityIndex, + formIndex, + gender, + shiny, + variant, + ivs, + nature, + heldItemConfig, + dataSource, + ); if (Overrides.STATUS_OVERRIDE) { this.status = new Status(Overrides.STATUS_OVERRIDE, 0, 4); @@ -5754,7 +5748,7 @@ export class PlayerPokemon extends Pokemon { fusionStarterSpeciesId ? globalScene.gameData.starterData[fusionStarterSpeciesId] : null, ].filter(d => !!d); const amount = new NumberHolder(friendship); - globalScene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount); + applyHeldItems(HELD_ITEM_EFFECT.FRIENDSHIP_BOOSTER, { pokemon: this, friendship: amount }); const candyFriendshipMultiplier = globalScene.gameMode.isClassic ? timedEventManager.getClassicFriendshipMultiplier() : 1; @@ -5814,6 +5808,7 @@ export class PlayerPokemon extends Pokemon { this.variant, this.ivs, this.nature, + this.heldItemManager.generateHeldItemConfiguration(), this, ); this.fusionSpecies = originalFusionSpecies; @@ -5836,6 +5831,7 @@ export class PlayerPokemon extends Pokemon { this.variant, this.ivs, this.nature, + this.heldItemManager.generateHeldItemConfiguration(), this, ); } @@ -5908,9 +5904,9 @@ export class PlayerPokemon extends Pokemon { }); }; if (preEvolution.speciesId === SpeciesId.GIMMIGHOUL) { - const evotracker = this.getHeldItems().filter(m => m instanceof EvoTrackerModifier)[0] ?? null; + const evotracker = this.heldItemManager.hasItem(HeldItemId.GIMMIGHOUL_EVO_TRACKER); if (evotracker) { - globalScene.removeModifier(evotracker); + this.heldItemManager.remove(HeldItemId.GIMMIGHOUL_EVO_TRACKER, 0, true); } } if (!globalScene.gameMode.isDaily || this.metBiome > -1) { @@ -5963,16 +5959,12 @@ export class PlayerPokemon extends Pokemon { globalScene.getPlayerParty().push(newPokemon); newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies); - const modifiers = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id, - true, - ) as PokemonHeldItemModifier[]; - modifiers.forEach(m => { - const clonedModifier = m.clone() as PokemonHeldItemModifier; - clonedModifier.pokemonId = newPokemon.id; - globalScene.addModifier(clonedModifier, true); + //TODO: This currently does not consider any values associated with the items e.g. disabled + const heldItems = this.getHeldItems(); + heldItems.forEach(item => { + newPokemon.heldItemManager.add(item, this.heldItemManager.getStack(item)); }); - globalScene.updateModifiers(true); + globalScene.updateItems(true); } } } @@ -5993,6 +5985,7 @@ export class PlayerPokemon extends Pokemon { this.variant, this.ivs, this.nature, + this.heldItemManager.generateHeldItemConfiguration(), this, ); ret.loadAssets().then(() => resolve(ret)); @@ -6017,7 +6010,7 @@ export class PlayerPokemon extends Pokemon { const updateAndResolve = () => { this.loadAssets().then(() => { this.calculateStats(); - globalScene.updateModifiers(true, true); + globalScene.updateItems(true); this.updateInfo(true).then(() => resolve()); }); }; @@ -6083,15 +6076,11 @@ export class PlayerPokemon extends Pokemon { } // combine the two mons' held items - const fusedPartyMemberHeldModifiers = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, - true, - ) as PokemonHeldItemModifier[]; - for (const modifier of fusedPartyMemberHeldModifiers) { - globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false); + const fusedPartyMemberHeldItems = pokemon.getHeldItems(); + for (const item of fusedPartyMemberHeldItems) { + globalScene.tryTransferHeldItem(item, pokemon, this, false, pokemon.heldItemManager.getStack(item), true, false); } - globalScene.updateModifiers(true, true); - globalScene.removePartyMemberModifiers(fusedPartyMemberIndex); + globalScene.updateItems(true); globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0]; const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this); pokemon @@ -6139,6 +6128,7 @@ export class EnemyPokemon extends Pokemon { trainerSlot: TrainerSlot, boss: boolean, shinyLock = false, + heldItemConfig?: HeldItemConfiguration, dataSource?: PokemonData, ) { super( @@ -6153,6 +6143,7 @@ export class EnemyPokemon extends Pokemon { !shinyLock && dataSource ? dataSource.variant : undefined, undefined, dataSource ? dataSource.nature : undefined, + heldItemConfig, dataSource, ); @@ -6789,6 +6780,7 @@ export class EnemyPokemon extends Pokemon { this.variant, this.ivs, this.nature, + this.heldItemManager.generateHeldItemConfiguration(), this, ); diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 8ac896e2717..472de052360 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -12,7 +12,6 @@ import { TrainerPoolTier } from "#enums/trainer-pool-tier"; import { TeraAIMode } from "#enums/tera-ai-mode"; import type { EnemyPokemon } from "#app/field/pokemon"; import { randSeedWeightedItem, randSeedItem, randSeedInt } from "#app/utils/common"; -import type { PersistentModifier } from "#app/modifier/modifier"; import { ArenaTrapTag } from "#app/data/arena-tag"; import { ArenaTagSide } from "#enums/arena-tag-side"; import { getIsInitialized, initI18n } from "#app/plugins/i18n"; @@ -22,6 +21,7 @@ import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import { signatureSpecies } from "#app/data/balance/signature-species"; import { TrainerVariant } from "#enums/trainer-variant"; +import type { TrainerItemConfiguration } from "#app/items/trainer-item-data-types"; export default class Trainer extends Phaser.GameObjects.Container { public config: TrainerConfig; @@ -647,7 +647,7 @@ export default class Trainer extends Phaser.GameObjects.Container { } } - genModifiers(party: EnemyPokemon[]): PersistentModifier[] { + genTrainerItems(party: EnemyPokemon[]): TrainerItemConfiguration { if (this.config.genModifiersFunc) { return this.config.genModifiersFunc(party); } diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts new file mode 100644 index 00000000000..8d6f7b37a4c --- /dev/null +++ b/src/items/all-held-items.ts @@ -0,0 +1,215 @@ +import { allHeldItems } from "#app/data/data-lists"; +import { getEnumValues } from "#app/utils/common"; +import { BerryType } from "#enums/berry-type"; +import { HeldItemId } from "#enums/held-item-id"; +import type { PokemonType } from "#enums/pokemon-type"; +import { SpeciesId } from "#enums/species-id"; +import { Stat, type PermanentStat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; +import { HELD_ITEM_EFFECT } from "./held-item"; +import { type ACCURACY_BOOST_PARAMS, AccuracyBoosterHeldItem } from "./held-items/accuracy-booster"; +import { + type ATTACK_TYPE_BOOST_PARAMS, + AttackTypeBoosterHeldItem, + attackTypeToHeldItem, +} from "./held-items/attack-type-booster"; +import { + type BASE_STAT_BOOSTER_PARAMS, + BaseStatBoosterHeldItem, + permanentStatToHeldItem, +} from "./held-items/base-stat-booster"; +import { type BASE_STAT_FLAT_PARAMS, BaseStatFlatHeldItem } from "./held-items/base-stat-flat"; +import { type BASE_STAT_TOTAL_PARAMS, BaseStatTotalHeldItem } from "./held-items/base-stat-total"; +import { type BATON_PARAMS, BatonHeldItem } from "./held-items/baton"; +import { type BERRY_PARAMS, BerryHeldItem, berryTypeToHeldItem } from "./held-items/berry"; +import { type BYPASS_SPEED_CHANCE_PARAMS, BypassSpeedChanceHeldItem } from "./held-items/bypass-speed-chance"; +import { type CRIT_BOOST_PARAMS, CritBoostHeldItem, SpeciesCritBoostHeldItem } from "./held-items/crit-booster"; +import { type DAMAGE_MONEY_REWARD_PARAMS, DamageMoneyRewardHeldItem } from "./held-items/damage-money-reward"; +import { type EVO_TRACKER_PARAMS, GimmighoulEvoTrackerHeldItem } from "./held-items/evo-tracker"; +import { type EXP_BOOST_PARAMS, ExpBoosterHeldItem } from "./held-items/exp-booster"; +import { type FIELD_EFFECT_PARAMS, FieldEffectHeldItem } from "./held-items/field-effect"; +import { type FLINCH_CHANCE_PARAMS, FlinchChanceHeldItem } from "./held-items/flinch-chance"; +import { type FRIENDSHIP_BOOST_PARAMS, FriendshipBoosterHeldItem } from "./held-items/friendship-booster"; +import { type HIT_HEAL_PARAMS, HitHealHeldItem } from "./held-items/hit-heal"; +import { type INCREMENTING_STAT_PARAMS, IncrementingStatHeldItem } from "./held-items/incrementing-stat"; +import { InstantReviveHeldItem, type INSTANT_REVIVE_PARAMS } from "./held-items/instant-revive"; +import { + ContactItemStealChanceHeldItem, + type ITEM_STEAL_PARAMS, + TurnEndItemStealHeldItem, +} from "./held-items/item-steal"; +import { type MULTI_HIT_PARAMS, MultiHitHeldItem } from "./held-items/multi-hit"; +import { type NATURE_WEIGHT_BOOST_PARAMS, NatureWeightBoosterHeldItem } from "./held-items/nature-weight-booster"; +import { + ResetNegativeStatStageHeldItem, + type RESET_NEGATIVE_STAT_STAGE_PARAMS, +} from "./held-items/reset-negative-stat-stage"; +import { + EvolutionStatBoostHeldItem, + SpeciesStatBoostHeldItem, + type STAT_BOOST_PARAMS, +} from "./held-items/stat-booster"; +import { type SURVIVE_CHANCE_PARAMS, SurviveChanceHeldItem } from "./held-items/survive-chance"; +import { type TURN_END_HEAL_PARAMS, TurnEndHealHeldItem } from "./held-items/turn-end-heal"; +import { type TURN_END_STATUS_PARAMS, TurnEndStatusHeldItem } from "./held-items/turn-end-status"; + +export function initHeldItems() { + for (const berry of getEnumValues(BerryType)) { + let maxStackCount: number; + if ([BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(berry)) { + maxStackCount = 2; + } else { + maxStackCount = 3; + } + const berryId = berryTypeToHeldItem[berry]; + allHeldItems[berryId] = new BerryHeldItem(berry, maxStackCount); + } + console.log(allHeldItems); + + allHeldItems[HeldItemId.REVIVER_SEED] = new InstantReviveHeldItem(HeldItemId.REVIVER_SEED, 1); + allHeldItems[HeldItemId.WHITE_HERB] = new ResetNegativeStatStageHeldItem(HeldItemId.WHITE_HERB, 2); + + // SILK_SCARF, BLACK_BELT, etc... + for (const [typeKey, heldItemType] of Object.entries(attackTypeToHeldItem)) { + // TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2114957526 + const pokemonType = Number(typeKey) as PokemonType; + allHeldItems[heldItemType] = new AttackTypeBoosterHeldItem(heldItemType, 99, pokemonType, 0.2); + } + + // Items that boost specific stats + allHeldItems[HeldItemId.EVIOLITE] = new EvolutionStatBoostHeldItem( + HeldItemId.EVIOLITE, + 1, + [Stat.DEF, Stat.SPDEF], + 1.5, + ); + allHeldItems[HeldItemId.LIGHT_BALL] = new SpeciesStatBoostHeldItem( + HeldItemId.LIGHT_BALL, + 1, + [Stat.ATK, Stat.SPATK], + 2, + [SpeciesId.PIKACHU], + ); + allHeldItems[HeldItemId.THICK_CLUB] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.ATK], 2, [ + SpeciesId.CUBONE, + SpeciesId.MAROWAK, + SpeciesId.ALOLA_MAROWAK, + ]); + allHeldItems[HeldItemId.METAL_POWDER] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.DEF], 2, [ + SpeciesId.DITTO, + ]); + allHeldItems[HeldItemId.QUICK_POWDER] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.SPD], 2, [ + SpeciesId.DITTO, + ]); + allHeldItems[HeldItemId.DEEP_SEA_SCALE] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.SPDEF], 2, [ + SpeciesId.CLAMPERL, + ]); + allHeldItems[HeldItemId.DEEP_SEA_TOOTH] = new SpeciesStatBoostHeldItem(HeldItemId.LIGHT_BALL, 1, [Stat.SPATK], 2, [ + SpeciesId.CLAMPERL, + ]); + + // Items that boost the crit rate + allHeldItems[HeldItemId.SCOPE_LENS] = new CritBoostHeldItem(HeldItemId.SCOPE_LENS, 1, 1); + allHeldItems[HeldItemId.LEEK] = new SpeciesCritBoostHeldItem(HeldItemId.LEEK, 1, 2, [ + SpeciesId.FARFETCHD, + SpeciesId.GALAR_FARFETCHD, + SpeciesId.SIRFETCHD, + ]); + + allHeldItems[HeldItemId.LUCKY_EGG] = new ExpBoosterHeldItem(HeldItemId.LUCKY_EGG, 99, 40); + allHeldItems[HeldItemId.GOLDEN_EGG] = new ExpBoosterHeldItem(HeldItemId.GOLDEN_EGG, 99, 100); + allHeldItems[HeldItemId.SOOTHE_BELL] = new FriendshipBoosterHeldItem(HeldItemId.SOOTHE_BELL, 3); + + allHeldItems[HeldItemId.LEFTOVERS] = new TurnEndHealHeldItem(HeldItemId.LEFTOVERS, 4); + allHeldItems[HeldItemId.SHELL_BELL] = new HitHealHeldItem(HeldItemId.SHELL_BELL, 4); + + allHeldItems[HeldItemId.FOCUS_BAND] = new SurviveChanceHeldItem(HeldItemId.FOCUS_BAND, 5); + allHeldItems[HeldItemId.QUICK_CLAW] = new BypassSpeedChanceHeldItem(HeldItemId.QUICK_CLAW, 3); + allHeldItems[HeldItemId.KINGS_ROCK] = new FlinchChanceHeldItem(HeldItemId.KINGS_ROCK, 3, 10); + allHeldItems[HeldItemId.MYSTICAL_ROCK] = new FieldEffectHeldItem(HeldItemId.MYSTICAL_ROCK, 2); + allHeldItems[HeldItemId.SOUL_DEW] = new NatureWeightBoosterHeldItem(HeldItemId.SOUL_DEW, 10); + allHeldItems[HeldItemId.WIDE_LENS] = new AccuracyBoosterHeldItem(HeldItemId.WIDE_LENS, 3, 5); + allHeldItems[HeldItemId.MULTI_LENS] = new MultiHitHeldItem(HeldItemId.MULTI_LENS, 2); + allHeldItems[HeldItemId.GOLDEN_PUNCH] = new DamageMoneyRewardHeldItem(HeldItemId.GOLDEN_PUNCH, 5); + allHeldItems[HeldItemId.BATON] = new BatonHeldItem(HeldItemId.BATON, 1); + allHeldItems[HeldItemId.GRIP_CLAW] = new ContactItemStealChanceHeldItem(HeldItemId.GRIP_CLAW, 5, 10); + allHeldItems[HeldItemId.MINI_BLACK_HOLE] = new TurnEndItemStealHeldItem(HeldItemId.MINI_BLACK_HOLE, 1) + .unstealable() + .untransferable(); + + allHeldItems[HeldItemId.FLAME_ORB] = new TurnEndStatusHeldItem(HeldItemId.FLAME_ORB, 1, StatusEffect.BURN); + allHeldItems[HeldItemId.TOXIC_ORB] = new TurnEndStatusHeldItem(HeldItemId.TOXIC_ORB, 1, StatusEffect.TOXIC); + + // vitamins + for (const [statKey, heldItemType] of Object.entries(permanentStatToHeldItem)) { + const stat = Number(statKey) as PermanentStat; + allHeldItems[heldItemType] = new BaseStatBoosterHeldItem(heldItemType, 30, stat) + .unstealable() + .untransferable() + .unsuppressable(); + } + + allHeldItems[HeldItemId.SHUCKLE_JUICE_GOOD] = new BaseStatTotalHeldItem(HeldItemId.SHUCKLE_JUICE_GOOD, 1, 10) + .unstealable() + .untransferable() + .unsuppressable(); + allHeldItems[HeldItemId.SHUCKLE_JUICE_BAD] = new BaseStatTotalHeldItem(HeldItemId.SHUCKLE_JUICE_BAD, 1, -15) + .unstealable() + .untransferable() + .unsuppressable(); + allHeldItems[HeldItemId.OLD_GATEAU] = new BaseStatFlatHeldItem(HeldItemId.OLD_GATEAU, 1) + .unstealable() + .untransferable() + .unsuppressable(); + allHeldItems[HeldItemId.MACHO_BRACE] = new IncrementingStatHeldItem(HeldItemId.MACHO_BRACE, 50) + .unstealable() + .untransferable() + .unsuppressable(); + allHeldItems[HeldItemId.GIMMIGHOUL_EVO_TRACKER] = new GimmighoulEvoTrackerHeldItem( + HeldItemId.GIMMIGHOUL_EVO_TRACKER, + 999, + SpeciesId.GIMMIGHOUL, + 10, + ); +} + +type APPLY_HELD_ITEMS_PARAMS = { + [HELD_ITEM_EFFECT.ATTACK_TYPE_BOOST]: ATTACK_TYPE_BOOST_PARAMS; + [HELD_ITEM_EFFECT.TURN_END_HEAL]: TURN_END_HEAL_PARAMS; + [HELD_ITEM_EFFECT.HIT_HEAL]: HIT_HEAL_PARAMS; + [HELD_ITEM_EFFECT.RESET_NEGATIVE_STAT_STAGE]: RESET_NEGATIVE_STAT_STAGE_PARAMS; + [HELD_ITEM_EFFECT.EXP_BOOSTER]: EXP_BOOST_PARAMS; + [HELD_ITEM_EFFECT.BERRY]: BERRY_PARAMS; + [HELD_ITEM_EFFECT.BASE_STAT_BOOSTER]: BASE_STAT_BOOSTER_PARAMS; + [HELD_ITEM_EFFECT.INSTANT_REVIVE]: INSTANT_REVIVE_PARAMS; + [HELD_ITEM_EFFECT.STAT_BOOST]: STAT_BOOST_PARAMS; + [HELD_ITEM_EFFECT.CRIT_BOOST]: CRIT_BOOST_PARAMS; + [HELD_ITEM_EFFECT.TURN_END_STATUS]: TURN_END_STATUS_PARAMS; + [HELD_ITEM_EFFECT.SURVIVE_CHANCE]: SURVIVE_CHANCE_PARAMS; + [HELD_ITEM_EFFECT.BYPASS_SPEED_CHANCE]: BYPASS_SPEED_CHANCE_PARAMS; + [HELD_ITEM_EFFECT.FLINCH_CHANCE]: FLINCH_CHANCE_PARAMS; + [HELD_ITEM_EFFECT.FIELD_EFFECT]: FIELD_EFFECT_PARAMS; + [HELD_ITEM_EFFECT.FRIENDSHIP_BOOSTER]: FRIENDSHIP_BOOST_PARAMS; + [HELD_ITEM_EFFECT.NATURE_WEIGHT_BOOSTER]: NATURE_WEIGHT_BOOST_PARAMS; + [HELD_ITEM_EFFECT.ACCURACY_BOOSTER]: ACCURACY_BOOST_PARAMS; + [HELD_ITEM_EFFECT.MULTI_HIT]: MULTI_HIT_PARAMS; + [HELD_ITEM_EFFECT.DAMAGE_MONEY_REWARD]: DAMAGE_MONEY_REWARD_PARAMS; + [HELD_ITEM_EFFECT.BATON]: BATON_PARAMS; + [HELD_ITEM_EFFECT.CONTACT_ITEM_STEAL_CHANCE]: ITEM_STEAL_PARAMS; + [HELD_ITEM_EFFECT.TURN_END_ITEM_STEAL]: ITEM_STEAL_PARAMS; + [HELD_ITEM_EFFECT.BASE_STAT_TOTAL]: BASE_STAT_TOTAL_PARAMS; + [HELD_ITEM_EFFECT.BASE_STAT_FLAT]: BASE_STAT_FLAT_PARAMS; + [HELD_ITEM_EFFECT.INCREMENTING_STAT]: INCREMENTING_STAT_PARAMS; + [HELD_ITEM_EFFECT.EVO_TRACKER]: EVO_TRACKER_PARAMS; +}; + +export function applyHeldItems(effect: T, params: APPLY_HELD_ITEMS_PARAMS[T]) { + const pokemon = params.pokemon; + if (pokemon) { + for (const item of Object.keys(pokemon.heldItemManager.heldItems)) { + if (allHeldItems[item].effects.includes(effect)) { + allHeldItems[item].apply(params); + } + } + } +} diff --git a/src/items/all-trainer-items.ts b/src/items/all-trainer-items.ts new file mode 100644 index 00000000000..cd047a6bfa2 --- /dev/null +++ b/src/items/all-trainer-items.ts @@ -0,0 +1,114 @@ +import { allTrainerItems } from "#app/data/data-lists"; +import { Stat, type TempBattleStat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import { + CriticalCatchChanceBoosterTrainerItem, + DoubleBattleChanceBoosterTrainerItem, + EnemyAttackStatusEffectChanceTrainerItem, + EnemyDamageBoosterTrainerItem, + EnemyDamageReducerTrainerItem, + EnemyEndureChanceTrainerItem, + EnemyFusionChanceTrainerItem, + EnemyStatusEffectHealChanceTrainerItem, + EnemyTurnHealTrainerItem, + ExpBoosterTrainerItem, + ExtraRewardTrainerItem, + HealingBoosterTrainerItem, + HealShopCostTrainerItem, + HiddenAbilityChanceBoosterTrainerItem, + LevelIncrementBoosterTrainerItem, + MoneyMultiplierTrainerItem, + PreserveBerryTrainerItem, + ShinyRateBoosterTrainerItem, + TempAccuracyBoosterTrainerItem, + TempCritBoosterTrainerItem, + TempStatStageBoosterTrainerItem, + tempStatToTrainerItem, + TrainerItem, +} from "./trainer-item"; + +export function initTrainerItems() { + allTrainerItems[TrainerItemId.MAP] = new TrainerItem(TrainerItemId.MAP, 1); + allTrainerItems[TrainerItemId.IV_SCANNER] = new TrainerItem(TrainerItemId.IV_SCANNER, 1); + allTrainerItems[TrainerItemId.LOCK_CAPSULE] = new TrainerItem(TrainerItemId.LOCK_CAPSULE, 1); + allTrainerItems[TrainerItemId.MEGA_BRACELET] = new TrainerItem(TrainerItemId.MEGA_BRACELET, 1); + allTrainerItems[TrainerItemId.DYNAMAX_BAND] = new TrainerItem(TrainerItemId.DYNAMAX_BAND, 1); + allTrainerItems[TrainerItemId.TERA_ORB] = new TrainerItem(TrainerItemId.TERA_ORB, 1); + + allTrainerItems[TrainerItemId.OVAL_CHARM] = new TrainerItem(TrainerItemId.OVAL_CHARM, 5); + allTrainerItems[TrainerItemId.EXP_SHARE] = new TrainerItem(TrainerItemId.EXP_SHARE, 5); + allTrainerItems[TrainerItemId.EXP_BALANCE] = new TrainerItem(TrainerItemId.EXP_BALANCE, 4); + + allTrainerItems[TrainerItemId.CANDY_JAR] = new LevelIncrementBoosterTrainerItem(TrainerItemId.CANDY_JAR, 99); + allTrainerItems[TrainerItemId.BERRY_POUCH] = new PreserveBerryTrainerItem(TrainerItemId.BERRY_POUCH, 3); + + allTrainerItems[TrainerItemId.HEALING_CHARM] = new HealingBoosterTrainerItem(TrainerItemId.HEALING_CHARM, 1.1, 5); + allTrainerItems[TrainerItemId.EXP_CHARM] = new ExpBoosterTrainerItem(TrainerItemId.EXP_CHARM, 25, 99); + allTrainerItems[TrainerItemId.SUPER_EXP_CHARM] = new ExpBoosterTrainerItem(TrainerItemId.SUPER_EXP_CHARM, 60, 30); + allTrainerItems[TrainerItemId.GOLDEN_EXP_CHARM] = new ExpBoosterTrainerItem(TrainerItemId.GOLDEN_EXP_CHARM, 100, 10); + allTrainerItems[TrainerItemId.AMULET_COIN] = new MoneyMultiplierTrainerItem(TrainerItemId.AMULET_COIN, 5); + + allTrainerItems[TrainerItemId.ABILITY_CHARM] = new HiddenAbilityChanceBoosterTrainerItem( + TrainerItemId.ABILITY_CHARM, + 4, + ); + allTrainerItems[TrainerItemId.GOLDEN_POKEBALL] = new ExtraRewardTrainerItem(TrainerItemId.GOLDEN_POKEBALL, 3); + allTrainerItems[TrainerItemId.SHINY_CHARM] = new ShinyRateBoosterTrainerItem(TrainerItemId.SHINY_CHARM, 4); + allTrainerItems[TrainerItemId.CATCHING_CHARM] = new CriticalCatchChanceBoosterTrainerItem( + TrainerItemId.CATCHING_CHARM, + 3, + ); + + allTrainerItems[TrainerItemId.BLACK_SLUDGE] = new HealShopCostTrainerItem(TrainerItemId.BLACK_SLUDGE, 2.5, 1); + allTrainerItems[TrainerItemId.GOLDEN_BUG_NET] = new TrainerItem(TrainerItemId.GOLDEN_BUG_NET, 1); + + allTrainerItems[TrainerItemId.LURE] = new DoubleBattleChanceBoosterTrainerItem(TrainerItemId.LURE, 10); + allTrainerItems[TrainerItemId.SUPER_LURE] = new DoubleBattleChanceBoosterTrainerItem(TrainerItemId.SUPER_LURE, 15); + allTrainerItems[TrainerItemId.MAX_LURE] = new DoubleBattleChanceBoosterTrainerItem(TrainerItemId.MAX_LURE, 30); + + for (const [statKey, trainerItemType] of Object.entries(tempStatToTrainerItem)) { + const stat = Number(statKey) as TempBattleStat; + if (stat === Stat.ACC) { + allTrainerItems[trainerItemType] = new TempAccuracyBoosterTrainerItem(trainerItemType, 5); + } else { + allTrainerItems[trainerItemType] = new TempStatStageBoosterTrainerItem(trainerItemType, stat, 5); + } + } + allTrainerItems[TrainerItemId.DIRE_HIT] = new TempCritBoosterTrainerItem(TrainerItemId.DIRE_HIT, 5); + + allTrainerItems[TrainerItemId.ENEMY_DAMAGE_BOOSTER] = new EnemyDamageBoosterTrainerItem( + TrainerItemId.ENEMY_DAMAGE_BOOSTER, + ); + allTrainerItems[TrainerItemId.ENEMY_DAMAGE_REDUCTION] = new EnemyDamageReducerTrainerItem( + TrainerItemId.ENEMY_DAMAGE_REDUCTION, + ); + allTrainerItems[TrainerItemId.ENEMY_HEAL] = new EnemyTurnHealTrainerItem(TrainerItemId.ENEMY_HEAL, 10); + allTrainerItems[TrainerItemId.ENEMY_ATTACK_POISON_CHANCE] = new EnemyAttackStatusEffectChanceTrainerItem( + TrainerItemId.ENEMY_ATTACK_POISON_CHANCE, + StatusEffect.POISON, + 10, + ); + allTrainerItems[TrainerItemId.ENEMY_ATTACK_PARALYZE_CHANCE] = new EnemyAttackStatusEffectChanceTrainerItem( + TrainerItemId.ENEMY_ATTACK_PARALYZE_CHANCE, + StatusEffect.PARALYSIS, + 10, + ); + allTrainerItems[TrainerItemId.ENEMY_ATTACK_BURN_CHANCE] = new EnemyAttackStatusEffectChanceTrainerItem( + TrainerItemId.ENEMY_ATTACK_BURN_CHANCE, + StatusEffect.BURN, + 10, + ); + allTrainerItems[TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE] = new EnemyStatusEffectHealChanceTrainerItem( + TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE, + 10, + ); + allTrainerItems[TrainerItemId.ENEMY_ENDURE_CHANCE] = new EnemyEndureChanceTrainerItem( + TrainerItemId.ENEMY_ENDURE_CHANCE, + 10, + ); + allTrainerItems[TrainerItemId.ENEMY_FUSED_CHANCE] = new EnemyFusionChanceTrainerItem( + TrainerItemId.ENEMY_FUSED_CHANCE, + 10, + ); +} diff --git a/src/items/apply-trainer-items.ts b/src/items/apply-trainer-items.ts new file mode 100644 index 00000000000..f6db615cb25 --- /dev/null +++ b/src/items/apply-trainer-items.ts @@ -0,0 +1,47 @@ +import { allTrainerItems } from "./all-trainer-items"; +import { + type BOOLEAN_HOLDER_PARAMS, + type NUMBER_HOLDER_PARAMS, + type POKEMON_PARAMS, + type PRESERVE_BERRY_PARAMS, + TRAINER_ITEM_EFFECT, +} from "./trainer-item"; +import type { TrainerItemManager } from "./trainer-item-manager"; + +export type APPLY_TRAINER_ITEMS_PARAMS = { + [TRAINER_ITEM_EFFECT.LEVEL_INCREMENT_BOOSTER]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.PRESERVE_BERRY]: PRESERVE_BERRY_PARAMS; + [TRAINER_ITEM_EFFECT.HEALING_BOOSTER]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.EXP_BOOSTER]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.SHINY_RATE_BOOSTER]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.CRITICAL_CATCH_CHANCE_BOOSTER]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.EXTRA_REWARD]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.HEAL_SHOP_COST]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.DOUBLE_BATTLE_CHANCE_BOOSTER]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.TEMP_STAT_STAGE_BOOSTER]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.TEMP_ACCURACY_BOOSTER]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.TEMP_CRIT_BOOSTER]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.ENEMY_DAMAGE_BOOSTER]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.ENEMY_DAMAGE_REDUCER]: NUMBER_HOLDER_PARAMS; + [TRAINER_ITEM_EFFECT.ENEMY_HEAL]: POKEMON_PARAMS; + [TRAINER_ITEM_EFFECT.ENEMY_ATTACK_STATUS_CHANCE]: POKEMON_PARAMS; + [TRAINER_ITEM_EFFECT.ENEMY_STATUS_HEAL_CHANCE]: POKEMON_PARAMS; + [TRAINER_ITEM_EFFECT.ENEMY_ENDURE_CHANCE]: POKEMON_PARAMS; + [TRAINER_ITEM_EFFECT.ENEMY_FUSED_CHANCE]: BOOLEAN_HOLDER_PARAMS; +}; + +export function applyTrainerItems( + effect: T, + manager: TrainerItemManager, + params: APPLY_TRAINER_ITEMS_PARAMS[T], +) { + if (manager) { + for (const item of Object.keys(manager.trainerItems)) { + if (allTrainerItems[item].effects.includes(effect)) { + allTrainerItems[item].apply(manager, params); + } + } + } +} diff --git a/src/items/held-item-data-types.ts b/src/items/held-item-data-types.ts new file mode 100644 index 00000000000..1670b2bb6d0 --- /dev/null +++ b/src/items/held-item-data-types.ts @@ -0,0 +1,84 @@ +// TODO: move to `src/@types/` +import type Pokemon from "#app/field/pokemon"; +import type { FormChangeItem } from "#enums/form-change-item"; +import type { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import type { RewardTier } from "#enums/reward-tier"; + +export type HeldItemData = { + stack: number; + disabled?: boolean; + cooldown?: number; +}; + +export type HeldItemDataMap = { + [key in HeldItemId]?: HeldItemData; +}; + +export type HeldItemSpecs = HeldItemData & { + id: HeldItemId; +}; + +export function isHeldItemSpecs(entry: any): entry is HeldItemSpecs { + return typeof entry.id === "number" && "stack" in entry; +} + +// Types used for form change items +interface FormChangeItemData { + active: boolean; +} + +export type FormChangeItemPropertyMap = { + [key in FormChangeItem]?: FormChangeItemData; +}; + +export type FormChangeItemSpecs = FormChangeItemData & { + id: FormChangeItem; +}; + +export function isFormChangeItemSpecs(entry: any): entry is FormChangeItemSpecs { + return typeof entry.id === "number" && "active" in entry; +} + +export type HeldItemWeights = { + [key in HeldItemId]?: number; +}; + +export type HeldItemWeightFunc = (party: Pokemon[]) => number; + +export type HeldItemCategoryEntry = HeldItemData & { + id: HeldItemCategoryId; + customWeights?: HeldItemWeights; +}; + +export function isHeldItemCategoryEntry(entry: any): entry is HeldItemCategoryEntry { + return entry?.id && isHeldItemCategoryEntry(entry.id) && "customWeights" in entry; +} + +type HeldItemPoolEntry = { + entry: HeldItemId | HeldItemCategoryId | HeldItemCategoryEntry | HeldItemSpecs; + weight: number | HeldItemWeightFunc; +}; + +export type HeldItemPool = HeldItemPoolEntry[]; + +export function isHeldItemPool(value: any): value is HeldItemPool { + return Array.isArray(value) && value.every(entry => "entry" in entry && "weight" in entry); +} + +export type HeldItemTieredPool = { + [key in RewardTier]?: HeldItemPool; +}; + +type HeldItemConfigurationEntry = { + entry: HeldItemId | HeldItemCategoryId | HeldItemCategoryEntry | HeldItemSpecs | HeldItemPool | FormChangeItemSpecs; + count?: number | (() => number); +}; + +export type HeldItemConfiguration = HeldItemConfigurationEntry[]; + +export type PokemonItemMap = { + item: HeldItemSpecs | FormChangeItemSpecs; + pokemonId: number; +}; + +export type HeldItemSaveData = (HeldItemSpecs | FormChangeItemSpecs)[]; diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts new file mode 100644 index 00000000000..1cdd95ae744 --- /dev/null +++ b/src/items/held-item-pool.ts @@ -0,0 +1,324 @@ +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { coerceArray, getEnumValues, isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#app/utils/common"; +import { BerryType } from "#enums/berry-type"; +import { HeldItemCategoryId, HeldItemId, HeldItemNames, isCategoryId } from "#enums/held-item-id"; +import { HeldItemPoolType } from "#enums/modifier-pool-type"; +import type { PokemonType } from "#enums/pokemon-type"; +import { RewardTier } from "#enums/reward-tier"; +import { PERMANENT_STATS } from "#enums/stat"; +import { allHeldItems } from "./all-held-items"; +import { + type HeldItemConfiguration, + type HeldItemPool, + type HeldItemSaveData, + type HeldItemSpecs, + type HeldItemTieredPool, + type HeldItemWeights, + isFormChangeItemSpecs, + isHeldItemCategoryEntry, + isHeldItemPool, + isHeldItemSpecs, +} from "./held-item-data-types"; +import { attackTypeToHeldItem } from "./held-items/attack-type-booster"; +import { permanentStatToHeldItem } from "./held-items/base-stat-booster"; +import { berryTypeToHeldItem } from "./held-items/berry"; + +export const wildHeldItemPool: HeldItemTieredPool = {}; + +export const trainerHeldItemPool: HeldItemTieredPool = {}; + +export const dailyStarterHeldItemPool: HeldItemTieredPool = {}; + +export function assignDailyRunStarterHeldItems(party: PlayerPokemon[]) { + for (const p of party) { + for (let m = 0; m < 3; m++) { + const tierValue = randSeedInt(64); + + const tier = getDailyRewardTier(tierValue); + + const item = getNewHeldItemFromPool( + getHeldItemPool(HeldItemPoolType.DAILY_STARTER)[tier] as HeldItemPool, + p, + party, + ); + p.heldItemManager.add(item); + } + } +} + +function getDailyRewardTier(tierValue: number): RewardTier { + if (tierValue > 25) { + return RewardTier.COMMON; + } + if (tierValue > 12) { + return RewardTier.GREAT; + } + if (tierValue > 4) { + return RewardTier.ULTRA; + } + if (tierValue > 0) { + return RewardTier.ROGUE; + } + return RewardTier.MASTER; +} + +function getHeldItemPool(poolType: HeldItemPoolType): HeldItemTieredPool { + let pool: HeldItemTieredPool; + switch (poolType) { + case HeldItemPoolType.WILD: + pool = wildHeldItemPool; + break; + case HeldItemPoolType.TRAINER: + pool = trainerHeldItemPool; + break; + case HeldItemPoolType.DAILY_STARTER: + pool = dailyStarterHeldItemPool; + break; + } + return pool; +} + +// TODO: Add proper documentation to this function (once it fully works...) +export function assignEnemyHeldItemsForWave( + waveIndex: number, + count: number, + enemy: EnemyPokemon, + poolType: HeldItemPoolType.WILD | HeldItemPoolType.TRAINER, + upgradeChance = 0, +): void { + for (let i = 1; i <= count; i++) { + const item = getNewHeldItemFromTieredPool( + getHeldItemPool(poolType), + enemy, + upgradeChance && !randSeedInt(upgradeChance) ? 1 : 0, + ); + if (item) { + enemy.heldItemManager.add(item); + } + } + if (!(waveIndex % 1000)) { + enemy.heldItemManager.add(HeldItemId.MINI_BLACK_HOLE); + } +} + +function getRandomTier(): RewardTier { + const tierValue = randSeedInt(1024); + + if (tierValue > 255) { + return RewardTier.COMMON; + } + if (tierValue > 60) { + return RewardTier.GREAT; + } + if (tierValue > 12) { + return RewardTier.ULTRA; + } + if (tierValue) { + return RewardTier.ROGUE; + } + return RewardTier.MASTER; +} + +function determineItemPoolTier(pool: HeldItemTieredPool, upgradeCount?: number): RewardTier { + let tier = getRandomTier(); + + if (!upgradeCount) { + upgradeCount = 0; + } + + tier += upgradeCount; + while (tier && !pool[tier]?.length) { + tier--; + if (upgradeCount) { + upgradeCount--; + } + } + + return tier; +} + +function getNewHeldItemFromTieredPool( + pool: HeldItemTieredPool, + pokemon: Pokemon, + upgradeCount: number, +): HeldItemId | HeldItemSpecs { + const tier = determineItemPoolTier(pool, upgradeCount); + const tierPool = pool[tier]; + + return getNewHeldItemFromPool(tierPool!, pokemon); +} + +export function getNewVitaminHeldItem(customWeights: HeldItemWeights = {}, target?: Pokemon): HeldItemId { + const items = PERMANENT_STATS.map(s => permanentStatToHeldItem[s]); + const weights = items.map(t => (target?.heldItemManager.isMaxStack(t) ? 0 : (customWeights[t] ?? 1))); + const pickedIndex = pickWeightedIndex(weights); + return !isNullOrUndefined(pickedIndex) ? items[pickedIndex] : 0; +} + +export function getNewBerryHeldItem(customWeights: HeldItemWeights = {}, target?: Pokemon): HeldItemId { + const berryTypes = getEnumValues(BerryType); + const items = berryTypes.map(b => berryTypeToHeldItem[b]); + + const weights = items.map(t => + target?.heldItemManager.isMaxStack(t) + ? 0 + : (customWeights[t] ?? + (t === HeldItemId.SITRUS_BERRY || t === HeldItemId.LUM_BERRY || t === HeldItemId.LEPPA_BERRY)) + ? 2 + : 1, + ); + + const pickedIndex = pickWeightedIndex(weights); + return !isNullOrUndefined(pickedIndex) ? items[pickedIndex] : 0; +} + +export function getNewAttackTypeBoosterHeldItem( + pokemon: Pokemon | Pokemon[], + customWeights: HeldItemWeights = {}, + target?: Pokemon, +): HeldItemId | null { + const party = coerceArray(pokemon); + + // TODO: make this consider moves or abilities that change types + const attackMoveTypes = party.flatMap(p => + p + .getMoveset() + .filter(m => m.getMove().is("AttackMove")) + .map(m => p.getMoveType(m.getMove(), true)), + ); + if (!attackMoveTypes.length) { + return null; + } + + const attackMoveTypeWeights = attackMoveTypes.reduce((map, type) => { + const current = map.get(type) ?? 0; + if (current < 3) { + map.set(type, current + 1); + } + return map; + }, new Map()); + + const types = Array.from(attackMoveTypeWeights.keys()); + + const weights = types.map(type => + target?.heldItemManager.isMaxStack(attackTypeToHeldItem[type]) + ? 0 + : (customWeights[attackTypeToHeldItem[type]] ?? attackMoveTypeWeights.get(type)!), + ); + + const pickedIndex = pickWeightedIndex(weights); + return !isNullOrUndefined(pickedIndex) ? attackTypeToHeldItem[types[pickedIndex]] : 0; +} + +export function getNewHeldItemFromCategory( + id: HeldItemCategoryId, + pokemon: Pokemon | Pokemon[], + customWeights: HeldItemWeights = {}, + target?: Pokemon, +): HeldItemId | null { + if (id === HeldItemCategoryId.BERRY) { + return getNewBerryHeldItem(customWeights, target); + } + if (id === HeldItemCategoryId.VITAMIN) { + return getNewVitaminHeldItem(customWeights, target); + } + if (id === HeldItemCategoryId.TYPE_ATTACK_BOOSTER) { + return getNewAttackTypeBoosterHeldItem(pokemon, customWeights, target); + } + return null; +} + +function getPoolWeights(pool: HeldItemPool, pokemon: Pokemon): number[] { + return pool.map(p => { + let weight = typeof p.weight === "function" ? p.weight(coerceArray(pokemon)) : p.weight; + + if (typeof p.entry === "number" && !isCategoryId(p.entry)) { + const itemId = p.entry as HeldItemId; + console.log("ITEM ID: ", itemId, HeldItemNames[itemId]); + console.log(allHeldItems[itemId]); + + if (pokemon.heldItemManager.getStack(itemId) >= allHeldItems[itemId].getMaxStackCount()) { + weight = 0; + } + } + + return weight; + }); +} + +function getNewHeldItemFromPool(pool: HeldItemPool, pokemon: Pokemon, party?: Pokemon[]): HeldItemId | HeldItemSpecs { + const weights = getPoolWeights(pool, pokemon); + + const pickedIndex = pickWeightedIndex(weights); + if (isNullOrUndefined(pickedIndex)) { + return 0; + } + const entry = pool[pickedIndex].entry; + + if (typeof entry === "number") { + if (isCategoryId(entry)) { + return getNewHeldItemFromCategory(entry, party ?? pokemon, {}, pokemon) as HeldItemId; + } + return entry as HeldItemId; + } + + if (isHeldItemCategoryEntry(entry)) { + return getNewHeldItemFromCategory(entry.id, party ?? pokemon, entry?.customWeights, pokemon) as HeldItemId; + } + + return entry as HeldItemSpecs; +} + +function assignItemsFromCategory(id: HeldItemCategoryId, pokemon: Pokemon, count: number) { + for (let i = 1; i <= count; i++) { + const newItem = getNewHeldItemFromCategory(id, pokemon, {}, pokemon); + if (newItem) { + pokemon.heldItemManager.add(newItem); + } + } +} + +export function assignItemsFromConfiguration(config: HeldItemConfiguration, pokemon: Pokemon) { + config.forEach(item => { + const { entry, count } = item; + const actualCount = typeof count === "function" ? count() : (count ?? 1); + + if (typeof entry === "number") { + if (isCategoryId(entry)) { + assignItemsFromCategory(entry, pokemon, actualCount); + } + pokemon.heldItemManager.add(entry, actualCount); + } + + if (isHeldItemSpecs(entry)) { + pokemon.heldItemManager.add(entry); + } + + if (isFormChangeItemSpecs(entry)) { + pokemon.heldItemManager.addFormChangeItemWithSpecs(entry); + } + + if (isHeldItemCategoryEntry(entry)) { + assignItemsFromCategory(entry.id, pokemon, actualCount); + } + + if (isHeldItemPool(entry)) { + for (let i = 1; i <= actualCount; i++) { + const newItem = getNewHeldItemFromPool(entry, pokemon); + if (newItem) { + pokemon.heldItemManager.add(newItem); + } + } + } + }); +} + +// TODO: Handle form change items +export function saveDataToConfig(saveData: HeldItemSaveData): HeldItemConfiguration { + const config: HeldItemConfiguration = []; + for (const specs of saveData) { + config.push({ entry: specs, count: 1 }); + } + return config; +} diff --git a/src/items/held-item-tiers.ts b/src/items/held-item-tiers.ts new file mode 100644 index 00000000000..fb340998198 --- /dev/null +++ b/src/items/held-item-tiers.ts @@ -0,0 +1,47 @@ +import { getHeldItemCategory, HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { RewardTier } from "#enums/reward-tier"; + +export const heldItemTiers = { + [HeldItemCategoryId.BERRY]: RewardTier.COMMON, + + [HeldItemCategoryId.BASE_STAT_BOOST]: RewardTier.GREAT, + [HeldItemId.WHITE_HERB]: RewardTier.GREAT, + [HeldItemId.METAL_POWDER]: RewardTier.GREAT, + [HeldItemId.QUICK_POWDER]: RewardTier.GREAT, + [HeldItemId.DEEP_SEA_SCALE]: RewardTier.GREAT, + [HeldItemId.DEEP_SEA_TOOTH]: RewardTier.GREAT, + [HeldItemId.SOOTHE_BELL]: RewardTier.GREAT, + + [HeldItemCategoryId.TYPE_ATTACK_BOOSTER]: RewardTier.ULTRA, + [HeldItemId.REVIVER_SEED]: RewardTier.ULTRA, + [HeldItemId.LIGHT_BALL]: RewardTier.ULTRA, + [HeldItemId.EVIOLITE]: RewardTier.ULTRA, + [HeldItemId.QUICK_CLAW]: RewardTier.ULTRA, + [HeldItemId.MYSTICAL_ROCK]: RewardTier.ULTRA, + [HeldItemId.WIDE_LENS]: RewardTier.ULTRA, + [HeldItemId.GOLDEN_PUNCH]: RewardTier.ULTRA, + [HeldItemId.TOXIC_ORB]: RewardTier.ULTRA, + [HeldItemId.FLAME_ORB]: RewardTier.ULTRA, + [HeldItemId.LUCKY_EGG]: RewardTier.ULTRA, + + [HeldItemId.FOCUS_BAND]: RewardTier.ROGUE, + [HeldItemId.KINGS_ROCK]: RewardTier.ROGUE, + [HeldItemId.LEFTOVERS]: RewardTier.ROGUE, + [HeldItemId.SHELL_BELL]: RewardTier.ROGUE, + [HeldItemId.GRIP_CLAW]: RewardTier.ROGUE, + [HeldItemId.SOUL_DEW]: RewardTier.ROGUE, + [HeldItemId.BATON]: RewardTier.ROGUE, + [HeldItemId.GOLDEN_EGG]: RewardTier.ULTRA, + + [HeldItemId.MINI_BLACK_HOLE]: RewardTier.MASTER, + [HeldItemId.MULTI_LENS]: RewardTier.MASTER, +}; + +export function getHeldItemTier(item: HeldItemId): RewardTier | undefined { + let tier = heldItemTiers[item]; + if (!tier) { + const category = getHeldItemCategory(item); + tier = heldItemTiers[category]; + } + return tier; +} diff --git a/src/items/held-item.ts b/src/items/held-item.ts new file mode 100644 index 00000000000..d317c1ec0fd --- /dev/null +++ b/src/items/held-item.ts @@ -0,0 +1,171 @@ +import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { HeldItemNames, type HeldItemId } from "#enums/held-item-id"; +import i18next from "i18next"; + +export const HELD_ITEM_EFFECT = { + ATTACK_TYPE_BOOST: 1, + TURN_END_HEAL: 2, + HIT_HEAL: 3, + RESET_NEGATIVE_STAT_STAGE: 4, + EXP_BOOSTER: 5, + // Should we actually distinguish different berry effects? + BERRY: 6, + BASE_STAT_BOOSTER: 7, + INSTANT_REVIVE: 8, + STAT_BOOST: 9, + CRIT_BOOST: 10, + TURN_END_STATUS: 11, + SURVIVE_CHANCE: 12, + BYPASS_SPEED_CHANCE: 13, + FLINCH_CHANCE: 14, + FIELD_EFFECT: 15, + FRIENDSHIP_BOOSTER: 16, + NATURE_WEIGHT_BOOSTER: 17, + ACCURACY_BOOSTER: 18, + MULTI_HIT: 19, + DAMAGE_MONEY_REWARD: 20, + BATON: 21, + TURN_END_ITEM_STEAL: 22, + CONTACT_ITEM_STEAL_CHANCE: 23, + EVO_TRACKER: 40, + BASE_STAT_TOTAL: 50, + BASE_STAT_FLAT: 51, + INCREMENTING_STAT: 52, +} as const; + +export type HELD_ITEM_EFFECT = (typeof HELD_ITEM_EFFECT)[keyof typeof HELD_ITEM_EFFECT]; + +export class HeldItem { + // public pokemonId: number; + public type: HeldItemId; + public maxStackCount: number; + public isTransferable = true; + public isStealable = true; + public isSuppressable = true; + + //TODO: If this is actually never changed by any subclass, perhaps it should not be here + public soundName = "se/restore"; + + constructor(type: HeldItemId, maxStackCount = 1) { + this.type = type; + this.maxStackCount = maxStackCount; + + this.isTransferable = true; + this.isStealable = true; + this.isSuppressable = true; + } + + get name(): string { + return i18next.t(`modifierType:ModifierType.${HeldItemNames[this.type]}.name`); + } + + get description(): string { + return i18next.t(`modifierType:ModifierType.${HeldItemNames[this.type]}.description`); + } + + get iconName(): string { + return `${HeldItemNames[this.type]?.toLowerCase()}`; + } + + // TODO: Aren't these fine as just properties to set in the subclass definition? + untransferable(): HeldItem { + this.isTransferable = false; + return this; + } + + unstealable(): HeldItem { + this.isStealable = false; + return this; + } + + unsuppressable(): HeldItem { + this.isSuppressable = false; + return this; + } + + // TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2114950716 + getMaxStackCount(): number { + return this.maxStackCount; + } + + createSummaryIcon(pokemon?: Pokemon, overrideStackCount?: number): Phaser.GameObjects.Container { + const stackCount = overrideStackCount ?? (pokemon ? this.getStackCount(pokemon) : 0); + + const container = globalScene.add.container(0, 0); + + const item = globalScene.add.sprite(0, 12, "items").setFrame(this.iconName).setOrigin(0, 0.5); + container.add(item); + + const stackText = this.getIconStackText(stackCount); + if (stackText) { + container.add(stackText); + } + + container.setScale(0.5); + + return container; + } + + createPokemonIcon(pokemon: Pokemon): Phaser.GameObjects.Container { + const container = globalScene.add.container(0, 0); + + const pokemonIcon = globalScene.addPokemonIcon(pokemon, -2, 10, 0, 0.5, undefined, true); + container.add(pokemonIcon); + container.setName(pokemon.id.toString()); + + const item = globalScene.add + .sprite(16, 16, "items") + .setScale(0.5) + .setOrigin(0, 0.5) + .setTexture("items", this.iconName); + container.add(item); + + const stackText = this.getIconStackText(this.getStackCount(pokemon)); + if (stackText) { + container.add(stackText); + } + + return container; + } + + getIconStackText(stackCount: number): Phaser.GameObjects.BitmapText | null { + if (this.getMaxStackCount() === 1) { + return null; + } + + const text = globalScene.add.bitmapText(10, 15, "item-count", stackCount.toString(), 11); + text.letterSpacing = -0.5; + if (stackCount >= this.getMaxStackCount()) { + // TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2114955458 + text.setTint(0xf89890); + } + text.setOrigin(0); + + return text; + } + + getStackCount(pokemon: Pokemon): number { + const stackCount = pokemon.heldItemManager.getStack(this.type); + return stackCount; + } + + getScoreMultiplier(): number { + return 1; + } +} + +export class ConsumableHeldItem extends HeldItem { + // Sometimes berries are not eaten, some stuff may not proc unburden... + consume(pokemon: Pokemon, isPlayer: boolean, remove = true, unburden = true): void { + if (remove) { + pokemon.heldItemManager.remove(this.type, 1); + // TODO: Turn this into updateItemBar or something + globalScene.updateItems(isPlayer); + } + if (unburden) { + applyAbAttrs("PostItemLostAbAttr", { pokemon: pokemon }); + } + } +} diff --git a/src/items/held-items/accuracy-booster.ts b/src/items/held-items/accuracy-booster.ts new file mode 100644 index 00000000000..cba45f29c24 --- /dev/null +++ b/src/items/held-items/accuracy-booster.ts @@ -0,0 +1,47 @@ +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import type { HeldItemId } from "#enums/held-item-id"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; + +export interface ACCURACY_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + moveAccuracy: NumberHolder; +} + +export class AccuracyBoosterHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.ACCURACY_BOOSTER]; + + private accuracyAmount: number; + + constructor(type: HeldItemId, maxStackCount = 1, accuracy: number) { + super(type, maxStackCount); + this.accuracyAmount = accuracy; + } + + /** + * 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; + // } + + /** + * 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` + */ + apply(params: ACCURACY_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const moveAccuracy = params.moveAccuracy; + const stackCount = pokemon.heldItemManager.getStack(this.type); + moveAccuracy.value = moveAccuracy.value + this.accuracyAmount * stackCount; + + return true; + } +} diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts new file mode 100644 index 00000000000..6fd56721d5c --- /dev/null +++ b/src/items/held-items/attack-type-booster.ts @@ -0,0 +1,78 @@ +import { HeldItemNames, HeldItemId } from "#enums/held-item-id"; +import { PokemonType } from "#enums/pokemon-type"; +import i18next from "i18next"; +import type { NumberHolder } from "#app/utils/common"; +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; + +export interface ATTACK_TYPE_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The resolved type of the move */ + moveType: PokemonType; + /** Holder for the damage value */ + // TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2119660807 + movePower: NumberHolder; +} + +interface AttackTypeToHeldItemMap { + [key: number]: HeldItemId; +} + +export const attackTypeToHeldItem: AttackTypeToHeldItemMap = { + [PokemonType.NORMAL]: HeldItemId.SILK_SCARF, + [PokemonType.FIGHTING]: HeldItemId.BLACK_BELT, + [PokemonType.FLYING]: HeldItemId.SHARP_BEAK, + [PokemonType.POISON]: HeldItemId.POISON_BARB, + [PokemonType.GROUND]: HeldItemId.SOFT_SAND, + [PokemonType.ROCK]: HeldItemId.HARD_STONE, + [PokemonType.BUG]: HeldItemId.SILVER_POWDER, + [PokemonType.GHOST]: HeldItemId.SPELL_TAG, + [PokemonType.STEEL]: HeldItemId.METAL_COAT, + [PokemonType.FIRE]: HeldItemId.CHARCOAL, + [PokemonType.WATER]: HeldItemId.MYSTIC_WATER, + [PokemonType.GRASS]: HeldItemId.MIRACLE_SEED, + [PokemonType.ELECTRIC]: HeldItemId.MAGNET, + [PokemonType.PSYCHIC]: HeldItemId.TWISTED_SPOON, + [PokemonType.ICE]: HeldItemId.NEVER_MELT_ICE, + [PokemonType.DRAGON]: HeldItemId.DRAGON_FANG, + [PokemonType.DARK]: HeldItemId.BLACK_GLASSES, + [PokemonType.FAIRY]: HeldItemId.FAIRY_FEATHER, +}; + +export class AttackTypeBoosterHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.TURN_END_HEAL]; + public moveType: PokemonType; + public powerBoost: number; + + // This constructor may need a revision + constructor(type: HeldItemId, maxStackCount = 1, moveType: PokemonType, powerBoost: number) { + super(type, maxStackCount); + this.moveType = moveType; + this.powerBoost = powerBoost; + } + + get name(): string { + return i18next.t(`modifierType:AttackTypeBoosterItem.${HeldItemNames[this.type]?.toLowerCase()}`); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { + moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`), + }); + } + + get iconName(): string { + return `${HeldItemNames[this.type]?.toLowerCase()}`; + } + + apply(params: ATTACK_TYPE_BOOST_PARAMS): void { + const pokemon = params.pokemon; + const moveType = params.moveType; + const movePower = params.movePower; + const stackCount = pokemon.heldItemManager.getStack(this.type); + if (moveType === this.moveType && movePower.value >= 1) { + movePower.value = Math.floor(movePower.value * (1 + stackCount * this.powerBoost)); + } + } +} diff --git a/src/items/held-items/base-stat-booster.ts b/src/items/held-items/base-stat-booster.ts new file mode 100644 index 00000000000..e5075c41a15 --- /dev/null +++ b/src/items/held-items/base-stat-booster.ts @@ -0,0 +1,81 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItemId } from "#enums/held-item-id"; +import { getStatKey, type PermanentStat, Stat } from "#enums/stat"; +import i18next from "i18next"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; + +export interface BASE_STAT_BOOSTER_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + baseStats: number[]; +} + +interface PermanentStatToHeldItemMap { + [key: number]: HeldItemId; +} + +export const permanentStatToHeldItem: PermanentStatToHeldItemMap = { + [Stat.HP]: HeldItemId.HP_UP, + [Stat.ATK]: HeldItemId.PROTEIN, + [Stat.DEF]: HeldItemId.IRON, + [Stat.SPATK]: HeldItemId.CALCIUM, + [Stat.SPDEF]: HeldItemId.ZINC, + [Stat.SPD]: HeldItemId.CARBOS, +}; + +export const statBoostItems: Record = { + [Stat.HP]: "hp_up", + [Stat.ATK]: "protein", + [Stat.DEF]: "iron", + [Stat.SPATK]: "calcium", + [Stat.SPDEF]: "zinc", + [Stat.SPD]: "carbos", +}; + +export class BaseStatBoosterHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.BASE_STAT_BOOSTER]; + public stat: PermanentStat; + + constructor(type: HeldItemId, maxStackCount = 1, stat: PermanentStat) { + super(type, maxStackCount); + this.stat = stat; + } + + get name(): string { + return i18next.t(`modifierType:BaseStatBoosterItem.${statBoostItems[this.stat]}`); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.BaseStatBoosterModifierType.description", { + stat: i18next.t(getStatKey(this.stat)), + }); + } + + get iconName(): string { + return statBoostItems[this.stat]; + } + + /** + * 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); + // } + + /** + * 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` + */ + apply(params: BASE_STAT_BOOSTER_PARAMS): boolean { + const pokemon = params.pokemon; + const stackCount = pokemon.heldItemManager.getStack(this.type); + const baseStats = params.baseStats; + baseStats[this.stat] = Math.floor(baseStats[this.stat] * (1 + stackCount * 0.1)); + return true; + } +} diff --git a/src/items/held-items/base-stat-flat.ts b/src/items/held-items/base-stat-flat.ts new file mode 100644 index 00000000000..75c12b1f0f5 --- /dev/null +++ b/src/items/held-items/base-stat-flat.ts @@ -0,0 +1,71 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { Stat } from "#enums/stat"; +import i18next from "i18next"; + +export interface BASE_STAT_FLAT_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + baseStats: number[]; +} + +/** + * Currently used by Old Gateau item + */ +export class BaseStatFlatHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.BASE_STAT_FLAT]; + public isTransferable = false; + + get description(): string { + return i18next.t("modifierType:ModifierType.PokemonBaseStatFlatModifierType.description"); + } + + /** + * 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); + // } + + /** + * 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` + */ + apply(params: BASE_STAT_FLAT_PARAMS): boolean { + const pokemon = params.pokemon; + const baseStats = params.baseStats; + const stats = this.getStats(pokemon); + const statModifier = 20; + // Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats + baseStats.forEach((v, i) => { + if (stats.includes(i)) { + const newVal = Math.floor(v + statModifier); + baseStats[i] = Math.min(Math.max(newVal, 1), 999999); + } + }); + + return true; + } + + /** + * Get the lowest of HP/Spd, lowest of Atk/SpAtk, and lowest of Def/SpDef + * @returns Array of 3 {@linkcode Stat}s to boost + */ + getStats(pokemon: Pokemon): Stat[] { + const stats: Stat[] = []; + const baseStats = pokemon.getSpeciesForm().baseStats.slice(0); + // HP or Speed + stats.push(baseStats[Stat.HP] < baseStats[Stat.SPD] ? Stat.HP : Stat.SPD); + // Attack or SpAtk + stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK); + // Def or SpDef + stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF); + return stats; + } +} diff --git a/src/items/held-items/base-stat-total.ts b/src/items/held-items/base-stat-total.ts new file mode 100644 index 00000000000..b48ab0f3430 --- /dev/null +++ b/src/items/held-items/base-stat-total.ts @@ -0,0 +1,70 @@ +import type Pokemon from "#app/field/pokemon"; +import i18next from "i18next"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import type { HeldItemId } from "#enums/held-item-id"; + +export interface BASE_STAT_TOTAL_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + baseStats: number[]; +} + +/** + * Currently used by Shuckle Juice item + */ +export class BaseStatTotalHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.BASE_STAT_TOTAL]; + public isTransferable = false; + public statModifier: number; + + constructor(type: HeldItemId, maxStackCount = 1, statModifier: number) { + super(type, maxStackCount); + this.statModifier = statModifier; + } + + get name(): string { + return this.statModifier > 0 + ? i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD.name") + : i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD.name"); + } + + // TODO: where is this description shown? + get description(): string { + return this.statModifier > 0 + ? i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD.description") + : i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD.description"); + } + + get iconName(): string { + return this.statModifier > 0 ? "berry_juice_good" : "berry_juice_bad"; + } + + /** + * 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); + // } + + /** + * Applies the {@linkcode PokemonBaseStatTotalModifier} + * @param _pokemon the {@linkcode Pokemon} to be modified + * @param baseStats the base stats of the {@linkcode Pokemon} + * @returns always `true` + */ + apply(params: BASE_STAT_TOTAL_PARAMS): boolean { + const baseStats = params.baseStats; + // Modifies the passed in baseStats[] array + 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); + baseStats[i] = Math.min(Math.max(newVal, 1), 999999); + }); + + return true; + } +} diff --git a/src/items/held-items/baton.ts b/src/items/held-items/baton.ts new file mode 100644 index 00000000000..0b36f6c77c7 --- /dev/null +++ b/src/items/held-items/baton.ts @@ -0,0 +1,22 @@ +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; + +export interface BATON_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + expAmount: NumberHolder; +} + +export class BatonHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.BATON]; + + /** + * Applies {@linkcode SwitchEffectTransferModifier} + * @returns always `true` + */ + apply(): boolean { + return true; + } +} diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts new file mode 100644 index 00000000000..f93df60c8a5 --- /dev/null +++ b/src/items/held-items/berry.ts @@ -0,0 +1,96 @@ +import { getBerryEffectDescription, getBerryEffectFunc, getBerryName, getBerryPredicate } from "#app/data/berry"; +import { BerryUsedEvent } from "#app/events/battle-scene"; +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { ConsumableHeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { BooleanHolder } from "#app/utils/common"; +import { BerryType } from "#enums/berry-type"; +import { HeldItemId } from "#enums/held-item-id"; +import { TRAINER_ITEM_EFFECT } from "../trainer-item"; + +interface BerryTypeToHeldItemMap { + [key: number]: HeldItemId; +} + +export const berryTypeToHeldItem: BerryTypeToHeldItemMap = { + [BerryType.SITRUS]: HeldItemId.SITRUS_BERRY, + [BerryType.LUM]: HeldItemId.LUM_BERRY, + [BerryType.ENIGMA]: HeldItemId.ENIGMA_BERRY, + [BerryType.LIECHI]: HeldItemId.LIECHI_BERRY, + [BerryType.GANLON]: HeldItemId.GANLON_BERRY, + [BerryType.PETAYA]: HeldItemId.PETAYA_BERRY, + [BerryType.APICOT]: HeldItemId.APICOT_BERRY, + [BerryType.SALAC]: HeldItemId.SALAC_BERRY, + [BerryType.LANSAT]: HeldItemId.LANSAT_BERRY, + [BerryType.STARF]: HeldItemId.STARF_BERRY, + [BerryType.LEPPA]: HeldItemId.LEPPA_BERRY, +}; + +export interface BERRY_PARAMS { + /** The pokemon with the berry */ + pokemon: Pokemon; +} + +// TODO: Maybe split up into subclasses? +export class BerryHeldItem extends ConsumableHeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.BERRY]; + public berryType: BerryType; + + constructor(berryType: BerryType, maxStackCount = 1) { + const type = berryTypeToHeldItem[berryType]; + super(type, maxStackCount); + + this.berryType = berryType; + } + + get name(): string { + return getBerryName(this.berryType); + } + + get description(): string { + return getBerryEffectDescription(this.berryType); + } + + get iconName(): string { + return `${BerryType[this.berryType].toLowerCase()}_berry`; + } + + /** + * Checks if {@linkcode BerryModifier} should be applied + * @param pokemon The {@linkcode Pokemon} that holds the berry + * @returns `true` if {@linkcode BerryModifier} should be applied + */ + shouldApply(pokemon: Pokemon): boolean { + return getBerryPredicate(this.berryType)(pokemon); + } + + /** + * Applies {@linkcode BerryHeldItem} + * @param pokemon The {@linkcode Pokemon} that holds the berry + * @returns always `true` + */ + apply(params: BERRY_PARAMS): boolean { + const pokemon = params.pokemon; + + if (!this.shouldApply(pokemon)) { + return false; + } + + const preserve = new BooleanHolder(false); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.PRESERVE_BERRY, { pokemon: pokemon, doPreserve: preserve }); + const consumed = !preserve.value; + + // munch the berry and trigger unburden-like effects + getBerryEffectFunc(this.berryType)(pokemon); + this.consume(pokemon, pokemon.isPlayer(), consumed); + + // TODO: Update this method to work with held items + // Update berry eaten trackers for Belch, Harvest, Cud Chew, etc. + // Don't recover it if we proc berry pouch (no item duplication) + pokemon.recordEatenBerry(this.berryType, consumed); + + globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(pokemon, this.berryType)); + + return true; + } +} diff --git a/src/items/held-items/bypass-speed-chance.ts b/src/items/held-items/bypass-speed-chance.ts new file mode 100644 index 00000000000..c454463a14e --- /dev/null +++ b/src/items/held-items/bypass-speed-chance.ts @@ -0,0 +1,62 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import type { BooleanHolder } from "#app/utils/common"; +import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { Command } from "#enums/command"; + +export interface BYPASS_SPEED_CHANCE_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + doBypassSpeed: BooleanHolder; +} + +/** + * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a + * set {@linkcode StatusEffect} at the end of a turn. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class BypassSpeedChanceHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.BYPASS_SPEED_CHANCE]; + + /** + * 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; + // } + + /** + * 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 + */ + apply(params: BYPASS_SPEED_CHANCE_PARAMS): boolean { + const pokemon = params.pokemon; + const doBypassSpeed = params.doBypassSpeed; + const stackCount = pokemon.heldItemManager.getStack(this.type); + if (!doBypassSpeed.value && pokemon.randBattleSeedInt(10) < stackCount) { + doBypassSpeed.value = true; + const isCommandFight = + globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT; + + if (isCommandFight) { + globalScene.phaseManager.queueMessage( + i18next.t("modifier:bypassSpeedChanceApply", { + pokemonName: getPokemonNameWithAffix(pokemon), + itemName: this.name, + }), + ); + } + return true; + } + + return false; + } +} diff --git a/src/items/held-items/crit-booster.ts b/src/items/held-items/crit-booster.ts new file mode 100644 index 00000000000..cc305b33640 --- /dev/null +++ b/src/items/held-items/crit-booster.ts @@ -0,0 +1,86 @@ +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import type { HeldItemId } from "#enums/held-item-id"; +import type { SpeciesId } from "#enums/species-id"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; + +export interface CRIT_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + critStage: NumberHolder; +} + +/** + * Modifier used for held items that apply critical-hit stage boost(s). + * using a multiplier. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class CritBoostHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.CRIT_BOOST]; + + /** The amount of stages by which the held item increases the current critical-hit stage value */ + protected stageIncrement: number; + + constructor(type: HeldItemId, maxStackCount = 1, stageIncrement: number) { + super(type, maxStackCount); + + this.stageIncrement = stageIncrement; + } + + /** + * Increases the current critical-hit stage value by {@linkcode stageIncrement}. + * @param _pokemon {@linkcode Pokemon} N/A + * @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level + * @returns always `true` + */ + apply(params: CRIT_BOOST_PARAMS): boolean { + params.critStage.value += this.stageIncrement; + return true; + } +} + +/** + * Modifier used for held items that apply critical-hit stage boost(s) + * if the holder is of a specific {@linkcode SpeciesId}. + * @extends CritBoosterModifier + * @see {@linkcode shouldApply} + */ +export class SpeciesCritBoostHeldItem extends CritBoostHeldItem { + /** The species that the held item's critical-hit stage boost applies to */ + private species: SpeciesId[]; + + constructor(type: HeldItemId, maxStackCount = 1, stageIncrement: number, species: SpeciesId[]) { + super(type, maxStackCount, stageIncrement); + + this.species = species; + } + + /** + * Checks if the holder's {@linkcode SpeciesId} (or its fused species) is listed + * in {@linkcode species}. + * @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 + */ + // 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))) + // ); + // } + + apply(params: CRIT_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const fitsSpecies = + this.species.includes(pokemon.getSpeciesForm(true).speciesId) || + (pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId)); + + if (fitsSpecies) { + return super.apply(params); + } + + return false; + } +} diff --git a/src/items/held-items/damage-money-reward.ts b/src/items/held-items/damage-money-reward.ts new file mode 100644 index 00000000000..0e0905d183e --- /dev/null +++ b/src/items/held-items/damage-money-reward.ts @@ -0,0 +1,33 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { NumberHolder } from "#app/utils/common"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { TRAINER_ITEM_EFFECT } from "../trainer-item"; + +export interface DAMAGE_MONEY_REWARD_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + damage: number; +} + +export class DamageMoneyRewardHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.DAMAGE_MONEY_REWARD]; + + /** + * Applies {@linkcode DamageMoneyRewardModifier} + * @param pokemon The {@linkcode Pokemon} attacking + * @param multiplier {@linkcode NumberHolder} holding the multiplier value + * @returns always `true` + */ + apply(params: DAMAGE_MONEY_REWARD_PARAMS): boolean { + const pokemon = params.pokemon; + const damage = params.damage; + const stackCount = pokemon.heldItemManager.getStack(this.type); + const moneyAmount = new NumberHolder(Math.floor(damage * (0.5 * stackCount))); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); + globalScene.addMoney(moneyAmount.value); + + return true; + } +} diff --git a/src/items/held-items/evo-tracker.ts b/src/items/held-items/evo-tracker.ts new file mode 100644 index 00000000000..37020e67fec --- /dev/null +++ b/src/items/held-items/evo-tracker.ts @@ -0,0 +1,57 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { HeldItemId } from "#enums/held-item-id"; +import type { SpeciesId } from "#enums/species-id"; +import i18next from "i18next"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { TrainerItemId } from "#enums/trainer-item-id"; + +export interface EVO_TRACKER_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; +} + +export class EvoTrackerHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.EVO_TRACKER]; + + protected species: SpeciesId; + protected required: number; + public isTransferable = false; + + constructor(type: HeldItemId, maxStackCount = 1, species: SpeciesId, required: number) { + super(type, maxStackCount); + this.species = species; + this.required = required; + } + + /** + * Applies the {@linkcode EvoTrackerModifier} + * @returns always `true` + */ + apply(): boolean { + return true; + } +} + +export class GimmighoulEvoTrackerHeldItem extends EvoTrackerHeldItem { + get name(): string { + return i18next.t("modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL.name"); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL.description"); + } + + get iconName(): string { + return "relic_gold"; + } + + getStackCount(pokemon: Pokemon): number { + const stackCount = + pokemon.heldItemManager.getStack(this.type) + + pokemon.heldItemManager.getStack(HeldItemId.GOLDEN_PUNCH) + + globalScene.trainerItems.getStack(TrainerItemId.AMULET_COIN) + + globalScene.trainerItems.getStack(TrainerItemId.GOLDEN_POKEBALL); + return stackCount; + } +} diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts new file mode 100644 index 00000000000..c4dd7ae9773 --- /dev/null +++ b/src/items/held-items/exp-booster.ts @@ -0,0 +1,56 @@ +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import type { HeldItemId } from "#enums/held-item-id"; +import i18next from "i18next"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; + +export interface EXP_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + expAmount: NumberHolder; +} + +export class ExpBoosterHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.EXP_BOOSTER]; + private boostPercent: number; + private boostMultiplier: number; + + constructor(type: HeldItemId, maxStackCount = 1, boostPercent: number) { + super(type, maxStackCount); + this.boostPercent = boostPercent; + this.boostMultiplier = boostPercent * 0.01; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.PokemonExpBoosterModifierType.description", { + boostPercent: this.boostPercent, + }); + } + + // TODO: What do we do with this? Need to look up all the shouldApply + /** + * 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; + // } + + /** + * 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` + */ + apply(params: EXP_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const expAmount = params.expAmount; + const stackCount = pokemon.heldItemManager.getStack(this.type); + expAmount.value = Math.floor(expAmount.value * (1 + stackCount * this.boostMultiplier)); + + return true; + } +} diff --git a/src/items/held-items/field-effect.ts b/src/items/held-items/field-effect.ts new file mode 100644 index 00000000000..048aa8f38b6 --- /dev/null +++ b/src/items/held-items/field-effect.ts @@ -0,0 +1,33 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import type { NumberHolder } from "#app/utils/common"; + +export interface FIELD_EFFECT_PARAMS { + pokemon: Pokemon; + /** The pokemon with the item */ + fieldDuration: NumberHolder; +} + +/** + * Modifier used for held items, namely Mystical Rock, that extend the + * duration of weather and terrain effects. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class FieldEffectHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.FIELD_EFFECT]; + + /** + * Provides two more turns per stack to any weather or terrain effect caused + * by the holder. + * @param pokemon {@linkcode Pokemon} that holds the held item + * @param fieldDuration {@linkcode NumberHolder} that stores the current field effect duration + * @returns `true` if the field effect extension was applied successfully + */ + apply(params: FIELD_EFFECT_PARAMS): boolean { + const pokemon = params.pokemon; + const stackCount = pokemon.heldItemManager.getStack(this.type); + params.fieldDuration.value += 2 * stackCount; + return true; + } +} diff --git a/src/items/held-items/flinch-chance.ts b/src/items/held-items/flinch-chance.ts new file mode 100644 index 00000000000..ca46ce79528 --- /dev/null +++ b/src/items/held-items/flinch-chance.ts @@ -0,0 +1,57 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import type { BooleanHolder } from "#app/utils/common"; +import type { HeldItemId } from "#enums/held-item-id"; + +export interface FLINCH_CHANCE_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + flinched: BooleanHolder; +} + +/** + * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a + * set {@linkcode StatusEffect} at the end of a turn. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class FlinchChanceHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.FLINCH_CHANCE]; + private chance: number; + + constructor(type: HeldItemId, maxStackCount = 1, chance: number) { + super(type, maxStackCount); + + this.chance = chance; // 10 + } + + /** + * 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; + // } + + /** + * Applies {@linkcode FlinchChanceModifier} to randomly flinch targets hit. + * @param pokemon - The {@linkcode Pokemon} that holds the item + * @param flinched - A {@linkcode BooleanHolder} holding whether the pokemon has flinched + * @returns `true` if {@linkcode FlinchChanceModifier} was applied successfully + */ + apply(params: FLINCH_CHANCE_PARAMS): boolean { + const pokemon = params.pokemon; + const flinched = params.flinched; + const stackCount = pokemon.heldItemManager.getStack(this.type); + // The check for pokemon.summonData is to ensure that a crash doesn't occur when a Pokemon with King's Rock procs a flinch + // TODO: Since summonData is always defined now, we can probably remove this + if (pokemon.summonData && !flinched.value && pokemon.randBattleSeedInt(100) < stackCount * this.chance) { + flinched.value = true; + return true; + } + + return false; + } +} diff --git a/src/items/held-items/friendship-booster.ts b/src/items/held-items/friendship-booster.ts new file mode 100644 index 00000000000..72a42ea48dc --- /dev/null +++ b/src/items/held-items/friendship-booster.ts @@ -0,0 +1,34 @@ +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import i18next from "i18next"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; + +export interface FRIENDSHIP_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + friendship: NumberHolder; +} + +export class FriendshipBoosterHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.FRIENDSHIP_BOOSTER]; + + get description(): string { + return i18next.t("modifierType:ModifierType.PokemonFriendshipBoosterModifierType.description"); + } + + /** + * 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` + */ + apply(params: FRIENDSHIP_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const friendship = params.friendship; + const stackCount = pokemon.heldItemManager.getStack(this.type); + friendship.value = Math.floor(friendship.value * (1 + 0.5 * stackCount)); + + return true; + } +} diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts new file mode 100644 index 00000000000..1432aa3370b --- /dev/null +++ b/src/items/held-items/hit-heal.ts @@ -0,0 +1,53 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; +import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; +import { toDmgValue } from "#app/utils/common"; +import { getPokemonNameWithAffix } from "#app/messages"; + +export interface HIT_HEAL_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; +} + +export class HitHealHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.TURN_END_HEAL]; + + get name(): string { + return i18next.t("modifierType:ModifierType.SHELL_BELL.name"); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.SHELL_BELL.description"); + } + + get iconName(): string { + return "shell_bell"; + } + + /** + * Applies {@linkcode HitHealModifier} + * @param pokemon The {@linkcode Pokemon} that holds the item + * @returns `true` if the {@linkcode Pokemon} was healed + */ + apply(params: HIT_HEAL_PARAMS): boolean { + const pokemon = params.pokemon; + const stackCount = pokemon.heldItemManager.getStack(this.type); + if (pokemon.turnData.totalDamageDealt > 0 && !pokemon.isFullHp()) { + // TODO: this shouldn't be undefined AFAIK + globalScene.unshiftPhase( + new PokemonHealPhase( + pokemon.getBattlerIndex(), + toDmgValue(pokemon.turnData.totalDamageDealt / 8) * stackCount, + i18next.t("modifier:hitHealApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.name, + }), + true, + ), + ); + } + return true; + } +} diff --git a/src/items/held-items/incrementing-stat.ts b/src/items/held-items/incrementing-stat.ts new file mode 100644 index 00000000000..262d8d00f70 --- /dev/null +++ b/src/items/held-items/incrementing-stat.ts @@ -0,0 +1,71 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { Stat } from "#enums/stat"; +import type { NumberHolder } from "#app/utils/common"; +import i18next from "i18next"; + +export interface INCREMENTING_STAT_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + stat: Stat; + // TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2135612276 + statHolder: NumberHolder; +} + +/** + * Currently used by Macho Brace item + */ +export class IncrementingStatHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.INCREMENTING_STAT]; + public isTransferable = false; + + /** + * 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; + // } + + get name(): string { + return i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE.name") + " (new)"; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE.description"); + } + + /** + * 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` + */ + apply(params: INCREMENTING_STAT_PARAMS): boolean { + const pokemon = params.pokemon; + const stackCount = pokemon.heldItemManager.getStack(this.type); + const statHolder = params.statHolder; + + // Modifies the passed in stat number holder by +2 per stack for HP, +1 per stack for other stats + // If the Macho Brace is at max stacks (50), adds additional 10% to total HP and 5% to other stats + const isHp = params.stat === Stat.HP; + + if (isHp) { + statHolder.value += 2 * stackCount; + if (stackCount === this.maxStackCount) { + statHolder.value = Math.floor(statHolder.value * 1.1); + } + } else { + statHolder.value += stackCount; + if (stackCount === this.maxStackCount) { + statHolder.value = Math.floor(statHolder.value * 1.05); + } + } + + return true; + } +} diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts new file mode 100644 index 00000000000..40e88c1e5bf --- /dev/null +++ b/src/items/held-items/instant-revive.ts @@ -0,0 +1,68 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; +import { ConsumableHeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; +import { toDmgValue } from "#app/utils/common"; +import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; + +export interface INSTANT_REVIVE_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; +} + +/** + * Modifier used for held items, namely White Herb, that restore adverse stat + * stages in battle. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class InstantReviveHeldItem extends ConsumableHeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.INSTANT_REVIVE]; + + get name(): string { + return i18next.t("modifierType:ModifierType.REVIVER_SEED.name"); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.REVIVER_SEED.description"); + } + + get iconName(): string { + return "reviver_seed"; + } + /** + * Goes through the holder's stat stages and, if any are negative, resets that + * stat stage back to 0. + * @param pokemon {@linkcode Pokemon} that holds the item + * @returns `true` if any stat stages were reset, false otherwise + */ + apply(params: INSTANT_REVIVE_PARAMS): boolean { + const pokemon = params.pokemon; + // Restore the Pokemon to half HP + globalScene.phaseManager.unshiftPhase( + new PokemonHealPhase( + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 2), + i18next.t("modifier:pokemonInstantReviveApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.name, + }), + false, + false, + true, + ), + ); + + // Remove the Pokemon's FAINT status + pokemon.resetStatus(true, false, true, false); + + // Reapply Commander on the Pokemon's side of the field, if applicable + const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); + for (const p of field) { + applyAbAttrs("CommanderAbAttr", p, null, false); + } + return true; + } +} diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts new file mode 100644 index 00000000000..ec78ebdeeb3 --- /dev/null +++ b/src/items/held-items/item-steal.ts @@ -0,0 +1,168 @@ +import Pokemon from "#app/field/pokemon"; +import { randSeedFloat } from "#app/utils/common"; +import type { HeldItemId } from "#enums/held-item-id"; +import i18next from "i18next"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { allHeldItems } from "../all-held-items"; +import { globalScene } from "#app/global-scene"; + +export interface ITEM_STEAL_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The pokemon to steal from (optional) */ + // TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2135607083 + target?: Pokemon; +} + +// constructor(type: HeldItemId, maxStackCount = 1, boostPercent: number) { + +/** + * Abstract class for held items that steal other Pokemon's items. + * @see {@linkcode TurnEndItemStealHeldItem} + * @see {@linkcode ContactItemStealChanceHeldItem} + */ +export abstract class ItemTransferHeldItem extends HeldItem { + /** + * Steals an item, chosen randomly, from a set of target Pokemon. + * @param pokemon The {@linkcode Pokemon} holding this item + * @param target The {@linkcode Pokemon} to steal from (optional) + * @param _args N/A + * @returns `true` if an item was stolen; false otherwise. + */ + apply(params: ITEM_STEAL_PARAMS): boolean { + const opponents = this.getTargets(params); + + if (!opponents.length) { + return false; + } + + const pokemon = params.pokemon; + //TODO: Simplify this logic here + const targetPokemon = opponents[pokemon.randBattleSeedInt(opponents.length)]; + + const transferredItemCount = this.getTransferredItemCount(params); + if (!transferredItemCount) { + return false; + } + + // TODO: Change this logic to use held items + const transferredModifierTypes: HeldItemId[] = []; + const heldItems = targetPokemon.heldItemManager.getTransferableHeldItems(); + + for (let i = 0; i < transferredItemCount; i++) { + if (!heldItems.length) { + break; + } + const randItemIndex = pokemon.randBattleSeedInt(heldItems.length); + const randItem = heldItems[randItemIndex]; + // TODO: Fix this after updating the various methods in battle-scene.ts + if (globalScene.tryTransferHeldItem(randItem, targetPokemon, pokemon, false)) { + transferredModifierTypes.push(randItem); + heldItems.splice(randItemIndex, 1); + } + } + + for (const mt of transferredModifierTypes) { + globalScene.phaseManager.queueMessage(this.getTransferMessage(params, mt)); + } + + return !!transferredModifierTypes.length; + } + + abstract getTargets(params: ITEM_STEAL_PARAMS): Pokemon[]; + + abstract getTransferredItemCount(params: ITEM_STEAL_PARAMS): number; + + abstract getTransferMessage(params: ITEM_STEAL_PARAMS, itemId: HeldItemId): string; +} + +/** + * Modifier for held items that steal items from the enemy at the end of + * each turn. + * @see {@linkcode modifierTypes[MINI_BLACK_HOLE]} + */ +export class TurnEndItemStealHeldItem extends ItemTransferHeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.TURN_END_ITEM_STEAL]; + isTransferable = true; + + get description(): string { + return i18next.t("modifierType:ModifierType.TurnHeldItemTransferModifierType.description"); + } + + /** + * Determines the targets to transfer items from when this applies. + * @param pokemon the {@linkcode Pokemon} holding this item + * @param _args N/A + * @returns the opponents of the source {@linkcode Pokemon} + */ + getTargets(params: ITEM_STEAL_PARAMS): Pokemon[] { + return params.pokemon instanceof Pokemon ? params.pokemon.getOpponents() : []; + } + + getTransferredItemCount(_params: ITEM_STEAL_PARAMS): number { + return 1; + } + + getTransferMessage(params: ITEM_STEAL_PARAMS, itemId: HeldItemId): string { + return i18next.t("modifier:turnHeldItemTransferApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(params.target), + itemName: allHeldItems[itemId].name, + pokemonName: params.pokemon.getNameToRender(), + typeName: this.name, + }); + } + + setTransferrableFalse(): void { + this.isTransferable = false; + } +} + +/** + * Modifier for held items that add a chance to steal items from the target of a + * successful attack. + * @see {@linkcode modifierTypes[GRIP_CLAW]} + * @see {@linkcode HeldItemTransferModifier} + */ +export class ContactItemStealChanceHeldItem extends ItemTransferHeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.CONTACT_ITEM_STEAL_CHANCE]; + public readonly chancePercent: number; + public readonly chance: number; + + constructor(type: HeldItemId, maxStackCount = 1, chancePercent: number) { + super(type, maxStackCount); + + this.chancePercent = chancePercent; + this.chance = chancePercent / 100; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.ContactHeldItemTransferChanceModifierType.description", { + chancePercent: this.chancePercent, + }); + } + + /** + * Determines the target to steal items from when this applies. + * @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(params: ITEM_STEAL_PARAMS): Pokemon[] { + return params.target ? [params.target] : []; + } + + getTransferredItemCount(params: ITEM_STEAL_PARAMS): number { + const stackCount = params.pokemon.heldItemManager.getStack(this.type); + return randSeedFloat() <= this.chance * stackCount ? 1 : 0; + } + + getTransferMessage(params: ITEM_STEAL_PARAMS, itemId: HeldItemId): string { + return i18next.t("modifier:contactHeldItemTransferApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(params.target), + itemName: allHeldItems[itemId].name, + pokemonName: params.pokemon.getNameToRender(), + typeName: this.name, + }); + } +} diff --git a/src/items/held-items/multi-hit.ts b/src/items/held-items/multi-hit.ts new file mode 100644 index 00000000000..5efbdf71386 --- /dev/null +++ b/src/items/held-items/multi-hit.ts @@ -0,0 +1,89 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { isNullOrUndefined, type NumberHolder } from "#app/utils/common"; +import type { MoveId } from "#enums/move-id"; +import { allMoves } from "#app/data/data-lists"; +import i18next from "i18next"; + +export interface MULTI_HIT_PARAMS { + pokemon: Pokemon; + moveId: MoveId; + count?: NumberHolder; + damageMultiplier?: NumberHolder; +} + +/** + * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a + * set {@linkcode StatusEffect} at the end of a turn. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class MultiHitHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.MULTI_HIT]; + + get description(): string { + return i18next.t("modifierType:ModifierType.PokemonMultiHitModifierType.description"); + } + + /** + * For each stack, converts 25 percent of attack damage into an additional strike. + * @param pokemon The {@linkcode Pokemon} using the move + * @param moveId The {@linkcode MoveId | identifier} for the move being used + * @param count {@linkcode NumberHolder} holding the move's hit count for this turn + * @param damageMultiplier {@linkcode NumberHolder} holding a damage multiplier applied to a strike of this move + * @returns always `true` + */ + apply(params: MULTI_HIT_PARAMS): boolean { + const pokemon = params.pokemon; + const move = allMoves[params.moveId]; + /** + * The move must meet Parental Bond's restrictions for this item + * to apply. This means + * - Only attacks are boosted + * - Multi-strike moves, charge moves, and self-sacrificial moves are not boosted + * (though Multi-Lens can still affect moves boosted by Parental Bond) + * - Multi-target moves are not boosted *unless* they can only hit a single Pokemon + * - Fling, Uproar, Rollout, Ice Ball, and Endeavor are not boosted + */ + if (!move.canBeMultiStrikeEnhanced(pokemon)) { + return false; + } + + if (!isNullOrUndefined(params.count)) { + return this.applyHitCountBoost(pokemon, params.count); + } + if (!isNullOrUndefined(params.damageMultiplier)) { + return this.applyDamageModifier(pokemon, params.damageMultiplier); + } + + return false; + } + + /** Adds strikes to a move equal to the number of stacked Multi-Lenses */ + private applyHitCountBoost(pokemon: Pokemon, count: NumberHolder): boolean { + const stackCount = pokemon.heldItemManager.getStack(this.type); + count.value += stackCount; + return true; + } + + /** + * If applied to the first hit of a move, sets the damage multiplier + * equal to (1 - the number of stacked Multi-Lenses). + * Additional strikes beyond that are given a 0.25x damage multiplier + */ + private applyDamageModifier(pokemon: Pokemon, damageMultiplier: NumberHolder): boolean { + const stackCount = pokemon.heldItemManager.getStack(this.type); + if (pokemon.turnData.hitsLeft === pokemon.turnData.hitCount) { + // Reduce first hit by 25% for each stack count + damageMultiplier.value *= 1 - 0.25 * stackCount; + return true; + } + if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== stackCount + 1) { + // Deal 25% damage for each remaining Multi Lens hit + damageMultiplier.value *= 0.25; + return true; + } + // An extra hit not caused by Multi Lens -- assume it is Parental Bond + return false; + } +} diff --git a/src/items/held-items/nature-weight-booster.ts b/src/items/held-items/nature-weight-booster.ts new file mode 100644 index 00000000000..ae515a96196 --- /dev/null +++ b/src/items/held-items/nature-weight-booster.ts @@ -0,0 +1,32 @@ +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; + +export interface NATURE_WEIGHT_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** The amount of exp to gain */ + multiplier: NumberHolder; +} + +export class NatureWeightBoosterHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.NATURE_WEIGHT_BOOSTER]; + + /** + * 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 + */ + apply(params: NATURE_WEIGHT_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const multiplier = params.multiplier; + const stackCount = pokemon.heldItemManager.getStack(this.type); + if (multiplier.value !== 1) { + multiplier.value += 0.1 * stackCount * (multiplier.value > 1 ? 1 : -1); + return true; + } + + return false; + } +} diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts new file mode 100644 index 00000000000..94bb72c9ed4 --- /dev/null +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -0,0 +1,66 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { BATTLE_STATS } from "#enums/stat"; +import i18next from "i18next"; +import { ConsumableHeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { getPokemonNameWithAffix } from "#app/messages"; + +export interface RESET_NEGATIVE_STAT_STAGE_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + /** Whether the move was used by a player pokemon */ + isPlayer: boolean; +} + +/** + * Modifier used for held items, namely White Herb, that restore adverse stat + * stages in battle. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.RESET_NEGATIVE_STAT_STAGE]; + + get name(): string { + return i18next.t("modifierType:ModifierType.WHITE_HERB.name"); + } + + get description(): string { + return i18next.t("modifierType:ModifierType.WHITE_HERB.description"); + } + + get iconName(): string { + return "white_herb"; + } + /** + * Goes through the holder's stat stages and, if any are negative, resets that + * stat stage back to 0. + * @param pokemon {@linkcode Pokemon} that holds the item + * @returns `true` if any stat stages were reset, false otherwise + */ + apply(params: RESET_NEGATIVE_STAT_STAGE_PARAMS): boolean { + const pokemon = params.pokemon; + const isPlayer = params.isPlayer; + let statRestored = false; + + for (const s of BATTLE_STATS) { + if (pokemon.getStatStage(s) < 0) { + pokemon.setStatStage(s, 0); + statRestored = true; + } + } + + if (statRestored) { + globalScene.phaseManager.queueMessage( + i18next.t("modifier:resetNegativeStatStageApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.name, + }), + ); + + this.consume(pokemon, isPlayer, true, false); + } + + return statRestored; + } +} diff --git a/src/items/held-items/stat-booster.ts b/src/items/held-items/stat-booster.ts new file mode 100644 index 00000000000..711b4f276ab --- /dev/null +++ b/src/items/held-items/stat-booster.ts @@ -0,0 +1,191 @@ +import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; +import type Pokemon from "#app/field/pokemon"; +import type { NumberHolder } from "#app/utils/common"; +import { HeldItemId } from "#enums/held-item-id"; +import type { SpeciesId } from "#enums/species-id"; +import type { Stat } from "#enums/stat"; +import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; + +export interface STAT_BOOST_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + stat: Stat; + statValue: NumberHolder; +} + +/** + * Modifier used for held items that Applies {@linkcode Stat} boost(s) + * using a multiplier. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class StatBoostHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.STAT_BOOST]; + /** The stats that the held item boosts */ + protected stats: Stat[]; + /** The multiplier used to increase the relevant stat(s) */ + protected multiplier: number; + + constructor(type: HeldItemId, maxStackCount = 1, stats: Stat[], multiplier: number) { + super(type, maxStackCount); + + this.stats = stats; + this.multiplier = multiplier; + } + + /** + * Checks if the incoming stat is listed in {@linkcode stats} + * @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 + */ + // 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 _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(params: STAT_BOOST_PARAMS): boolean { + params.statValue.value *= this.multiplier; + return true; + } + + getMaxHeldItemCount(_pokemon: Pokemon): number { + return 1; + } +} + +/** + * Modifier used for held items, specifically Eviolite, that apply + * {@linkcode Stat} boost(s) using a multiplier if the holder can evolve. + * @extends StatBoosterModifier + * @see {@linkcode apply} + */ +export class EvolutionStatBoostHeldItem extends StatBoostHeldItem { + /** + * Checks if the stat boosts can apply and if the holder is not currently + * Gigantamax'd. + * @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 + */ + // 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 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 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 + */ + override apply(params: STAT_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const isUnevolved = pokemon.getSpeciesForm(true).speciesId in pokemonEvolutions; + + 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 + params.statValue.value *= 1 + (this.multiplier - 1) / 2; + return true; + } + if (isUnevolved && !pokemon.isMax()) { + // Full boost applied if holder is unfused and unevolved or, if fused, both parts of fusion are unevolved + return super.apply(params); + } + + return false; + } +} + +export type SpeciesStatBoosterItemId = + | typeof HeldItemId.LIGHT_BALL + | typeof HeldItemId.THICK_CLUB + | typeof HeldItemId.METAL_POWDER + | typeof HeldItemId.QUICK_POWDER + | typeof HeldItemId.DEEP_SEA_SCALE + | typeof HeldItemId.DEEP_SEA_TOOTH; + +export const SPECIES_STAT_BOOSTER_ITEMS: SpeciesStatBoosterItemId[] = [ + HeldItemId.LIGHT_BALL, + HeldItemId.THICK_CLUB, + HeldItemId.METAL_POWDER, + HeldItemId.QUICK_POWDER, + HeldItemId.DEEP_SEA_SCALE, + HeldItemId.DEEP_SEA_TOOTH, +]; + +/** + * Modifier used for held items that Applies {@linkcode Stat} boost(s) using a + * multiplier if the holder is of a specific {@linkcode SpeciesId}. + * @extends StatBoostHeldItem + * @see {@linkcode apply} + */ +export class SpeciesStatBoostHeldItem extends StatBoostHeldItem { + /** The species that the held item's stat boost(s) apply to */ + public species: SpeciesId[]; + + constructor( + type: SpeciesStatBoosterItemId, + maxStackCount = 1, + stats: Stat[], + multiplier: number, + species: SpeciesId[], + ) { + super(type, maxStackCount, stats, multiplier); + this.species = species; + } + + /** + * Checks if the incoming stat is listed in {@linkcode stats} and if the holder's {@linkcode SpeciesId} + * (or its fused species) is listed in {@linkcode species}. + * @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 + */ + // 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))) + // ); + // } + + apply(params: STAT_BOOST_PARAMS): boolean { + const pokemon = params.pokemon; + const fitsSpecies = + this.species.includes(pokemon.getSpeciesForm(true).speciesId) || + (pokemon.isFusion() && this.species.includes(pokemon.getFusionSpeciesForm(true).speciesId)); + + if (fitsSpecies) { + return super.apply(params); + } + + return false; + } + + /** + * Checks if either parameter is included in the corresponding lists + * @param speciesId {@linkcode SpeciesId} being checked + * @param stat {@linkcode Stat} being checked + * @returns `true` if both parameters are in {@linkcode species} and {@linkcode stats} respectively, false otherwise + */ + contains(speciesId: SpeciesId, stat: Stat): boolean { + return this.species.includes(speciesId) && this.stats.includes(stat); + } +} diff --git a/src/items/held-items/survive-chance.ts b/src/items/held-items/survive-chance.ts new file mode 100644 index 00000000000..e23477f0fab --- /dev/null +++ b/src/items/held-items/survive-chance.ts @@ -0,0 +1,57 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import type { BooleanHolder } from "#app/utils/common"; +import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; +import { getPokemonNameWithAffix } from "#app/messages"; + +export interface SURVIVE_CHANCE_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; + surviveDamage: BooleanHolder; +} + +/** + * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a + * set {@linkcode StatusEffect} at the end of a turn. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class SurviveChanceHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.SURVIVE_CHANCE]; + + /** + * 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; + // } + + /** + * 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 + */ + apply(params: SURVIVE_CHANCE_PARAMS): boolean { + const pokemon = params.pokemon; + const surviveDamage = params.surviveDamage; + const stackCount = pokemon.heldItemManager.getStack(this.type); + if (!surviveDamage.value && pokemon.randBattleSeedInt(10) < stackCount) { + surviveDamage.value = true; + + globalScene.phaseManager.queueMessage( + i18next.t("modifier:surviveDamageApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.name, + }), + ); + return true; + } + + return false; + } +} diff --git a/src/items/held-items/turn-end-heal.ts b/src/items/held-items/turn-end-heal.ts new file mode 100644 index 00000000000..0ba42db70ae --- /dev/null +++ b/src/items/held-items/turn-end-heal.ts @@ -0,0 +1,36 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; +import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; +import { toDmgValue } from "#app/utils/common"; +import { getPokemonNameWithAffix } from "#app/messages"; + +export interface TURN_END_HEAL_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; +} + +export class TurnEndHealHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.TURN_END_HEAL]; + + apply(params: TURN_END_HEAL_PARAMS): boolean { + const pokemon = params.pokemon; + const stackCount = pokemon.heldItemManager.getStack(this.type); + if (pokemon.isFullHp()) { + return false; + } + globalScene.phaseManager.unshiftPhase( + new PokemonHealPhase( + pokemon.getBattlerIndex(), + toDmgValue(pokemon.getMaxHp() / 16) * stackCount, + i18next.t("modifier:turnHealApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + typeName: this.name, + }), + true, + ), + ); + return true; + } +} diff --git a/src/items/held-items/turn-end-status.ts b/src/items/held-items/turn-end-status.ts new file mode 100644 index 00000000000..ed0a1d609fc --- /dev/null +++ b/src/items/held-items/turn-end-status.ts @@ -0,0 +1,40 @@ +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import type { StatusEffect } from "#enums/status-effect"; +import type { HeldItemId } from "#enums/held-item-id"; + +export interface TURN_END_STATUS_PARAMS { + /** The pokemon with the item */ + pokemon: Pokemon; +} + +/** + * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a + * set {@linkcode StatusEffect} at the end of a turn. + * @extends PokemonHeldItemModifier + * @see {@linkcode apply} + */ +export class TurnEndStatusHeldItem extends HeldItem { + public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.TURN_END_STATUS]; + /** The status effect to be applied by the held item */ + public effect: StatusEffect; + + constructor(type: HeldItemId, maxStackCount = 1, effect: StatusEffect) { + super(type, maxStackCount); + + this.effect = effect; + } + + /** + * Tries to inflicts the holder with the associated {@linkcode StatusEffect}. + * @param pokemon {@linkcode Pokemon} that holds the held item + * @returns `true` if the status effect was applied successfully + */ + apply(params: TURN_END_STATUS_PARAMS): boolean { + return params.pokemon.trySetStatus(this.effect, true, undefined, undefined, this.name); + } + + getStatusEffect(): StatusEffect { + return this.effect; + } +} diff --git a/src/items/init-held-item-pools.ts b/src/items/init-held-item-pools.ts new file mode 100644 index 00000000000..0e6dc7b99e6 --- /dev/null +++ b/src/items/init-held-item-pools.ts @@ -0,0 +1,80 @@ +import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { RewardTier } from "#enums/reward-tier"; +import { dailyStarterHeldItemPool, trainerHeldItemPool, wildHeldItemPool } from "./held-item-pool"; + +/** + * Initialize the wild held item pool + */ +function initWildHeldItemPool() { + wildHeldItemPool[RewardTier.COMMON] = [{ entry: HeldItemCategoryId.BERRY, weight: 1 }]; + wildHeldItemPool[RewardTier.GREAT] = [{ entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 1 }]; + wildHeldItemPool[RewardTier.ULTRA] = [ + { entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 5 }, + { entry: HeldItemId.WHITE_HERB, weight: 0 }, + ]; + wildHeldItemPool[RewardTier.ROGUE] = [{ entry: HeldItemId.LUCKY_EGG, weight: 4 }]; + wildHeldItemPool[RewardTier.MASTER] = [{ entry: HeldItemId.GOLDEN_EGG, weight: 1 }]; +} + +/** + * Initialize the trainer pokemon held item pool + */ +function initTrainerHeldItemPool() { + trainerHeldItemPool[RewardTier.COMMON] = [ + { entry: HeldItemCategoryId.BERRY, weight: 8 }, + { entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 3 }, + ]; + trainerHeldItemPool[RewardTier.GREAT] = [{ entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 3 }]; + trainerHeldItemPool[RewardTier.ULTRA] = [ + { entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 10 }, + { entry: HeldItemId.WHITE_HERB, weight: 0 }, + ]; + trainerHeldItemPool[RewardTier.ROGUE] = [ + { entry: HeldItemId.FOCUS_BAND, weight: 2 }, + { entry: HeldItemId.LUCKY_EGG, weight: 4 }, + { entry: HeldItemId.QUICK_CLAW, weight: 1 }, + { entry: HeldItemId.GRIP_CLAW, weight: 1 }, + { entry: HeldItemId.WIDE_LENS, weight: 1 }, + ]; + trainerHeldItemPool[RewardTier.MASTER] = [ + { entry: HeldItemId.KINGS_ROCK, weight: 1 }, + { entry: HeldItemId.LEFTOVERS, weight: 1 }, + { entry: HeldItemId.SHELL_BELL, weight: 1 }, + { entry: HeldItemId.SCOPE_LENS, weight: 1 }, + ]; +} + +/** + * Initialize the daily starter held item pool + */ +function initDailyStarterModifierPool() { + dailyStarterHeldItemPool[RewardTier.COMMON] = [ + { entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 1 }, + { entry: HeldItemCategoryId.BERRY, weight: 3 }, + ]; + dailyStarterHeldItemPool[RewardTier.GREAT] = [{ entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 5 }]; + dailyStarterHeldItemPool[RewardTier.ULTRA] = [ + { entry: HeldItemId.REVIVER_SEED, weight: 4 }, + { entry: HeldItemId.SOOTHE_BELL, weight: 1 }, + { entry: HeldItemId.SOUL_DEW, weight: 1 }, + { entry: HeldItemId.GOLDEN_PUNCH, weight: 1 }, + ]; + dailyStarterHeldItemPool[RewardTier.ROGUE] = [ + { entry: HeldItemId.GRIP_CLAW, weight: 5 }, + { entry: HeldItemId.BATON, weight: 2 }, + { entry: HeldItemId.FOCUS_BAND, weight: 5 }, + { entry: HeldItemId.QUICK_CLAW, weight: 3 }, + { entry: HeldItemId.KINGS_ROCK, weight: 3 }, + ]; + dailyStarterHeldItemPool[RewardTier.MASTER] = [ + { entry: HeldItemId.LEFTOVERS, weight: 1 }, + { entry: HeldItemId.SHELL_BELL, weight: 1 }, + ]; +} + +export function initHeldItemPools() { + // Default held item pools for specific scenarios + initWildHeldItemPool(); + initTrainerHeldItemPool(); + initDailyStarterModifierPool(); +} diff --git a/src/items/init-trainer-item-pools.ts b/src/items/init-trainer-item-pools.ts new file mode 100644 index 00000000000..40439764b0c --- /dev/null +++ b/src/items/init-trainer-item-pools.ts @@ -0,0 +1,41 @@ +import { RewardTier } from "#enums/reward-tier"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import { enemyBuffTokenPool } from "./trainer-item-pool"; + +/** + * Initialize the enemy buff modifier pool + */ +function initEnemyBuffTokenPool() { + enemyBuffTokenPool[RewardTier.COMMON] = [ + { entry: TrainerItemId.ENEMY_DAMAGE_BOOSTER, weight: 9 }, + { entry: TrainerItemId.ENEMY_DAMAGE_REDUCTION, weight: 9 }, + { entry: TrainerItemId.ENEMY_ATTACK_POISON_CHANCE, weight: 3 }, + { entry: TrainerItemId.ENEMY_ATTACK_PARALYZE_CHANCE, weight: 3 }, + { entry: TrainerItemId.ENEMY_ATTACK_BURN_CHANCE, weight: 3 }, + { entry: TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE, weight: 9 }, + { entry: TrainerItemId.ENEMY_ENDURE_CHANCE, weight: 4 }, + { entry: TrainerItemId.ENEMY_FUSED_CHANCE, weight: 1 }, + ]; + enemyBuffTokenPool[RewardTier.GREAT] = [ + { entry: TrainerItemId.ENEMY_DAMAGE_BOOSTER, weight: 5 }, + { entry: TrainerItemId.ENEMY_DAMAGE_REDUCTION, weight: 5 }, + { entry: TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE, weight: 5 }, + { entry: TrainerItemId.ENEMY_ENDURE_CHANCE, weight: 5 }, + { entry: TrainerItemId.ENEMY_FUSED_CHANCE, weight: 1 }, + ]; + enemyBuffTokenPool[RewardTier.ULTRA] = [ + { entry: TrainerItemId.ENEMY_DAMAGE_BOOSTER, weight: 10 }, + { entry: TrainerItemId.ENEMY_DAMAGE_REDUCTION, weight: 10 }, + { entry: TrainerItemId.ENEMY_HEAL, weight: 10 }, + { entry: TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE, weight: 10 }, + { entry: TrainerItemId.ENEMY_ENDURE_CHANCE, weight: 10 }, + { entry: TrainerItemId.ENEMY_FUSED_CHANCE, weight: 5 }, + ]; + enemyBuffTokenPool[RewardTier.ROGUE] = []; + enemyBuffTokenPool[RewardTier.MASTER] = []; +} + +export function initTrainerItemPools() { + // Default held item pools for specific scenarios + initEnemyBuffTokenPool(); +} diff --git a/src/items/item-utility.ts b/src/items/item-utility.ts new file mode 100644 index 00000000000..540406acdf2 --- /dev/null +++ b/src/items/item-utility.ts @@ -0,0 +1,41 @@ +import { allHeldItems, allTrainerItems } from "#app/data/data-lists"; +import { formChangeItemName } from "#app/data/pokemon-forms"; +import type { FormChangeItem } from "#enums/form-change-item"; +import type { HeldItemId } from "#enums/held-item-id"; +import type { TrainerItemId } from "#enums/trainer-item-id"; + +export const trainerItemSortFunc = (a: TrainerItemId, b: TrainerItemId): number => { + const itemNameMatch = allTrainerItems[a].name.localeCompare(allTrainerItems[b].name); + const itemIdMatch = a - b; + + if (itemIdMatch === 0) { + return itemNameMatch; + //Finally sort by item name + } + return itemIdMatch; +}; + +//TODO: revisit this function +export const heldItemSortFunc = (a: HeldItemId, b: HeldItemId): number => { + const itemNameMatch = allHeldItems[a].name.localeCompare(allHeldItems[b].name); + const itemIdMatch = a - b; + + if (itemIdMatch === 0) { + return itemNameMatch; + //Finally sort by item name + } + return itemIdMatch; +}; + +export const formChangeItemSortFunc = (a: FormChangeItem, b: FormChangeItem): number => { + const nameA = formChangeItemName(a); + const nameB = formChangeItemName(b); + const itemNameMatch = nameA.localeCompare(nameB); + const itemIdMatch = a - b; + + if (itemIdMatch === 0) { + return itemNameMatch; + //Finally sort by item name + } + return itemIdMatch; +}; diff --git a/src/items/modifier-to-item-migrator-utils.ts b/src/items/modifier-to-item-migrator-utils.ts new file mode 100644 index 00000000000..15682064597 --- /dev/null +++ b/src/items/modifier-to-item-migrator-utils.ts @@ -0,0 +1,152 @@ +import type ModifierData from "#app/system/modifier-data"; +import type { BerryType } from "#enums/berry-type"; +import { HeldItemId } from "#enums/held-item-id"; +import type { PokemonType } from "#enums/pokemon-type"; +import { Stat, type PermanentStat } from "#enums/stat"; +import type { PokemonItemMap } from "./held-item-data-types"; +import { attackTypeToHeldItem } from "./held-items/attack-type-booster"; +import { permanentStatToHeldItem } from "./held-items/base-stat-booster"; +import { berryTypeToHeldItem } from "./held-items/berry"; +import { SpeciesId } from "#enums/species-id"; + +const uniqueModifierToItem = { + EvoTrackerModifier: HeldItemId.GIMMIGHOUL_EVO_TRACKER, + PokemonBaseStatFlatModifier: HeldItemId.OLD_GATEAU, + PokemonIncrementingStatModifier: HeldItemId.MACHO_BRACE, + SurviveDamageModifier: HeldItemId.FOCUS_BAND, + BypassSpeedChanceModifier: HeldItemId.QUICK_CLAW, + FlinchChanceModifier: HeldItemId.KINGS_ROCK, + TurnHealModifier: HeldItemId.LEFTOVERS, + HitHealModifier: HeldItemId.SHELL_BELL, + PokemonInstantReviveModifier: HeldItemId.REVIVER_SEED, + ResetNegativeStatStageModifier: HeldItemId.WHITE_HERB, + FieldEffectModifier: HeldItemId.MYSTICAL_ROCK, + PokemonFriendshipBoosterModifier: HeldItemId.SOOTHE_BELL, + PokemonNatureWeightModifier: HeldItemId.SOUL_DEW, + PokemonMoveAccuracyBoosterModifier: HeldItemId.WIDE_LENS, + PokemonMultiHitModifier: HeldItemId.MULTI_LENS, + DamageMoneyRewardModifier: HeldItemId.GOLDEN_PUNCH, + SwitchEffectTransferModifier: HeldItemId.BATON, + TurnHeldItemTransferModifier: HeldItemId.MINI_BLACK_HOLE, + ContactHeldItemTransferChanceModifier: HeldItemId.GRIP_CLAW, +} as const; + +type UniqueModifierString = keyof typeof uniqueModifierToItem; + +function isUniqueModifierString(value: string): value is UniqueModifierString { + return value in uniqueModifierToItem; +} + +const modifierCategoryList = [ + "BaseStatModifier", + "EvolutionStatBoosterModifier", + "SpeciesStatBoosterModifier", + "CritBoosterModifier", + "SpeciesCritBoosterModifier", + "AttackTypeBoosterModifier", + "TurnStatusEffectModifier", + "BerryModifier", + "PokemonExpBoosterModifier", + "PokemonFormChangeItemModifier", + "PokemonBaseStatTotalModifier", +] as const; + +type ModifierCategoryString = (typeof modifierCategoryList)[number]; + +function isModifierCategoryString(value: string): value is ModifierCategoryString { + return modifierCategoryList.includes(value as ModifierCategoryString); +} + +function mapModifierCategoryToItems(modifier: ModifierCategoryString, typeId: string, args: any): HeldItemId { + if (modifier === "BaseStatModifier") { + const stat = args[1] as PermanentStat; + return permanentStatToHeldItem[stat]; + } + if (modifier === "EvolutionStatBoosterModifier") { + return HeldItemId.EVIOLITE; + } + if (modifier === "SpeciesStatBoosterModifier") { + const stats = args[1]; + const species = args[3]; + if (SpeciesId.PIKACHU in species) { + return HeldItemId.LIGHT_BALL; + } + if (SpeciesId.CUBONE in species) { + return HeldItemId.THICK_CLUB; + } + if (SpeciesId.DITTO in species && Stat.DEF in stats) { + return HeldItemId.METAL_POWDER; + } + if (SpeciesId.DITTO in species && Stat.SPDEF in stats) { + return HeldItemId.QUICK_POWDER; + } + if (SpeciesId.CLAMPERL in species && Stat.SPDEF in stats) { + return HeldItemId.DEEP_SEA_SCALE; + } + if (SpeciesId.CLAMPERL in species && Stat.SPATK in stats) { + return HeldItemId.DEEP_SEA_TOOTH; + } + } + if (modifier === "CritBoosterModifier") { + return HeldItemId.SCOPE_LENS; + } + if (modifier === "SpeciesCritBoosterModifier") { + return HeldItemId.LEEK; + } + if (modifier === "AttackTypeBoosterModifier") { + const moveType = args[1] as PokemonType; + return attackTypeToHeldItem[moveType]; + } + if (modifier === "TurnStatusEffectModifier") { + switch (typeId) { + case "TOXIC_ORB": + return HeldItemId.TOXIC_ORB; + case "FLAME_ORB": + return HeldItemId.FLAME_ORB; + } + } + if (modifier === "BerryModifier") { + const berryType = args[1] as BerryType; + return berryTypeToHeldItem[berryType]; + } + if (modifier === "PokemonExpBoosterModifier") { + const boost = args[1] as number; + return boost === 100 ? HeldItemId.GOLDEN_EGG : HeldItemId.LUCKY_EGG; + } + if (modifier === "PokemonBaseStatTotalModifier") { + const statModifier = args[1] as number; + return statModifier > 0 ? HeldItemId.SHUCKLE_JUICE_GOOD : HeldItemId.SHUCKLE_JUICE_BAD; + } + return 0; +} + +export function convertModifierSaveData(data: ModifierData[]) { + const pokemonItems: PokemonItemMap[] = []; + for (const entry of data) { + const typeId = entry.typeId; + const args = entry.args; + const pokemonId = args[0]; + const stack = entry.stackCount; + const className = entry.className; + + if (className === "PokemonFormChangeItemModifier") { + } + //TODO: Code to filter out modifiers which are not held items + + let itemId: HeldItemId = 0; + + if (isModifierCategoryString(className)) { + itemId = mapModifierCategoryToItems(className, typeId, args); + } + + if (isUniqueModifierString(className)) { + itemId = uniqueModifierToItem[className]; + } + + if (itemId) { + const specs = { id: itemId, stack: stack }; + const pokemonItem = { item: specs, pokemonId: pokemonId }; + pokemonItems.push(pokemonItem); + } + } +} diff --git a/src/items/trainer-item-data-types.ts b/src/items/trainer-item-data-types.ts new file mode 100644 index 00000000000..a024c4c362f --- /dev/null +++ b/src/items/trainer-item-data-types.ts @@ -0,0 +1,44 @@ +import type { RewardTier } from "#enums/reward-tier"; +import type { TrainerItemId } from "#enums/trainer-item-id"; + +export type TrainerItemData = { + stack: number; + disabled?: boolean; + cooldown?: number; +}; + +export type TrainerItemDataMap = { + [key in TrainerItemId]?: TrainerItemData; +}; + +export type TrainerItemSpecs = TrainerItemData & { + id: TrainerItemId; +}; + +export function isTrainerItemSpecs(entry: any): entry is TrainerItemSpecs { + return typeof entry.id === "number" && "stack" in entry; +} + +type TrainerItemPoolEntry = { + entry: TrainerItemId; + weight: number; +}; + +export type TrainerItemPool = TrainerItemPoolEntry[]; + +export type TrainerItemTieredPool = { + [key in RewardTier]?: TrainerItemPool; +}; + +export function isTrainerItemPool(value: any): value is TrainerItemPool { + return Array.isArray(value) && value.every(entry => "entry" in entry && "weight" in entry); +} + +type TrainerItemConfigurationEntry = { + entry: TrainerItemId | TrainerItemSpecs; + count?: number | (() => number); +}; + +export type TrainerItemConfiguration = TrainerItemConfigurationEntry[]; + +export type TrainerItemSaveData = TrainerItemSpecs[]; diff --git a/src/items/trainer-item-manager.ts b/src/items/trainer-item-manager.ts new file mode 100644 index 00000000000..04da440cde6 --- /dev/null +++ b/src/items/trainer-item-manager.ts @@ -0,0 +1,158 @@ +import { allTrainerItems } from "#app/data/data-lists"; +import type { TrainerItemId } from "#app/enums/trainer-item-id"; +import { + isTrainerItemSpecs, + type TrainerItemSaveData, + type TrainerItemConfiguration, + type TrainerItemDataMap, + type TrainerItemSpecs, +} from "#app/items/trainer-item-data-types"; + +export class TrainerItemManager { + public trainerItems: TrainerItemDataMap; + + constructor() { + this.trainerItems = {}; + } + + getItemSpecs(id: TrainerItemId): TrainerItemSpecs | undefined { + const item = this.trainerItems[id]; + if (item) { + const itemSpecs: TrainerItemSpecs = { + ...item, + id, + }; + return itemSpecs; + } + return undefined; + } + + generateTrainerItemConfiguration(restrictedIds?: TrainerItemId[]): TrainerItemConfiguration { + const config: TrainerItemConfiguration = []; + for (const [k, item] of Object.entries(this.trainerItems)) { + const id = Number(k); + if (item && (!restrictedIds || id in restrictedIds)) { + const specs: TrainerItemSpecs = { ...item, id }; + config.push({ entry: specs, count: 1 }); + } + } + return config; + } + + generateSaveData(): TrainerItemSaveData { + const saveData: TrainerItemSaveData = []; + for (const [k, item] of Object.entries(this.trainerItems)) { + const id = Number(k); + if (item) { + const specs: TrainerItemSpecs = { ...item, id }; + saveData.push(specs); + } + } + return saveData; + } + + getTrainerItems(): number[] { + return Object.keys(this.trainerItems).map(k => Number(k)); + } + + hasItem(itemType: TrainerItemId): boolean { + return itemType in this.trainerItems; + } + + getStack(itemType: TrainerItemId): number { + const item = this.trainerItems[itemType]; + return item ? item.stack : 0; + } + + isMaxStack(itemType: TrainerItemId): boolean { + const item = this.trainerItems[itemType]; + return item ? item.stack >= allTrainerItems[itemType].getMaxStackCount() : false; + } + + overrideItems(newItems: TrainerItemDataMap) { + this.trainerItems = newItems; + // The following is to allow randomly generated item configs to have stack 0 + for (const [item, properties] of Object.entries(this.trainerItems)) { + if (!properties || properties.stack <= 0) { + delete this.trainerItems[item]; + } + } + } + + add(itemType: TrainerItemId | TrainerItemSpecs, addStack?: number): boolean { + if (isTrainerItemSpecs(itemType)) { + return this.addItemWithSpecs(itemType); + } + + if (!addStack) { + addStack = allTrainerItems[itemType].isLapsing ? allTrainerItems[itemType].getMaxStackCount() : 1; + } + const maxStack = allTrainerItems[itemType].getMaxStackCount(); + const item = this.trainerItems[itemType]; + + if (item) { + // TODO: We may want an error message of some kind instead + if (item.stack < maxStack) { + item.stack = Math.min(item.stack + addStack, maxStack); + return true; + } + } else { + this.trainerItems[itemType] = { stack: Math.min(addStack, maxStack) }; + return true; + } + return false; + } + + addItemWithSpecs(itemSpecs: TrainerItemSpecs): boolean { + const id = itemSpecs.id; + const maxStack = allTrainerItems[id].getMaxStackCount(); + const item = this.trainerItems[id]; + + const tempStack = item?.stack ?? 0; + + this.trainerItems[id] = itemSpecs; + this.trainerItems[id].stack = Math.min(itemSpecs.stack + tempStack, maxStack); + + return true; + } + + remove(itemType: TrainerItemId, removeStack = 1, all = false) { + const item = this.trainerItems[itemType]; + + if (item) { + item.stack -= removeStack; + + if (all || item.stack <= 0) { + delete this.trainerItems[itemType]; + } + } + } + + filterRequestedItems(requestedItems: TrainerItemId[], exclude = false) { + const currentItems = this.getTrainerItems(); + return currentItems.filter(it => !exclude && requestedItems.some(entry => it === entry)); + } + + getTrainerItemCount(): number { + let total = 0; + for (const properties of Object.values(this.trainerItems)) { + total += properties?.stack ?? 0; + } + return total; + } + + lapseItems(): void { + for (const [item, properties] of Object.entries(this.trainerItems)) { + if (allTrainerItems[item].isLapsing && properties) { + properties.stack -= 1; + } + if (!properties || properties.stack <= 0) { + delete this.trainerItems[item]; + } + } + } + + clearItems() { + this.trainerItems = {}; + } +} diff --git a/src/items/trainer-item-pool.ts b/src/items/trainer-item-pool.ts new file mode 100644 index 00000000000..86a87925325 --- /dev/null +++ b/src/items/trainer-item-pool.ts @@ -0,0 +1,62 @@ +import { globalScene } from "#app/global-scene"; +import { isNullOrUndefined, pickWeightedIndex } from "#app/utils/common"; +import { RewardTier } from "#enums/reward-tier"; +import type { TrainerItemId } from "#enums/trainer-item-id"; +import { allTrainerItems } from "./all-trainer-items"; +import type { TrainerItemPool, TrainerItemTieredPool } from "./trainer-item-data-types"; +import type { TrainerItemManager } from "./trainer-item-manager"; + +export const enemyBuffTokenPool: TrainerItemTieredPool = {}; + +function getPoolWeights(pool: TrainerItemPool, manager: TrainerItemManager): number[] { + return pool.map(p => { + if (manager.isMaxStack(p.entry)) { + return 0; + } + return p.weight; + }); +} + +export function getNewTrainerItemFromPool(pool: TrainerItemPool, manager: TrainerItemManager): TrainerItemId { + const weights = getPoolWeights(pool, manager); + + const pickedIndex = pickWeightedIndex(weights); + if (isNullOrUndefined(pickedIndex)) { + return 0; + } + const entry = pool[pickedIndex].entry; + + return entry as TrainerItemId; +} + +export function assignEnemyBuffTokenForWave(tier: RewardTier) { + let tierStackCount: number; + switch (tier) { + case RewardTier.ULTRA: + tierStackCount = 5; + break; + case RewardTier.GREAT: + tierStackCount = 3; + break; + default: + tierStackCount = 1; + break; + } + + if (!enemyBuffTokenPool[tier]) { + return; + } + + const retryCount = 50; + let candidate = getNewTrainerItemFromPool(enemyBuffTokenPool[tier], globalScene.enemyTrainerItems); + let r = 0; + while ( + ++r < retryCount && + allTrainerItems[candidate].getMaxStackCount() < + globalScene.enemyTrainerItems.getStack(candidate) + (r < 10 ? tierStackCount : 1) + ) { + candidate = getNewTrainerItemFromPool(enemyBuffTokenPool[tier], globalScene.enemyTrainerItems); + } + + globalScene.enemyTrainerItems.add(candidate, tierStackCount); +} diff --git a/src/items/trainer-item.ts b/src/items/trainer-item.ts new file mode 100644 index 00000000000..a5877a9ad97 --- /dev/null +++ b/src/items/trainer-item.ts @@ -0,0 +1,575 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { hslToHex, randSeedFloat, toDmgValue, type BooleanHolder, type NumberHolder } from "#app/utils/common"; +import { TrainerItemId, TrainerItemNames } from "#enums/trainer-item-id"; +import i18next from "i18next"; +import type { TrainerItemManager } from "./trainer-item-manager"; +import { addTextObject, TextStyle } from "#app/ui/text"; +import { getStatKey, Stat, type TempBattleStat } from "#enums/stat"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { StatusEffect } from "#enums/status-effect"; + +export const TRAINER_ITEM_EFFECT = { + LEVEL_INCREMENT_BOOSTER: 1, + PRESERVE_BERRY: 2, + HEALING_BOOSTER: 3, + EXP_BOOSTER: 4, + MONEY_MULTIPLIER: 5, + HIDDEN_ABILITY_CHANCE_BOOSTER: 6, + SHINY_RATE_BOOSTER: 7, + CRITICAL_CATCH_CHANCE_BOOSTER: 8, + EXTRA_REWARD: 9, + + HEAL_SHOP_COST: 10, + DOUBLE_BATTLE_CHANCE_BOOSTER: 11, + + TEMP_STAT_STAGE_BOOSTER: 12, + TEMP_ACCURACY_BOOSTER: 13, + TEMP_CRIT_BOOSTER: 14, + + ENEMY_DAMAGE_BOOSTER: 15, + ENEMY_DAMAGE_REDUCER: 16, + ENEMY_HEAL: 17, + ENEMY_ATTACK_STATUS_CHANCE: 18, + ENEMY_STATUS_HEAL_CHANCE: 19, + ENEMY_ENDURE_CHANCE: 20, + ENEMY_FUSED_CHANCE: 21, +} as const; + +export type TRAINER_ITEM_EFFECT = (typeof TRAINER_ITEM_EFFECT)[keyof typeof TRAINER_ITEM_EFFECT]; + +export interface NUMBER_HOLDER_PARAMS { + numberHolder: NumberHolder; +} + +export interface BOOLEAN_HOLDER_PARAMS { + booleanHolder: BooleanHolder; +} + +export interface POKEMON_PARAMS { + pokemon: Pokemon; +} + +export class TrainerItem { + // public pokemonId: number; + public type: TrainerItemId; + public maxStackCount: number; + public isLapsing = false; + public effects: TRAINER_ITEM_EFFECT[] = []; + + //TODO: If this is actually never changed by any subclass, perhaps it should not be here + public soundName = "se/restore"; + + constructor(type: TrainerItemId, maxStackCount = 1) { + this.type = type; + this.maxStackCount = maxStackCount; + } + + get name(): string { + return i18next.t(`modifierType:ModifierType.${TrainerItemNames[this.type]}.name`); + } + + get description(): string { + return i18next.t(`modifierType:ModifierType.${TrainerItemNames[this.type]}.description`); + } + + get iconName(): string { + return `${TrainerItemNames[this.type]?.toLowerCase()}`; + } + + getMaxStackCount(): number { + return this.maxStackCount; + } + + createIcon(stackCount: number): Phaser.GameObjects.Container { + const container = globalScene.add.container(0, 0); + + const item = globalScene.add.sprite(0, 12, "items").setFrame(this.iconName).setOrigin(0, 0.5); + container.add(item); + + const stackText = this.getIconStackText(stackCount); + if (stackText) { + container.add(stackText); + } + + return container; + } + + getIconStackText(stackCount: number): Phaser.GameObjects.BitmapText | null { + if (this.getMaxStackCount() === 1 || stackCount < 1) { + return null; + } + + const text = globalScene.add.bitmapText(10, 15, "item-count", stackCount.toString(), 11); + text.letterSpacing = -0.5; + if (stackCount >= this.getMaxStackCount()) { + text.setTint(0xf89890); + } + text.setOrigin(0); + + return text; + } + + getScoreMultiplier(): number { + return 1; + } +} + +// Candy Jar +export class LevelIncrementBoosterTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.LEVEL_INCREMENT_BOOSTER]; + + apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + const count = params.numberHolder; + const stack = manager.getStack(this.type); + count.value += stack; + } +} + +// Berry Pouch +export interface PRESERVE_BERRY_PARAMS { + pokemon: Pokemon; + doPreserve: BooleanHolder; +} + +export class PreserveBerryTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.PRESERVE_BERRY]; + + apply(manager: TrainerItemManager, params: PRESERVE_BERRY_PARAMS) { + const stack = manager.getStack(this.type); + params.doPreserve.value ||= params.pokemon.randBattleSeedInt(10) < stack * 3; + } +} + +// Healing Charm +export class HealingBoosterTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.HEALING_BOOSTER]; + private multiplier: number; + + constructor(type: TrainerItemId, multiplier: number, stackCount?: number) { + super(type, stackCount); + + this.multiplier = multiplier; + } + + apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + const healingMultiplier = params.numberHolder; + const stack = manager.getStack(this.type); + healingMultiplier.value *= 1 + (this.multiplier - 1) * stack; + } +} + +// Exp Booster +export class ExpBoosterTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.EXP_BOOSTER]; + private boostPercent: number; + + constructor(type: TrainerItemId, boostPercent: number, stackCount?: number) { + super(type, stackCount); + + this.boostPercent = boostPercent; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.ExpBoosterModifierType.description", { + boostPercent: this.boostPercent, + }); + } + + apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + const boost = params.numberHolder; + const stack = manager.getStack(this.type); + boost.value = Math.floor(boost.value * (1 + stack * this.boostPercent * 0.01)); + } +} + +export class MoneyMultiplierTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER]; + + apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + const moneyMultiplier = params.numberHolder; + const stack = manager.getStack(this.type); + moneyMultiplier.value += Math.floor(moneyMultiplier.value * 0.2 * stack); + } +} + +export class HiddenAbilityChanceBoosterTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER]; + + apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + const boost = params.numberHolder; + const stack = manager.getStack(this.type); + boost.value *= Math.pow(2, -1 - stack); + } +} + +export class ShinyRateBoosterTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.SHINY_RATE_BOOSTER]; + + apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + const boost = params.numberHolder; + const stack = manager.getStack(this.type); + boost.value *= Math.pow(2, 1 + stack); + } +} + +export class CriticalCatchChanceBoosterTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.CRITICAL_CATCH_CHANCE_BOOSTER]; + + apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + const boost = params.numberHolder; + const stack = manager.getStack(this.type); + boost.value *= 1.5 + stack / 2; + } +} + +export class ExtraRewardTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.EXTRA_REWARD]; + + apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + const count = params.numberHolder; + const stack = manager.getStack(this.type); + count.value += stack; + } +} + +export class HealShopCostTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.HEAL_SHOP_COST]; + public readonly shopMultiplier: number; + + constructor(type: TrainerItemId, shopMultiplier: number, stackCount?: number) { + super(type, stackCount); + + this.shopMultiplier = shopMultiplier; + } + + apply(_manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + const moneyCost = params.numberHolder; + moneyCost.value = Math.floor(moneyCost.value * this.shopMultiplier); + } +} + +export class LapsingTrainerItem extends TrainerItem { + isLapsing = true; + + createIcon(battleCount: number): Phaser.GameObjects.Container { + const container = globalScene.add.container(0, 0); + + const item = globalScene.add.sprite(0, 12, "items").setFrame(this.iconName).setOrigin(0, 0.5); + container.add(item); + + // Linear interpolation on hue + const hue = Math.floor(120 * (battleCount / this.getMaxStackCount()) + 5); + + // Generates the color hex code with a constant saturation and lightness but varying hue + const typeHex = hslToHex(hue, 0.5, 0.9); + const strokeHex = hslToHex(hue, 0.7, 0.3); + + const battleCountText = addTextObject(27, 0, battleCount.toString(), TextStyle.PARTY, { + fontSize: "66px", + color: typeHex, + }); + battleCountText.setShadow(0, 0); + battleCountText.setStroke(strokeHex, 16); + battleCountText.setOrigin(1, 0); + container.add(battleCountText); + + return container; + } +} + +export class DoubleBattleChanceBoosterTrainerItem extends LapsingTrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.DOUBLE_BATTLE_CHANCE_BOOSTER]; + + get description(): string { + return i18next.t("modifierType:ModifierType.DoubleBattleChanceBoosterModifierType.description", { + battleCount: this.getMaxStackCount(), + }); + } + + apply(_manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + const doubleBattleChance = params.numberHolder; + // 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 /= 4; + } +} + +interface TempStatToTrainerItemMap { + [key: number]: TrainerItemId; +} + +export const tempStatToTrainerItem: TempStatToTrainerItemMap = { + [Stat.ATK]: TrainerItemId.X_ATTACK, + [Stat.DEF]: TrainerItemId.X_DEFENSE, + [Stat.SPATK]: TrainerItemId.X_SP_ATK, + [Stat.SPDEF]: TrainerItemId.X_SP_DEF, + [Stat.SPD]: TrainerItemId.X_SPEED, + [Stat.ACC]: TrainerItemId.X_ACCURACY, +}; + +export class TempStatStageBoosterTrainerItem extends LapsingTrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.TEMP_STAT_STAGE_BOOSTER]; + private stat: TempBattleStat; + + constructor(type: TrainerItemId, stat: TempBattleStat, stackCount?: number) { + super(type, stackCount); + + this.stat = stat; + } + + get name(): string { + return i18next.t(`modifierType:TempStatStageBoosterItem.${TrainerItemNames[this.type]?.toLowerCase()}`); + } + + get description(): string { + console.log(); + return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { + stat: i18next.t(getStatKey(this.stat)), + amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.percentage"), + }); + } + + apply(_manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + const statLevel = params.numberHolder; + const boost = 0.3; + statLevel.value += boost; + } +} + +export class TempAccuracyBoosterTrainerItem extends LapsingTrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.TEMP_ACCURACY_BOOSTER]; + + get name(): string { + return i18next.t(`modifierType:TempStatStageBoosterItem.${TrainerItemNames[this.type]?.toLowerCase()}`); + } + + get description(): string { + console.log(); + return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { + stat: i18next.t(getStatKey(Stat.ACC)), + amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.percentage"), + }); + } + + apply(_manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + const statLevel = params.numberHolder; + const boost = 1; + statLevel.value += boost; + } +} + +export class TempCritBoosterTrainerItem extends LapsingTrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.TEMP_CRIT_BOOSTER]; + + get description(): string { + return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { + stat: i18next.t("modifierType:ModifierType.DIRE_HIT.extra.raises"), + amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.stage"), + }); + } + + apply(_manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + const critLevel = params.numberHolder; + critLevel.value++; + } +} + +export class EnemyDamageBoosterTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_DAMAGE_BOOSTER]; + public damageBoost = 1.05; + + get iconName(): string { + return "wl_item_drop"; + } + + apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS): boolean { + const stack = manager.getStack(this.type); + const multiplier = params.numberHolder; + + multiplier.value = toDmgValue(multiplier.value * Math.pow(this.damageBoost, stack)); + + return true; + } + + getMaxStackCount(): number { + return 999; + } +} + +export class EnemyDamageReducerTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_DAMAGE_REDUCER]; + public damageReduction = 0.975; + + get iconName(): string { + return "wl_guard_spec"; + } + + apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS): boolean { + const stack = manager.getStack(this.type); + const multiplier = params.numberHolder; + + multiplier.value = toDmgValue(multiplier.value * Math.pow(this.damageReduction, stack)); + + return true; + } + + getMaxStackCount(): number { + return globalScene.currentBattle.waveIndex < 2000 ? 99 : 999; + } +} + +export class EnemyTurnHealTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_HEAL]; + public healPercent = 2; + + get iconName(): string { + return "wl_potion"; + } + + apply(manager: TrainerItemManager, params: POKEMON_PARAMS): boolean { + const stack = manager.getStack(this.type); + const enemyPokemon = params.pokemon; + + if (!enemyPokemon.isFullHp()) { + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + enemyPokemon.getBattlerIndex(), + Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * stack, 1), + i18next.t("modifier:enemyTurnHealApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon), + }), + true, + false, + false, + false, + true, + ); + return true; + } + + return false; + } +} + +export class EnemyAttackStatusEffectChanceTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_ATTACK_STATUS_CHANCE]; + public effect: StatusEffect; + + constructor(type: TrainerItemId, effect: StatusEffect, stackCount?: number) { + super(type, stackCount); + + this.effect = effect; + } + + get iconName(): string { + if (this.effect === StatusEffect.POISON) { + return "wl_antidote"; + } + if (this.effect === StatusEffect.PARALYSIS) { + return "wl_paralyze_heal"; + } + if (this.effect === StatusEffect.BURN) { + return "wl_burn_heal"; + } + return ""; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.EnemyAttackStatusEffectChanceModifierType.description", { + chancePercent: this.getChance() * 100, + statusEffect: getStatusEffectDescriptor(this.effect), + }); + } + + apply(manager: TrainerItemManager, params: POKEMON_PARAMS): boolean { + const stack = manager.getStack(this.type); + const enemyPokemon = params.pokemon; + const chance = this.getChance(); + + if (randSeedFloat() <= chance * stack) { + return enemyPokemon.trySetStatus(this.effect, true); + } + + return false; + } + + getChance(): number { + return 0.025 * (this.effect === StatusEffect.BURN || this.effect === StatusEffect.POISON ? 2 : 1); + } +} + +export class EnemyStatusEffectHealChanceTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_STATUS_HEAL_CHANCE]; + public chance = 0.025; + + get iconName(): string { + return "wl_full_heal"; + } + + apply(manager: TrainerItemManager, params: POKEMON_PARAMS): boolean { + const stack = manager.getStack(this.type); + const enemyPokemon = params.pokemon; + + if (!enemyPokemon.status || randSeedFloat() > this.chance * stack) { + return false; + } + + globalScene.phaseManager.queueMessage( + getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon)), + ); + enemyPokemon.resetStatus(); + enemyPokemon.updateInfo(); + return true; + } +} + +export class EnemyEndureChanceTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_ENDURE_CHANCE]; + public chance = 2; + + get iconName(): string { + return "wl_reset_urge"; + } + + get description(): string { + return i18next.t("modifierType:ModifierType.EnemyEndureChanceModifierType.description", { + chancePercent: this.chance, + }); + } + + apply(manager: TrainerItemManager, params: POKEMON_PARAMS): boolean { + const stack = manager.getStack(this.type); + const target = params.pokemon; + + if (target.waveData.endured || target.randBattleSeedInt(100) >= this.chance * stack) { + return false; + } + + target.addTag(BattlerTagType.ENDURE_TOKEN, 1); + + target.waveData.endured = true; + + return true; + } +} + +export class EnemyFusionChanceTrainerItem extends TrainerItem { + public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_FUSED_CHANCE]; + public chance = 0.01; + + get iconName(): string { + return "wl_custom_spliced"; + } + + apply(manager: TrainerItemManager, params: BOOLEAN_HOLDER_PARAMS) { + const stack = manager.getStack(this.type); + const isFusion = params.booleanHolder; + if (randSeedFloat() > this.chance * stack) { + return false; + } + isFusion.value = true; + } +} diff --git a/src/loading-scene.ts b/src/loading-scene.ts index f67d19e1027..bcc0cdbc1fc 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -21,8 +21,12 @@ import { initVouchers } from "#app/system/voucher"; import { BiomeId } from "#enums/biome-id"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; import { timedEventManager } from "./global-event-manager"; +import { initHeldItems } from "./items/all-held-items"; import { initModifierPools } from "./modifier/init-modifier-pools"; import { initModifierTypes } from "./modifier/modifier-type"; +import { initHeldItemPools } from "./items/init-held-item-pools"; +import { initTrainerItemPools } from "./items/init-trainer-item-pools"; +import { initTrainerItems } from "./items/all-trainer-items"; export class LoadingScene extends SceneBase { public static readonly KEY = "loading"; @@ -367,6 +371,8 @@ export class LoadingScene extends SceneBase { initModifierTypes(); initModifierPools(); + initHeldItemPools(); + initTrainerItemPools(); initAchievements(); initVouchers(); @@ -380,6 +386,8 @@ export class LoadingScene extends SceneBase { initSpecies(); initMoves(); initAbilities(); + initHeldItems(); + initTrainerItems(); initChallenges(); initMysteryEncounters(); } diff --git a/src/modifier/init-modifier-pools.ts b/src/modifier/init-modifier-pools.ts index 60697333600..afaf34e7af9 100644 --- a/src/modifier/init-modifier-pools.ts +++ b/src/modifier/init-modifier-pools.ts @@ -1,20 +1,11 @@ import type Pokemon from "#app/field/pokemon"; -import { - dailyStarterModifierPool, - enemyBuffModifierPool, - modifierPool, - trainerModifierPool, - wildModifierPool, -} from "#app/modifier/modifier-pools"; +import { modifierPool } from "#app/modifier/modifier-pools"; import { globalScene } from "#app/global-scene"; -import { DoubleBattleChanceBoosterModifier, SpeciesCritBoosterModifier, TurnStatusEffectModifier } from "./modifier"; import { WeightedModifierType } from "./modifier-type"; -import { ModifierTier } from "../enums/modifier-tier"; +import { RewardTier } from "#app/enums/reward-tier"; import type { WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types"; import { modifierTypes } from "#app/data/data-lists"; import { PokeballType } from "#enums/pokeball"; -import { BerryModifier } from "./modifier"; -import { BerryType } from "#enums/berry-type"; import { SpeciesId } from "#enums/species-id"; import { timedEventManager } from "#app/global-event-manager"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; @@ -26,41 +17,17 @@ import { AbilityId } from "#enums/ability-id"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; // biome-ignore lint/correctness/noUnusedImports: This is used in a tsdoc comment import type { initModifierTypes } from "./modifier-type"; - -/** - * Initialize the wild modifier pool - */ -function initWildModifierPool() { - wildModifierPool[ModifierTier.COMMON] = [new WeightedModifierType(modifierTypes.BERRY, 1)].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }); - wildModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1)].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }); - wildModifierPool[ModifierTier.ULTRA] = [ - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.WHITE_HERB, 0), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }); - wildModifierPool[ModifierTier.ROGUE] = [new WeightedModifierType(modifierTypes.LUCKY_EGG, 4)].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }); - wildModifierPool[ModifierTier.MASTER] = [new WeightedModifierType(modifierTypes.GOLDEN_EGG, 1)].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }); -} +import { HeldItemId } from "#enums/held-item-id"; +import { allHeldItems } from "#app/data/data-lists"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import { allTrainerItems } from "#app/data/data-lists"; +import type { TurnEndStatusHeldItem } from "#app/items/held-items/turn-end-status"; /** * Initialize the common modifier pool */ function initCommonModifierPool() { - modifierPool[ModifierTier.COMMON] = [ + modifierPool[RewardTier.COMMON] = [ new WeightedModifierType(modifierTypes.POKEBALL, () => (hasMaximumBalls(PokeballType.POKEBALL) ? 0 : 6), 6), new WeightedModifierType(modifierTypes.RARE_CANDY, 2), new WeightedModifierType( @@ -92,7 +59,7 @@ function initCommonModifierPool() { party.filter( p => p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + !p.heldItemManager.hasItem(HeldItemId.LEPPA_BERRY) && p .getMoveset() .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) @@ -111,7 +78,7 @@ function initCommonModifierPool() { party.filter( p => p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + !p.heldItemManager.hasItem(HeldItemId.LEPPA_BERRY) && p .getMoveset() .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) @@ -123,12 +90,12 @@ function initCommonModifierPool() { }, 3, ), - new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)), + new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(TrainerItemId.LURE, 2)), new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4), new WeightedModifierType(modifierTypes.BERRY, 2), new WeightedModifierType(modifierTypes.TM_COMMON, 2), ].map(m => { - m.setTier(ModifierTier.COMMON); + m.setTier(RewardTier.COMMON); return m; }); } @@ -137,7 +104,7 @@ function initCommonModifierPool() { * Initialize the Great modifier pool */ function initGreatModifierPool() { - modifierPool[ModifierTier.GREAT] = [ + modifierPool[RewardTier.GREAT] = [ new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6), new WeightedModifierType(modifierTypes.PP_UP, 2), new WeightedModifierType( @@ -148,12 +115,10 @@ function initGreatModifierPool() { p => p.hp && !!p.status && - !p.getHeldItems().some(i => { - if (i instanceof TurnStatusEffectModifier) { - return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; - } - return false; - }), + !p + .getHeldItems() + .filter(i => i in [HeldItemId.TOXIC_ORB, HeldItemId.FLAME_ORB]) + .some(i => (allHeldItems[i] as TurnEndStatusHeldItem).effect === p.status?.effect), ).length, 3, ); @@ -214,12 +179,10 @@ function initGreatModifierPool() { p => p.hp && !!p.status && - !p.getHeldItems().some(i => { - if (i instanceof TurnStatusEffectModifier) { - return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect; - } - return false; - }), + !p + .getHeldItems() + .filter(i => i in [HeldItemId.TOXIC_ORB, HeldItemId.FLAME_ORB]) + .some(i => (allHeldItems[i] as TurnEndStatusHeldItem).effect === p.status?.effect), ).length, 3, ); @@ -239,7 +202,7 @@ function initGreatModifierPool() { party.filter( p => p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + !p.heldItemManager.hasItem(HeldItemId.LEPPA_BERRY) && p .getMoveset() .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) @@ -258,7 +221,7 @@ function initGreatModifierPool() { party.filter( p => p.hp && - !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) && + !p.heldItemManager.hasItem(HeldItemId.LEPPA_BERRY) && p .getMoveset() .filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)) @@ -271,7 +234,7 @@ function initGreatModifierPool() { 3, ), new WeightedModifierType(modifierTypes.DIRE_HIT, 4), - new WeightedModifierType(modifierTypes.SUPER_LURE, lureWeightFunc(15, 4)), + new WeightedModifierType(modifierTypes.SUPER_LURE, lureWeightFunc(TrainerItemId.SUPER_LURE, 4)), new WeightedModifierType(modifierTypes.NUGGET, skipInLastClassicWaveOrDefault(5)), new WeightedModifierType(modifierTypes.SPECIES_STAT_BOOSTER, 4), new WeightedModifierType( @@ -331,7 +294,7 @@ function initGreatModifierPool() { 1, ), ].map(m => { - m.setTier(ModifierTier.GREAT); + m.setTier(RewardTier.GREAT); return m; }); } @@ -340,9 +303,9 @@ function initGreatModifierPool() { * Initialize the Ultra modifier pool */ function initUltraModifierPool() { - modifierPool[ModifierTier.ULTRA] = [ + modifierPool[RewardTier.ULTRA] = [ new WeightedModifierType(modifierTypes.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15), - new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)), + new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(TrainerItemId.MAX_LURE, 4)), new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), new WeightedModifierType(modifierTypes.PP_MAX, 3), new WeightedModifierType(modifierTypes.MINT, 4), @@ -368,7 +331,7 @@ function initUltraModifierPool() { (p.isFusion() && p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions)) ) { // Check if Pokemon is already holding an Eviolite - return !p.getHeldItems().some(i => i.type.id === "EVIOLITE"); + return !p.heldItemManager.hasItem(HeldItemId.EVIOLITE); } return false; }) @@ -385,7 +348,7 @@ function initUltraModifierPool() { // If a party member doesn't already have a Leek and is one of the relevant species, Leek can appear return party.some( p => - !p.getHeldItems().some(i => i instanceof SpeciesCritBoosterModifier) && + !p.heldItemManager.hasItem(HeldItemId.LEEK) && (checkedSpecies.includes(p.getSpeciesForm(true).speciesId) || (p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId))), ) @@ -398,7 +361,7 @@ function initUltraModifierPool() { modifierTypes.TOXIC_ORB, (party: Pokemon[]) => { return party.some(p => { - const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); + const isHoldingOrb = p.getHeldItems().some(i => i in [HeldItemId.FLAME_ORB, HeldItemId.TOXIC_ORB]); if (!isHoldingOrb) { const moveset = p @@ -444,7 +407,7 @@ function initUltraModifierPool() { modifierTypes.FLAME_ORB, (party: Pokemon[]) => { return party.some(p => { - const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); + const isHoldingOrb = p.getHeldItems().some(i => i in [HeldItemId.FLAME_ORB, HeldItemId.TOXIC_ORB]); if (!isHoldingOrb) { const moveset = p @@ -490,13 +453,8 @@ function initUltraModifierPool() { modifierTypes.MYSTICAL_ROCK, (party: Pokemon[]) => { return party.some(p => { - let isHoldingMax = false; - for (const i of p.getHeldItems()) { - if (i.type.id === "MYSTICAL_ROCK") { - isHoldingMax = i.getStackCount() === i.getMaxStackCount(); - break; - } - } + const stack = p.heldItemManager.getStack(HeldItemId.MYSTICAL_ROCK); + const isHoldingMax = stack === allHeldItems[HeldItemId.MYSTICAL_ROCK].maxStackCount; if (!isHoldingMax) { const moveset = p.getMoveset(true).map(m => m.moveId); @@ -558,13 +516,13 @@ function initUltraModifierPool() { new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), new WeightedModifierType(modifierTypes.WIDE_LENS, 7), ].map(m => { - m.setTier(ModifierTier.ULTRA); + m.setTier(RewardTier.ULTRA); return m; }); } function initRogueModifierPool() { - modifierPool[ModifierTier.ROGUE] = [ + modifierPool[RewardTier.ROGUE] = [ new WeightedModifierType(modifierTypes.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16), new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), new WeightedModifierType(modifierTypes.LEFTOVERS, 3), @@ -602,7 +560,7 @@ function initRogueModifierPool() { 3, ), ].map(m => { - m.setTier(ModifierTier.ROGUE); + m.setTier(RewardTier.ROGUE); return m; }); } @@ -611,7 +569,7 @@ function initRogueModifierPool() { * Initialize the Master modifier pool */ function initMasterModifierPool() { - modifierPool[ModifierTier.MASTER] = [ + modifierPool[RewardTier.MASTER] = [ new WeightedModifierType(modifierTypes.MASTER_BALL, () => (hasMaximumBalls(PokeballType.MASTER_BALL) ? 0 : 24), 24), new WeightedModifierType(modifierTypes.SHINY_CHARM, 14), new WeightedModifierType(modifierTypes.HEALING_CHARM, 18), @@ -644,140 +602,7 @@ function initMasterModifierPool() { 1, ), ].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }); -} - -function initTrainerModifierPool() { - trainerModifierPool[ModifierTier.COMMON] = [ - new WeightedModifierType(modifierTypes.BERRY, 8), - new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), - ].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }); - trainerModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3)].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }); - trainerModifierPool[ModifierTier.ULTRA] = [ - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.WHITE_HERB, 0), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }); - trainerModifierPool[ModifierTier.ROGUE] = [ - new WeightedModifierType(modifierTypes.FOCUS_BAND, 2), - new WeightedModifierType(modifierTypes.LUCKY_EGG, 4), - new WeightedModifierType(modifierTypes.QUICK_CLAW, 1), - new WeightedModifierType(modifierTypes.GRIP_CLAW, 1), - new WeightedModifierType(modifierTypes.WIDE_LENS, 1), - ].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }); - trainerModifierPool[ModifierTier.MASTER] = [ - new WeightedModifierType(modifierTypes.KINGS_ROCK, 1), - new WeightedModifierType(modifierTypes.LEFTOVERS, 1), - new WeightedModifierType(modifierTypes.SHELL_BELL, 1), - new WeightedModifierType(modifierTypes.SCOPE_LENS, 1), - ].map(m => { - m.setTier(ModifierTier.MASTER); - return m; - }); -} - -/** - * Initialize the enemy buff modifier pool - */ -function initEnemyBuffModifierPool() { - enemyBuffModifierPool[ModifierTier.COMMON] = [ - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 9), - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 9), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_POISON_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_PARALYZE_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_ATTACK_BURN_CHANCE, 3), - new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 9), - new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 4), - new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1), - ].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }); - enemyBuffModifierPool[ModifierTier.GREAT] = [ - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 5), - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 5), - new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 5), - new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 5), - new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1), - ].map(m => { - m.setTier(ModifierTier.GREAT); - return m; - }); - enemyBuffModifierPool[ModifierTier.ULTRA] = [ - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 10), - new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 10), - new WeightedModifierType(modifierTypes.ENEMY_HEAL, 10), - new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 10), - new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 10), - new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 5), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }); - enemyBuffModifierPool[ModifierTier.ROGUE] = [].map((m: WeightedModifierType) => { - m.setTier(ModifierTier.ROGUE); - return m; - }); - enemyBuffModifierPool[ModifierTier.MASTER] = [].map((m: WeightedModifierType) => { - m.setTier(ModifierTier.MASTER); - return m; - }); -} - -/** - * Initialize the daily starter modifier pool - */ -function initDailyStarterModifierPool() { - dailyStarterModifierPool[ModifierTier.COMMON] = [ - new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1), - new WeightedModifierType(modifierTypes.BERRY, 3), - ].map(m => { - m.setTier(ModifierTier.COMMON); - return m; - }); - dailyStarterModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 5)].map( - m => { - m.setTier(ModifierTier.GREAT); - return m; - }, - ); - dailyStarterModifierPool[ModifierTier.ULTRA] = [ - new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), - new WeightedModifierType(modifierTypes.SOOTHE_BELL, 1), - new WeightedModifierType(modifierTypes.SOUL_DEW, 1), - new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, 1), - ].map(m => { - m.setTier(ModifierTier.ULTRA); - return m; - }); - dailyStarterModifierPool[ModifierTier.ROGUE] = [ - new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), - new WeightedModifierType(modifierTypes.BATON, 2), - new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), - new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), - new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), - ].map(m => { - m.setTier(ModifierTier.ROGUE); - return m; - }); - dailyStarterModifierPool[ModifierTier.MASTER] = [ - new WeightedModifierType(modifierTypes.LEFTOVERS, 1), - new WeightedModifierType(modifierTypes.SHELL_BELL, 1), - ].map(m => { - m.setTier(ModifierTier.MASTER); + m.setTier(RewardTier.MASTER); return m; }); } @@ -793,12 +618,6 @@ export function initModifierPools() { initUltraModifierPool(); initRogueModifierPool(); initMasterModifierPool(); - - // Modifier pools for specific scenarios - initWildModifierPool(); - initTrainerModifierPool(); - initEnemyBuffModifierPool(); - initDailyStarterModifierPool(); } /** @@ -829,16 +648,15 @@ function skipInLastClassicWaveOrDefault(defaultWeight: number): WeightedModifier /** * High order function that returns a WeightedModifierTypeWeightFunc to ensure Lures don't spawn on Classic 199 * or if the lure still has over 60% of its duration left - * @param maxBattles The max battles the lure type in question lasts. 10 for green, 15 for Super, 30 for Max + * @param lureId The id of the lure type in question. * @param weight The desired weight for the lure when it does spawn * @returns A WeightedModifierTypeWeightFunc */ -function lureWeightFunc(maxBattles: number, weight: number): WeightedModifierTypeWeightFunc { +function lureWeightFunc(lureId: TrainerItemId, weight: number): WeightedModifierTypeWeightFunc { return () => { - const lures = globalScene.getModifiers(DoubleBattleChanceBoosterModifier); + const lureCount = globalScene.trainerItems.getStack(lureId); return !(globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex === 199) && - (lures.length === 0 || - lures.filter(m => m.getMaxBattles() === maxBattles && m.getBattleCount() >= maxBattles * 0.6).length === 0) + lureCount < allTrainerItems[lureId].getMaxStackCount() * 0.6 ? weight : 0; }; diff --git a/src/modifier/modifier-pools.ts b/src/modifier/modifier-pools.ts index 3396dca1f93..b0477f3c02f 100644 --- a/src/modifier/modifier-pools.ts +++ b/src/modifier/modifier-pools.ts @@ -6,11 +6,3 @@ import type { ModifierPool } from "#app/@types/modifier-types"; export const modifierPool: ModifierPool = {}; - -export const wildModifierPool: ModifierPool = {}; - -export const trainerModifierPool: ModifierPool = {}; - -export const enemyBuffModifierPool: ModifierPool = {}; - -export const dailyStarterModifierPool: ModifierPool = {}; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 96aea699eff..6bf1c517795 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1,103 +1,37 @@ import { globalScene } from "#app/global-scene"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; -import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; import { allMoves, modifierTypes } from "#app/data/data-lists"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; import { getPokeballCatchMultiplier, getPokeballName } from "#app/data/pokeball"; import { pokemonFormChanges, SpeciesFormChangeCondition } from "#app/data/pokemon-forms"; import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { FormChangeItem } from "#enums/form-change-item"; -import { getStatusEffectDescriptor } from "#app/data/status-effect"; +import { formChangeItemName } from "#app/data/pokemon-forms"; import { PokemonType } from "#enums/pokemon-type"; -import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { 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, TerrastalizeModifier, TmModifier, - TurnHealModifier, - TurnHeldItemTransferModifier, - TurnStatusEffectModifier, - type EnemyPersistentModifier, type Modifier, - type PersistentModifier, - TempExtraModifierModifier, - CriticalCatchChanceBoosterModifier, - FieldEffectModifier, + MoneyRewardModifier, } from "#app/modifier/modifier"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import Overrides from "#app/overrides"; import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system/voucher"; import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; @@ -120,19 +54,29 @@ import { PokeballType } from "#enums/pokeball"; import { SpeciesId } from "#enums/species-id"; import { SpeciesFormKey } from "#enums/species-form-key"; import type { PermanentStat, TempBattleStat } from "#enums/stat"; -import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; -import { StatusEffect } from "#enums/status-effect"; +import { Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import i18next from "i18next"; import { timedEventManager } from "#app/global-event-manager"; +import { HeldItemId } from "#enums/held-item-id"; +import { allHeldItems } from "#app/data/data-lists"; import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; +import { attackTypeToHeldItem } from "#app/items/held-items/attack-type-booster"; +import { permanentStatToHeldItem, statBoostItems } from "#app/items/held-items/base-stat-booster"; +import { + SPECIES_STAT_BOOSTER_ITEMS, + type SpeciesStatBoostHeldItem, + type SpeciesStatBoosterItemId, +} from "#app/items/held-items/stat-booster"; import { ModifierPoolType } from "#enums/modifier-pool-type"; -import { getModifierPoolForType, getModifierType } from "#app/utils/modifier-utils"; +import { getModifierPoolForType } from "#app/utils/modifier-utils"; import type { ModifierTypeFunc, WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types"; +import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#app/items/held-item-pool"; +import { berryTypeToHeldItem } from "#app/items/held-items/berry"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import { allTrainerItems } from "#app/data/data-lists"; +import { tempStatToTrainerItem, TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; -const outputModifierData = false; -const useMaxWeightForOutput = false; - -type NewModifierFunc = (type: ModifierType, args: any[]) => Modifier; +type NewModifierFunc = (type: ModifierType, args: any[]) => Modifier | null; export class ModifierType { public id: string; @@ -140,7 +84,7 @@ export class ModifierType { public iconImage: string; public group: string; public soundName: string; - public tier: ModifierTier; + public tier: RewardTier; protected newModifierFunc: NewModifierFunc | null; /** @@ -178,45 +122,12 @@ export class ModifierType { return i18next.t(`${this.localeKey}.description` as any); } - setTier(tier: ModifierTier): void { - this.tier = tier; + getIcon(): string { + return this.iconImage; } - getOrInferTier(poolType: ModifierPoolType = ModifierPoolType.PLAYER): ModifierTier | null { - if (this.tier) { - return this.tier; - } - if (!this.id) { - return null; - } - let poolTypes: ModifierPoolType[]; - switch (poolType) { - case ModifierPoolType.PLAYER: - poolTypes = [poolType, ModifierPoolType.TRAINER, ModifierPoolType.WILD]; - break; - case ModifierPoolType.WILD: - poolTypes = [poolType, ModifierPoolType.PLAYER, ModifierPoolType.TRAINER]; - break; - case ModifierPoolType.TRAINER: - poolTypes = [poolType, ModifierPoolType.PLAYER, ModifierPoolType.WILD]; - break; - default: - poolTypes = [poolType]; - break; - } - // Try multiple pool types in case of stolen items - for (const type of poolTypes) { - const pool = getModifierPoolForType(type); - for (const tier of getEnumValues(ModifierTier)) { - if (!pool.hasOwnProperty(tier)) { - continue; - } - if (pool[tier].find(m => (m as WeightedModifierType).modifierType.id === this.id)) { - return (this.tier = tier); - } - } - } - return null; + setTier(tier: RewardTier): void { + this.tier = tier; } /** @@ -245,7 +156,7 @@ export class ModifierType { party?: PlayerPokemon[], rerollCount = 0, ): ModifierType { - let defaultTier: undefined | ModifierTier; + let defaultTier: undefined | RewardTier; for (const tier of Object.values(getModifierPoolForType(poolType))) { for (const modifier of tier) { if (this.id === modifier.modifierType.id) { @@ -382,30 +293,22 @@ export class PokemonModifierType extends ModifierType { } } -export class PokemonHeldItemModifierType extends PokemonModifierType { - constructor( - localeKey: string, - iconImage: string, - newModifierFunc: NewModifierFunc, - group?: string, - soundName?: string, - ) { +export class HeldItemReward extends PokemonModifierType { + public itemId: HeldItemId; + constructor(itemId: HeldItemId, group?: string, soundName?: string) { super( - localeKey, - iconImage, - newModifierFunc, + "", + "", + () => null, (pokemon: PlayerPokemon) => { - const dummyModifier = this.newModifier(pokemon); - const matchingModifier = globalScene.findModifier( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(dummyModifier), - ) as PokemonHeldItemModifier; - const maxStackCount = dummyModifier.getMaxStackCount(); + const hasItem = pokemon.heldItemManager.hasItem(this.itemId); + const maxStackCount = allHeldItems[this.itemId].getMaxStackCount(); if (!maxStackCount) { return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.inoperable", { pokemonName: getPokemonNameWithAffix(pokemon), }); } - if (matchingModifier && matchingModifier.stackCount === maxStackCount) { + if (hasItem && pokemon.heldItemManager.getStack(this.itemId) === maxStackCount) { return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.tooMany", { pokemonName: getPokemonNameWithAffix(pokemon), }); @@ -415,10 +318,54 @@ export class PokemonHeldItemModifierType extends PokemonModifierType { group, soundName, ); + this.itemId = itemId; } - newModifier(...args: any[]): PokemonHeldItemModifier { - return super.newModifier(...args) as PokemonHeldItemModifier; + get name(): string { + return allHeldItems[this.itemId].name; + } + + getDescription(): string { + return allHeldItems[this.itemId].description; + } + + getIcon(): string { + return allHeldItems[this.itemId].iconName; + } + + apply(pokemon: Pokemon) { + pokemon.heldItemManager.add(this.itemId); + } +} + +export class TrainerItemReward extends ModifierType { + public itemId: TrainerItemId; + constructor(itemId: TrainerItemId, group?: string, soundName?: string) { + super("", "", () => null, group, soundName); + this.itemId = itemId; + } + + get name(): string { + return allTrainerItems[this.itemId].name; + } + + getDescription(): string { + return allTrainerItems[this.itemId].description; + } + + getIcon(): string { + return allTrainerItems[this.itemId].iconName; + } + + apply() { + globalScene.trainerItems.add(this.itemId); + } +} + +export class LapsingTrainerItemReward extends TrainerItemReward { + apply() { + globalScene.trainerItems.add(this.itemId, allTrainerItems[this.itemId].getMaxStackCount()); + console.log("WE GOT HERE WE ADDED IT"); } } @@ -736,165 +683,35 @@ export class RememberMoveModifierType extends PokemonModifierType { } } -export class DoubleBattleChanceBoosterModifierType extends ModifierType { - private maxBattles: number; - - constructor(localeKey: string, iconImage: string, maxBattles: number) { - super(localeKey, iconImage, (_type, _args) => new DoubleBattleChanceBoosterModifier(this, maxBattles), "lure"); - - this.maxBattles = maxBattles; - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.DoubleBattleChanceBoosterModifierType.description", { - battleCount: this.maxBattles, +class BerryRewardGenerator extends ModifierTypeGenerator { + constructor() { + super((_party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { + const item = berryTypeToHeldItem[pregenArgs[0] as BerryType]; + return new HeldItemReward(item); + } + const item = getNewBerryHeldItem(); + return new HeldItemReward(item); }); } } -export class TempStatStageBoosterModifierType extends ModifierType implements GeneratedPersistentModifierType { - private stat: TempBattleStat; - private nameKey: string; - private quantityKey: string; - - constructor(stat: TempBattleStat) { - const nameKey = TempStatStageBoosterModifierTypeGenerator.items[stat]; - super("", nameKey, (_type, _args) => new TempStatStageBoosterModifier(this, this.stat, 5)); - - this.stat = stat; - this.nameKey = nameKey; - this.quantityKey = stat !== Stat.ACC ? "percentage" : "stage"; - } - - get name(): string { - return i18next.t(`modifierType:TempStatStageBoosterItem.${this.nameKey}`); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { - stat: i18next.t(getStatKey(this.stat)), - amount: i18next.t(`modifierType:ModifierType.TempStatStageBoosterModifierType.extra.${this.quantityKey}`), - }); - } - - getPregenArgs(): any[] { - return [this.stat]; - } -} - -export class BerryModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType { - private berryType: BerryType; - - constructor(berryType: BerryType) { - super( - "", - `${BerryType[berryType].toLowerCase()}_berry`, - (type, args) => new BerryModifier(type, (args[0] as Pokemon).id, berryType), - "berry", - ); - - this.berryType = berryType; - this.id = "BERRY"; // needed to prevent harvest item deletion; remove after modifier rework - } - - get name(): string { - return getBerryName(this.berryType); - } - - getDescription(): string { - return getBerryEffectDescription(this.berryType); - } - - getPregenArgs(): any[] { - return [this.berryType]; - } -} - -enum AttackTypeBoosterItem { - SILK_SCARF, - BLACK_BELT, - SHARP_BEAK, - POISON_BARB, - SOFT_SAND, - HARD_STONE, - SILVER_POWDER, - SPELL_TAG, - METAL_COAT, - CHARCOAL, - MYSTIC_WATER, - MIRACLE_SEED, - MAGNET, - TWISTED_SPOON, - NEVER_MELT_ICE, - DRAGON_FANG, - BLACK_GLASSES, - FAIRY_FEATHER, -} - -export class AttackTypeBoosterModifierType - extends PokemonHeldItemModifierType - implements GeneratedPersistentModifierType -{ +export class AttackTypeBoosterReward extends HeldItemReward implements GeneratedPersistentModifierType { public moveType: PokemonType; public boostPercent: number; constructor(moveType: PokemonType, boostPercent: number) { - super( - "", - `${AttackTypeBoosterItem[moveType]?.toLowerCase()}`, - (_type, args) => new AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent), - ); - + const itemId = attackTypeToHeldItem[moveType]; + super(itemId); this.moveType = moveType; this.boostPercent = boostPercent; } - get name(): string { - return i18next.t(`modifierType:AttackTypeBoosterItem.${AttackTypeBoosterItem[this.moveType]?.toLowerCase()}`); - } - - getDescription(): string { - // TODO: Need getTypeName? - return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { - moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`), - }); - } - getPregenArgs(): any[] { return [this.moveType]; } } -export type SpeciesStatBoosterItem = keyof typeof SpeciesStatBoosterModifierTypeGenerator.items; - -/** - * Modifier type for {@linkcode SpeciesStatBoosterModifier} - * @extends PokemonHeldItemModifierType - * @implements GeneratedPersistentModifierType - */ -export class SpeciesStatBoosterModifierType - extends PokemonHeldItemModifierType - implements GeneratedPersistentModifierType -{ - public key: SpeciesStatBoosterItem; - - constructor(key: SpeciesStatBoosterItem) { - const item = SpeciesStatBoosterModifierTypeGenerator.items[key]; - 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; - } - - getPregenArgs(): any[] { - return [this.key]; - } -} - export class PokemonLevelIncrementModifierType extends PokemonModifierType { constructor(localeKey: string, iconImage: string) { super( @@ -907,10 +724,8 @@ export class PokemonLevelIncrementModifierType extends PokemonModifierType { getDescription(): string { let levels = 1; - const hasCandyJar = globalScene.modifiers.find(modifier => modifier instanceof LevelIncrementBoosterModifier); - if (hasCandyJar) { - levels += hasCandyJar.stackCount; - } + const candyJarStack = globalScene.trainerItems.getStack(TrainerItemId.CANDY_JAR); + levels += candyJarStack; return i18next.t("modifierType:ModifierType.PokemonLevelIncrementModifierType.description", { levels }); } } @@ -922,72 +737,40 @@ export class AllPokemonLevelIncrementModifierType extends ModifierType { getDescription(): string { let levels = 1; - const hasCandyJar = globalScene.modifiers.find(modifier => modifier instanceof LevelIncrementBoosterModifier); - if (hasCandyJar) { - levels += hasCandyJar.stackCount; - } + const candyJarStack = globalScene.trainerItems.getStack(TrainerItemId.CANDY_JAR); + levels += candyJarStack; return i18next.t("modifierType:ModifierType.AllPokemonLevelIncrementModifierType.description", { levels }); } } -export class BaseStatBoosterModifierType - extends PokemonHeldItemModifierType - implements GeneratedPersistentModifierType -{ +export class BaseStatBoosterReward extends HeldItemReward { private stat: PermanentStat; private key: string; constructor(stat: PermanentStat) { - const key = BaseStatBoosterModifierTypeGenerator.items[stat]; - super("", key, (_type, args) => new BaseStatModifier(this, (args[0] as Pokemon).id, this.stat)); + const key = statBoostItems[stat]; + const itemId = permanentStatToHeldItem[stat]; + super(itemId); this.stat = stat; this.key = key; } - - get name(): string { - return i18next.t(`modifierType:BaseStatBoosterItem.${this.key}`); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.BaseStatBoosterModifierType.description", { - stat: i18next.t(getStatKey(this.stat)), - }); - } - - getPregenArgs(): any[] { - return [this.stat]; - } } /** * Shuckle Juice item */ -export class PokemonBaseStatTotalModifierType - extends PokemonHeldItemModifierType - implements GeneratedPersistentModifierType -{ - private readonly statModifier: 10 | -15; +export class BaseStatTotalHeldItemReward extends HeldItemReward { + private readonly statModifier: number; - constructor(statModifier: 10 | -15) { - super( - statModifier > 0 - ? "modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD" - : "modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD", - statModifier > 0 ? "berry_juice_good" : "berry_juice_bad", - (_type, args) => new PokemonBaseStatTotalModifier(this, (args[0] as Pokemon).id, statModifier), - ); + constructor(itemId: HeldItemId, statModifier: number) { + super(itemId); this.statModifier = statModifier; } - override getDescription(): string { - return this.statModifier > 0 - ? i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD.description") - : i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD.description"); - } - - public getPregenArgs(): any[] { - return [this.statModifier]; + apply(pokemon: Pokemon) { + super.apply(pokemon); + pokemon.heldItemManager[this.itemId].data.statModifier = this.statModifier; } } @@ -1035,7 +818,7 @@ export class MoneyRewardModifierType extends ModifierType { getDescription(): string { const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); - globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); const formattedMoney = formatMoney(globalScene.moneyFormat, moneyAmount.value); return i18next.t("modifierType:ModifierType.MoneyRewardModifierType.description", { @@ -1045,88 +828,6 @@ export class MoneyRewardModifierType extends ModifierType { } } -export class ExpBoosterModifierType extends ModifierType { - private boostPercent: number; - - constructor(localeKey: string, iconImage: string, boostPercent: number) { - super(localeKey, iconImage, () => new ExpBoosterModifier(this, boostPercent)); - - this.boostPercent = boostPercent; - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.ExpBoosterModifierType.description", { - boostPercent: this.boostPercent, - }); - } -} - -export class PokemonExpBoosterModifierType extends PokemonHeldItemModifierType { - private boostPercent: number; - - constructor(localeKey: string, iconImage: string, boostPercent: number) { - super( - localeKey, - iconImage, - (_type, args) => new PokemonExpBoosterModifier(this, (args[0] as Pokemon).id, boostPercent), - ); - - this.boostPercent = boostPercent; - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonExpBoosterModifierType.description", { - boostPercent: this.boostPercent, - }); - } -} - -export class PokemonFriendshipBoosterModifierType extends PokemonHeldItemModifierType { - constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, (_type, args) => new PokemonFriendshipBoosterModifier(this, (args[0] as Pokemon).id)); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonFriendshipBoosterModifierType.description"); - } -} - -export class PokemonMoveAccuracyBoosterModifierType extends PokemonHeldItemModifierType { - private amount: number; - - constructor(localeKey: string, iconImage: string, amount: number, group?: string, soundName?: string) { - super( - localeKey, - iconImage, - (_type, args) => new PokemonMoveAccuracyBoosterModifier(this, (args[0] as Pokemon).id, amount), - group, - soundName, - ); - - this.amount = amount; - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonMoveAccuracyBoosterModifierType.description", { - accuracyAmount: this.amount, - }); - } -} - -export class PokemonMultiHitModifierType extends PokemonHeldItemModifierType { - constructor(localeKey: string, iconImage: string) { - super( - localeKey, - iconImage, - (type, args) => new PokemonMultiHitModifier(type as PokemonMultiHitModifierType, (args[0] as Pokemon).id), - ); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonMultiHitModifierType.description"); - } -} - export class TmModifierType extends PokemonModifierType { public moveId: MoveId; @@ -1218,14 +919,14 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge /** * Class that represents form changing items */ -export class FormChangeItemModifierType extends PokemonModifierType implements GeneratedPersistentModifierType { +export class FormChangeItemReward extends PokemonModifierType { public formChangeItem: FormChangeItem; constructor(formChangeItem: FormChangeItem) { super( "", FormChangeItem[formChangeItem].toLowerCase(), - (_type, args) => new PokemonFormChangeItemModifier(this, (args[0] as PlayerPokemon).id, formChangeItem, true), + () => null, (pokemon: PlayerPokemon) => { // Make sure the Pokemon has alternate forms if ( @@ -1251,15 +952,24 @@ export class FormChangeItemModifierType extends PokemonModifierType implements G } get name(): string { - return i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.formChangeItem]}`); + return formChangeItemName(this.formChangeItem); } getDescription(): string { return i18next.t("modifierType:ModifierType.FormChangeItemModifierType.description"); } - getPregenArgs(): any[] { - return [this.formChangeItem]; + apply(pokemon: Pokemon) { + if (pokemon.heldItemManager.hasFormChangeItem(this.formChangeItem)) { + return; + } + + pokemon.heldItemManager.addFormChangeItem(this.formChangeItem); + pokemon.heldItemManager.toggleActive(this.formChangeItem); + + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger); + + globalScene.updateItems(true); } } @@ -1283,85 +993,32 @@ export class FusePokemonModifierType extends PokemonModifierType { } } -class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { +class AttackTypeBoosterRewardGenerator extends ModifierTypeGenerator { constructor() { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { - return new AttackTypeBoosterModifierType(pregenArgs[0] as PokemonType, TYPE_BOOST_ITEM_BOOST_PERCENT); + return new AttackTypeBoosterReward(pregenArgs[0] as PokemonType, TYPE_BOOST_ITEM_BOOST_PERCENT); } - const attackMoveTypes = party.flatMap(p => - p - .getMoveset() - .map(m => m.getMove()) - .filter(m => m.is("AttackMove")) - .map(m => m.type), - ); - if (!attackMoveTypes.length) { - return null; - } + const item = getNewAttackTypeBoosterHeldItem(party); - const attackMoveTypeWeights = new Map(); - let totalWeight = 0; - for (const t of attackMoveTypes) { - if (attackMoveTypeWeights.has(t)) { - if (attackMoveTypeWeights.get(t)! < 3) { - // attackMoveTypeWeights.has(t) was checked before - attackMoveTypeWeights.set(t, attackMoveTypeWeights.get(t)! + 1); - } else { - continue; - } - } else { - attackMoveTypeWeights.set(t, 1); - } - totalWeight++; - } - - if (!totalWeight) { - return null; - } - - let type: PokemonType; - - const randInt = randSeedInt(totalWeight); - let weight = 0; - - for (const t of attackMoveTypeWeights.keys()) { - const typeWeight = attackMoveTypeWeights.get(t)!; // guranteed to be defined - if (randInt <= weight + typeWeight) { - type = t; - break; - } - weight += typeWeight; - } - - return new AttackTypeBoosterModifierType(type!, TYPE_BOOST_ITEM_BOOST_PERCENT); + return item ? new HeldItemReward(item) : null; }); } } -class BaseStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { - public static readonly items: Record = { - [Stat.HP]: "hp_up", - [Stat.ATK]: "protein", - [Stat.DEF]: "iron", - [Stat.SPATK]: "calcium", - [Stat.SPDEF]: "zinc", - [Stat.SPD]: "carbos", - }; - +class BaseStatBoosterRewardGenerator extends ModifierTypeGenerator { constructor() { super((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs) { - return new BaseStatBoosterModifierType(pregenArgs[0]); + return new BaseStatBoosterReward(pregenArgs[0]); } - const randStat: PermanentStat = randSeedInt(Stat.SPD + 1); - return new BaseStatBoosterModifierType(randStat); + return new HeldItemReward(getNewVitaminHeldItem()); }); } } -class TempStatStageBoosterModifierTypeGenerator extends ModifierTypeGenerator { +class TempStatStageBoosterRewardGenerator extends ModifierTypeGenerator { public static readonly items: Record = { [Stat.ATK]: "x_attack", [Stat.DEF]: "x_defense", @@ -1374,10 +1031,10 @@ class TempStatStageBoosterModifierTypeGenerator extends ModifierTypeGenerator { constructor() { super((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && TEMP_BATTLE_STATS.includes(pregenArgs[0])) { - return new TempStatStageBoosterModifierType(pregenArgs[0]); + return new LapsingTrainerItemReward(tempStatToTrainerItem[pregenArgs[0]]); } const randStat: TempBattleStat = randSeedInt(Stat.ACC, Stat.ATK); - return new TempStatStageBoosterModifierType(randStat); + return new LapsingTrainerItemReward(tempStatToTrainerItem[randStat]); }); } } @@ -1388,66 +1045,21 @@ class TempStatStageBoosterModifierTypeGenerator extends ModifierTypeGenerator { * the current list of {@linkcode items}. * @extends ModifierTypeGenerator */ -class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { +class SpeciesStatBoosterRewardGenerator extends ModifierTypeGenerator { /** Object comprised of the currently available species-based stat boosting held items */ - public static readonly items = { - LIGHT_BALL: { - stats: [Stat.ATK, Stat.SPATK], - multiplier: 2, - species: [SpeciesId.PIKACHU], - rare: true, - }, - THICK_CLUB: { - stats: [Stat.ATK], - multiplier: 2, - species: [SpeciesId.CUBONE, SpeciesId.MAROWAK, SpeciesId.ALOLA_MAROWAK], - rare: true, - }, - METAL_POWDER: { - stats: [Stat.DEF], - multiplier: 2, - species: [SpeciesId.DITTO], - rare: true, - }, - QUICK_POWDER: { - stats: [Stat.SPD], - multiplier: 2, - species: [SpeciesId.DITTO], - rare: true, - }, - DEEP_SEA_SCALE: { - stats: [Stat.SPDEF], - multiplier: 2, - species: [SpeciesId.CLAMPERL], - rare: false, - }, - DEEP_SEA_TOOTH: { - stats: [Stat.SPATK], - multiplier: 2, - species: [SpeciesId.CLAMPERL], - rare: false, - }, - }; constructor(rare: boolean) { super((party: Pokemon[], pregenArgs?: any[]) => { - const items = SpeciesStatBoosterModifierTypeGenerator.items; - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in items) { - return new SpeciesStatBoosterModifierType(pregenArgs[0] as SpeciesStatBoosterItem); + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in SPECIES_STAT_BOOSTER_ITEMS) { + return new HeldItemReward(pregenArgs[0] as HeldItemId); } // Get a pool of items based on the rarity. - const keys: (keyof SpeciesStatBoosterItem)[] = []; - const values: (typeof items)[keyof typeof items][] = []; - const weights: number[] = []; - for (const [key, val] of Object.entries(SpeciesStatBoosterModifierTypeGenerator.items)) { - if (val.rare !== rare) { - continue; - } - values.push(val); - keys.push(key as keyof SpeciesStatBoosterItem); - weights.push(0); - } + const tierItems = rare + ? [HeldItemId.LIGHT_BALL, HeldItemId.THICK_CLUB, HeldItemId.METAL_POWDER, HeldItemId.QUICK_POWDER] + : [HeldItemId.DEEP_SEA_SCALE, HeldItemId.DEEP_SEA_TOOTH]; + + const weights = new Array(tierItems.length).fill(0); for (const p of party) { const speciesId = p.getSpeciesForm(true).speciesId; @@ -1455,18 +1067,11 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { // TODO: Use commented boolean when Fling is implemented const hasFling = false; /* p.getMoveset(true).some(m => m.moveId === MoveId.FLING) */ - for (const i in values) { - const checkedSpecies = values[i].species; - const checkedStats = values[i].stats; + for (const i in tierItems) { + const checkedSpecies = (allHeldItems[tierItems[i]] as SpeciesStatBoostHeldItem).species; // If party member already has the item being weighted currently, skip to the next item - const hasItem = p - .getHeldItems() - .some( - m => - m instanceof SpeciesStatBoosterModifier && - (m as SpeciesStatBoosterModifier).contains(checkedSpecies[0], checkedStats[0]), - ); + const hasItem = p.heldItemManager.hasItem(tierItems[i]); if (!hasItem) { if (checkedSpecies.includes(speciesId) || (!!fusionSpeciesId && checkedSpecies.includes(fusionSpeciesId))) { @@ -1480,6 +1085,7 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { } } + // TODO: Replace this with a helper function let totalWeight = 0; for (const weight of weights) { totalWeight += weight; @@ -1493,7 +1099,7 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { if (weights[i] !== 0) { const curWeight = weight + weights[i]; if (randInt <= weight + weights[i]) { - return new SpeciesStatBoosterModifierType(keys[i] as SpeciesStatBoosterItem); + return new HeldItemReward(tierItems[i]); } weight = curWeight; } @@ -1506,7 +1112,7 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { } class TmModifierTypeGenerator extends ModifierTypeGenerator { - constructor(tier: ModifierTier) { + constructor(tier: RewardTier) { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in MoveId) { return new TmModifierType(pregenArgs[0] as MoveId); @@ -1583,11 +1189,11 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { } } -export class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { +export class FormChangeItemRewardGenerator extends ModifierTypeGenerator { constructor(isRareFormChangeItem: boolean) { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in FormChangeItem) { - return new FormChangeItemModifierType(pregenArgs[0] as FormChangeItem); + return new FormChangeItemReward(pregenArgs[0] as FormChangeItem); } const formChangeItemPool = [ @@ -1601,26 +1207,17 @@ export class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { fc => ((fc.formKey.indexOf(SpeciesFormKey.MEGA) === -1 && fc.formKey.indexOf(SpeciesFormKey.PRIMAL) === -1) || - globalScene.getModifiers(MegaEvolutionAccessModifier).length) && + globalScene.trainerItems.hasItem(TrainerItemId.MEGA_BRACELET)) && ((fc.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) === -1 && fc.formKey.indexOf(SpeciesFormKey.ETERNAMAX) === -1) || - globalScene.getModifiers(GigantamaxAccessModifier).length) && + globalScene.trainerItems.hasItem(TrainerItemId.DYNAMAX_BAND)) && (!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?.active && - !globalScene.findModifier( - m => - m instanceof PokemonFormChangeItemModifier && - m.pokemonId === p.id && - m.formChangeItem === t.item, - ), - ); + .filter(t => t?.active && !p.heldItemManager.hasFormChangeItem(t.item)); if (p.species.speciesId === SpeciesId.NECROZMA) { // technically we could use a simplified version and check for formChanges.length > 3, but in case any code changes later, this might break... @@ -1663,85 +1260,7 @@ export class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { return null; } - return new FormChangeItemModifierType(formChangeItemPool[randSeedInt(formChangeItemPool.length)]); - }); - } -} - -export class ContactHeldItemTransferChanceModifierType extends PokemonHeldItemModifierType { - private chancePercent: number; - - constructor(localeKey: string, iconImage: string, chancePercent: number, group?: string, soundName?: string) { - super( - localeKey, - iconImage, - (type, args) => new ContactHeldItemTransferChanceModifier(type, (args[0] as Pokemon).id, chancePercent), - group, - soundName, - ); - - this.chancePercent = chancePercent; - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.ContactHeldItemTransferChanceModifierType.description", { - chancePercent: this.chancePercent, - }); - } -} - -export class TurnHeldItemTransferModifierType extends PokemonHeldItemModifierType { - constructor(localeKey: string, iconImage: string, group?: string, soundName?: string) { - super( - localeKey, - iconImage, - (type, args) => new TurnHeldItemTransferModifier(type, (args[0] as Pokemon).id), - group, - soundName, - ); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.TurnHeldItemTransferModifierType.description"); - } -} - -export class EnemyAttackStatusEffectChanceModifierType extends ModifierType { - private chancePercent: number; - private effect: StatusEffect; - - constructor(localeKey: string, iconImage: string, chancePercent: number, effect: StatusEffect, stackCount?: number) { - super( - localeKey, - iconImage, - (type, _args) => new EnemyAttackStatusEffectChanceModifier(type, effect, chancePercent, stackCount), - "enemy_status_chance", - ); - - this.chancePercent = chancePercent; - this.effect = effect; - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.EnemyAttackStatusEffectChanceModifierType.description", { - chancePercent: this.chancePercent, - statusEffect: getStatusEffectDescriptor(this.effect), - }); - } -} - -export class EnemyEndureChanceModifierType extends ModifierType { - private chancePercent: number; - - constructor(localeKey: string, iconImage: string, chancePercent: number) { - super(localeKey, iconImage, (type, _args) => new EnemyEndureChanceModifier(type, chancePercent), "enemy_endure"); - - this.chancePercent = chancePercent; - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.EnemyEndureChanceModifierType.description", { - chancePercent: this.chancePercent, + return new FormChangeItemReward(formChangeItemPool[randSeedInt(formChangeItemPool.length)]); }); } } @@ -1762,7 +1281,7 @@ export class WeightedModifierType { this.maxWeight = maxWeight || (!(weight instanceof Function) ? weight : 0); } - setTier(tier: ModifierTier) { + setTier(tier: RewardTier) { this.modifierType.setTier(tier); } } @@ -1778,7 +1297,7 @@ export type GeneratorModifierOverride = { } & ( | { name: keyof Pick; - type?: SpeciesStatBoosterItem; + type?: SpeciesStatBoosterItemId; } | { name: keyof Pick; @@ -1831,37 +1350,17 @@ const modifierTypeInitObj = Object.freeze({ EVOLUTION_ITEM: () => new EvolutionItemModifierTypeGenerator(false), RARE_EVOLUTION_ITEM: () => new EvolutionItemModifierTypeGenerator(true), - FORM_CHANGE_ITEM: () => new FormChangeItemModifierTypeGenerator(false), - RARE_FORM_CHANGE_ITEM: () => new FormChangeItemModifierTypeGenerator(true), - EVOLUTION_TRACKER_GIMMIGHOUL: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.EVOLUTION_TRACKER_GIMMIGHOUL", - "relic_gold", - (type, args) => - new EvoTrackerModifier(type, (args[0] as Pokemon).id, SpeciesId.GIMMIGHOUL, 10, (args[1] as number) ?? 1), - ), + FORM_CHANGE_ITEM: () => new FormChangeItemRewardGenerator(false), + RARE_FORM_CHANGE_ITEM: () => new FormChangeItemRewardGenerator(true), - 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), - ), + EVOLUTION_TRACKER_GIMMIGHOUL: () => new HeldItemReward(HeldItemId.GIMMIGHOUL_EVO_TRACKER), - MAP: () => new ModifierType("modifierType:ModifierType.MAP", "map", (type, _args) => new MapModifier(type)), + MEGA_BRACELET: () => new TrainerItemReward(TrainerItemId.MEGA_BRACELET), + DYNAMAX_BAND: () => new TrainerItemReward(TrainerItemId.DYNAMAX_BAND), + TERA_ORB: () => new TrainerItemReward(TrainerItemId.TERA_ORB), + + MAP: () => new TrainerItemReward(TrainerItemId.MAP), POTION: () => new PokemonHpRestoreModifierType("modifierType:ModifierType.POTION", "potion", 20, 10), SUPER_POTION: () => @@ -1879,18 +1378,9 @@ const modifierTypeInitObj = Object.freeze({ SACRED_ASH: () => new AllPokemonFullReviveModifierType("modifierType:ModifierType.SACRED_ASH", "sacred_ash"), - 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), - ), + REVIVER_SEED: () => new HeldItemReward(HeldItemId.REVIVER_SEED), + + WHITE_HERB: () => new HeldItemReward(HeldItemId.WHITE_HERB), ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.ETHER", "ether", 10), MAX_ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.MAX_ETHER", "max_ether", -1), @@ -1905,28 +1395,20 @@ const modifierTypeInitObj = Object.freeze({ SUPER_REPEL: () => new DoubleBattleChanceBoosterModifierType('Super Repel', 10), MAX_REPEL: () => new DoubleBattleChanceBoosterModifierType('Max Repel', 25),*/ - LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.LURE", "lure", 10), - SUPER_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.SUPER_LURE", "super_lure", 15), - MAX_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.MAX_LURE", "max_lure", 30), + LURE: () => new LapsingTrainerItemReward(TrainerItemId.LURE), + SUPER_LURE: () => new LapsingTrainerItemReward(TrainerItemId.SUPER_LURE), + MAX_LURE: () => new LapsingTrainerItemReward(TrainerItemId.MAX_LURE), - SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterModifierTypeGenerator(false), - RARE_SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterModifierTypeGenerator(true), + SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterRewardGenerator(false), + RARE_SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterRewardGenerator(true), - TEMP_STAT_STAGE_BOOSTER: () => new TempStatStageBoosterModifierTypeGenerator(), + TEMP_STAT_STAGE_BOOSTER: () => new TempStatStageBoosterRewardGenerator(), - DIRE_HIT: () => - new (class extends ModifierType { - getDescription(): string { - return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { - stat: i18next.t("modifierType:ModifierType.DIRE_HIT.extra.raises"), - amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.stage"), - }); - } - })("modifierType:ModifierType.DIRE_HIT", "dire_hit", (type, _args) => new TempCritBoosterModifier(type, 5)), + DIRE_HIT: () => new LapsingTrainerItemReward(TrainerItemId.DIRE_HIT), - BASE_STAT_BOOSTER: () => new BaseStatBoosterModifierTypeGenerator(), + BASE_STAT_BOOSTER: () => new BaseStatBoosterRewardGenerator(), - ATTACK_TYPE_BOOSTER: () => new AttackTypeBoosterModifierTypeGenerator(), + ATTACK_TYPE_BOOSTER: () => new AttackTypeBoosterRewardGenerator(), MINT: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { @@ -1936,19 +1418,14 @@ const modifierTypeInitObj = Object.freeze({ return new PokemonNatureChangeModifierType(randSeedInt(getEnumValues(Nature).length) as Nature); }), - MYSTICAL_ROCK: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.MYSTICAL_ROCK", - "mystical_rock", - (type, args) => new FieldEffectModifier(type, (args[0] as Pokemon).id), - ), + MYSTICAL_ROCK: () => new HeldItemReward(HeldItemId.MYSTICAL_ROCK), TERA_SHARD: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { return new TerastallizeModifierType(pregenArgs[0] as PokemonType); } - if (!globalScene.getModifiers(TerastallizeAccessModifier).length) { + if (!globalScene.trainerItems.hasItem(TrainerItemId.TERA_ORB)) { return null; } const teraTypes: PokemonType[] = []; @@ -1970,89 +1447,33 @@ const modifierTypeInitObj = Object.freeze({ return new TerastallizeModifierType(shardType); }), - BERRY: () => - new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { - return new BerryModifierType(pregenArgs[0] as BerryType); - } - const berryTypes = getEnumValues(BerryType); - let randBerryType: BerryType; - const rand = randSeedInt(12); - if (rand < 2) { - randBerryType = BerryType.SITRUS; - } else if (rand < 4) { - randBerryType = BerryType.LUM; - } else if (rand < 6) { - randBerryType = BerryType.LEPPA; - } else { - randBerryType = berryTypes[randSeedInt(berryTypes.length - 3) + 2]; - } - return new BerryModifierType(randBerryType); - }), + BERRY: () => new BerryRewardGenerator(), - TM_COMMON: () => new TmModifierTypeGenerator(ModifierTier.COMMON), - TM_GREAT: () => new TmModifierTypeGenerator(ModifierTier.GREAT), - TM_ULTRA: () => new TmModifierTypeGenerator(ModifierTier.ULTRA), + TM_COMMON: () => new TmModifierTypeGenerator(RewardTier.COMMON), + TM_GREAT: () => new TmModifierTypeGenerator(RewardTier.GREAT), + TM_ULTRA: () => new TmModifierTypeGenerator(RewardTier.ULTRA), MEMORY_MUSHROOM: () => new RememberMoveModifierType("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom"), - 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), - ), + EXP_SHARE: () => new TrainerItemReward(TrainerItemId.EXP_SHARE), + EXP_BALANCE: () => new TrainerItemReward(TrainerItemId.EXP_BALANCE), - OVAL_CHARM: () => - new ModifierType( - "modifierType:ModifierType.OVAL_CHARM", - "oval_charm", - (type, _args) => new MultipleParticipantExpBonusModifier(type), - ), + OVAL_CHARM: () => new TrainerItemReward(TrainerItemId.OVAL_CHARM), - 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), - GOLDEN_EXP_CHARM: () => - new ExpBoosterModifierType("modifierType:ModifierType.GOLDEN_EXP_CHARM", "golden_exp_charm", 100), + EXP_CHARM: () => new TrainerItemReward(TrainerItemId.EXP_CHARM), + SUPER_EXP_CHARM: () => new TrainerItemReward(TrainerItemId.SUPER_EXP_CHARM), - LUCKY_EGG: () => new PokemonExpBoosterModifierType("modifierType:ModifierType.LUCKY_EGG", "lucky_egg", 40), - GOLDEN_EGG: () => new PokemonExpBoosterModifierType("modifierType:ModifierType.GOLDEN_EGG", "golden_egg", 100), + LUCKY_EGG: () => new HeldItemReward(HeldItemId.LUCKY_EGG), + GOLDEN_EGG: () => new HeldItemReward(HeldItemId.GOLDEN_EGG), - SOOTHE_BELL: () => new PokemonFriendshipBoosterModifierType("modifierType:ModifierType.SOOTHE_BELL", "soothe_bell"), + SOOTHE_BELL: () => new HeldItemReward(HeldItemId.SOOTHE_BELL), - 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, [ - SpeciesId.FARFETCHD, - SpeciesId.GALAR_FARFETCHD, - SpeciesId.SIRFETCHD, - ]), - ), + SCOPE_LENS: () => new HeldItemReward(HeldItemId.SCOPE_LENS), + LEEK: () => new HeldItemReward(HeldItemId.LEEK), - EVIOLITE: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.EVIOLITE", - "eviolite", - (type, args) => new EvolutionStatBoosterModifier(type, (args[0] as Pokemon).id, [Stat.DEF, Stat.SPDEF], 1.5), - ), + EVIOLITE: () => new HeldItemReward(HeldItemId.EVIOLITE), - SOUL_DEW: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.SOUL_DEW", - "soul_dew", - (type, args) => new PokemonNatureWeightModifier(type, (args[0] as Pokemon).id), - ), + SOUL_DEW: () => new HeldItemReward(HeldItemId.SOUL_DEW), NUGGET: () => new MoneyRewardModifierType( @@ -2076,258 +1497,74 @@ const modifierTypeInitObj = Object.freeze({ "modifierType:ModifierType.MoneyRewardModifierType.extra.large", ), - 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), - ), + AMULET_COIN: () => new TrainerItemReward(TrainerItemId.AMULET_COIN), + GOLDEN_PUNCH: () => new HeldItemReward(HeldItemId.GOLDEN_PUNCH), - LOCK_CAPSULE: () => - new ModifierType( - "modifierType:ModifierType.LOCK_CAPSULE", - "lock_capsule", - (type, _args) => new LockModifierTiersModifier(type), - ), + LOCK_CAPSULE: () => new TrainerItemReward(TrainerItemId.LOCK_CAPSULE), - GRIP_CLAW: () => - new ContactHeldItemTransferChanceModifierType("modifierType:ModifierType.GRIP_CLAW", "grip_claw", 10), - WIDE_LENS: () => new PokemonMoveAccuracyBoosterModifierType("modifierType:ModifierType.WIDE_LENS", "wide_lens", 5), + GRIP_CLAW: () => new HeldItemReward(HeldItemId.GRIP_CLAW), + WIDE_LENS: () => new HeldItemReward(HeldItemId.WIDE_LENS), - MULTI_LENS: () => new PokemonMultiHitModifierType("modifierType:ModifierType.MULTI_LENS", "zoom_lens"), + MULTI_LENS: () => new HeldItemReward(HeldItemId.MULTI_LENS), - 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), - ), + HEALING_CHARM: () => new TrainerItemReward(TrainerItemId.HEALING_CHARM), + CANDY_JAR: () => new TrainerItemReward(TrainerItemId.CANDY_JAR), - BERRY_POUCH: () => - new ModifierType( - "modifierType:ModifierType.BERRY_POUCH", - "berry_pouch", - (type, _args) => new PreserveBerryModifier(type), - ), + BERRY_POUCH: () => new TrainerItemReward(TrainerItemId.BERRY_POUCH), - FOCUS_BAND: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.FOCUS_BAND", - "focus_band", - (type, args) => new SurviveDamageModifier(type, (args[0] as Pokemon).id), - ), + FOCUS_BAND: () => new HeldItemReward(HeldItemId.FOCUS_BAND), - QUICK_CLAW: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.QUICK_CLAW", - "quick_claw", - (type, args) => new BypassSpeedChanceModifier(type, (args[0] as Pokemon).id), - ), + QUICK_CLAW: () => new HeldItemReward(HeldItemId.QUICK_CLAW), - KINGS_ROCK: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.KINGS_ROCK", - "kings_rock", - (type, args) => new FlinchChanceModifier(type, (args[0] as Pokemon).id), - ), + KINGS_ROCK: () => new HeldItemReward(HeldItemId.KINGS_ROCK), - 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), - ), + LEFTOVERS: () => new HeldItemReward(HeldItemId.LEFTOVERS), - 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), - ), + SHELL_BELL: () => new HeldItemReward(HeldItemId.SHELL_BELL), - BATON: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.BATON", - "baton", - (type, args) => new SwitchEffectTransferModifier(type, (args[0] as Pokemon).id), - ), + TOXIC_ORB: () => new HeldItemReward(HeldItemId.TOXIC_ORB), - 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), - ), - CATCHING_CHARM: () => - new ModifierType( - "modifierType:ModifierType.CATCHING_CHARM", - "catching_charm", - (type, _args) => new CriticalCatchChanceBoosterModifier(type), - ), + FLAME_ORB: () => new HeldItemReward(HeldItemId.FLAME_ORB), - IV_SCANNER: () => - new ModifierType("modifierType:ModifierType.IV_SCANNER", "scanner", (type, _args) => new IvScannerModifier(type)), + BATON: () => new HeldItemReward(HeldItemId.BATON), + + SHINY_CHARM: () => new TrainerItemReward(TrainerItemId.SHINY_CHARM), + ABILITY_CHARM: () => new TrainerItemReward(TrainerItemId.ABILITY_CHARM), + CATCHING_CHARM: () => new TrainerItemReward(TrainerItemId.CATCHING_CHARM), + + IV_SCANNER: () => new TrainerItemReward(TrainerItemId.IV_SCANNER), DNA_SPLICERS: () => new FusePokemonModifierType("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers"), - MINI_BLACK_HOLE: () => - new TurnHeldItemTransferModifierType("modifierType:ModifierType.MINI_BLACK_HOLE", "mini_black_hole"), + MINI_BLACK_HOLE: () => new HeldItemReward(HeldItemId.MINI_BLACK_HOLE), VOUCHER: () => new AddVoucherModifierType(VoucherType.REGULAR, 1), 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 ExtraModifierModifier(type), - undefined, - "se/pb_bounce_1", - ), - SILVER_POKEBALL: () => - new ModifierType( - "modifierType:ModifierType.SILVER_POKEBALL", - "pb_silver", - (type, _args) => new TempExtraModifierModifier(type, 100), - undefined, - "se/pb_bounce_1", - ), + GOLDEN_POKEBALL: () => new TrainerItemReward(TrainerItemId.GOLDEN_POKEBALL), - 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_DAMAGE_BOOSTER: () => new TrainerItemReward(TrainerItemId.ENEMY_DAMAGE_BOOSTER), + ENEMY_DAMAGE_REDUCTION: () => new TrainerItemReward(TrainerItemId.ENEMY_DAMAGE_REDUCTION), //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 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 EnemyFusionChanceModifier(type, 1), - ), + ENEMY_HEAL: () => new TrainerItemReward(TrainerItemId.ENEMY_HEAL), + ENEMY_ATTACK_POISON_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ATTACK_POISON_CHANCE), + ENEMY_ATTACK_PARALYZE_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ATTACK_PARALYZE_CHANCE), + ENEMY_ATTACK_BURN_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ATTACK_BURN_CHANCE), + ENEMY_STATUS_EFFECT_HEAL_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE), + ENEMY_ENDURE_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ENDURE_CHANCE), + ENEMY_FUSED_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_FUSED_CHANCE), - MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () => - new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { - return new PokemonBaseStatTotalModifierType(pregenArgs[0] as 10 | -15); - } - return new PokemonBaseStatTotalModifierType(10); - }), - MYSTERY_ENCOUNTER_OLD_GATEAU: () => - new PokemonHeldItemModifierType( - "modifierType:ModifierType.MYSTERY_ENCOUNTER_OLD_GATEAU", - "old_gateau", - (type, args) => new PokemonBaseStatFlatModifier(type, (args[0] as Pokemon).id), - ), - 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 HealShopCostModifier(type, pregenArgs[0] as number), - ); - } - 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 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), - ), + MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD: () => new HeldItemReward(HeldItemId.SHUCKLE_JUICE_GOOD), + MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD: () => new HeldItemReward(HeldItemId.SHUCKLE_JUICE_BAD), + + MYSTERY_ENCOUNTER_OLD_GATEAU: () => new HeldItemReward(HeldItemId.OLD_GATEAU), + + MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new TrainerItemReward(TrainerItemId.BLACK_SLUDGE), + + MYSTERY_ENCOUNTER_MACHO_BRACE: () => new HeldItemReward(HeldItemId.MACHO_BRACE), + + MYSTERY_ENCOUNTER_GOLDEN_BUG_NET: () => new TrainerItemReward(TrainerItemId.GOLDEN_BUG_NET), }); /** @@ -2342,19 +1579,6 @@ export interface ModifierPool { let modifierPoolThresholds = {}; let ignoredPoolIndexes = {}; -let dailyStarterModifierPoolThresholds = {}; -// biome-ignore lint/correctness/noUnusedVariables: TODO explain why this is marked as OK -let ignoredDailyStarterPoolIndexes = {}; - -let enemyModifierPoolThresholds = {}; -// biome-ignore lint/correctness/noUnusedVariables: TODO explain why this is marked as OK -let enemyIgnoredPoolIndexes = {}; - -let enemyBuffModifierPoolThresholds = {}; -// biome-ignore lint/correctness/noUnusedVariables: TODO explain why this is marked as OK -let enemyBuffIgnoredPoolIndexes = {}; - -const tierWeights = [768 / 1024, 195 / 1024, 48 / 1024, 12 / 1024, 1 / 1024]; /** * Allows a unit test to check if an item exists in the Modifier Pool. Checks the pool directly, rather than attempting to reroll for the item. */ @@ -2367,30 +1591,27 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod }); const ignoredIndexes = {}; - const modifierTableData = {}; const thresholds = Object.fromEntries( new Map( Object.keys(pool).map(t => { ignoredIndexes[t] = []; const thresholds = new Map(); const tierModifierIds: string[] = []; - let tierMaxWeight = 0; let i = 0; pool[t].reduce((total: number, modifierType: WeightedModifierType) => { const weightedModifierType = modifierType as WeightedModifierType; - const existingModifiers = globalScene.findModifiers( - m => m.type.id === weightedModifierType.modifierType.id, - poolType === ModifierPoolType.PLAYER, - ); const itemModifierType = weightedModifierType.modifierType instanceof ModifierTypeGenerator ? weightedModifierType.modifierType.generateType(party) : weightedModifierType.modifierType; + const trainerItemfullStack = + itemModifierType instanceof TrainerItemReward + ? globalScene.trainerItems.isMaxStack(itemModifierType.itemId) + : false; const weight = - !existingModifiers.length || - itemModifierType instanceof PokemonHeldItemModifierType || - itemModifierType instanceof FormChangeItemModifierType || - existingModifiers.find(m => m.stackCount < m.getMaxStackCount(true)) + !trainerItemfullStack || + itemModifierType instanceof HeldItemReward || + itemModifierType instanceof FormChangeItemReward ? weightedModifierType.weight instanceof Function ? // biome-ignore lint/complexity/noBannedTypes: TODO: refactor to not use Function type (weightedModifierType.weight as Function)(party, rerollCount) @@ -2399,14 +1620,6 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod if (weightedModifierType.maxWeight) { const modifierId = weightedModifierType.modifierType.id; tierModifierIds.push(modifierId); - const outputWeight = useMaxWeightForOutput ? weightedModifierType.maxWeight : weight; - modifierTableData[modifierId] = { - weight: outputWeight, - tier: Number.parseInt(t), - tierPercent: 0, - totalPercent: 0, - }; - tierMaxWeight += outputWeight; } if (weight) { total += weight; @@ -2420,46 +1633,20 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod thresholds.set(total, i++); return total; }, 0); - for (const id of tierModifierIds) { - modifierTableData[id].tierPercent = Math.floor((modifierTableData[id].weight / tierMaxWeight) * 10000) / 100; - } return [t, Object.fromEntries(thresholds)]; }), ), ); - for (const id of Object.keys(modifierTableData)) { - modifierTableData[id].totalPercent = - Math.floor(modifierTableData[id].tierPercent * tierWeights[modifierTableData[id].tier] * 100) / 100; - modifierTableData[id].tier = ModifierTier[modifierTableData[id].tier]; - } - if (outputModifierData) { - console.table(modifierTableData); - } switch (poolType) { case ModifierPoolType.PLAYER: modifierPoolThresholds = thresholds; ignoredPoolIndexes = ignoredIndexes; break; - case ModifierPoolType.WILD: - case ModifierPoolType.TRAINER: - enemyModifierPoolThresholds = thresholds; - enemyIgnoredPoolIndexes = ignoredIndexes; - break; - case ModifierPoolType.ENEMY_BUFF: - enemyBuffModifierPoolThresholds = thresholds; - enemyBuffIgnoredPoolIndexes = ignoredIndexes; - break; - case ModifierPoolType.DAILY_STARTER: - dailyStarterModifierPoolThresholds = thresholds; - ignoredDailyStarterPoolIndexes = ignoredIndexes; - break; } } export interface CustomModifierSettings { - /** If specified, will override the next X items to be the specified tier. These can upgrade with luck. */ - guaranteedModifierTiers?: ModifierTier[]; - /** If specified, will override the first X items to be specific modifier options (these should be pre-genned). */ + guaranteedModifierTiers?: RewardTier[]; guaranteedModifierTypeOptions?: ModifierTypeOption[]; /** If specified, will override the next X items to be auto-generated from specific modifier functions (these don't have to be pre-genned). */ guaranteedModifierTypeFuncs?: ModifierTypeFunc[]; @@ -2499,7 +1686,7 @@ export function getModifierTypeFuncById(id: string): ModifierTypeFunc { export function getPlayerModifierTypeOptions( count: number, party: PlayerPokemon[], - modifierTiers?: ModifierTier[], + modifierTiers?: RewardTier[], customModifierSettings?: CustomModifierSettings, ): ModifierTypeOption[] { const options: ModifierTypeOption[] = []; @@ -2574,7 +1761,7 @@ function getModifierTypeOptionWithRetry( existingOptions: ModifierTypeOption[], retryCount: number, party: PlayerPokemon[], - tier?: ModifierTier, + tier?: RewardTier, allowLuckUpgrades?: boolean, ): ModifierTypeOption { allowLuckUpgrades = allowLuckUpgrades ?? true; @@ -2585,6 +1772,11 @@ function getModifierTypeOptionWithRetry( ++r < retryCount && existingOptions.filter(o => o.type.name === candidate?.type.name || o.type.group === candidate?.type.group).length ) { + console.log("Retry count:", r); + console.log(candidate?.type.group); + console.log(candidate?.type.name); + console.log(existingOptions.filter(o => o.type.name === candidate?.type.name).length); + console.log(existingOptions.filter(o => o.type.group === candidate?.type.group).length); candidate = getNewModifierTypeOption( party, ModifierPoolType.PLAYER, @@ -2656,90 +1848,6 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: number, baseC return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat(); } -export function getEnemyBuffModifierForWave( - tier: ModifierTier, - enemyModifiers: PersistentModifier[], -): EnemyPersistentModifier { - let tierStackCount: number; - switch (tier) { - case ModifierTier.ULTRA: - tierStackCount = 5; - break; - case ModifierTier.GREAT: - tierStackCount = 3; - break; - default: - tierStackCount = 1; - break; - } - - const retryCount = 50; - let candidate = getNewModifierTypeOption([], ModifierPoolType.ENEMY_BUFF, tier); - let r = 0; - let matchingModifier: PersistentModifier | undefined; - while ( - ++r < retryCount && - (matchingModifier = enemyModifiers.find(m => m.type.id === candidate?.type?.id)) && - matchingModifier.getMaxStackCount() < matchingModifier.stackCount + (r < 10 ? tierStackCount : 1) - ) { - candidate = getNewModifierTypeOption([], ModifierPoolType.ENEMY_BUFF, tier); - } - - const modifier = candidate?.type?.newModifier() as EnemyPersistentModifier; - modifier.stackCount = tierStackCount; - - return modifier; -} - -export function getEnemyModifierTypesForWave( - waveIndex: number, - count: number, - party: EnemyPokemon[], - poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, - upgradeChance = 0, -): 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(modifierTypeInitObj.MINI_BLACK_HOLE) as PokemonHeldItemModifierType); - } - return ret; -} - -export function getDailyRunStarterModifiers(party: PlayerPokemon[]): PokemonHeldItemModifier[] { - const ret: PokemonHeldItemModifier[] = []; - for (const p of party) { - for (let m = 0; m < 3; m++) { - const tierValue = randSeedInt(64); - - let tier: ModifierTier; - if (tierValue > 25) { - tier = ModifierTier.COMMON; - } else if (tierValue > 12) { - tier = ModifierTier.GREAT; - } else if (tierValue > 4) { - tier = ModifierTier.ULTRA; - } else if (tierValue) { - tier = ModifierTier.ROGUE; - } else { - tier = ModifierTier.MASTER; - } - - const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier)?.type?.newModifier( - p, - ) as PokemonHeldItemModifier; - ret.push(modifier); - } - } - - return ret; -} - /** * Generates a ModifierType from the specified pool * @param party party of the trainer using the item @@ -2752,84 +1860,24 @@ export function getDailyRunStarterModifiers(party: PlayerPokemon[]): PokemonHeld function getNewModifierTypeOption( party: Pokemon[], poolType: ModifierPoolType, - tier?: ModifierTier, + baseTier?: RewardTier, upgradeCount?: number, retryCount = 0, allowLuckUpgrades = true, ): ModifierTypeOption | null { const player = !poolType; const pool = getModifierPoolForType(poolType); - let thresholds: object; - switch (poolType) { - case ModifierPoolType.PLAYER: - thresholds = modifierPoolThresholds; - break; - case ModifierPoolType.WILD: - thresholds = enemyModifierPoolThresholds; - break; - case ModifierPoolType.TRAINER: - thresholds = enemyModifierPoolThresholds; - break; - case ModifierPoolType.ENEMY_BUFF: - thresholds = enemyBuffModifierPoolThresholds; - break; - case ModifierPoolType.DAILY_STARTER: - thresholds = dailyStarterModifierPoolThresholds; - break; + const thresholds = getPoolThresholds(poolType); + + let tier = 0; + if (isNullOrUndefined(baseTier)) { + baseTier = randomBaseTier(); } - if (tier === undefined) { - const tierValue = randSeedInt(1024); - if (!upgradeCount) { - upgradeCount = 0; - } - if (player && tierValue && allowLuckUpgrades) { - const partyLuckValue = getPartyLuckValue(party); - const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4)); - let upgraded = false; - do { - upgraded = randSeedInt(upgradeOdds) < 4; - if (upgraded) { - upgradeCount++; - } - } while (upgraded); - } - - if (tierValue > 255) { - tier = ModifierTier.COMMON; - } else if (tierValue > 60) { - tier = ModifierTier.GREAT; - } else if (tierValue > 12) { - tier = ModifierTier.ULTRA; - } else if (tierValue) { - tier = ModifierTier.ROGUE; - } else { - tier = ModifierTier.MASTER; - } - - tier += upgradeCount; - while (tier && (!pool.hasOwnProperty(tier) || !pool[tier].length)) { - tier--; - if (upgradeCount) { - upgradeCount--; - } - } - } else if (upgradeCount === undefined && player) { - upgradeCount = 0; - if (tier < ModifierTier.MASTER && allowLuckUpgrades) { - const partyLuckValue = getPartyLuckValue(party); - const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4)); - while (pool.hasOwnProperty(tier + upgradeCount + 1) && pool[tier + upgradeCount + 1].length) { - if (randSeedInt(upgradeOdds) < 4) { - upgradeCount++; - } else { - break; - } - } - tier += upgradeCount; - } - } else if (retryCount >= 100 && tier) { - retryCount = 0; - tier--; + if (isNullOrUndefined(upgradeCount)) { + upgradeCount = allowLuckUpgrades ? getUpgradeCount(party, player, baseTier) : 0; + tier = baseTier + upgradeCount; + } else { + tier = baseTier; } const tierThresholds = Object.keys(thresholds[tier]); @@ -2856,7 +1904,7 @@ function getNewModifierTypeOption( modifierType = (modifierType as ModifierTypeGenerator).generateType(party); if (modifierType === null) { if (player) { - console.log(ModifierTier[tier], upgradeCount); + console.log(RewardTier[tier], upgradeCount); } return getNewModifierTypeOption(party, poolType, tier, upgradeCount, ++retryCount); } @@ -2867,9 +1915,61 @@ function getNewModifierTypeOption( return new ModifierTypeOption(modifierType as ModifierType, upgradeCount!); // TODO: is this bang correct? } -export function getDefaultModifierTypeForTier(tier: ModifierTier): ModifierType { +function getPoolThresholds(poolType: ModifierPoolType) { + let thresholds: object; + switch (poolType) { + case ModifierPoolType.PLAYER: + thresholds = modifierPoolThresholds; + break; + } + return thresholds; +} + +function randomBaseTier(): RewardTier { + const tierValue = randSeedInt(1024); + + if (tierValue > 255) { + return RewardTier.COMMON; + } + if (tierValue > 60) { + return RewardTier.GREAT; + } + if (tierValue > 12) { + return RewardTier.ULTRA; + } + if (tierValue) { + return RewardTier.ROGUE; + } + return RewardTier.MASTER; +} + +function getUpgradeCount( + party: Pokemon[], + player: boolean, + baseTier: RewardTier, + allowLuckUpgrades = true, +): RewardTier { + const pool = getModifierPoolForType(ModifierPoolType.PLAYER); + let upgradeCount = 0; + if (player) { + if (baseTier < RewardTier.MASTER && allowLuckUpgrades) { + const partyLuckValue = getPartyLuckValue(party); + const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4)); + while (pool.hasOwnProperty(baseTier + upgradeCount + 1) && pool[baseTier + upgradeCount + 1].length) { + if (randSeedInt(upgradeOdds) < 4) { + upgradeCount++; + } else { + break; + } + } + } + } + return upgradeCount; +} + +export function getDefaultModifierTypeForTier(tier: RewardTier): ModifierType { const modifierPool = getModifierPoolForType(ModifierPoolType.PLAYER); - let modifierType: ModifierType | WeightedModifierType = modifierPool[tier || ModifierTier.COMMON][0]; + let modifierType: ModifierType | WeightedModifierType = modifierPool[tier || RewardTier.COMMON][0]; if (modifierType instanceof WeightedModifierType) { modifierType = (modifierType as WeightedModifierType).modifierType; } @@ -2921,19 +2021,19 @@ export function getLuckString(luckValue: number): string { } export function getLuckTextTint(luckValue: number): number { - let modifierTier: ModifierTier; + let modifierTier: RewardTier; if (luckValue > 11) { - modifierTier = ModifierTier.LUXURY; + modifierTier = RewardTier.LUXURY; } else if (luckValue > 9) { - modifierTier = ModifierTier.MASTER; + modifierTier = RewardTier.MASTER; } else if (luckValue > 5) { - modifierTier = ModifierTier.ROGUE; + modifierTier = RewardTier.ROGUE; } else if (luckValue > 2) { - modifierTier = ModifierTier.ULTRA; + modifierTier = RewardTier.ULTRA; } else if (luckValue) { - modifierTier = ModifierTier.GREAT; + modifierTier = RewardTier.GREAT; } else { - modifierTier = ModifierTier.COMMON; + modifierTier = RewardTier.COMMON; } return getModifierTierTextTint(modifierTier); } @@ -2948,7 +2048,6 @@ export function initModifierTypes() { // For now, doing the minimal work until the modifier rework lands. const ModifierTypeConstructorMap = Object.freeze({ ModifierTypeGenerator, - PokemonHeldItemModifierType, }); /** diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 93fb5f5b6f4..716937289b4 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,156 +1,33 @@ import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; -import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; import { getLevelTotalExp } from "#app/data/exp"; -import { allMoves, modifierTypes } from "#app/data/data-lists"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; -import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; -import type { FormChangeItem } from "#enums/form-change-item"; -import { getStatusEffectHealText } from "#app/data/status-effect"; -import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { getPokemonNameWithAffix } from "#app/messages"; +import type { PlayerPokemon } from "#app/field/pokemon"; import Overrides from "#app/overrides"; import { LearnMoveType } from "#enums/learn-move-type"; import type { VoucherType } from "#app/system/voucher"; -import { Command } from "#enums/command"; -import { addTextObject, TextStyle } from "#app/ui/text"; -import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, randSeedFloat, toDmgValue } from "#app/utils/common"; -import { BattlerTagType } from "#enums/battler-tag-type"; -import { BerryType } from "#enums/berry-type"; -import type { MoveId } from "#enums/move-id"; +import { isNullOrUndefined, NumberHolder } from "#app/utils/common"; import type { Nature } from "#enums/nature"; import type { PokeballType } from "#enums/pokeball"; import { SpeciesId } from "#enums/species-id"; -import { type PermanentStat, type TempBattleStat, BATTLE_STATS, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; -import { StatusEffect } from "#enums/status-effect"; import type { PokemonType } from "#enums/pokemon-type"; -import i18next from "i18next"; import type { - DoubleBattleChanceBoosterModifierType, EvolutionItemModifierType, - FormChangeItemModifierType, - ModifierOverride, ModifierType, - PokemonBaseStatTotalModifierType, - PokemonExpBoosterModifierType, - PokemonFriendshipBoosterModifierType, - PokemonMoveAccuracyBoosterModifierType, - PokemonMultiHitModifierType, TerastallizeModifierType, TmModifierType, } from "./modifier-type"; -import { getModifierType } from "#app/utils/modifier-utils"; -import { Color, ShadowColor } from "#enums/color"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; -import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import type { ModifierInstanceMap, ModifierString } from "#app/@types/modifier-types"; +import { assignItemsFromConfiguration } from "#app/items/held-item-pool"; +import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; +import { HeldItemId } from "#enums/held-item-id"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import type { TrainerItemConfiguration } from "#app/items/trainer-item-data-types"; export type ModifierPredicate = (modifier: Modifier) => boolean; -const iconOverflowIndex = 24; - -export const modifierSortFunc = (a: Modifier, b: Modifier): number => { - const itemNameMatch = a.type.name.localeCompare(b.type.name); - const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name); - const aId = a instanceof PokemonHeldItemModifier && a.pokemonId ? a.pokemonId : 4294967295; - const bId = b instanceof PokemonHeldItemModifier && b.pokemonId ? b.pokemonId : 4294967295; - - //First sort by pokemonID - if (aId < bId) { - return 1; - } - if (aId > bId) { - return -1; - } - if (aId === bId) { - //Then sort by item type - if (typeNameMatch === 0) { - return itemNameMatch; - //Finally sort by item name - } - return typeNameMatch; - } - return 0; -}; - -export class ModifierBar extends Phaser.GameObjects.Container { - private player: boolean; - private modifierCache: PersistentModifier[]; - - constructor(enemy?: boolean) { - super(globalScene, 1 + (enemy ? 302 : 0), 2); - - this.player = !enemy; - this.setScale(0.5); - } - - /** - * Method to update content displayed in {@linkcode ModifierBar} - * @param {PersistentModifier[]} modifiers - The list of modifiers to be displayed in the {@linkcode ModifierBar} - * @param {boolean} hideHeldItems - If set to "true", only modifiers not assigned to a Pokémon are displayed - */ - updateModifiers(modifiers: PersistentModifier[], hideHeldItems = false) { - this.removeAll(true); - - const visibleIconModifiers = modifiers.filter(m => m.isIconVisible()); - const nonPokemonSpecificModifiers = visibleIconModifiers - .filter(m => !(m as PokemonHeldItemModifier).pokemonId) - .sort(modifierSortFunc); - const pokemonSpecificModifiers = visibleIconModifiers - .filter(m => (m as PokemonHeldItemModifier).pokemonId) - .sort(modifierSortFunc); - - const sortedVisibleIconModifiers = hideHeldItems - ? nonPokemonSpecificModifiers - : nonPokemonSpecificModifiers.concat(pokemonSpecificModifiers); - - sortedVisibleIconModifiers.forEach((modifier: PersistentModifier, i: number) => { - const icon = modifier.getIcon(); - if (i >= iconOverflowIndex) { - icon.setVisible(false); - } - this.add(icon); - this.setModifierIconPosition(icon, sortedVisibleIconModifiers.length); - icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 24), Phaser.Geom.Rectangle.Contains); - icon.on("pointerover", () => { - globalScene.ui.showTooltip(modifier.type.name, modifier.type.getDescription()); - if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { - this.updateModifierOverflowVisibility(true); - } - }); - icon.on("pointerout", () => { - globalScene.ui.hideTooltip(); - if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { - this.updateModifierOverflowVisibility(false); - } - }); - }); - - for (const icon of this.getAll()) { - this.sendToBack(icon); - } - - this.modifierCache = modifiers; - } - - updateModifierOverflowVisibility(ignoreLimit: boolean) { - const modifierIcons = this.getAll().reverse(); - for (const modifier of modifierIcons.map(m => m as Phaser.GameObjects.Container).slice(iconOverflowIndex)) { - modifier.setVisible(ignoreLimit); - } - } - - 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; - - icon.setPosition(this.player ? x : -x, y); - } -} - export abstract class Modifier { public type: ModifierType; @@ -195,106 +72,6 @@ export abstract class Modifier { abstract apply(...args: unknown[]): boolean; } -export abstract class PersistentModifier extends Modifier { - public stackCount: number; - public virtualStackCount: number; - - /** This field does not exist at runtime and must not be used. - * Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called. - */ - private declare _: never; - - constructor(type: ModifierType, stackCount = 1) { - super(type); - this.stackCount = stackCount; - this.virtualStackCount = 0; - } - - add(modifiers: PersistentModifier[], virtual: boolean): boolean { - for (const modifier of modifiers) { - if (this.match(modifier)) { - return modifier.incrementStack(this.stackCount, virtual); - } - } - - if (virtual) { - this.virtualStackCount += this.stackCount; - this.stackCount = 0; - } - modifiers.push(this); - return true; - } - - abstract clone(): PersistentModifier; - - getArgs(): any[] { - return []; - } - - incrementStack(amount: number, virtual: boolean): boolean { - if (this.getStackCount() + amount <= this.getMaxStackCount()) { - if (!virtual) { - this.stackCount += amount; - } else { - this.virtualStackCount += amount; - } - return true; - } - - return false; - } - - getStackCount(): number { - return this.stackCount + this.virtualStackCount; - } - - abstract getMaxStackCount(forThreshold?: boolean): number; - - getCountUnderMax(): number { - return this.getMaxStackCount() - this.getStackCount(); - } - - isIconVisible(): boolean { - return true; - } - - getIcon(_forSummary?: boolean): Phaser.GameObjects.Container { - const container = globalScene.add.container(0, 0); - - const item = globalScene.add.sprite(0, 12, "items"); - item.setFrame(this.type.iconImage); - item.setOrigin(0, 0.5); - container.add(item); - - const stackText = this.getIconStackText(); - if (stackText) { - container.add(stackText); - } - - const virtualStackText = this.getIconStackText(true); - if (virtualStackText) { - container.add(virtualStackText); - } - - return container; - } - - getIconStackText(virtual?: boolean): Phaser.GameObjects.BitmapText | null { - if (this.getMaxStackCount() === 1 || (virtual && !this.virtualStackCount)) { - return null; - } - - const text = globalScene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11); - text.letterSpacing = -0.5; - if (this.getStackCount() >= this.getMaxStackCount()) { - text.setTint(0xf89890); - } - text.setOrigin(0, 0); - - return text; - } -} - export abstract class ConsumableModifier extends Modifier { add(_modifiers: Modifier[]): boolean { return true; @@ -352,1710 +129,6 @@ export class AddVoucherModifier extends ConsumableModifier { } } -/** - * Modifier used for party-wide or passive items that start an initial - * {@linkcode battleCount} equal to {@linkcode maxBattles} that, for every - * battle, decrements. Typically, when {@linkcode battleCount} reaches 0, the - * modifier will be removed. If a modifier of the same type is to be added, it - * will reset {@linkcode battleCount} back to {@linkcode maxBattles} of the - * existing modifier instead of adding that modifier directly. - * @extends PersistentModifier - * @abstract - * @see {@linkcode add} - */ -export abstract class LapsingPersistentModifier extends PersistentModifier { - /** The maximum amount of battles the modifier will exist for */ - private maxBattles: number; - /** The current amount of battles the modifier will exist for */ - private battleCount: number; - - constructor(type: ModifierType, maxBattles: number, battleCount?: number, stackCount?: number) { - super(type, stackCount); - - this.maxBattles = maxBattles; - this.battleCount = battleCount ?? this.maxBattles; - } - - /** - * Goes through existing modifiers for any that match the selected modifier, - * which will then either add it to the existing modifiers if none were found - * or, if one was found, it will refresh {@linkcode battleCount}. - * @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 - */ - add(modifiers: PersistentModifier[], _virtual: boolean): boolean { - for (const modifier of modifiers) { - if (this.match(modifier)) { - const modifierInstance = modifier as LapsingPersistentModifier; - if (modifierInstance.getBattleCount() < modifierInstance.getMaxBattles()) { - modifierInstance.resetBattleCount(); - globalScene.playSound("se/restore"); - return true; - } - // should never get here - return false; - } - } - - modifiers.push(this); - return true; - } - - /** - * 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; - } - - getIcon(): Phaser.GameObjects.Container { - const container = super.getIcon(); - - // Linear interpolation on hue - 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 = hslToHex(hue, 0.5, 0.9); - const strokeHex = hslToHex(hue, 0.7, 0.3); - - const battleCountText = addTextObject(27, 0, this.battleCount.toString(), TextStyle.PARTY, { - fontSize: "66px", - color: typeHex, - }); - battleCountText.setShadow(0, 0); - battleCountText.setStroke(strokeHex, 16); - battleCountText.setOrigin(1, 0); - container.add(battleCountText); - - return container; - } - - getIconStackText(_virtual?: boolean): Phaser.GameObjects.BitmapText | null { - return null; - } - - getBattleCount(): number { - return this.battleCount; - } - - resetBattleCount(): void { - this.battleCount = this.maxBattles; - } - - /** - * Updates an existing modifier with a new `maxBattles` and `battleCount`. - */ - setNewBattleCount(count: number): void { - this.maxBattles = count; - this.battleCount = count; - } - - getMaxBattles(): number { - return this.maxBattles; - } - - getArgs(): any[] { - return [this.maxBattles, this.battleCount]; - } - - getMaxStackCount(_forThreshold?: boolean): number { - // Must be an abitrary number greater than 1 - return 2; - } -} - -/** - * Modifier used for passive items, specifically lures, that - * temporarily increases the chance of a double battle. - * @extends LapsingPersistentModifier - * @see {@linkcode apply} - */ -export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier { - public override type: DoubleBattleChanceBoosterModifierType; - - match(modifier: Modifier): boolean { - return modifier instanceof DoubleBattleChanceBoosterModifier && modifier.getMaxBattles() === this.getMaxBattles(); - } - - clone(): DoubleBattleChanceBoosterModifier { - return new DoubleBattleChanceBoosterModifier( - this.type, - this.getMaxBattles(), - this.getBattleCount(), - this.stackCount, - ); - } - - /** - * Increases the chance of a double battle occurring - * @param doubleBattleChance {@linkcode NumberHolder} for double battle chance - * @returns true - */ - 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; - - return true; - } -} - -/** - * Modifier used for party-wide items, specifically the X items, that - * temporarily increases the stat stage multiplier of the corresponding - * {@linkcode TempBattleStat}. - * @extends LapsingPersistentModifier - * @see {@linkcode apply} - */ -export class TempStatStageBoosterModifier extends LapsingPersistentModifier { - /** The stat whose stat stage multiplier will be temporarily increased */ - private stat: TempBattleStat; - /** The amount by which the stat stage itself or its multiplier will be increased by */ - private boost: number; - - constructor(type: ModifierType, stat: TempBattleStat, maxBattles: number, battleCount?: number, stackCount?: number) { - super(type, maxBattles, battleCount, stackCount); - - this.stat = stat; - // Note that, because we want X Accuracy to maintain its original behavior, - // it will increment as it did previously, directly to the stat stage. - this.boost = stat !== Stat.ACC ? 0.3 : 1; - } - - match(modifier: Modifier): boolean { - if (modifier instanceof TempStatStageBoosterModifier) { - const modifierInstance = modifier as TempStatStageBoosterModifier; - return modifierInstance.stat === this.stat; - } - return false; - } - - clone() { - return new TempStatStageBoosterModifier( - this.type, - this.stat, - this.getMaxBattles(), - this.getBattleCount(), - this.stackCount, - ); - } - - getArgs(): any[] { - return [this.stat, ...super.getArgs()]; - } - - /** - * Checks if {@linkcode args} contains the necessary elements and if the - * incoming stat is matches {@linkcode stat}. - * @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 - */ - 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 _tempBattleStat {@linkcode TempBattleStat} N/A - * @param statLevel {@linkcode NumberHolder} that holds the resulting value of the stat stage multiplier - */ - override apply(_tempBattleStat: TempBattleStat, statLevel: NumberHolder): boolean { - statLevel.value += this.boost; - return true; - } -} - -/** - * Modifier used for party-wide items, namely Dire Hit, that - * temporarily increments the critical-hit stage - * @extends LapsingPersistentModifier - * @see {@linkcode apply} - */ -export class TempCritBoosterModifier extends LapsingPersistentModifier { - clone() { - return new TempCritBoosterModifier(this.type, this.getMaxBattles(), this.getBattleCount(), this.stackCount); - } - - match(modifier: Modifier): boolean { - return modifier instanceof TempCritBoosterModifier; - } - - /** - * Checks if {@linkcode args} contains the necessary elements. - * @param critLevel {@linkcode NumberHolder} that holds the resulting critical-hit level - * @returns `true` if the critical-hit stage boost applies successfully - */ - override shouldApply(critLevel?: NumberHolder): boolean { - return !!critLevel; - } - - /** - * Increases the current critical-hit stage value by 1. - * @param critLevel {@linkcode NumberHolder} that holds the resulting critical-hit level - * @returns `true` if the critical-hit stage boost applies successfully - */ - override apply(critLevel: NumberHolder): boolean { - critLevel.value++; - return true; - } -} - -export class MapModifier extends PersistentModifier { - clone(): MapModifier { - return new MapModifier(this.type, this.stackCount); - } - - override apply(..._args: unknown[]): boolean { - return true; - } - - getMaxStackCount(): number { - return 1; - } -} - -export class MegaEvolutionAccessModifier extends PersistentModifier { - clone(): MegaEvolutionAccessModifier { - return new MegaEvolutionAccessModifier(this.type, this.stackCount); - } - - override apply(..._args: unknown[]): boolean { - return true; - } - - getMaxStackCount(): number { - return 1; - } -} - -export class GigantamaxAccessModifier extends PersistentModifier { - clone(): GigantamaxAccessModifier { - return new GigantamaxAccessModifier(this.type, this.stackCount); - } - - /** - * Applies {@linkcode GigantamaxAccessModifier} - * @param _args N/A - * @returns always `true` - */ - apply(..._args: unknown[]): boolean { - return true; - } - - getMaxStackCount(): number { - return 1; - } -} - -export class TerastallizeAccessModifier extends PersistentModifier { - clone(): TerastallizeAccessModifier { - return new TerastallizeAccessModifier(this.type, this.stackCount); - } - - /** - * Applies {@linkcode TerastallizeAccessModifier} - * @param _args N/A - * @returns always `true` - */ - override apply(..._args: unknown[]): boolean { - return true; - } - - getMaxStackCount(): number { - return 1; - } -} - -export abstract class PokemonHeldItemModifier extends PersistentModifier { - /** The ID of the {@linkcode Pokemon} that this item belongs to. */ - public pokemonId: number; - /** Whether this item can be transfered to or stolen by another Pokemon. */ - public isTransferable = true; - - constructor(type: ModifierType, pokemonId: number, stackCount?: number) { - super(type, stackCount); - - this.pokemonId = pokemonId; - } - - abstract matchType(_modifier: Modifier): boolean; - - match(modifier: Modifier) { - return this.matchType(modifier) && (modifier as PokemonHeldItemModifier).pokemonId === this.pokemonId; - } - - getArgs(): any[] { - return [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(): boolean { - return !!this.getPokemon()?.isOnField(); - } - - getIcon(forSummary?: boolean): Phaser.GameObjects.Container { - const container = !forSummary ? globalScene.add.container(0, 0) : super.getIcon(); - - if (!forSummary) { - const pokemon = this.getPokemon(); - if (pokemon) { - const pokemonIcon = globalScene.addPokemonIcon(pokemon, -2, 10, 0, 0.5, undefined, true); - container.add(pokemonIcon); - container.setName(pokemon.id.toString()); - } - - const item = globalScene.add.sprite(16, this.virtualStackCount ? 8 : 16, "items"); - item.setScale(0.5); - item.setOrigin(0, 0.5); - item.setTexture("items", this.type.iconImage); - container.add(item); - - const stackText = this.getIconStackText(); - if (stackText) { - container.add(stackText); - } - - const virtualStackText = this.getIconStackText(true); - if (virtualStackText) { - container.add(virtualStackText); - } - } else { - container.setScale(0.5); - } - - return container; - } - - getPokemon(): Pokemon | undefined { - return globalScene.getPokemonById(this.pokemonId) ?? undefined; - } - - getScoreMultiplier(): number { - return 1; - } - - getMaxStackCount(forThreshold?: boolean): number { - const pokemon = this.getPokemon(); - if (!pokemon) { - return 0; - } - if (pokemon.isPlayer() && forThreshold) { - return globalScene - .getPlayerParty() - .map(p => this.getMaxHeldItemCount(p)) - .reduce((stackCount: number, maxStackCount: number) => Math.max(stackCount, maxStackCount), 0); - } - return this.getMaxHeldItemCount(pokemon); - } - - getSpecies(): SpeciesId | null { - return null; - } - - abstract getMaxHeldItemCount(pokemon?: Pokemon): number; -} - -export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModifier { - protected battlesLeft: number; - public isTransferable = false; - - constructor(type: ModifierType, pokemonId: number, battlesLeft?: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.battlesLeft = battlesLeft!; // TODO: is this bang correct? - } - - /** - * 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; - } - - /** - * Retrieve the {@linkcode Modifier | Modifiers} icon as a {@linkcode Phaser.GameObjects.Container | Container} - * @param forSummary `true` if the icon is for the summary screen - * @returns the icon as a {@linkcode Phaser.GameObjects.Container | Container} - */ - public getIcon(forSummary?: boolean): Phaser.GameObjects.Container { - const container = super.getIcon(forSummary); - - if (this.getPokemon()?.isPlayer()) { - const battleCountText = addTextObject(27, 0, this.battlesLeft.toString(), TextStyle.PARTY, { - fontSize: "66px", - color: Color.PINK, - }); - battleCountText.setShadow(0, 0); - battleCountText.setStroke(ShadowColor.RED, 16); - battleCountText.setOrigin(1, 0); - container.add(battleCountText); - } - - return container; - } - - getBattlesLeft(): number { - return this.battlesLeft; - } - - getMaxStackCount(_forThreshold?: boolean): number { - return 1; - } -} - -/** - * Modifier used for held items, specifically vitamins like Carbos, Hp Up, etc., that - * increase the value of a given {@linkcode PermanentStat}. - * @extends PokemonHeldItemModifier - * @see {@linkcode apply} - */ -export class BaseStatModifier extends PokemonHeldItemModifier { - protected stat: PermanentStat; - public isTransferable = false; - - constructor(type: ModifierType, pokemonId: number, stat: PermanentStat, stackCount?: number) { - super(type, pokemonId, stackCount); - this.stat = stat; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof BaseStatModifier) { - return (modifier as BaseStatModifier).stat === this.stat; - } - return false; - } - - clone(): PersistentModifier { - return new BaseStatModifier(this.type, this.pokemonId, this.stat, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat(this.stat); - } - - /** - * 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); - } - - /** - * 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; - } - - getScoreMultiplier(): number { - return 1.1; - } - - getMaxHeldItemCount(pokemon: Pokemon): number { - return pokemon.ivs[this.stat]; - } -} - -export class EvoTrackerModifier extends PokemonHeldItemModifier { - protected species: SpeciesId; - protected required: number; - public isTransferable = false; - - constructor(type: ModifierType, pokemonId: number, species: SpeciesId, required: number, stackCount?: number) { - super(type, pokemonId, stackCount); - this.species = species; - this.required = required; - } - - matchType(modifier: Modifier): boolean { - return ( - modifier instanceof EvoTrackerModifier && modifier.species === this.species && modifier.required === this.required - ); - } - - clone(): PersistentModifier { - return new EvoTrackerModifier(this.type, this.pokemonId, this.species, this.required, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat([this.species, this.required]); - } - - /** - * Applies the {@linkcode EvoTrackerModifier} - * @returns always `true` - */ - override apply(): boolean { - return true; - } - - getIconStackText(_virtual?: boolean): Phaser.GameObjects.BitmapText | null { - const pokemon = this.getPokemon(); - - const count = (pokemon?.getPersistentTreasureCount() || 0) + this.getStackCount(); - - const text = globalScene.add.bitmapText(10, 15, "item-count", count.toString(), 11); - text.letterSpacing = -0.5; - if (count >= this.required) { - text.setTint(0xf89890); - } - text.setOrigin(0, 0); - - return text; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 999; - } - - override getSpecies(): SpeciesId { - return this.species; - } -} - -/** - * Currently used by Shuckle Juice item - */ -export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier { - public override type: PokemonBaseStatTotalModifierType; - public isTransferable = false; - public statModifier: 10 | -15; - - constructor(type: PokemonBaseStatTotalModifierType, pokemonId: number, statModifier: 10 | -15, stackCount?: number) { - super(type, pokemonId, stackCount); - this.statModifier = statModifier; - } - - override matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonBaseStatTotalModifier && this.statModifier === modifier.statModifier; - } - - override clone(): PersistentModifier { - return new PokemonBaseStatTotalModifier(this.type, this.pokemonId, this.statModifier, this.stackCount); - } - - override getArgs(): any[] { - return super.getArgs().concat(this.statModifier); - } - - /** - * 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); - } - - /** - * 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 - 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); - baseStats[i] = Math.min(Math.max(newVal, 1), 999999); - }); - - return true; - } - - override getScoreMultiplier(): number { - return 1.2; - } - - override getMaxHeldItemCount(_pokemon: Pokemon): number { - return 2; - } -} - -/** - * Currently used by Old Gateau item - */ -export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier { - public isTransferable = false; - - override matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonBaseStatFlatModifier; - } - - override clone(): PersistentModifier { - return new PokemonBaseStatFlatModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * 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); - } - - /** - * 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 - const stats = this.getStats(pokemon); - const statModifier = 20; - baseStats.forEach((v, i) => { - if (stats.includes(i)) { - const newVal = Math.floor(v + statModifier); - baseStats[i] = Math.min(Math.max(newVal, 1), 999999); - } - }); - - return true; - } - - /** - * Get the lowest of HP/Spd, lowest of Atk/SpAtk, and lowest of Def/SpDef - * @returns Array of 3 {@linkcode Stat}s to boost - */ - getStats(pokemon: Pokemon): Stat[] { - const stats: Stat[] = []; - const baseStats = pokemon.getSpeciesForm().baseStats.slice(0); - // HP or Speed - stats.push(baseStats[Stat.HP] < baseStats[Stat.SPD] ? Stat.HP : Stat.SPD); - // Attack or SpAtk - stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK); - // Def or SpDef - stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF); - return stats; - } - - override getScoreMultiplier(): number { - return 1.1; - } - - override getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } -} - -/** - * Currently used by Macho Brace item - */ -export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier { - public isTransferable = false; - - matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonIncrementingStatModifier; - } - - clone(): PokemonIncrementingStatModifier { - return new PokemonIncrementingStatModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * 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; - } - - /** - * 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 +2 per stack for HP, +1 per stack for other stats - // If the Macho Brace is at max stacks (50), adds additional 10% to total HP and 5% to other stats - const isHp = stat === Stat.HP; - - if (isHp) { - statHolder.value += 2 * this.stackCount; - if (this.stackCount === this.getMaxHeldItemCount()) { - statHolder.value = Math.floor(statHolder.value * 1.1); - } - } else { - statHolder.value += this.stackCount; - if (this.stackCount === this.getMaxHeldItemCount()) { - statHolder.value = Math.floor(statHolder.value * 1.05); - } - } - - return true; - } - - getScoreMultiplier(): number { - return 1.2; - } - - getMaxHeldItemCount(_pokemon?: Pokemon): number { - return 50; - } -} - -/** - * Modifier used for held items that Applies {@linkcode Stat} boost(s) - * using a multiplier. - * @extends PokemonHeldItemModifier - * @see {@linkcode apply} - */ -export class StatBoosterModifier extends PokemonHeldItemModifier { - /** The stats that the held item boosts */ - protected stats: Stat[]; - /** The multiplier used to increase the relevant stat(s) */ - protected multiplier: number; - - constructor(type: ModifierType, pokemonId: number, stats: Stat[], multiplier: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.stats = stats; - this.multiplier = multiplier; - } - - clone() { - return new StatBoosterModifier(this.type, this.pokemonId, this.stats, this.multiplier, this.stackCount); - } - - getArgs(): any[] { - return [...super.getArgs(), this.stats, this.multiplier]; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof StatBoosterModifier) { - const modifierInstance = modifier as StatBoosterModifier; - if (modifierInstance.multiplier === this.multiplier && modifierInstance.stats.length === this.stats.length) { - return modifierInstance.stats.every((e, i) => e === this.stats[i]); - } - } - - return false; - } - - /** - * Checks if the incoming stat is listed in {@linkcode stats} - * @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 - */ - 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 _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 - */ - override apply(_pokemon: Pokemon, _stat: Stat, statValue: NumberHolder): boolean { - statValue.value *= this.multiplier; - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } -} - -/** - * Modifier used for held items, specifically Eviolite, that apply - * {@linkcode Stat} boost(s) using a multiplier if the holder can evolve. - * @extends StatBoosterModifier - * @see {@linkcode apply} - */ -export class EvolutionStatBoosterModifier extends StatBoosterModifier { - matchType(modifier: Modifier): boolean { - return modifier instanceof EvolutionStatBoosterModifier; - } - - /** - * Checks if the stat boosts can apply and if the holder is not currently - * Gigantamax'd. - * @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 - */ - 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 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 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 - */ - override apply(pokemon: Pokemon, stat: Stat, statValue: NumberHolder): boolean { - const isUnevolved = pokemon.getSpeciesForm(true).speciesId in pokemonEvolutions; - - 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; - } - if (isUnevolved) { - // Full boost applied if holder is unfused and unevolved or, if fused, both parts of fusion are unevolved - return super.apply(pokemon, stat, statValue); - } - - return false; - } -} - -/** - * Modifier used for held items that Applies {@linkcode Stat} boost(s) using a - * multiplier if the holder is of a specific {@linkcode SpeciesId}. - * @extends StatBoosterModifier - * @see {@linkcode apply} - */ -export class SpeciesStatBoosterModifier extends StatBoosterModifier { - /** The species that the held item's stat boost(s) apply to */ - private species: SpeciesId[]; - - constructor( - type: ModifierType, - pokemonId: number, - stats: Stat[], - multiplier: number, - species: SpeciesId[], - stackCount?: number, - ) { - super(type, pokemonId, stats, multiplier, stackCount); - - this.species = species; - } - - clone() { - return new SpeciesStatBoosterModifier( - this.type, - this.pokemonId, - this.stats, - this.multiplier, - this.species, - this.stackCount, - ); - } - - getArgs(): any[] { - return [...super.getArgs(), this.species]; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof SpeciesStatBoosterModifier) { - const modifierInstance = modifier as SpeciesStatBoosterModifier; - if (modifierInstance.species.length === this.species.length) { - return super.matchType(modifier) && modifierInstance.species.every((e, i) => e === this.species[i]); - } - } - - return false; - } - - /** - * Checks if the incoming stat is listed in {@linkcode stats} and if the holder's {@linkcode SpeciesId} - * (or its fused species) is listed in {@linkcode species}. - * @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 - */ - 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 SpeciesId} being checked - * @param stat {@linkcode Stat} being checked - * @returns `true` if both parameters are in {@linkcode species} and {@linkcode stats} respectively, false otherwise - */ - contains(speciesId: SpeciesId, stat: Stat): boolean { - return this.species.includes(speciesId) && this.stats.includes(stat); - } -} - -/** - * Modifier used for held items that apply critical-hit stage boost(s). - * @extends PokemonHeldItemModifier - * @see {@linkcode apply} - */ -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: number, stageIncrement: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.stageIncrement = stageIncrement; - } - - clone() { - return new CritBoosterModifier(this.type, this.pokemonId, this.stageIncrement, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat(this.stageIncrement); - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof CritBoosterModifier) { - return (modifier as CritBoosterModifier).stageIncrement === this.stageIncrement; - } - - return false; - } - - /** - * Increases the current critical-hit stage value by {@linkcode stageIncrement}. - * @param _pokemon {@linkcode Pokemon} N/A - * @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level - * @returns always `true` - */ - override apply(_pokemon: Pokemon, critStage: NumberHolder): boolean { - critStage.value += this.stageIncrement; - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } -} - -/** - * Modifier used for held items that apply critical-hit stage boost(s) - * if the holder is of a specific {@linkcode SpeciesId}. - * @extends CritBoosterModifier - * @see {@linkcode shouldApply} - */ -export class SpeciesCritBoosterModifier extends CritBoosterModifier { - /** The species that the held item's critical-hit stage boost applies to */ - private species: SpeciesId[]; - - constructor( - type: ModifierType, - pokemonId: number, - stageIncrement: number, - species: SpeciesId[], - stackCount?: number, - ) { - super(type, pokemonId, stageIncrement, stackCount); - - this.species = species; - } - - clone() { - return new SpeciesCritBoosterModifier( - this.type, - this.pokemonId, - this.stageIncrement, - this.species, - this.stackCount, - ); - } - - getArgs(): any[] { - return [...super.getArgs(), this.species]; - } - - matchType(modifier: Modifier): boolean { - return modifier instanceof SpeciesCritBoosterModifier; - } - - /** - * Checks if the holder's {@linkcode SpeciesId} (or its fused species) is listed - * in {@linkcode species}. - * @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 - */ - 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))) - ); - } -} - -/** - * Applies Specific Type item boosts (e.g., Magnet) - */ -export class AttackTypeBoosterModifier extends PokemonHeldItemModifier { - public moveType: PokemonType; - private boostMultiplier: number; - - constructor(type: ModifierType, pokemonId: number, moveType: PokemonType, boostPercent: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.moveType = moveType; - this.boostMultiplier = boostPercent * 0.01; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof AttackTypeBoosterModifier) { - const attackTypeBoosterModifier = modifier as AttackTypeBoosterModifier; - return ( - attackTypeBoosterModifier.moveType === this.moveType && - attackTypeBoosterModifier.boostMultiplier === this.boostMultiplier - ); - } - - return false; - } - - clone() { - return new AttackTypeBoosterModifier( - this.type, - this.pokemonId, - this.moveType, - this.boostMultiplier * 100, - this.stackCount, - ); - } - - getArgs(): any[] { - return super.getArgs().concat([this.moveType, this.boostMultiplier * 100]); - } - - /** - * Checks if {@linkcode AttackTypeBoosterModifier} should be applied - * @param pokemon the {@linkcode Pokemon} that holds the held item - * @param moveType the {@linkcode PokemonType} 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?: PokemonType, movePower?: NumberHolder): boolean { - return ( - super.shouldApply(pokemon, moveType, movePower) && - typeof moveType === "number" && - movePower instanceof NumberHolder && - this.moveType === moveType - ); - } - - /** - * Applies {@linkcode AttackTypeBoosterModifier} - * @param pokemon {@linkcode Pokemon} that holds the held item - * @param moveType {@linkcode PokemonType} 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: PokemonType, 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; - } - - return false; - } - - getScoreMultiplier(): number { - return 1.2; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 99; - } -} - -export class SurviveDamageModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier): boolean { - return modifier instanceof SurviveDamageModifier; - } - - clone() { - return new SurviveDamageModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * 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; - } - - /** - * 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.randBattleSeedInt(10) < this.getStackCount()) { - surviveDamage.value = true; - - globalScene.phaseManager.queueMessage( - i18next.t("modifier:surviveDamageApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }), - ); - return true; - } - - return false; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 5; - } -} - -export class BypassSpeedChanceModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier) { - return modifier instanceof BypassSpeedChanceModifier; - } - - clone() { - return new BypassSpeedChanceModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * 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; - } - - /** - * 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.randBattleSeedInt(10) < this.getStackCount()) { - doBypassSpeed.value = true; - const isCommandFight = - globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT; - const hasQuickClaw = this.type.is("PokemonHeldItemModifierType") && this.type.id === "QUICK_CLAW"; - - if (isCommandFight && hasQuickClaw) { - globalScene.phaseManager.queueMessage( - i18next.t("modifier:bypassSpeedChanceApply", { - pokemonName: getPokemonNameWithAffix(pokemon), - itemName: i18next.t("modifierType:ModifierType.QUICK_CLAW.name"), - }), - ); - } - return true; - } - - return false; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 3; - } -} - -/** - * Class for Pokemon held items like King's Rock - * Because King's Rock can be stacked in PokeRogue, unlike mainline, it does not receive a boost from AbilityId.SERENE_GRACE - */ -export class FlinchChanceModifier extends PokemonHeldItemModifier { - private chance: number; - constructor(type: ModifierType, pokemonId: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.chance = 10; - } - - matchType(modifier: Modifier) { - return modifier instanceof FlinchChanceModifier; - } - - clone() { - return new FlinchChanceModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * 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; - } - - /** - * Applies {@linkcode FlinchChanceModifier} to randomly flinch targets hit. - * @param pokemon - The {@linkcode Pokemon} that holds the item - * @param flinched - A {@linkcode BooleanHolder} holding whether the pokemon has flinched - * @returns `true` if {@linkcode FlinchChanceModifier} was applied successfully - */ - override apply(pokemon: Pokemon, flinched: BooleanHolder): boolean { - // The check for pokemon.summonData is to ensure that a crash doesn't occur when a Pokemon with King's Rock procs a flinch - // TODO: Since summonData is always defined now, we can probably remove this - if (pokemon.summonData && !flinched.value && pokemon.randBattleSeedInt(100) < this.getStackCount() * this.chance) { - flinched.value = true; - return true; - } - - return false; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 3; - } -} - -export class TurnHealModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier) { - return modifier instanceof TurnHealModifier; - } - - clone() { - return new TurnHealModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * 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()) { - globalScene.phaseManager.unshiftNew( - "PokemonHealPhase", - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, - i18next.t("modifier:turnHealApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }), - true, - ); - return true; - } - - return false; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 4; - } -} - -/** - * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a - * set {@linkcode StatusEffect} at the end of a turn. - * @extends PokemonHeldItemModifier - * @see {@linkcode apply} - */ -export class TurnStatusEffectModifier extends PokemonHeldItemModifier { - /** The status effect to be applied by the held item */ - private effect: StatusEffect; - - constructor(type: ModifierType, pokemonId: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - switch (type.id) { - case "TOXIC_ORB": - this.effect = StatusEffect.TOXIC; - break; - case "FLAME_ORB": - this.effect = StatusEffect.BURN; - break; - } - } - - /** - * Checks if {@linkcode modifier} is an instance of this class, - * intentionally ignoring potentially different {@linkcode effect}s - * to prevent held item stockpiling since the item obtained first - * 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 - * TurnStatusEffectModifier, false otherwise - */ - matchType(modifier: Modifier): boolean { - return modifier instanceof TurnStatusEffectModifier; - } - - clone() { - return new TurnStatusEffectModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Tries to inflicts the holder with the associated {@linkcode StatusEffect}. - * @param pokemon {@linkcode Pokemon} that holds the held item - * @returns `true` if the status effect was applied successfully - */ - override apply(pokemon: Pokemon): boolean { - return pokemon.trySetStatus(this.effect, true, undefined, undefined, this.type.name); - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } - - getStatusEffect(): StatusEffect { - return this.effect; - } -} - -export class HitHealModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier) { - return modifier instanceof HitHealModifier; - } - - clone() { - return new HitHealModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * 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.totalDamageDealt && !pokemon.isFullHp()) { - // TODO: this shouldn't be undefined AFAIK - globalScene.phaseManager.unshiftNew( - "PokemonHealPhase", - pokemon.getBattlerIndex(), - toDmgValue(pokemon.turnData.totalDamageDealt / 8) * this.stackCount, - i18next.t("modifier:hitHealApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }), - true, - ); - } - - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 4; - } -} - -export class LevelIncrementBoosterModifier extends PersistentModifier { - match(modifier: Modifier) { - return modifier instanceof LevelIncrementBoosterModifier; - } - - clone() { - return new LevelIncrementBoosterModifier(this.type, this.stackCount); - } - - /** - * 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; - } - - /** - * 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; - } - - getMaxStackCount(_forThreshold?: boolean): number { - return 99; - } -} - -export class BerryModifier extends PokemonHeldItemModifier { - public berryType: BerryType; - public consumed: boolean; - - constructor(type: ModifierType, pokemonId: number, berryType: BerryType, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.berryType = berryType; - this.consumed = false; - } - - matchType(modifier: Modifier) { - return modifier instanceof BerryModifier && (modifier as BerryModifier).berryType === this.berryType; - } - - clone() { - return new BerryModifier(this.type, this.pokemonId, this.berryType, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat(this.berryType); - } - - /** - * 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); - } - - /** - * 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); - globalScene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve); - this.consumed = !preserve.value; - - // munch the berry and trigger unburden-like effects - getBerryEffectFunc(this.berryType)(pokemon); - applyAbAttrs("PostItemLostAbAttr", { pokemon }); - - // Update berry eaten trackers for Belch, Harvest, Cud Chew, etc. - // Don't recover it if we proc berry pouch (no item duplication) - pokemon.recordEatenBerry(this.berryType, this.consumed); - - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - if ([BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(this.berryType)) { - return 2; - } - return 3; - } -} - -export class PreserveBerryModifier extends PersistentModifier { - match(modifier: Modifier) { - return modifier instanceof PreserveBerryModifier; - } - - clone() { - return new PreserveBerryModifier(this.type, this.stackCount); - } - - /** - * 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; - } - - /** - * 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 { - doPreserve.value ||= pokemon.randBattleSeedInt(10) < this.getStackCount() * 3; - - return true; - } - - getMaxStackCount(): number { - return 3; - } -} - -export class PokemonInstantReviveModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier) { - return modifier instanceof PokemonInstantReviveModifier; - } - - clone() { - return new PokemonInstantReviveModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Applies {@linkcode PokemonInstantReviveModifier} - * @param pokemon The {@linkcode Pokemon} that holds the item - * @returns always `true` - */ - override apply(pokemon: Pokemon): boolean { - // Restore the Pokemon to half HP - globalScene.phaseManager.unshiftNew( - "PokemonHealPhase", - pokemon.getBattlerIndex(), - toDmgValue(pokemon.getMaxHp() / 2), - i18next.t("modifier:pokemonInstantReviveApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }), - false, - false, - true, - ); - - // Remove the Pokemon's FAINT status - pokemon.resetStatus(true, false, true, false); - - // Reapply Commander on the Pokemon's side of the field, if applicable - const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); - for (const p of field) { - applyAbAttrs("CommanderAbAttr", { pokemon: p }); - } - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } -} - -/** - * Modifier used for held items, namely White Herb, that restore adverse stat - * stages in battle. - * @extends PokemonHeldItemModifier - * @see {@linkcode apply} - */ -export class ResetNegativeStatStageModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier) { - return modifier instanceof ResetNegativeStatStageModifier; - } - - clone() { - return new ResetNegativeStatStageModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Goes through the holder's stat stages and, if any are negative, resets that - * stat stage back to 0. - * @param pokemon {@linkcode Pokemon} that holds the item - * @returns `true` if any stat stages were reset, false otherwise - */ - override apply(pokemon: Pokemon): boolean { - let statRestored = false; - - for (const s of BATTLE_STATS) { - if (pokemon.getStatStage(s) < 0) { - pokemon.setStatStage(s, 0); - statRestored = true; - } - } - - if (statRestored) { - globalScene.phaseManager.queueMessage( - i18next.t("modifier:resetNegativeStatStageApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - typeName: this.type.name, - }), - ); - } - return statRestored; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 2; - } -} - -/** - * Modifier used for held items, namely Mystical Rock, that extend the - * duration of weather and terrain effects. - * @extends PokemonHeldItemModifier - * @see {@linkcode apply} - */ -export class FieldEffectModifier extends PokemonHeldItemModifier { - /** - * Provides two more turns per stack to any weather or terrain effect caused - * by the holder. - * @param pokemon {@linkcode Pokemon} that holds the held item - * @param fieldDuration {@linkcode NumberHolder} that stores the current field effect duration - * @returns `true` if the field effect extension was applied successfully - */ - override apply(_pokemon: Pokemon, fieldDuration: NumberHolder): boolean { - fieldDuration.value += 2 * this.stackCount; - return true; - } - - override matchType(modifier: Modifier): boolean { - return modifier instanceof FieldEffectModifier; - } - - override clone(): FieldEffectModifier { - return new FieldEffectModifier(this.type, this.pokemonId, this.stackCount); - } - - override getMaxHeldItemCount(_pokemon?: Pokemon): number { - return 2; - } -} - export abstract class ConsumablePokemonModifier extends ConsumableModifier { public pokemonId: number; @@ -2310,7 +383,7 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier { * @returns always `true` */ override apply(playerPokemon: PlayerPokemon, levelCount: NumberHolder = new NumberHolder(1)): boolean { - globalScene.applyModifiers(LevelIncrementBoosterModifier, true, levelCount); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.LEVEL_INCREMENT_BOOSTER, { numberHolder: levelCount }); playerPokemon.level += levelCount.value; if (playerPokemon.level <= globalScene.getMaxExpLevel(true)) { @@ -2443,455 +516,6 @@ export class FusePokemonModifier extends ConsumablePokemonModifier { } } -export class MultipleParticipantExpBonusModifier extends PersistentModifier { - match(modifier: Modifier): boolean { - return modifier instanceof MultipleParticipantExpBonusModifier; - } - - /** - * Applies {@linkcode MultipleParticipantExpBonusModifier} - * @returns always `true` - */ - apply(): boolean { - return true; - } - - clone(): MultipleParticipantExpBonusModifier { - return new MultipleParticipantExpBonusModifier(this.type, this.stackCount); - } - - getMaxStackCount(): number { - return 5; - } -} - -export class HealingBoosterModifier extends PersistentModifier { - private multiplier: number; - - constructor(type: ModifierType, multiplier: number, stackCount?: number) { - super(type, stackCount); - - this.multiplier = multiplier; - } - - match(modifier: Modifier): boolean { - return modifier instanceof HealingBoosterModifier; - } - - clone(): HealingBoosterModifier { - return new HealingBoosterModifier(this.type, this.multiplier, this.stackCount); - } - - getArgs(): any[] { - return [this.multiplier]; - } - - /** - * 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(): number { - return 5; - } -} - -export class ExpBoosterModifier extends PersistentModifier { - private boostMultiplier: number; - - constructor(type: ModifierType, boostPercent: number, stackCount?: number) { - super(type, stackCount); - - this.boostMultiplier = boostPercent * 0.01; - } - - match(modifier: Modifier): boolean { - if (modifier instanceof ExpBoosterModifier) { - const expModifier = modifier as ExpBoosterModifier; - return expModifier.boostMultiplier === this.boostMultiplier; - } - return false; - } - - clone(): ExpBoosterModifier { - return new ExpBoosterModifier(this.type, this.boostMultiplier * 100, this.stackCount); - } - - getArgs(): any[] { - return [this.boostMultiplier * 100]; - } - - /** - * 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(_forThreshold?: boolean): number { - return this.boostMultiplier < 1 ? (this.boostMultiplier < 0.6 ? 99 : 30) : 10; - } -} - -export class PokemonExpBoosterModifier extends PokemonHeldItemModifier { - public override type: PokemonExpBoosterModifierType; - - private boostMultiplier: number; - - constructor(type: PokemonExpBoosterModifierType, pokemonId: number, boostPercent: number, stackCount?: number) { - super(type, pokemonId, stackCount); - this.boostMultiplier = boostPercent * 0.01; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof PokemonExpBoosterModifier) { - const pokemonExpModifier = modifier as PokemonExpBoosterModifier; - return pokemonExpModifier.boostMultiplier === this.boostMultiplier; - } - return false; - } - - clone(): PersistentModifier { - return new PokemonExpBoosterModifier(this.type, this.pokemonId, this.boostMultiplier * 100, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat(this.boostMultiplier * 100); - } - - /** - * 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; - } - - /** - * 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): number { - return 99; - } -} - -export class ExpShareModifier extends PersistentModifier { - match(modifier: Modifier): boolean { - return modifier instanceof ExpShareModifier; - } - - clone(): ExpShareModifier { - return new ExpShareModifier(this.type, this.stackCount); - } - - /** - * Applies {@linkcode ExpShareModifier} - * @returns always `true` - */ - override apply(): boolean { - return true; - } - - getMaxStackCount(): number { - return 5; - } -} - -export class ExpBalanceModifier extends PersistentModifier { - match(modifier: Modifier): boolean { - return modifier instanceof ExpBalanceModifier; - } - - clone(): ExpBalanceModifier { - return new ExpBalanceModifier(this.type, this.stackCount); - } - - /** - * Applies {@linkcode ExpBalanceModifier} - * @returns always `true` - */ - override apply(): boolean { - return true; - } - - getMaxStackCount(): number { - return 4; - } -} - -export class PokemonFriendshipBoosterModifier extends PokemonHeldItemModifier { - public override type: PokemonFriendshipBoosterModifierType; - - matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonFriendshipBoosterModifier; - } - - clone(): PersistentModifier { - return new PokemonFriendshipBoosterModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * 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): number { - return 3; - } -} - -export class PokemonNatureWeightModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonNatureWeightModifier; - } - - clone(): PersistentModifier { - return new PokemonNatureWeightModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * 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; - } - - return false; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 10; - } -} - -export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier { - public override type: PokemonMoveAccuracyBoosterModifierType; - private accuracyAmount: number; - - constructor(type: PokemonMoveAccuracyBoosterModifierType, pokemonId: number, accuracy: number, stackCount?: number) { - super(type, pokemonId, stackCount); - this.accuracyAmount = accuracy; - } - - matchType(modifier: Modifier): boolean { - if (modifier instanceof PokemonMoveAccuracyBoosterModifier) { - const pokemonAccuracyBoosterModifier = modifier as PokemonMoveAccuracyBoosterModifier; - return pokemonAccuracyBoosterModifier.accuracyAmount === this.accuracyAmount; - } - return false; - } - - clone(): PersistentModifier { - return new PokemonMoveAccuracyBoosterModifier(this.type, this.pokemonId, this.accuracyAmount, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat(this.accuracyAmount); - } - - /** - * 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; - } - - /** - * 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 = moveAccuracy.value + this.accuracyAmount * this.getStackCount(); - - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 3; - } -} - -export class PokemonMultiHitModifier extends PokemonHeldItemModifier { - public override type: PokemonMultiHitModifierType; - - matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonMultiHitModifier; - } - - clone(): PersistentModifier { - return new PokemonMultiHitModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * For each stack, converts 25 percent of attack damage into an additional strike. - * @param pokemon The {@linkcode Pokemon} using the move - * @param moveId The {@linkcode MoveId | identifier} for the move being used - * @param count {@linkcode NumberHolder} holding the move's hit count for this turn - * @param damageMultiplier {@linkcode NumberHolder} holding a damage multiplier applied to a strike of this move - * @returns always `true` - */ - override apply( - pokemon: Pokemon, - moveId: MoveId, - count: NumberHolder | null = null, - damageMultiplier: NumberHolder | null = null, - ): boolean { - const move = allMoves[moveId]; - /** - * The move must meet Parental Bond's restrictions for this item - * to apply. This means - * - Only attacks are boosted - * - Multi-strike moves, charge moves, and self-sacrificial moves are not boosted - * (though Multi-Lens can still affect moves boosted by Parental Bond) - * - Multi-target moves are not boosted *unless* they can only hit a single Pokemon - * - Fling, Uproar, Rollout, Ice Ball, and Endeavor are not boosted - */ - if (!move.canBeMultiStrikeEnhanced(pokemon)) { - return false; - } - - if (!isNullOrUndefined(count)) { - return this.applyHitCountBoost(count); - } - if (!isNullOrUndefined(damageMultiplier)) { - return this.applyDamageModifier(pokemon, damageMultiplier); - } - - return false; - } - - /** Adds strikes to a move equal to the number of stacked Multi-Lenses */ - private applyHitCountBoost(count: NumberHolder): boolean { - count.value += this.getStackCount(); - return true; - } - - /** - * If applied to the first hit of a move, sets the damage multiplier - * equal to (1 - the number of stacked Multi-Lenses). - * Additional strikes beyond that are given a 0.25x damage multiplier - */ - private applyDamageModifier(pokemon: Pokemon, damageMultiplier: NumberHolder): boolean { - if (pokemon.turnData.hitsLeft === pokemon.turnData.hitCount) { - // Reduce first hit by 25% for each stack count - damageMultiplier.value *= 1 - 0.25 * this.getStackCount(); - return true; - } - if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== this.getStackCount() + 1) { - // Deal 25% damage for each remaining Multi Lens hit - damageMultiplier.value *= 0.25; - return true; - } - // An extra hit not caused by Multi Lens -- assume it is Parental Bond - return false; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 2; - } -} - -export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier { - public override type: FormChangeItemModifierType; - public formChangeItem: FormChangeItem; - public active: boolean; - public isTransferable = false; - - constructor( - type: FormChangeItemModifierType, - pokemonId: number, - formChangeItem: FormChangeItem, - active: boolean, - stackCount?: number, - ) { - super(type, pokemonId, stackCount); - this.formChangeItem = formChangeItem; - this.active = active; - } - - matchType(modifier: Modifier): boolean { - return modifier instanceof PokemonFormChangeItemModifier && modifier.formChangeItem === this.formChangeItem; - } - - clone(): PersistentModifier { - return new PokemonFormChangeItemModifier( - this.type, - this.pokemonId, - this.formChangeItem, - this.active, - this.stackCount, - ); - } - - getArgs(): any[] { - return super.getArgs().concat(this.formChangeItem, this.active); - } - - /** - * 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) { - this.active = false; - } - - const ret = globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger); - - if (switchActive) { - this.active = true; - } - - return ret; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } -} - export class MoneyRewardModifier extends ConsumableModifier { private moneyMultiplier: number; @@ -2908,855 +532,19 @@ export class MoneyRewardModifier extends ConsumableModifier { override apply(): boolean { const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); - globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); globalScene.addMoney(moneyAmount.value); - globalScene.getPlayerParty().map(p => { + for (const p of globalScene.getPlayerParty()) { if (p.species?.speciesId === SpeciesId.GIMMIGHOUL || p.fusionSpecies?.speciesId === SpeciesId.GIMMIGHOUL) { const factor = Math.min(Math.floor(this.moneyMultiplier), 3); - const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier( - p, - factor, - ) as EvoTrackerModifier; - globalScene.addModifier(modifier); - } - }); - - return true; - } -} - -export class MoneyMultiplierModifier extends PersistentModifier { - match(modifier: Modifier): boolean { - return modifier instanceof MoneyMultiplierModifier; - } - - clone(): MoneyMultiplierModifier { - return new MoneyMultiplierModifier(this.type, this.stackCount); - } - - /** - * 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(): number { - return 5; - } -} - -export class DamageMoneyRewardModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier): boolean { - return modifier instanceof DamageMoneyRewardModifier; - } - - clone(): DamageMoneyRewardModifier { - return new DamageMoneyRewardModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * 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 moneyAmount = new NumberHolder(Math.floor(multiplier.value * (0.5 * this.getStackCount()))); - globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); - globalScene.addMoney(moneyAmount.value); - - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 5; - } -} - -export class MoneyInterestModifier extends PersistentModifier { - match(modifier: Modifier): boolean { - return modifier instanceof MoneyInterestModifier; - } - - /** - * Applies {@linkcode MoneyInterestModifier} - * @returns always `true` - */ - override apply(): boolean { - const interestAmount = Math.floor(globalScene.money * 0.1 * this.getStackCount()); - globalScene.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, - }); - globalScene.phaseManager.queueMessage(message, undefined, true); - - return true; - } - - clone(): MoneyInterestModifier { - return new MoneyInterestModifier(this.type, this.stackCount); - } - - getMaxStackCount(): number { - return 5; - } -} - -export class HiddenAbilityRateBoosterModifier extends PersistentModifier { - match(modifier: Modifier): boolean { - return modifier instanceof HiddenAbilityRateBoosterModifier; - } - - clone(): HiddenAbilityRateBoosterModifier { - return new HiddenAbilityRateBoosterModifier(this.type, this.stackCount); - } - - /** - * 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(): number { - return 4; - } -} - -export class ShinyRateBoosterModifier extends PersistentModifier { - match(modifier: Modifier): boolean { - return modifier instanceof ShinyRateBoosterModifier; - } - - clone(): ShinyRateBoosterModifier { - return new ShinyRateBoosterModifier(this.type, this.stackCount); - } - - /** - * 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(): number { - return 4; - } -} - -export class CriticalCatchChanceBoosterModifier extends PersistentModifier { - match(modifier: Modifier): boolean { - return modifier instanceof CriticalCatchChanceBoosterModifier; - } - - clone(): CriticalCatchChanceBoosterModifier { - return new CriticalCatchChanceBoosterModifier(this.type, this.stackCount); - } - - /** - * Applies {@linkcode CriticalCatchChanceBoosterModifier} - * @param boost {@linkcode NumberHolder} holding the boost value - * @returns always `true` - */ - override apply(boost: NumberHolder): boolean { - // 1 stack: 2x - // 2 stack: 2.5x - // 3 stack: 3x - boost.value *= 1.5 + this.getStackCount() / 2; - - return true; - } - - getMaxStackCount(): number { - return 3; - } -} - -export class LockModifierTiersModifier extends PersistentModifier { - match(modifier: Modifier): boolean { - return modifier instanceof LockModifierTiersModifier; - } - - /** - * Applies {@linkcode LockModifierTiersModifier} - * @returns always `true` - */ - override apply(): boolean { - return true; - } - - clone(): LockModifierTiersModifier { - return new LockModifierTiersModifier(this.type, this.stackCount); - } - - getMaxStackCount(): number { - return 1; - } -} - -/** - * Black Sludge item - */ -export class HealShopCostModifier extends PersistentModifier { - public readonly shopMultiplier: number; - - constructor(type: ModifierType, shopMultiplier: number, stackCount?: number) { - super(type, stackCount); - - this.shopMultiplier = shopMultiplier ?? 2.5; - } - - match(modifier: Modifier): boolean { - return modifier instanceof HealShopCostModifier; - } - - clone(): HealShopCostModifier { - return new HealShopCostModifier(this.type, this.shopMultiplier, this.stackCount); - } - - /** - * 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; - } - - getArgs(): any[] { - return super.getArgs().concat(this.shopMultiplier); - } - - getMaxStackCount(): number { - return 1; - } -} - -export class BoostBugSpawnModifier extends PersistentModifier { - match(modifier: Modifier): boolean { - return modifier instanceof BoostBugSpawnModifier; - } - - clone(): BoostBugSpawnModifier { - return new BoostBugSpawnModifier(this.type, this.stackCount); - } - - /** - * Applies {@linkcode BoostBugSpawnModifier} - * @returns always `true` - */ - override apply(): boolean { - return true; - } - - getMaxStackCount(): number { - return 1; - } -} - -export class SwitchEffectTransferModifier extends PokemonHeldItemModifier { - matchType(modifier: Modifier): boolean { - return modifier instanceof SwitchEffectTransferModifier; - } - - clone(): SwitchEffectTransferModifier { - return new SwitchEffectTransferModifier(this.type, this.pokemonId, this.stackCount); - } - - /** - * Applies {@linkcode SwitchEffectTransferModifier} - * @returns always `true` - */ - override apply(): boolean { - return true; - } - - getMaxHeldItemCount(_pokemon: Pokemon): number { - return 1; - } -} - -/** - * Abstract class for held items that steal other Pokemon's items. - * @see {@linkcode TurnHeldItemTransferModifier} - * @see {@linkcode ContactHeldItemTransferChanceModifier} - */ -export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { - /** - * Determines the targets to transfer items from when this applies. - * @param pokemon the {@linkcode Pokemon} holding this item - * @param _args N/A - * @returns the opponents of the source {@linkcode Pokemon} - */ - getTargets(pokemon?: Pokemon, ..._args: unknown[]): Pokemon[] { - return pokemon?.getOpponents?.() ?? []; - } - - /** - * Steals an item, chosen randomly, from a set of target Pokemon. - * @param pokemon The {@linkcode Pokemon} holding this item - * @param target The {@linkcode Pokemon} to steal from (optional) - * @param _args N/A - * @returns `true` if an item was stolen; false otherwise. - */ - override apply(pokemon: Pokemon, target?: Pokemon, ..._args: unknown[]): boolean { - const opponents = this.getTargets(pokemon, target); - - if (!opponents.length) { - return false; - } - - const targetPokemon = opponents[pokemon.randBattleSeedInt(opponents.length)]; - - const transferredItemCount = this.getTransferredItemCount(); - if (!transferredItemCount) { - return false; - } - - const transferredModifierTypes: ModifierType[] = []; - const itemModifiers = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === targetPokemon.id && m.isTransferable, - targetPokemon.isPlayer(), - ) as PokemonHeldItemModifier[]; - - for (let i = 0; i < transferredItemCount; i++) { - if (!itemModifiers.length) { - break; - } - const randItemIndex = pokemon.randBattleSeedInt(itemModifiers.length); - const randItem = itemModifiers[randItemIndex]; - if (globalScene.tryTransferHeldItemModifier(randItem, pokemon, false)) { - transferredModifierTypes.push(randItem.type); - itemModifiers.splice(randItemIndex, 1); + p.heldItemManager.add(HeldItemId.GIMMIGHOUL_EVO_TRACKER, factor); } } - for (const mt of transferredModifierTypes) { - globalScene.phaseManager.queueMessage(this.getTransferMessage(pokemon, targetPokemon, mt)); - } - - return !!transferredModifierTypes.length; - } - - abstract getTransferredItemCount(): number; - - abstract getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierType): string; -} - -/** - * Modifier for held items that steal items from the enemy at the end of - * each turn. - * @see {@linkcode modifierTypes[MINI_BLACK_HOLE]} - */ -export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { - isTransferable = true; - - matchType(modifier: Modifier): boolean { - return modifier instanceof TurnHeldItemTransferModifier; - } - - clone(): TurnHeldItemTransferModifier { - return new TurnHeldItemTransferModifier(this.type, this.pokemonId, this.stackCount); - } - - getTransferredItemCount(): number { - return this.getStackCount(); - } - - 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): number { - return 1; - } - - setTransferrableFalse(): void { - this.isTransferable = false; - } -} - -/** - * Modifier for held items that add a chance to steal items from the target of a - * successful attack. - * @see {@linkcode modifierTypes[GRIP_CLAW]} - * @see {@linkcode HeldItemTransferModifier} - */ -export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModifier { - public readonly chance: number; - - constructor(type: ModifierType, pokemonId: number, chancePercent: number, stackCount?: number) { - super(type, pokemonId, stackCount); - - this.chance = chancePercent / 100; - } - - /** - * Determines the target to steal items from when this applies. - * @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 - */ - override getTargets(_holderPokemon: Pokemon, targetPokemon: Pokemon): Pokemon[] { - return targetPokemon ? [targetPokemon] : []; - } - - matchType(modifier: Modifier): boolean { - return modifier instanceof ContactHeldItemTransferChanceModifier; - } - - clone(): ContactHeldItemTransferChanceModifier { - return new ContactHeldItemTransferChanceModifier(this.type, this.pokemonId, this.chance * 100, this.stackCount); - } - - getArgs(): any[] { - return super.getArgs().concat(this.chance * 100); - } - - getTransferredItemCount(): number { - return randSeedFloat() <= this.chance * this.getStackCount() ? 1 : 0; - } - - 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): number { - return 5; - } -} - -export class IvScannerModifier extends PersistentModifier { - constructor(type: ModifierType, _stackCount?: number) { - super(type); - } - - match(modifier: Modifier): boolean { - return modifier instanceof IvScannerModifier; - } - - clone(): IvScannerModifier { - return new IvScannerModifier(this.type); - } - - /** - * Applies {@linkcode IvScannerModifier} - * @returns always `true` - */ - override apply(): boolean { - return true; //Dude are you kidding me - } - - getMaxStackCount(): number { - return 1; - } -} - -export class ExtraModifierModifier extends PersistentModifier { - match(modifier: Modifier): boolean { - return modifier instanceof ExtraModifierModifier; - } - - clone(): ExtraModifierModifier { - return new ExtraModifierModifier(this.type, this.stackCount); - } - - /** - * 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(): number { - return 3; - } -} - -/** - * Modifier used for timed boosts to the player's shop item rewards. - * @extends LapsingPersistentModifier - * @see {@linkcode apply} - */ -export class TempExtraModifierModifier extends LapsingPersistentModifier { - /** - * Goes through existing modifiers for any that match Silver Pokeball, - * which will then add the max count of the new item to the existing count of the current item. - * If no existing Silver Pokeballs are found, will add a new one. - * @param modifiers {@linkcode PersistentModifier} array of the player's modifiers - * @param _virtual N/A - * @returns true if the modifier was successfully added or applied, false otherwise - */ - add(modifiers: PersistentModifier[], _virtual: boolean): boolean { - for (const modifier of modifiers) { - if (this.match(modifier)) { - const modifierInstance = modifier as TempExtraModifierModifier; - const newBattleCount = this.getMaxBattles() + modifierInstance.getBattleCount(); - - modifierInstance.setNewBattleCount(newBattleCount); - globalScene.playSound("se/restore"); - return true; - } - } - - modifiers.push(this); - return true; - } - - clone() { - return new TempExtraModifierModifier(this.type, this.getMaxBattles(), this.getBattleCount(), this.stackCount); - } - - match(modifier: Modifier): boolean { - return modifier instanceof TempExtraModifierModifier; - } - - /** - * Increases the current rewards in the battle by the `stackCount`. - * @returns `true` if the shop reward number modifier applies successfully - * @param count {@linkcode NumberHolder} that holds the resulting shop item reward count - */ - apply(count: NumberHolder): boolean { - count.value += this.getStackCount(); - return true; - } -} - -export abstract class EnemyPersistentModifier extends PersistentModifier { - getMaxStackCount(): number { - return 5; - } -} - -abstract class EnemyDamageMultiplierModifier extends EnemyPersistentModifier { - protected damageMultiplier: number; - - constructor(type: ModifierType, damageMultiplier: number, stackCount?: number) { - super(type, stackCount); - - this.damageMultiplier = damageMultiplier; - } - - /** - * Applies {@linkcode EnemyDamageMultiplierModifier} - * @param multiplier {NumberHolder} holding the multiplier value - * @returns always `true` - */ - override apply(multiplier: NumberHolder): boolean { - multiplier.value = toDmgValue(multiplier.value * Math.pow(this.damageMultiplier, this.getStackCount())); - - return true; - } - - getMaxStackCount(): number { - return 99; - } -} - -export class EnemyDamageBoosterModifier extends EnemyDamageMultiplierModifier { - constructor(type: ModifierType, _boostPercent: number, stackCount?: number) { - //super(type, 1 + ((boostPercent || 10) * 0.01), stackCount); - super(type, 1.05, stackCount); // Hardcode multiplier temporarily - } - - match(modifier: Modifier): boolean { - return modifier instanceof EnemyDamageBoosterModifier; - } - - clone(): EnemyDamageBoosterModifier { - return new EnemyDamageBoosterModifier(this.type, (this.damageMultiplier - 1) * 100, this.stackCount); - } - - getArgs(): any[] { - return [(this.damageMultiplier - 1) * 100]; - } - - getMaxStackCount(): number { - return 999; - } -} - -export class EnemyDamageReducerModifier extends EnemyDamageMultiplierModifier { - constructor(type: ModifierType, _reductionPercent: number, stackCount?: number) { - //super(type, 1 - ((reductionPercent || 5) * 0.01), stackCount); - super(type, 0.975, stackCount); // Hardcode multiplier temporarily - } - - match(modifier: Modifier): boolean { - return modifier instanceof EnemyDamageReducerModifier; - } - - clone(): EnemyDamageReducerModifier { - return new EnemyDamageReducerModifier(this.type, (1 - this.damageMultiplier) * 100, this.stackCount); - } - - getArgs(): any[] { - return [(1 - this.damageMultiplier) * 100]; - } - - getMaxStackCount(): number { - return globalScene.currentBattle.waveIndex < 2000 ? super.getMaxStackCount() : 999; - } -} - -export class EnemyTurnHealModifier extends EnemyPersistentModifier { - public healPercent: number; - - constructor(type: ModifierType, _healPercent: number, stackCount?: number) { - super(type, stackCount); - - // Hardcode temporarily - this.healPercent = 2; - } - - match(modifier: Modifier): boolean { - return modifier instanceof EnemyTurnHealModifier; - } - - clone(): EnemyTurnHealModifier { - return new EnemyTurnHealModifier(this.type, this.healPercent, this.stackCount); - } - - getArgs(): any[] { - return [this.healPercent]; - } - - /** - * 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()) { - globalScene.phaseManager.unshiftNew( - "PokemonHealPhase", - enemyPokemon.getBattlerIndex(), - Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), - i18next.t("modifier:enemyTurnHealApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon), - }), - true, - false, - false, - false, - true, - ); - return true; - } - - return false; - } - - getMaxStackCount(): number { - return 10; - } -} - -export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifier { - public effect: StatusEffect; - public chance: number; - - constructor(type: ModifierType, effect: StatusEffect, _chancePercent: number, stackCount?: number) { - super(type, stackCount); - - this.effect = effect; - // Hardcode temporarily - this.chance = 0.025 * (this.effect === StatusEffect.BURN || this.effect === StatusEffect.POISON ? 2 : 1); - } - - match(modifier: Modifier): boolean { - return modifier instanceof EnemyAttackStatusEffectChanceModifier && modifier.effect === this.effect; - } - - clone(): EnemyAttackStatusEffectChanceModifier { - return new EnemyAttackStatusEffectChanceModifier(this.type, this.effect, this.chance * 100, this.stackCount); - } - - getArgs(): any[] { - return [this.effect, this.chance * 100]; - } - - /** - * 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 (randSeedFloat() <= this.chance * this.getStackCount()) { - return enemyPokemon.trySetStatus(this.effect, true); - } - - return false; - } - - getMaxStackCount(): number { - return 10; - } -} - -export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier { - public chance: number; - - constructor(type: ModifierType, _chancePercent: number, stackCount?: number) { - super(type, stackCount); - - //Hardcode temporarily - this.chance = 0.025; - } - - match(modifier: Modifier): boolean { - return modifier instanceof EnemyStatusEffectHealChanceModifier; - } - - clone(): EnemyStatusEffectHealChanceModifier { - return new EnemyStatusEffectHealChanceModifier(this.type, this.chance * 100, this.stackCount); - } - - getArgs(): any[] { - return [this.chance * 100]; - } - - /** - * Applies {@linkcode EnemyStatusEffectHealChanceModifier} to randomly heal status. - * @param enemyPokemon - The {@linkcode Pokemon} to heal - * @returns `true` if the {@linkcode Pokemon} was healed - */ - override apply(enemyPokemon: Pokemon): boolean { - if (!enemyPokemon.status || randSeedFloat() > this.chance * this.getStackCount()) { - return false; - } - - globalScene.phaseManager.queueMessage( - getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon)), - ); - enemyPokemon.resetStatus(); - enemyPokemon.updateInfo(); - return true; - } - - getMaxStackCount(): number { - return 10; - } -} - -export class EnemyEndureChanceModifier extends EnemyPersistentModifier { - public chance: number; - - constructor(type: ModifierType, _chancePercent?: number, stackCount?: number) { - super(type, stackCount || 10); - - //Hardcode temporarily - this.chance = 2; - } - - match(modifier: Modifier) { - return modifier instanceof EnemyEndureChanceModifier; - } - - clone() { - return new EnemyEndureChanceModifier(this.type, this.chance, this.stackCount); - } - - getArgs(): any[] { - return [this.chance]; - } - - /** - * Applies a chance of enduring a lethal hit of an attack - * @param target the {@linkcode Pokemon} to apply the {@linkcode BattlerTagType.ENDURING} chance to - * @returns `true` if {@linkcode Pokemon} endured - */ - override apply(target: Pokemon): boolean { - if (target.waveData.endured || target.randBattleSeedInt(100) >= this.chance * this.getStackCount()) { - return false; - } - - target.addTag(BattlerTagType.ENDURE_TOKEN, 1); - - target.waveData.endured = true; - - return true; - } - - getMaxStackCount(): number { - return 10; - } -} - -export class EnemyFusionChanceModifier extends EnemyPersistentModifier { - private chance: number; - - constructor(type: ModifierType, chancePercent: number, stackCount?: number) { - super(type, stackCount); - - this.chance = chancePercent / 100; - } - - match(modifier: Modifier) { - return modifier instanceof EnemyFusionChanceModifier && modifier.chance === this.chance; - } - - clone() { - return new EnemyFusionChanceModifier(this.type, this.chance * 100, this.stackCount); - } - - getArgs(): any[] { - return [this.chance * 100]; - } - - /** - * 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 (randSeedFloat() > this.chance * this.getStackCount()) { - return false; - } - - isFusion.value = true; - - return true; - } - - getMaxStackCount(): number { - return 10; - } } /** @@ -3765,39 +553,20 @@ export class EnemyFusionChanceModifier extends EnemyPersistentModifier { * - The enemy * @param isPlayer {@linkcode boolean} for whether the player (`true`) or enemy (`false`) is being overridden */ -export function overrideModifiers(isPlayer = true): void { - const modifiersOverride: ModifierOverride[] = isPlayer - ? Overrides.STARTING_MODIFIER_OVERRIDE - : Overrides.OPP_MODIFIER_OVERRIDE; - if (!modifiersOverride || modifiersOverride.length === 0 || !globalScene) { +export function overrideTrainerItems(isPlayer = true): void { + const trainerItemsOverride: TrainerItemConfiguration = isPlayer + ? Overrides.STARTING_TRAINER_ITEMS_OVERRIDE + : Overrides.OPP_TRAINER_ITEMS_OVERRIDE; + if (!trainerItemsOverride || trainerItemsOverride.length === 0 || !globalScene) { return; } // If it's the opponent, clear all of their current modifiers to avoid stacking if (!isPlayer) { - globalScene.clearEnemyModifiers(); + globalScene.clearEnemyItems(); } - for (const item of modifiersOverride) { - const modifierFunc = modifierTypes[item.name]; - let modifierType: ModifierType | null = modifierFunc(); - - if (modifierType.is("ModifierTypeGenerator")) { - const pregenArgs = "type" in item && item.type !== null ? [item.type] : undefined; - modifierType = modifierType.generateType([], pregenArgs); - } - - const modifier = modifierType && (modifierType.withIdFromFunc(modifierFunc).newModifier() as PersistentModifier); - if (modifier) { - modifier.stackCount = item.count || 1; - - if (isPlayer) { - globalScene.addModifier(modifier, true, false, false, true); - } else { - globalScene.addEnemyModifier(modifier, true, true); - } - } - } + globalScene.assignTrainerItemsFromConfiguration(trainerItemsOverride, isPlayer); } /** @@ -3808,7 +577,7 @@ export function overrideModifiers(isPlayer = true): void { * @param isPlayer {@linkcode boolean} for whether the {@linkcode pokemon} is the player's (`true`) or an enemy (`false`) */ export function overrideHeldItems(pokemon: Pokemon, isPlayer = true): void { - const heldItemsOverride: ModifierOverride[] = isPlayer + const heldItemsOverride: HeldItemConfiguration = isPlayer ? Overrides.STARTING_HELD_ITEMS_OVERRIDE : Overrides.OPP_HELD_ITEMS_OVERRIDE; if (!heldItemsOverride || heldItemsOverride.length === 0 || !globalScene) { @@ -3816,31 +585,10 @@ export function overrideHeldItems(pokemon: Pokemon, isPlayer = true): void { } if (!isPlayer) { - globalScene.clearEnemyHeldItemModifiers(pokemon); + pokemon.heldItemManager.clearItems(); } - for (const item of heldItemsOverride) { - const modifierFunc = modifierTypes[item.name]; - let modifierType: ModifierType | null = modifierFunc(); - const qty = item.count || 1; - - if (modifierType.is("ModifierTypeGenerator")) { - const pregenArgs = "type" in item && item.type !== null ? [item.type] : undefined; - modifierType = modifierType.generateType([], pregenArgs); - } - - const heldItemModifier = - modifierType && (modifierType.withIdFromFunc(modifierFunc).newModifier(pokemon) as PokemonHeldItemModifier); - if (heldItemModifier) { - heldItemModifier.pokemonId = pokemon.id; - heldItemModifier.stackCount = qty; - if (isPlayer) { - globalScene.addModifier(heldItemModifier, true, false, false, true); - } else { - globalScene.addEnemyModifier(heldItemModifier, true, true); - } - } - } + assignItemsFromConfiguration(heldItemsOverride, pokemon); } /** @@ -3851,42 +599,9 @@ export function overrideHeldItems(pokemon: Pokemon, isPlayer = true): void { * requiring modifier types to be imported in every file. */ const ModifierClassMap = Object.freeze({ - PersistentModifier, ConsumableModifier, AddPokeballModifier, AddVoucherModifier, - LapsingPersistentModifier, - DoubleBattleChanceBoosterModifier, - TempStatStageBoosterModifier, - TempCritBoosterModifier, - MapModifier, - MegaEvolutionAccessModifier, - GigantamaxAccessModifier, - TerastallizeAccessModifier, - PokemonHeldItemModifier, - LapsingPokemonHeldItemModifier, - BaseStatModifier, - EvoTrackerModifier, - PokemonBaseStatTotalModifier, - PokemonBaseStatFlatModifier, - PokemonIncrementingStatModifier, - StatBoosterModifier, - SpeciesStatBoosterModifier, - CritBoosterModifier, - SpeciesCritBoosterModifier, - AttackTypeBoosterModifier, - SurviveDamageModifier, - BypassSpeedChanceModifier, - FlinchChanceModifier, - TurnHealModifier, - TurnStatusEffectModifier, - HitHealModifier, - LevelIncrementBoosterModifier, - BerryModifier, - PreserveBerryModifier, - PokemonInstantReviveModifier, - ResetNegativeStatStageModifier, - FieldEffectModifier, ConsumablePokemonModifier, TerrastalizeModifier, PokemonHpRestoreModifier, @@ -3901,43 +616,6 @@ const ModifierClassMap = Object.freeze({ RememberMoveModifier, EvolutionItemModifier, FusePokemonModifier, - MultipleParticipantExpBonusModifier, - HealingBoosterModifier, - ExpBoosterModifier, - PokemonExpBoosterModifier, - ExpShareModifier, - ExpBalanceModifier, - PokemonFriendshipBoosterModifier, - PokemonNatureWeightModifier, - PokemonMoveAccuracyBoosterModifier, - PokemonMultiHitModifier, - PokemonFormChangeItemModifier, - MoneyRewardModifier, - DamageMoneyRewardModifier, - MoneyInterestModifier, - HiddenAbilityRateBoosterModifier, - ShinyRateBoosterModifier, - CriticalCatchChanceBoosterModifier, - LockModifierTiersModifier, - HealShopCostModifier, - BoostBugSpawnModifier, - SwitchEffectTransferModifier, - HeldItemTransferModifier, - TurnHeldItemTransferModifier, - ContactHeldItemTransferChanceModifier, - IvScannerModifier, - ExtraModifierModifier, - TempExtraModifierModifier, - EnemyPersistentModifier, - EnemyDamageMultiplierModifier, - EnemyDamageBoosterModifier, - EnemyDamageReducerModifier, - EnemyTurnHealModifier, - EnemyAttackStatusEffectChanceModifier, - EnemyStatusEffectHealChanceModifier, - EnemyEndureChanceModifier, - EnemyFusionChanceModifier, - MoneyMultiplierModifier, }); export type ModifierConstructorMap = typeof ModifierClassMap; diff --git a/src/overrides.ts b/src/overrides.ts index ea50e9b088c..fa8e342b161 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -23,6 +23,8 @@ import { TrainerType } from "#enums/trainer-type"; import { Unlockables } from "#enums/unlockables"; import { VariantTier } from "#enums/variant-tier"; import { WeatherType } from "#enums/weather-type"; +import { HeldItemConfiguration } from "./items/held-item-data-types"; +import { TrainerItemConfiguration } from "./items/trainer-item-data-types"; /** * This comment block exists to prevent IDEs from automatically removing unused imports @@ -271,18 +273,15 @@ class DefaultOverrides { * STARTING_HELD_ITEM_OVERRIDE = [{name: "BERRY"}] * ``` */ - readonly STARTING_MODIFIER_OVERRIDE: ModifierOverride[] = []; - /** - * Override array of {@linkcode ModifierOverride}s used to provide modifiers to enemies. - * - * Note that any previous modifiers are cleared. - */ - readonly OPP_MODIFIER_OVERRIDE: ModifierOverride[] = []; + /** Override array of {@linkcode ModifierOverride}s used to provide held items to first party member when starting a new game. */ + readonly STARTING_TRAINER_ITEMS_OVERRIDE: TrainerItemConfiguration = []; + /** Override array of {@linkcode ModifierOverride}s used to provide held items to enemies on spawn. */ + readonly OPP_TRAINER_ITEMS_OVERRIDE: TrainerItemConfiguration = []; /** Override array of {@linkcode ModifierOverride}s used to provide held items to first party member when starting a new game. */ - readonly STARTING_HELD_ITEMS_OVERRIDE: ModifierOverride[] = []; + readonly STARTING_HELD_ITEMS_OVERRIDE: HeldItemConfiguration = []; /** Override array of {@linkcode ModifierOverride}s used to provide held items to enemies on spawn. */ - readonly OPP_HELD_ITEMS_OVERRIDE: ModifierOverride[] = []; + readonly OPP_HELD_ITEMS_OVERRIDE: HeldItemConfiguration = []; /** * Override array of {@linkcode ModifierOverride}s used to replace the generated item rolls after a wave. diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-buff-modifier-phase.ts index 218a3a653ff..fcbfe096d52 100644 --- a/src/phases/add-enemy-buff-modifier-phase.ts +++ b/src/phases/add-enemy-buff-modifier-phase.ts @@ -1,9 +1,7 @@ -import { ModifierTier } from "#enums/modifier-tier"; -import { regenerateModifierPoolThresholds, getEnemyBuffModifierForWave } from "#app/modifier/modifier-type"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; -import { EnemyPersistentModifier } from "#app/modifier/modifier"; +import { RewardTier } from "#enums/reward-tier"; import { Phase } from "#app/phase"; import { globalScene } from "#app/global-scene"; +import { assignEnemyBuffTokenForWave } from "#app/items/trainer-item-pool"; export class AddEnemyBuffModifierPhase extends Phase { public readonly phaseName = "AddEnemyBuffModifierPhase"; @@ -11,26 +9,13 @@ export class AddEnemyBuffModifierPhase extends Phase { super.start(); const waveIndex = globalScene.currentBattle.waveIndex; - const tier = !(waveIndex % 1000) - ? ModifierTier.ULTRA - : !(waveIndex % 250) - ? ModifierTier.GREAT - : ModifierTier.COMMON; - - regenerateModifierPoolThresholds(globalScene.getEnemyParty(), ModifierPoolType.ENEMY_BUFF); + const tier = !(waveIndex % 1000) ? RewardTier.ULTRA : !(waveIndex % 250) ? RewardTier.GREAT : RewardTier.COMMON; const count = Math.ceil(waveIndex / 250); for (let i = 0; i < count; i++) { - globalScene.addEnemyModifier( - getEnemyBuffModifierForWave( - tier, - globalScene.findModifiers(m => m instanceof EnemyPersistentModifier, false), - ), - true, - true, - ); + assignEnemyBuffTokenForWave(tier); } - globalScene.updateModifiers(false, true); + globalScene.updateItems(false); this.end(); } } diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index f4e6725935a..cbef0224cae 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -12,7 +12,6 @@ import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims"; import type { EnemyPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { PokemonPhase } from "#app/phases/pokemon-phase"; import { achvs } from "#app/system/achv"; import type { PartyOption } from "#app/ui/party-ui-handler"; @@ -255,6 +254,7 @@ export class AttemptCapturePhase extends PokemonPhase { }), null, () => { + const heldItemConfig = pokemon.heldItemManager.generateHeldItemConfiguration(); const end = () => { globalScene.phaseManager.unshiftNew("VictoryPhase", this.battlerIndex); globalScene.pokemonInfoContainer.hide(); @@ -265,24 +265,20 @@ export class AttemptCapturePhase extends PokemonPhase { globalScene.addFaintedEnemyScore(pokemon); pokemon.hp = 0; pokemon.trySetStatus(StatusEffect.FAINT); - globalScene.clearEnemyHeldItemModifiers(); pokemon.leaveField(true, true, true); }; const addToParty = (slotIndex?: number) => { const newPokemon = pokemon.addToParty(this.pokeballType, slotIndex); - const modifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); if (globalScene.getPlayerParty().filter(p => p.isShiny()).length === PLAYER_PARTY_MAX_SIZE) { globalScene.validateAchv(achvs.SHINY_PARTY); } - Promise.all(modifiers.map(m => globalScene.addModifier(m, true))).then(() => { - globalScene.updateModifiers(true); - removePokemon(); - if (newPokemon) { - newPokemon.loadAssets().then(end); - } else { - end(); - } - }); + globalScene.updateItems(true); + removePokemon(); + if (newPokemon) { + newPokemon.loadAssets().then(end); + } else { + end(); + } }; Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => { if (globalScene.getPlayerParty().length === PLAYER_PARTY_MAX_SIZE) { @@ -307,6 +303,7 @@ export class AttemptCapturePhase extends PokemonPhase { pokemon.variant, pokemon.ivs, pokemon.nature, + heldItemConfig, pokemon, ); globalScene.ui.setMode( diff --git a/src/phases/attempt-run-phase.ts b/src/phases/attempt-run-phase.ts index 3709374287a..78d8d7c482d 100644 --- a/src/phases/attempt-run-phase.ts +++ b/src/phases/attempt-run-phase.ts @@ -40,8 +40,6 @@ export class AttemptRunPhase extends FieldPhase { onComplete: () => enemyField.forEach(enemyPokemon => enemyPokemon.destroy()), }); - globalScene.clearEnemyHeldItemModifiers(); - enemyField.forEach(enemyPokemon => { enemyPokemon.hideInfo().then(() => enemyPokemon.destroy()); enemyPokemon.hp = 0; diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index 297e20cb445..900fd9eeb57 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -1,6 +1,5 @@ import { globalScene } from "#app/global-scene"; import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; -import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier"; import { BattlePhase } from "./battle-phase"; export class BattleEndPhase extends BattlePhase { @@ -72,7 +71,6 @@ export class BattleEndPhase extends BattlePhase { globalScene.currentBattle.pickUpScatteredMoney(); } - globalScene.clearEnemyHeldItemModifiers(); for (const p of globalScene.getEnemyParty()) { try { p.destroy(); @@ -81,20 +79,9 @@ export class BattleEndPhase extends BattlePhase { } } - const lapsingModifiers = globalScene.findModifiers( - m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier, - ) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[]; - for (const m of lapsingModifiers) { - const args: any[] = []; - if (m instanceof LapsingPokemonHeldItemModifier) { - args.push(globalScene.getPokemonById(m.pokemonId)); - } - if (!m.lapse(...args)) { - globalScene.removeModifier(m); - } - } + globalScene.trainerItems.lapseItems(); - globalScene.updateModifiers(); + globalScene.updateItems(); this.end(); } } diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index 61124a7cda8..2b361427d7c 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -1,13 +1,14 @@ import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { CommonAnim } from "#enums/move-anims-common"; -import { BerryUsedEvent } from "#app/events/battle-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { BerryModifier } from "#app/modifier/modifier"; import i18next from "i18next"; import { BooleanHolder } from "#app/utils/common"; import { FieldPhase } from "./field-phase"; import { globalScene } from "#app/global-scene"; import type Pokemon from "#app/field/pokemon"; +import { allHeldItems, applyHeldItems } from "#app/items/all-held-items"; +import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemCategoryId, isItemInCategory } from "#enums/held-item-id"; /** * The phase after attacks where the pokemon eat berries. @@ -31,10 +32,9 @@ export class BerryPhase extends FieldPhase { * @param pokemon - The {@linkcode Pokemon} to check */ eatBerries(pokemon: Pokemon): void { - const hasUsableBerry = !!globalScene.findModifier( - m => m instanceof BerryModifier && m.shouldApply(pokemon), - pokemon.isPlayer(), - ); + const hasUsableBerry = pokemon.getHeldItems().some(m => { + return isItemInCategory(m, HeldItemCategoryId.BERRY) && allHeldItems[m].shouldApply(pokemon); + }); if (!hasUsableBerry) { return; @@ -59,15 +59,8 @@ export class BerryPhase extends FieldPhase { CommonAnim.USE_ITEM, ); - for (const berryModifier of globalScene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon)) { - // No need to track berries being eaten; already done inside applyModifiers - if (berryModifier.consumed) { - berryModifier.consumed = false; - pokemon.loseHeldItem(berryModifier); - } - globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier)); - } - globalScene.updateModifiers(pokemon.isPlayer()); + applyHeldItems(HELD_ITEM_EFFECT.BERRY, { pokemon: pokemon }); + globalScene.updateItems(pokemon.isPlayer()); // AbilityId.CHEEK_POUCH only works once per round of nom noms applyAbAttrs("HealFromBerryUseAbAttr", { pokemon }); diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 52c2b2e465d..ceb60a303b9 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -14,9 +14,6 @@ import { EncounterPhaseEvent } from "#app/events/battle-scene"; import type Pokemon from "#app/field/pokemon"; import { FieldPosition } from "#enums/field-position"; import { getPokemonNameWithAffix } from "#app/messages"; -import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier"; -import { regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; import Overrides from "#app/overrides"; import { BattlePhase } from "#app/phases/battle-phase"; import { achvs } from "#app/system/achv"; @@ -28,10 +25,11 @@ import { BiomeId } from "#enums/biome-id"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { PlayerGender } from "#enums/player-gender"; import { SpeciesId } from "#enums/species-id"; -import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier"; +import { overrideHeldItems, overrideTrainerItems } from "#app/modifier/modifier"; import i18next from "i18next"; import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/constants"; import { getNatureName } from "#app/data/nature"; +import { TrainerItemId } from "#enums/trainer-item-id"; export class EncounterPhase extends BattlePhase { // Union type is necessary as this is subclassed, and typescript will otherwise complain @@ -107,7 +105,7 @@ export class EncounterPhase extends BattlePhase { let enemySpecies = globalScene.randomSpecies(battle.waveIndex, level, true); // If player has golden bug net, rolls 10% chance to replace non-boss wave wild species from the golden bug net bug pool if ( - globalScene.findModifier(m => m instanceof BoostBugSpawnModifier) && + globalScene.trainerItems.hasItem(TrainerItemId.GOLDEN_BUG_NET) && !globalScene.gameMode.isBoss(battle.waveIndex) && globalScene.arena.biomeType !== BiomeId.END && randSeedInt(10) === 0 @@ -271,12 +269,8 @@ export class EncounterPhase extends BattlePhase { if (!this.loaded && battle.battleType !== BattleType.MYSTERY_ENCOUNTER) { // generate modifiers for MEs, overriding prior ones as applicable - regenerateModifierPoolThresholds( - globalScene.getEnemyField(), - battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, - ); - globalScene.generateEnemyModifiers(); - overrideModifiers(false); + globalScene.generateEnemyItems(); + overrideTrainerItems(false); for (const enemy of globalScene.getEnemyField()) { overrideHeldItems(enemy, false); @@ -311,7 +305,7 @@ export class EncounterPhase extends BattlePhase { doEncounter() { globalScene.playBgm(undefined, true); - globalScene.updateModifiers(false); + globalScene.updateItems(false); globalScene.setFieldScale(1); const { battleType, waveIndex } = globalScene.currentBattle; @@ -349,6 +343,7 @@ export class EncounterPhase extends BattlePhase { } }, }); + globalScene.updateItems(false); const encounterIntroVisuals = globalScene.currentBattle?.mysteryEncounter?.introVisuals; if (encounterIntroVisuals) { @@ -543,22 +538,6 @@ export class EncounterPhase extends BattlePhase { if (enemyPokemon.isShiny(true)) { globalScene.phaseManager.unshiftNew("ShinySparklePhase", BattlerIndex.ENEMY + e); } - /** This sets Eternatus' held item to be untransferrable, preventing it from being stolen */ - if ( - enemyPokemon.species.speciesId === SpeciesId.ETERNATUS && - (globalScene.gameMode.isBattleClassicFinalBoss(globalScene.currentBattle.waveIndex) || - globalScene.gameMode.isEndlessMajorBoss(globalScene.currentBattle.waveIndex)) - ) { - const enemyMBH = globalScene.findModifier( - m => m instanceof TurnHeldItemTransferModifier, - false, - ) as TurnHeldItemTransferModifier; - if (enemyMBH) { - globalScene.removeModifier(enemyMBH, true); - enemyMBH.setTransferrableFalse(); - globalScene.addEnemyModifier(enemyMBH); - } - } }); if (![BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(globalScene.currentBattle.battleType)) { @@ -585,8 +564,7 @@ export class EncounterPhase extends BattlePhase { }, ), ); - const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); - if (ivScannerModifier) { + if (globalScene.trainerItems.hasItem(TrainerItemId.IV_SCANNER)) { enemyField.map(p => globalScene.phaseManager.pushNew("ScanIvsPhase", p.getBattlerIndex())); } } diff --git a/src/phases/exp-phase.ts b/src/phases/exp-phase.ts index 74768e86186..123d03de62b 100644 --- a/src/phases/exp-phase.ts +++ b/src/phases/exp-phase.ts @@ -1,9 +1,9 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { ExpBoosterModifier } from "#app/modifier/modifier"; import i18next from "i18next"; import { NumberHolder } from "#app/utils/common"; import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; export class ExpPhase extends PlayerPartyMemberPokemonPhase { public readonly phaseName = "ExpPhase"; @@ -20,7 +20,7 @@ export class ExpPhase extends PlayerPartyMemberPokemonPhase { const pokemon = this.getPokemon(); const exp = new NumberHolder(this.expValue); - globalScene.applyModifiers(ExpBoosterModifier, true, exp); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.EXP_BOOSTER, { numberHolder: exp }); exp.value = Math.floor(exp.value); globalScene.ui.showText( i18next.t("battle:expGain", { diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index c2658b62b23..9dc54ef4642 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -14,13 +14,14 @@ import { PokemonMove } from "#app/data/moves/pokemon-move"; import { HitResult } from "#enums/hit-result"; import type { PlayerPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { SwitchType } from "#enums/switch-type"; import i18next from "i18next"; import { PokemonPhase } from "./pokemon-phase"; import { isNullOrUndefined } from "#app/utils/common"; import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters"; import { BattlerTagType } from "#enums/battler-tag-type"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { HELD_ITEM_EFFECT } from "#app/items/held-item"; export class FaintPhase extends PokemonPhase { public readonly phaseName = "FaintPhase"; @@ -54,17 +55,7 @@ export class FaintPhase extends PokemonPhase { faintPokemon.resetSummonData(); if (!this.preventInstantRevive) { - const instantReviveModifier = globalScene.applyModifier( - PokemonInstantReviveModifier, - this.player, - faintPokemon, - ) as PokemonInstantReviveModifier; - - if (instantReviveModifier) { - faintPokemon.loseHeldItem(instantReviveModifier); - globalScene.updateModifiers(this.player); - return this.end(); - } + applyHeldItems(HELD_ITEM_EFFECT.INSTANT_REVIVE, { pokemon: faintPokemon }); } /** diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 3f92f26b9b9..eaee1e7da23 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -18,7 +18,6 @@ import { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; import i18next from "i18next"; import type { SessionSaveData } from "#app/system/game-data"; -import PersistentModifierData from "#app/system/modifier-data"; import PokemonData from "#app/system/pokemon-data"; import ChallengeData from "#app/system/challenge-data"; import TrainerData from "#app/system/trainer-data"; @@ -285,12 +284,10 @@ export class GameOverPhase extends BattlePhase { gameMode: globalScene.gameMode.modeId, party: globalScene.getPlayerParty().map(p => new PokemonData(p)), enemyParty: globalScene.getEnemyParty().map(p => new PokemonData(p)), - modifiers: preWaveSessionData - ? preWaveSessionData.modifiers - : globalScene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)), - enemyModifiers: preWaveSessionData - ? preWaveSessionData.enemyModifiers - : globalScene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)), + trainerItems: preWaveSessionData ? preWaveSessionData.trainerItems : globalScene.trainerItems.generateSaveData(), + enemyTrainerItems: preWaveSessionData + ? preWaveSessionData.enemyTrainerItems + : globalScene.enemyTrainerItems.generateSaveData(), arena: new ArenaData(globalScene.arena), pokeballCounts: globalScene.pokeballCounts, money: Math.floor(globalScene.money), diff --git a/src/phases/modifier-reward-phase.ts b/src/phases/modifier-reward-phase.ts index 29bbe215fc0..18f0ba0ea17 100644 --- a/src/phases/modifier-reward-phase.ts +++ b/src/phases/modifier-reward-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import type { ModifierType } from "#app/modifier/modifier-type"; +import { TrainerItemReward, type ModifierType } from "#app/modifier/modifier-type"; import type { ModifierTypeFunc } from "#app/@types/modifier-types"; import { getModifierType } from "#app/utils/modifier-utils"; import i18next from "i18next"; @@ -26,12 +26,16 @@ export class ModifierRewardPhase extends BattlePhase { doReward(): Promise { return new Promise(resolve => { - const newModifier = this.modifierType.newModifier(); - globalScene.addModifier(newModifier); + if (this.modifierType instanceof TrainerItemReward) { + this.modifierType.apply(); + } else { + const newModifier = this.modifierType.newModifier(); + globalScene.addModifier(newModifier); + } globalScene.playSound("item_fanfare"); globalScene.ui.showText( i18next.t("battle:rewardGain", { - modifierName: newModifier?.type.name, + modifierName: this.modifierType.name, }), null, () => resolve(), diff --git a/src/phases/money-reward-phase.ts b/src/phases/money-reward-phase.ts index 52cb9ecb3ff..6fff53542ae 100644 --- a/src/phases/money-reward-phase.ts +++ b/src/phases/money-reward-phase.ts @@ -1,9 +1,9 @@ import { globalScene } from "#app/global-scene"; import { ArenaTagType } from "#app/enums/arena-tag-type"; -import { MoneyMultiplierModifier } from "#app/modifier/modifier"; import i18next from "i18next"; import { NumberHolder } from "#app/utils/common"; import { BattlePhase } from "./battle-phase"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; export class MoneyRewardPhase extends BattlePhase { public readonly phaseName = "MoneyRewardPhase"; @@ -18,7 +18,7 @@ export class MoneyRewardPhase extends BattlePhase { start() { const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); - globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); if (globalScene.arena.getTag(ArenaTagType.HAPPY_HOUR)) { moneyAmount.value *= 2; diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index fb421d7a25f..21f869110d5 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -28,15 +28,6 @@ import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result"; import { HitResult } from "#enums/hit-result"; import { getPokemonNameWithAffix } from "#app/messages"; -import { - ContactHeldItemTransferChanceModifier, - DamageMoneyRewardModifier, - EnemyAttackStatusEffectChanceModifier, - EnemyEndureChanceModifier, - FlinchChanceModifier, - HitHealModifier, - PokemonMultiHitModifier, -} from "#app/modifier/modifier"; import { PokemonPhase } from "#app/phases/pokemon-phase"; import { BooleanHolder, isNullOrUndefined, NumberHolder } from "#app/utils/common"; import type { nil } from "#app/utils/common"; @@ -49,7 +40,10 @@ import { HitCheckResult } from "#enums/hit-check-result"; import type Move from "#app/data/moves/move"; import { isFieldTargeted } from "#app/data/moves/move-utils"; import { DamageAchv } from "#app/system/achv"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { HELD_ITEM_EFFECT } from "#app/items/held-item"; import { isVirtual, isReflected, MoveUseMode } from "#enums/move-use-mode"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; export type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier]; @@ -319,7 +313,7 @@ export class MoveEffectPhase extends PokemonPhase { // If Parental Bond is applicable, add another hit applyAbAttrs("AddSecondStrikeAbAttr", { pokemon: user, move, hitCount }); // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses - globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount); + applyHeldItems(HELD_ITEM_EFFECT.MULTI_HIT, { pokemon: user, moveId: move.id, count: hitCount }); // Set the user's relevant turnData fields to reflect the final hit count user.turnData.hitCount = hitCount.value; user.turnData.hitsLeft = hitCount.value; @@ -419,7 +413,7 @@ export class MoveEffectPhase extends PokemonPhase { globalScene.phaseManager.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); } - globalScene.applyModifiers(HitHealModifier, this.player, user); + applyHeldItems(HELD_ITEM_EFFECT.HIT_HEAL, { pokemon: user }); this.getTargets().forEach(target => { target.turnData.moveEffectiveness = null; }); @@ -461,7 +455,7 @@ export class MoveEffectPhase extends PokemonPhase { !this.move.hitsSubstitute(user, target) ) { const flinched = new BooleanHolder(false); - globalScene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); + applyHeldItems(HELD_ITEM_EFFECT.FLINCH_CHANCE, { pokemon: user, flinched: flinched }); if (flinched.value) { target.addTag(BattlerTagType.FLINCHED, undefined, this.move.id, user.id); } @@ -862,7 +856,7 @@ export class MoveEffectPhase extends PokemonPhase { if (isBlockedBySubstitute) { substitute.hp -= dmg; } else if (!target.isPlayer() && dmg >= target.hp) { - globalScene.applyModifiers(EnemyEndureChanceModifier, false, target); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.ENEMY_ENDURE_CHANCE, { pokemon: target }); } const damage = isBlockedBySubstitute @@ -906,7 +900,7 @@ export class MoveEffectPhase extends PokemonPhase { }); if (user.isPlayer() && target.isEnemy()) { - globalScene.applyModifiers(DamageMoneyRewardModifier, true, user, new NumberHolder(damage)); + applyHeldItems(HELD_ITEM_EFFECT.DAMAGE_MONEY_REWARD, { pokemon: user, damage: damage }); } return [result, isCritical]; @@ -1014,12 +1008,12 @@ export class MoveEffectPhase extends PokemonPhase { // We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens if (!user.isPlayer() && this.move.is("AttackMove")) { - globalScene.applyShuffledModifiers(EnemyAttackStatusEffectChanceModifier, false, target); + globalScene.applyShuffledStatusTokens(target); } // Apply Grip Claw's chance to steal an item from the target if (this.move.is("AttackMove")) { - globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); + applyHeldItems(HELD_ITEM_EFFECT.CONTACT_ITEM_STEAL_CHANCE, { pokemon: user, target: target }); } } } diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index 9aae796211f..1adb8f4d5ab 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -13,10 +13,10 @@ import { getCharVariantFromDialogue } from "../data/dialogue"; import type { OptionSelectSettings } from "../data/mystery-encounters/utils/encounter-phase-utils"; import { transitionMysteryEncounterIntroVisuals } from "../data/mystery-encounters/utils/encounter-phase-utils"; import { TrainerSlot } from "#enums/trainer-slot"; -import { IvScannerModifier } from "../modifier/modifier"; import { Phase } from "../phase"; import { UiMode } from "#enums/ui-mode"; import { isNullOrUndefined, randSeedItem } from "#app/utils/common"; +import { TrainerItemId } from "#enums/trainer-item-id"; /** * Will handle (in order): @@ -414,8 +414,7 @@ export class MysteryEncounterBattlePhase extends Phase { // PostSummon and ShinySparkle phases are handled by SummonPhase if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE) { - const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); - if (ivScannerModifier) { + if (globalScene.trainerItems.hasItem(TrainerItemId.IV_SCANNER)) { enemyField.map(p => globalScene.phaseManager.pushNew("ScanIvsPhase", p.getBattlerIndex())); } } diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts index cf6cf40a923..1f758133d87 100644 --- a/src/phases/pokemon-heal-phase.ts +++ b/src/phases/pokemon-heal-phase.ts @@ -5,13 +5,13 @@ import { getStatusEffectHealText } from "#app/data/status-effect"; import { StatusEffect } from "#app/enums/status-effect"; import { HitResult } from "#enums/hit-result"; import { getPokemonNameWithAffix } from "#app/messages"; -import { HealingBoosterModifier } from "#app/modifier/modifier"; import { HealAchv } from "#app/system/achv"; import i18next from "i18next"; import { NumberHolder } from "#app/utils/common"; import { CommonAnimPhase } from "./common-anim-phase"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import type { HealBlockTag } from "#app/data/battler-tags"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; export class PokemonHealPhase extends CommonAnimPhase { public readonly phaseName = "PokemonHealPhase"; @@ -75,7 +75,7 @@ export class PokemonHealPhase extends CommonAnimPhase { if (healOrDamage) { const hpRestoreMultiplier = new NumberHolder(1); if (!this.revive) { - globalScene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HEALING_BOOSTER, { numberHolder: hpRestoreMultiplier }); } const healAmount = new NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value)); if (healAmount.value < 0) { diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index e8b4946b6d1..d87640d14e6 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -1,11 +1,11 @@ import { globalScene } from "#app/global-scene"; import { biomeLinks, getBiomeName } from "#app/data/balance/biomes"; import { BiomeId } from "#enums/biome-id"; -import { MoneyInterestModifier, MapModifier } from "#app/modifier/modifier"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { UiMode } from "#enums/ui-mode"; import { BattlePhase } from "./battle-phase"; import { randSeedInt } from "#app/utils/common"; +import { TrainerItemId } from "#enums/trainer-item-id"; export class SelectBiomePhase extends BattlePhase { public readonly phaseName = "SelectBiomePhase"; @@ -19,7 +19,6 @@ export class SelectBiomePhase extends BattlePhase { const setNextBiome = (nextBiome: BiomeId) => { if (nextWaveIndex % 10 === 1) { - globalScene.applyModifiers(MoneyInterestModifier, true); globalScene.phaseManager.unshiftNew("PartyHealPhase", false); } globalScene.phaseManager.unshiftNew("SwitchBiomePhase", nextBiome); @@ -39,7 +38,7 @@ export class SelectBiomePhase extends BattlePhase { .filter(b => !Array.isArray(b) || !randSeedInt(b[1])) .map(b => (!Array.isArray(b) ? b : b[0])); - if (biomes.length > 1 && globalScene.findModifier(m => m instanceof MapModifier)) { + if (biomes.length > 1 && globalScene.trainerItems.hasItem(TrainerItemId.MAP)) { const biomeSelectItems = biomes.map(b => { const ret: OptionSelectItem = { label: getBiomeName(b), diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 53e1f5bc282..50b7594ca3e 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import type { ModifierTier } from "#enums/modifier-tier"; +import type { RewardTier } from "#enums/reward-tier"; import type { ModifierTypeOption, ModifierType } from "#app/modifier/modifier-type"; import { regenerateModifierPoolThresholds, @@ -12,15 +12,12 @@ import { PokemonPpRestoreModifierType, PokemonPpUpModifierType, getPlayerModifierTypeOptions, + HeldItemReward, + FormChangeItemReward, + TrainerItemReward, } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import type { Modifier } from "#app/modifier/modifier"; -import { - ExtraModifierModifier, - HealShopCostModifier, - PokemonHeldItemModifier, - TempExtraModifierModifier, -} from "#app/modifier/modifier"; import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler"; @@ -30,13 +27,14 @@ import { BattlePhase } from "./battle-phase"; import Overrides from "#app/overrides"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { isNullOrUndefined, NumberHolder } from "#app/utils/common"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; export type ModifierSelectCallback = (rowCursor: number, cursor: number) => boolean; export class SelectModifierPhase extends BattlePhase { public readonly phaseName = "SelectModifierPhase"; private rerollCount: number; - private modifierTiers?: ModifierTier[]; + private modifierTiers?: RewardTier[]; private customModifierSettings?: CustomModifierSettings; private isCopy: boolean; @@ -44,7 +42,7 @@ export class SelectModifierPhase extends BattlePhase { constructor( rerollCount = 0, - modifierTiers?: ModifierTier[], + modifierTiers?: RewardTier[], customModifierSettings?: CustomModifierSettings, isCopy = false, ) { @@ -154,7 +152,7 @@ export class SelectModifierPhase extends BattlePhase { const modifierType = shopOption.type; // Apply Black Sludge to healing item cost const healingItemCost = new NumberHolder(shopOption.cost); - globalScene.applyModifier(HealShopCostModifier, true, healingItemCost); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HEAL_SHOP_COST, { numberHolder: healingItemCost }); const cost = healingItemCost.value; if (globalScene.money < cost && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { @@ -172,11 +170,20 @@ export class SelectModifierPhase extends BattlePhase { modifierSelectCallback: ModifierSelectCallback, ): boolean { if (modifierType instanceof PokemonModifierType) { - if (modifierType instanceof FusePokemonModifierType) { + if (modifierType instanceof HeldItemReward || modifierType instanceof FormChangeItemReward) { + this.openGiveHeldItemMenu(modifierType, modifierSelectCallback); + } else if (modifierType instanceof FusePokemonModifierType) { this.openFusionMenu(modifierType, cost, modifierSelectCallback); } else { this.openModifierMenu(modifierType, cost, modifierSelectCallback); } + } else if (modifierType instanceof TrainerItemReward) { + console.log("WE GOT HERE"); + modifierType.apply(); + globalScene.updateItems(true); + globalScene.ui.clearText(); + globalScene.ui.setMode(UiMode.MESSAGE); + super.end(); } else { this.applyModifier(modifierType.newModifier()!); } @@ -194,7 +201,7 @@ export class SelectModifierPhase extends BattlePhase { globalScene.phaseManager.unshiftNew( "SelectModifierPhase", this.rerollCount + 1, - this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[], + this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as RewardTier[], ); globalScene.ui.clearText(); globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end()); @@ -222,17 +229,15 @@ export class SelectModifierPhase extends BattlePhase { fromSlotIndex !== toSlotIndex && itemIndex > -1 ) { - const itemModifiers = globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id, - ) as PokemonHeldItemModifier[]; - const itemModifier = itemModifiers[itemIndex]; - globalScene.tryTransferHeldItemModifier( - itemModifier, + const items = party[fromSlotIndex].heldItemManager.getTransferableHeldItems(); + const item = items[itemIndex]; + globalScene.tryTransferHeldItem( + item, + party[fromSlotIndex], party[toSlotIndex], true, itemQuantity, undefined, - undefined, false, ); } else { @@ -267,7 +272,7 @@ export class SelectModifierPhase extends BattlePhase { * @param playSound - Whether the 'obtain modifier' sound should be played when adding the modifier. */ private applyModifier(modifier: Modifier, cost = -1, playSound = false): void { - const result = globalScene.addModifier(modifier, false, playSound, undefined, undefined, cost); + const result = globalScene.addModifier(modifier, playSound, undefined, cost); // Queue a copy of this phase when applying a TM or Memory Mushroom. // If the player selects either of these, then escapes out of consuming them, // they are returned to a shop in the same state. @@ -372,11 +377,33 @@ export class SelectModifierPhase extends BattlePhase { ); } + private openGiveHeldItemMenu(reward, modifierSelectCallback) { + const party = globalScene.getPlayerParty(); + const partyUiMode = PartyUiMode.MODIFIER; + globalScene.ui.setModeWithoutClear( + UiMode.PARTY, + partyUiMode, + -1, + (slotIndex: number, _option: PartyOption) => { + if (slotIndex < 6) { + globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { + reward.apply(party[slotIndex]); + globalScene.ui.clearText(); + globalScene.ui.setMode(UiMode.MESSAGE); + super.end(); + }); + } else { + this.resetModifierSelect(modifierSelectCallback); + } + }, + reward.selectFilter, + ); + } + // Function that determines how many reward slots are available private getModifierCount(): number { const modifierCountHolder = new NumberHolder(3); - globalScene.applyModifiers(ExtraModifierModifier, true, modifierCountHolder); - globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCountHolder); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.EXTRA_REWARD, { numberHolder: modifierCountHolder }); // If custom modifiers are specified, overrides default item count if (this.customModifierSettings) { @@ -447,7 +474,7 @@ export class SelectModifierPhase extends BattlePhase { // Apply Black Sludge to reroll cost const modifiedRerollCost = new NumberHolder(baseMultiplier); - globalScene.applyModifier(HealShopCostModifier, true, modifiedRerollCost); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HEAL_SHOP_COST, { numberHolder: modifiedRerollCost }); return modifiedRerollCost.value; } diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index 76247c14ce0..ae77418261b 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -4,7 +4,7 @@ import { ChallengeType } from "#enums/challenge-type"; import { Gender } from "#app/data/gender"; import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; -import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier"; +import { overrideHeldItems, overrideTrainerItems } from "#app/modifier/modifier"; import Overrides from "#app/overrides"; import { Phase } from "#app/phase"; import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler"; @@ -102,7 +102,7 @@ export class SelectStarterPhase extends Phase { party.push(starterPokemon); loadPokemonAssets.push(starterPokemon.loadAssets()); }); - overrideModifiers(); + overrideTrainerItems(); overrideHeldItems(party[0]); Promise.all(loadPokemonAssets).then(() => { SoundFade.fadeOut(globalScene, globalScene.sound.get("menu"), 500, true); diff --git a/src/phases/show-party-exp-bar-phase.ts b/src/phases/show-party-exp-bar-phase.ts index 4849526b639..6c4e01fe840 100644 --- a/src/phases/show-party-exp-bar-phase.ts +++ b/src/phases/show-party-exp-bar-phase.ts @@ -1,9 +1,9 @@ import { globalScene } from "#app/global-scene"; import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; import { ExpNotification } from "#app/enums/exp-notification"; -import { ExpBoosterModifier } from "#app/modifier/modifier"; import { NumberHolder } from "#app/utils/common"; import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { public readonly phaseName = "ShowPartyExpBarPhase"; @@ -20,7 +20,7 @@ export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { const pokemon = this.getPokemon(); const exp = new NumberHolder(this.expValue); - globalScene.applyModifiers(ExpBoosterModifier, true, exp); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.EXP_BOOSTER, { numberHolder: exp }); exp.value = Math.floor(exp.value); const lastLevel = pokemon.level; diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 77fb7b38600..0fd77648b76 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -6,7 +6,6 @@ import { ArenaTagSide } from "#enums/arena-tag-side"; import type { ArenaTag } from "#app/data/arena-tag"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { ResetNegativeStatStageModifier } from "#app/modifier/modifier"; import { handleTutorial, Tutorial } from "#app/tutorial"; import { NumberHolder, BooleanHolder, isNullOrUndefined } from "#app/utils/common"; import i18next from "i18next"; @@ -14,6 +13,8 @@ import { PokemonPhase } from "./pokemon-phase"; import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat"; import { OctolockTag } from "#app/data/battler-tags"; import { ArenaTagType } from "#app/enums/arena-tag-type"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { HELD_ITEM_EFFECT } from "#app/items/held-item"; import type { ConditionalUserFieldProtectStatAbAttrParams, PreStatStageChangeAbAttrParams, @@ -235,16 +236,7 @@ export class StatStageChangePhase extends PokemonPhase { ); if (!existingPhase?.is("StatStageChangePhase")) { // Apply White Herb if needed - const whiteHerb = globalScene.applyModifier( - ResetNegativeStatStageModifier, - this.player, - pokemon, - ) as ResetNegativeStatStageModifier; - // If the White Herb was applied, consume it - if (whiteHerb) { - pokemon.loseHeldItem(whiteHerb); - globalScene.updateModifiers(this.player); - } + applyHeldItems(HELD_ITEM_EFFECT.RESET_NEGATIVE_STAT_STAGE, { pokemon: pokemon, isPlayer: this.player }); } pokemon.updateInfo(); diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index 95e4367d8df..a7dc1f18a6a 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -177,7 +177,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { globalScene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); } addPokeballOpenParticles(pokemon.x, pokemon.y - 16, pokemon.getPokeball(true)); - globalScene.updateModifiers(this.player); + globalScene.updateItems(this.player); globalScene.updateFieldScale(); pokemon.showInfo(); pokemon.playAnim(); @@ -235,7 +235,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { } globalScene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); } - globalScene.updateModifiers(this.player); + globalScene.updateItems(this.player); globalScene.updateFieldScale(); pokemon.showInfo(); pokemon.playAnim(); diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index ccd0681c068..bff6311b744 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -6,12 +6,12 @@ import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-cha import { TrainerSlot } from "#enums/trainer-slot"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { SwitchEffectTransferModifier } from "#app/modifier/modifier"; import { Command } from "#enums/command"; import i18next from "i18next"; import { SummonPhase } from "./summon-phase"; import { SubstituteTag } from "#app/data/battler-tags"; import { SwitchType } from "#enums/switch-type"; +import { HeldItemId } from "#enums/held-item-id"; export class SwitchSummonPhase extends SummonPhase { public readonly phaseName: "SwitchSummonPhase" | "ReturnPhase" = "SwitchSummonPhase"; @@ -138,29 +138,11 @@ export class SwitchSummonPhase extends SummonPhase { ); // If the recipient pokemon lacks a baton, give our baton to it during the swap - if ( - !globalScene.findModifier( - m => - m instanceof SwitchEffectTransferModifier && - (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id, - ) - ) { - const batonPassModifier = globalScene.findModifier( - m => - m instanceof SwitchEffectTransferModifier && - (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id, - ) as SwitchEffectTransferModifier; + if (!switchedInPokemon.heldItemManager.hasItem(HeldItemId.BATON)) { + const batonPassModifier = this.lastPokemon.heldItemManager.hasItem(HeldItemId.BATON); if (batonPassModifier) { - globalScene.tryTransferHeldItemModifier( - batonPassModifier, - switchedInPokemon, - false, - undefined, - undefined, - undefined, - false, - ); + globalScene.tryTransferHeldItem(HeldItemId.BATON, this.lastPokemon, switchedInPokemon, false); } } } diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 5e36081b899..171a776bdb2 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -5,10 +5,6 @@ import { Gender } from "#app/data/gender"; import { getBiomeKey } from "#app/field/arena"; import { GameMode, getGameMode } from "#app/game-mode"; import { GameModes } from "#enums/game-modes"; -import type { Modifier } from "#app/modifier/modifier"; -import { getDailyRunStarterModifiers, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/data/data-lists"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; import { Phase } from "#app/phase"; import type { SessionSaveData } from "#app/system/game-data"; import { Unlockables } from "#enums/unlockables"; @@ -20,6 +16,8 @@ import { isLocal, isLocalServerConnected, isNullOrUndefined } from "#app/utils/c import i18next from "i18next"; import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; +import { assignDailyRunStarterHeldItems } from "#app/items/held-item-pool"; +import { TrainerItemId } from "#enums/trainer-item-id"; export class TitlePhase extends Phase { public readonly phaseName = "TitlePhase"; @@ -238,24 +236,13 @@ export class TitlePhase extends Phase { loadPokemonAssets.push(starterPokemon.loadAssets()); } - regenerateModifierPoolThresholds(party, ModifierPoolType.DAILY_STARTER); + globalScene.trainerItems.add(TrainerItemId.EXP_SHARE, 3); + globalScene.trainerItems.add(TrainerItemId.GOLDEN_EXP_CHARM, 3); + globalScene.trainerItems.add(TrainerItemId.MAP); - const modifiers: Modifier[] = Array(3) - .fill(null) - .map(() => modifierTypes.EXP_SHARE().withIdFromFunc(modifierTypes.EXP_SHARE).newModifier()) - .concat( - Array(3) - .fill(null) - .map(() => modifierTypes.GOLDEN_EXP_CHARM().withIdFromFunc(modifierTypes.GOLDEN_EXP_CHARM).newModifier()), - ) - .concat([modifierTypes.MAP().withIdFromFunc(modifierTypes.MAP).newModifier()]) - .concat(getDailyRunStarterModifiers(party)) - .filter(m => m !== null); + assignDailyRunStarterHeldItems(party); - for (const m of modifiers) { - globalScene.addModifier(m, true, false, false, true); - } - globalScene.updateModifiers(true, true); + globalScene.updateItems(true); Promise.all(loadPokemonAssets).then(() => { globalScene.time.delayedCall(500, () => globalScene.playBgm()); diff --git a/src/phases/trainer-item-reward-phase.ts b/src/phases/trainer-item-reward-phase.ts new file mode 100644 index 00000000000..29bbe215fc0 --- /dev/null +++ b/src/phases/trainer-item-reward-phase.ts @@ -0,0 +1,43 @@ +import { globalScene } from "#app/global-scene"; +import type { ModifierType } from "#app/modifier/modifier-type"; +import type { ModifierTypeFunc } from "#app/@types/modifier-types"; +import { getModifierType } from "#app/utils/modifier-utils"; +import i18next from "i18next"; +import { BattlePhase } from "./battle-phase"; + +export class ModifierRewardPhase extends BattlePhase { + // RibbonModifierRewardPhase extends ModifierRewardPhase and to make typescript happy + // we need to use a union type here + public readonly phaseName: "ModifierRewardPhase" | "RibbonModifierRewardPhase" | "GameOverModifierRewardPhase" = + "ModifierRewardPhase"; + protected modifierType: ModifierType; + + constructor(modifierTypeFunc: ModifierTypeFunc) { + super(); + + this.modifierType = getModifierType(modifierTypeFunc); + } + + start() { + super.start(); + + this.doReward().then(() => this.end()); + } + + doReward(): Promise { + return new Promise(resolve => { + const newModifier = this.modifierType.newModifier(); + globalScene.addModifier(newModifier); + globalScene.playSound("item_fanfare"); + globalScene.ui.showText( + i18next.t("battle:rewardGain", { + modifierName: newModifier?.type.name, + }), + null, + () => resolve(), + null, + true, + ); + }); + } +} diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index b5e56f6d63f..1d1bd3ebb3c 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -5,16 +5,12 @@ import { WeatherType } from "#app/enums/weather-type"; import { TurnEndEvent } from "#app/events/battle-scene"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { - TurnHealModifier, - EnemyTurnHealModifier, - EnemyStatusEffectHealChanceModifier, - TurnStatusEffectModifier, - TurnHeldItemTransferModifier, -} from "#app/modifier/modifier"; import i18next from "i18next"; import { FieldPhase } from "./field-phase"; import { globalScene } from "#app/global-scene"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; export class TurnEndPhase extends FieldPhase { public readonly phaseName = "TurnEndPhase"; @@ -30,7 +26,7 @@ export class TurnEndPhase extends FieldPhase { if (!pokemon.switchOutStatus) { pokemon.lapseTags(BattlerTagLapseType.TURN_END); - globalScene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); + applyHeldItems(HELD_ITEM_EFFECT.TURN_END_HEAL, { pokemon: pokemon }); if (globalScene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { globalScene.phaseManager.unshiftNew( @@ -45,15 +41,16 @@ export class TurnEndPhase extends FieldPhase { } if (!pokemon.isPlayer()) { - globalScene.applyModifiers(EnemyTurnHealModifier, false, pokemon); - globalScene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.ENEMY_HEAL, { pokemon: pokemon }); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.ENEMY_STATUS_HEAL_CHANCE, { pokemon: pokemon }); } applyAbAttrs("PostTurnAbAttr", { pokemon }); } - globalScene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); - globalScene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); + applyHeldItems(HELD_ITEM_EFFECT.TURN_END_STATUS, { pokemon: pokemon }); + + applyHeldItems(HELD_ITEM_EFFECT.TURN_END_ITEM_STEAL, { pokemon: pokemon }); pokemon.tempSummonData.turnCount++; pokemon.tempSummonData.waveTurnCount++; diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index bfae2f06de4..cfdf68727f3 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -3,7 +3,6 @@ import { allMoves } from "#app/data/data-lists"; import { Stat } from "#app/enums/stat"; import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; -import { BypassSpeedChanceModifier } from "#app/modifier/modifier"; import { Command } from "#enums/command"; import { randSeedShuffle, BooleanHolder } from "#app/utils/common"; import { FieldPhase } from "./field-phase"; @@ -11,6 +10,8 @@ import { BattlerIndex } from "#enums/battler-index"; import { TrickRoomTag } from "#app/data/arena-tag"; import { SwitchType } from "#enums/switch-type"; import { globalScene } from "#app/global-scene"; +import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { applyHeldItems } from "#app/items/all-held-items"; export class TurnStartPhase extends FieldPhase { public readonly phaseName = "TurnStartPhase"; @@ -72,7 +73,7 @@ export class TurnStartPhase extends FieldPhase { canCheckHeldItems: canCheckHeldItems, }); if (canCheckHeldItems.value) { - globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); + applyHeldItems(HELD_ITEM_EFFECT.BYPASS_SPEED_CHANCE, { pokemon: p, doBypassSpeed: bypassSpeed }); } battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed; }); diff --git a/src/system/achv.ts b/src/system/achv.ts index 90816ff65c3..d94f05a6c9c 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -1,5 +1,4 @@ import type { Modifier } from "typescript"; -import { TurnHeldItemTransferModifier } from "../modifier/modifier"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import i18next from "i18next"; import { NumberHolder } from "#app/utils/common"; @@ -16,6 +15,8 @@ import type { ConditionFn } from "#app/@types/common"; import { Stat, getShortenedStatKey } from "#app/enums/stat"; import { Challenges } from "#app/enums/challenges"; import { globalScene } from "#app/global-scene"; +import { HeldItemId } from "#enums/held-item-id"; +import type Pokemon from "#app/field/pokemon"; export enum AchvTier { COMMON, @@ -189,6 +190,19 @@ export class ModifierAchv extends Achv { } } +export class HeldItemAchv extends Achv { + constructor( + localizationKey: string, + name: string, + description: string, + iconImage: string, + score: number, + pokemonFunc: (pokemon: Pokemon) => boolean, + ) { + super(localizationKey, name, description, iconImage, score, (args: any[]) => pokemonFunc(args[0] as Pokemon)); + } +} + export class ChallengeAchv extends Achv { constructor( localizationKey: string, @@ -490,13 +504,13 @@ export const achvs = { 25, ).setSecret(true), SPLICE: new Achv("SPLICE", "", "SPLICE.description", "dna_splicers", 10), - MINI_BLACK_HOLE: new ModifierAchv( + MINI_BLACK_HOLE: new HeldItemAchv( "MINI_BLACK_HOLE", "", "MINI_BLACK_HOLE.description", "mini_black_hole", 25, - modifier => modifier instanceof TurnHeldItemTransferModifier, + pokemon => pokemon.heldItemManager.hasItem(HeldItemId.MINI_BLACK_HOLE), ).setSecret(), CATCH_MYTHICAL: new Achv("CATCH_MYTHICAL", "", "CATCH_MYTHICAL.description", "strange_ball", 50).setSecret(), CATCH_SUB_LEGENDARY: new Achv("CATCH_SUB_LEGENDARY", "", "CATCH_SUB_LEGENDARY.description", "rb", 75).setSecret(), diff --git a/src/system/game-data.ts b/src/system/game-data.ts index d5d4256f7d0..f9cf5cfcdf0 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -12,7 +12,6 @@ import { speciesStarterCosts } from "#app/data/balance/starters"; import { randInt, getEnumKeys, isLocal, executeIf, fixedInt, randSeedItem, NumberHolder } from "#app/utils/common"; import Overrides from "#app/overrides"; import PokemonData from "#app/system/pokemon-data"; -import PersistentModifierData from "#app/system/modifier-data"; import ArenaData from "#app/system/arena-data"; import { Unlockables } from "#enums/unlockables"; import { getGameMode } from "#app/game-mode"; @@ -39,9 +38,6 @@ import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/ import type { SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { setSettingKeyboard } from "#app/system/settings/settings-keyboard"; import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; -// biome-ignore lint/performance/noNamespaceImport: Something weird is going on here and I don't want to touch it -import * as Modifier from "#app/modifier/modifier"; -import { StatusEffect } from "#enums/status-effect"; import ChallengeData from "#app/system/challenge-data"; import { Device } from "#enums/devices"; import { GameDataType } from "#enums/game-data-type"; @@ -69,6 +65,7 @@ import { DexAttr } from "#enums/dex-attr"; import { AbilityAttr } from "#enums/ability-attr"; import { defaultStarterSpecies, saveKey } from "#app/constants"; import { encrypt, decrypt } from "#app/utils/data"; +import type { TrainerItemConfiguration, TrainerItemSaveData } from "#app/items/trainer-item-data-types"; function getDataTypeKey(dataType: GameDataType, slotId = 0): string { switch (dataType) { @@ -117,8 +114,8 @@ export interface SessionSaveData { gameMode: GameModes; party: PokemonData[]; enemyParty: PokemonData[]; - modifiers: PersistentModifierData[]; - enemyModifiers: PersistentModifierData[]; + trainerItems: TrainerItemSaveData; + enemyTrainerItems: TrainerItemSaveData; arena: ArenaData; pokeballCounts: PokeballCounts; money: number; @@ -917,8 +914,8 @@ export class GameData { gameMode: globalScene.gameMode.modeId, party: globalScene.getPlayerParty().map(p => new PokemonData(p)), enemyParty: globalScene.getEnemyParty().map(p => new PokemonData(p)), - modifiers: globalScene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)), - enemyModifiers: globalScene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)), + trainerItems: globalScene.trainerItems.generateSaveData(), + enemyTrainerItems: globalScene.enemyTrainerItems.generateSaveData(), arena: new ArenaData(globalScene.arena), pokeballCounts: globalScene.pokeballCounts, money: Math.floor(globalScene.money), @@ -946,6 +943,7 @@ export class GameData { } const handleSessionData = async (sessionDataStr: string) => { try { + console.log(sessionDataStr); const sessionData = this.parseSessionData(sessionDataStr); resolve(sessionData); } catch (err) { @@ -1097,28 +1095,16 @@ export class GameData { } } - if (globalScene.modifiers.length) { - console.warn("Existing modifiers not cleared on session load, deleting..."); - globalScene.modifiers = []; - } - for (const modifierData of sessionData.modifiers) { - const modifier = modifierData.toModifier(Modifier[modifierData.className]); - if (modifier) { - globalScene.addModifier(modifier, true); - } - } - globalScene.updateModifiers(true); + globalScene.trainerItems.clearItems(); + globalScene.assignTrainerItemsFromSaveData(sessionData.trainerItems, true); - for (const enemyModifierData of sessionData.enemyModifiers) { - const modifier = enemyModifierData.toModifier(Modifier[enemyModifierData.className]); - if (modifier) { - globalScene.addEnemyModifier(modifier, true); - } - } - - globalScene.updateModifiers(false); + globalScene.enemyTrainerItems.clearItems(); + globalScene.assignTrainerItemsFromSaveData(sessionData.enemyTrainerItems, false); Promise.all(loadPokemonAssets).then(() => resolve(true)); + + globalScene.updateItems(true); + globalScene.updateItems(false); }; if (sessionData) { initSessionFromData(sessionData); @@ -1262,25 +1248,12 @@ export class GameData { case "trainer": return v ? new TrainerData(v) : null; - case "modifiers": - case "enemyModifiers": { - const ret: PersistentModifierData[] = []; + // TODO: Figure out what to do with this + case "trainerItems": + case "enemyTrainerItems": { + const ret: TrainerItemConfiguration = []; for (const md of v ?? []) { - if (md?.className === "ExpBalanceModifier") { - // Temporarily limit EXP Balance until it gets reworked - md.stackCount = Math.min(md.stackCount, 4); - } - - if ( - md instanceof Modifier.EnemyAttackStatusEffectChanceModifier && - (md.effect === StatusEffect.FREEZE || md.effect === StatusEffect.SLEEP) - ) { - // Discard any old "sleep/freeze chance tokens". - // TODO: make this migrate script - continue; - } - - ret.push(new PersistentModifierData(md, k === "modifiers")); + ret.push(md); } return ret; } @@ -1321,6 +1294,17 @@ export class GameData { if (sync) { globalScene.ui.savingIcon.show(); } + if (useCachedSession) { + console.log("REPARSING!"); + console.log( + decrypt( + localStorage.getItem( + `sessionData${globalScene.sessionSlotId ? globalScene.sessionSlotId : ""}_${loggedInUser?.username}`, + )!, + bypassLogin, + ), + ); + } const sessionData = useCachedSession ? this.parseSessionData( decrypt( @@ -1356,6 +1340,7 @@ export class GameData { `sessionData${globalScene.sessionSlotId ? globalScene.sessionSlotId : ""}_${loggedInUser?.username}`, encrypt(JSON.stringify(sessionData), bypassLogin), ); + console.log(JSON.stringify(sessionData)); console.debug("Session data saved!"); diff --git a/src/system/modifier-data.ts b/src/system/modifier-data.ts deleted file mode 100644 index cbda45572ac..00000000000 --- a/src/system/modifier-data.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { globalScene } from "#app/global-scene"; -import { PersistentModifier } from "#app/modifier/modifier"; -import type { GeneratedPersistentModifierType, ModifierType } from "#app/modifier/modifier-type"; -import { ModifierTypeGenerator, getModifierTypeFuncById } from "#app/modifier/modifier-type"; - -export default class ModifierData { - public player: boolean; - public typeId: string; - public typePregenArgs: any[]; - public args: any[]; - public stackCount: number; - - public className: string; - - constructor(source: PersistentModifier | any, player: boolean) { - const sourceModifier = source instanceof PersistentModifier ? (source as PersistentModifier) : null; - this.player = player; - this.typeId = sourceModifier ? sourceModifier.type.id : source.typeId; - if (sourceModifier) { - if ("getPregenArgs" in source.type) { - this.typePregenArgs = (source.type as GeneratedPersistentModifierType).getPregenArgs(); - } - } else if (source.typePregenArgs) { - this.typePregenArgs = source.typePregenArgs; - } - this.args = sourceModifier ? sourceModifier.getArgs() : source.args || []; - this.stackCount = source.stackCount; - this.className = sourceModifier ? sourceModifier.constructor.name : source.className; - } - - toModifier(_constructor: any): PersistentModifier | null { - const typeFunc = getModifierTypeFuncById(this.typeId); - if (!typeFunc) { - return null; - } - - try { - let type: ModifierType | null = typeFunc(); - type.id = this.typeId; - - if (type instanceof ModifierTypeGenerator) { - type = (type as ModifierTypeGenerator).generateType( - this.player ? globalScene.getPlayerParty() : globalScene.getEnemyField(), - this.typePregenArgs, - ); - } - - const ret = Reflect.construct( - _constructor, - ([type] as any[]).concat(this.args).concat(this.stackCount), - ) as PersistentModifier; - - if (ret.stackCount > ret.getMaxStackCount()) { - ret.stackCount = ret.getMaxStackCount(); - } - - return ret; - } catch (err) { - console.error(err); - return null; - } - } -} diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 28763fe970a..dacab225420 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -15,6 +15,8 @@ import type { MoveId } from "#enums/move-id"; import type { SpeciesId } from "#enums/species-id"; import { CustomPokemonData, PokemonBattleData, PokemonSummonData } from "#app/data/pokemon/pokemon-data"; import type { PokemonType } from "#enums/pokemon-type"; +import type { HeldItemSaveData } from "#app/items/held-item-data-types"; +import { saveDataToConfig } from "#app/items/held-item-pool"; export default class PokemonData { public id: number; @@ -35,6 +37,7 @@ export default class PokemonData { public stats: number[]; public ivs: number[]; public nature: Nature; + public heldItems: HeldItemSaveData; public moveset: PokemonMove[]; public status: Status | null; public friendship: number; @@ -102,6 +105,9 @@ export default class PokemonData { this.hp = source.hp; this.stats = source.stats; this.ivs = source.ivs; + console.log("SAVE ITEMS:", sourcePokemon?.heldItemManager.generateSaveData()); + console.log(sourcePokemon, sourcePokemon?.heldItemManager); + this.heldItems = sourcePokemon?.heldItemManager.generateSaveData() ?? source.heldItems; // TODO: Can't we move some of this verification stuff to an upgrade script? this.nature = source.nature ?? Nature.HARDY; @@ -154,6 +160,8 @@ export default class PokemonData { toPokemon(battleType?: BattleType, partyMemberIndex = 0, double = false): Pokemon { const species = getPokemonSpecies(this.species); + console.log("LOADED ITEMS:", this.heldItems); + console.log(saveDataToConfig(this.heldItems)); const ret: Pokemon = this.player ? globalScene.addPlayerPokemon( species, @@ -165,6 +173,7 @@ export default class PokemonData { this.variant, this.ivs, this.nature, + saveDataToConfig(this.heldItems), this, playerPokemon => { if (this.nickname) { @@ -182,6 +191,7 @@ export default class PokemonData { : TrainerSlot.NONE, this.boss, false, + saveDataToConfig(this.heldItems), this, ); diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index f73c6dab1b2..f1c9778db1d 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -10,6 +10,7 @@ import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER } from "./data/balance/starters"; import { MysteryEncounterType } from "./enums/mystery-encounter-type"; import { MysteryEncounterTier } from "./enums/mystery-encounter-tier"; import { Challenges } from "#enums/challenges"; +import { TrainerItemId } from "#enums/trainer-item-id"; export enum EventType { SHINY, @@ -59,7 +60,7 @@ interface TimedEvent extends EventBanner { startDate: Date; endDate: Date; eventEncounters?: EventEncounter[]; - delibirdyBuff?: string[]; + delibirdyBuff?: TrainerItemId[]; weather?: WeatherPoolEntry[]; mysteryEncounterTierChanges?: EventMysteryEncounterTier[]; luckBoostedSpecies?: SpeciesId[]; @@ -103,7 +104,14 @@ const timedEvents: TimedEvent[] = [ { species: SpeciesId.GALAR_DARUMAKA }, { species: SpeciesId.IRON_BUNDLE }, ], - delibirdyBuff: ["CATCHING_CHARM", "SHINY_CHARM", "ABILITY_CHARM", "EXP_CHARM", "SUPER_EXP_CHARM", "HEALING_CHARM"], + delibirdyBuff: [ + TrainerItemId.CATCHING_CHARM, + TrainerItemId.SHINY_CHARM, + TrainerItemId.ABILITY_CHARM, + TrainerItemId.EXP_CHARM, + TrainerItemId.SUPER_EXP_CHARM, + TrainerItemId.HEALING_CHARM, + ], weather: [{ weatherType: WeatherType.SNOW, weight: 1 }], mysteryEncounterTierChanges: [ { @@ -460,8 +468,8 @@ export class TimedEventManager { * For events where Delibirdy gives extra items * @returns list of ids of {@linkcode ModifierType}s that Delibirdy hands out as a bonus */ - getDelibirdyBuff(): string[] { - const ret: string[] = []; + getDelibirdyBuff(): TrainerItemId[] { + const ret: TrainerItemId[] = []; timedEvents .filter(te => this.isActive(te)) .map(te => { diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts index 6f8f6a76b34..80f0f2601dd 100644 --- a/src/ui/battle-flyout.ts +++ b/src/ui/battle-flyout.ts @@ -177,8 +177,8 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { const berryUsedEvent = event as BerryUsedEvent; if ( !berryUsedEvent || - berryUsedEvent.berryModifier.pokemonId !== this.pokemon?.id || - berryUsedEvent.berryModifier.berryType !== BerryType.LEPPA + berryUsedEvent.pokemon.id !== this.pokemon?.id || + berryUsedEvent.berryType !== BerryType.LEPPA ) { // We only care about Leppa berries return; diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index 8df399b6d9b..417b663db36 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -7,11 +7,11 @@ import { Button } from "#enums/buttons"; import { getPokemonNameWithAffix } from "#app/messages"; import type { CommandPhase } from "#app/phases/command-phase"; import { globalScene } from "#app/global-scene"; -import { TerastallizeAccessModifier } from "#app/modifier/modifier"; import { PokemonType } from "#enums/pokemon-type"; import { getTypeRgb } from "#app/data/type"; import { SpeciesId } from "#enums/species-id"; import { Command } from "#enums/command"; +import { TrainerItemId } from "#enums/trainer-item-id"; export default class CommandUiHandler extends UiHandler { private commandsContainer: Phaser.GameObjects.Container; @@ -192,7 +192,7 @@ export default class CommandUiHandler extends UiHandler { } canTera(): boolean { - const hasTeraMod = !!globalScene.getModifiers(TerastallizeAccessModifier).length; + const hasTeraMod = !!globalScene.trainerItems.hasItem(TrainerItemId.TERA_ORB); const activePokemon = globalScene.getField()[this.fieldIndex]; const isBlockedForm = activePokemon.isMega() || activePokemon.isMax() || activePokemon.hasSpecies(SpeciesId.NECROZMA, "ultra"); diff --git a/src/ui/item-bar-ui.ts b/src/ui/item-bar-ui.ts new file mode 100644 index 00000000000..b18cd3a914c --- /dev/null +++ b/src/ui/item-bar-ui.ts @@ -0,0 +1,103 @@ +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { allHeldItems } from "#app/data/data-lists"; +import { allTrainerItems } from "#app/data/data-lists"; +import type { TrainerItemManager } from "#app/items/trainer-item-manager"; +import { heldItemSortFunc, trainerItemSortFunc } from "#app/items/item-utility"; + +const iconOverflowIndex = 24; + +export class ItemBar extends Phaser.GameObjects.Container { + private player: boolean; + private itemCache: number[]; + public totalVisibleLength = 0; + + constructor(enemy?: boolean) { + super(globalScene, 1 + (enemy ? 302 : 0), 2); + + this.player = !enemy; + this.setScale(0.5); + } + + /** + * Method to update content displayed in {@linkcode ItemBar} + * @param {PersistentItem[]} items - The list of items to be displayed in the {@linkcode ItemBar} + * @param {boolean} hideHeldItems - If set to "true", only items not assigned to a Pokémon are displayed + */ + updateItems(trainerItems: TrainerItemManager, pokemonA?: Pokemon, pokemonB?: Pokemon) { + this.removeAll(true); + + const sortedTrainerItems = trainerItems.getTrainerItems().sort(trainerItemSortFunc); + + const heldItemsA = pokemonA ? pokemonA.getHeldItems().sort(heldItemSortFunc) : []; + const heldItemsB = pokemonB ? pokemonB.getHeldItems().sort(heldItemSortFunc) : []; + + this.totalVisibleLength = sortedTrainerItems.length + heldItemsA.length + heldItemsB.length; + + let iconCount = 0; + sortedTrainerItems.forEach(item => { + const icon = allTrainerItems[item].createIcon(trainerItems.getStack(item)); + iconCount += 1; + this.addIcon(icon, iconCount, allTrainerItems[item].name, allTrainerItems[item].description); + }); + + if (pokemonA) { + heldItemsA.forEach(item => { + const icon = allHeldItems[item].createPokemonIcon(pokemonA); + iconCount += 1; + this.addIcon(icon, iconCount, allHeldItems[item].name, allHeldItems[item].description); + }); + } + + if (pokemonB) { + heldItemsB.forEach(item => { + const icon = allHeldItems[item].createPokemonIcon(pokemonB); + iconCount += 1; + this.addIcon(icon, iconCount, allHeldItems[item].name, allHeldItems[item].description); + }); + } + + for (const icon of this.getAll()) { + this.sendToBack(icon); + } + + this.itemCache = sortedTrainerItems.concat(heldItemsA).concat(heldItemsB); + } + + addIcon(icon: Phaser.GameObjects.Container, i: number, name: string, description: string) { + if (i >= iconOverflowIndex) { + icon.setVisible(false); + } + this.add(icon); + this.setItemIconPosition(icon, this.totalVisibleLength); + icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 24), Phaser.Geom.Rectangle.Contains); + icon.on("pointerover", () => { + globalScene.ui.showTooltip(name, description); + if (this.itemCache && this.itemCache.length > iconOverflowIndex) { + this.updateItemOverflowVisibility(true); + } + }); + icon.on("pointerout", () => { + globalScene.ui.hideTooltip(); + if (this.itemCache && this.itemCache.length > iconOverflowIndex) { + this.updateItemOverflowVisibility(false); + } + }); + } + + updateItemOverflowVisibility(ignoreLimit: boolean) { + const itemIcons = this.getAll().reverse(); + for (const item of itemIcons.map(m => m as Phaser.GameObjects.Container).slice(iconOverflowIndex)) { + item.setVisible(ignoreLimit); + } + } + + setItemIconPosition(icon: Phaser.GameObjects.Container, itemCount: number) { + const rowIcons: number = 12 + 6 * Math.max(Math.ceil(Math.min(itemCount, 24) / 12) - 2, 0); + + const x = ((this.getIndex(icon) % rowIcons) * 26) / (rowIcons / 12); + const y = Math.floor(this.getIndex(icon) / rowIcons) * 20; + + icon.setPosition(this.player ? x : -x, y); + } +} diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 7f5bf997f88..8f3cddbdb52 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -5,7 +5,6 @@ import { getPokeballAtlasKey } from "#app/data/pokeball"; import { addTextObject, getTextStyleOptions, getModifierTierTextTint, getTextColor, TextStyle } from "./text"; import AwaitableUiHandler from "./awaitable-ui-handler"; import { UiMode } from "#enums/ui-mode"; -import { LockModifierTiersModifier, PokemonHeldItemModifier, HealShopCostModifier } from "../modifier/modifier"; import { handleTutorial, Tutorial } from "../tutorial"; import { Button } from "#enums/buttons"; import MoveInfoOverlay from "./move-info-overlay"; @@ -16,6 +15,8 @@ import i18next from "i18next"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import Phaser from "phaser"; import type { PokeballType } from "#enums/pokeball"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; export const SHOP_OPTIONS_ROW_LIMIT = 7; const SINGLE_SHOP_ROW_YOFFSET = 12; @@ -183,8 +184,11 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.player = args[0]; const partyHasHeldItem = - this.player && !!globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable).length; - const canLockRarities = !!globalScene.findModifier(m => m instanceof LockModifierTiersModifier); + globalScene + .getPlayerParty() + .map(p => p.heldItemManager.getTransferableHeldItems().length) + .reduce((tot, i) => tot + i, 0) > 0; + const canLockRarities = !!globalScene.trainerItems.hasItem(TrainerItemId.LOCK_CAPSULE); this.transferButtonContainer.setVisible(false); this.transferButtonContainer.setAlpha(0); @@ -210,7 +214,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const typeOptions = args[1] as ModifierTypeOption[]; const removeHealShop = globalScene.gameMode.hasNoShop; const baseShopCost = new NumberHolder(globalScene.getWaveMoneyAmount(1)); - globalScene.applyModifier(HealShopCostModifier, true, baseShopCost); + globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HEAL_SHOP_COST, { numberHolder: baseShopCost }); const shopTypeOptions = !removeHealShop ? getPlayerShopModifierTypeOptionsForWave(globalScene.currentBattle.waveIndex, baseShopCost.value) : []; @@ -258,10 +262,12 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.shopOptionsRows[row].push(option); } - const maxUpgradeCount = typeOptions.map(to => to.upgradeCount).reduce((max, current) => Math.max(current, max), 0); + const maxUpgradeCount = typeOptions + .map(to => to.upgradeCount ?? 0) + .reduce((max, current) => Math.max(current, max), 0); - /* Force updateModifiers without pokemonSpecificModifiers */ - globalScene.getModifierBar().updateModifiers(globalScene.modifiers, true); + /* Force updateItems without pokemon held items */ + globalScene.updateItems(true, false); /* Multiplies the appearance duration by the speed parameter so that it is always constant, and avoids "flashbangs" at game speed x5 */ globalScene.showShopOverlay(750 * globalScene.gameSpeed); @@ -676,7 +682,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { globalScene.hideLuckText(250); /* Normally already called just after the shop, but not sure if it happens in 100% of cases */ - globalScene.getModifierBar().updateModifiers(globalScene.modifiers); + globalScene.updateItems(true); const options = this.options.concat(this.shopOptionsRows.flat()); this.options.splice(0, this.options.length); @@ -763,7 +769,7 @@ class ModifierOption extends Phaser.GameObjects.Container { this.add(this.itemContainer); const getItem = () => { - const item = globalScene.add.sprite(0, 0, "items", this.modifierTypeOption.type?.iconImage); + const item = globalScene.add.sprite(0, 0, "items", this.modifierTypeOption.type?.getIcon()); return item; }; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 60e9e846859..fe0e039c6af 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -8,7 +8,6 @@ import { Command } from "#enums/command"; import MessageUiHandler from "#app/ui/message-ui-handler"; import { UiMode } from "#enums/ui-mode"; import { BooleanHolder, toReadableString, randInt, getLocalizedSpriteKey } from "#app/utils/common"; -import type { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { allMoves } from "#app/data/data-lists"; import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; import { StatusEffect } from "#enums/status-effect"; @@ -29,6 +28,9 @@ import { SpeciesId } from "#enums/species-id"; import { getPokemonNameWithAffix } from "#app/messages"; import type { CommandPhase } from "#app/phases/command-phase"; import { globalScene } from "#app/global-scene"; +import { HeldItemId } from "#enums/held-item-id"; +import { formChangeItemName } from "#app/data/pokemon-forms"; +import { allHeldItems } from "#app/data/data-lists"; const defaultMessage = i18next.t("partyUiHandler:choosePokemon"); @@ -139,10 +141,7 @@ export type PartyModifierTransferSelectCallback = ( ) => void; export type PartyModifierSpliceSelectCallback = (fromCursor: number, toCursor?: number) => void; export type PokemonSelectFilter = (pokemon: PlayerPokemon) => string | null; -export type PokemonModifierTransferSelectFilter = ( - pokemon: PlayerPokemon, - modifier: PokemonHeldItemModifier, -) => string | null; +export type PokemonModifierTransferSelectFilter = (pokemon: PlayerPokemon, item: HeldItemId) => string | null; export type PokemonMoveSelectFilter = (pokemonMove: PokemonMove) => string | null; export default class PartyUiHandler extends MessageUiHandler { @@ -221,11 +220,8 @@ export default class PartyUiHandler extends MessageUiHandler { private static FilterAllMoves = (_pokemonMove: PokemonMove) => null; - public static FilterItemMaxStacks = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => { - const matchingModifier = globalScene.findModifier( - m => m.is("PokemonHeldItemModifier") && m.pokemonId === pokemon.id && m.matchType(modifier), - ) as PokemonHeldItemModifier; - if (matchingModifier && matchingModifier.stackCount === matchingModifier.getMaxStackCount()) { + public static FilterItemMaxStacks = (pokemon: PlayerPokemon, item: HeldItemId) => { + if (pokemon.heldItemManager.isMaxStack(item)) { return i18next.t("partyUiHandler:tooManyItems", { pokemonName: getPokemonNameWithAffix(pokemon, false) }); } return null; @@ -504,8 +500,10 @@ export default class PartyUiHandler extends MessageUiHandler { const ui = this.getUi(); if (this.transferCursor !== this.cursor) { if (this.transferAll) { - this.getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor]).forEach( - (_, i, array) => { + globalScene + .getPlayerParty() + [this.transferCursor].heldItemManager.getTransferableHeldItems() + .forEach((_, i, array) => { const invertedIndex = array.length - 1 - i; (this.selectCallback as PartyModifierTransferSelectCallback)( this.transferCursor, @@ -513,8 +511,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.transferQuantitiesMax[invertedIndex], this.cursor, ); - }, - ); + }); } else { (this.selectCallback as PartyModifierTransferSelectCallback)( this.transferCursor, @@ -549,18 +546,15 @@ export default class PartyUiHandler extends MessageUiHandler { const newPokemon = globalScene.getPlayerParty()[p]; // this next bit checks to see if the the selected item from the original transfer pokemon exists on the new pokemon `p` // this returns `undefined` if the new pokemon doesn't have the item at all, otherwise it returns the `pokemonHeldItemModifier` for that item - const matchingModifier = globalScene.findModifier( - m => - m.is("PokemonHeldItemModifier") && - m.pokemonId === newPokemon.id && - m.matchType(this.getTransferrableItemsFromPokemon(pokemon)[this.transferOptionCursor]), - ) as PokemonHeldItemModifier; + const transferItem = pokemon.heldItemManager.getTransferableHeldItems()[this.transferOptionCursor]; + const matchingItem = newPokemon.heldItemManager.hasItem(transferItem); + const partySlot = this.partySlots.filter(m => m.getPokemon() === newPokemon)[0]; // this gets pokemon [p] for us if (p !== this.transferCursor) { // this skips adding the able/not able labels on the pokemon doing the transfer - if (matchingModifier) { + if (matchingItem) { // if matchingModifier exists then the item exists on the new pokemon - if (matchingModifier.getMaxStackCount() === matchingModifier.stackCount) { + if (newPokemon.heldItemManager.isMaxStack(transferItem)) { // checks to see if the stack of items is at max stack; if so, set the description label to "Not able" ableToTransferText = i18next.t("partyUiHandler:notAble"); } else { @@ -682,12 +676,6 @@ export default class PartyUiHandler extends MessageUiHandler { return success; } - private getTransferrableItemsFromPokemon(pokemon: PlayerPokemon) { - return globalScene.findModifiers( - m => m.is("PokemonHeldItemModifier") && m.isTransferable && m.pokemonId === pokemon.id, - ) as PokemonHeldItemModifier[]; - } - private getFilterResult(option: number, pokemon: PlayerPokemon): string | null { let filterResult: string | null; if (option !== PartyOption.TRANSFER && option !== PartyOption.SPLICE) { @@ -701,7 +689,7 @@ export default class PartyUiHandler extends MessageUiHandler { } else { filterResult = (this.selectFilter as PokemonModifierTransferSelectFilter)( pokemon, - this.getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor])[ + globalScene.getPlayerParty()[this.transferCursor].heldItemManager.getTransferableHeldItems()[ this.transferOptionCursor ], ); @@ -752,9 +740,9 @@ export default class PartyUiHandler extends MessageUiHandler { globalScene.phaseManager.getCurrentPhase()?.is("SelectModifierPhase") && this.partyUiMode === PartyUiMode.CHECK ) { - const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); - const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; - modifier.active = !modifier.active; + const formChangeItems = this.getFormChangeItems(pokemon); + const item = formChangeItems[option - PartyOption.FORM_CHANGE_ITEM]; + pokemon.heldItemManager.toggleActive(item); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger, false, true); } @@ -923,14 +911,10 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.cursor < 6) { if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode) { /** Initialize item quantities for the selected Pokemon */ - const itemModifiers = globalScene.findModifiers( - m => - m.is("PokemonHeldItemModifier") && - m.isTransferable && - m.pokemonId === globalScene.getPlayerParty()[this.cursor].id, - ) as PokemonHeldItemModifier[]; - this.transferQuantities = itemModifiers.map(item => item.getStackCount()); - this.transferQuantitiesMax = itemModifiers.map(item => item.getStackCount()); + const pokemon = globalScene.getPlayerParty()[this.cursor]; + const items = pokemon.heldItemManager.getTransferableHeldItems(); + this.transferQuantities = items.map(item => pokemon.heldItemManager.getStack(item)); + this.transferQuantitiesMax = items.map(item => pokemon.heldItemManager.getStack(item)); } this.showOptions(); ui.playSelect(); @@ -1161,9 +1145,7 @@ export default class PartyUiHandler extends MessageUiHandler { private allowBatonModifierSwitch(): boolean { return !!( this.partyUiMode !== PartyUiMode.FAINT_SWITCH && - globalScene.findModifier( - m => m.is("SwitchEffectTransferModifier") && m.pokemonId === globalScene.getPlayerField()[this.fieldIndex].id, - ) + globalScene.getPlayerField()[this.fieldIndex].heldItemManager.hasItem(HeldItemId.BATON) ); } @@ -1178,14 +1160,6 @@ export default class PartyUiHandler extends MessageUiHandler { ); } - private getItemModifiers(pokemon: Pokemon): PokemonHeldItemModifier[] { - return ( - (globalScene.findModifiers( - m => m.is("PokemonHeldItemModifier") && m.isTransferable && m.pokemonId === pokemon.id, - ) as PokemonHeldItemModifier[]) ?? [] - ); - } - private updateOptionsWithRememberMoveModifierMode(pokemon): void { const learnableMoves = pokemon.getLearnableLevelMoves(); for (let m = 0; m < learnableMoves.length; m++) { @@ -1205,11 +1179,11 @@ export default class PartyUiHandler extends MessageUiHandler { } private updateOptionsWithModifierTransferMode(pokemon): void { - const itemModifiers = this.getItemModifiers(pokemon); - for (let im = 0; im < itemModifiers.length; im++) { + const items = pokemon.getHeldItems(); + for (let im = 0; im < items.length; im++) { this.options.push(im); } - if (itemModifiers.length > 1) { + if (items.length > 1) { this.options.push(PartyOption.ALL); } } @@ -1336,8 +1310,8 @@ export default class PartyUiHandler extends MessageUiHandler { break; case PartyUiMode.CHECK: if (globalScene.phaseManager.getCurrentPhase()?.is("SelectModifierPhase")) { - const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); - for (let i = 0; i < formChangeItemModifiers.length; i++) { + const formChangeItems = this.getFormChangeItems(pokemon); + for (let i = 0; i < formChangeItems.length; i++) { this.options.push(PartyOption.FORM_CHANGE_ITEM + i); } } @@ -1398,10 +1372,10 @@ export default class PartyUiHandler extends MessageUiHandler { break; } default: { - const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); - if (formChangeItemModifiers && option >= PartyOption.FORM_CHANGE_ITEM) { - const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; - optionName = `${modifier.active ? i18next.t("partyUiHandler:DEACTIVATE") : i18next.t("partyUiHandler:ACTIVATE")} ${modifier.type.name}`; + const formChangeItems = this.getFormChangeItems(pokemon); + if (formChangeItems && option >= PartyOption.FORM_CHANGE_ITEM) { + const item = formChangeItems[option - PartyOption.FORM_CHANGE_ITEM]; + optionName = `${pokemon.heldItemManager.hasActiveFormChangeItem(item) ? i18next.t("partyUiHandler:DEACTIVATE") : i18next.t("partyUiHandler:ACTIVATE")} ${formChangeItemName(item)}`; } else if (option === PartyOption.UNPAUSE_EVOLUTION) { optionName = `${pokemon.pauseEvolutions ? i18next.t("partyUiHandler:UNPAUSE_EVOLUTION") : i18next.t("partyUiHandler:PAUSE_EVOLUTION")}`; } else { @@ -1425,9 +1399,9 @@ export default class PartyUiHandler extends MessageUiHandler { } else if (option === PartyOption.ALL) { optionName = i18next.t("partyUiHandler:ALL"); } else { - const itemModifiers = this.getItemModifiers(pokemon); - const itemModifier = itemModifiers[option]; - optionName = itemModifier.type.name; + const items = pokemon.getHeldItems(); + const item = items[option]; + optionName = allHeldItems[item].name; } const yCoord = -6 - 16 * o; @@ -1439,19 +1413,19 @@ export default class PartyUiHandler extends MessageUiHandler { optionText.setOrigin(0, 0); /** For every item that has stack bigger than 1, display the current quantity selection */ - const itemModifiers = this.getItemModifiers(pokemon); - const itemModifier = itemModifiers[option]; + const items = pokemon.getHeldItems(); + const item = items[option]; if ( this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && this.transferQuantitiesMax[option] > 1 && !this.transferMode && - itemModifier !== undefined && - itemModifier.type.name === optionName + item !== undefined && + allHeldItems[item].name === optionName ) { let amountText = ` (${this.transferQuantities[option]})`; /** If the amount held is the maximum, display the count in red */ - if (this.transferQuantitiesMax[option] === itemModifier.getMaxHeldItemCount(undefined)) { + if (this.transferQuantitiesMax[option] === allHeldItems[item].maxStackCount) { amountText = `[color=${getTextColor(TextStyle.SUMMARY_RED)}]${amountText}[/color]`; } @@ -1500,7 +1474,6 @@ export default class PartyUiHandler extends MessageUiHandler { null, () => { this.clearPartySlots(); - globalScene.removePartyMemberModifiers(slotIndex); const releasedPokemon = globalScene.getPlayerParty().splice(slotIndex, 1)[0]; releasedPokemon.destroy(); this.populatePartySlots(); @@ -1561,29 +1534,24 @@ export default class PartyUiHandler extends MessageUiHandler { }); } - getFormChangeItemsModifiers(pokemon: Pokemon) { - let formChangeItemModifiers = globalScene.findModifiers( - m => m.is("PokemonFormChangeItemModifier") && m.pokemonId === pokemon.id, - ) as PokemonFormChangeItemModifier[]; - const ultraNecrozmaModifiers = formChangeItemModifiers.filter( - m => m.active && m.formChangeItem === FormChangeItem.ULTRANECROZIUM_Z, - ); - if (ultraNecrozmaModifiers.length > 0) { + getFormChangeItems(pokemon: Pokemon) { + let formChangeItems = pokemon.heldItemManager.getFormChangeItems(); + const hasActiveFormChangeItems = pokemon.heldItemManager.getFormChangeItems().length; + const ultraNecrozmaActive = pokemon.heldItemManager.hasActiveFormChangeItem(FormChangeItem.ULTRANECROZIUM_Z); + if (ultraNecrozmaActive) { // ULTRANECROZIUM_Z is active and deactivating it should be the only option - return ultraNecrozmaModifiers; + return [FormChangeItem.ULTRANECROZIUM_Z]; } - if (formChangeItemModifiers.find(m => m.active)) { + if (hasActiveFormChangeItems) { // a form is currently active. the user has to disable the form or activate ULTRANECROZIUM_Z - formChangeItemModifiers = formChangeItemModifiers.filter( - m => m.active || m.formChangeItem === FormChangeItem.ULTRANECROZIUM_Z, + formChangeItems = formChangeItems.filter( + m => pokemon.heldItemManager.hasActiveFormChangeItem(m) || m === FormChangeItem.ULTRANECROZIUM_Z, ); } else if (pokemon.species.speciesId === SpeciesId.NECROZMA) { // no form is currently active. the user has to activate some form, except ULTRANECROZIUM_Z - formChangeItemModifiers = formChangeItemModifiers.filter( - m => m.formChangeItem !== FormChangeItem.ULTRANECROZIUM_Z, - ); + formChangeItems = formChangeItems.filter(m => m !== FormChangeItem.ULTRANECROZIUM_Z); } - return formChangeItemModifiers; + return formChangeItems; } getOptionsCursorWithScroll(): number { diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 76e343d018a..4d4f71dc7ed 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -19,14 +19,15 @@ import { PokemonType } from "#enums/pokemon-type"; import { TypeColor, TypeShadow } from "#app/enums/color"; import { getNatureStatMultiplier, getNatureName } from "../data/nature"; import { getVariantTint } from "#app/sprites/variant"; -// biome-ignore lint/performance/noNamespaceImport: See `src/system/game-data.ts` -import * as Modifier from "#app/modifier/modifier"; import type { SpeciesId } from "#enums/species-id"; import { PlayerGender } from "#enums/player-gender"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { getBiomeName } from "#app/data/balance/biomes"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; +import { allTrainerItems } from "#app/data/data-lists"; +import { allHeldItems } from "#app/data/data-lists"; +import { heldItemSortFunc } from "#app/items/item-utility"; /** * RunInfoUiMode indicates possible overlays of RunInfoUiHandler. @@ -66,7 +67,6 @@ export default class RunInfoUiHandler extends UiHandler { private endCardContainer: Phaser.GameObjects.Container; private partyVisibility: boolean; - private modifiersModule: any; constructor() { super(UiMode.RUN_INFO); @@ -74,8 +74,6 @@ export default class RunInfoUiHandler extends UiHandler { override async setup() { this.runContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); - // The import of the modifiersModule is loaded here to sidestep async/await issues. - this.modifiersModule = Modifier; this.runContainer.setVisible(false); globalScene.loadImage("encounter_exclaim", "mystery-encounters"); } @@ -176,7 +174,7 @@ export default class RunInfoUiHandler extends UiHandler { const headerBg = addWindow(0, 0, globalScene.game.canvas.width / 6 - 2, 24); headerBg.setOrigin(0, 0); this.runContainer.add(headerBg); - if (this.runInfo.modifiers.length !== 0) { + if (this.runInfo.trainerItems.length !== 0) { const headerBgCoords = headerBg.getTopRight(); const abilityButtonContainer = globalScene.add.container(0, 0); const abilityButtonText = addTextObject(8, 0, i18next.t("runHistory:viewHeldItems"), TextStyle.WINDOW, { @@ -641,34 +639,31 @@ export default class RunInfoUiHandler extends UiHandler { // Player Held Items // A max of 20 items can be displayed. A + sign will be added if the run's held items pushes past this maximum to show the user that there are more. - if (this.runInfo.modifiers.length) { - let visibleModifierIndex = 0; + if (this.runInfo.trainerItems.length) { + let visibleTrainerItemIndex = 0; - const modifierIconsContainer = globalScene.add.container( + const trainerItemIconsContainer = globalScene.add.container( 8, this.runInfo.gameMode === GameModes.CHALLENGE ? 20 : 15, ); - modifierIconsContainer.setScale(0.45); - for (const m of this.runInfo.modifiers) { - const modifier = m.toModifier(this.modifiersModule[m.className]); - if (modifier instanceof Modifier.PokemonHeldItemModifier) { - continue; - } - const icon = modifier?.getIcon(false); + trainerItemIconsContainer.setScale(0.45); + for (const m of this.runInfo.trainerItems) { + const itemId = m.id; + const icon = allTrainerItems[itemId].createIcon(m.stack); if (icon) { - const rowHeightModifier = Math.floor(visibleModifierIndex / 7); - icon.setPosition(24 * (visibleModifierIndex % 7), 20 + 35 * rowHeightModifier); - modifierIconsContainer.add(icon); + const rowHeightTrainerItem = Math.floor(visibleTrainerItemIndex / 7); + icon.setPosition(24 * (visibleTrainerItemIndex % 7), 20 + 35 * rowHeightTrainerItem); + trainerItemIconsContainer.add(icon); } - if (++visibleModifierIndex === 20) { + if (++visibleTrainerItemIndex === 20) { const maxItems = addTextObject(45, 90, "+", TextStyle.WINDOW); - maxItems.setPositionRelative(modifierIconsContainer, 70, 45); + maxItems.setPositionRelative(trainerItemIconsContainer, 70, 45); this.runInfoContainer.add(maxItems); break; } } - this.runInfoContainer.add(modifierIconsContainer); + this.runInfoContainer.add(trainerItemIconsContainer); } this.runInfoContainer.add(modeText); @@ -884,38 +879,22 @@ export default class RunInfoUiHandler extends UiHandler { const heldItemsScale = this.runInfo.gameMode === GameModes.SPLICED_ENDLESS || this.runInfo.gameMode === GameModes.ENDLESS ? 0.25 : 0.5; const heldItemsContainer = globalScene.add.container(-82, 2); - const heldItemsList: Modifier.PokemonHeldItemModifier[] = []; - if (this.runInfo.modifiers.length) { - for (const m of this.runInfo.modifiers) { - const modifier = m.toModifier(this.modifiersModule[m.className]); - if (modifier instanceof Modifier.PokemonHeldItemModifier && modifier.pokemonId === pokemon.id) { - modifier.stackCount = m["stackCount"]; - heldItemsList.push(modifier); - } + let row = 0; + for (const [index, item] of pokemon.heldItemManager.getHeldItems().sort(heldItemSortFunc).entries()) { + if (index > 36) { + const overflowIcon = addTextObject(182, 4, "+", TextStyle.WINDOW); + heldItemsContainer.add(overflowIcon); + break; } - if (heldItemsList.length > 0) { - (heldItemsList as Modifier.PokemonHeldItemModifier[]).sort(Modifier.modifierSortFunc); - let row = 0; - for (const [index, item] of heldItemsList.entries()) { - if (index > 36) { - const overflowIcon = addTextObject(182, 4, "+", TextStyle.WINDOW); - heldItemsContainer.add(overflowIcon); - break; - } - const itemIcon = item?.getIcon(true); - if ( - item?.stackCount < item?.getMaxHeldItemCount(pokemon) && - itemIcon.list[1] instanceof Phaser.GameObjects.BitmapText - ) { - itemIcon.list[1].clearTint(); - } - itemIcon.setScale(heldItemsScale); - itemIcon.setPosition((index % 19) * 10, row * 10); - heldItemsContainer.add(itemIcon); - if (index !== 0 && index % 18 === 0) { - row++; - } - } + const itemIcon = allHeldItems[item].createSummaryIcon(pokemon); + if (!pokemon.heldItemManager.isMaxStack(item) && itemIcon.list[1] instanceof Phaser.GameObjects.BitmapText) { + itemIcon.list[1].clearTint(); + } + itemIcon.setScale(heldItemsScale); + itemIcon.setPosition((index % 19) * 10, row * 10); + heldItemsContainer.add(itemIcon); + if (index !== 0 && index % 18 === 0) { + row++; } } heldItemsContainer.setName("heldItems"); @@ -1142,7 +1121,7 @@ export default class RunInfoUiHandler extends UiHandler { } break; case Button.CYCLE_ABILITY: - if (this.runInfo.modifiers.length !== 0 && this.pageMode === RunInfoUiMode.MAIN) { + if (this.runInfo.trainerItems.length !== 0 && this.pageMode === RunInfoUiMode.MAIN) { if (this.partyVisibility) { this.showParty(false); } else { diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 7dee041fa63..139a4c7b853 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -2,8 +2,6 @@ import i18next from "i18next"; import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { GameMode } from "../game-mode"; -// biome-ignore lint/performance/noNamespaceImport: See `src/system/game-data.ts` -import * as Modifier from "#app/modifier/modifier"; import type { SessionSaveData } from "../system/game-data"; import type PokemonData from "../system/pokemon-data"; import { isNullOrUndefined, fixedInt, getPlayTimeString, formatLargeNumber } from "#app/utils/common"; @@ -12,6 +10,7 @@ import { TextStyle, addTextObject } from "./text"; import { UiMode } from "#enums/ui-mode"; import { addWindow } from "./ui-theme"; import { RunDisplayMode } from "#app/ui/run-info-ui-handler"; +import { allTrainerItems } from "#app/data/data-lists"; const SESSION_SLOTS_COUNT = 5; const SLOTS_ON_SCREEN = 3; @@ -443,12 +442,8 @@ class SessionSlot extends Phaser.GameObjects.Container { const modifierIconsContainer = globalScene.add.container(148, 30); modifierIconsContainer.setScale(0.5); let visibleModifierIndex = 0; - for (const m of data.modifiers) { - const modifier = m.toModifier(Modifier[m.className]); - if (modifier instanceof Modifier.PokemonHeldItemModifier) { - continue; - } - const icon = modifier?.getIcon(false); + for (const m of data.trainerItems) { + const icon = allTrainerItems[m.id].createIcon(m.stack); if (icon) { icon.setPosition(24 * visibleModifierIndex, 0); modifierIconsContainer.add(icon); diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index f108faf1646..4b27e9bf144 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -25,7 +25,6 @@ import { MoveCategory } from "#enums/MoveCategory"; 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 "#enums/status-effect"; import { getBiomeName } from "#app/data/balance/biomes"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; @@ -35,11 +34,12 @@ import { getVariantTint } from "#app/sprites/variant"; import { Button } from "#enums/buttons"; import type { Ability } from "#app/data/abilities/ability"; import i18next from "i18next"; -import { modifierSortFunc } from "#app/modifier/modifier"; import { PlayerGender } from "#enums/player-gender"; import { Stat, PERMANENT_STATS, getStatKey } from "#enums/stat"; import { Nature } from "#enums/nature"; import { achvs } from "#app/system/achv"; +import { allHeldItems } from "#app/data/data-lists"; +import { heldItemSortFunc } from "#app/items/item-utility"; enum Page { PROFILE, @@ -1032,24 +1032,39 @@ export default class SummaryUiHandler extends UiHandler { }); this.ivContainer.setVisible(false); - const itemModifiers = ( - globalScene.findModifiers( - m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.pokemon?.id, - this.playerParty, - ) as PokemonHeldItemModifier[] - ).sort(modifierSortFunc); + const heldItems = this.pokemon?.getHeldItems().sort(heldItemSortFunc); - itemModifiers.forEach((item, i) => { - const icon = item.getIcon(true); + heldItems?.forEach((itemKey, i) => { + const heldItem = allHeldItems[itemKey]; + if (this.pokemon) { + const icon = heldItem.createSummaryIcon(this.pokemon); + + console.log(icon); + icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); + this.statsContainer.add(icon); + + icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); + icon.on("pointerover", () => globalScene.ui.showTooltip(heldItem.name, heldItem.description, true)); + icon.on("pointerout", () => globalScene.ui.hideTooltip()); + } + }); + /* + const formChangeItems = this.pokemon?.heldItemManager.getFormChangeItems().sort(formChangeItemSortFunc); + + //TODO: Make an equivalent function for form change items + formChangeItems?.forEach((itemKey, i) => { + const icon = heldItem.createSummaryIcon(stack); + + console.log(icon); icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); this.statsContainer.add(icon); icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); - icon.on("pointerover", () => globalScene.ui.showTooltip(item.type.name, item.type.getDescription(), true)); + icon.on("pointerover", () => globalScene.ui.showTooltip(heldItem.getName(), heldItem.getDescription(), true)); icon.on("pointerout", () => globalScene.ui.hideTooltip()); }); - +*/ const pkmLvl = this.pokemon?.level!; // TODO: is this bang correct? const pkmLvlExp = this.pokemon?.levelExp!; // TODO: is this bang correct? const pkmExp = this.pokemon?.exp!; // TODO: is this bang correct? diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index 8106e4de2da..87ea21e1c48 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -6,9 +6,9 @@ import { getMoveTargets } from "#app/data/moves/move-utils"; import { Button } from "#enums/buttons"; import type { MoveId } from "#enums/move-id"; import type Pokemon from "#app/field/pokemon"; -import type { ModifierBar } from "#app/modifier/modifier"; import { SubstituteTag } from "#app/data/battler-tags"; import { globalScene } from "#app/global-scene"; +import type { ModifierBar } from "#app/modifier/modifier-bar"; export type TargetSelectCallback = (targets: BattlerIndex[]) => void; diff --git a/src/ui/text.ts b/src/ui/text.ts index 76c85bac5cf..785af31a54b 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -4,7 +4,7 @@ import type Phaser from "phaser"; import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; import InputText from "phaser3-rex-plugins/plugins/inputtext"; import { globalScene } from "#app/global-scene"; -import { ModifierTier } from "../enums/modifier-tier"; +import { RewardTier } from "#app/enums/reward-tier"; import i18next from "#app/plugins/i18n"; export enum TextStyle { @@ -423,19 +423,19 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui } } -export function getModifierTierTextTint(tier: ModifierTier): number { +export function getModifierTierTextTint(tier: RewardTier): number { switch (tier) { - case ModifierTier.COMMON: + case RewardTier.COMMON: return 0xf8f8f8; - case ModifierTier.GREAT: + case RewardTier.GREAT: return 0x4998f8; - case ModifierTier.ULTRA: + case RewardTier.ULTRA: return 0xf8d038; - case ModifierTier.ROGUE: + case RewardTier.ROGUE: return 0xdb4343; - case ModifierTier.MASTER: + case RewardTier.MASTER: return 0xe331c5; - case ModifierTier.LUXURY: + case RewardTier.LUXURY: return 0xe74c18; } } @@ -443,12 +443,12 @@ export function getModifierTierTextTint(tier: ModifierTier): number { export function getEggTierTextTint(tier: EggTier): number { switch (tier) { case EggTier.COMMON: - return getModifierTierTextTint(ModifierTier.COMMON); + return getModifierTierTextTint(RewardTier.COMMON); case EggTier.RARE: - return getModifierTierTextTint(ModifierTier.GREAT); + return getModifierTierTextTint(RewardTier.GREAT); case EggTier.EPIC: - return getModifierTierTextTint(ModifierTier.ULTRA); + return getModifierTierTextTint(RewardTier.ULTRA); case EggTier.LEGENDARY: - return getModifierTierTextTint(ModifierTier.MASTER); + return getModifierTierTextTint(RewardTier.MASTER); } } diff --git a/src/utils/common.ts b/src/utils/common.ts index 753d6ebb865..2e9f429f1c7 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -658,3 +658,22 @@ export function enumValueToKey>(input: } throw new Error(`Invalid value passed to \`enumValueToKey\`! Value: ${val}`); } + +export function pickWeightedIndex(weights: number[]): number | undefined { + const totalWeight = weights.reduce((sum, w) => sum + w, 0); + + if (totalWeight <= 0) { + return undefined; + } + + let r = randSeedFloat() * totalWeight; + + for (let i = 0; i < weights.length; i++) { + if (r < weights[i]) { + return i; + } + r -= weights[i]; + } + + return undefined; // TODO: Change to something more appropriate +} diff --git a/src/utils/modifier-utils.ts b/src/utils/modifier-utils.ts index 3be4af3730c..5093d96b5ba 100644 --- a/src/utils/modifier-utils.ts +++ b/src/utils/modifier-utils.ts @@ -1,11 +1,5 @@ import { ModifierPoolType } from "#enums/modifier-pool-type"; -import { - dailyStarterModifierPool, - enemyBuffModifierPool, - modifierPool, - trainerModifierPool, - wildModifierPool, -} from "#app/modifier/modifier-pools"; +import { modifierPool } from "#app/modifier/modifier-pools"; import type { ModifierPool, ModifierTypeFunc } from "#app/@types/modifier-types"; import { modifierTypes } from "#app/data/data-lists"; import type { ModifierType } from "#app/modifier/modifier-type"; @@ -14,14 +8,6 @@ export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool switch (poolType) { case ModifierPoolType.PLAYER: return modifierPool; - case ModifierPoolType.WILD: - return wildModifierPool; - case ModifierPoolType.TRAINER: - return trainerModifierPool; - case ModifierPoolType.ENEMY_BUFF: - return enemyBuffModifierPool; - case ModifierPoolType.DAILY_STARTER: - return dailyStarterModifierPool; } } diff --git a/test/abilities/cud_chew.test.ts b/test/abilities/cud_chew.test.ts index e563e7537dd..b8e2ae7e812 100644 --- a/test/abilities/cud_chew.test.ts +++ b/test/abilities/cud_chew.test.ts @@ -4,6 +4,7 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { AbilityId } from "#enums/ability-id"; import { BerryType } from "#enums/berry-type"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; @@ -30,7 +31,7 @@ describe("Abilities - Cud Chew", () => { game = new GameManager(phaserGame); game.override .moveset([MoveId.BUG_BITE, MoveId.SPLASH, MoveId.HYPER_VOICE, MoveId.STUFF_CHEEKS]) - .startingHeldItems([{ name: "BERRY", type: BerryType.SITRUS, count: 1 }]) + .startingHeldItems([{ entry: HeldItemId.SITRUS_BERRY }]) .ability(AbilityId.CUD_CHEW) .battleStyle("single") .criticalHits(false) @@ -114,8 +115,8 @@ describe("Abilities - Cud Chew", () => { vi.spyOn(Pokemon.prototype, "randBattleSeedInt").mockReturnValue(0); game.override .startingHeldItems([ - { name: "BERRY", type: BerryType.PETAYA, count: 3 }, - { name: "BERRY", type: BerryType.LIECHI, count: 3 }, + { entry: HeldItemId.PETAYA_BERRY, count: 3 }, + { entry: HeldItemId.LIECHI_BERRY, count: 3 }, ]) .enemyMoveset(MoveId.TEATIME); await game.classicMode.startBattle([SpeciesId.FARIGIRAF]); @@ -258,7 +259,7 @@ describe("Abilities - Cud Chew", () => { it("works with pluck", async () => { game.override .enemySpecies(SpeciesId.BLAZIKEN) - .enemyHeldItems([{ name: "BERRY", type: BerryType.PETAYA, count: 1 }]) + .enemyHeldItems([{ entry: HeldItemId.PETAYA_BERRY }]) .startingHeldItems([]); await game.classicMode.startBattle([SpeciesId.FARIGIRAF]); diff --git a/test/abilities/harvest.test.ts b/test/abilities/harvest.test.ts index 42c9772bd10..0cf2d712368 100644 --- a/test/abilities/harvest.test.ts +++ b/test/abilities/harvest.test.ts @@ -1,7 +1,6 @@ import { BattlerIndex } from "#enums/battler-index"; import { PostTurnRestoreBerryAbAttr } from "#app/data/abilities/ability"; import type Pokemon from "#app/field/pokemon"; -import { BerryModifier, PreserveBerryModifier } from "#app/modifier/modifier"; import type { ModifierOverride } from "#app/modifier/modifier-type"; import type { BooleanHolder } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; @@ -13,6 +12,7 @@ import { WeatherType } from "#enums/weather-type"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; describe("Abilities - Harvest", () => { let phaserGame: Phaser.Game; @@ -57,7 +57,7 @@ describe("Abilities - Harvest", () => { }); it("replenishes eaten berries", async () => { - game.override.startingHeldItems([{ name: "BERRY", type: BerryType.LUM, count: 1 }]); + game.override.startingHeldItems([{ entry: HeldItemId.LUM_BERRY }]); await game.classicMode.startBattle([SpeciesId.FEEBAS]); game.move.select(MoveId.SPLASH); @@ -117,9 +117,8 @@ describe("Abilities - Harvest", () => { }); it("remembers berries eaten array across waves", async () => { - game.override - .startingHeldItems([{ name: "BERRY", type: BerryType.PETAYA, count: 2 }]) - .ability(AbilityId.BALL_FETCH); // don't actually need harvest for this test + game.override; + game.override.startingHeldItems([{ entry: HeldItemId.PETAYA_BERRY, count: 2 }]).ability(AbilityId.BALL_FETCH); // don't actually need harvest for this test await game.classicMode.startBattle([SpeciesId.REGIELEKI]); const regieleki = game.scene.getPlayerPokemon()!; @@ -144,7 +143,7 @@ describe("Abilities - Harvest", () => { it("keeps harvested berries across reloads", async () => { game.override - .startingHeldItems([{ name: "BERRY", type: BerryType.PETAYA, count: 1 }]) + .startingHeldItems([{ entry: HeldItemId.PETAYA_BERRY }]) .moveset([MoveId.SPLASH, MoveId.EARTHQUAKE]) .enemyMoveset([MoveId.SUPER_FANG, MoveId.HEAL_PULSE]) .enemyAbility(AbilityId.COMPOUND_EYES); @@ -180,11 +179,10 @@ describe("Abilities - Harvest", () => { }); it("cannot restore capped berries", async () => { - const initBerries: ModifierOverride[] = [ - { name: "BERRY", type: BerryType.LUM, count: 2 }, - { name: "BERRY", type: BerryType.STARF, count: 2 }, - ]; - game.override.startingHeldItems(initBerries); + game.override.startingHeldItems([ + { entry: HeldItemId.LUM_BERRY, count: 2 }, + { entry: HeldItemId.STARF_BERRY, count: 2 }, + ]); await game.classicMode.startBattle([SpeciesId.FEEBAS]); const feebas = game.scene.getPlayerPokemon()!; @@ -227,7 +225,7 @@ describe("Abilities - Harvest", () => { describe("move/ability interactions", () => { it("cannot restore incinerated berries", async () => { - game.override.startingHeldItems([{ name: "BERRY", type: BerryType.STARF, count: 3 }]); + game.override.startingHeldItems([{ entry: HeldItemId.STARF_BERRY, count: 3 }]); await game.classicMode.startBattle([SpeciesId.FEEBAS]); game.move.select(MoveId.SPLASH); @@ -249,8 +247,7 @@ describe("Abilities - Harvest", () => { }); it("can restore berries eaten by Teatime", async () => { - const initBerries: ModifierOverride[] = [{ name: "BERRY", type: BerryType.STARF, count: 1 }]; - game.override.startingHeldItems(initBerries).enemyMoveset(MoveId.TEATIME); + game.override.startingHeldItems([{ entry: HeldItemId.STARF_BERRY }]).enemyMoveset(MoveId.TEATIME); await game.classicMode.startBattle([SpeciesId.FEEBAS]); // nom nom the berr berr yay yay @@ -262,8 +259,10 @@ describe("Abilities - Harvest", () => { }); it("cannot restore Plucked berries for either side", async () => { - const initBerries: ModifierOverride[] = [{ name: "BERRY", type: BerryType.PETAYA, count: 1 }]; - game.override.startingHeldItems(initBerries).enemyAbility(AbilityId.HARVEST).enemyMoveset(MoveId.PLUCK); + game.override + .startingHeldItems([{ entry: HeldItemId.PETAYA_BERRY }]) + .enemyAbility(AbilityId.HARVEST) + .enemyMoveset(MoveId.PLUCK); await game.classicMode.startBattle([SpeciesId.FEEBAS]); // gobble gobble gobble @@ -285,8 +284,9 @@ describe("Abilities - Harvest", () => { }, ); - const initBerries: ModifierOverride[] = [{ name: "BERRY", type: BerryType.PETAYA, count: 1 }]; - game.override.startingHeldItems(initBerries).startingModifier([{ name: "BERRY_POUCH", count: 1 }]); + game.override + .startingHeldItems([{ entry: HeldItemId.PETAYA_BERRY }]) + .startingModifier([{ name: "BERRY_POUCH", count: 1 }]); await game.classicMode.startBattle([SpeciesId.FEEBAS]); game.move.select(MoveId.SPLASH); @@ -320,7 +320,7 @@ describe("Abilities - Harvest", () => { // TODO: Enable once fling actually works...??? it.todo("can restore berries flung at user", async () => { - game.override.enemyHeldItems([{ name: "BERRY", type: BerryType.STARF, count: 1 }]).enemyMoveset(MoveId.FLING); + game.override.enemyHeldItems([{ entry: HeldItemId.STARF_BERRY }]).enemyMoveset(MoveId.FLING); await game.classicMode.startBattle([SpeciesId.FEEBAS]); game.move.select(MoveId.SPLASH); @@ -332,8 +332,7 @@ describe("Abilities - Harvest", () => { // TODO: Enable once Nat Gift gets implemented...??? it.todo("can restore berries consumed via Natural Gift", async () => { - const initBerries: ModifierOverride[] = [{ name: "BERRY", type: BerryType.STARF, count: 1 }]; - game.override.startingHeldItems(initBerries); + game.override.startingHeldItems([{ entry: HeldItemId.STARF_BERRY }]); await game.classicMode.startBattle([SpeciesId.FEEBAS]); game.move.select(MoveId.NATURAL_GIFT); diff --git a/test/abilities/unburden.test.ts b/test/abilities/unburden.test.ts index fff37daff7b..c67703dcec3 100644 --- a/test/abilities/unburden.test.ts +++ b/test/abilities/unburden.test.ts @@ -2,16 +2,16 @@ import { BattlerIndex } from "#enums/battler-index"; import { StealHeldItemChanceAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import type Pokemon from "#app/field/pokemon"; -import type { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { BerryType } from "#enums/berry-type"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; +import { TrainerItemId } from "#enums/trainer-item-id"; describe("Abilities - Unburden", () => { let phaserGame: Phaser.Game; @@ -21,7 +21,7 @@ describe("Abilities - Unburden", () => { * Count the number of held items a Pokemon has, accounting for stacks of multiple items. */ function getHeldItemCount(pokemon: Pokemon): number { - const stackCounts = pokemon.getHeldItems().map(m => m.getStackCount()); + const stackCounts = pokemon.getHeldItems().map(t => pokemon.heldItemManager.getStack(t)); return stackCounts.reduce((a, b) => a + b, 0); } @@ -43,19 +43,16 @@ describe("Abilities - Unburden", () => { .ability(AbilityId.UNBURDEN) .moveset([MoveId.SPLASH, MoveId.KNOCK_OFF, MoveId.PLUCK, MoveId.FALSE_SWIPE]) .startingHeldItems([ - { name: "BERRY", count: 1, type: BerryType.SITRUS }, - { name: "BERRY", count: 2, type: BerryType.APICOT }, - { name: "BERRY", count: 2, type: BerryType.LUM }, + { entry: HeldItemId.SITRUS_BERRY }, + { entry: HeldItemId.APICOT_BERRY, count: 2 }, + { entry: HeldItemId.LUM_BERRY, count: 2 }, ]) .enemySpecies(SpeciesId.NINJASK) .enemyLevel(100) .enemyMoveset(MoveId.SPLASH) .enemyAbility(AbilityId.UNBURDEN) .enemyPassiveAbility(AbilityId.NO_GUARD) - .enemyHeldItems([ - { name: "BERRY", type: BerryType.SITRUS, count: 1 }, - { name: "BERRY", type: BerryType.LUM, count: 1 }, - ]); + .enemyHeldItems([{ entry: HeldItemId.SITRUS_BERRY }, { entry: HeldItemId.LUM_BERRY }]); // For the various tests that use Thief, give it a 100% steal rate vi.spyOn(allMoves[MoveId.THIEF], "attrs", "get").mockReturnValue([new StealHeldItemChanceAttr(1.0)]); }); @@ -77,7 +74,9 @@ describe("Abilities - Unburden", () => { }); it("should activate when a berry is eaten, even if Berry Pouch preserves the berry", async () => { - game.override.enemyMoveset(MoveId.FALSE_SWIPE).startingModifier([{ name: "BERRY_POUCH", count: 5850 }]); + game.override + .enemyMoveset(MoveId.FALSE_SWIPE) + .startingHeldItems([{ entry: TrainerItemId.BERRY_POUCH, count: 5850 }]); await game.classicMode.startBattle([SpeciesId.TREECKO]); const playerPokemon = game.scene.getPlayerPokemon()!; @@ -174,13 +173,9 @@ describe("Abilities - Unburden", () => { }); it("should activate when an item is stolen via grip claw", async () => { - game.override.startingHeldItems([{ name: "GRIP_CLAW", count: 1 }]); + game.override.startingHeldItems([{ entry: HeldItemId.GRIP_CLAW, count: 10 }]); await game.classicMode.startBattle([SpeciesId.TREECKO]); - const playerPokemon = game.scene.getPlayerPokemon()!; - const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier; - vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100); - const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyHeldItemCt = getHeldItemCount(enemyPokemon); const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD); @@ -267,7 +262,7 @@ describe("Abilities - Unburden", () => { }); it("should not activate when passing a baton to a teammate switching in", async () => { - game.override.startingHeldItems([{ name: "BATON" }]).moveset(MoveId.BATON_PASS); + game.override.startingHeldItems([{ entry: HeldItemId.BATON }]).moveset(MoveId.BATON_PASS); await game.classicMode.startBattle([SpeciesId.TREECKO, SpeciesId.PURRLOIN]); const [treecko, purrloin] = game.scene.getPlayerParty(); @@ -314,7 +309,7 @@ describe("Abilities - Unburden", () => { }); it("should activate when a reviver seed is used", async () => { - game.override.startingHeldItems([{ name: "REVIVER_SEED" }]).enemyMoveset([MoveId.WING_ATTACK]); + game.override.startingHeldItems([{ entry: HeldItemId.REVIVER_SEED }]).enemyMoveset([MoveId.WING_ATTACK]); await game.classicMode.startBattle([SpeciesId.TREECKO]); const playerPokemon = game.scene.getPlayerPokemon()!; @@ -359,7 +354,7 @@ describe("Abilities - Unburden", () => { .battleStyle("double") .enemyMoveset([MoveId.SPLASH, MoveId.THIEF]) .moveset([MoveId.SPLASH, MoveId.REVIVAL_BLESSING]) - .startingHeldItems([{ name: "WIDE_LENS" }]); + .startingHeldItems([{ entry: HeldItemId.WIDE_LENS }]); await game.classicMode.startBattle([SpeciesId.TREECKO, SpeciesId.FEEBAS, SpeciesId.MILOTIC]); const treecko = game.scene.getPlayerField()[0]; diff --git a/test/items/dire_hit.test.ts b/test/items/dire_hit.test.ts index a484a0ad302..ce1ff4cd801 100644 --- a/test/items/dire_hit.test.ts +++ b/test/items/dire_hit.test.ts @@ -5,7 +5,6 @@ import GameManager from "#test/testUtils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { BattleEndPhase } from "#app/phases/battle-end-phase"; -import { TempCritBoosterModifier } from "#app/modifier/modifier"; import { UiMode } from "#enums/ui-mode"; import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { Button } from "#app/enums/buttons"; @@ -13,6 +12,7 @@ import { CommandPhase } from "#app/phases/command-phase"; import { NewBattlePhase } from "#app/phases/new-battle-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; +import { TrainerItemId } from "#enums/trainer-item-id"; describe("Items - Dire Hit", () => { let phaserGame: Phaser.Game; @@ -35,7 +35,7 @@ describe("Items - Dire Hit", () => { .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.SPLASH) .moveset([MoveId.POUND]) - .startingHeldItems([{ name: "DIRE_HIT" }]) + .startingTrainerItems([{ entry: TrainerItemId.DIRE_HIT }]) .battleStyle("single"); }); @@ -63,8 +63,8 @@ describe("Items - Dire Hit", () => { await game.phaseInterceptor.to(BattleEndPhase); - const modifier = game.scene.findModifier(m => m instanceof TempCritBoosterModifier) as TempCritBoosterModifier; - expect(modifier.getBattleCount()).toBe(4); + const stack = game.scene.trainerItems.getStack(TrainerItemId.DIRE_HIT); + expect(stack).toBe(4); // Forced DIRE_HIT to spawn in the first slot with override game.onNextPrompt( @@ -83,14 +83,7 @@ describe("Items - Dire Hit", () => { await game.phaseInterceptor.to(TurnInitPhase); - // Making sure only one booster is in the modifier list even after picking up another - let count = 0; - for (const m of game.scene.modifiers) { - if (m instanceof TempCritBoosterModifier) { - count++; - expect((m as TempCritBoosterModifier).getBattleCount()).toBe(5); - } - } - expect(count).toBe(1); + const newStack = game.scene.trainerItems.getStack(TrainerItemId.DIRE_HIT); + expect(newStack).toBe(5); }); }); diff --git a/test/items/double_battle_chance_booster.test.ts b/test/items/double_battle_chance_booster.test.ts index d1a9e826cda..6b19cf37266 100644 --- a/test/items/double_battle_chance_booster.test.ts +++ b/test/items/double_battle_chance_booster.test.ts @@ -1,6 +1,5 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import { DoubleBattleChanceBoosterModifier } from "#app/modifier/modifier"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -8,6 +7,7 @@ import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import { UiMode } from "#enums/ui-mode"; import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { Button } from "#app/enums/buttons"; +import { TrainerItemId } from "#enums/trainer-item-id"; describe("Items - Double Battle Chance Boosters", () => { let phaserGame: Phaser.Game; @@ -27,7 +27,9 @@ describe("Items - Double Battle Chance Boosters", () => { }); it("should guarantee double battle with 2 unique tiers", async () => { - game.override.startingModifier([{ name: "LURE" }, { name: "SUPER_LURE" }]).startingWave(2); + game.override + .startingTrainerItems([{ entry: TrainerItemId.LURE }, { entry: TrainerItemId.SUPER_LURE }]) + .startingWave(2); await game.classicMode.startBattle(); @@ -35,7 +37,13 @@ describe("Items - Double Battle Chance Boosters", () => { }); it("should guarantee double boss battle with 3 unique tiers", async () => { - game.override.startingModifier([{ name: "LURE" }, { name: "SUPER_LURE" }, { name: "MAX_LURE" }]).startingWave(10); + game.override + .startingTrainerItems([ + { entry: TrainerItemId.LURE }, + { entry: TrainerItemId.SUPER_LURE }, + { entry: TrainerItemId.MAX_LURE }, + ]) + .startingWave(10); await game.classicMode.startBattle(); @@ -48,7 +56,7 @@ describe("Items - Double Battle Chance Boosters", () => { it("should renew how many battles are left of existing booster when picking up new booster of same tier", async () => { game.override - .startingModifier([{ name: "LURE" }]) + .startingTrainerItems([{ entry: TrainerItemId.LURE }]) .itemRewards([{ name: "LURE" }]) .moveset(MoveId.SPLASH) .startingLevel(200); @@ -61,10 +69,8 @@ describe("Items - Double Battle Chance Boosters", () => { await game.phaseInterceptor.to("BattleEndPhase"); - const modifier = game.scene.findModifier( - m => m instanceof DoubleBattleChanceBoosterModifier, - ) as DoubleBattleChanceBoosterModifier; - expect(modifier.getBattleCount()).toBe(9); + const stack = game.scene.trainerItems.getStack(TrainerItemId.LURE); + expect(stack).toBe(9); // Forced LURE to spawn in the first slot with override game.onNextPrompt( @@ -84,14 +90,7 @@ describe("Items - Double Battle Chance Boosters", () => { await game.phaseInterceptor.to("TurnInitPhase"); // Making sure only one booster is in the modifier list even after picking up another - let count = 0; - for (const m of game.scene.modifiers) { - if (m instanceof DoubleBattleChanceBoosterModifier) { - count++; - const modifierInstance = m as DoubleBattleChanceBoosterModifier; - expect(modifierInstance.getBattleCount()).toBe(modifierInstance.getMaxBattles()); - } - } - expect(count).toBe(1); + const newStack = game.scene.trainerItems.getStack(TrainerItemId.LURE); + expect(newStack).toBe(10); }); }); diff --git a/test/items/eviolite.test.ts b/test/items/eviolite.test.ts index 353cdbf91b4..0539280e132 100644 --- a/test/items/eviolite.test.ts +++ b/test/items/eviolite.test.ts @@ -1,5 +1,7 @@ -import { StatBoosterModifier } from "#app/modifier/modifier"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { HELD_ITEM_EFFECT } from "#app/items/held-item"; import { NumberHolder, randItem } from "#app/utils/common"; +import { HeldItemId } from "#enums/held-item-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; @@ -22,7 +24,7 @@ describe("Items - Eviolite", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleStyle("single").startingHeldItems([{ name: "EVIOLITE" }]); + game.override.battleStyle("single").startingHeldItems([{ entry: HeldItemId.EVIOLITE }]); }); it("should provide 50% boost to DEF and SPDEF for unevolved, unfused pokemon", async () => { @@ -32,9 +34,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - game.scene.applyModifiers(StatBoosterModifier, partyMember.isPlayer(), partyMember, stat, statValue); - - // Ignore other calculations for simplicity + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); return Math.floor(statValue.value); }); @@ -53,7 +53,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - game.scene.applyModifiers(StatBoosterModifier, partyMember.isPlayer(), partyMember, stat, statValue); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); // Ignore other calculations for simplicity @@ -83,7 +83,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - game.scene.applyModifiers(StatBoosterModifier, partyMember.isPlayer(), partyMember, stat, statValue); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); // Ignore other calculations for simplicity @@ -113,7 +113,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - game.scene.applyModifiers(StatBoosterModifier, partyMember.isPlayer(), partyMember, stat, statValue); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); // Ignore other calculations for simplicity @@ -143,7 +143,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - game.scene.applyModifiers(StatBoosterModifier, partyMember.isPlayer(), partyMember, stat, statValue); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); // Ignore other calculations for simplicity @@ -173,7 +173,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - game.scene.applyModifiers(StatBoosterModifier, partyMember.isPlayer(), partyMember, stat, statValue); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); // Ignore other calculations for simplicity @@ -203,7 +203,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - game.scene.applyModifiers(StatBoosterModifier, partyMember.isPlayer(), partyMember, stat, statValue); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); // Ignore other calculations for simplicity diff --git a/test/items/exp_booster.test.ts b/test/items/exp_booster.test.ts index 44d7721aba2..ded5cb85544 100644 --- a/test/items/exp_booster.test.ts +++ b/test/items/exp_booster.test.ts @@ -1,9 +1,11 @@ import { AbilityId } from "#enums/ability-id"; -import { PokemonExpBoosterModifier } from "#app/modifier/modifier"; import { NumberHolder } from "#app/utils/common"; import GameManager from "#test/testUtils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { HELD_ITEM_EFFECT } from "#app/items/held-item"; describe("EXP Modifier Items", () => { let phaserGame: Phaser.Game; @@ -26,13 +28,13 @@ describe("EXP Modifier Items", () => { }); it("EXP booster items stack multiplicatively", async () => { - game.override.startingHeldItems([{ name: "LUCKY_EGG", count: 3 }, { name: "GOLDEN_EGG" }]); + game.override.startingHeldItems([{ entry: HeldItemId.LUCKY_EGG, count: 3 }, { entry: HeldItemId.GOLDEN_EGG }]); await game.classicMode.startBattle(); const partyMember = game.scene.getPlayerPokemon()!; partyMember.exp = 100; const expHolder = new NumberHolder(partyMember.exp); - game.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, expHolder); + applyHeldItems(HELD_ITEM_EFFECT.EXP_BOOSTER, { pokemon: partyMember, expAmount: expHolder }); expect(expHolder.value).toBe(440); }); }); diff --git a/test/items/grip_claw.test.ts b/test/items/grip_claw.test.ts index 9c3e6548140..736481b523b 100644 --- a/test/items/grip_claw.test.ts +++ b/test/items/grip_claw.test.ts @@ -1,13 +1,11 @@ import { BattlerIndex } from "#enums/battler-index"; -import type Pokemon from "#app/field/pokemon"; -import type { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier"; import { AbilityId } from "#enums/ability-id"; -import { BerryType } from "#enums/berry-type"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phase from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; describe("Items - Grip Claw", () => { let phaserGame: Phaser.Game; @@ -29,14 +27,14 @@ describe("Items - Grip Claw", () => { game.override .battleStyle("double") .moveset([MoveId.TACKLE, MoveId.SPLASH, MoveId.ATTRACT]) - .startingHeldItems([{ name: "GRIP_CLAW", count: 1 }]) + .startingHeldItems([{ entry: HeldItemId.GRIP_CLAW }]) .enemySpecies(SpeciesId.SNORLAX) .enemyAbility(AbilityId.UNNERVE) .ability(AbilityId.UNNERVE) .enemyMoveset(MoveId.SPLASH) .enemyHeldItems([ - { name: "BERRY", type: BerryType.SITRUS, count: 2 }, - { name: "BERRY", type: BerryType.LUM, count: 2 }, + { entry: HeldItemId.SITRUS_BERRY, count: 2 }, + { entry: HeldItemId.LUM_BERRY, count: 2 }, ]) .enemyLevel(100); }); @@ -45,15 +43,12 @@ describe("Items - Grip Claw", () => { await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MILOTIC]); const [playerPokemon] = game.scene.getPlayerField(); - - const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier; - vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100); - const enemyPokemon = game.scene.getEnemyField(); + playerPokemon.heldItemManager.setStack(HeldItemId.GRIP_CLAW, 10); - const playerHeldItemCount = getHeldItemCount(playerPokemon); - const enemy1HeldItemCount = getHeldItemCount(enemyPokemon[0]); - const enemy2HeldItemCount = getHeldItemCount(enemyPokemon[1]); + const playerHeldItemCount = playerPokemon.heldItemManager.getHeldItemCount(); + const enemy1HeldItemCount = enemyPokemon[0].heldItemManager.getHeldItemCount(); + const enemy2HeldItemCount = enemyPokemon[1].heldItemManager.getHeldItemCount(); expect(enemy2HeldItemCount).toBeGreaterThan(0); game.move.select(MoveId.TACKLE, 0, BattlerIndex.ENEMY_2); @@ -61,9 +56,9 @@ describe("Items - Grip Claw", () => { await game.phaseInterceptor.to("BerryPhase", false); - const playerHeldItemCountAfter = getHeldItemCount(playerPokemon); - const enemy1HeldItemCountsAfter = getHeldItemCount(enemyPokemon[0]); - const enemy2HeldItemCountsAfter = getHeldItemCount(enemyPokemon[1]); + const playerHeldItemCountAfter = playerPokemon.heldItemManager.getHeldItemCount(); + const enemy1HeldItemCountsAfter = enemyPokemon[0].heldItemManager.getHeldItemCount(); + const enemy2HeldItemCountsAfter = enemyPokemon[1].heldItemManager.getHeldItemCount(); expect(playerHeldItemCountAfter).toBe(playerHeldItemCount + 1); expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount); @@ -74,15 +69,11 @@ describe("Items - Grip Claw", () => { await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MILOTIC]); const [playerPokemon] = game.scene.getPlayerField(); - - const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier; - vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100); - const enemyPokemon = game.scene.getEnemyField(); - const playerHeldItemCount = getHeldItemCount(playerPokemon); - const enemy1HeldItemCount = getHeldItemCount(enemyPokemon[0]); - const enemy2HeldItemCount = getHeldItemCount(enemyPokemon[1]); + const playerHeldItemCount = playerPokemon.heldItemManager.getHeldItemCount(); + const enemy1HeldItemCount = enemyPokemon[0].heldItemManager.getHeldItemCount(); + const enemy2HeldItemCount = enemyPokemon[1].heldItemManager.getHeldItemCount(); expect(enemy2HeldItemCount).toBeGreaterThan(0); game.move.select(MoveId.ATTRACT, 0, BattlerIndex.ENEMY_2); @@ -90,9 +81,9 @@ describe("Items - Grip Claw", () => { await game.phaseInterceptor.to("BerryPhase", false); - const playerHeldItemCountAfter = getHeldItemCount(playerPokemon); - const enemy1HeldItemCountsAfter = getHeldItemCount(enemyPokemon[0]); - const enemy2HeldItemCountsAfter = getHeldItemCount(enemyPokemon[1]); + const playerHeldItemCountAfter = playerPokemon.heldItemManager.getHeldItemCount(); + const enemy1HeldItemCountsAfter = enemyPokemon[0].heldItemManager.getHeldItemCount(); + const enemy2HeldItemCountsAfter = enemyPokemon[1].heldItemManager.getHeldItemCount(); expect(playerHeldItemCountAfter).toBe(playerHeldItemCount); expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount); @@ -103,31 +94,19 @@ describe("Items - Grip Claw", () => { game.override .battleStyle("double") .moveset([MoveId.POLLEN_PUFF, MoveId.ENDURE]) - .startingHeldItems([ - { name: "GRIP_CLAW", count: 1 }, - { name: "BERRY", type: BerryType.LUM, count: 1 }, - ]); + .startingHeldItems([{ entry: HeldItemId.GRIP_CLAW }, { entry: HeldItemId.LUM_BERRY }]); await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.OMANYTE]); const [leftPokemon, rightPokemon] = game.scene.getPlayerField(); + leftPokemon.heldItemManager.setStack(HeldItemId.GRIP_CLAW, 10); - const gripClaw = leftPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier; - vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100); - - const heldItemCountBefore = getHeldItemCount(rightPokemon); + const heldItemCountBefore = rightPokemon.heldItemManager.getHeldItemCount(); game.move.select(MoveId.POLLEN_PUFF, 0, BattlerIndex.PLAYER_2); game.move.select(MoveId.ENDURE, 1); await game.toNextTurn(); - expect(getHeldItemCount(rightPokemon)).toBe(heldItemCountBefore); + expect(rightPokemon.heldItemManager.getHeldItemCount()).toBe(heldItemCountBefore); }); }); - -/* - * Gets the total number of items a Pokemon holds - */ -function getHeldItemCount(pokemon: Pokemon) { - return pokemon.getHeldItems().reduce((currentTotal, item) => currentTotal + item.getStackCount(), 0); -} diff --git a/test/items/leek.test.ts b/test/items/leek.test.ts index eedb6667b9b..058790ef7fa 100644 --- a/test/items/leek.test.ts +++ b/test/items/leek.test.ts @@ -1,5 +1,6 @@ import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { randInt } from "#app/utils/common"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; @@ -26,7 +27,7 @@ describe("Items - Leek", () => { game.override .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.SPLASH) - .startingHeldItems([{ name: "LEEK" }]) + .startingHeldItems([{ entry: HeldItemId.LEEK }]) .moveset([MoveId.TACKLE]) .battleStyle("single"); }); diff --git a/test/items/leftovers.test.ts b/test/items/leftovers.test.ts index 21319d2c9d7..30cbe6434ba 100644 --- a/test/items/leftovers.test.ts +++ b/test/items/leftovers.test.ts @@ -1,6 +1,7 @@ import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { AbilityId } from "#enums/ability-id"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; @@ -31,17 +32,17 @@ describe("Items - Leftovers", () => { .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.UNNERVE) .enemyMoveset(MoveId.TACKLE) - .startingHeldItems([{ name: "LEFTOVERS", count: 1 }]); + .startingHeldItems([{ entry: HeldItemId.LEFTOVERS }]); }); it("leftovers works", async () => { await game.classicMode.startBattle([SpeciesId.ARCANINE]); - // Make sure leftovers are there - expect(game.scene.modifiers[0].type.id).toBe("LEFTOVERS"); - const leadPokemon = game.scene.getPlayerPokemon()!; + // Make sure leftovers are there + expect(leadPokemon.heldItemManager.hasItem(HeldItemId.LEFTOVERS)).toBe(true); + // We should have full hp expect(leadPokemon.isFullHp()).toBe(true); diff --git a/test/items/light_ball.test.ts b/test/items/light_ball.test.ts index 6dfed3389b9..742e7dc8d61 100644 --- a/test/items/light_ball.test.ts +++ b/test/items/light_ball.test.ts @@ -1,12 +1,13 @@ import { Stat } from "#enums/stat"; -import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; -import { modifierTypes } from "#app/data/data-lists"; import i18next from "#app/plugins/i18n"; import { NumberHolder } from "#app/utils/common"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { HELD_ITEM_EFFECT } from "#app/items/held-item"; describe("Items - Light Ball", () => { let phaserGame: Phaser.Game; @@ -29,7 +30,7 @@ describe("Items - Light Ball", () => { }); it("LIGHT_BALL activates in battle correctly", async () => { - game.override.startingHeldItems([{ name: "RARE_SPECIES_STAT_BOOSTER", type: "LIGHT_BALL" }]); + game.override.startingHeldItems([{ entry: HeldItemId.LIGHT_BALL }]); const consoleSpy = vi.spyOn(console, "log"); await game.classicMode.startBattle([SpeciesId.PIKACHU]); @@ -91,20 +92,17 @@ describe("Items - Light Ball", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: atkValue }); const spAtkValue = new NumberHolder(spAtkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); - + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPDEF, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); - // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["LIGHT_BALL"])!.newModifier(partyMember), - true, - ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); + // Giving Light Ball to party member and testing if it applies + partyMember.heldItemManager.add(HeldItemId.LIGHT_BALL); + + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPATK, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(2); expect(spAtkValue.value / spAtkStat).toBe(2); @@ -130,20 +128,18 @@ describe("Items - Light Ball", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: atkValue }); const spAtkValue = new NumberHolder(spAtkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPDEF, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); - // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["LIGHT_BALL"])!.newModifier(partyMember), - true, - ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); + // Giving Light Ball to party member and testing if it applies + partyMember.heldItemManager.add(HeldItemId.LIGHT_BALL); + + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPATK, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(2); expect(spAtkValue.value / spAtkStat).toBe(2); @@ -169,20 +165,18 @@ describe("Items - Light Ball", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: atkValue }); const spAtkValue = new NumberHolder(spAtkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPDEF, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); - // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["LIGHT_BALL"])!.newModifier(partyMember), - true, - ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); + // Giving Light Ball to party member and testing if it applies + partyMember.heldItemManager.add(HeldItemId.LIGHT_BALL); + + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPATK, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(2); expect(spAtkValue.value / spAtkStat).toBe(2); @@ -198,20 +192,17 @@ describe("Items - Light Ball", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: atkValue }); const spAtkValue = new NumberHolder(spAtkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPDEF, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); - // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["LIGHT_BALL"])!.newModifier(partyMember), - true, - ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); + // Giving Light Ball to party member and testing if it applies + partyMember.heldItemManager.add(HeldItemId.LIGHT_BALL); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); + applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPATK, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); diff --git a/test/items/lock_capsule.test.ts b/test/items/lock_capsule.test.ts index beacc3a3907..0395d775019 100644 --- a/test/items/lock_capsule.test.ts +++ b/test/items/lock_capsule.test.ts @@ -1,11 +1,12 @@ import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { UiMode } from "#enums/ui-mode"; import GameManager from "#test/testUtils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { TrainerItemId } from "#enums/trainer-item-id"; describe("Items - Lock Capsule", () => { let phaserGame: Phaser.Game; @@ -29,14 +30,14 @@ describe("Items - Lock Capsule", () => { .startingLevel(200) .moveset([MoveId.SURF]) .enemyAbility(AbilityId.BALL_FETCH) - .startingModifier([{ name: "LOCK_CAPSULE" }]); + .startingTrainerItems([{ entry: TrainerItemId.LOCK_CAPSULE }]); }); it("doesn't set the cost of common tier items to 0", async () => { await game.classicMode.startBattle(); game.scene.phaseManager.overridePhase( new SelectModifierPhase(0, undefined, { - guaranteedModifierTiers: [ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.COMMON], + guaranteedModifierTiers: [RewardTier.COMMON, RewardTier.COMMON, RewardTier.COMMON], fillRemaining: false, }), ); diff --git a/test/items/mystical_rock.test.ts b/test/items/mystical_rock.test.ts index 091815aa604..acbd9d50a32 100644 --- a/test/items/mystical_rock.test.ts +++ b/test/items/mystical_rock.test.ts @@ -5,6 +5,7 @@ import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; describe("Items - Mystical Rock", () => { let phaserGame: Phaser.Game; @@ -28,7 +29,7 @@ describe("Items - Mystical Rock", () => { .enemyMoveset(MoveId.SPLASH) .enemyAbility(AbilityId.BALL_FETCH) .moveset([MoveId.SUNNY_DAY, MoveId.GRASSY_TERRAIN]) - .startingHeldItems([{ name: "MYSTICAL_ROCK", count: 2 }]) + .startingHeldItems([{ entry: HeldItemId.MYSTICAL_ROCK, count: 2 }]) .battleStyle("single"); }); diff --git a/test/items/reviver_seed.test.ts b/test/items/reviver_seed.test.ts index f444a6eac66..d977189789c 100644 --- a/test/items/reviver_seed.test.ts +++ b/test/items/reviver_seed.test.ts @@ -1,13 +1,13 @@ import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import type { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; describe("Items - Reviver Seed", () => { let phaserGame: Phaser.Game; @@ -32,8 +32,8 @@ describe("Items - Reviver Seed", () => { .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) - .startingHeldItems([{ name: "REVIVER_SEED" }]) - .enemyHeldItems([{ name: "REVIVER_SEED" }]) + .startingHeldItems([{ entry: HeldItemId.REVIVER_SEED }]) + .enemyHeldItems([{ entry: HeldItemId.REVIVER_SEED }]) .enemyMoveset(MoveId.SPLASH); vi.spyOn(allMoves[MoveId.SHEER_COLD], "accuracy", "get").mockReturnValue(100); vi.spyOn(allMoves[MoveId.LEECH_SEED], "accuracy", "get").mockReturnValue(100); diff --git a/test/items/scope_lens.test.ts b/test/items/scope_lens.test.ts index 16be8aab930..6e7712bac2f 100644 --- a/test/items/scope_lens.test.ts +++ b/test/items/scope_lens.test.ts @@ -1,4 +1,5 @@ import { TurnEndPhase } from "#app/phases/turn-end-phase"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; @@ -26,7 +27,7 @@ describe("Items - Scope Lens", () => { .enemySpecies(SpeciesId.MAGIKARP) .enemyMoveset(MoveId.SPLASH) .moveset([MoveId.POUND]) - .startingHeldItems([{ name: "SCOPE_LENS" }]) + .startingHeldItems([{ entry: HeldItemId.SCOPE_LENS }]) .battleStyle("single"); }); diff --git a/test/items/temp_stat_stage_booster.test.ts b/test/items/temp_stat_stage_booster.test.ts index b8cd0cde4eb..621697f441d 100644 --- a/test/items/temp_stat_stage_booster.test.ts +++ b/test/items/temp_stat_stage_booster.test.ts @@ -6,7 +6,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import { MoveId } from "#enums/move-id"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { AbilityId } from "#enums/ability-id"; -import { TempStatStageBoosterModifier } from "#app/modifier/modifier"; import { UiMode } from "#enums/ui-mode"; import { Button } from "#app/enums/buttons"; import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; diff --git a/test/items/toxic_orb.test.ts b/test/items/toxic_orb.test.ts index e0d86655028..36ccb073318 100644 --- a/test/items/toxic_orb.test.ts +++ b/test/items/toxic_orb.test.ts @@ -1,5 +1,6 @@ import i18next from "#app/plugins/i18n"; import { AbilityId } from "#enums/ability-id"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; @@ -30,11 +31,7 @@ describe("Items - Toxic orb", () => { .enemyAbility(AbilityId.BALL_FETCH) .moveset(MoveId.SPLASH) .enemyMoveset(MoveId.SPLASH) - .startingHeldItems([ - { - name: "TOXIC_ORB", - }, - ]); + .startingHeldItems([{ entry: HeldItemId.TOXIC_ORB }]); vi.spyOn(i18next, "t"); }); @@ -43,7 +40,7 @@ describe("Items - Toxic orb", () => { await game.classicMode.startBattle([SpeciesId.FEEBAS]); const player = game.scene.getPlayerPokemon()!; - expect(player.getHeldItems()[0].type.id).toBe("TOXIC_ORB"); + expect(player.heldItemManager.hasItem(HeldItemId.TOXIC_ORB)).toBe(true); game.move.select(MoveId.SPLASH); diff --git a/test/moves/fusion_flare_bolt.test.ts b/test/moves/fusion_flare_bolt.test.ts index 1967e9f12d1..d7af700d39b 100644 --- a/test/moves/fusion_flare_bolt.test.ts +++ b/test/moves/fusion_flare_bolt.test.ts @@ -165,7 +165,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { // Get rid of any modifiers that may alter power game.scene.clearEnemyHeldItemModifiers(); - game.scene.clearEnemyModifiers(); + game.scene.clearEnemyItems(); // Mock stats by replacing entries in copy with desired values for specific stats const stats = { @@ -219,7 +219,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { // Get rid of any modifiers that may alter power game.scene.clearEnemyHeldItemModifiers(); - game.scene.clearEnemyModifiers(); + game.scene.clearEnemyItems(); // Mock stats by replacing entries in copy with desired values for specific stats const stats = { diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 85193d1ec72..213fea75081 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -20,7 +20,7 @@ import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowning-around-encounter"; import { TrainerType } from "#enums/trainer-type"; import { AbilityId } from "#enums/ability-id"; @@ -296,10 +296,10 @@ describe("Clowning Around - Mystery Encounter", () => { const leadItemsAfter = scene.getPlayerParty()[0].getHeldItems(); const ultraCountAfter = leadItemsAfter - .filter(m => m.type.tier === ModifierTier.ULTRA) + .filter(m => m.type.tier === RewardTier.ULTRA) .reduce((a, b) => a + b.stackCount, 0); const rogueCountAfter = leadItemsAfter - .filter(m => m.type.tier === ModifierTier.ROGUE) + .filter(m => m.type.tier === RewardTier.ROGUE) .reduce((a, b) => a + b.stackCount, 0); expect(ultraCountAfter).toBe(13); expect(rogueCountAfter).toBe(7); @@ -391,5 +391,5 @@ async function addItemToPokemon( const itemMod = itemType.newModifier(pokemon) as PokemonHeldItemModifier; itemMod.stackCount = stackCount; scene.addModifier(itemMod, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); } diff --git a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index 3ef8431cc2c..c88e1677bcf 100644 --- a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -134,7 +134,7 @@ describe("Delibird-y - Mystery Encounter", () => { const amuletCoin = generateModifierType(modifierTypes.AMULET_COIN)!.newModifier() as MoneyMultiplierModifier; amuletCoin.stackCount = 5; scene.addModifier(amuletCoin, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await runMysteryEncounterToEnd(game, 1); @@ -204,7 +204,7 @@ describe("Delibird-y - Mystery Encounter", () => { const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier; sitrusMod.stackCount = 2; scene.addModifier(sitrusMod, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); @@ -225,7 +225,7 @@ describe("Delibird-y - Mystery Encounter", () => { const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier; modifier.stackCount = 1; scene.addModifier(modifier, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); @@ -251,7 +251,7 @@ describe("Delibird-y - Mystery Encounter", () => { const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier; sitrusMod.stackCount = 2; scene.addModifier(sitrusMod, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); @@ -280,7 +280,7 @@ describe("Delibird-y - Mystery Encounter", () => { const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier; modifier.stackCount = 1; scene.addModifier(modifier, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); @@ -303,7 +303,7 @@ describe("Delibird-y - Mystery Encounter", () => { const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]); scene.addModifier(modifier, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await game.phaseInterceptor.to(MysteryEncounterPhase, false); @@ -332,7 +332,7 @@ describe("Delibird-y - Mystery Encounter", () => { const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier; modifier.stackCount = 1; scene.addModifier(modifier, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); @@ -366,7 +366,7 @@ describe("Delibird-y - Mystery Encounter", () => { const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 2; scene.addModifier(modifier, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); @@ -387,7 +387,7 @@ describe("Delibird-y - Mystery Encounter", () => { const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 1; scene.addModifier(modifier, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); @@ -413,7 +413,7 @@ describe("Delibird-y - Mystery Encounter", () => { const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 1; scene.addModifier(modifier, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); @@ -436,7 +436,7 @@ describe("Delibird-y - Mystery Encounter", () => { const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!; const modifier = revSeed.newModifier(scene.getPlayerParty()[0]); scene.addModifier(modifier, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await game.phaseInterceptor.to(MysteryEncounterPhase, false); @@ -466,7 +466,7 @@ describe("Delibird-y - Mystery Encounter", () => { const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 1; scene.addModifier(modifier, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); diff --git a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index bb598f4ae6e..5dc3ba90bb0 100644 --- a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -17,7 +17,7 @@ import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/myst import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { UiMode } from "#enums/ui-mode"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import * as Utils from "#app/utils/common"; const namespace = "mysteryEncounters/globalTradeSystem"; @@ -223,7 +223,7 @@ describe("Global Trade System - Mystery Encounter", () => { const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 2; scene.addModifier(modifier, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); @@ -234,7 +234,7 @@ describe("Global Trade System - Mystery Encounter", () => { h => h instanceof ModifierSelectUiHandler, ) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(1); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier).toBe(ModifierTier.MASTER); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier).toBe(RewardTier.MASTER); const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier); expect(soulDewAfter?.stackCount).toBe(1); }); @@ -250,7 +250,7 @@ describe("Global Trade System - Mystery Encounter", () => { const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 1; scene.addModifier(modifier, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); diff --git a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index 478648d88a7..0b9d3f2a5f3 100644 --- a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -14,7 +14,7 @@ import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import { MysteriousChallengersEncounter } from "#app/data/mystery-encounters/encounters/mysterious-challengers-encounter"; import { TrainerConfig } from "#app/data/trainers/trainer-config"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; @@ -218,19 +218,19 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.ULTRA); + ).toBe(RewardTier.ULTRA); expect( modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.ULTRA); + ).toBe(RewardTier.ULTRA); expect( modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.GREAT); + ).toBe(RewardTier.GREAT); expect( modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.GREAT); + ).toBe(RewardTier.GREAT); }); }); @@ -275,19 +275,19 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.ROGUE); + ).toBe(RewardTier.ROGUE); expect( modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.ROGUE); + ).toBe(RewardTier.ROGUE); expect( modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.ULTRA); + ).toBe(RewardTier.ULTRA); expect( modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toBe(ModifierTier.GREAT); + ).toBe(RewardTier.GREAT); }); }); }); diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 9ab5f16d1b9..7626e4c9660 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -14,7 +14,7 @@ import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { HealShopCostModifier, HitHealModifier, TurnHealModifier } from "#app/modifier/modifier"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; import { CommandPhase } from "#app/phases/command-phase"; @@ -254,19 +254,19 @@ describe("Trash to Treasure - Mystery Encounter", () => { expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ROGUE); + ).toEqual(RewardTier.ROGUE); expect( modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ROGUE); + ).toEqual(RewardTier.ROGUE); expect( modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ULTRA); + ).toEqual(RewardTier.ULTRA); expect( modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.GREAT); + ).toEqual(RewardTier.GREAT); }); }); }); diff --git a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index ec64a17d291..2fef4fa4cd3 100644 --- a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -188,7 +188,7 @@ describe("Uncommon Breed - Mystery Encounter", () => { scene.modifiers.forEach(mod => { scene.removeModifier(mod); }); - await scene.updateModifiers(true); + await scene.updateItems(true); await game.phaseInterceptor.to(MysteryEncounterPhase, false); const encounterPhase = scene.phaseManager.getCurrentPhase(); @@ -221,7 +221,7 @@ describe("Uncommon Breed - Mystery Encounter", () => { const ganlonMod = ganlon.newModifier(scene.getPlayerParty()[0]) as BerryModifier; ganlonMod.stackCount = 3; scene.addModifier(ganlonMod, true, false, false, true); - await scene.updateModifiers(true); + await scene.updateItems(true); await runMysteryEncounterToEnd(game, 2); diff --git a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index 475d5cc3c6e..3690a64cab4 100644 --- a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -19,7 +19,7 @@ import { WeirdDreamEncounter } from "#app/data/mystery-encounters/encounters/wei import * as EncounterTransformationSequence from "#app/data/mystery-encounters/utils/encounter-transformation-sequence"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { CommandPhase } from "#app/phases/command-phase"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; const namespace = "mysteryEncounters/weirdDream"; const defaultParty = [SpeciesId.MAGBY, SpeciesId.HAUNTER, SpeciesId.ABRA]; @@ -207,27 +207,27 @@ describe("Weird Dream - Mystery Encounter", () => { expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ROGUE); + ).toEqual(RewardTier.ROGUE); expect( modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ROGUE); + ).toEqual(RewardTier.ROGUE); expect( modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ULTRA); + ).toEqual(RewardTier.ULTRA); expect( modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ULTRA); + ).toEqual(RewardTier.ULTRA); expect( modifierSelectHandler.options[4].modifierTypeOption.type.tier - modifierSelectHandler.options[4].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.GREAT); + ).toEqual(RewardTier.GREAT); expect( modifierSelectHandler.options[5].modifierTypeOption.type.tier - modifierSelectHandler.options[5].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.GREAT); + ).toEqual(RewardTier.GREAT); }); }); diff --git a/test/phases/select-modifier-phase.test.ts b/test/phases/select-modifier-phase.test.ts index b6c3089e236..0a4d80c7ed7 100644 --- a/test/phases/select-modifier-phase.test.ts +++ b/test/phases/select-modifier-phase.test.ts @@ -1,7 +1,7 @@ import type BattleScene from "#app/battle-scene"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { PlayerPokemon } from "#app/field/pokemon"; -import { ModifierTier } from "#enums/modifier-tier"; +import { RewardTier } from "#enums/reward-tier"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { ModifierTypeOption } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; @@ -129,7 +129,7 @@ describe("SelectModifierPhase", () => { h => h instanceof ModifierSelectUiHandler, ) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); - const firstRollTiers: ModifierTier[] = modifierSelectHandler.options.map(o => o.modifierTypeOption.type.tier); + const firstRollTiers: RewardTier[] = modifierSelectHandler.options.map(o => o.modifierTypeOption.type.tier); // TODO: nagivate ui to reroll with lock capsule enabled @@ -184,11 +184,11 @@ describe("SelectModifierPhase", () => { scene.money = 1000000; const customModifiers: CustomModifierSettings = { guaranteedModifierTiers: [ - ModifierTier.COMMON, - ModifierTier.GREAT, - ModifierTier.ULTRA, - ModifierTier.ROGUE, - ModifierTier.MASTER, + RewardTier.COMMON, + RewardTier.GREAT, + RewardTier.ULTRA, + RewardTier.ROGUE, + RewardTier.MASTER, ], }; const pokemon = new PlayerPokemon(getPokemonSpecies(SpeciesId.BULBASAUR), 10, undefined, 0, undefined, true, 2); @@ -212,23 +212,23 @@ describe("SelectModifierPhase", () => { expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.COMMON); + ).toEqual(RewardTier.COMMON); expect( modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.GREAT); + ).toEqual(RewardTier.GREAT); expect( modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ULTRA); + ).toEqual(RewardTier.ULTRA); expect( modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.ROGUE); + ).toEqual(RewardTier.ROGUE); expect( modifierSelectHandler.options[4].modifierTypeOption.type.tier - modifierSelectHandler.options[4].modifierTypeOption.upgradeCount, - ).toEqual(ModifierTier.MASTER); + ).toEqual(RewardTier.MASTER); }); it("should generate custom modifiers and modifier tiers together", async () => { @@ -236,7 +236,7 @@ describe("SelectModifierPhase", () => { scene.money = 1000000; const customModifiers: CustomModifierSettings = { guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM, modifierTypes.TM_COMMON], - guaranteedModifierTiers: [ModifierTier.MASTER, ModifierTier.MASTER], + guaranteedModifierTiers: [RewardTier.MASTER, RewardTier.MASTER], }; const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); scene.phaseManager.unshiftPhase(selectModifierPhase); @@ -250,8 +250,8 @@ describe("SelectModifierPhase", () => { expect(modifierSelectHandler.options.length).toEqual(4); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("TM_COMMON"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.tier).toEqual(ModifierTier.MASTER); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.tier).toEqual(ModifierTier.MASTER); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.tier).toEqual(RewardTier.MASTER); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.tier).toEqual(RewardTier.MASTER); }); it("should fill remaining modifiers if fillRemaining is true with custom modifiers", async () => { @@ -259,7 +259,7 @@ describe("SelectModifierPhase", () => { scene.money = 1000000; const customModifiers: CustomModifierSettings = { guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM], - guaranteedModifierTiers: [ModifierTier.MASTER], + guaranteedModifierTiers: [RewardTier.MASTER], fillRemaining: true, }; const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); @@ -273,6 +273,6 @@ describe("SelectModifierPhase", () => { ) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.tier).toEqual(ModifierTier.MASTER); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.tier).toEqual(RewardTier.MASTER); }); }); diff --git a/test/testUtils/gameManager.ts b/test/testUtils/gameManager.ts index 91f9096f3d4..e8ce3e430b7 100644 --- a/test/testUtils/gameManager.ts +++ b/test/testUtils/gameManager.ts @@ -542,8 +542,7 @@ export default class GameManager { * Removes all held items from enemy pokemon. */ removeEnemyHeldItems(): void { - this.scene.clearEnemyHeldItemModifiers(); - this.scene.clearEnemyModifiers(); + this.scene.clearEnemyItems(); console.log("Enemy held items removed"); } } diff --git a/test/testUtils/helpers/overridesHelper.ts b/test/testUtils/helpers/overridesHelper.ts index 8bf68489479..69b319eb40a 100644 --- a/test/testUtils/helpers/overridesHelper.ts +++ b/test/testUtils/helpers/overridesHelper.ts @@ -20,6 +20,8 @@ import type { Unlockables } from "#enums/unlockables"; import { WeatherType } from "#enums/weather-type"; import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper"; import { expect, vi } from "vitest"; +import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; +import type { TrainerItemConfiguration } from "#app/items/trainer-item-data-types"; /** * Helper to handle overrides in tests @@ -115,9 +117,20 @@ export class OverridesHelper extends GameManagerHelper { * @param items - The items to hold * @returns `this` */ - public startingHeldItems(items: ModifierOverride[]): this { - vi.spyOn(Overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue(items); - this.log("Player Pokemon starting held items set to:", items); + public startingHeldItems(itemConfiguration: HeldItemConfiguration): this { + vi.spyOn(Overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue(itemConfiguration); + this.log("Player Pokemon starting held items set to:", itemConfiguration); + return this; + } + + /** + * Override the player's starting trainer items + * @param items - The items to have + * @returns `this` + */ + public startingTrainerItems(itemConfiguration: TrainerItemConfiguration): this { + vi.spyOn(Overrides, "STARTING_TRAINER_ITEMS_OVERRIDE", "get").mockReturnValue(itemConfiguration); + this.log("Player starting trainer items set to:", itemConfiguration); return this; } @@ -167,17 +180,6 @@ export class OverridesHelper extends GameManagerHelper { return this; } - /** - * Override the player's starting modifiers - * @param modifiers - The modifiers to set - * @returns `this` - */ - public startingModifier(modifiers: ModifierOverride[]): this { - vi.spyOn(Overrides, "STARTING_MODIFIER_OVERRIDE", "get").mockReturnValue(modifiers); - this.log(`Player starting modifiers set to: ${modifiers}`); - return this; - } - /** * Override the player pokemon's {@linkcode AbilityId | ability}. * @param ability - The {@linkcode AbilityId | ability} to set @@ -509,9 +511,20 @@ export class OverridesHelper extends GameManagerHelper { * @param items the items to hold * @returns `this` */ - public enemyHeldItems(items: ModifierOverride[]): this { - vi.spyOn(Overrides, "OPP_HELD_ITEMS_OVERRIDE", "get").mockReturnValue(items); - this.log("Enemy Pokemon held items set to:", items); + public enemyHeldItems(itemConfiguration: HeldItemConfiguration): this { + vi.spyOn(Overrides, "OPP_HELD_ITEMS_OVERRIDE", "get").mockReturnValue(itemConfiguration); + this.log("Enemy Pokemon held items set to:", itemConfiguration); + return this; + } + + /** + * Override the enemy's trainer items + * @param items - The items to have + * @returns `this` + */ + public enemyTrainerItems(itemConfiguration: TrainerItemConfiguration): this { + vi.spyOn(Overrides, "OPP_TRAINER_ITEMS_OVERRIDE", "get").mockReturnValue(itemConfiguration); + this.log("Enemy trainer items set to:", itemConfiguration); return this; } diff --git a/test/testUtils/helpers/reloadHelper.ts b/test/testUtils/helpers/reloadHelper.ts index 4f9d6c810f8..1715bee5c1c 100644 --- a/test/testUtils/helpers/reloadHelper.ts +++ b/test/testUtils/helpers/reloadHelper.ts @@ -46,16 +46,6 @@ export class ReloadHelper extends GameManagerHelper { scene.phaseManager.unshiftPhase(titlePhase); this.game.endPhase(); // End the currently ongoing battle - // remove all persistent mods before loading - // TODO: Look into why these aren't removed before load - if (this.game.scene.modifiers.length) { - console.log( - "Removing %d modifiers from scene on load...", - this.game.scene.modifiers.length, - this.game.scene.modifiers, - ); - this.game.scene.modifiers = []; - } titlePhase.loadSaveSlot(-1); // Load the desired session data this.game.phaseInterceptor.shift(); // Loading the save slot also ended TitlePhase, clean it up diff --git a/test/testUtils/testFileInitialization.ts b/test/testUtils/testFileInitialization.ts index 578747b0529..ed50f6a3683 100644 --- a/test/testUtils/testFileInitialization.ts +++ b/test/testUtils/testFileInitialization.ts @@ -24,6 +24,10 @@ import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { manageListeners } from "./listenersManager"; import { initI18n } from "#app/plugins/i18n"; import { initModifierTypes } from "#app/modifier/modifier-type"; +import { initHeldItems } from "#app/items/all-held-items"; +import { initHeldItemPools } from "#app/items/init-held-item-pools"; +import { initTrainerItems } from "#app/items/all-trainer-items"; +import { initTrainerItemPools } from "#app/items/init-trainer-item-pools"; let wasInitialized = false; /** @@ -90,6 +94,10 @@ export function initTestFile() { if (!wasInitialized) { wasInitialized = true; initI18n(); + initHeldItems(); + initHeldItemPools(); + initTrainerItems(); + initTrainerItemPools(); initModifierTypes(); initModifierPools(); initVouchers(); From 1f77eb4dff6f286f1cc4243ea6761344125400b7 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 10 Jul 2025 01:25:02 -0700 Subject: [PATCH 02/39] Add `as const` to enum-objects in `held-item-id.ts` --- src/enums/held-item-id.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index 944d76b0a0f..dc9eedf29c5 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -90,7 +90,7 @@ export const HeldItemId = { // Evo trackers GIMMIGHOUL_EVO_TRACKER: 0x0A01, -}; +} as const; export type HeldItemId = (typeof HeldItemId)[keyof typeof HeldItemId]; @@ -106,7 +106,6 @@ export const HeldItemNames: Record = Object.entries {} as Record ); - export const HeldItemCategoryId = { NONE: 0x0000, BERRY: 0x0100, @@ -119,14 +118,14 @@ export const HeldItemCategoryId = { VITAMIN: 0x0800, BASE_STAT_BOOST: 0x0900, EVO_TRACKER: 0x0A00, -}; +} as const; export type HeldItemCategoryId = (typeof HeldItemCategoryId)[keyof typeof HeldItemCategoryId]; const ITEM_CATEGORY_MASK = 0xFF00 export function getHeldItemCategory(itemId: HeldItemId): HeldItemCategoryId { - return itemId & ITEM_CATEGORY_MASK; + return (itemId & ITEM_CATEGORY_MASK) as HeldItemCategoryId; } export function isCategoryId(categoryId: HeldItemCategoryId): boolean { From e64117d6a664ab0bf3082575288ea9136a0f8895 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 10 Jul 2025 01:26:00 -0700 Subject: [PATCH 03/39] Fix `isItemInRequested` --- src/enums/held-item-id.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index dc9eedf29c5..186052c747b 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -140,7 +140,5 @@ export function isItemInRequested( itemId: HeldItemId, requestedItems: (HeldItemCategoryId | HeldItemId)[] ): boolean { - return requestedItems.some(entry => { - itemId === entry || (itemId & ITEM_CATEGORY_MASK) === entry - }); + return requestedItems.some(entry => itemId === entry || (itemId & ITEM_CATEGORY_MASK) === entry); } From 79779765e2b725d650c77cd624dae4baeec990e5 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 10 Jul 2025 01:27:54 -0700 Subject: [PATCH 04/39] Add `as const` in `trainer-item-id.ts` --- src/enums/held-item-id.ts | 2 +- src/enums/trainer-item-id.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index 186052c747b..c5f101dad5c 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -95,7 +95,7 @@ export const HeldItemId = { export type HeldItemId = (typeof HeldItemId)[keyof typeof HeldItemId]; type HeldItemName = keyof typeof HeldItemId; -type HeldItemValue = typeof HeldItemId[HeldItemName]; +type HeldItemValue = typeof HeldItemId[HeldItemName]; // equivalent to `HeldItemId` // Use a type-safe reducer to force number keys and values export const HeldItemNames: Record = Object.entries(HeldItemId).reduce( diff --git a/src/enums/trainer-item-id.ts b/src/enums/trainer-item-id.ts index 8069da2836e..032c8933eb8 100644 --- a/src/enums/trainer-item-id.ts +++ b/src/enums/trainer-item-id.ts @@ -51,7 +51,7 @@ export const TrainerItemId = { ENEMY_STATUS_EFFECT_HEAL_CHANCE: 0x0E07, ENEMY_ENDURE_CHANCE: 0x0E08, ENEMY_FUSED_CHANCE: 0x0E09, -}; +} as const; export type TrainerItemId = (typeof TrainerItemId)[keyof typeof TrainerItemId]; From d576d66617966ee2be1563d33961a0b1fcd582ff Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 10 Jul 2025 01:31:55 -0700 Subject: [PATCH 05/39] Rename `HELD_ITEM_EFFECT` enum-object to `HeldItemEffect` --- src/battle-scene.ts | 4 +- src/data/moves/move.ts | 47 ++++++++------- src/field/arena.ts | 6 +- src/field/pokemon.ts | 26 ++++----- src/items/all-held-items.ts | 58 +++++++++---------- src/items/held-item.ts | 5 +- src/items/held-items/accuracy-booster.ts | 4 +- src/items/held-items/attack-type-booster.ts | 4 +- src/items/held-items/base-stat-booster.ts | 4 +- src/items/held-items/base-stat-flat.ts | 4 +- src/items/held-items/base-stat-total.ts | 4 +- src/items/held-items/baton.ts | 4 +- src/items/held-items/berry.ts | 4 +- src/items/held-items/bypass-speed-chance.ts | 4 +- src/items/held-items/crit-booster.ts | 4 +- src/items/held-items/damage-money-reward.ts | 4 +- src/items/held-items/evo-tracker.ts | 4 +- src/items/held-items/exp-booster.ts | 4 +- src/items/held-items/field-effect.ts | 4 +- src/items/held-items/flinch-chance.ts | 4 +- src/items/held-items/friendship-booster.ts | 4 +- src/items/held-items/hit-heal.ts | 4 +- src/items/held-items/incrementing-stat.ts | 4 +- src/items/held-items/instant-revive.ts | 4 +- src/items/held-items/item-steal.ts | 6 +- src/items/held-items/multi-hit.ts | 4 +- src/items/held-items/nature-weight-booster.ts | 4 +- .../held-items/reset-negative-stat-stage.ts | 4 +- src/items/held-items/stat-booster.ts | 4 +- src/items/held-items/survive-chance.ts | 4 +- src/items/held-items/turn-end-heal.ts | 4 +- src/items/held-items/turn-end-status.ts | 4 +- src/phases/berry-phase.ts | 4 +- src/phases/faint-phase.ts | 4 +- src/phases/move-effect-phase.ts | 12 ++-- src/phases/stat-stage-change-phase.ts | 4 +- src/phases/turn-end-phase.ts | 8 +-- src/phases/turn-start-phase.ts | 4 +- test/items/eviolite.test.ts | 16 ++--- test/items/exp_booster.test.ts | 4 +- test/items/light_ball.test.ts | 34 +++++------ 41 files changed, 171 insertions(+), 171 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 5fa6df23051..51b72d82234 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -141,7 +141,7 @@ import { timedEventManager } from "./global-event-manager"; import { starterColors } from "./global-vars/starter-colors"; import { startingWave } from "./starting-wave"; import { applyHeldItems } from "./items/all-held-items"; -import { HELD_ITEM_EFFECT } from "./items/held-item"; +import { HeldItemEffect } from "./items/held-item"; import { PhaseManager } from "./phase-manager"; import { HeldItemId } from "#enums/held-item-id"; import { assignEnemyHeldItemsForWave, assignItemsFromConfiguration } from "./items/held-item-pool"; @@ -3222,7 +3222,7 @@ export default class BattleScene extends SceneBase { expMultiplier = Overrides.XP_MULTIPLIER_OVERRIDE; } const pokemonExp = new NumberHolder(expValue * expMultiplier); - applyHeldItems(HELD_ITEM_EFFECT.EXP_BOOSTER, { pokemon: partyMember, expAmount: pokemonExp }); + applyHeldItems(HeldItemEffect.EXP_BOOSTER, { pokemon: partyMember, expAmount: pokemonExp }); partyMemberExp.push(Math.floor(pokemonExp.value)); } diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 04df60f0251..c38418ca510 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -1,16 +1,16 @@ import { MoveChargeAnim } from "../battle-anims"; import { ChargeAnim } from "#enums/move-anims-common"; import { - CommandedTag, - EncoreTag, - GulpMissileTag, - HelpingHandTag, - SemiInvulnerableTag, - ShellTrapTag, - StockpilingTag, - SubstituteTag, - TrappedTag, - TypeBoostTag, + CommandedTag, + EncoreTag, + GulpMissileTag, + HelpingHandTag, + SemiInvulnerableTag, + ShellTrapTag, + StockpilingTag, + SubstituteTag, + TrappedTag, + TypeBoostTag, } from "../battler-tags"; import { getPokemonNameWithAffix } from "../../messages"; import type { TurnMove } from "#app/@types/turn-move"; @@ -22,9 +22,9 @@ import { MoveResult } from "#enums/move-result"; import { HitResult } from "#enums/hit-result"; import { FieldPosition } from "#enums/field-position"; import { - getNonVolatileStatusEffects, - getStatusEffectHealText, - isNonVolatileStatusEffect, + getNonVolatileStatusEffects, + getStatusEffectHealText, + isNonVolatileStatusEffect, } from "../status-effect"; import { getTypeDamageMultiplier } from "../type"; import { PokemonType } from "#enums/pokemon-type"; @@ -34,13 +34,12 @@ import type { ArenaTrapTag } from "../arena-tag"; import { WeakenMoveTypeTag } from "../arena-tag"; import { ArenaTagSide } from "#enums/arena-tag-side"; import { - applyAbAttrs + applyAbAttrs } from "../abilities/apply-ab-attrs"; import { allAbilities, allHeldItems, allMoves } from "../data-lists"; import type { BattlerIndex } from "#enums/battler-index"; import { BattleType } from "#enums/battle-type"; import { TerrainType } from "../terrain"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; import { Command } from "#enums/command"; import i18next from "i18next"; import type { Localizable } from "#app/@types/locales"; @@ -53,11 +52,11 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { MoveUsedEvent } from "#app/events/battle-scene"; import { - BATTLE_STATS, - type BattleStat, - type EffectiveStat, - getStatKey, - Stat, + BATTLE_STATS, + type BattleStat, + type EffectiveStat, + getStatKey, + Stat, } from "#app/enums/stat"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { MovePhase } from "#app/phases/move-phase"; @@ -78,14 +77,14 @@ import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MultiHitType } from "#enums/MultiHitType"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves, invalidSketchMoves } from "./invalid-moves"; import { isVirtual, MoveUseMode } from "#enums/move-use-mode"; -import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect } from "#app/items/held-item"; import { BerryHeldItem, berryTypeToHeldItem } from "#app/items/held-items/berry"; import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id"; import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap } from "#app/@types/move-types"; import { applyMoveAttrs } from "./apply-attrs"; import { frenzyMissFunc, getMoveTargets } from "./move-utils"; import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; -import { AbAttrBaseParams, AbAttrParamsWithCancel, PreAttackModifyPowerAbAttrParams } from "../abilities/ability"; +import { AbAttrParamsWithCancel, PreAttackModifyPowerAbAttrParams } from "../abilities/ability"; import { applyHeldItems } from "#app/items/all-held-items"; /** @@ -766,7 +765,7 @@ export default abstract class Move implements Localizable { const isOhko = this.hasAttr("OneHitKOAccuracyAttr"); if (!isOhko) { - applyHeldItems(HELD_ITEM_EFFECT.ACCURACY_BOOSTER, { pokemon: user, moveAccuracy: moveAccuracy }); + applyHeldItems(HeldItemEffect.ACCURACY_BOOSTER, { pokemon: user, moveAccuracy: moveAccuracy }); } if (globalScene.arena.weather?.weatherType === WeatherType.FOG) { @@ -848,7 +847,7 @@ export default abstract class Move implements Localizable { if (!this.hasAttr("TypelessAttr")) { globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power); - applyHeldItems(HELD_ITEM_EFFECT.ATTACK_TYPE_BOOST, { + applyHeldItems(HeldItemEffect.ATTACK_TYPE_BOOST, { pokemon: source, moveType: typeChangeHolder.value, movePower: power, diff --git a/src/field/arena.ts b/src/field/arena.ts index 08ed42ff72a..86e04117a51 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -35,7 +35,7 @@ import { } from "#app/data/pokemon-forms/form-change-triggers"; import { WeatherType } from "#enums/weather-type"; import { applyHeldItems } from "#app/items/all-held-items"; -import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect } from "#app/items/held-item"; export class Arena { public biomeType: BiomeId; @@ -339,7 +339,7 @@ export class Arena { if (!isNullOrUndefined(user)) { weatherDuration.value = 5; - applyHeldItems(HELD_ITEM_EFFECT.FIELD_EFFECT, { pokemon: user, fieldDuration: weatherDuration }); + applyHeldItems(HeldItemEffect.FIELD_EFFECT, { pokemon: user, fieldDuration: weatherDuration }); } this.weather = weather ? new Weather(weather, weatherDuration.value) : null; @@ -426,7 +426,7 @@ export class Arena { if (!isNullOrUndefined(user)) { terrainDuration.value = 5; - applyHeldItems(HELD_ITEM_EFFECT.FIELD_EFFECT, { pokemon: user, fieldDuration: terrainDuration }); + applyHeldItems(HeldItemEffect.FIELD_EFFECT, { pokemon: user, fieldDuration: terrainDuration }); } this.terrain = terrain ? new Terrain(terrain, terrainDuration.value) : null; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index aca18a02ef8..8cea1eadd82 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -154,7 +154,7 @@ import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { PokemonItemManager } from "./pokemon-held-item-manager"; import { applyHeldItems } from "#app/items/all-held-items"; -import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect } from "#app/items/held-item"; import { HeldItemId } from "#enums/held-item-id"; import { isVirtual, isIgnorePP, MoveUseMode } from "#enums/move-use-mode"; import { FieldPosition } from "#enums/field-position"; @@ -1346,7 +1346,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getCritStage(source: Pokemon, move: Move): number { const critStage = new NumberHolder(0); applyMoveAttrs("HighCritAttr", source, this, move, critStage); - applyHeldItems(HELD_ITEM_EFFECT.CRIT_BOOST, { pokemon: source, critStage: critStage }); + applyHeldItems(HeldItemEffect.CRIT_BOOST, { pokemon: source, critStage: critStage }); globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.TEMP_CRIT_BOOSTER, { numberHolder: critStage }); applyAbAttrs("BonusCritAbAttr", { pokemon: source, critStage }); const critBoostTag = source.getTag(CritBoostTag); @@ -1401,7 +1401,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ): number { const statVal = new NumberHolder(this.getStat(stat, false)); if (!ignoreHeldItems) { - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: this, stat: stat, statValue: statVal }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: this, stat: stat, statValue: statVal }); } // The Ruin abilities here are never ignored, but they reveal themselves on summon anyway @@ -1509,7 +1509,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const statHolder = new NumberHolder(Math.floor((2 * baseStats[s] + this.ivs[s]) * this.level * 0.01)); if (s === Stat.HP) { statHolder.value = statHolder.value + this.level + 10; - applyHeldItems(HELD_ITEM_EFFECT.INCREMENTING_STAT, { pokemon: this, stat: s, statHolder: statHolder }); + applyHeldItems(HeldItemEffect.INCREMENTING_STAT, { pokemon: this, stat: s, statHolder: statHolder }); if (this.hasAbility(AbilityId.WONDER_GUARD, false, true)) { statHolder.value = 1; } @@ -1524,14 +1524,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } else { statHolder.value += 5; const natureStatMultiplier = new NumberHolder(getNatureStatMultiplier(this.getNature(), s)); - applyHeldItems(HELD_ITEM_EFFECT.NATURE_WEIGHT_BOOSTER, { pokemon: this, multiplier: natureStatMultiplier }); + applyHeldItems(HeldItemEffect.NATURE_WEIGHT_BOOSTER, { pokemon: this, multiplier: natureStatMultiplier }); if (natureStatMultiplier.value !== 1) { statHolder.value = Math.max( Math[natureStatMultiplier.value > 1 ? "ceil" : "floor"](statHolder.value * natureStatMultiplier.value), 1, ); } - applyHeldItems(HELD_ITEM_EFFECT.INCREMENTING_STAT, { pokemon: this, stat: s, statHolder: statHolder }); + applyHeldItems(HeldItemEffect.INCREMENTING_STAT, { pokemon: this, stat: s, statHolder: statHolder }); } statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER); @@ -1544,9 +1544,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const baseStats = this.getSpeciesForm(true).baseStats.slice(0); applyChallenges(ChallengeType.FLIP_STAT, this, baseStats); // Shuckle Juice - applyHeldItems(HELD_ITEM_EFFECT.BASE_STAT_TOTAL, { pokemon: this, baseStats: baseStats }); + applyHeldItems(HeldItemEffect.BASE_STAT_TOTAL, { pokemon: this, baseStats: baseStats }); // Old Gateau - applyHeldItems(HELD_ITEM_EFFECT.BASE_STAT_FLAT, { pokemon: this, baseStats: baseStats }); + applyHeldItems(HeldItemEffect.BASE_STAT_FLAT, { pokemon: this, baseStats: baseStats }); if (this.isFusion()) { const fusionBaseStats = this.getFusionSpeciesForm(true).baseStats; applyChallenges(ChallengeType.FLIP_STAT, this, fusionBaseStats); @@ -1560,7 +1560,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } // Vitamins - applyHeldItems(HELD_ITEM_EFFECT.BASE_STAT_BOOSTER, { pokemon: this, baseStats: baseStats }); + applyHeldItems(HeldItemEffect.BASE_STAT_BOOSTER, { pokemon: this, baseStats: baseStats }); return baseStats; } @@ -3707,7 +3707,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs("FixedDamageAttr", source, this, move, fixedDamage); if (fixedDamage.value) { const multiLensMultiplier = new NumberHolder(1); - applyHeldItems(HELD_ITEM_EFFECT.MULTI_HIT, { + applyHeldItems(HeldItemEffect.MULTI_HIT, { pokemon: source, moveId: move.id, damageMultiplier: multiLensMultiplier, @@ -3755,7 +3755,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Multiplier for moves enhanced by Multi-Lens and/or Parental Bond */ const multiStrikeEnhancementMultiplier = new NumberHolder(1); - applyHeldItems(HELD_ITEM_EFFECT.MULTI_HIT, { + applyHeldItems(HeldItemEffect.MULTI_HIT, { pokemon: source, moveId: move.id, damageMultiplier: multiStrikeEnhancementMultiplier, @@ -3993,7 +3993,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { surviveDamage.value = this.lapseTag(BattlerTagType.ENDURE_TOKEN); } if (!surviveDamage.value) { - applyHeldItems(HELD_ITEM_EFFECT.SURVIVE_CHANCE, { pokemon: this, surviveDamage: surviveDamage }); + applyHeldItems(HeldItemEffect.SURVIVE_CHANCE, { pokemon: this, surviveDamage: surviveDamage }); } if (surviveDamage.value) { damage = this.hp - 1; @@ -5748,7 +5748,7 @@ export class PlayerPokemon extends Pokemon { fusionStarterSpeciesId ? globalScene.gameData.starterData[fusionStarterSpeciesId] : null, ].filter(d => !!d); const amount = new NumberHolder(friendship); - applyHeldItems(HELD_ITEM_EFFECT.FRIENDSHIP_BOOSTER, { pokemon: this, friendship: amount }); + applyHeldItems(HeldItemEffect.FRIENDSHIP_BOOSTER, { pokemon: this, friendship: amount }); const candyFriendshipMultiplier = globalScene.gameMode.isClassic ? timedEventManager.getClassicFriendshipMultiplier() : 1; diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 8d6f7b37a4c..54c0682a5c9 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -6,7 +6,7 @@ import type { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; import { Stat, type PermanentStat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; -import { HELD_ITEM_EFFECT } from "./held-item"; +import { HeldItemEffect } from "./held-item"; import { type ACCURACY_BOOST_PARAMS, AccuracyBoosterHeldItem } from "./held-items/accuracy-booster"; import { type ATTACK_TYPE_BOOST_PARAMS, @@ -174,36 +174,36 @@ export function initHeldItems() { } type APPLY_HELD_ITEMS_PARAMS = { - [HELD_ITEM_EFFECT.ATTACK_TYPE_BOOST]: ATTACK_TYPE_BOOST_PARAMS; - [HELD_ITEM_EFFECT.TURN_END_HEAL]: TURN_END_HEAL_PARAMS; - [HELD_ITEM_EFFECT.HIT_HEAL]: HIT_HEAL_PARAMS; - [HELD_ITEM_EFFECT.RESET_NEGATIVE_STAT_STAGE]: RESET_NEGATIVE_STAT_STAGE_PARAMS; - [HELD_ITEM_EFFECT.EXP_BOOSTER]: EXP_BOOST_PARAMS; - [HELD_ITEM_EFFECT.BERRY]: BERRY_PARAMS; - [HELD_ITEM_EFFECT.BASE_STAT_BOOSTER]: BASE_STAT_BOOSTER_PARAMS; - [HELD_ITEM_EFFECT.INSTANT_REVIVE]: INSTANT_REVIVE_PARAMS; - [HELD_ITEM_EFFECT.STAT_BOOST]: STAT_BOOST_PARAMS; - [HELD_ITEM_EFFECT.CRIT_BOOST]: CRIT_BOOST_PARAMS; - [HELD_ITEM_EFFECT.TURN_END_STATUS]: TURN_END_STATUS_PARAMS; - [HELD_ITEM_EFFECT.SURVIVE_CHANCE]: SURVIVE_CHANCE_PARAMS; - [HELD_ITEM_EFFECT.BYPASS_SPEED_CHANCE]: BYPASS_SPEED_CHANCE_PARAMS; - [HELD_ITEM_EFFECT.FLINCH_CHANCE]: FLINCH_CHANCE_PARAMS; - [HELD_ITEM_EFFECT.FIELD_EFFECT]: FIELD_EFFECT_PARAMS; - [HELD_ITEM_EFFECT.FRIENDSHIP_BOOSTER]: FRIENDSHIP_BOOST_PARAMS; - [HELD_ITEM_EFFECT.NATURE_WEIGHT_BOOSTER]: NATURE_WEIGHT_BOOST_PARAMS; - [HELD_ITEM_EFFECT.ACCURACY_BOOSTER]: ACCURACY_BOOST_PARAMS; - [HELD_ITEM_EFFECT.MULTI_HIT]: MULTI_HIT_PARAMS; - [HELD_ITEM_EFFECT.DAMAGE_MONEY_REWARD]: DAMAGE_MONEY_REWARD_PARAMS; - [HELD_ITEM_EFFECT.BATON]: BATON_PARAMS; - [HELD_ITEM_EFFECT.CONTACT_ITEM_STEAL_CHANCE]: ITEM_STEAL_PARAMS; - [HELD_ITEM_EFFECT.TURN_END_ITEM_STEAL]: ITEM_STEAL_PARAMS; - [HELD_ITEM_EFFECT.BASE_STAT_TOTAL]: BASE_STAT_TOTAL_PARAMS; - [HELD_ITEM_EFFECT.BASE_STAT_FLAT]: BASE_STAT_FLAT_PARAMS; - [HELD_ITEM_EFFECT.INCREMENTING_STAT]: INCREMENTING_STAT_PARAMS; - [HELD_ITEM_EFFECT.EVO_TRACKER]: EVO_TRACKER_PARAMS; + [HeldItemEffect.ATTACK_TYPE_BOOST]: ATTACK_TYPE_BOOST_PARAMS; + [HeldItemEffect.TURN_END_HEAL]: TURN_END_HEAL_PARAMS; + [HeldItemEffect.HIT_HEAL]: HIT_HEAL_PARAMS; + [HeldItemEffect.RESET_NEGATIVE_STAT_STAGE]: RESET_NEGATIVE_STAT_STAGE_PARAMS; + [HeldItemEffect.EXP_BOOSTER]: EXP_BOOST_PARAMS; + [HeldItemEffect.BERRY]: BERRY_PARAMS; + [HeldItemEffect.BASE_STAT_BOOSTER]: BASE_STAT_BOOSTER_PARAMS; + [HeldItemEffect.INSTANT_REVIVE]: INSTANT_REVIVE_PARAMS; + [HeldItemEffect.STAT_BOOST]: STAT_BOOST_PARAMS; + [HeldItemEffect.CRIT_BOOST]: CRIT_BOOST_PARAMS; + [HeldItemEffect.TURN_END_STATUS]: TURN_END_STATUS_PARAMS; + [HeldItemEffect.SURVIVE_CHANCE]: SURVIVE_CHANCE_PARAMS; + [HeldItemEffect.BYPASS_SPEED_CHANCE]: BYPASS_SPEED_CHANCE_PARAMS; + [HeldItemEffect.FLINCH_CHANCE]: FLINCH_CHANCE_PARAMS; + [HeldItemEffect.FIELD_EFFECT]: FIELD_EFFECT_PARAMS; + [HeldItemEffect.FRIENDSHIP_BOOSTER]: FRIENDSHIP_BOOST_PARAMS; + [HeldItemEffect.NATURE_WEIGHT_BOOSTER]: NATURE_WEIGHT_BOOST_PARAMS; + [HeldItemEffect.ACCURACY_BOOSTER]: ACCURACY_BOOST_PARAMS; + [HeldItemEffect.MULTI_HIT]: MULTI_HIT_PARAMS; + [HeldItemEffect.DAMAGE_MONEY_REWARD]: DAMAGE_MONEY_REWARD_PARAMS; + [HeldItemEffect.BATON]: BATON_PARAMS; + [HeldItemEffect.CONTACT_ITEM_STEAL_CHANCE]: ITEM_STEAL_PARAMS; + [HeldItemEffect.TURN_END_ITEM_STEAL]: ITEM_STEAL_PARAMS; + [HeldItemEffect.BASE_STAT_TOTAL]: BASE_STAT_TOTAL_PARAMS; + [HeldItemEffect.BASE_STAT_FLAT]: BASE_STAT_FLAT_PARAMS; + [HeldItemEffect.INCREMENTING_STAT]: INCREMENTING_STAT_PARAMS; + [HeldItemEffect.EVO_TRACKER]: EVO_TRACKER_PARAMS; }; -export function applyHeldItems(effect: T, params: APPLY_HELD_ITEMS_PARAMS[T]) { +export function applyHeldItems(effect: T, params: APPLY_HELD_ITEMS_PARAMS[T]) { const pokemon = params.pokemon; if (pokemon) { for (const item of Object.keys(pokemon.heldItemManager.heldItems)) { diff --git a/src/items/held-item.ts b/src/items/held-item.ts index d317c1ec0fd..6fdfc0dd6e7 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -4,7 +4,8 @@ import { globalScene } from "#app/global-scene"; import { HeldItemNames, type HeldItemId } from "#enums/held-item-id"; import i18next from "i18next"; -export const HELD_ITEM_EFFECT = { +// TODO: this should be moved to its own file +export const HeldItemEffect = { ATTACK_TYPE_BOOST: 1, TURN_END_HEAL: 2, HIT_HEAL: 3, @@ -35,7 +36,7 @@ export const HELD_ITEM_EFFECT = { INCREMENTING_STAT: 52, } as const; -export type HELD_ITEM_EFFECT = (typeof HELD_ITEM_EFFECT)[keyof typeof HELD_ITEM_EFFECT]; +export type HeldItemEffect = (typeof HeldItemEffect)[keyof typeof HeldItemEffect]; export class HeldItem { // public pokemonId: number; diff --git a/src/items/held-items/accuracy-booster.ts b/src/items/held-items/accuracy-booster.ts index cba45f29c24..0dcc6293a95 100644 --- a/src/items/held-items/accuracy-booster.ts +++ b/src/items/held-items/accuracy-booster.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; export interface ACCURACY_BOOST_PARAMS { /** The pokemon with the item */ @@ -11,7 +11,7 @@ export interface ACCURACY_BOOST_PARAMS { } export class AccuracyBoosterHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.ACCURACY_BOOSTER]; + public effects: HeldItemEffect[] = [HeldItemEffect.ACCURACY_BOOSTER]; private accuracyAmount: number; diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index 6fd56721d5c..09d4f3e9c50 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -3,7 +3,7 @@ import { PokemonType } from "#enums/pokemon-type"; import i18next from "i18next"; import type { NumberHolder } from "#app/utils/common"; import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect, HeldItem } from "#app/items/held-item"; export interface ATTACK_TYPE_BOOST_PARAMS { /** The pokemon with the item */ @@ -41,7 +41,7 @@ export const attackTypeToHeldItem: AttackTypeToHeldItemMap = { }; export class AttackTypeBoosterHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.TURN_END_HEAL]; + public effects: HeldItemEffect[] = [HeldItemEffect.TURN_END_HEAL]; public moveType: PokemonType; public powerBoost: number; diff --git a/src/items/held-items/base-stat-booster.ts b/src/items/held-items/base-stat-booster.ts index e5075c41a15..1595c25b986 100644 --- a/src/items/held-items/base-stat-booster.ts +++ b/src/items/held-items/base-stat-booster.ts @@ -2,7 +2,7 @@ import type Pokemon from "#app/field/pokemon"; import { HeldItemId } from "#enums/held-item-id"; import { getStatKey, type PermanentStat, Stat } from "#enums/stat"; import i18next from "i18next"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; export interface BASE_STAT_BOOSTER_PARAMS { /** The pokemon with the item */ @@ -33,7 +33,7 @@ export const statBoostItems: Record = { }; export class BaseStatBoosterHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.BASE_STAT_BOOSTER]; + public effects: HeldItemEffect[] = [HeldItemEffect.BASE_STAT_BOOSTER]; public stat: PermanentStat; constructor(type: HeldItemId, maxStackCount = 1, stat: PermanentStat) { diff --git a/src/items/held-items/base-stat-flat.ts b/src/items/held-items/base-stat-flat.ts index 75c12b1f0f5..80285d91716 100644 --- a/src/items/held-items/base-stat-flat.ts +++ b/src/items/held-items/base-stat-flat.ts @@ -1,5 +1,5 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; import { Stat } from "#enums/stat"; import i18next from "i18next"; @@ -14,7 +14,7 @@ export interface BASE_STAT_FLAT_PARAMS { * Currently used by Old Gateau item */ export class BaseStatFlatHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.BASE_STAT_FLAT]; + public effects: HeldItemEffect[] = [HeldItemEffect.BASE_STAT_FLAT]; public isTransferable = false; get description(): string { diff --git a/src/items/held-items/base-stat-total.ts b/src/items/held-items/base-stat-total.ts index b48ab0f3430..d54602c227a 100644 --- a/src/items/held-items/base-stat-total.ts +++ b/src/items/held-items/base-stat-total.ts @@ -1,6 +1,6 @@ import type Pokemon from "#app/field/pokemon"; import i18next from "i18next"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; import type { HeldItemId } from "#enums/held-item-id"; export interface BASE_STAT_TOTAL_PARAMS { @@ -14,7 +14,7 @@ export interface BASE_STAT_TOTAL_PARAMS { * Currently used by Shuckle Juice item */ export class BaseStatTotalHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.BASE_STAT_TOTAL]; + public effects: HeldItemEffect[] = [HeldItemEffect.BASE_STAT_TOTAL]; public isTransferable = false; public statModifier: number; diff --git a/src/items/held-items/baton.ts b/src/items/held-items/baton.ts index 0b36f6c77c7..1045b3b5a9d 100644 --- a/src/items/held-items/baton.ts +++ b/src/items/held-items/baton.ts @@ -1,6 +1,6 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; export interface BATON_PARAMS { /** The pokemon with the item */ @@ -10,7 +10,7 @@ export interface BATON_PARAMS { } export class BatonHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.BATON]; + public effects: HeldItemEffect[] = [HeldItemEffect.BATON]; /** * Applies {@linkcode SwitchEffectTransferModifier} diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts index f93df60c8a5..68e4a5b10bb 100644 --- a/src/items/held-items/berry.ts +++ b/src/items/held-items/berry.ts @@ -2,7 +2,7 @@ import { getBerryEffectDescription, getBerryEffectFunc, getBerryName, getBerryPr import { BerryUsedEvent } from "#app/events/battle-scene"; import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import { ConsumableHeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { ConsumableHeldItem, HeldItemEffect } from "#app/items/held-item"; import { BooleanHolder } from "#app/utils/common"; import { BerryType } from "#enums/berry-type"; import { HeldItemId } from "#enums/held-item-id"; @@ -33,7 +33,7 @@ export interface BERRY_PARAMS { // TODO: Maybe split up into subclasses? export class BerryHeldItem extends ConsumableHeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.BERRY]; + public effects: HeldItemEffect[] = [HeldItemEffect.BERRY]; public berryType: BerryType; constructor(berryType: BerryType, maxStackCount = 1) { diff --git a/src/items/held-items/bypass-speed-chance.ts b/src/items/held-items/bypass-speed-chance.ts index c454463a14e..c7734b1ca48 100644 --- a/src/items/held-items/bypass-speed-chance.ts +++ b/src/items/held-items/bypass-speed-chance.ts @@ -1,5 +1,5 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect, HeldItem } from "#app/items/held-item"; import type { BooleanHolder } from "#app/utils/common"; import { globalScene } from "#app/global-scene"; import i18next from "i18next"; @@ -19,7 +19,7 @@ export interface BYPASS_SPEED_CHANCE_PARAMS { * @see {@linkcode apply} */ export class BypassSpeedChanceHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.BYPASS_SPEED_CHANCE]; + public effects: HeldItemEffect[] = [HeldItemEffect.BYPASS_SPEED_CHANCE]; /** * Checks if {@linkcode BypassSpeedChanceModifier} should be applied diff --git a/src/items/held-items/crit-booster.ts b/src/items/held-items/crit-booster.ts index cc305b33640..646a3221a80 100644 --- a/src/items/held-items/crit-booster.ts +++ b/src/items/held-items/crit-booster.ts @@ -2,7 +2,7 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; export interface CRIT_BOOST_PARAMS { /** The pokemon with the item */ @@ -17,7 +17,7 @@ export interface CRIT_BOOST_PARAMS { * @see {@linkcode apply} */ export class CritBoostHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.CRIT_BOOST]; + public effects: HeldItemEffect[] = [HeldItemEffect.CRIT_BOOST]; /** The amount of stages by which the held item increases the current critical-hit stage value */ protected stageIncrement: number; diff --git a/src/items/held-items/damage-money-reward.ts b/src/items/held-items/damage-money-reward.ts index 0e0905d183e..8844f08b9eb 100644 --- a/src/items/held-items/damage-money-reward.ts +++ b/src/items/held-items/damage-money-reward.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { NumberHolder } from "#app/utils/common"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; import { TRAINER_ITEM_EFFECT } from "../trainer-item"; export interface DAMAGE_MONEY_REWARD_PARAMS { @@ -12,7 +12,7 @@ export interface DAMAGE_MONEY_REWARD_PARAMS { } export class DamageMoneyRewardHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.DAMAGE_MONEY_REWARD]; + public effects: HeldItemEffect[] = [HeldItemEffect.DAMAGE_MONEY_REWARD]; /** * Applies {@linkcode DamageMoneyRewardModifier} diff --git a/src/items/held-items/evo-tracker.ts b/src/items/held-items/evo-tracker.ts index 37020e67fec..5d7f124073d 100644 --- a/src/items/held-items/evo-tracker.ts +++ b/src/items/held-items/evo-tracker.ts @@ -3,7 +3,7 @@ import { globalScene } from "#app/global-scene"; import { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import i18next from "i18next"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; import { TrainerItemId } from "#enums/trainer-item-id"; export interface EVO_TRACKER_PARAMS { @@ -12,7 +12,7 @@ export interface EVO_TRACKER_PARAMS { } export class EvoTrackerHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.EVO_TRACKER]; + public effects: HeldItemEffect[] = [HeldItemEffect.EVO_TRACKER]; protected species: SpeciesId; protected required: number; diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts index c4dd7ae9773..e6d49747542 100644 --- a/src/items/held-items/exp-booster.ts +++ b/src/items/held-items/exp-booster.ts @@ -2,7 +2,7 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; import i18next from "i18next"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; export interface EXP_BOOST_PARAMS { /** The pokemon with the item */ @@ -12,7 +12,7 @@ export interface EXP_BOOST_PARAMS { } export class ExpBoosterHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.EXP_BOOSTER]; + public effects: HeldItemEffect[] = [HeldItemEffect.EXP_BOOSTER]; private boostPercent: number; private boostMultiplier: number; diff --git a/src/items/held-items/field-effect.ts b/src/items/held-items/field-effect.ts index 048aa8f38b6..3853cf1381e 100644 --- a/src/items/held-items/field-effect.ts +++ b/src/items/held-items/field-effect.ts @@ -1,5 +1,5 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect, HeldItem } from "#app/items/held-item"; import type { NumberHolder } from "#app/utils/common"; export interface FIELD_EFFECT_PARAMS { @@ -15,7 +15,7 @@ export interface FIELD_EFFECT_PARAMS { * @see {@linkcode apply} */ export class FieldEffectHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.FIELD_EFFECT]; + public effects: HeldItemEffect[] = [HeldItemEffect.FIELD_EFFECT]; /** * Provides two more turns per stack to any weather or terrain effect caused diff --git a/src/items/held-items/flinch-chance.ts b/src/items/held-items/flinch-chance.ts index ca46ce79528..c6c5565058d 100644 --- a/src/items/held-items/flinch-chance.ts +++ b/src/items/held-items/flinch-chance.ts @@ -1,5 +1,5 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect, HeldItem } from "#app/items/held-item"; import type { BooleanHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; @@ -16,7 +16,7 @@ export interface FLINCH_CHANCE_PARAMS { * @see {@linkcode apply} */ export class FlinchChanceHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.FLINCH_CHANCE]; + public effects: HeldItemEffect[] = [HeldItemEffect.FLINCH_CHANCE]; private chance: number; constructor(type: HeldItemId, maxStackCount = 1, chance: number) { diff --git a/src/items/held-items/friendship-booster.ts b/src/items/held-items/friendship-booster.ts index 72a42ea48dc..4e3fd32a6b1 100644 --- a/src/items/held-items/friendship-booster.ts +++ b/src/items/held-items/friendship-booster.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; import i18next from "i18next"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; export interface FRIENDSHIP_BOOST_PARAMS { /** The pokemon with the item */ @@ -11,7 +11,7 @@ export interface FRIENDSHIP_BOOST_PARAMS { } export class FriendshipBoosterHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.FRIENDSHIP_BOOSTER]; + public effects: HeldItemEffect[] = [HeldItemEffect.FRIENDSHIP_BOOSTER]; get description(): string { return i18next.t("modifierType:ModifierType.PokemonFriendshipBoosterModifierType.description"); diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts index 1432aa3370b..799a3e4d56d 100644 --- a/src/items/held-items/hit-heal.ts +++ b/src/items/held-items/hit-heal.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import i18next from "i18next"; -import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect, HeldItem } from "#app/items/held-item"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { toDmgValue } from "#app/utils/common"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -12,7 +12,7 @@ export interface HIT_HEAL_PARAMS { } export class HitHealHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.TURN_END_HEAL]; + public effects: HeldItemEffect[] = [HeldItemEffect.TURN_END_HEAL]; get name(): string { return i18next.t("modifierType:ModifierType.SHELL_BELL.name"); diff --git a/src/items/held-items/incrementing-stat.ts b/src/items/held-items/incrementing-stat.ts index 262d8d00f70..c6157856cc2 100644 --- a/src/items/held-items/incrementing-stat.ts +++ b/src/items/held-items/incrementing-stat.ts @@ -1,5 +1,5 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; import { Stat } from "#enums/stat"; import type { NumberHolder } from "#app/utils/common"; import i18next from "i18next"; @@ -16,7 +16,7 @@ export interface INCREMENTING_STAT_PARAMS { * Currently used by Macho Brace item */ export class IncrementingStatHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.INCREMENTING_STAT]; + public effects: HeldItemEffect[] = [HeldItemEffect.INCREMENTING_STAT]; public isTransferable = false; /** diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts index 40e88c1e5bf..c700dcf97b2 100644 --- a/src/items/held-items/instant-revive.ts +++ b/src/items/held-items/instant-revive.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import i18next from "i18next"; -import { ConsumableHeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { ConsumableHeldItem, HeldItemEffect } from "../held-item"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { toDmgValue } from "#app/utils/common"; @@ -19,7 +19,7 @@ export interface INSTANT_REVIVE_PARAMS { * @see {@linkcode apply} */ export class InstantReviveHeldItem extends ConsumableHeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.INSTANT_REVIVE]; + public effects: HeldItemEffect[] = [HeldItemEffect.INSTANT_REVIVE]; get name(): string { return i18next.t("modifierType:ModifierType.REVIVER_SEED.name"); diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts index ec78ebdeeb3..3279b4d98cf 100644 --- a/src/items/held-items/item-steal.ts +++ b/src/items/held-items/item-steal.ts @@ -2,7 +2,7 @@ import Pokemon from "#app/field/pokemon"; import { randSeedFloat } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; import i18next from "i18next"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; import { getPokemonNameWithAffix } from "#app/messages"; import { allHeldItems } from "../all-held-items"; import { globalScene } from "#app/global-scene"; @@ -83,7 +83,7 @@ export abstract class ItemTransferHeldItem extends HeldItem { * @see {@linkcode modifierTypes[MINI_BLACK_HOLE]} */ export class TurnEndItemStealHeldItem extends ItemTransferHeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.TURN_END_ITEM_STEAL]; + public effects: HeldItemEffect[] = [HeldItemEffect.TURN_END_ITEM_STEAL]; isTransferable = true; get description(): string { @@ -125,7 +125,7 @@ export class TurnEndItemStealHeldItem extends ItemTransferHeldItem { * @see {@linkcode HeldItemTransferModifier} */ export class ContactItemStealChanceHeldItem extends ItemTransferHeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.CONTACT_ITEM_STEAL_CHANCE]; + public effects: HeldItemEffect[] = [HeldItemEffect.CONTACT_ITEM_STEAL_CHANCE]; public readonly chancePercent: number; public readonly chance: number; diff --git a/src/items/held-items/multi-hit.ts b/src/items/held-items/multi-hit.ts index 5efbdf71386..12472162afe 100644 --- a/src/items/held-items/multi-hit.ts +++ b/src/items/held-items/multi-hit.ts @@ -1,5 +1,5 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect, HeldItem } from "#app/items/held-item"; import { isNullOrUndefined, type NumberHolder } from "#app/utils/common"; import type { MoveId } from "#enums/move-id"; import { allMoves } from "#app/data/data-lists"; @@ -19,7 +19,7 @@ export interface MULTI_HIT_PARAMS { * @see {@linkcode apply} */ export class MultiHitHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.MULTI_HIT]; + public effects: HeldItemEffect[] = [HeldItemEffect.MULTI_HIT]; get description(): string { return i18next.t("modifierType:ModifierType.PokemonMultiHitModifierType.description"); diff --git a/src/items/held-items/nature-weight-booster.ts b/src/items/held-items/nature-weight-booster.ts index ae515a96196..76a81df6423 100644 --- a/src/items/held-items/nature-weight-booster.ts +++ b/src/items/held-items/nature-weight-booster.ts @@ -1,6 +1,6 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; export interface NATURE_WEIGHT_BOOST_PARAMS { /** The pokemon with the item */ @@ -10,7 +10,7 @@ export interface NATURE_WEIGHT_BOOST_PARAMS { } export class NatureWeightBoosterHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.NATURE_WEIGHT_BOOSTER]; + public effects: HeldItemEffect[] = [HeldItemEffect.NATURE_WEIGHT_BOOSTER]; /** * Applies {@linkcode PokemonNatureWeightModifier} diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index 94bb72c9ed4..76b17e19ffb 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -2,7 +2,7 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { BATTLE_STATS } from "#enums/stat"; import i18next from "i18next"; -import { ConsumableHeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { ConsumableHeldItem, HeldItemEffect } from "../held-item"; import { getPokemonNameWithAffix } from "#app/messages"; export interface RESET_NEGATIVE_STAT_STAGE_PARAMS { @@ -19,7 +19,7 @@ export interface RESET_NEGATIVE_STAT_STAGE_PARAMS { * @see {@linkcode apply} */ export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.RESET_NEGATIVE_STAT_STAGE]; + public effects: HeldItemEffect[] = [HeldItemEffect.RESET_NEGATIVE_STAT_STAGE]; get name(): string { return i18next.t("modifierType:ModifierType.WHITE_HERB.name"); diff --git a/src/items/held-items/stat-booster.ts b/src/items/held-items/stat-booster.ts index 711b4f276ab..8e6553053f5 100644 --- a/src/items/held-items/stat-booster.ts +++ b/src/items/held-items/stat-booster.ts @@ -4,7 +4,7 @@ import type { NumberHolder } from "#app/utils/common"; import { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import type { Stat } from "#enums/stat"; -import { HeldItem, HELD_ITEM_EFFECT } from "../held-item"; +import { HeldItemEffect, HeldItem } from "../held-item"; export interface STAT_BOOST_PARAMS { /** The pokemon with the item */ @@ -20,7 +20,7 @@ export interface STAT_BOOST_PARAMS { * @see {@linkcode apply} */ export class StatBoostHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.STAT_BOOST]; + public effects: HeldItemEffect[] = [HeldItemEffect.STAT_BOOST]; /** The stats that the held item boosts */ protected stats: Stat[]; /** The multiplier used to increase the relevant stat(s) */ diff --git a/src/items/held-items/survive-chance.ts b/src/items/held-items/survive-chance.ts index e23477f0fab..1e3b1718ab1 100644 --- a/src/items/held-items/survive-chance.ts +++ b/src/items/held-items/survive-chance.ts @@ -1,5 +1,5 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect, HeldItem } from "#app/items/held-item"; import type { BooleanHolder } from "#app/utils/common"; import { globalScene } from "#app/global-scene"; import i18next from "i18next"; @@ -18,7 +18,7 @@ export interface SURVIVE_CHANCE_PARAMS { * @see {@linkcode apply} */ export class SurviveChanceHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.SURVIVE_CHANCE]; + public effects: HeldItemEffect[] = [HeldItemEffect.SURVIVE_CHANCE]; /** * Checks if the {@linkcode SurviveDamageModifier} should be applied diff --git a/src/items/held-items/turn-end-heal.ts b/src/items/held-items/turn-end-heal.ts index 0ba42db70ae..ebd86718d7a 100644 --- a/src/items/held-items/turn-end-heal.ts +++ b/src/items/held-items/turn-end-heal.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import i18next from "i18next"; -import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect, HeldItem } from "#app/items/held-item"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { toDmgValue } from "#app/utils/common"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -12,7 +12,7 @@ export interface TURN_END_HEAL_PARAMS { } export class TurnEndHealHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.TURN_END_HEAL]; + public effects: HeldItemEffect[] = [HeldItemEffect.TURN_END_HEAL]; apply(params: TURN_END_HEAL_PARAMS): boolean { const pokemon = params.pokemon; diff --git a/src/items/held-items/turn-end-status.ts b/src/items/held-items/turn-end-status.ts index ed0a1d609fc..6a4ae620b47 100644 --- a/src/items/held-items/turn-end-status.ts +++ b/src/items/held-items/turn-end-status.ts @@ -1,5 +1,5 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect, HeldItem } from "#app/items/held-item"; import type { StatusEffect } from "#enums/status-effect"; import type { HeldItemId } from "#enums/held-item-id"; @@ -15,7 +15,7 @@ export interface TURN_END_STATUS_PARAMS { * @see {@linkcode apply} */ export class TurnEndStatusHeldItem extends HeldItem { - public effects: HELD_ITEM_EFFECT[] = [HELD_ITEM_EFFECT.TURN_END_STATUS]; + public effects: HeldItemEffect[] = [HeldItemEffect.TURN_END_STATUS]; /** The status effect to be applied by the held item */ public effect: StatusEffect; diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index 2b361427d7c..1fe547d7469 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -7,7 +7,7 @@ import { FieldPhase } from "./field-phase"; import { globalScene } from "#app/global-scene"; import type Pokemon from "#app/field/pokemon"; import { allHeldItems, applyHeldItems } from "#app/items/all-held-items"; -import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect } from "#app/items/held-item"; import { HeldItemCategoryId, isItemInCategory } from "#enums/held-item-id"; /** @@ -59,7 +59,7 @@ export class BerryPhase extends FieldPhase { CommonAnim.USE_ITEM, ); - applyHeldItems(HELD_ITEM_EFFECT.BERRY, { pokemon: pokemon }); + applyHeldItems(HeldItemEffect.BERRY, { pokemon: pokemon }); globalScene.updateItems(pokemon.isPlayer()); // AbilityId.CHEEK_POUCH only works once per round of nom noms diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 9dc54ef4642..0733e168ae5 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -21,7 +21,7 @@ import { isNullOrUndefined } from "#app/utils/common"; import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters"; import { BattlerTagType } from "#enums/battler-tag-type"; import { applyHeldItems } from "#app/items/all-held-items"; -import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect } from "#app/items/held-item"; export class FaintPhase extends PokemonPhase { public readonly phaseName = "FaintPhase"; @@ -55,7 +55,7 @@ export class FaintPhase extends PokemonPhase { faintPokemon.resetSummonData(); if (!this.preventInstantRevive) { - applyHeldItems(HELD_ITEM_EFFECT.INSTANT_REVIVE, { pokemon: faintPokemon }); + applyHeldItems(HeldItemEffect.INSTANT_REVIVE, { pokemon: faintPokemon }); } /** diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 21f869110d5..2e006c3a46f 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -41,7 +41,7 @@ import type Move from "#app/data/moves/move"; import { isFieldTargeted } from "#app/data/moves/move-utils"; import { DamageAchv } from "#app/system/achv"; import { applyHeldItems } from "#app/items/all-held-items"; -import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect } from "#app/items/held-item"; import { isVirtual, isReflected, MoveUseMode } from "#enums/move-use-mode"; import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; @@ -313,7 +313,7 @@ export class MoveEffectPhase extends PokemonPhase { // If Parental Bond is applicable, add another hit applyAbAttrs("AddSecondStrikeAbAttr", { pokemon: user, move, hitCount }); // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses - applyHeldItems(HELD_ITEM_EFFECT.MULTI_HIT, { pokemon: user, moveId: move.id, count: hitCount }); + applyHeldItems(HeldItemEffect.MULTI_HIT, { pokemon: user, moveId: move.id, count: hitCount }); // Set the user's relevant turnData fields to reflect the final hit count user.turnData.hitCount = hitCount.value; user.turnData.hitsLeft = hitCount.value; @@ -413,7 +413,7 @@ export class MoveEffectPhase extends PokemonPhase { globalScene.phaseManager.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); } - applyHeldItems(HELD_ITEM_EFFECT.HIT_HEAL, { pokemon: user }); + applyHeldItems(HeldItemEffect.HIT_HEAL, { pokemon: user }); this.getTargets().forEach(target => { target.turnData.moveEffectiveness = null; }); @@ -455,7 +455,7 @@ export class MoveEffectPhase extends PokemonPhase { !this.move.hitsSubstitute(user, target) ) { const flinched = new BooleanHolder(false); - applyHeldItems(HELD_ITEM_EFFECT.FLINCH_CHANCE, { pokemon: user, flinched: flinched }); + applyHeldItems(HeldItemEffect.FLINCH_CHANCE, { pokemon: user, flinched: flinched }); if (flinched.value) { target.addTag(BattlerTagType.FLINCHED, undefined, this.move.id, user.id); } @@ -900,7 +900,7 @@ export class MoveEffectPhase extends PokemonPhase { }); if (user.isPlayer() && target.isEnemy()) { - applyHeldItems(HELD_ITEM_EFFECT.DAMAGE_MONEY_REWARD, { pokemon: user, damage: damage }); + applyHeldItems(HeldItemEffect.DAMAGE_MONEY_REWARD, { pokemon: user, damage: damage }); } return [result, isCritical]; @@ -1013,7 +1013,7 @@ export class MoveEffectPhase extends PokemonPhase { // Apply Grip Claw's chance to steal an item from the target if (this.move.is("AttackMove")) { - applyHeldItems(HELD_ITEM_EFFECT.CONTACT_ITEM_STEAL_CHANCE, { pokemon: user, target: target }); + applyHeldItems(HeldItemEffect.CONTACT_ITEM_STEAL_CHANCE, { pokemon: user, target: target }); } } } diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 0fd77648b76..95364908d5d 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -14,7 +14,7 @@ import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } f import { OctolockTag } from "#app/data/battler-tags"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { applyHeldItems } from "#app/items/all-held-items"; -import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect } from "#app/items/held-item"; import type { ConditionalUserFieldProtectStatAbAttrParams, PreStatStageChangeAbAttrParams, @@ -236,7 +236,7 @@ export class StatStageChangePhase extends PokemonPhase { ); if (!existingPhase?.is("StatStageChangePhase")) { // Apply White Herb if needed - applyHeldItems(HELD_ITEM_EFFECT.RESET_NEGATIVE_STAT_STAGE, { pokemon: pokemon, isPlayer: this.player }); + applyHeldItems(HeldItemEffect.RESET_NEGATIVE_STAT_STAGE, { pokemon: pokemon, isPlayer: this.player }); } pokemon.updateInfo(); diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 1d1bd3ebb3c..a222734161d 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -9,7 +9,7 @@ import i18next from "i18next"; import { FieldPhase } from "./field-phase"; import { globalScene } from "#app/global-scene"; import { applyHeldItems } from "#app/items/all-held-items"; -import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect } from "#app/items/held-item"; import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; export class TurnEndPhase extends FieldPhase { @@ -26,7 +26,7 @@ export class TurnEndPhase extends FieldPhase { if (!pokemon.switchOutStatus) { pokemon.lapseTags(BattlerTagLapseType.TURN_END); - applyHeldItems(HELD_ITEM_EFFECT.TURN_END_HEAL, { pokemon: pokemon }); + applyHeldItems(HeldItemEffect.TURN_END_HEAL, { pokemon: pokemon }); if (globalScene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { globalScene.phaseManager.unshiftNew( @@ -48,9 +48,9 @@ export class TurnEndPhase extends FieldPhase { applyAbAttrs("PostTurnAbAttr", { pokemon }); } - applyHeldItems(HELD_ITEM_EFFECT.TURN_END_STATUS, { pokemon: pokemon }); + applyHeldItems(HeldItemEffect.TURN_END_STATUS, { pokemon: pokemon }); - applyHeldItems(HELD_ITEM_EFFECT.TURN_END_ITEM_STEAL, { pokemon: pokemon }); + applyHeldItems(HeldItemEffect.TURN_END_ITEM_STEAL, { pokemon: pokemon }); pokemon.tempSummonData.turnCount++; pokemon.tempSummonData.waveTurnCount++; diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index cfdf68727f3..f7ded3fe2eb 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -10,7 +10,7 @@ import { BattlerIndex } from "#enums/battler-index"; import { TrickRoomTag } from "#app/data/arena-tag"; import { SwitchType } from "#enums/switch-type"; import { globalScene } from "#app/global-scene"; -import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect } from "#app/items/held-item"; import { applyHeldItems } from "#app/items/all-held-items"; export class TurnStartPhase extends FieldPhase { @@ -73,7 +73,7 @@ export class TurnStartPhase extends FieldPhase { canCheckHeldItems: canCheckHeldItems, }); if (canCheckHeldItems.value) { - applyHeldItems(HELD_ITEM_EFFECT.BYPASS_SPEED_CHANCE, { pokemon: p, doBypassSpeed: bypassSpeed }); + applyHeldItems(HeldItemEffect.BYPASS_SPEED_CHANCE, { pokemon: p, doBypassSpeed: bypassSpeed }); } battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed; }); diff --git a/test/items/eviolite.test.ts b/test/items/eviolite.test.ts index 0539280e132..fae83947885 100644 --- a/test/items/eviolite.test.ts +++ b/test/items/eviolite.test.ts @@ -1,5 +1,5 @@ import { applyHeldItems } from "#app/items/all-held-items"; -import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect } from "#app/items/held-item"; import { NumberHolder, randItem } from "#app/utils/common"; import { HeldItemId } from "#enums/held-item-id"; import { SpeciesId } from "#enums/species-id"; @@ -34,7 +34,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); return Math.floor(statValue.value); }); @@ -53,7 +53,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); // Ignore other calculations for simplicity @@ -83,7 +83,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); // Ignore other calculations for simplicity @@ -113,7 +113,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); // Ignore other calculations for simplicity @@ -143,7 +143,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); // Ignore other calculations for simplicity @@ -173,7 +173,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); // Ignore other calculations for simplicity @@ -203,7 +203,7 @@ describe("Items - Eviolite", () => { vi.spyOn(partyMember, "getEffectiveStat").mockImplementation((stat, _opponent?, _move?, _isCritical?) => { const statValue = new NumberHolder(partyMember.getStat(stat, false)); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: stat, statValue: statValue }); // Ignore other calculations for simplicity diff --git a/test/items/exp_booster.test.ts b/test/items/exp_booster.test.ts index ded5cb85544..11546c4a4fb 100644 --- a/test/items/exp_booster.test.ts +++ b/test/items/exp_booster.test.ts @@ -5,7 +5,7 @@ import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { HeldItemId } from "#enums/held-item-id"; import { applyHeldItems } from "#app/items/all-held-items"; -import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect } from "#app/items/held-item"; describe("EXP Modifier Items", () => { let phaserGame: Phaser.Game; @@ -34,7 +34,7 @@ describe("EXP Modifier Items", () => { const partyMember = game.scene.getPlayerPokemon()!; partyMember.exp = 100; const expHolder = new NumberHolder(partyMember.exp); - applyHeldItems(HELD_ITEM_EFFECT.EXP_BOOSTER, { pokemon: partyMember, expAmount: expHolder }); + applyHeldItems(HeldItemEffect.EXP_BOOSTER, { pokemon: partyMember, expAmount: expHolder }); expect(expHolder.value).toBe(440); }); }); diff --git a/test/items/light_ball.test.ts b/test/items/light_ball.test.ts index 742e7dc8d61..b0509041162 100644 --- a/test/items/light_ball.test.ts +++ b/test/items/light_ball.test.ts @@ -7,7 +7,7 @@ import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { HeldItemId } from "#enums/held-item-id"; import { applyHeldItems } from "#app/items/all-held-items"; -import { HELD_ITEM_EFFECT } from "#app/items/held-item"; +import { HeldItemEffect } from "#app/items/held-item"; describe("Items - Light Ball", () => { let phaserGame: Phaser.Game; @@ -92,17 +92,17 @@ describe("Items - Light Ball", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: atkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: atkValue }); const spAtkValue = new NumberHolder(spAtkStat); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPDEF, statValue: spAtkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPDEF, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); // Giving Light Ball to party member and testing if it applies partyMember.heldItemManager.add(HeldItemId.LIGHT_BALL); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPATK, statValue: spAtkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPATK, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(2); expect(spAtkValue.value / spAtkStat).toBe(2); @@ -128,9 +128,9 @@ describe("Items - Light Ball", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: atkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: atkValue }); const spAtkValue = new NumberHolder(spAtkStat); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPDEF, statValue: spAtkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPDEF, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); @@ -138,8 +138,8 @@ describe("Items - Light Ball", () => { // Giving Light Ball to party member and testing if it applies partyMember.heldItemManager.add(HeldItemId.LIGHT_BALL); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPATK, statValue: spAtkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPATK, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(2); expect(spAtkValue.value / spAtkStat).toBe(2); @@ -165,9 +165,9 @@ describe("Items - Light Ball", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: atkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: atkValue }); const spAtkValue = new NumberHolder(spAtkStat); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPDEF, statValue: spAtkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPDEF, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); @@ -175,8 +175,8 @@ describe("Items - Light Ball", () => { // Giving Light Ball to party member and testing if it applies partyMember.heldItemManager.add(HeldItemId.LIGHT_BALL); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPATK, statValue: spAtkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPATK, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(2); expect(spAtkValue.value / spAtkStat).toBe(2); @@ -192,17 +192,17 @@ describe("Items - Light Ball", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: atkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: atkValue }); const spAtkValue = new NumberHolder(spAtkStat); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPDEF, statValue: spAtkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPDEF, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); // Giving Light Ball to party member and testing if it applies partyMember.heldItemManager.add(HeldItemId.LIGHT_BALL); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); - applyHeldItems(HELD_ITEM_EFFECT.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPATK, statValue: spAtkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPATK, statValue: spAtkValue }); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); From fee63205028539e70898a6fa51e1761c1397aaad Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 10 Jul 2025 01:41:18 -0700 Subject: [PATCH 06/39] Normalize naming --- src/battle-scene.ts | 10 +- src/battle.ts | 4 +- src/data/moves/move.ts | 4 +- .../global-trade-system-encounter.ts | 6 +- .../encounters/safari-zone-encounter.ts | 4 +- .../encounters/weird-dream-encounter.ts | 7 +- src/data/pokeball.ts | 4 +- src/field/pokemon.ts | 24 ++-- src/items/all-held-items.ts | 114 +++++++++--------- src/items/apply-trainer-items.ts | 58 ++++----- src/items/held-items/accuracy-booster.ts | 4 +- src/items/held-items/attack-type-booster.ts | 4 +- src/items/held-items/base-stat-booster.ts | 4 +- src/items/held-items/base-stat-flat.ts | 4 +- src/items/held-items/base-stat-total.ts | 4 +- src/items/held-items/baton.ts | 2 +- src/items/held-items/berry.ts | 8 +- src/items/held-items/bypass-speed-chance.ts | 4 +- src/items/held-items/crit-booster.ts | 6 +- src/items/held-items/damage-money-reward.ts | 8 +- src/items/held-items/evo-tracker.ts | 2 +- src/items/held-items/exp-booster.ts | 4 +- src/items/held-items/field-effect.ts | 4 +- src/items/held-items/flinch-chance.ts | 4 +- src/items/held-items/friendship-booster.ts | 4 +- src/items/held-items/hit-heal.ts | 4 +- src/items/held-items/incrementing-stat.ts | 4 +- src/items/held-items/instant-revive.ts | 4 +- src/items/held-items/item-steal.ts | 22 ++-- src/items/held-items/multi-hit.ts | 4 +- src/items/held-items/nature-weight-booster.ts | 4 +- .../held-items/reset-negative-stat-stage.ts | 4 +- src/items/held-items/stat-booster.ts | 8 +- src/items/held-items/survive-chance.ts | 4 +- src/items/held-items/turn-end-heal.ts | 4 +- src/items/held-items/turn-end-status.ts | 4 +- src/items/trainer-item.ts | 98 +++++++-------- src/modifier/modifier-type.ts | 4 +- src/modifier/modifier.ts | 6 +- src/phases/exp-phase.ts | 4 +- src/phases/money-reward-phase.ts | 4 +- src/phases/move-effect-phase.ts | 4 +- src/phases/pokemon-heal-phase.ts | 4 +- src/phases/select-modifier-phase.ts | 8 +- src/phases/show-party-exp-bar-phase.ts | 4 +- src/phases/turn-end-phase.ts | 6 +- src/ui/modifier-select-ui-handler.ts | 4 +- 47 files changed, 254 insertions(+), 259 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 51b72d82234..ae2aad447e1 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -147,8 +147,8 @@ import { HeldItemId } from "#enums/held-item-id"; import { assignEnemyHeldItemsForWave, assignItemsFromConfiguration } from "./items/held-item-pool"; import type { HeldItemConfiguration } from "./items/held-item-data-types"; import { TrainerItemManager } from "./items/trainer-item-manager"; -import { type EnemyAttackStatusEffectChanceTrainerItem, TRAINER_ITEM_EFFECT } from "./items/trainer-item"; -import { applyTrainerItems, type APPLY_TRAINER_ITEMS_PARAMS } from "./items/apply-trainer-items"; +import { type EnemyAttackStatusEffectChanceTrainerItem, TrainerItemEffect } from "./items/trainer-item"; +import { applyTrainerItems, type ApplyTrainerItemsParams } from "./items/apply-trainer-items"; import { TrainerItemId } from "#enums/trainer-item-id"; import { isTrainerItemPool, @@ -1301,7 +1301,7 @@ export default class BattleScene extends SceneBase { getDoubleBattleChance(newWaveIndex: number, playerField: PlayerPokemon[]) { const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8); - this.applyPlayerItems(TRAINER_ITEM_EFFECT.DOUBLE_BATTLE_CHANCE_BOOSTER, { numberHolder: doubleChance }); + this.applyPlayerItems(TrainerItemEffect.DOUBLE_BATTLE_CHANCE_BOOSTER, { numberHolder: doubleChance }); for (const p of playerField) { applyAbAttrs("DoubleBattleChanceAbAttr", { pokemon: p, chance: doubleChance }); } @@ -2650,7 +2650,7 @@ export default class BattleScene extends SceneBase { return Math.floor(moneyValue / 10) * 10; } - applyPlayerItems(effect: T, params: APPLY_TRAINER_ITEMS_PARAMS[T]) { + applyPlayerItems(effect: T, params: ApplyTrainerItemsParams[T]) { applyTrainerItems(effect, this.trainerItems, params); } @@ -2677,7 +2677,7 @@ export default class BattleScene extends SceneBase { if (modifier instanceof PokemonHpRestoreModifier) { if (!(modifier as PokemonHpRestoreModifier).fainted) { const hpRestoreMultiplier = new NumberHolder(1); - this.applyPlayerItems(TRAINER_ITEM_EFFECT.HEALING_BOOSTER, { numberHolder: hpRestoreMultiplier }); + this.applyPlayerItems(TrainerItemEffect.HEALING_BOOSTER, { numberHolder: hpRestoreMultiplier }); args.push(hpRestoreMultiplier.value); } else { args.push(1); diff --git a/src/battle.ts b/src/battle.ts index 7242c4b8650..315cdd1c798 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -36,7 +36,7 @@ import { BattleType } from "#enums/battle-type"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import type { HeldItemId } from "#enums/held-item-id"; import { BattlerIndex } from "#enums/battler-index"; -import { TRAINER_ITEM_EFFECT } from "./items/trainer-item"; +import { TrainerItemEffect } from "./items/trainer-item"; export interface TurnCommand { command: Command; @@ -183,7 +183,7 @@ export default class Battle { pickUpScatteredMoney(): void { const moneyAmount = new NumberHolder(globalScene.currentBattle.moneyScattered); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); + globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); if (globalScene.arena.getTag(ArenaTagType.HAPPY_HOUR)) { moneyAmount.value *= 2; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index c38418ca510..9ecc2bee9b4 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -83,7 +83,7 @@ import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-it import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap } from "#app/@types/move-types"; import { applyMoveAttrs } from "./apply-attrs"; import { frenzyMissFunc, getMoveTargets } from "./move-utils"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; import { AbAttrParamsWithCancel, PreAttackModifyPowerAbAttrParams } from "../abilities/ability"; import { applyHeldItems } from "#app/items/all-held-items"; @@ -2764,7 +2764,7 @@ export class EatBerryAttr extends MoveEffectAttr { this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)]; const preserve = new BooleanHolder(false); // check for berry pouch preservation - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.PRESERVE_BERRY, {pokemon: pokemon, doPreserve: preserve}); + globalScene.applyPlayerItems(TrainerItemEffect.PRESERVE_BERRY, {pokemon: pokemon, doPreserve: preserve}); if (!preserve.value) { this.reduceBerryModifier(pokemon); } diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 3c3a8d29475..09af8c1a919 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -49,7 +49,7 @@ import { HeldItemCategoryId, type HeldItemId, isItemInCategory } from "#enums/he import { allHeldItems } from "#app/data/data-lists"; import { RewardTier } from "#enums/reward-tier"; import { getHeldItemTier } from "#app/items/held-item-tiers"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/globalTradeSystem"; @@ -276,7 +276,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil if (timedEventManager.isEventActive()) { shinyThreshold.value *= timedEventManager.getShinyMultiplier(); } - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold }); + globalScene.applyPlayerItems(TrainerItemEffect.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold }); // Base shiny chance of 512/65536 -> 1/128, affected by events and Shiny Charms // Maximum shiny chance of 4096/65536 -> 1/16, cannot improve further after that @@ -290,7 +290,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil if (tradePokemon.species.abilityHidden) { if (tradePokemon.abilityIndex < hiddenIndex) { const hiddenAbilityChance = new NumberHolder(64); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER, { + globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, { numberHolder: hiddenAbilityChance, }); diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index 6d47959caac..7fb436c3dce 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -31,7 +31,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups"; import { TrainerItemId } from "#enums/trainer-item-id"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/safariZone"; @@ -298,7 +298,7 @@ async function summonSafariPokemon() { const hiddenIndex = pokemon.species.ability2 ? 2 : 1; if (pokemon.abilityIndex < hiddenIndex) { const hiddenAbilityChance = new NumberHolder(256); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER, { + globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, { numberHolder: hiddenAbilityChance, }); diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 68aac8d9d49..9e683a85f57 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -38,10 +38,9 @@ import { Nature } from "#enums/nature"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { PartyMemberStrength } from "#enums/party-member-strength"; -import type { HeldItemConfiguration, HeldItemSpecs } from "#app/items/held-item-data-types"; -import { assignItemsFromConfiguration } from "#app/items/held-item-pool"; +import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; import { HeldItemId } from "#enums/held-item-id"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/weirdDream"; @@ -487,7 +486,7 @@ async function postProcessTransformedPokemon( const hiddenIndex = newPokemon.species.ability2 ? 2 : 1; if (newPokemon.abilityIndex < hiddenIndex) { const hiddenAbilityChance = new NumberHolder(256); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER, { + globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, { numberHolder: hiddenAbilityChance, }); diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index b39b29561b4..38aeb40b433 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; import { NumberHolder } from "#app/utils/common"; import { PokeballType } from "#enums/pokeball"; import i18next from "i18next"; @@ -94,7 +94,7 @@ export function getCriticalCaptureChance(modifiedCatchRate: number): number { } const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr); const catchingCharmMultiplier = new NumberHolder(1); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.CRITICAL_CATCH_CHANCE_BOOSTER, { + globalScene.applyPlayerItems(TrainerItemEffect.CRITICAL_CATCH_CHANCE_BOOSTER, { numberHolder: catchingCharmMultiplier, }); const dexMultiplier = diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8cea1eadd82..df07cc020eb 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -165,7 +165,7 @@ import type { IllusionData } from "#app/@types/illusion-data"; import type { TurnMove } from "#app/@types/turn-move"; import type { DamageCalculationResult, DamageResult } from "#app/@types/damage-result"; import type { AbAttrMap, AbAttrString, TypeMultiplierAbAttrParams } from "#app/@types/ability-types"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; import { assignItemsFromConfiguration } from "#app/items/held-item-pool"; import { getTerrainBlockMessage } from "#app/data/terrain"; @@ -410,7 +410,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (level > 1) { const fused = new BooleanHolder(globalScene.gameMode.isSplicedOnly); if (!fused.value && this.isEnemy() && !this.hasTrainer()) { - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.ENEMY_FUSED_CHANCE, { booleanHolder: fused }); + globalScene.applyPlayerItems(TrainerItemEffect.ENEMY_FUSED_CHANCE, { booleanHolder: fused }); } if (fused.value) { @@ -593,7 +593,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Roll for hidden ability chance, applying any ability charms for enemy mons const hiddenAbilityChance = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); if (!this.hasTrainer()) { - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER, { + globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, { numberHolder: hiddenAbilityChance, }); } @@ -1347,7 +1347,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const critStage = new NumberHolder(0); applyMoveAttrs("HighCritAttr", source, this, move, critStage); applyHeldItems(HeldItemEffect.CRIT_BOOST, { pokemon: source, critStage: critStage }); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.TEMP_CRIT_BOOSTER, { numberHolder: critStage }); + globalScene.applyPlayerItems(TrainerItemEffect.TEMP_CRIT_BOOSTER, { numberHolder: critStage }); applyAbAttrs("BonusCritAbAttr", { pokemon: source, critStage }); const critBoostTag = source.getTag(CritBoostTag); if (critBoostTag) { @@ -2784,7 +2784,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } if (!this.hasTrainer()) { - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold }); + globalScene.applyPlayerItems(TrainerItemEffect.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold }); } } else { shinyThreshold.value = thresholdOverride; @@ -2816,7 +2816,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (timedEventManager.isEventActive()) { shinyThreshold.value *= timedEventManager.getShinyMultiplier(); } - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold }); + globalScene.applyPlayerItems(TrainerItemEffect.SHINY_RATE_BOOSTER, { numberHolder: shinyThreshold }); } this.shiny = randSeedInt(65536) < shinyThreshold.value; @@ -2888,7 +2888,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const haThreshold = new NumberHolder(thresholdOverride ?? BASE_HIDDEN_ABILITY_CHANCE); if (applyModifiersToOverride) { if (!this.hasTrainer()) { - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER, { numberHolder: haThreshold }); + globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, { numberHolder: haThreshold }); } } @@ -2902,7 +2902,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public generateFusionSpecies(forStarter?: boolean): void { const hiddenAbilityChance = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); if (!this.hasTrainer()) { - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER, { + globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, { numberHolder: hiddenAbilityChance, }); } @@ -3418,7 +3418,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!ignoreStatStage.value) { const statStageMultiplier = new NumberHolder(Math.max(2, 2 + statStage.value) / Math.max(2, 2 - statStage.value)); if (!ignoreHeldItems) { - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.TEMP_STAT_STAGE_BOOSTER, { + globalScene.applyPlayerItems(TrainerItemEffect.TEMP_STAT_STAGE_BOOSTER, { numberHolder: statStageMultiplier, }); } @@ -3454,7 +3454,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyAbAttrs("IgnoreOpponentStatStagesAbAttr", { pokemon: this, stat: Stat.EVA, ignored: ignoreEvaStatStage }); applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.TEMP_ACCURACY_BOOSTER, { numberHolder: userAccStage }); + globalScene.applyPlayerItems(TrainerItemEffect.TEMP_ACCURACY_BOOSTER, { numberHolder: userAccStage }); userAccStage.value = ignoreAccStatStage.value ? 0 : Math.min(userAccStage.value, 6); targetEvaStage.value = ignoreEvaStatStage.value ? 0 : targetEvaStage.value; @@ -3878,10 +3878,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Apply the enemy's Damage and Resistance tokens */ if (!source.isPlayer()) { - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.ENEMY_DAMAGE_BOOSTER, { numberHolder: damage }); + globalScene.applyPlayerItems(TrainerItemEffect.ENEMY_DAMAGE_BOOSTER, { numberHolder: damage }); } if (!this.isPlayer()) { - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.ENEMY_DAMAGE_REDUCER, { numberHolder: damage }); + globalScene.applyPlayerItems(TrainerItemEffect.ENEMY_DAMAGE_REDUCER, { numberHolder: damage }); } const abAttrParams: PreAttackModifyDamageAbAttrParams = { diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 54c0682a5c9..f44563e6dbb 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -7,51 +7,47 @@ import { SpeciesId } from "#enums/species-id"; import { Stat, type PermanentStat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { HeldItemEffect } from "./held-item"; -import { type ACCURACY_BOOST_PARAMS, AccuracyBoosterHeldItem } from "./held-items/accuracy-booster"; +import { type AccuracyBoostParams, AccuracyBoosterHeldItem } from "./held-items/accuracy-booster"; import { - type ATTACK_TYPE_BOOST_PARAMS, + type AttackTypeBoostParams, AttackTypeBoosterHeldItem, attackTypeToHeldItem, } from "./held-items/attack-type-booster"; import { - type BASE_STAT_BOOSTER_PARAMS, + type BaseStatBoosterParams, BaseStatBoosterHeldItem, permanentStatToHeldItem, } from "./held-items/base-stat-booster"; -import { type BASE_STAT_FLAT_PARAMS, BaseStatFlatHeldItem } from "./held-items/base-stat-flat"; -import { type BASE_STAT_TOTAL_PARAMS, BaseStatTotalHeldItem } from "./held-items/base-stat-total"; -import { type BATON_PARAMS, BatonHeldItem } from "./held-items/baton"; -import { type BERRY_PARAMS, BerryHeldItem, berryTypeToHeldItem } from "./held-items/berry"; -import { type BYPASS_SPEED_CHANCE_PARAMS, BypassSpeedChanceHeldItem } from "./held-items/bypass-speed-chance"; -import { type CRIT_BOOST_PARAMS, CritBoostHeldItem, SpeciesCritBoostHeldItem } from "./held-items/crit-booster"; -import { type DAMAGE_MONEY_REWARD_PARAMS, DamageMoneyRewardHeldItem } from "./held-items/damage-money-reward"; -import { type EVO_TRACKER_PARAMS, GimmighoulEvoTrackerHeldItem } from "./held-items/evo-tracker"; -import { type EXP_BOOST_PARAMS, ExpBoosterHeldItem } from "./held-items/exp-booster"; -import { type FIELD_EFFECT_PARAMS, FieldEffectHeldItem } from "./held-items/field-effect"; -import { type FLINCH_CHANCE_PARAMS, FlinchChanceHeldItem } from "./held-items/flinch-chance"; -import { type FRIENDSHIP_BOOST_PARAMS, FriendshipBoosterHeldItem } from "./held-items/friendship-booster"; -import { type HIT_HEAL_PARAMS, HitHealHeldItem } from "./held-items/hit-heal"; -import { type INCREMENTING_STAT_PARAMS, IncrementingStatHeldItem } from "./held-items/incrementing-stat"; -import { InstantReviveHeldItem, type INSTANT_REVIVE_PARAMS } from "./held-items/instant-revive"; +import { type BaseStatFlatParams, BaseStatFlatHeldItem } from "./held-items/base-stat-flat"; +import { type BaseStatTotalParams, BaseStatTotalHeldItem } from "./held-items/base-stat-total"; +import { type BatonParams, BatonHeldItem } from "./held-items/baton"; +import { type BerryParams, BerryHeldItem, berryTypeToHeldItem } from "./held-items/berry"; +import { type BypassSpeedChanceParams, BypassSpeedChanceHeldItem } from "./held-items/bypass-speed-chance"; +import { type CritBoostParams, CritBoostHeldItem, SpeciesCritBoostHeldItem } from "./held-items/crit-booster"; +import { type DamageMoneyRewardParams, DamageMoneyRewardHeldItem } from "./held-items/damage-money-reward"; +import { type EvoTrackerParams, GimmighoulEvoTrackerHeldItem } from "./held-items/evo-tracker"; +import { type ExpBoostParams, ExpBoosterHeldItem } from "./held-items/exp-booster"; +import { type FieldEffectParams, FieldEffectHeldItem } from "./held-items/field-effect"; +import { type FlinchChanceParams, FlinchChanceHeldItem } from "./held-items/flinch-chance"; +import { type FriendshipBoostParams, FriendshipBoosterHeldItem } from "./held-items/friendship-booster"; +import { type HitHealParams, HitHealHeldItem } from "./held-items/hit-heal"; +import { type IncrementingStatParams, IncrementingStatHeldItem } from "./held-items/incrementing-stat"; +import { InstantReviveHeldItem, type InstantReviveParams } from "./held-items/instant-revive"; import { ContactItemStealChanceHeldItem, - type ITEM_STEAL_PARAMS, + type ItemStealParams, TurnEndItemStealHeldItem, } from "./held-items/item-steal"; -import { type MULTI_HIT_PARAMS, MultiHitHeldItem } from "./held-items/multi-hit"; -import { type NATURE_WEIGHT_BOOST_PARAMS, NatureWeightBoosterHeldItem } from "./held-items/nature-weight-booster"; +import { type MultiHitParams, MultiHitHeldItem } from "./held-items/multi-hit"; +import { type NatureWeightBoostParams, NatureWeightBoosterHeldItem } from "./held-items/nature-weight-booster"; import { ResetNegativeStatStageHeldItem, - type RESET_NEGATIVE_STAT_STAGE_PARAMS, + type ResetNegativeStatStageParams, } from "./held-items/reset-negative-stat-stage"; -import { - EvolutionStatBoostHeldItem, - SpeciesStatBoostHeldItem, - type STAT_BOOST_PARAMS, -} from "./held-items/stat-booster"; -import { type SURVIVE_CHANCE_PARAMS, SurviveChanceHeldItem } from "./held-items/survive-chance"; -import { type TURN_END_HEAL_PARAMS, TurnEndHealHeldItem } from "./held-items/turn-end-heal"; -import { type TURN_END_STATUS_PARAMS, TurnEndStatusHeldItem } from "./held-items/turn-end-status"; +import { EvolutionStatBoostHeldItem, SpeciesStatBoostHeldItem, type StatBoostParams } from "./held-items/stat-booster"; +import { type SurviveChanceParams, SurviveChanceHeldItem } from "./held-items/survive-chance"; +import { type TurnEndHealParams, TurnEndHealHeldItem } from "./held-items/turn-end-heal"; +import { type TurnEndStatusParams, TurnEndStatusHeldItem } from "./held-items/turn-end-status"; export function initHeldItems() { for (const berry of getEnumValues(BerryType)) { @@ -173,37 +169,37 @@ export function initHeldItems() { ); } -type APPLY_HELD_ITEMS_PARAMS = { - [HeldItemEffect.ATTACK_TYPE_BOOST]: ATTACK_TYPE_BOOST_PARAMS; - [HeldItemEffect.TURN_END_HEAL]: TURN_END_HEAL_PARAMS; - [HeldItemEffect.HIT_HEAL]: HIT_HEAL_PARAMS; - [HeldItemEffect.RESET_NEGATIVE_STAT_STAGE]: RESET_NEGATIVE_STAT_STAGE_PARAMS; - [HeldItemEffect.EXP_BOOSTER]: EXP_BOOST_PARAMS; - [HeldItemEffect.BERRY]: BERRY_PARAMS; - [HeldItemEffect.BASE_STAT_BOOSTER]: BASE_STAT_BOOSTER_PARAMS; - [HeldItemEffect.INSTANT_REVIVE]: INSTANT_REVIVE_PARAMS; - [HeldItemEffect.STAT_BOOST]: STAT_BOOST_PARAMS; - [HeldItemEffect.CRIT_BOOST]: CRIT_BOOST_PARAMS; - [HeldItemEffect.TURN_END_STATUS]: TURN_END_STATUS_PARAMS; - [HeldItemEffect.SURVIVE_CHANCE]: SURVIVE_CHANCE_PARAMS; - [HeldItemEffect.BYPASS_SPEED_CHANCE]: BYPASS_SPEED_CHANCE_PARAMS; - [HeldItemEffect.FLINCH_CHANCE]: FLINCH_CHANCE_PARAMS; - [HeldItemEffect.FIELD_EFFECT]: FIELD_EFFECT_PARAMS; - [HeldItemEffect.FRIENDSHIP_BOOSTER]: FRIENDSHIP_BOOST_PARAMS; - [HeldItemEffect.NATURE_WEIGHT_BOOSTER]: NATURE_WEIGHT_BOOST_PARAMS; - [HeldItemEffect.ACCURACY_BOOSTER]: ACCURACY_BOOST_PARAMS; - [HeldItemEffect.MULTI_HIT]: MULTI_HIT_PARAMS; - [HeldItemEffect.DAMAGE_MONEY_REWARD]: DAMAGE_MONEY_REWARD_PARAMS; - [HeldItemEffect.BATON]: BATON_PARAMS; - [HeldItemEffect.CONTACT_ITEM_STEAL_CHANCE]: ITEM_STEAL_PARAMS; - [HeldItemEffect.TURN_END_ITEM_STEAL]: ITEM_STEAL_PARAMS; - [HeldItemEffect.BASE_STAT_TOTAL]: BASE_STAT_TOTAL_PARAMS; - [HeldItemEffect.BASE_STAT_FLAT]: BASE_STAT_FLAT_PARAMS; - [HeldItemEffect.INCREMENTING_STAT]: INCREMENTING_STAT_PARAMS; - [HeldItemEffect.EVO_TRACKER]: EVO_TRACKER_PARAMS; +type ApplyHeldItemsParams = { + [HeldItemEffect.ATTACK_TYPE_BOOST]: AttackTypeBoostParams; + [HeldItemEffect.TURN_END_HEAL]: TurnEndHealParams; + [HeldItemEffect.HIT_HEAL]: HitHealParams; + [HeldItemEffect.RESET_NEGATIVE_STAT_STAGE]: ResetNegativeStatStageParams; + [HeldItemEffect.EXP_BOOSTER]: ExpBoostParams; + [HeldItemEffect.BERRY]: BerryParams; + [HeldItemEffect.BASE_STAT_BOOSTER]: BaseStatBoosterParams; + [HeldItemEffect.INSTANT_REVIVE]: InstantReviveParams; + [HeldItemEffect.STAT_BOOST]: StatBoostParams; + [HeldItemEffect.CRIT_BOOST]: CritBoostParams; + [HeldItemEffect.TURN_END_STATUS]: TurnEndStatusParams; + [HeldItemEffect.SURVIVE_CHANCE]: SurviveChanceParams; + [HeldItemEffect.BYPASS_SPEED_CHANCE]: BypassSpeedChanceParams; + [HeldItemEffect.FLINCH_CHANCE]: FlinchChanceParams; + [HeldItemEffect.FIELD_EFFECT]: FieldEffectParams; + [HeldItemEffect.FRIENDSHIP_BOOSTER]: FriendshipBoostParams; + [HeldItemEffect.NATURE_WEIGHT_BOOSTER]: NatureWeightBoostParams; + [HeldItemEffect.ACCURACY_BOOSTER]: AccuracyBoostParams; + [HeldItemEffect.MULTI_HIT]: MultiHitParams; + [HeldItemEffect.DAMAGE_MONEY_REWARD]: DamageMoneyRewardParams; + [HeldItemEffect.BATON]: BatonParams; + [HeldItemEffect.CONTACT_ITEM_STEAL_CHANCE]: ItemStealParams; + [HeldItemEffect.TURN_END_ITEM_STEAL]: ItemStealParams; + [HeldItemEffect.BASE_STAT_TOTAL]: BaseStatTotalParams; + [HeldItemEffect.BASE_STAT_FLAT]: BaseStatFlatParams; + [HeldItemEffect.INCREMENTING_STAT]: IncrementingStatParams; + [HeldItemEffect.EVO_TRACKER]: EvoTrackerParams; }; -export function applyHeldItems(effect: T, params: APPLY_HELD_ITEMS_PARAMS[T]) { +export function applyHeldItems(effect: T, params: ApplyHeldItemsParams[T]) { const pokemon = params.pokemon; if (pokemon) { for (const item of Object.keys(pokemon.heldItemManager.heldItems)) { diff --git a/src/items/apply-trainer-items.ts b/src/items/apply-trainer-items.ts index f6db615cb25..7135edd0ff9 100644 --- a/src/items/apply-trainer-items.ts +++ b/src/items/apply-trainer-items.ts @@ -1,41 +1,41 @@ import { allTrainerItems } from "./all-trainer-items"; import { - type BOOLEAN_HOLDER_PARAMS, - type NUMBER_HOLDER_PARAMS, - type POKEMON_PARAMS, - type PRESERVE_BERRY_PARAMS, - TRAINER_ITEM_EFFECT, + type BooleanHolderParams, + type NumberHolderParams, + type PokemonParams, + type PreserveBerryParams, + TrainerItemEffect, } from "./trainer-item"; import type { TrainerItemManager } from "./trainer-item-manager"; -export type APPLY_TRAINER_ITEMS_PARAMS = { - [TRAINER_ITEM_EFFECT.LEVEL_INCREMENT_BOOSTER]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.PRESERVE_BERRY]: PRESERVE_BERRY_PARAMS; - [TRAINER_ITEM_EFFECT.HEALING_BOOSTER]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.EXP_BOOSTER]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.SHINY_RATE_BOOSTER]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.CRITICAL_CATCH_CHANCE_BOOSTER]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.EXTRA_REWARD]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.HEAL_SHOP_COST]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.DOUBLE_BATTLE_CHANCE_BOOSTER]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.TEMP_STAT_STAGE_BOOSTER]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.TEMP_ACCURACY_BOOSTER]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.TEMP_CRIT_BOOSTER]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.ENEMY_DAMAGE_BOOSTER]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.ENEMY_DAMAGE_REDUCER]: NUMBER_HOLDER_PARAMS; - [TRAINER_ITEM_EFFECT.ENEMY_HEAL]: POKEMON_PARAMS; - [TRAINER_ITEM_EFFECT.ENEMY_ATTACK_STATUS_CHANCE]: POKEMON_PARAMS; - [TRAINER_ITEM_EFFECT.ENEMY_STATUS_HEAL_CHANCE]: POKEMON_PARAMS; - [TRAINER_ITEM_EFFECT.ENEMY_ENDURE_CHANCE]: POKEMON_PARAMS; - [TRAINER_ITEM_EFFECT.ENEMY_FUSED_CHANCE]: BOOLEAN_HOLDER_PARAMS; +export type ApplyTrainerItemsParams = { + [TrainerItemEffect.LEVEL_INCREMENT_BOOSTER]: NumberHolderParams; + [TrainerItemEffect.PRESERVE_BERRY]: PreserveBerryParams; + [TrainerItemEffect.HEALING_BOOSTER]: NumberHolderParams; + [TrainerItemEffect.EXP_BOOSTER]: NumberHolderParams; + [TrainerItemEffect.MONEY_MULTIPLIER]: NumberHolderParams; + [TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER]: NumberHolderParams; + [TrainerItemEffect.SHINY_RATE_BOOSTER]: NumberHolderParams; + [TrainerItemEffect.CRITICAL_CATCH_CHANCE_BOOSTER]: NumberHolderParams; + [TrainerItemEffect.EXTRA_REWARD]: NumberHolderParams; + [TrainerItemEffect.HEAL_SHOP_COST]: NumberHolderParams; + [TrainerItemEffect.DOUBLE_BATTLE_CHANCE_BOOSTER]: NumberHolderParams; + [TrainerItemEffect.TEMP_STAT_STAGE_BOOSTER]: NumberHolderParams; + [TrainerItemEffect.TEMP_ACCURACY_BOOSTER]: NumberHolderParams; + [TrainerItemEffect.TEMP_CRIT_BOOSTER]: NumberHolderParams; + [TrainerItemEffect.ENEMY_DAMAGE_BOOSTER]: NumberHolderParams; + [TrainerItemEffect.ENEMY_DAMAGE_REDUCER]: NumberHolderParams; + [TrainerItemEffect.ENEMY_HEAL]: PokemonParams; + [TrainerItemEffect.ENEMY_ATTACK_STATUS_CHANCE]: PokemonParams; + [TrainerItemEffect.ENEMY_STATUS_HEAL_CHANCE]: PokemonParams; + [TrainerItemEffect.ENEMY_ENDURE_CHANCE]: PokemonParams; + [TrainerItemEffect.ENEMY_FUSED_CHANCE]: BooleanHolderParams; }; -export function applyTrainerItems( +export function applyTrainerItems( effect: T, manager: TrainerItemManager, - params: APPLY_TRAINER_ITEMS_PARAMS[T], + params: ApplyTrainerItemsParams[T], ) { if (manager) { for (const item of Object.keys(manager.trainerItems)) { diff --git a/src/items/held-items/accuracy-booster.ts b/src/items/held-items/accuracy-booster.ts index 0dcc6293a95..6ecbc883c26 100644 --- a/src/items/held-items/accuracy-booster.ts +++ b/src/items/held-items/accuracy-booster.ts @@ -3,7 +3,7 @@ import type { NumberHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; import { HeldItemEffect, HeldItem } from "../held-item"; -export interface ACCURACY_BOOST_PARAMS { +export interface AccuracyBoostParams { /** The pokemon with the item */ pokemon: Pokemon; /** The amount of exp to gain */ @@ -36,7 +36,7 @@ export class AccuracyBoosterHeldItem extends HeldItem { * @param moveAccuracy {@linkcode NumberHolder} holding the move accuracy boost * @returns always `true` */ - apply(params: ACCURACY_BOOST_PARAMS): boolean { + apply(params: AccuracyBoostParams): boolean { const pokemon = params.pokemon; const moveAccuracy = params.moveAccuracy; const stackCount = pokemon.heldItemManager.getStack(this.type); diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index 09d4f3e9c50..3e102527d49 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -5,7 +5,7 @@ import type { NumberHolder } from "#app/utils/common"; import type Pokemon from "#app/field/pokemon"; import { HeldItemEffect, HeldItem } from "#app/items/held-item"; -export interface ATTACK_TYPE_BOOST_PARAMS { +export interface AttackTypeBoostParams { /** The pokemon with the item */ pokemon: Pokemon; /** The resolved type of the move */ @@ -66,7 +66,7 @@ export class AttackTypeBoosterHeldItem extends HeldItem { return `${HeldItemNames[this.type]?.toLowerCase()}`; } - apply(params: ATTACK_TYPE_BOOST_PARAMS): void { + apply(params: AttackTypeBoostParams): void { const pokemon = params.pokemon; const moveType = params.moveType; const movePower = params.movePower; diff --git a/src/items/held-items/base-stat-booster.ts b/src/items/held-items/base-stat-booster.ts index 1595c25b986..72428a2d572 100644 --- a/src/items/held-items/base-stat-booster.ts +++ b/src/items/held-items/base-stat-booster.ts @@ -4,7 +4,7 @@ import { getStatKey, type PermanentStat, Stat } from "#enums/stat"; import i18next from "i18next"; import { HeldItemEffect, HeldItem } from "../held-item"; -export interface BASE_STAT_BOOSTER_PARAMS { +export interface BaseStatBoosterParams { /** The pokemon with the item */ pokemon: Pokemon; baseStats: number[]; @@ -71,7 +71,7 @@ export class BaseStatBoosterHeldItem extends HeldItem { * @param baseStats the base stats of the {@linkcode Pokemon} * @returns always `true` */ - apply(params: BASE_STAT_BOOSTER_PARAMS): boolean { + apply(params: BaseStatBoosterParams): boolean { const pokemon = params.pokemon; const stackCount = pokemon.heldItemManager.getStack(this.type); const baseStats = params.baseStats; diff --git a/src/items/held-items/base-stat-flat.ts b/src/items/held-items/base-stat-flat.ts index 80285d91716..999e0788faa 100644 --- a/src/items/held-items/base-stat-flat.ts +++ b/src/items/held-items/base-stat-flat.ts @@ -3,7 +3,7 @@ import { HeldItemEffect, HeldItem } from "../held-item"; import { Stat } from "#enums/stat"; import i18next from "i18next"; -export interface BASE_STAT_FLAT_PARAMS { +export interface BaseStatFlatParams { /** The pokemon with the item */ pokemon: Pokemon; /** The amount of exp to gain */ @@ -37,7 +37,7 @@ export class BaseStatFlatHeldItem extends HeldItem { * @param baseStats The base stats of the {@linkcode Pokemon} * @returns always `true` */ - apply(params: BASE_STAT_FLAT_PARAMS): boolean { + apply(params: BaseStatFlatParams): boolean { const pokemon = params.pokemon; const baseStats = params.baseStats; const stats = this.getStats(pokemon); diff --git a/src/items/held-items/base-stat-total.ts b/src/items/held-items/base-stat-total.ts index d54602c227a..ecce12dca41 100644 --- a/src/items/held-items/base-stat-total.ts +++ b/src/items/held-items/base-stat-total.ts @@ -3,7 +3,7 @@ import i18next from "i18next"; import { HeldItemEffect, HeldItem } from "../held-item"; import type { HeldItemId } from "#enums/held-item-id"; -export interface BASE_STAT_TOTAL_PARAMS { +export interface BaseStatTotalParams { /** The pokemon with the item */ pokemon: Pokemon; /** The amount of exp to gain */ @@ -56,7 +56,7 @@ export class BaseStatTotalHeldItem extends HeldItem { * @param baseStats the base stats of the {@linkcode Pokemon} * @returns always `true` */ - apply(params: BASE_STAT_TOTAL_PARAMS): boolean { + apply(params: BaseStatTotalParams): boolean { const baseStats = params.baseStats; // Modifies the passed in baseStats[] array baseStats.forEach((v, i) => { diff --git a/src/items/held-items/baton.ts b/src/items/held-items/baton.ts index 1045b3b5a9d..9e2c31f2cf0 100644 --- a/src/items/held-items/baton.ts +++ b/src/items/held-items/baton.ts @@ -2,7 +2,7 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; import { HeldItemEffect, HeldItem } from "../held-item"; -export interface BATON_PARAMS { +export interface BatonParams { /** The pokemon with the item */ pokemon: Pokemon; /** The amount of exp to gain */ diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts index 68e4a5b10bb..4387009761e 100644 --- a/src/items/held-items/berry.ts +++ b/src/items/held-items/berry.ts @@ -6,7 +6,7 @@ import { ConsumableHeldItem, HeldItemEffect } from "#app/items/held-item"; import { BooleanHolder } from "#app/utils/common"; import { BerryType } from "#enums/berry-type"; import { HeldItemId } from "#enums/held-item-id"; -import { TRAINER_ITEM_EFFECT } from "../trainer-item"; +import { TrainerItemEffect } from "../trainer-item"; interface BerryTypeToHeldItemMap { [key: number]: HeldItemId; @@ -26,7 +26,7 @@ export const berryTypeToHeldItem: BerryTypeToHeldItemMap = { [BerryType.LEPPA]: HeldItemId.LEPPA_BERRY, }; -export interface BERRY_PARAMS { +export interface BerryParams { /** The pokemon with the berry */ pokemon: Pokemon; } @@ -69,7 +69,7 @@ export class BerryHeldItem extends ConsumableHeldItem { * @param pokemon The {@linkcode Pokemon} that holds the berry * @returns always `true` */ - apply(params: BERRY_PARAMS): boolean { + apply(params: BerryParams): boolean { const pokemon = params.pokemon; if (!this.shouldApply(pokemon)) { @@ -77,7 +77,7 @@ export class BerryHeldItem extends ConsumableHeldItem { } const preserve = new BooleanHolder(false); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.PRESERVE_BERRY, { pokemon: pokemon, doPreserve: preserve }); + globalScene.applyPlayerItems(TrainerItemEffect.PRESERVE_BERRY, { pokemon: pokemon, doPreserve: preserve }); const consumed = !preserve.value; // munch the berry and trigger unburden-like effects diff --git a/src/items/held-items/bypass-speed-chance.ts b/src/items/held-items/bypass-speed-chance.ts index c7734b1ca48..c99f1022114 100644 --- a/src/items/held-items/bypass-speed-chance.ts +++ b/src/items/held-items/bypass-speed-chance.ts @@ -6,7 +6,7 @@ import i18next from "i18next"; import { getPokemonNameWithAffix } from "#app/messages"; import { Command } from "#enums/command"; -export interface BYPASS_SPEED_CHANCE_PARAMS { +export interface BypassSpeedChanceParams { /** The pokemon with the item */ pokemon: Pokemon; doBypassSpeed: BooleanHolder; @@ -37,7 +37,7 @@ export class BypassSpeedChanceHeldItem extends HeldItem { * @param doBypassSpeed {@linkcode BooleanHolder} that is `true` if speed should be bypassed * @returns `true` if {@linkcode BypassSpeedChanceModifier} has been applied */ - apply(params: BYPASS_SPEED_CHANCE_PARAMS): boolean { + apply(params: BypassSpeedChanceParams): boolean { const pokemon = params.pokemon; const doBypassSpeed = params.doBypassSpeed; const stackCount = pokemon.heldItemManager.getStack(this.type); diff --git a/src/items/held-items/crit-booster.ts b/src/items/held-items/crit-booster.ts index 646a3221a80..e6e264f9867 100644 --- a/src/items/held-items/crit-booster.ts +++ b/src/items/held-items/crit-booster.ts @@ -4,7 +4,7 @@ import type { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import { HeldItemEffect, HeldItem } from "../held-item"; -export interface CRIT_BOOST_PARAMS { +export interface CritBoostParams { /** The pokemon with the item */ pokemon: Pokemon; critStage: NumberHolder; @@ -34,7 +34,7 @@ export class CritBoostHeldItem extends HeldItem { * @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level * @returns always `true` */ - apply(params: CRIT_BOOST_PARAMS): boolean { + apply(params: CritBoostParams): boolean { params.critStage.value += this.stageIncrement; return true; } @@ -71,7 +71,7 @@ export class SpeciesCritBoostHeldItem extends CritBoostHeldItem { // ); // } - apply(params: CRIT_BOOST_PARAMS): boolean { + apply(params: CritBoostParams): boolean { const pokemon = params.pokemon; const fitsSpecies = this.species.includes(pokemon.getSpeciesForm(true).speciesId) || diff --git a/src/items/held-items/damage-money-reward.ts b/src/items/held-items/damage-money-reward.ts index 8844f08b9eb..cd97c61938f 100644 --- a/src/items/held-items/damage-money-reward.ts +++ b/src/items/held-items/damage-money-reward.ts @@ -2,9 +2,9 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { NumberHolder } from "#app/utils/common"; import { HeldItemEffect, HeldItem } from "../held-item"; -import { TRAINER_ITEM_EFFECT } from "../trainer-item"; +import { TrainerItemEffect } from "../trainer-item"; -export interface DAMAGE_MONEY_REWARD_PARAMS { +export interface DamageMoneyRewardParams { /** The pokemon with the item */ pokemon: Pokemon; /** The amount of exp to gain */ @@ -20,12 +20,12 @@ export class DamageMoneyRewardHeldItem extends HeldItem { * @param multiplier {@linkcode NumberHolder} holding the multiplier value * @returns always `true` */ - apply(params: DAMAGE_MONEY_REWARD_PARAMS): boolean { + apply(params: DamageMoneyRewardParams): boolean { const pokemon = params.pokemon; const damage = params.damage; const stackCount = pokemon.heldItemManager.getStack(this.type); const moneyAmount = new NumberHolder(Math.floor(damage * (0.5 * stackCount))); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); + globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); globalScene.addMoney(moneyAmount.value); return true; diff --git a/src/items/held-items/evo-tracker.ts b/src/items/held-items/evo-tracker.ts index 5d7f124073d..851b1f8c7ec 100644 --- a/src/items/held-items/evo-tracker.ts +++ b/src/items/held-items/evo-tracker.ts @@ -6,7 +6,7 @@ import i18next from "i18next"; import { HeldItemEffect, HeldItem } from "../held-item"; import { TrainerItemId } from "#enums/trainer-item-id"; -export interface EVO_TRACKER_PARAMS { +export interface EvoTrackerParams { /** The pokemon with the item */ pokemon: Pokemon; } diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts index e6d49747542..f817aafbbcf 100644 --- a/src/items/held-items/exp-booster.ts +++ b/src/items/held-items/exp-booster.ts @@ -4,7 +4,7 @@ import type { HeldItemId } from "#enums/held-item-id"; import i18next from "i18next"; import { HeldItemEffect, HeldItem } from "../held-item"; -export interface EXP_BOOST_PARAMS { +export interface ExpBoostParams { /** The pokemon with the item */ pokemon: Pokemon; /** The amount of exp to gain */ @@ -45,7 +45,7 @@ export class ExpBoosterHeldItem extends HeldItem { * @param boost {@linkcode NumberHolder} holding the exp boost value * @returns always `true` */ - apply(params: EXP_BOOST_PARAMS): boolean { + apply(params: ExpBoostParams): boolean { const pokemon = params.pokemon; const expAmount = params.expAmount; const stackCount = pokemon.heldItemManager.getStack(this.type); diff --git a/src/items/held-items/field-effect.ts b/src/items/held-items/field-effect.ts index 3853cf1381e..95ef5e0ab08 100644 --- a/src/items/held-items/field-effect.ts +++ b/src/items/held-items/field-effect.ts @@ -2,7 +2,7 @@ import type Pokemon from "#app/field/pokemon"; import { HeldItemEffect, HeldItem } from "#app/items/held-item"; import type { NumberHolder } from "#app/utils/common"; -export interface FIELD_EFFECT_PARAMS { +export interface FieldEffectParams { pokemon: Pokemon; /** The pokemon with the item */ fieldDuration: NumberHolder; @@ -24,7 +24,7 @@ export class FieldEffectHeldItem extends HeldItem { * @param fieldDuration {@linkcode NumberHolder} that stores the current field effect duration * @returns `true` if the field effect extension was applied successfully */ - apply(params: FIELD_EFFECT_PARAMS): boolean { + apply(params: FieldEffectParams): boolean { const pokemon = params.pokemon; const stackCount = pokemon.heldItemManager.getStack(this.type); params.fieldDuration.value += 2 * stackCount; diff --git a/src/items/held-items/flinch-chance.ts b/src/items/held-items/flinch-chance.ts index c6c5565058d..d90dd7ade41 100644 --- a/src/items/held-items/flinch-chance.ts +++ b/src/items/held-items/flinch-chance.ts @@ -3,7 +3,7 @@ import { HeldItemEffect, HeldItem } from "#app/items/held-item"; import type { BooleanHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; -export interface FLINCH_CHANCE_PARAMS { +export interface FlinchChanceParams { /** The pokemon with the item */ pokemon: Pokemon; flinched: BooleanHolder; @@ -41,7 +41,7 @@ export class FlinchChanceHeldItem extends HeldItem { * @param flinched - A {@linkcode BooleanHolder} holding whether the pokemon has flinched * @returns `true` if {@linkcode FlinchChanceModifier} was applied successfully */ - apply(params: FLINCH_CHANCE_PARAMS): boolean { + apply(params: FlinchChanceParams): boolean { const pokemon = params.pokemon; const flinched = params.flinched; const stackCount = pokemon.heldItemManager.getStack(this.type); diff --git a/src/items/held-items/friendship-booster.ts b/src/items/held-items/friendship-booster.ts index 4e3fd32a6b1..5d187033fe6 100644 --- a/src/items/held-items/friendship-booster.ts +++ b/src/items/held-items/friendship-booster.ts @@ -3,7 +3,7 @@ import type { NumberHolder } from "#app/utils/common"; import i18next from "i18next"; import { HeldItemEffect, HeldItem } from "../held-item"; -export interface FRIENDSHIP_BOOST_PARAMS { +export interface FriendshipBoostParams { /** The pokemon with the item */ pokemon: Pokemon; /** The amount of exp to gain */ @@ -23,7 +23,7 @@ export class FriendshipBoosterHeldItem extends HeldItem { * @param friendship {@linkcode NumberHolder} holding the friendship boost value * @returns always `true` */ - apply(params: FRIENDSHIP_BOOST_PARAMS): boolean { + apply(params: FriendshipBoostParams): boolean { const pokemon = params.pokemon; const friendship = params.friendship; const stackCount = pokemon.heldItemManager.getStack(this.type); diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts index 799a3e4d56d..74e5168ee62 100644 --- a/src/items/held-items/hit-heal.ts +++ b/src/items/held-items/hit-heal.ts @@ -6,7 +6,7 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { toDmgValue } from "#app/utils/common"; import { getPokemonNameWithAffix } from "#app/messages"; -export interface HIT_HEAL_PARAMS { +export interface HitHealParams { /** The pokemon with the item */ pokemon: Pokemon; } @@ -31,7 +31,7 @@ export class HitHealHeldItem extends HeldItem { * @param pokemon The {@linkcode Pokemon} that holds the item * @returns `true` if the {@linkcode Pokemon} was healed */ - apply(params: HIT_HEAL_PARAMS): boolean { + apply(params: HitHealParams): boolean { const pokemon = params.pokemon; const stackCount = pokemon.heldItemManager.getStack(this.type); if (pokemon.turnData.totalDamageDealt > 0 && !pokemon.isFullHp()) { diff --git a/src/items/held-items/incrementing-stat.ts b/src/items/held-items/incrementing-stat.ts index c6157856cc2..d95d2f81044 100644 --- a/src/items/held-items/incrementing-stat.ts +++ b/src/items/held-items/incrementing-stat.ts @@ -4,7 +4,7 @@ import { Stat } from "#enums/stat"; import type { NumberHolder } from "#app/utils/common"; import i18next from "i18next"; -export interface INCREMENTING_STAT_PARAMS { +export interface IncrementingStatParams { /** The pokemon with the item */ pokemon: Pokemon; stat: Stat; @@ -45,7 +45,7 @@ export class IncrementingStatHeldItem extends HeldItem { * @param statHolder The {@linkcode NumberHolder} that holds the stat * @returns always `true` */ - apply(params: INCREMENTING_STAT_PARAMS): boolean { + apply(params: IncrementingStatParams): boolean { const pokemon = params.pokemon; const stackCount = pokemon.heldItemManager.getStack(this.type); const statHolder = params.statHolder; diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts index c700dcf97b2..765e3a611e5 100644 --- a/src/items/held-items/instant-revive.ts +++ b/src/items/held-items/instant-revive.ts @@ -7,7 +7,7 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { toDmgValue } from "#app/utils/common"; import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; -export interface INSTANT_REVIVE_PARAMS { +export interface InstantReviveParams { /** The pokemon with the item */ pokemon: Pokemon; } @@ -38,7 +38,7 @@ export class InstantReviveHeldItem extends ConsumableHeldItem { * @param pokemon {@linkcode Pokemon} that holds the item * @returns `true` if any stat stages were reset, false otherwise */ - apply(params: INSTANT_REVIVE_PARAMS): boolean { + apply(params: InstantReviveParams): boolean { const pokemon = params.pokemon; // Restore the Pokemon to half HP globalScene.phaseManager.unshiftPhase( diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts index 3279b4d98cf..26ae198c46e 100644 --- a/src/items/held-items/item-steal.ts +++ b/src/items/held-items/item-steal.ts @@ -7,7 +7,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { allHeldItems } from "../all-held-items"; import { globalScene } from "#app/global-scene"; -export interface ITEM_STEAL_PARAMS { +export interface ItemStealParams { /** The pokemon with the item */ pokemon: Pokemon; /** The pokemon to steal from (optional) */ @@ -30,7 +30,7 @@ export abstract class ItemTransferHeldItem extends HeldItem { * @param _args N/A * @returns `true` if an item was stolen; false otherwise. */ - apply(params: ITEM_STEAL_PARAMS): boolean { + apply(params: ItemStealParams): boolean { const opponents = this.getTargets(params); if (!opponents.length) { @@ -70,11 +70,11 @@ export abstract class ItemTransferHeldItem extends HeldItem { return !!transferredModifierTypes.length; } - abstract getTargets(params: ITEM_STEAL_PARAMS): Pokemon[]; + abstract getTargets(params: ItemStealParams): Pokemon[]; - abstract getTransferredItemCount(params: ITEM_STEAL_PARAMS): number; + abstract getTransferredItemCount(params: ItemStealParams): number; - abstract getTransferMessage(params: ITEM_STEAL_PARAMS, itemId: HeldItemId): string; + abstract getTransferMessage(params: ItemStealParams, itemId: HeldItemId): string; } /** @@ -96,15 +96,15 @@ export class TurnEndItemStealHeldItem extends ItemTransferHeldItem { * @param _args N/A * @returns the opponents of the source {@linkcode Pokemon} */ - getTargets(params: ITEM_STEAL_PARAMS): Pokemon[] { + getTargets(params: ItemStealParams): Pokemon[] { return params.pokemon instanceof Pokemon ? params.pokemon.getOpponents() : []; } - getTransferredItemCount(_params: ITEM_STEAL_PARAMS): number { + getTransferredItemCount(_params: ItemStealParams): number { return 1; } - getTransferMessage(params: ITEM_STEAL_PARAMS, itemId: HeldItemId): string { + getTransferMessage(params: ItemStealParams, itemId: HeldItemId): string { return i18next.t("modifier:turnHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(params.target), itemName: allHeldItems[itemId].name, @@ -148,16 +148,16 @@ export class ContactItemStealChanceHeldItem extends ItemTransferHeldItem { * @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(params: ITEM_STEAL_PARAMS): Pokemon[] { + getTargets(params: ItemStealParams): Pokemon[] { return params.target ? [params.target] : []; } - getTransferredItemCount(params: ITEM_STEAL_PARAMS): number { + getTransferredItemCount(params: ItemStealParams): number { const stackCount = params.pokemon.heldItemManager.getStack(this.type); return randSeedFloat() <= this.chance * stackCount ? 1 : 0; } - getTransferMessage(params: ITEM_STEAL_PARAMS, itemId: HeldItemId): string { + getTransferMessage(params: ItemStealParams, itemId: HeldItemId): string { return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(params.target), itemName: allHeldItems[itemId].name, diff --git a/src/items/held-items/multi-hit.ts b/src/items/held-items/multi-hit.ts index 12472162afe..d4773630739 100644 --- a/src/items/held-items/multi-hit.ts +++ b/src/items/held-items/multi-hit.ts @@ -5,7 +5,7 @@ import type { MoveId } from "#enums/move-id"; import { allMoves } from "#app/data/data-lists"; import i18next from "i18next"; -export interface MULTI_HIT_PARAMS { +export interface MultiHitParams { pokemon: Pokemon; moveId: MoveId; count?: NumberHolder; @@ -33,7 +33,7 @@ export class MultiHitHeldItem extends HeldItem { * @param damageMultiplier {@linkcode NumberHolder} holding a damage multiplier applied to a strike of this move * @returns always `true` */ - apply(params: MULTI_HIT_PARAMS): boolean { + apply(params: MultiHitParams): boolean { const pokemon = params.pokemon; const move = allMoves[params.moveId]; /** diff --git a/src/items/held-items/nature-weight-booster.ts b/src/items/held-items/nature-weight-booster.ts index 76a81df6423..f24646ee4ea 100644 --- a/src/items/held-items/nature-weight-booster.ts +++ b/src/items/held-items/nature-weight-booster.ts @@ -2,7 +2,7 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; import { HeldItemEffect, HeldItem } from "../held-item"; -export interface NATURE_WEIGHT_BOOST_PARAMS { +export interface NatureWeightBoostParams { /** The pokemon with the item */ pokemon: Pokemon; /** The amount of exp to gain */ @@ -18,7 +18,7 @@ export class NatureWeightBoosterHeldItem extends HeldItem { * @param multiplier {@linkcode NumberHolder} holding the nature weight * @returns `true` if multiplier was applied */ - apply(params: NATURE_WEIGHT_BOOST_PARAMS): boolean { + apply(params: NatureWeightBoostParams): boolean { const pokemon = params.pokemon; const multiplier = params.multiplier; const stackCount = pokemon.heldItemManager.getStack(this.type); diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index 76b17e19ffb..fbda4a27e14 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -5,7 +5,7 @@ import i18next from "i18next"; import { ConsumableHeldItem, HeldItemEffect } from "../held-item"; import { getPokemonNameWithAffix } from "#app/messages"; -export interface RESET_NEGATIVE_STAT_STAGE_PARAMS { +export interface ResetNegativeStatStageParams { /** The pokemon with the item */ pokemon: Pokemon; /** Whether the move was used by a player pokemon */ @@ -38,7 +38,7 @@ export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem { * @param pokemon {@linkcode Pokemon} that holds the item * @returns `true` if any stat stages were reset, false otherwise */ - apply(params: RESET_NEGATIVE_STAT_STAGE_PARAMS): boolean { + apply(params: ResetNegativeStatStageParams): boolean { const pokemon = params.pokemon; const isPlayer = params.isPlayer; let statRestored = false; diff --git a/src/items/held-items/stat-booster.ts b/src/items/held-items/stat-booster.ts index 8e6553053f5..71647cb821b 100644 --- a/src/items/held-items/stat-booster.ts +++ b/src/items/held-items/stat-booster.ts @@ -6,7 +6,7 @@ import type { SpeciesId } from "#enums/species-id"; import type { Stat } from "#enums/stat"; import { HeldItemEffect, HeldItem } from "../held-item"; -export interface STAT_BOOST_PARAMS { +export interface StatBoostParams { /** The pokemon with the item */ pokemon: Pokemon; stat: Stat; @@ -53,7 +53,7 @@ export class StatBoostHeldItem extends HeldItem { * @returns `true` if the stat boost applies successfully, false otherwise * @see shouldApply */ - apply(params: STAT_BOOST_PARAMS): boolean { + apply(params: StatBoostParams): boolean { params.statValue.value *= this.multiplier; return true; } @@ -94,7 +94,7 @@ export class EvolutionStatBoostHeldItem extends StatBoostHeldItem { * @returns `true` if the stat boost applies successfully, false otherwise * @see shouldApply */ - override apply(params: STAT_BOOST_PARAMS): boolean { + override apply(params: StatBoostParams): boolean { const pokemon = params.pokemon; const isUnevolved = pokemon.getSpeciesForm(true).speciesId in pokemonEvolutions; @@ -166,7 +166,7 @@ export class SpeciesStatBoostHeldItem extends StatBoostHeldItem { // ); // } - apply(params: STAT_BOOST_PARAMS): boolean { + apply(params: StatBoostParams): boolean { const pokemon = params.pokemon; const fitsSpecies = this.species.includes(pokemon.getSpeciesForm(true).speciesId) || diff --git a/src/items/held-items/survive-chance.ts b/src/items/held-items/survive-chance.ts index 1e3b1718ab1..2b9717163f3 100644 --- a/src/items/held-items/survive-chance.ts +++ b/src/items/held-items/survive-chance.ts @@ -5,7 +5,7 @@ import { globalScene } from "#app/global-scene"; import i18next from "i18next"; import { getPokemonNameWithAffix } from "#app/messages"; -export interface SURVIVE_CHANCE_PARAMS { +export interface SurviveChanceParams { /** The pokemon with the item */ pokemon: Pokemon; surviveDamage: BooleanHolder; @@ -36,7 +36,7 @@ export class SurviveChanceHeldItem extends HeldItem { * @param surviveDamage {@linkcode BooleanHolder} that holds the survive damage * @returns `true` if the survive damage has been applied */ - apply(params: SURVIVE_CHANCE_PARAMS): boolean { + apply(params: SurviveChanceParams): boolean { const pokemon = params.pokemon; const surviveDamage = params.surviveDamage; const stackCount = pokemon.heldItemManager.getStack(this.type); diff --git a/src/items/held-items/turn-end-heal.ts b/src/items/held-items/turn-end-heal.ts index ebd86718d7a..f78f3e91d91 100644 --- a/src/items/held-items/turn-end-heal.ts +++ b/src/items/held-items/turn-end-heal.ts @@ -6,7 +6,7 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { toDmgValue } from "#app/utils/common"; import { getPokemonNameWithAffix } from "#app/messages"; -export interface TURN_END_HEAL_PARAMS { +export interface TurnEndHealParams { /** The pokemon with the item */ pokemon: Pokemon; } @@ -14,7 +14,7 @@ export interface TURN_END_HEAL_PARAMS { export class TurnEndHealHeldItem extends HeldItem { public effects: HeldItemEffect[] = [HeldItemEffect.TURN_END_HEAL]; - apply(params: TURN_END_HEAL_PARAMS): boolean { + apply(params: TurnEndHealParams): boolean { const pokemon = params.pokemon; const stackCount = pokemon.heldItemManager.getStack(this.type); if (pokemon.isFullHp()) { diff --git a/src/items/held-items/turn-end-status.ts b/src/items/held-items/turn-end-status.ts index 6a4ae620b47..66abd455886 100644 --- a/src/items/held-items/turn-end-status.ts +++ b/src/items/held-items/turn-end-status.ts @@ -3,7 +3,7 @@ import { HeldItemEffect, HeldItem } from "#app/items/held-item"; import type { StatusEffect } from "#enums/status-effect"; import type { HeldItemId } from "#enums/held-item-id"; -export interface TURN_END_STATUS_PARAMS { +export interface TurnEndStatusParams { /** The pokemon with the item */ pokemon: Pokemon; } @@ -30,7 +30,7 @@ export class TurnEndStatusHeldItem extends HeldItem { * @param pokemon {@linkcode Pokemon} that holds the held item * @returns `true` if the status effect was applied successfully */ - apply(params: TURN_END_STATUS_PARAMS): boolean { + apply(params: TurnEndStatusParams): boolean { return params.pokemon.trySetStatus(this.effect, true, undefined, undefined, this.name); } diff --git a/src/items/trainer-item.ts b/src/items/trainer-item.ts index a5877a9ad97..bc7ce86644a 100644 --- a/src/items/trainer-item.ts +++ b/src/items/trainer-item.ts @@ -11,7 +11,7 @@ import { getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/st import { getPokemonNameWithAffix } from "#app/messages"; import { StatusEffect } from "#enums/status-effect"; -export const TRAINER_ITEM_EFFECT = { +export const TrainerItemEffect = { LEVEL_INCREMENT_BOOSTER: 1, PRESERVE_BERRY: 2, HEALING_BOOSTER: 3, @@ -38,17 +38,17 @@ export const TRAINER_ITEM_EFFECT = { ENEMY_FUSED_CHANCE: 21, } as const; -export type TRAINER_ITEM_EFFECT = (typeof TRAINER_ITEM_EFFECT)[keyof typeof TRAINER_ITEM_EFFECT]; +export type TrainerItemEffect = (typeof TrainerItemEffect)[keyof typeof TrainerItemEffect]; -export interface NUMBER_HOLDER_PARAMS { +export interface NumberHolderParams { numberHolder: NumberHolder; } -export interface BOOLEAN_HOLDER_PARAMS { +export interface BooleanHolderParams { booleanHolder: BooleanHolder; } -export interface POKEMON_PARAMS { +export interface PokemonParams { pokemon: Pokemon; } @@ -57,7 +57,7 @@ export class TrainerItem { public type: TrainerItemId; public maxStackCount: number; public isLapsing = false; - public effects: TRAINER_ITEM_EFFECT[] = []; + public effects: TrainerItemEffect[] = []; //TODO: If this is actually never changed by any subclass, perhaps it should not be here public soundName = "se/restore"; @@ -119,9 +119,9 @@ export class TrainerItem { // Candy Jar export class LevelIncrementBoosterTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.LEVEL_INCREMENT_BOOSTER]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.LEVEL_INCREMENT_BOOSTER]; - apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + apply(manager: TrainerItemManager, params: NumberHolderParams) { const count = params.numberHolder; const stack = manager.getStack(this.type); count.value += stack; @@ -129,15 +129,15 @@ export class LevelIncrementBoosterTrainerItem extends TrainerItem { } // Berry Pouch -export interface PRESERVE_BERRY_PARAMS { +export interface PreserveBerryParams { pokemon: Pokemon; doPreserve: BooleanHolder; } export class PreserveBerryTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.PRESERVE_BERRY]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.PRESERVE_BERRY]; - apply(manager: TrainerItemManager, params: PRESERVE_BERRY_PARAMS) { + apply(manager: TrainerItemManager, params: PreserveBerryParams) { const stack = manager.getStack(this.type); params.doPreserve.value ||= params.pokemon.randBattleSeedInt(10) < stack * 3; } @@ -145,7 +145,7 @@ export class PreserveBerryTrainerItem extends TrainerItem { // Healing Charm export class HealingBoosterTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.HEALING_BOOSTER]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.HEALING_BOOSTER]; private multiplier: number; constructor(type: TrainerItemId, multiplier: number, stackCount?: number) { @@ -154,7 +154,7 @@ export class HealingBoosterTrainerItem extends TrainerItem { this.multiplier = multiplier; } - apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + apply(manager: TrainerItemManager, params: NumberHolderParams) { const healingMultiplier = params.numberHolder; const stack = manager.getStack(this.type); healingMultiplier.value *= 1 + (this.multiplier - 1) * stack; @@ -163,7 +163,7 @@ export class HealingBoosterTrainerItem extends TrainerItem { // Exp Booster export class ExpBoosterTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.EXP_BOOSTER]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.EXP_BOOSTER]; private boostPercent: number; constructor(type: TrainerItemId, boostPercent: number, stackCount?: number) { @@ -178,7 +178,7 @@ export class ExpBoosterTrainerItem extends TrainerItem { }); } - apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + apply(manager: TrainerItemManager, params: NumberHolderParams) { const boost = params.numberHolder; const stack = manager.getStack(this.type); boost.value = Math.floor(boost.value * (1 + stack * this.boostPercent * 0.01)); @@ -186,9 +186,9 @@ export class ExpBoosterTrainerItem extends TrainerItem { } export class MoneyMultiplierTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.MONEY_MULTIPLIER]; - apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + apply(manager: TrainerItemManager, params: NumberHolderParams) { const moneyMultiplier = params.numberHolder; const stack = manager.getStack(this.type); moneyMultiplier.value += Math.floor(moneyMultiplier.value * 0.2 * stack); @@ -196,9 +196,9 @@ export class MoneyMultiplierTrainerItem extends TrainerItem { } export class HiddenAbilityChanceBoosterTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.HIDDEN_ABILITY_CHANCE_BOOSTER]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER]; - apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + apply(manager: TrainerItemManager, params: NumberHolderParams) { const boost = params.numberHolder; const stack = manager.getStack(this.type); boost.value *= Math.pow(2, -1 - stack); @@ -206,9 +206,9 @@ export class HiddenAbilityChanceBoosterTrainerItem extends TrainerItem { } export class ShinyRateBoosterTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.SHINY_RATE_BOOSTER]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.SHINY_RATE_BOOSTER]; - apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + apply(manager: TrainerItemManager, params: NumberHolderParams) { const boost = params.numberHolder; const stack = manager.getStack(this.type); boost.value *= Math.pow(2, 1 + stack); @@ -216,9 +216,9 @@ export class ShinyRateBoosterTrainerItem extends TrainerItem { } export class CriticalCatchChanceBoosterTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.CRITICAL_CATCH_CHANCE_BOOSTER]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.CRITICAL_CATCH_CHANCE_BOOSTER]; - apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + apply(manager: TrainerItemManager, params: NumberHolderParams) { const boost = params.numberHolder; const stack = manager.getStack(this.type); boost.value *= 1.5 + stack / 2; @@ -226,9 +226,9 @@ export class CriticalCatchChanceBoosterTrainerItem extends TrainerItem { } export class ExtraRewardTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.EXTRA_REWARD]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.EXTRA_REWARD]; - apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + apply(manager: TrainerItemManager, params: NumberHolderParams) { const count = params.numberHolder; const stack = manager.getStack(this.type); count.value += stack; @@ -236,7 +236,7 @@ export class ExtraRewardTrainerItem extends TrainerItem { } export class HealShopCostTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.HEAL_SHOP_COST]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.HEAL_SHOP_COST]; public readonly shopMultiplier: number; constructor(type: TrainerItemId, shopMultiplier: number, stackCount?: number) { @@ -245,7 +245,7 @@ export class HealShopCostTrainerItem extends TrainerItem { this.shopMultiplier = shopMultiplier; } - apply(_manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + apply(_manager: TrainerItemManager, params: NumberHolderParams) { const moneyCost = params.numberHolder; moneyCost.value = Math.floor(moneyCost.value * this.shopMultiplier); } @@ -281,7 +281,7 @@ export class LapsingTrainerItem extends TrainerItem { } export class DoubleBattleChanceBoosterTrainerItem extends LapsingTrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.DOUBLE_BATTLE_CHANCE_BOOSTER]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.DOUBLE_BATTLE_CHANCE_BOOSTER]; get description(): string { return i18next.t("modifierType:ModifierType.DoubleBattleChanceBoosterModifierType.description", { @@ -289,7 +289,7 @@ export class DoubleBattleChanceBoosterTrainerItem extends LapsingTrainerItem { }); } - apply(_manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + apply(_manager: TrainerItemManager, params: NumberHolderParams) { const doubleBattleChance = params.numberHolder; // 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 @@ -311,7 +311,7 @@ export const tempStatToTrainerItem: TempStatToTrainerItemMap = { }; export class TempStatStageBoosterTrainerItem extends LapsingTrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.TEMP_STAT_STAGE_BOOSTER]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.TEMP_STAT_STAGE_BOOSTER]; private stat: TempBattleStat; constructor(type: TrainerItemId, stat: TempBattleStat, stackCount?: number) { @@ -332,7 +332,7 @@ export class TempStatStageBoosterTrainerItem extends LapsingTrainerItem { }); } - apply(_manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + apply(_manager: TrainerItemManager, params: NumberHolderParams) { const statLevel = params.numberHolder; const boost = 0.3; statLevel.value += boost; @@ -340,7 +340,7 @@ export class TempStatStageBoosterTrainerItem extends LapsingTrainerItem { } export class TempAccuracyBoosterTrainerItem extends LapsingTrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.TEMP_ACCURACY_BOOSTER]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.TEMP_ACCURACY_BOOSTER]; get name(): string { return i18next.t(`modifierType:TempStatStageBoosterItem.${TrainerItemNames[this.type]?.toLowerCase()}`); @@ -354,7 +354,7 @@ export class TempAccuracyBoosterTrainerItem extends LapsingTrainerItem { }); } - apply(_manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + apply(_manager: TrainerItemManager, params: NumberHolderParams) { const statLevel = params.numberHolder; const boost = 1; statLevel.value += boost; @@ -362,7 +362,7 @@ export class TempAccuracyBoosterTrainerItem extends LapsingTrainerItem { } export class TempCritBoosterTrainerItem extends LapsingTrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.TEMP_CRIT_BOOSTER]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.TEMP_CRIT_BOOSTER]; get description(): string { return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { @@ -371,21 +371,21 @@ export class TempCritBoosterTrainerItem extends LapsingTrainerItem { }); } - apply(_manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS) { + apply(_manager: TrainerItemManager, params: NumberHolderParams) { const critLevel = params.numberHolder; critLevel.value++; } } export class EnemyDamageBoosterTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_DAMAGE_BOOSTER]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_DAMAGE_BOOSTER]; public damageBoost = 1.05; get iconName(): string { return "wl_item_drop"; } - apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS): boolean { + apply(manager: TrainerItemManager, params: NumberHolderParams): boolean { const stack = manager.getStack(this.type); const multiplier = params.numberHolder; @@ -400,14 +400,14 @@ export class EnemyDamageBoosterTrainerItem extends TrainerItem { } export class EnemyDamageReducerTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_DAMAGE_REDUCER]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_DAMAGE_REDUCER]; public damageReduction = 0.975; get iconName(): string { return "wl_guard_spec"; } - apply(manager: TrainerItemManager, params: NUMBER_HOLDER_PARAMS): boolean { + apply(manager: TrainerItemManager, params: NumberHolderParams): boolean { const stack = manager.getStack(this.type); const multiplier = params.numberHolder; @@ -422,14 +422,14 @@ export class EnemyDamageReducerTrainerItem extends TrainerItem { } export class EnemyTurnHealTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_HEAL]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_HEAL]; public healPercent = 2; get iconName(): string { return "wl_potion"; } - apply(manager: TrainerItemManager, params: POKEMON_PARAMS): boolean { + apply(manager: TrainerItemManager, params: PokemonParams): boolean { const stack = manager.getStack(this.type); const enemyPokemon = params.pokemon; @@ -455,7 +455,7 @@ export class EnemyTurnHealTrainerItem extends TrainerItem { } export class EnemyAttackStatusEffectChanceTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_ATTACK_STATUS_CHANCE]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_ATTACK_STATUS_CHANCE]; public effect: StatusEffect; constructor(type: TrainerItemId, effect: StatusEffect, stackCount?: number) { @@ -484,7 +484,7 @@ export class EnemyAttackStatusEffectChanceTrainerItem extends TrainerItem { }); } - apply(manager: TrainerItemManager, params: POKEMON_PARAMS): boolean { + apply(manager: TrainerItemManager, params: PokemonParams): boolean { const stack = manager.getStack(this.type); const enemyPokemon = params.pokemon; const chance = this.getChance(); @@ -502,14 +502,14 @@ export class EnemyAttackStatusEffectChanceTrainerItem extends TrainerItem { } export class EnemyStatusEffectHealChanceTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_STATUS_HEAL_CHANCE]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_STATUS_HEAL_CHANCE]; public chance = 0.025; get iconName(): string { return "wl_full_heal"; } - apply(manager: TrainerItemManager, params: POKEMON_PARAMS): boolean { + apply(manager: TrainerItemManager, params: PokemonParams): boolean { const stack = manager.getStack(this.type); const enemyPokemon = params.pokemon; @@ -527,7 +527,7 @@ export class EnemyStatusEffectHealChanceTrainerItem extends TrainerItem { } export class EnemyEndureChanceTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_ENDURE_CHANCE]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_ENDURE_CHANCE]; public chance = 2; get iconName(): string { @@ -540,7 +540,7 @@ export class EnemyEndureChanceTrainerItem extends TrainerItem { }); } - apply(manager: TrainerItemManager, params: POKEMON_PARAMS): boolean { + apply(manager: TrainerItemManager, params: PokemonParams): boolean { const stack = manager.getStack(this.type); const target = params.pokemon; @@ -557,14 +557,14 @@ export class EnemyEndureChanceTrainerItem extends TrainerItem { } export class EnemyFusionChanceTrainerItem extends TrainerItem { - public effects: TRAINER_ITEM_EFFECT[] = [TRAINER_ITEM_EFFECT.ENEMY_FUSED_CHANCE]; + public effects: TrainerItemEffect[] = [TrainerItemEffect.ENEMY_FUSED_CHANCE]; public chance = 0.01; get iconName(): string { return "wl_custom_spliced"; } - apply(manager: TrainerItemManager, params: BOOLEAN_HOLDER_PARAMS) { + apply(manager: TrainerItemManager, params: BooleanHolderParams) { const stack = manager.getStack(this.type); const isFusion = params.booleanHolder; if (randSeedFloat() > this.chance * stack) { diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 6bf1c517795..f9bf653c978 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -74,7 +74,7 @@ import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeld import { berryTypeToHeldItem } from "#app/items/held-items/berry"; import { TrainerItemId } from "#enums/trainer-item-id"; import { allTrainerItems } from "#app/data/data-lists"; -import { tempStatToTrainerItem, TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { tempStatToTrainerItem, TrainerItemEffect } from "#app/items/trainer-item"; type NewModifierFunc = (type: ModifierType, args: any[]) => Modifier | null; @@ -818,7 +818,7 @@ export class MoneyRewardModifierType extends ModifierType { getDescription(): string { const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); + globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); const formattedMoney = formatMoney(globalScene.moneyFormat, moneyAmount.value); return i18next.t("modifierType:ModifierType.MoneyRewardModifierType.description", { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 716937289b4..dfaf897018c 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -23,7 +23,7 @@ import type { ModifierInstanceMap, ModifierString } from "#app/@types/modifier-t import { assignItemsFromConfiguration } from "#app/items/held-item-pool"; import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; import { HeldItemId } from "#enums/held-item-id"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; import type { TrainerItemConfiguration } from "#app/items/trainer-item-data-types"; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -383,7 +383,7 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier { * @returns always `true` */ override apply(playerPokemon: PlayerPokemon, levelCount: NumberHolder = new NumberHolder(1)): boolean { - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.LEVEL_INCREMENT_BOOSTER, { numberHolder: levelCount }); + globalScene.applyPlayerItems(TrainerItemEffect.LEVEL_INCREMENT_BOOSTER, { numberHolder: levelCount }); playerPokemon.level += levelCount.value; if (playerPokemon.level <= globalScene.getMaxExpLevel(true)) { @@ -532,7 +532,7 @@ export class MoneyRewardModifier extends ConsumableModifier { override apply(): boolean { const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); + globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); globalScene.addMoney(moneyAmount.value); diff --git a/src/phases/exp-phase.ts b/src/phases/exp-phase.ts index 123d03de62b..24bec4de725 100644 --- a/src/phases/exp-phase.ts +++ b/src/phases/exp-phase.ts @@ -3,7 +3,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import i18next from "i18next"; import { NumberHolder } from "#app/utils/common"; import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; export class ExpPhase extends PlayerPartyMemberPokemonPhase { public readonly phaseName = "ExpPhase"; @@ -20,7 +20,7 @@ export class ExpPhase extends PlayerPartyMemberPokemonPhase { const pokemon = this.getPokemon(); const exp = new NumberHolder(this.expValue); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.EXP_BOOSTER, { numberHolder: exp }); + globalScene.applyPlayerItems(TrainerItemEffect.EXP_BOOSTER, { numberHolder: exp }); exp.value = Math.floor(exp.value); globalScene.ui.showText( i18next.t("battle:expGain", { diff --git a/src/phases/money-reward-phase.ts b/src/phases/money-reward-phase.ts index 6fff53542ae..30aa451989c 100644 --- a/src/phases/money-reward-phase.ts +++ b/src/phases/money-reward-phase.ts @@ -3,7 +3,7 @@ import { ArenaTagType } from "#app/enums/arena-tag-type"; import i18next from "i18next"; import { NumberHolder } from "#app/utils/common"; import { BattlePhase } from "./battle-phase"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; export class MoneyRewardPhase extends BattlePhase { public readonly phaseName = "MoneyRewardPhase"; @@ -18,7 +18,7 @@ export class MoneyRewardPhase extends BattlePhase { start() { const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); + globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); if (globalScene.arena.getTag(ArenaTagType.HAPPY_HOUR)) { moneyAmount.value *= 2; diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 2e006c3a46f..22dccec3eb7 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -43,7 +43,7 @@ import { DamageAchv } from "#app/system/achv"; import { applyHeldItems } from "#app/items/all-held-items"; import { HeldItemEffect } from "#app/items/held-item"; import { isVirtual, isReflected, MoveUseMode } from "#enums/move-use-mode"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; export type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier]; @@ -856,7 +856,7 @@ export class MoveEffectPhase extends PokemonPhase { if (isBlockedBySubstitute) { substitute.hp -= dmg; } else if (!target.isPlayer() && dmg >= target.hp) { - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.ENEMY_ENDURE_CHANCE, { pokemon: target }); + globalScene.applyPlayerItems(TrainerItemEffect.ENEMY_ENDURE_CHANCE, { pokemon: target }); } const damage = isBlockedBySubstitute diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts index 1f758133d87..af7fd7fe404 100644 --- a/src/phases/pokemon-heal-phase.ts +++ b/src/phases/pokemon-heal-phase.ts @@ -11,7 +11,7 @@ import { NumberHolder } from "#app/utils/common"; import { CommonAnimPhase } from "./common-anim-phase"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import type { HealBlockTag } from "#app/data/battler-tags"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; export class PokemonHealPhase extends CommonAnimPhase { public readonly phaseName = "PokemonHealPhase"; @@ -75,7 +75,7 @@ export class PokemonHealPhase extends CommonAnimPhase { if (healOrDamage) { const hpRestoreMultiplier = new NumberHolder(1); if (!this.revive) { - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HEALING_BOOSTER, { numberHolder: hpRestoreMultiplier }); + globalScene.applyPlayerItems(TrainerItemEffect.HEALING_BOOSTER, { numberHolder: hpRestoreMultiplier }); } const healAmount = new NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value)); if (healAmount.value < 0) { diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 50b7594ca3e..301280b1636 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -27,7 +27,7 @@ import { BattlePhase } from "./battle-phase"; import Overrides from "#app/overrides"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { isNullOrUndefined, NumberHolder } from "#app/utils/common"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; export type ModifierSelectCallback = (rowCursor: number, cursor: number) => boolean; @@ -152,7 +152,7 @@ export class SelectModifierPhase extends BattlePhase { const modifierType = shopOption.type; // Apply Black Sludge to healing item cost const healingItemCost = new NumberHolder(shopOption.cost); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HEAL_SHOP_COST, { numberHolder: healingItemCost }); + globalScene.applyPlayerItems(TrainerItemEffect.HEAL_SHOP_COST, { numberHolder: healingItemCost }); const cost = healingItemCost.value; if (globalScene.money < cost && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { @@ -403,7 +403,7 @@ export class SelectModifierPhase extends BattlePhase { // Function that determines how many reward slots are available private getModifierCount(): number { const modifierCountHolder = new NumberHolder(3); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.EXTRA_REWARD, { numberHolder: modifierCountHolder }); + globalScene.applyPlayerItems(TrainerItemEffect.EXTRA_REWARD, { numberHolder: modifierCountHolder }); // If custom modifiers are specified, overrides default item count if (this.customModifierSettings) { @@ -474,7 +474,7 @@ export class SelectModifierPhase extends BattlePhase { // Apply Black Sludge to reroll cost const modifiedRerollCost = new NumberHolder(baseMultiplier); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HEAL_SHOP_COST, { numberHolder: modifiedRerollCost }); + globalScene.applyPlayerItems(TrainerItemEffect.HEAL_SHOP_COST, { numberHolder: modifiedRerollCost }); return modifiedRerollCost.value; } diff --git a/src/phases/show-party-exp-bar-phase.ts b/src/phases/show-party-exp-bar-phase.ts index 6c4e01fe840..613e2a3f5c9 100644 --- a/src/phases/show-party-exp-bar-phase.ts +++ b/src/phases/show-party-exp-bar-phase.ts @@ -3,7 +3,7 @@ import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; import { ExpNotification } from "#app/enums/exp-notification"; import { NumberHolder } from "#app/utils/common"; import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { public readonly phaseName = "ShowPartyExpBarPhase"; @@ -20,7 +20,7 @@ export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { const pokemon = this.getPokemon(); const exp = new NumberHolder(this.expValue); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.EXP_BOOSTER, { numberHolder: exp }); + globalScene.applyPlayerItems(TrainerItemEffect.EXP_BOOSTER, { numberHolder: exp }); exp.value = Math.floor(exp.value); const lastLevel = pokemon.level; diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index a222734161d..895db0a888a 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -10,7 +10,7 @@ import { FieldPhase } from "./field-phase"; import { globalScene } from "#app/global-scene"; import { applyHeldItems } from "#app/items/all-held-items"; import { HeldItemEffect } from "#app/items/held-item"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; export class TurnEndPhase extends FieldPhase { public readonly phaseName = "TurnEndPhase"; @@ -41,8 +41,8 @@ export class TurnEndPhase extends FieldPhase { } if (!pokemon.isPlayer()) { - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.ENEMY_HEAL, { pokemon: pokemon }); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.ENEMY_STATUS_HEAL_CHANCE, { pokemon: pokemon }); + globalScene.applyPlayerItems(TrainerItemEffect.ENEMY_HEAL, { pokemon: pokemon }); + globalScene.applyPlayerItems(TrainerItemEffect.ENEMY_STATUS_HEAL_CHANCE, { pokemon: pokemon }); } applyAbAttrs("PostTurnAbAttr", { pokemon }); diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 8f3cddbdb52..4776310062e 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -16,7 +16,7 @@ import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import Phaser from "phaser"; import type { PokeballType } from "#enums/pokeball"; import { TrainerItemId } from "#enums/trainer-item-id"; -import { TRAINER_ITEM_EFFECT } from "#app/items/trainer-item"; +import { TrainerItemEffect } from "#app/items/trainer-item"; export const SHOP_OPTIONS_ROW_LIMIT = 7; const SINGLE_SHOP_ROW_YOFFSET = 12; @@ -214,7 +214,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const typeOptions = args[1] as ModifierTypeOption[]; const removeHealShop = globalScene.gameMode.hasNoShop; const baseShopCost = new NumberHolder(globalScene.getWaveMoneyAmount(1)); - globalScene.applyPlayerItems(TRAINER_ITEM_EFFECT.HEAL_SHOP_COST, { numberHolder: baseShopCost }); + globalScene.applyPlayerItems(TrainerItemEffect.HEAL_SHOP_COST, { numberHolder: baseShopCost }); const shopTypeOptions = !removeHealShop ? getPlayerShopModifierTypeOptionsForWave(globalScene.currentBattle.waveIndex, baseShopCost.value) : []; From d0ebb32f9eb088fd9de6b345ae65dfcc04462708 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 10 Jul 2025 01:49:20 -0700 Subject: [PATCH 07/39] Add a couple of `TODO`s --- src/items/modifier-to-item-migrator-utils.ts | 1 + src/items/trainer-item-data-types.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/items/modifier-to-item-migrator-utils.ts b/src/items/modifier-to-item-migrator-utils.ts index 15682064597..0927099bac8 100644 --- a/src/items/modifier-to-item-migrator-utils.ts +++ b/src/items/modifier-to-item-migrator-utils.ts @@ -68,6 +68,7 @@ function mapModifierCategoryToItems(modifier: ModifierCategoryString, typeId: st if (modifier === "SpeciesStatBoosterModifier") { const stats = args[1]; const species = args[3]; + // TODO: why is this not `species === SpeciesId.SPECIES_NAME`? if (SpeciesId.PIKACHU in species) { return HeldItemId.LIGHT_BALL; } diff --git a/src/items/trainer-item-data-types.ts b/src/items/trainer-item-data-types.ts index a024c4c362f..179c4de3eaa 100644 --- a/src/items/trainer-item-data-types.ts +++ b/src/items/trainer-item-data-types.ts @@ -1,3 +1,4 @@ +// TODO: move to `src/@types/` import type { RewardTier } from "#enums/reward-tier"; import type { TrainerItemId } from "#enums/trainer-item-id"; From 61cd122223516a5618d343c1e69cac08795787eb Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 10 Jul 2025 18:50:41 +0200 Subject: [PATCH 08/39] Removed default value from maxStackCount --- src/items/held-items/accuracy-booster.ts | 2 +- src/items/held-items/attack-type-booster.ts | 2 +- src/items/held-items/base-stat-booster.ts | 2 +- src/items/held-items/base-stat-total.ts | 2 +- src/items/held-items/crit-booster.ts | 4 ++-- src/items/held-items/evo-tracker.ts | 2 +- src/items/held-items/exp-booster.ts | 2 +- src/items/held-items/flinch-chance.ts | 2 +- src/items/held-items/item-steal.ts | 4 ++-- src/items/held-items/stat-booster.ts | 4 ++-- src/items/held-items/turn-end-status.ts | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/items/held-items/accuracy-booster.ts b/src/items/held-items/accuracy-booster.ts index 6ecbc883c26..fde2a06d351 100644 --- a/src/items/held-items/accuracy-booster.ts +++ b/src/items/held-items/accuracy-booster.ts @@ -15,7 +15,7 @@ export class AccuracyBoosterHeldItem extends HeldItem { private accuracyAmount: number; - constructor(type: HeldItemId, maxStackCount = 1, accuracy: number) { + constructor(type: HeldItemId, maxStackCount: number, accuracy: number) { super(type, maxStackCount); this.accuracyAmount = accuracy; } diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index 3e102527d49..aad80b3c1d0 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -46,7 +46,7 @@ export class AttackTypeBoosterHeldItem extends HeldItem { public powerBoost: number; // This constructor may need a revision - constructor(type: HeldItemId, maxStackCount = 1, moveType: PokemonType, powerBoost: number) { + constructor(type: HeldItemId, maxStackCount: number, moveType: PokemonType, powerBoost: number) { super(type, maxStackCount); this.moveType = moveType; this.powerBoost = powerBoost; diff --git a/src/items/held-items/base-stat-booster.ts b/src/items/held-items/base-stat-booster.ts index 72428a2d572..8ca87da7732 100644 --- a/src/items/held-items/base-stat-booster.ts +++ b/src/items/held-items/base-stat-booster.ts @@ -36,7 +36,7 @@ export class BaseStatBoosterHeldItem extends HeldItem { public effects: HeldItemEffect[] = [HeldItemEffect.BASE_STAT_BOOSTER]; public stat: PermanentStat; - constructor(type: HeldItemId, maxStackCount = 1, stat: PermanentStat) { + constructor(type: HeldItemId, maxStackCount: number, stat: PermanentStat) { super(type, maxStackCount); this.stat = stat; } diff --git a/src/items/held-items/base-stat-total.ts b/src/items/held-items/base-stat-total.ts index ecce12dca41..c862d56e176 100644 --- a/src/items/held-items/base-stat-total.ts +++ b/src/items/held-items/base-stat-total.ts @@ -18,7 +18,7 @@ export class BaseStatTotalHeldItem extends HeldItem { public isTransferable = false; public statModifier: number; - constructor(type: HeldItemId, maxStackCount = 1, statModifier: number) { + constructor(type: HeldItemId, maxStackCount: number, statModifier: number) { super(type, maxStackCount); this.statModifier = statModifier; } diff --git a/src/items/held-items/crit-booster.ts b/src/items/held-items/crit-booster.ts index e6e264f9867..10bf5f0b1dd 100644 --- a/src/items/held-items/crit-booster.ts +++ b/src/items/held-items/crit-booster.ts @@ -22,7 +22,7 @@ export class CritBoostHeldItem extends HeldItem { /** The amount of stages by which the held item increases the current critical-hit stage value */ protected stageIncrement: number; - constructor(type: HeldItemId, maxStackCount = 1, stageIncrement: number) { + constructor(type: HeldItemId, maxStackCount: number, stageIncrement: number) { super(type, maxStackCount); this.stageIncrement = stageIncrement; @@ -50,7 +50,7 @@ export class SpeciesCritBoostHeldItem extends CritBoostHeldItem { /** The species that the held item's critical-hit stage boost applies to */ private species: SpeciesId[]; - constructor(type: HeldItemId, maxStackCount = 1, stageIncrement: number, species: SpeciesId[]) { + constructor(type: HeldItemId, maxStackCount: number, stageIncrement: number, species: SpeciesId[]) { super(type, maxStackCount, stageIncrement); this.species = species; diff --git a/src/items/held-items/evo-tracker.ts b/src/items/held-items/evo-tracker.ts index 851b1f8c7ec..9aa397bb421 100644 --- a/src/items/held-items/evo-tracker.ts +++ b/src/items/held-items/evo-tracker.ts @@ -18,7 +18,7 @@ export class EvoTrackerHeldItem extends HeldItem { protected required: number; public isTransferable = false; - constructor(type: HeldItemId, maxStackCount = 1, species: SpeciesId, required: number) { + constructor(type: HeldItemId, maxStackCount: number, species: SpeciesId, required: number) { super(type, maxStackCount); this.species = species; this.required = required; diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts index f817aafbbcf..4c38398e78b 100644 --- a/src/items/held-items/exp-booster.ts +++ b/src/items/held-items/exp-booster.ts @@ -16,7 +16,7 @@ export class ExpBoosterHeldItem extends HeldItem { private boostPercent: number; private boostMultiplier: number; - constructor(type: HeldItemId, maxStackCount = 1, boostPercent: number) { + constructor(type: HeldItemId, maxStackCount: number, boostPercent: number) { super(type, maxStackCount); this.boostPercent = boostPercent; this.boostMultiplier = boostPercent * 0.01; diff --git a/src/items/held-items/flinch-chance.ts b/src/items/held-items/flinch-chance.ts index d90dd7ade41..a8b65aa0db5 100644 --- a/src/items/held-items/flinch-chance.ts +++ b/src/items/held-items/flinch-chance.ts @@ -19,7 +19,7 @@ export class FlinchChanceHeldItem extends HeldItem { public effects: HeldItemEffect[] = [HeldItemEffect.FLINCH_CHANCE]; private chance: number; - constructor(type: HeldItemId, maxStackCount = 1, chance: number) { + constructor(type: HeldItemId, maxStackCount: number, chance: number) { super(type, maxStackCount); this.chance = chance; // 10 diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts index 26ae198c46e..f9dce8cc75b 100644 --- a/src/items/held-items/item-steal.ts +++ b/src/items/held-items/item-steal.ts @@ -15,7 +15,7 @@ export interface ItemStealParams { target?: Pokemon; } -// constructor(type: HeldItemId, maxStackCount = 1, boostPercent: number) { +// constructor(type: HeldItemId, maxStackCount: number, boostPercent: number) { /** * Abstract class for held items that steal other Pokemon's items. @@ -129,7 +129,7 @@ export class ContactItemStealChanceHeldItem extends ItemTransferHeldItem { public readonly chancePercent: number; public readonly chance: number; - constructor(type: HeldItemId, maxStackCount = 1, chancePercent: number) { + constructor(type: HeldItemId, maxStackCount: number, chancePercent: number) { super(type, maxStackCount); this.chancePercent = chancePercent; diff --git a/src/items/held-items/stat-booster.ts b/src/items/held-items/stat-booster.ts index 71647cb821b..43a655b364d 100644 --- a/src/items/held-items/stat-booster.ts +++ b/src/items/held-items/stat-booster.ts @@ -26,7 +26,7 @@ export class StatBoostHeldItem extends HeldItem { /** The multiplier used to increase the relevant stat(s) */ protected multiplier: number; - constructor(type: HeldItemId, maxStackCount = 1, stats: Stat[], multiplier: number) { + constructor(type: HeldItemId, maxStackCount: number, stats: Stat[], multiplier: number) { super(type, maxStackCount); this.stats = stats; @@ -141,7 +141,7 @@ export class SpeciesStatBoostHeldItem extends StatBoostHeldItem { constructor( type: SpeciesStatBoosterItemId, - maxStackCount = 1, + maxStackCount: number, stats: Stat[], multiplier: number, species: SpeciesId[], diff --git a/src/items/held-items/turn-end-status.ts b/src/items/held-items/turn-end-status.ts index 66abd455886..ae6b9f6dffc 100644 --- a/src/items/held-items/turn-end-status.ts +++ b/src/items/held-items/turn-end-status.ts @@ -19,7 +19,7 @@ export class TurnEndStatusHeldItem extends HeldItem { /** The status effect to be applied by the held item */ public effect: StatusEffect; - constructor(type: HeldItemId, maxStackCount = 1, effect: StatusEffect) { + constructor(type: HeldItemId, maxStackCount: number, effect: StatusEffect) { super(type, maxStackCount); this.effect = effect; From 175f2b74e92b0efe574d24a49930c95e4e655a79 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 10 Jul 2025 19:21:40 +0200 Subject: [PATCH 09/39] Fixed some ME tests --- .../fiery-fallout-encounter.test.ts | 20 +++++++------- .../global-trade-system-encounter.test.ts | 20 ++++---------- .../the-strong-stuff-encounter.test.ts | 27 +++++++------------ 3 files changed, 24 insertions(+), 43 deletions(-) diff --git a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index 16adc47ff11..b5e54a23157 100644 --- a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -16,7 +16,6 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import { MoveId } from "#enums/move-id"; import type BattleScene from "#app/battle-scene"; -import { AttackTypeBoosterModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { PokemonType } from "#enums/pokemon-type"; import { Status } from "#app/data/status-effect"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; @@ -30,6 +29,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { AbilityId } from "#enums/ability-id"; import i18next from "i18next"; import { StatusEffect } from "#enums/status-effect"; +import { HeldItemCategoryId } from "#enums/held-item-id"; const namespace = "mysteryEncounters/fieryFallout"; /** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */ @@ -179,13 +179,10 @@ describe("Fiery Fallout - Mystery Encounter", () => { await game.phaseInterceptor.to(SelectModifierPhase, false); expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - const leadPokemonId = scene.getPlayerParty()?.[0].id; - const leadPokemonItems = scene.findModifiers( - m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === leadPokemonId, - true, - ) as PokemonHeldItemModifier[]; - const item = leadPokemonItems.find(i => i instanceof AttackTypeBoosterModifier); - expect(item).toBeDefined; + const hasAttackBooster = scene + .getPlayerParty()?.[0] + .heldItemManager.hasItemCategory(HeldItemCategoryId.TYPE_ATTACK_BOOSTER); + expect(hasAttackBooster).toBe(true); }); }); @@ -268,9 +265,10 @@ describe("Fiery Fallout - Mystery Encounter", () => { await game.phaseInterceptor.to(SelectModifierPhase, false); expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - const leadPokemonItems = scene.getPlayerParty()?.[0].getHeldItems() as PokemonHeldItemModifier[]; - const item = leadPokemonItems.find(i => i instanceof AttackTypeBoosterModifier); - expect(item).toBeDefined; + const hasAttackBooster = scene + .getPlayerParty()?.[0] + .heldItemManager.hasItemCategory(HeldItemCategoryId.TYPE_ATTACK_BOOSTER); + expect(hasAttackBooster).toBe(true); }); it("should leave encounter without battle", async () => { diff --git a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index 5dc3ba90bb0..8a878441e7e 100644 --- a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -9,9 +9,6 @@ import type BattleScene from "#app/battle-scene"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; -import { PokemonNatureWeightModifier } from "#app/modifier/modifier"; -import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { modifierTypes } from "#app/data/data-lists"; import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter"; import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; @@ -19,6 +16,7 @@ import { UiMode } from "#enums/ui-mode"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { RewardTier } from "#enums/reward-tier"; import * as Utils from "#app/utils/common"; +import { HeldItemId } from "#enums/held-item-id"; const namespace = "mysteryEncounters/globalTradeSystem"; const defaultParty = [SpeciesId.LAPRAS, SpeciesId.GENGAR, SpeciesId.ABRA]; @@ -218,11 +216,7 @@ describe("Global Trade System - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty); // Set 2 Soul Dew on party lead - scene.modifiers = []; - const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; - const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; - modifier.stackCount = 2; - scene.addModifier(modifier, true, false, false, true); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.SOUL_DEW, 2); await scene.updateItems(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); @@ -235,8 +229,8 @@ describe("Global Trade System - Mystery Encounter", () => { ) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(1); expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier).toBe(RewardTier.MASTER); - const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier); - expect(soulDewAfter?.stackCount).toBe(1); + const soulDewAfter = scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SOUL_DEW); + expect(soulDewAfter).toBe(1); }); it("should leave encounter without battle", async () => { @@ -245,11 +239,7 @@ describe("Global Trade System - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty); // Set 1 Soul Dew on party lead - scene.modifiers = []; - const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; - const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; - modifier.stackCount = 1; - scene.addModifier(modifier, true, false, false, true); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.SOUL_DEW, 1); await scene.updateItems(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index c1f13d61817..39aa4c41757 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -15,12 +15,10 @@ import { MoveId } from "#enums/move-id"; import type BattleScene from "#app/battle-scene"; import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter"; import { Nature } from "#enums/nature"; -import { BerryType } from "#enums/berry-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { UiMode } from "#enums/ui-mode"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import { BerryModifier, PokemonBaseStatTotalModifier } from "#app/modifier/modifier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; @@ -29,6 +27,9 @@ import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { AbilityId } from "#enums/ability-id"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { HeldItemEffect } from "#app/items/held-item"; +import { HeldItemId } from "#enums/held-item-id"; const namespace = "mysteryEncounters/theStrongStuff"; const defaultParty = [SpeciesId.LAPRAS, SpeciesId.GENGAR, SpeciesId.ABRA]; @@ -113,7 +114,7 @@ describe("The Strong Stuff - Mystery Encounter", () => { customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), nature: Nature.HARDY, moveSet: [MoveId.INFESTATION, MoveId.SALT_CURE, MoveId.GASTRO_ACID, MoveId.HEAL_ORDER], - modifierConfigs: expect.any(Array), + heldItemConfig: expect.any(Array), tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: expect.any(Function), }, @@ -149,7 +150,7 @@ describe("The Strong Stuff - Mystery Encounter", () => { const bstsAfter = scene.getPlayerParty().map(p => { const baseStats = p.getSpeciesForm().baseStats.slice(0); - scene.applyModifiers(PokemonBaseStatTotalModifier, true, p, baseStats); + applyHeldItems(HeldItemEffect.BASE_STAT_TOTAL, { pokemon: p, baseStats: baseStats }); return baseStats.reduce((a, b) => a + b); }); @@ -198,19 +199,11 @@ describe("The Strong Stuff - Mystery Encounter", () => { expect(enemyField[0].summonData.statStages).toEqual([0, 1, 0, 1, 0, 0, 0]); const shuckleItems = enemyField[0].getHeldItems(); expect(shuckleItems.length).toBe(5); - expect(shuckleItems.find(m => m instanceof BerryModifier && m.berryType === BerryType.SITRUS)?.stackCount).toBe( - 1, - ); - expect(shuckleItems.find(m => m instanceof BerryModifier && m.berryType === BerryType.ENIGMA)?.stackCount).toBe( - 1, - ); - expect(shuckleItems.find(m => m instanceof BerryModifier && m.berryType === BerryType.GANLON)?.stackCount).toBe( - 1, - ); - expect(shuckleItems.find(m => m instanceof BerryModifier && m.berryType === BerryType.APICOT)?.stackCount).toBe( - 1, - ); - expect(shuckleItems.find(m => m instanceof BerryModifier && m.berryType === BerryType.LUM)?.stackCount).toBe(2); + expect(enemyField[0].heldItemManager.getStack(HeldItemId.SITRUS_BERRY)).toBe(1); + expect(enemyField[0].heldItemManager.getStack(HeldItemId.ENIGMA_BERRY)).toBe(1); + expect(enemyField[0].heldItemManager.getStack(HeldItemId.GANLON_BERRY)).toBe(1); + expect(enemyField[0].heldItemManager.getStack(HeldItemId.APICOT_BERRY)).toBe(1); + expect(enemyField[0].heldItemManager.getStack(HeldItemId.LUM_BERRY)).toBe(2); expect(enemyField[0].moveset).toEqual([ new PokemonMove(MoveId.INFESTATION), new PokemonMove(MoveId.SALT_CURE), From 2744567b21b755e2439622e9b45aa4349ddca21b Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 10 Jul 2025 23:21:58 +0200 Subject: [PATCH 10/39] Auxiliary functions to fix type complaints in item managers --- src/field/pokemon-held-item-manager.ts | 53 +++++++++++++------------- src/items/trainer-item-manager.ts | 11 +++--- src/utils/common.ts | 8 ++++ 3 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index dceaa7fae0c..839414f8790 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -10,6 +10,7 @@ import { type FormChangeItemSpecs, type HeldItemSaveData, } from "#app/items/held-item-data-types"; +import { getTypedEntries, getTypedKeys } from "#app/utils/common"; export class PokemonItemManager { public heldItems: HeldItemDataMap; @@ -34,58 +35,58 @@ export class PokemonItemManager { generateHeldItemConfiguration(restrictedIds?: HeldItemId[]): HeldItemConfiguration { const config: HeldItemConfiguration = []; - for (const [k, item] of Object.entries(this.heldItems)) { - const id = Number(k); + for (const [id, item] of getTypedEntries(this.heldItems)) { if (item && (!restrictedIds || id in restrictedIds)) { const specs: HeldItemSpecs = { ...item, id }; config.push({ entry: specs, count: 1 }); } } - for (const [k, item] of Object.entries(this.formChangeItems)) { - const id = Number(k); - const specs: FormChangeItemSpecs = { ...item, id }; - config.push({ entry: specs, count: 1 }); + for (const [id, item] of getTypedEntries(this.formChangeItems)) { + if (item) { + const specs: FormChangeItemSpecs = { ...item, id }; + config.push({ entry: specs, count: 1 }); + } } return config; } generateSaveData(): HeldItemSaveData { const saveData: HeldItemSaveData = []; - for (const [k, item] of Object.entries(this.heldItems)) { - const id = Number(k); + for (const [id, item] of getTypedEntries(this.heldItems)) { if (item) { const specs: HeldItemSpecs = { ...item, id }; saveData.push(specs); } } - for (const [k, item] of Object.entries(this.formChangeItems)) { - const id = Number(k); - const specs: FormChangeItemSpecs = { ...item, id }; - saveData.push(specs); + for (const [id, item] of getTypedEntries(this.formChangeItems)) { + if (item) { + const specs: FormChangeItemSpecs = { ...item, id }; + saveData.push(specs); + } } return saveData; } - getHeldItems(): number[] { - return Object.keys(this.heldItems).map(k => Number(k)); + getHeldItems(): HeldItemId[] { + return getTypedKeys(this.heldItems); } - getTransferableHeldItems(): number[] { - return Object.keys(this.heldItems) + getTransferableHeldItems(): HeldItemId[] { + return getTypedKeys(this.heldItems) .filter(k => allHeldItems[k].isTransferable) - .map(k => Number(k)); + .map(k => k); } - getStealableHeldItems(): number[] { - return Object.keys(this.heldItems) + getStealableHeldItems(): HeldItemId[] { + return getTypedKeys(this.heldItems) .filter(k => allHeldItems[k].isStealable) - .map(k => Number(k)); + .map(k => k); } - getSuppressableHeldItems(): number[] { - return Object.keys(this.heldItems) + getSuppressableHeldItems(): HeldItemId[] { + return getTypedKeys(this.heldItems) .filter(k => allHeldItems[k].isSuppressable) - .map(k => Number(k)); + .map(k => k); } hasItem(itemType: HeldItemId): boolean { @@ -93,7 +94,7 @@ export class PokemonItemManager { } hasItemCategory(categoryId: HeldItemCategoryId): boolean { - return Object.keys(this.heldItems).some(id => isItemInCategory(Number(id), categoryId)); + return getTypedKeys(this.heldItems).some(id => isItemInCategory(id, categoryId)); } getStack(itemType: HeldItemId): number { @@ -117,7 +118,7 @@ export class PokemonItemManager { overrideItems(newItems: HeldItemDataMap) { this.heldItems = newItems; // The following is to allow randomly generated item configs to have stack 0 - for (const [item, properties] of Object.entries(this.heldItems)) { + for (const [item, properties] of getTypedEntries(this.heldItems)) { if (!properties || properties.stack <= 0) { delete this.heldItems[item]; } @@ -208,7 +209,7 @@ export class PokemonItemManager { } getFormChangeItems(): FormChangeItem[] { - return Object.keys(this.formChangeItems).map(k => Number(k)); + return getTypedKeys(this.formChangeItems).map(k => k); } getActiveFormChangeItems(): FormChangeItem[] { diff --git a/src/items/trainer-item-manager.ts b/src/items/trainer-item-manager.ts index 04da440cde6..f8329ac6341 100644 --- a/src/items/trainer-item-manager.ts +++ b/src/items/trainer-item-manager.ts @@ -7,6 +7,7 @@ import { type TrainerItemDataMap, type TrainerItemSpecs, } from "#app/items/trainer-item-data-types"; +import { getTypedEntries, getTypedKeys } from "#app/utils/common"; export class TrainerItemManager { public trainerItems: TrainerItemDataMap; @@ -29,8 +30,7 @@ export class TrainerItemManager { generateTrainerItemConfiguration(restrictedIds?: TrainerItemId[]): TrainerItemConfiguration { const config: TrainerItemConfiguration = []; - for (const [k, item] of Object.entries(this.trainerItems)) { - const id = Number(k); + for (const [id, item] of getTypedEntries(this.trainerItems)) { if (item && (!restrictedIds || id in restrictedIds)) { const specs: TrainerItemSpecs = { ...item, id }; config.push({ entry: specs, count: 1 }); @@ -41,8 +41,7 @@ export class TrainerItemManager { generateSaveData(): TrainerItemSaveData { const saveData: TrainerItemSaveData = []; - for (const [k, item] of Object.entries(this.trainerItems)) { - const id = Number(k); + for (const [id, item] of getTypedEntries(this.trainerItems)) { if (item) { const specs: TrainerItemSpecs = { ...item, id }; saveData.push(specs); @@ -51,8 +50,8 @@ export class TrainerItemManager { return saveData; } - getTrainerItems(): number[] { - return Object.keys(this.trainerItems).map(k => Number(k)); + getTrainerItems(): TrainerItemId[] { + return getTypedKeys(this.trainerItems); } hasItem(itemType: TrainerItemId): boolean { diff --git a/src/utils/common.ts b/src/utils/common.ts index 2e9f429f1c7..0bb4fada71c 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -285,6 +285,14 @@ export function getEnumValues(enumType: any): number[] { .map(v => Number.parseInt(v!.toString())); } +export function getTypedKeys, K extends number = Extract>(obj: T): K[] { + return Object.keys(obj).map(k => Number(k) as K); +} + +export function getTypedEntries(obj: T): [keyof T, T[keyof T]][] { + return Object.entries(obj) as [keyof T, T[keyof T]][]; +} + export function executeIf(condition: boolean, promiseFunc: () => Promise): Promise { return condition ? promiseFunc() : new Promise(resolve => resolve(null)); } From 849550808a3fbb68d82929a620342c60fbd5ac11 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Fri, 11 Jul 2025 23:58:49 +0200 Subject: [PATCH 11/39] HeldItemManager's .hasItem() now also works on categories --- .../mystery-encounter-requirements.ts | 10 ++++++---- src/enums/held-item-id.ts | 4 ++-- src/field/pokemon-held-item-manager.ts | 17 +++++++++++------ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index ae4ae7ee02c..9ea4d55b1c6 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -800,13 +800,13 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen } export class HeldItemRequirement extends EncounterPokemonRequirement { - requiredHeldItems: HeldItemId[] | HeldItemCategoryId[]; + requiredHeldItems: (HeldItemId | HeldItemCategoryId)[]; minNumberOfPokemon: number; invertQuery: boolean; requireTransferable: boolean; constructor( - heldItem: HeldItemId | HeldItemId[] | HeldItemCategoryId | HeldItemCategoryId[], + heldItem: HeldItemId | HeldItemCategoryId | (HeldItemId | HeldItemCategoryId)[], minNumberOfPokemon = 1, invertQuery = false, requireTransferable = true, @@ -830,8 +830,10 @@ export class HeldItemRequirement extends EncounterPokemonRequirement { if (!this.invertQuery) { return partyPokemon.filter(pokemon => this.requiredHeldItems.some(heldItem => { - (pokemon.heldItemManager.hasItem(heldItem) || pokemon.heldItemManager.hasItemCategory(heldItem)) && - (!this.requireTransferable || allHeldItems[heldItem].isTransferable); + return ( + pokemon.heldItemManager.hasItem(heldItem) && + (!this.requireTransferable || allHeldItems[heldItem].isTransferable) + ); }), ); } diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index c5f101dad5c..0792ffce8b5 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -128,8 +128,8 @@ export function getHeldItemCategory(itemId: HeldItemId): HeldItemCategoryId { return (itemId & ITEM_CATEGORY_MASK) as HeldItemCategoryId; } -export function isCategoryId(categoryId: HeldItemCategoryId): boolean { - return (categoryId & ITEM_CATEGORY_MASK) === categoryId; +export function isCategoryId(id: number): boolean { + return (id & ITEM_CATEGORY_MASK) === id && id in HeldItemCategoryId; } export function isItemInCategory(itemId: HeldItemId, category: HeldItemCategoryId): boolean { diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 839414f8790..d5a6134c01a 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -1,5 +1,11 @@ import { allHeldItems } from "#app/data/data-lists"; -import { isItemInCategory, isItemInRequested, type HeldItemCategoryId, type HeldItemId } from "#app/enums/held-item-id"; +import { + isCategoryId, + isItemInCategory, + isItemInRequested, + type HeldItemCategoryId, + type HeldItemId, +} from "#app/enums/held-item-id"; import type { FormChangeItem } from "#enums/form-change-item"; import { type HeldItemConfiguration, @@ -89,14 +95,13 @@ export class PokemonItemManager { .map(k => k); } - hasItem(itemType: HeldItemId): boolean { + hasItem(itemType: HeldItemId | HeldItemCategoryId): boolean { + if (isCategoryId(itemType)) { + return getTypedKeys(this.heldItems).some(id => isItemInCategory(id, itemType as HeldItemCategoryId)); + } return itemType in this.heldItems; } - hasItemCategory(categoryId: HeldItemCategoryId): boolean { - return getTypedKeys(this.heldItems).some(id => isItemInCategory(id, categoryId)); - } - getStack(itemType: HeldItemId): number { const item = this.heldItems[itemType]; return item ? item.stack : 0; From dbfbc24c8ab0a39a137fe53a19f454aaf6b70d0c Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 12 Jul 2025 00:35:42 +0200 Subject: [PATCH 12/39] Fixed fiery fallout encounter and relative test by making sure that isCategoryId works properly --- .../encounters/fiery-fallout-encounter.ts | 2 +- src/enums/held-item-id.ts | 2 +- src/field/pokemon-held-item-manager.ts | 2 +- .../encounters/fiery-fallout-encounter.test.ts | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 8ad013be6fe..69a24469e20 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -299,7 +299,7 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w function giveLeadPokemonAttackTypeBoostItem() { // Give first party pokemon attack type boost item for free at end of battle - const leadPokemon = globalScene.getPlayerParty()?.[0]; + const leadPokemon = globalScene.getPlayerParty()[0]; if (leadPokemon) { // Generate type booster held item, default to Charcoal if item fails to generate let item = getNewHeldItemFromCategory(HeldItemCategoryId.TYPE_ATTACK_BOOSTER, leadPokemon); diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index 0792ffce8b5..d8fc58e28eb 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -129,7 +129,7 @@ export function getHeldItemCategory(itemId: HeldItemId): HeldItemCategoryId { } export function isCategoryId(id: number): boolean { - return (id & ITEM_CATEGORY_MASK) === id && id in HeldItemCategoryId; + return (Object.values(HeldItemCategoryId) as number[]).includes(id); } export function isItemInCategory(itemId: HeldItemId, category: HeldItemCategoryId): boolean { diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index d5a6134c01a..d5bbeea4389 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -1,9 +1,9 @@ import { allHeldItems } from "#app/data/data-lists"; import { + type HeldItemCategoryId, isCategoryId, isItemInCategory, isItemInRequested, - type HeldItemCategoryId, type HeldItemId, } from "#app/enums/held-item-id"; import type { FormChangeItem } from "#enums/form-change-item"; diff --git a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index b5e54a23157..54153246327 100644 --- a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -180,8 +180,8 @@ describe("Fiery Fallout - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); const hasAttackBooster = scene - .getPlayerParty()?.[0] - .heldItemManager.hasItemCategory(HeldItemCategoryId.TYPE_ATTACK_BOOSTER); + .getPlayerParty()[0] + .heldItemManager.hasItem(HeldItemCategoryId.TYPE_ATTACK_BOOSTER); expect(hasAttackBooster).toBe(true); }); }); @@ -266,8 +266,8 @@ describe("Fiery Fallout - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); const hasAttackBooster = scene - .getPlayerParty()?.[0] - .heldItemManager.hasItemCategory(HeldItemCategoryId.TYPE_ATTACK_BOOSTER); + .getPlayerParty()[0] + .heldItemManager.hasItem(HeldItemCategoryId.TYPE_ATTACK_BOOSTER); expect(hasAttackBooster).toBe(true); }); From e312a4b8f2de54f1db4e2017c651da32e59a81b8 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 12 Jul 2025 12:56:58 +0200 Subject: [PATCH 13/39] Fixed bug-type-superfan encounter; split up HoldingItemRequirement from HeldItemRequirement --- .../encounters/bug-type-superfan-encounter.ts | 20 ++-- .../encounters/delibirdy-encounter.ts | 10 +- .../mystery-encounter-requirements.ts | 93 ++++++++++++++++--- src/field/pokemon-held-item-manager.ts | 9 ++ .../bug-type-superfan-encounter.test.ts | 16 ++-- 5 files changed, 109 insertions(+), 39 deletions(-) diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index 9bc3e293cbe..be5fba3a616 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -31,7 +31,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { CombinationPokemonRequirement, - HeldItemRequirement, + HoldingItemRequirement, TypeRequirement, } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { PokemonType } from "#enums/pokemon-type"; @@ -181,7 +181,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde .withPrimaryPokemonRequirement( CombinationPokemonRequirement.Some( // Must have at least 1 Bug type on team, OR have a bug item somewhere on the team - new HeldItemRequirement(REQUIRED_ITEMS, 1), + new HoldingItemRequirement(REQUIRED_ITEMS, 1), new TypeRequirement(PokemonType.BUG, false, 1), ), ) @@ -403,7 +403,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde .withPrimaryPokemonRequirement( CombinationPokemonRequirement.Some( // Meets one or both of the below reqs - new HeldItemRequirement(REQUIRED_ITEMS, 1), + new HoldingItemRequirement(REQUIRED_ITEMS, 1), ), ) .withDialogue({ @@ -426,9 +426,9 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones - const validItems = pokemon.heldItemManager.getTransferableHeldItems().filter(item => { - item in REQUIRED_ITEMS; - }); + const validItems = pokemon.heldItemManager + .getTransferableHeldItems() + .filter(item => REQUIRED_ITEMS.some(i => i === item)); return validItems.map((item: HeldItemId) => { const option: OptionSelectItem = { @@ -449,9 +449,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde const selectableFilter = (pokemon: Pokemon) => { // If pokemon has valid item, it can be selected - const hasValidItem = pokemon.getHeldItems().some(item => { - item in REQUIRED_ITEMS; - }); + const hasValidItem = pokemon.getHeldItems().some(item => REQUIRED_ITEMS.some(i => i === item)); if (!hasValidItem) { return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null; } @@ -463,10 +461,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde }) .withOptionPhase(async () => { const encounter = globalScene.currentBattle.mysteryEncounter!; - const modifier = encounter.misc.chosenModifier; + const lostItem = encounter.misc.chosenItem; const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; - chosenPokemon.loseHeldItem(modifier, false); + chosenPokemon.loseHeldItem(lostItem, false); globalScene.updateItems(true); const bugNet = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!; diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index a516e3265bc..4e9063ae429 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -4,7 +4,7 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { CombinationPokemonRequirement, - HeldItemRequirement, + HoldingItemRequirement, MoneyRequirement, } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -85,8 +85,8 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with .withPrimaryPokemonRequirement( CombinationPokemonRequirement.Some( // Must also have either option 2 or 3 available to spawn - new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS), - new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true), + new HoldingItemRequirement(OPTION_2_ALLOWED_MODIFIERS), + new HoldingItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true), ), ) .withIntroSpriteConfigs([ @@ -180,7 +180,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with ) .withOption( MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) - .withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS)) + .withPrimaryPokemonRequirement(new HoldingItemRequirement(OPTION_2_ALLOWED_MODIFIERS)) .withDialogue({ buttonLabel: `${namespace}:option.2.label`, buttonTooltip: `${namespace}:option.2.tooltip`, @@ -264,7 +264,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with ) .withOption( MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) - .withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true)) + .withPrimaryPokemonRequirement(new HoldingItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true)) .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index 9ea4d55b1c6..fc829b29f02 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -14,7 +14,7 @@ import { MoveId } from "#enums/move-id"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { TimeOfDay } from "#enums/time-of-day"; -import type { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { getHeldItemCategory, type HeldItemCategoryId, type HeldItemId } from "#enums/held-item-id"; import { allHeldItems } from "#app/data/data-lists"; export interface EncounterRequirement { @@ -799,7 +799,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen } } -export class HeldItemRequirement extends EncounterPokemonRequirement { +export class HoldingItemRequirement extends EncounterPokemonRequirement { requiredHeldItems: (HeldItemId | HeldItemCategoryId)[]; minNumberOfPokemon: number; invertQuery: boolean; @@ -830,23 +830,21 @@ export class HeldItemRequirement extends EncounterPokemonRequirement { if (!this.invertQuery) { return partyPokemon.filter(pokemon => this.requiredHeldItems.some(heldItem => { - return ( - pokemon.heldItemManager.hasItem(heldItem) && - (!this.requireTransferable || allHeldItems[heldItem].isTransferable) - ); + return this.requireTransferable + ? pokemon.heldItemManager.hasTransferableItem(heldItem) + : pokemon.heldItemManager.hasItem(heldItem); }), ); } // for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers // E.g. functions as a blacklist - return partyPokemon.filter( - pokemon => - pokemon.getHeldItems().filter(item => { - return ( - !this.requiredHeldItems.some(heldItem => item === heldItem) && - (!this.requireTransferable || allHeldItems[item].isTransferable) - ); - }).length > 0, + return partyPokemon.filter(pokemon => + pokemon.getHeldItems().some(item => { + return ( + !this.requiredHeldItems.some(heldItem => item === heldItem || getHeldItemCategory(item) === heldItem) && + (!this.requireTransferable || allHeldItems[item].isTransferable) + ); + }), ); } @@ -864,6 +862,73 @@ export class HeldItemRequirement extends EncounterPokemonRequirement { } } +export class HeldItemRequirement extends EncounterSceneRequirement { + requiredHeldItems: (HeldItemId | HeldItemCategoryId)[]; + minNumberOfItems: number; + invertQuery: boolean; + requireTransferable: boolean; + + constructor( + heldItem: HeldItemId | HeldItemCategoryId | (HeldItemId | HeldItemCategoryId)[], + minNumberOfItems = 1, + invertQuery = false, + requireTransferable = true, + ) { + super(); + this.minNumberOfItems = minNumberOfItems; + this.invertQuery = invertQuery; + this.requiredHeldItems = coerceArray(heldItem); + this.requireTransferable = requireTransferable; + } + + override meetsRequirement(): boolean { + const partyPokemon = globalScene.getPlayerParty(); + if (isNullOrUndefined(partyPokemon)) { + return false; + } + return this.queryPartyForItems(partyPokemon) >= this.minNumberOfItems; + } + + queryPartyForItems(partyPokemon: PlayerPokemon[]): number { + if (!this.invertQuery) { + return partyPokemon.reduce((count, pokemon) => { + const matchingItems = this.requiredHeldItems.filter(heldItem => { + return this.requireTransferable + ? pokemon.heldItemManager.hasTransferableItem(heldItem) + : pokemon.heldItemManager.hasItem(heldItem); + }); + return count + matchingItems.length; + }, 0); + } + // for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers + // E.g. functions as a blacklist + return partyPokemon.reduce((count, pokemon) => { + const matchingItems = pokemon.getHeldItems().filter(item => { + const notRequired = !this.requiredHeldItems.some( + heldItem => item === heldItem || getHeldItemCategory(item) === heldItem, + ); + const transferableOk = !this.requireTransferable || allHeldItems[item].isTransferable; + return notRequired && transferableOk; + }); + + return count + matchingItems.length; + }, 0); + } + + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { + const requiredItems = pokemon?.getHeldItems().filter(item => { + return ( + this.requiredHeldItems.some(heldItem => item === heldItem) && + (!this.requireTransferable || allHeldItems[item].isTransferable) + ); + }); + if (requiredItems && requiredItems.length > 0) { + return ["heldItem", allHeldItems[requiredItems[0]].name]; + } + return ["heldItem", ""]; + } +} + export class LevelRequirement extends EncounterPokemonRequirement { requiredLevelRange: [number, number]; minNumberOfPokemon: number; diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index d5bbeea4389..6185b5fc2c6 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -102,6 +102,15 @@ export class PokemonItemManager { return itemType in this.heldItems; } + hasTransferableItem(itemType: HeldItemId | HeldItemCategoryId): boolean { + if (isCategoryId(itemType)) { + return getTypedKeys(this.heldItems).some( + id => isItemInCategory(id, itemType as HeldItemCategoryId) && allHeldItems[id].isTransferable, + ); + } + return itemType in this.heldItems && allHeldItems[itemType].isTransferable; + } + getStack(itemType: HeldItemId): number { const item = this.heldItems[itemType]; return item ? item.stack : 0; diff --git a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index 4da8ff7f643..5e0af7b642f 100644 --- a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -18,12 +18,12 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; import { TrainerType } from "#enums/trainer-type"; import { MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; -import { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier"; import { CommandPhase } from "#app/phases/command-phase"; import { BugTypeSuperfanEncounter } from "#app/data/mystery-encounters/encounters/bug-type-superfan-encounter"; import * as encounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import { HeldItemId } from "#enums/held-item-id"; const namespace = "mysteryEncounters/bugTypeSuperfan"; const defaultParty = [SpeciesId.LAPRAS, SpeciesId.GENGAR, SpeciesId.WEEDLE]; @@ -528,11 +528,11 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { }); it("should NOT be selectable if the player doesn't have any Bug items", async () => { - game.scene.modifiers = []; + game.scene.trainerItems.clearItems(); await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, defaultParty); await game.phaseInterceptor.to(MysteryEncounterPhase, false); - game.scene.modifiers = []; + game.scene.trainerItems.clearItems(); const encounterPhase = scene.phaseManager.getCurrentPhase(); expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; @@ -549,11 +549,10 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { }); it("should remove the gifted item and proceed to rewards screen", async () => { - game.override.startingHeldItems([{ name: "GRIP_CLAW", count: 1 }]); + game.override.startingHeldItems([{ entry: HeldItemId.GRIP_CLAW, count: 1 }]); await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, [SpeciesId.BUTTERFREE]); - const gripClawCountBefore = - scene.findModifier(m => m instanceof ContactHeldItemTransferChanceModifier)?.stackCount ?? 0; + const gripClawCountBefore = scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.GRIP_CLAW); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); @@ -568,13 +567,12 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MYSTERY_ENCOUNTER_GOLDEN_BUG_NET"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("REVIVER_SEED"); - const gripClawCountAfter = - scene.findModifier(m => m instanceof ContactHeldItemTransferChanceModifier)?.stackCount ?? 0; + const gripClawCountAfter = scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.GRIP_CLAW); expect(gripClawCountBefore - 1).toBe(gripClawCountAfter); }); it("should leave encounter without battle", async () => { - game.override.startingHeldItems([{ name: "GRIP_CLAW", count: 1 }]); + game.override.startingHeldItems([{ entry: HeldItemId.GRIP_CLAW, count: 1 }]); const leaveEncounterWithoutBattleSpy = vi.spyOn(encounterPhaseUtils, "leaveEncounterWithoutBattle"); await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, [SpeciesId.BUTTERFREE]); From ee2412cafb57206e6b14f0a8f3e29fb2778ef273 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 12 Jul 2025 13:14:35 +0200 Subject: [PATCH 14/39] Partially fixed absolute avarice, fixed HeldItemRequirement to actually count the items --- .../encounters/absolute-avarice-encounter.ts | 13 ++--- .../mystery-encounter-requirements.ts | 33 ++++------- .../absolute-avarice-encounter.test.ts | 57 +++++++------------ 3 files changed, 37 insertions(+), 66 deletions(-) diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index e1d59b58670..6637fa58c0f 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -30,7 +30,7 @@ import { Stat } from "#enums/stat"; import i18next from "i18next"; import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; import { MoveUseMode } from "#enums/move-use-mode"; -import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; +import type { HeldItemConfiguration, PokemonItemMap } from "#app/items/held-item-data-types"; import { allHeldItems } from "#app/data/data-lists"; import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; import { HeldItemRequirement } from "../mystery-encounter-requirements"; @@ -113,7 +113,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde // Get all berries in party, with references to the pokemon const berryItems = getPartyBerries(); - encounter.misc.berryItemsMap = berryItems; + encounter.misc = { berryItemsMap: berryItems }; // Adds stolen berries to the Greedent item configuration const bossHeldItemConfig: HeldItemConfiguration = []; @@ -164,7 +164,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde // Session has been safely saved at this point, so data won't be lost const berryItems = getPartyBerries(); berryItems.forEach(map => { - globalScene.getPokemonById(map.pokemonId)?.heldItemManager.remove(map.item.id); + globalScene.getPokemonById(map.pokemonId)?.heldItemManager.remove(map.item.id as HeldItemId); }); globalScene.updateItems(true); @@ -225,13 +225,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde }) .withOptionPhase(async () => { const encounter = globalScene.currentBattle.mysteryEncounter!; - const berryMap = encounter.misc.berryItemsMap; + const berryMap = encounter.misc.berryItemsMap as PokemonItemMap[]; // Returns 2/5 of the berries stolen to each Pokemon const party = globalScene.getPlayerParty(); party.forEach(pokemon => { - // TODO: is this check legal? - const stolenBerries = berryMap.filter(map => map.pokemon === pokemon); + const stolenBerries = berryMap.filter(map => map.pokemonId === pokemon.id); const returnedBerryCount = Math.floor(((stolenBerries.length ?? 0) * 2) / 5); if (returnedBerryCount > 0) { @@ -239,7 +238,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde // Shuffle remaining berry types and pop Phaser.Math.RND.shuffle(stolenBerries); const randBerryType = stolenBerries.pop(); - pokemon.heldItemManager.add(randBerryType); + pokemon.heldItemManager.add(randBerryType?.item.id as HeldItemId); } } }); diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index fc829b29f02..f0d828076d1 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -886,33 +886,24 @@ export class HeldItemRequirement extends EncounterSceneRequirement { if (isNullOrUndefined(partyPokemon)) { return false; } + console.log("COUNTED:", this.queryPartyForItems(partyPokemon), this.minNumberOfItems); return this.queryPartyForItems(partyPokemon) >= this.minNumberOfItems; } queryPartyForItems(partyPokemon: PlayerPokemon[]): number { - if (!this.invertQuery) { - return partyPokemon.reduce((count, pokemon) => { - const matchingItems = this.requiredHeldItems.filter(heldItem => { - return this.requireTransferable - ? pokemon.heldItemManager.hasTransferableItem(heldItem) - : pokemon.heldItemManager.hasItem(heldItem); - }); - return count + matchingItems.length; - }, 0); - } - // for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers - // E.g. functions as a blacklist - return partyPokemon.reduce((count, pokemon) => { - const matchingItems = pokemon.getHeldItems().filter(item => { - const notRequired = !this.requiredHeldItems.some( + let count = 0; + for (const pokemon of partyPokemon) { + for (const item of pokemon.getHeldItems()) { + const itemInList = this.requiredHeldItems.some( heldItem => item === heldItem || getHeldItemCategory(item) === heldItem, ); - const transferableOk = !this.requireTransferable || allHeldItems[item].isTransferable; - return notRequired && transferableOk; - }); - - return count + matchingItems.length; - }, 0); + const requiredItem = this.invertQuery ? !itemInList : itemInList; + if (requiredItem && (!this.requireTransferable || allHeldItems[item].isTransferable)) { + count += pokemon.heldItemManager.getStack(item); + } + } + } + return count; } override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { diff --git a/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts b/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts index b6c4e4d85fb..dc695089e6c 100644 --- a/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts +++ b/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts @@ -12,14 +12,12 @@ import type BattleScene from "#app/battle-scene"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; -import { BerryModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; -import { BerryType } from "#enums/berry-type"; import { AbsoluteAvariceEncounter } from "#app/data/mystery-encounters/encounters/absolute-avarice-encounter"; import { MoveId } from "#enums/move-id"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; -import i18next from "i18next"; +import { HeldItemId } from "#enums/held-item-id"; const namespace = "mysteryEncounters/absoluteAvarice"; const defaultParty = [SpeciesId.LAPRAS, SpeciesId.GENGAR, SpeciesId.ABRA]; @@ -77,8 +75,6 @@ describe("Absolute Avarice - Mystery Encounter", () => { }); it("should not spawn if player does not have enough berries", async () => { - scene.modifiers = []; - await game.runToMysteryEncounter(); expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.ABSOLUTE_AVARICE); @@ -86,9 +82,9 @@ describe("Absolute Avarice - Mystery Encounter", () => { it("should spawn if player has enough berries", async () => { game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT).startingHeldItems([ - { name: "BERRY", count: 2, type: BerryType.SITRUS }, - { name: "BERRY", count: 3, type: BerryType.GANLON }, - { name: "BERRY", count: 2, type: BerryType.APICOT }, + { entry: HeldItemId.SITRUS_BERRY, count: 2 }, + { entry: HeldItemId.GANLON_BERRY, count: 3 }, + { entry: HeldItemId.APICOT_BERRY, count: 2 }, ]); await game.runToMysteryEncounter(); @@ -98,15 +94,15 @@ describe("Absolute Avarice - Mystery Encounter", () => { it("should remove all player's berries at the start of the encounter", async () => { game.override.startingHeldItems([ - { name: "BERRY", count: 2, type: BerryType.SITRUS }, - { name: "BERRY", count: 3, type: BerryType.GANLON }, - { name: "BERRY", count: 2, type: BerryType.APICOT }, + { entry: HeldItemId.SITRUS_BERRY, count: 2 }, + { entry: HeldItemId.GANLON_BERRY, count: 3 }, + { entry: HeldItemId.APICOT_BERRY, count: 2 }, ]); await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty); expect(scene.currentBattle?.mysteryEncounter?.encounterType).toBe(MysteryEncounterType.ABSOLUTE_AVARICE); - expect(scene.modifiers?.length).toBe(0); + expect(scene.getPlayerParty()[0].getHeldItems().length).toBe(0); }); describe("Option 1 - Fight the Greedent", () => { @@ -151,16 +147,7 @@ describe("Absolute Avarice - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); for (const partyPokemon of scene.getPlayerParty()) { - const pokemonId = partyPokemon.id; - const pokemonItems = scene.findModifiers( - m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === pokemonId, - true, - ) as PokemonHeldItemModifier[]; - const revSeed = pokemonItems.find( - i => i.type.name === i18next.t("modifierType:ModifierType.REVIVER_SEED.name"), - ); - expect(revSeed).toBeDefined; - expect(revSeed?.stackCount).toBe(1); + expect(partyPokemon.heldItemManager.getStack(HeldItemId.REVIVER_SEED)).toBe(1); } }); }); @@ -183,42 +170,36 @@ describe("Absolute Avarice - Mystery Encounter", () => { it("Should return 3 (2/5ths floored) berries if 8 were stolen", { retry: 5 }, async () => { game.override.startingHeldItems([ - { name: "BERRY", count: 2, type: BerryType.SITRUS }, - { name: "BERRY", count: 3, type: BerryType.GANLON }, - { name: "BERRY", count: 3, type: BerryType.APICOT }, + { entry: HeldItemId.SITRUS_BERRY, count: 2 }, + { entry: HeldItemId.GANLON_BERRY, count: 3 }, + { entry: HeldItemId.APICOT_BERRY, count: 3 }, ]); await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty); expect(scene.currentBattle?.mysteryEncounter?.encounterType).toBe(MysteryEncounterType.ABSOLUTE_AVARICE); - expect(scene.modifiers?.length).toBe(0); + expect(scene.getPlayerParty()[0].getHeldItems().length).toBe(0); await runMysteryEncounterToEnd(game, 2); - const berriesAfter = scene.findModifiers(m => m instanceof BerryModifier); - const berryCountAfter = berriesAfter.reduce((a, b) => a + b.stackCount, 0); - expect(berriesAfter).toBeDefined(); - expect(berryCountAfter).toBe(3); + expect(scene.getPlayerParty()[0].heldItemManager.getHeldItemCount()).toBe(3); }); it("Should return 2 (2/5ths floored) berries if 7 were stolen", { retry: 5 }, async () => { game.override.startingHeldItems([ - { name: "BERRY", count: 2, type: BerryType.SITRUS }, - { name: "BERRY", count: 3, type: BerryType.GANLON }, - { name: "BERRY", count: 2, type: BerryType.APICOT }, + { entry: HeldItemId.SITRUS_BERRY, count: 2 }, + { entry: HeldItemId.GANLON_BERRY, count: 3 }, + { entry: HeldItemId.APICOT_BERRY, count: 2 }, ]); await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty); expect(scene.currentBattle?.mysteryEncounter?.encounterType).toBe(MysteryEncounterType.ABSOLUTE_AVARICE); - expect(scene.modifiers?.length).toBe(0); + expect(scene.getPlayerParty()[0].getHeldItems().length).toBe(0); await runMysteryEncounterToEnd(game, 2); - const berriesAfter = scene.findModifiers(m => m instanceof BerryModifier); - const berryCountAfter = berriesAfter.reduce((a, b) => a + b.stackCount, 0); - expect(berriesAfter).toBeDefined(); - expect(berryCountAfter).toBe(2); + expect(scene.getPlayerParty()[0].heldItemManager.getHeldItemCount()).toBe(2); }); it("should leave encounter without battle", async () => { From 6fc7db8939c3098352259c5699ba0a9531f8914b Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 12 Jul 2025 13:22:49 +0200 Subject: [PATCH 15/39] More partial test fixes --- .../encounters/uncommon-breed-encounter.ts | 4 +- .../trash-to-treasure-encounter.test.ts | 66 ++++--------------- .../uncommon-breed-encounter.test.ts | 22 ++----- test/phases/select-modifier-phase.test.ts | 3 +- test/ui/transfer-item.test.ts | 8 +-- 5 files changed, 26 insertions(+), 77 deletions(-) diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 7e5cc9568b1..29030604a0a 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -35,7 +35,7 @@ import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { MoveUseMode } from "#enums/move-use-mode"; import type { PokemonItemMap } from "#app/items/held-item-data-types"; -import { HeldItemCategoryId } from "#enums/held-item-id"; +import { HeldItemCategoryId, type HeldItemId } from "#enums/held-item-id"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/uncommonBreed"; @@ -213,7 +213,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder. for (let i = 0; i < 4; i++) { const index = randSeedInt(berryMap.length); const randBerry = berryMap[index]; - globalScene.getPokemonById(randBerry.pokemonId)?.heldItemManager.remove(randBerry.item.id); + globalScene.getPokemonById(randBerry.pokemonId)?.heldItemManager.remove(randBerry.item.id as HeldItemId); stolenBerryMap.push(randBerry); berryMap.splice(index, 1); } diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 7626e4c9660..dad60c642ce 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -3,20 +3,13 @@ import * as BattleAnims from "#app/data/battle-anims"; import { TrashToTreasureEncounter } from "#app/data/mystery-encounters/encounters/trash-to-treasure-encounter"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { - type EnemyPartyConfig, - type EnemyPokemonConfig, - generateModifierType, -} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { EnemyPartyConfig, EnemyPokemonConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { BiomeId } from "#enums/biome-id"; import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { PokemonMove } from "#app/data/moves/pokemon-move"; -import { HealShopCostModifier, HitHealModifier, TurnHealModifier } from "#app/modifier/modifier"; import { RewardTier } from "#enums/reward-tier"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/data/data-lists"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; @@ -33,6 +26,9 @@ import { import GameManager from "#test/testUtils/gameManager"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { randSeedInt } from "#app/utils/common"; +import { TrainerItemId } from "#enums/trainer-item-id"; const namespace = "mysteryEncounters/trashToTreasure"; const defaultParty = [SpeciesId.LAPRAS, SpeciesId.GENGAR, SpeciesId.ABRA]; @@ -101,41 +97,13 @@ describe("Trash to Treasure - Mystery Encounter", () => { formIndex: 1, // Gmax bossSegmentModifier: 1, // +1 Segment from normal moveSet: [MoveId.GUNK_SHOT, MoveId.STOMPING_TANTRUM, MoveId.HAMMER_ARM, MoveId.PAYBACK], - modifierConfigs: [ - { - modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER) as PokemonHeldItemModifierType, - }, - { - modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType, - stackCount: Utils.randSeedInt(2, 0), - }, - { - modifier: generateModifierType(modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType, - stackCount: Utils.randSeedInt(2, 1), - }, - { - modifier: generateModifierType(modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType, - stackCount: Utils.randSeedInt(3, 1), - }, - { - modifier: generateModifierType(modifierTypes.GOLDEN_EGG) as PokemonHeldItemModifierType, - stackCount: Utils.randSeedInt(2, 0), - }, + heldItemConfig: [ + { entry: HeldItemCategoryId.BERRY, count: 4 }, + { entry: HeldItemCategoryId.BASE_STAT_BOOST, count: 2 }, + { entry: HeldItemId.TOXIC_ORB, count: randSeedInt(2, 0) }, + { entry: HeldItemId.SOOTHE_BELL, count: randSeedInt(2, 1) }, + { entry: HeldItemId.LUCKY_EGG, count: randSeedInt(3, 1) }, + { entry: HeldItemId.GOLDEN_EGG, count: randSeedInt(2, 0) }, ], }; const config: EnemyPartyConfig = { @@ -175,17 +143,11 @@ describe("Trash to Treasure - Mystery Encounter", () => { await game.phaseInterceptor.to(SelectModifierPhase, false); expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - const leftovers = scene.findModifier(m => m instanceof TurnHealModifier) as TurnHealModifier; - expect(leftovers).toBeDefined(); - expect(leftovers?.stackCount).toBe(2); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.LEFTOVERS)).toBe(2); - const shellBell = scene.findModifier(m => m instanceof HitHealModifier) as HitHealModifier; - expect(shellBell).toBeDefined(); - expect(shellBell?.stackCount).toBe(1); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SHELL_BELL)).toBe(1); - const blackSludge = scene.findModifier(m => m instanceof HealShopCostModifier) as HealShopCostModifier; - expect(blackSludge).toBeDefined(); - expect(blackSludge?.stackCount).toBe(1); + expect(scene.trainerItems.getStack(TrainerItemId.BLACK_SLUDGE)).toBe(1); }); it("should leave encounter without battle", async () => { diff --git a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index 2fef4fa4cd3..1345be19677 100644 --- a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -15,19 +15,16 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; 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/balance/egg-moves"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; -import { BerryType } from "#enums/berry-type"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; -import type { BerryModifier } from "#app/modifier/modifier"; -import { modifierTypes } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; +import { HeldItemId } from "#enums/held-item-id"; const namespace = "mysteryEncounters/uncommonBreed"; const defaultParty = [SpeciesId.LAPRAS, SpeciesId.GENGAR, SpeciesId.ABRA]; @@ -184,11 +181,6 @@ describe("Uncommon Breed - Mystery Encounter", () => { // TODO: there is some severe test flakiness occurring for this file, needs to be looked at/addressed in separate issue it.skip("should NOT be selectable if the player doesn't have enough berries", async () => { await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty); - // Clear out any pesky mods that slipped through test spin-up - scene.modifiers.forEach(mod => { - scene.removeModifier(mod); - }); - await scene.updateItems(true); await game.phaseInterceptor.to(MysteryEncounterPhase, false); const encounterPhase = scene.phaseManager.getCurrentPhase(); @@ -213,15 +205,9 @@ describe("Uncommon Breed - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty); // Berries on party lead - const sitrus = generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS])!; - const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier; - sitrusMod.stackCount = 2; - scene.addModifier(sitrusMod, true, false, false, true); - const ganlon = generateModifierType(modifierTypes.BERRY, [BerryType.GANLON])!; - const ganlonMod = ganlon.newModifier(scene.getPlayerParty()[0]) as BerryModifier; - ganlonMod.stackCount = 3; - scene.addModifier(ganlonMod, true, false, false, true); - await scene.updateItems(true); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.SITRUS_BERRY, 2); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.GANLON_BERRY, 3); + scene.updateItems(true); await runMysteryEncounterToEnd(game, 2); diff --git a/test/phases/select-modifier-phase.test.ts b/test/phases/select-modifier-phase.test.ts index 0a4d80c7ed7..5f7d9759d66 100644 --- a/test/phases/select-modifier-phase.test.ts +++ b/test/phases/select-modifier-phase.test.ts @@ -17,6 +17,7 @@ import GameManager from "#test/testUtils/gameManager"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { TrainerItemId } from "#enums/trainer-item-id"; describe("SelectModifierPhase", () => { let phaserGame: Phaser.Game; @@ -110,7 +111,7 @@ describe("SelectModifierPhase", () => { }); it.todo("should generate random modifiers of same tier for reroll with reroll lock", async () => { - game.override.startingModifier([{ name: "LOCK_CAPSULE" }]); + game.override.startingTrainerItems([{ entry: TrainerItemId.LOCK_CAPSULE }]); await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); scene.money = 1000000; // Just use fully random seed for this test diff --git a/test/ui/transfer-item.test.ts b/test/ui/transfer-item.test.ts index 572d56c5903..febcf3ee5ca 100644 --- a/test/ui/transfer-item.test.ts +++ b/test/ui/transfer-item.test.ts @@ -1,4 +1,3 @@ -import { BerryType } from "#app/enums/berry-type"; import { Button } from "#app/enums/buttons"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; @@ -9,6 +8,7 @@ import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; describe("UI - Transfer Items", () => { let phaserGame: Phaser.Game; @@ -31,9 +31,9 @@ describe("UI - Transfer Items", () => { .startingLevel(100) .startingWave(1) .startingHeldItems([ - { name: "BERRY", count: 1, type: BerryType.SITRUS }, - { name: "BERRY", count: 2, type: BerryType.APICOT }, - { name: "BERRY", count: 2, type: BerryType.LUM }, + { entry: HeldItemId.SITRUS_BERRY, count: 1 }, + { entry: HeldItemId.APICOT_BERRY, count: 2 }, + { entry: HeldItemId.LUM_BERRY, count: 2 }, ]) .moveset([MoveId.DRAGON_CLAW]) .enemySpecies(SpeciesId.MAGIKARP) From 88634ca081dd6e52e54242022b424a9043d10366 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 12 Jul 2025 17:01:15 +0200 Subject: [PATCH 16/39] Partially fixed delibirdy test --- src/battle-scene.ts | 13 ++ .../encounters/delibirdy-encounter.test.ts | 212 +++++------------- 2 files changed, 73 insertions(+), 152 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index ae2aad447e1..340570c00d0 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2911,6 +2911,19 @@ export default class BattleScene extends SceneBase { this.updateUIPositions(); } + clearAllItems(): void { + this.trainerItems.clearItems(); + this.enemyTrainerItems.clearItems(); + for (const p of this.getPlayerParty()) { + p.heldItemManager.clearItems(); + } + for (const p of this.getEnemyParty()) { + p.heldItemManager.clearItems(); + } + this.updateItems(false); + this.updateUIPositions(); + } + setModifiersVisible(visible: boolean) { [this.itemBar, this.enemyItemBar].map(m => m.setVisible(visible)); } diff --git a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index c88e1677bcf..e5ebff3a6f7 100644 --- a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -14,20 +14,9 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { DelibirdyEncounter } from "#app/data/mystery-encounters/encounters/delibirdy-encounter"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; import type { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; -import { - BerryModifier, - HealingBoosterModifier, - HitHealModifier, - LevelIncrementBoosterModifier, - MoneyMultiplierModifier, - PokemonInstantReviveModifier, - PokemonNatureWeightModifier, - PreserveBerryModifier, -} from "#app/modifier/modifier"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; -import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { modifierTypes } from "#app/data/data-lists"; -import { BerryType } from "#enums/berry-type"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import { HeldItemId } from "#enums/held-item-id"; const namespace = "mysteryEncounters/delibirdy"; const defaultParty = [SpeciesId.LAPRAS, SpeciesId.GENGAR, SpeciesId.ABRA]; @@ -119,10 +108,7 @@ describe("Delibird-y - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); await runMysteryEncounterToEnd(game, 1); - const itemModifier = scene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier; - - expect(itemModifier).toBeDefined(); - expect(itemModifier?.stackCount).toBe(1); + expect(scene.trainerItems.getStack(TrainerItemId.AMULET_COIN)).toBe(1); }); it("Should give the player a Shell Bell if they have max stacks of Amulet Coins", async () => { @@ -130,21 +116,14 @@ describe("Delibird-y - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Max Amulet Coins - scene.modifiers = []; - const amuletCoin = generateModifierType(modifierTypes.AMULET_COIN)!.newModifier() as MoneyMultiplierModifier; - amuletCoin.stackCount = 5; - scene.addModifier(amuletCoin, true, false, false, true); - await scene.updateItems(true); + scene.clearAllItems(); + scene.trainerItems.add(TrainerItemId.AMULET_COIN, 5); + scene.updateItems(true); await runMysteryEncounterToEnd(game, 1); - const amuletCoinAfter = scene.findModifier(m => m instanceof MoneyMultiplierModifier); - const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier); - - expect(amuletCoinAfter).toBeDefined(); - expect(amuletCoinAfter?.stackCount).toBe(5); - expect(shellBellAfter).toBeDefined(); - expect(shellBellAfter?.stackCount).toBe(1); + expect(scene.trainerItems.getStack(TrainerItemId.AMULET_COIN)).toBe(5); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SHELL_BELL)).toBe(1); }); it("should be disabled if player does not have enough money", async () => { @@ -199,111 +178,73 @@ describe("Delibird-y - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 2 Sitrus berries on party lead - scene.modifiers = []; - const sitrus = generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS])!; - const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier; - sitrusMod.stackCount = 2; - scene.addModifier(sitrusMod, true, false, false, true); - await scene.updateItems(true); + scene.clearAllItems(); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.SITRUS_BERRY, 2); + scene.updateItems(true); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); - const sitrusAfter = scene.findModifier(m => m instanceof BerryModifier); - const candyJarAfter = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier); - - expect(sitrusAfter?.stackCount).toBe(1); - expect(candyJarAfter).toBeDefined(); - expect(candyJarAfter?.stackCount).toBe(1); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SITRUS_BERRY)).toBe(1); + expect(scene.trainerItems.getStack(TrainerItemId.CANDY_JAR)).toBe(1); }); it("Should remove Reviver Seed and give the player a Berry Pouch", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 1 Reviver Seed on party lead - scene.modifiers = []; - const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!; - const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier; - modifier.stackCount = 1; - scene.addModifier(modifier, true, false, false, true); - await scene.updateItems(true); + scene.clearAllItems(); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.REVIVER_SEED); + scene.updateItems(true); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); - const reviverSeedAfter = scene.findModifier(m => m instanceof PokemonInstantReviveModifier); - const berryPouchAfter = scene.findModifier(m => m instanceof PreserveBerryModifier); - - expect(reviverSeedAfter).toBeUndefined(); - expect(berryPouchAfter).toBeDefined(); - expect(berryPouchAfter?.stackCount).toBe(1); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.REVIVER_SEED)).toBe(0); + expect(scene.trainerItems.getStack(TrainerItemId.BERRY_POUCH)).toBe(1); }); it("Should give the player a Shell Bell if they have max stacks of Candy Jars", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // 99 Candy Jars - scene.modifiers = []; - const candyJar = generateModifierType(modifierTypes.CANDY_JAR)!.newModifier() as LevelIncrementBoosterModifier; - candyJar.stackCount = 99; - scene.addModifier(candyJar, true, false, false, true); - const sitrus = generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS])!; + scene.clearAllItems(); + scene.trainerItems.add(TrainerItemId.CANDY_JAR, 99); // Sitrus berries on party - const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier; - sitrusMod.stackCount = 2; - scene.addModifier(sitrusMod, true, false, false, true); - await scene.updateItems(true); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.SITRUS_BERRY, 2); + scene.updateItems(true); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); - const sitrusAfter = scene.findModifier(m => m instanceof BerryModifier); - const candyJarAfter = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier); - const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier); - - expect(sitrusAfter?.stackCount).toBe(1); - expect(candyJarAfter).toBeDefined(); - expect(candyJarAfter?.stackCount).toBe(99); - expect(shellBellAfter).toBeDefined(); - expect(shellBellAfter?.stackCount).toBe(1); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SITRUS_BERRY)).toBe(1); + expect(scene.trainerItems.getStack(TrainerItemId.CANDY_JAR)).toBe(99); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SHELL_BELL)).toBe(1); }); it("Should give the player a Shell Bell if they have max stacks of Berry Pouches", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // 3 Berry Pouches - scene.modifiers = []; - const healingCharm = generateModifierType(modifierTypes.BERRY_POUCH)!.newModifier() as PreserveBerryModifier; - healingCharm.stackCount = 3; - scene.addModifier(healingCharm, true, false, false, true); + scene.clearAllItems(); + scene.trainerItems.add(TrainerItemId.BERRY_POUCH, 3); // Set 1 Reviver Seed on party lead - const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!; - const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier; - modifier.stackCount = 1; - scene.addModifier(modifier, true, false, false, true); - await scene.updateItems(true); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.REVIVER_SEED); + scene.updateItems(true); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); - const reviverSeedAfter = scene.findModifier(m => m instanceof PokemonInstantReviveModifier); - const healingCharmAfter = scene.findModifier(m => m instanceof PreserveBerryModifier); - const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier); - - expect(reviverSeedAfter).toBeUndefined(); - expect(healingCharmAfter).toBeDefined(); - expect(healingCharmAfter?.stackCount).toBe(3); - expect(shellBellAfter).toBeDefined(); - expect(shellBellAfter?.stackCount).toBe(1); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.REVIVER_SEED)).toBe(0); + expect(scene.trainerItems.getStack(TrainerItemId.BERRY_POUCH)).toBe(3); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SHELL_BELL)).toBe(1); }); it("should be disabled if player does not have any proper items", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 1 Soul Dew on party lead - scene.modifiers = []; - const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; - const modifier = soulDew.newModifier(scene.getPlayerParty()[0]); - scene.addModifier(modifier, true, false, false, true); - await scene.updateItems(true); + scene.clearAllItems(); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.SOUL_DEW); + scene.updateItems(true); await game.phaseInterceptor.to(MysteryEncounterPhase, false); @@ -328,11 +269,8 @@ describe("Delibird-y - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 1 Reviver Seed on party lead - const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!; - const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier; - modifier.stackCount = 1; - scene.addModifier(modifier, true, false, false, true); - await scene.updateItems(true); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.REVIVER_SEED); + scene.updateItems(true); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); @@ -361,82 +299,55 @@ describe("Delibird-y - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 2 Soul Dew on party lead - scene.modifiers = []; - const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; - const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; - modifier.stackCount = 2; - scene.addModifier(modifier, true, false, false, true); - await scene.updateItems(true); + scene.clearAllItems(); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.SOUL_DEW, 2); + scene.updateItems(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); - const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier); - const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier); - - expect(soulDewAfter?.stackCount).toBe(1); - expect(healingCharmAfter).toBeDefined(); - expect(healingCharmAfter?.stackCount).toBe(1); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SOUL_DEW)).toBe(1); + expect(scene.trainerItems.getStack(TrainerItemId.HEALING_CHARM)).toBe(1); }); it("Should remove held item and give the player a Healing Charm", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 1 Soul Dew on party lead - scene.modifiers = []; - const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; - const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; - modifier.stackCount = 1; - scene.addModifier(modifier, true, false, false, true); - await scene.updateItems(true); + scene.clearAllItems(); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.SOUL_DEW); + scene.updateItems(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); - const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier); - const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier); - - expect(soulDewAfter).toBeUndefined(); - expect(healingCharmAfter).toBeDefined(); - expect(healingCharmAfter?.stackCount).toBe(1); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SOUL_DEW)).toBe(0); + expect(scene.trainerItems.getStack(TrainerItemId.HEALING_CHARM)).toBe(1); }); it("Should give the player a Shell Bell if they have max stacks of Healing Charms", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // 5 Healing Charms - scene.modifiers = []; - const healingCharm = generateModifierType(modifierTypes.HEALING_CHARM)!.newModifier() as HealingBoosterModifier; - healingCharm.stackCount = 5; - scene.addModifier(healingCharm, true, false, false, true); + scene.clearAllItems(); + scene.trainerItems.add(TrainerItemId.HEALING_CHARM, 5); // Set 1 Soul Dew on party lead - const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; - const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; - modifier.stackCount = 1; - scene.addModifier(modifier, true, false, false, true); - await scene.updateItems(true); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.SOUL_DEW); + scene.updateItems(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); - const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier); - const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier); - const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier); - - expect(soulDewAfter).toBeUndefined(); - expect(healingCharmAfter).toBeDefined(); - expect(healingCharmAfter?.stackCount).toBe(5); - expect(shellBellAfter).toBeDefined(); - expect(shellBellAfter?.stackCount).toBe(1); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SOUL_DEW)).toBe(0); + expect(scene.trainerItems.getStack(TrainerItemId.HEALING_CHARM)).toBe(5); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SHELL_BELL)).toBe(1); }); it("should be disabled if player does not have any proper items", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 1 Reviver Seed on party lead - scene.modifiers = []; - const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!; - const modifier = revSeed.newModifier(scene.getPlayerParty()[0]); - scene.addModifier(modifier, true, false, false, true); - await scene.updateItems(true); + scene.clearAllItems(); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.REVIVER_SEED); + scene.updateItems(true); await game.phaseInterceptor.to(MysteryEncounterPhase, false); @@ -461,12 +372,9 @@ describe("Delibird-y - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 1 Soul Dew on party lead - scene.modifiers = []; - const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; - const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; - modifier.stackCount = 1; - scene.addModifier(modifier, true, false, false, true); - await scene.updateItems(true); + scene.clearAllItems(); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.SOUL_DEW); + scene.updateItems(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); From 2344bdf32aea50744ef9e824355b48d248fdeba1 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 12 Jul 2025 17:14:03 +0200 Subject: [PATCH 17/39] Renamed modifier-se-ect-ui-handler to reward-select-ui-handler --- .../encounters/delibirdy-encounter.ts | 20 ++++++------- src/phases/select-modifier-phase.ts | 6 ++-- ...handler.ts => reward-select-ui-handler.ts} | 2 +- src/ui/ui.ts | 4 +-- test/daily_mode.test.ts | 6 ++-- test/items/dire_hit.test.ts | 4 +-- .../double_battle_chance_booster.test.ts | 4 +-- test/items/temp_stat_stage_booster.test.ts | 4 +-- .../berries-abound-encounter.test.ts | 10 +++---- .../bug-type-superfan-encounter.test.ts | 22 +++++++------- .../dancing-lessons-encounter.test.ts | 6 ++-- .../department-store-sale-encounter.test.ts | 18 +++++------ .../encounters/field-trip-encounter.test.ts | 26 ++++++++-------- .../fight-or-flight-encounter.test.ts | 10 +++---- .../fun-and-games-encounter.test.ts | 18 +++++------ .../global-trade-system-encounter.test.ts | 6 ++-- .../mysterious-challengers-encounter.test.ts | 14 ++++----- .../teleporting-hijinks-encounter.test.ts | 6 ++-- .../the-strong-stuff-encounter.test.ts | 6 ++-- .../the-winstrate-challenge-encounter.test.ts | 10 +++---- .../trash-to-treasure-encounter.test.ts | 6 ++-- .../encounters/weird-dream-encounter.test.ts | 10 +++---- test/phases/select-modifier-phase.test.ts | 30 +++++++++---------- test/testUtils/gameManager.ts | 6 ++-- test/ui/transfer-item.test.ts | 6 ++-- 25 files changed, 130 insertions(+), 130 deletions(-) rename src/ui/{modifier-select-ui-handler.ts => reward-select-ui-handler.ts} (99%) diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 4e9063ae429..92c6dfaf761 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -34,10 +34,10 @@ import { TrainerItemId } from "#enums/trainer-item-id"; const namespace = "mysteryEncounters/delibirdy"; /** Berries only */ -const OPTION_2_ALLOWED_MODIFIERS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED]; +const OPTION_2_ALLOWED_HELD_ITEMS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED]; /** Disallowed items are berries, Reviver Seeds, and Vitamins (form change items and fusion items are not PokemonHeldItemModifiers) */ -const OPTION_3_DISALLOWED_MODIFIERS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED]; +const OPTION_3_DISALLOWED_HELD_ITEMS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED]; const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2; @@ -85,8 +85,8 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with .withPrimaryPokemonRequirement( CombinationPokemonRequirement.Some( // Must also have either option 2 or 3 available to spawn - new HoldingItemRequirement(OPTION_2_ALLOWED_MODIFIERS), - new HoldingItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true), + new HoldingItemRequirement(OPTION_2_ALLOWED_HELD_ITEMS), + new HoldingItemRequirement(OPTION_3_DISALLOWED_HELD_ITEMS, 1, true), ), ) .withIntroSpriteConfigs([ @@ -180,7 +180,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with ) .withOption( MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) - .withPrimaryPokemonRequirement(new HoldingItemRequirement(OPTION_2_ALLOWED_MODIFIERS)) + .withPrimaryPokemonRequirement(new HoldingItemRequirement(OPTION_2_ALLOWED_HELD_ITEMS)) .withDialogue({ buttonLabel: `${namespace}:option.2.label`, buttonTooltip: `${namespace}:option.2.tooltip`, @@ -195,7 +195,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones - const validItems = pokemon.heldItemManager.filterRequestedItems(OPTION_2_ALLOWED_MODIFIERS, true); + const validItems = pokemon.heldItemManager.filterRequestedItems(OPTION_2_ALLOWED_HELD_ITEMS, true); return validItems.map((item: HeldItemId) => { const option: OptionSelectItem = { @@ -264,7 +264,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with ) .withOption( MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) - .withPrimaryPokemonRequirement(new HoldingItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true)) + .withPrimaryPokemonRequirement(new HoldingItemRequirement(OPTION_3_DISALLOWED_HELD_ITEMS, 1, true)) .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, @@ -279,7 +279,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones - const validItems = pokemon.heldItemManager.filterRequestedItems(OPTION_3_DISALLOWED_MODIFIERS, true, true); + const validItems = pokemon.heldItemManager.filterRequestedItems(OPTION_3_DISALLOWED_HELD_ITEMS, true, true); return validItems.map((item: HeldItemId) => { const option: OptionSelectItem = { @@ -312,7 +312,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with }) .withOptionPhase(async () => { const encounter = globalScene.currentBattle.mysteryEncounter!; - const modifier = encounter.misc.chosenModifier; + const chosenItem = encounter.misc.chosenItem; const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Check if the player has max stacks of Healing Charm already @@ -327,7 +327,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with doEventReward(); } - chosenPokemon.loseHeldItem(modifier, false); + chosenPokemon.loseHeldItem(chosenItem, false); leaveEncounterWithoutBattle(true); }) diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 301280b1636..f2cf249af08 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -18,8 +18,8 @@ import { } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import type { Modifier } from "#app/modifier/modifier"; -import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; +import type RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; +import { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/reward-select-ui-handler"; import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler"; import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; @@ -258,7 +258,7 @@ export class SelectModifierPhase extends BattlePhase { return false; } globalScene.lockModifierTiers = !globalScene.lockModifierTiers; - const uiHandler = globalScene.ui.getHandler() as ModifierSelectUiHandler; + const uiHandler = globalScene.ui.getHandler() as RewardSelectUiHandler; uiHandler.setRerollCost(this.getRerollCost(globalScene.lockModifierTiers)); uiHandler.updateLockRaritiesText(); uiHandler.updateRerollCostText(); diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/reward-select-ui-handler.ts similarity index 99% rename from src/ui/modifier-select-ui-handler.ts rename to src/ui/reward-select-ui-handler.ts index 4776310062e..42b5e0f6bf0 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/reward-select-ui-handler.ts @@ -23,7 +23,7 @@ const SINGLE_SHOP_ROW_YOFFSET = 12; const DOUBLE_SHOP_ROW_YOFFSET = 24; const OPTION_BUTTON_YPOSITION = -62; -export default class ModifierSelectUiHandler extends AwaitableUiHandler { +export default class RewardSelectUiHandler extends AwaitableUiHandler { private modifierContainer: Phaser.GameObjects.Container; private rerollButtonContainer: Phaser.GameObjects.Container; private lockRarityButtonContainer: Phaser.GameObjects.Container; diff --git a/src/ui/ui.ts b/src/ui/ui.ts index ad496df6382..c2e085b243a 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -6,7 +6,7 @@ import PartyUiHandler from "./party-ui-handler"; import FightUiHandler from "./fight-ui-handler"; import MessageUiHandler from "./message-ui-handler"; import ConfirmUiHandler from "./confirm-ui-handler"; -import ModifierSelectUiHandler from "./modifier-select-ui-handler"; +import RewardSelectUiHandler from "./modifier-select-ui-handler"; import BallUiHandler from "./ball-ui-handler"; import SummaryUiHandler from "./summary-ui-handler"; import StarterSelectUiHandler from "./starter-select-ui-handler"; @@ -131,7 +131,7 @@ export default class UI extends Phaser.GameObjects.Container { new FightUiHandler(), new BallUiHandler(), new TargetSelectUiHandler(), - new ModifierSelectUiHandler(), + new RewardSelectUiHandler(), new SaveSlotSelectUiHandler(), new PartyUiHandler(), new SummaryUiHandler(), diff --git a/test/daily_mode.test.ts b/test/daily_mode.test.ts index 41dcf51ccf6..4a408bd6c2c 100644 --- a/test/daily_mode.test.ts +++ b/test/daily_mode.test.ts @@ -2,7 +2,7 @@ import { BiomeId } from "#enums/biome-id"; import { MoveId } from "#enums/move-id"; import { MapModifier } from "#app/modifier/modifier"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; @@ -77,7 +77,7 @@ describe("Shop modifications", async () => { await game.doKillOpponents(); await game.phaseInterceptor.to("BattleEndPhase"); game.onNextPrompt("SelectModifierPhase", UiMode.MODIFIER_SELECT, () => { - expect(game.scene.ui.getHandler()).toBeInstanceOf(ModifierSelectUiHandler); + expect(game.scene.ui.getHandler()).toBeInstanceOf(RewardSelectUiHandler); game.modifiers.testCheck("EVIOLITE", false).testCheck("MINI_BLACK_HOLE", false); }); }); @@ -88,7 +88,7 @@ describe("Shop modifications", async () => { await game.doKillOpponents(); await game.phaseInterceptor.to("BattleEndPhase"); game.onNextPrompt("SelectModifierPhase", UiMode.MODIFIER_SELECT, () => { - expect(game.scene.ui.getHandler()).toBeInstanceOf(ModifierSelectUiHandler); + expect(game.scene.ui.getHandler()).toBeInstanceOf(RewardSelectUiHandler); game.modifiers.testCheck("EVIOLITE", true).testCheck("MINI_BLACK_HOLE", true); }); }); diff --git a/test/items/dire_hit.test.ts b/test/items/dire_hit.test.ts index ce1ff4cd801..5407f9abdc9 100644 --- a/test/items/dire_hit.test.ts +++ b/test/items/dire_hit.test.ts @@ -6,7 +6,7 @@ import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { UiMode } from "#enums/ui-mode"; -import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import type RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { Button } from "#app/enums/buttons"; import { CommandPhase } from "#app/phases/command-phase"; import { NewBattlePhase } from "#app/phases/new-battle-phase"; @@ -71,7 +71,7 @@ describe("Items - Dire Hit", () => { "SelectModifierPhase", UiMode.MODIFIER_SELECT, () => { - const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler; + const handler = game.scene.ui.getHandler() as RewardSelectUiHandler; // Traverse to first modifier slot handler.setCursor(0); handler.setRowCursor(ShopCursorTarget.REWARDS); diff --git a/test/items/double_battle_chance_booster.test.ts b/test/items/double_battle_chance_booster.test.ts index 6b19cf37266..3e59f1ebcad 100644 --- a/test/items/double_battle_chance_booster.test.ts +++ b/test/items/double_battle_chance_booster.test.ts @@ -5,7 +5,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import { UiMode } from "#enums/ui-mode"; -import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import type RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { Button } from "#app/enums/buttons"; import { TrainerItemId } from "#enums/trainer-item-id"; @@ -77,7 +77,7 @@ describe("Items - Double Battle Chance Boosters", () => { "SelectModifierPhase", UiMode.MODIFIER_SELECT, () => { - const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler; + const handler = game.scene.ui.getHandler() as RewardSelectUiHandler; // Traverse to first modifier slot handler.setCursor(0); handler.setRowCursor(ShopCursorTarget.REWARDS); diff --git a/test/items/temp_stat_stage_booster.test.ts b/test/items/temp_stat_stage_booster.test.ts index 621697f441d..2d445b3f370 100644 --- a/test/items/temp_stat_stage_booster.test.ts +++ b/test/items/temp_stat_stage_booster.test.ts @@ -8,7 +8,7 @@ import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { AbilityId } from "#enums/ability-id"; import { UiMode } from "#enums/ui-mode"; import { Button } from "#app/enums/buttons"; -import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import type RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; describe("Items - Temporary Stat Stage Boosters", () => { @@ -138,7 +138,7 @@ describe("Items - Temporary Stat Stage Boosters", () => { "SelectModifierPhase", UiMode.MODIFIER_SELECT, () => { - const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler; + const handler = game.scene.ui.getHandler() as RewardSelectUiHandler; // Traverse to first modifier slot handler.setCursor(0); handler.setRowCursor(ShopCursorTarget.REWARDS); diff --git a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts index 8c2c6c608cd..ebd394fe379 100644 --- a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts +++ b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts @@ -10,7 +10,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import type BattleScene from "#app/battle-scene"; import { UiMode } from "#enums/ui-mode"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { BerryModifier } from "#app/modifier/modifier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -153,8 +153,8 @@ describe("Berries Abound - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); for (const option of modifierSelectHandler.options) { expect(option.modifierTypeOption.type.id).toContain("BERRY"); @@ -238,8 +238,8 @@ describe("Berries Abound - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); for (const option of modifierSelectHandler.options) { expect(option.modifierTypeOption.type.id).toContain("BERRY"); diff --git a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index 5e0af7b642f..82be1749cac 100644 --- a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -22,7 +22,7 @@ import { CommandPhase } from "#app/phases/command-phase"; import { BugTypeSuperfanEncounter } from "#app/data/mystery-encounters/encounters/bug-type-superfan-encounter"; import * as encounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { HeldItemId } from "#enums/held-item-id"; const namespace = "mysteryEncounters/bugTypeSuperfan"; @@ -421,8 +421,8 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(2); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("SUPER_LURE"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("GREAT_BALL"); @@ -440,8 +440,8 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("QUICK_CLAW"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MAX_LURE"); @@ -462,8 +462,8 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("GRIP_CLAW"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MAX_LURE"); @@ -486,8 +486,8 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(4); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MASTER_BALL"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MEGA_BRACELET"); @@ -561,8 +561,8 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(2); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MYSTERY_ENCOUNTER_GOLDEN_BUG_NET"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("REVIVER_SEED"); diff --git a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index e47c7cc1a42..78ccec4f1a1 100644 --- a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -16,7 +16,7 @@ import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encount import { MoveId } from "#enums/move-id"; import { DancingLessonsEncounter } from "#app/data/mystery-encounters/encounters/dancing-lessons-encounter"; import { UiMode } from "#enums/ui-mode"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { CommandPhase } from "#app/phases/command-phase"; @@ -132,8 +132,8 @@ describe("Dancing Lessons - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); // Should fill remaining expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toContain("BATON"); }); diff --git a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts index a82734d0c03..9eeb31ba0da 100644 --- a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts +++ b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts @@ -8,7 +8,7 @@ import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encount import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; import type BattleScene from "#app/battle-scene"; import { UiMode } from "#enums/ui-mode"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { DepartmentStoreSaleEncounter } from "#app/data/mystery-encounters/encounters/department-store-sale-encounter"; import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -98,8 +98,8 @@ describe("Department Store Sale - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); for (const option of modifierSelectHandler.options) { expect(option.modifierTypeOption.type.id).toContain("TM_"); @@ -135,8 +135,8 @@ describe("Department Store Sale - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); for (const option of modifierSelectHandler.options) { expect( @@ -175,8 +175,8 @@ describe("Department Store Sale - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); for (const option of modifierSelectHandler.options) { expect( @@ -215,8 +215,8 @@ describe("Department Store Sale - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(4); for (const option of modifierSelectHandler.options) { expect(option.modifierTypeOption.type.id).toContain("BALL"); diff --git a/test/mystery-encounter/encounters/field-trip-encounter.test.ts b/test/mystery-encounter/encounters/field-trip-encounter.test.ts index f4b3c52eb65..a8bcc39c4eb 100644 --- a/test/mystery-encounter/encounters/field-trip-encounter.test.ts +++ b/test/mystery-encounter/encounters/field-trip-encounter.test.ts @@ -13,7 +13,7 @@ import { FieldTripEncounter } from "#app/data/mystery-encounters/encounters/fiel import { MoveId } from "#enums/move-id"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { UiMode } from "#enums/ui-mode"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import i18next from "i18next"; const namespace = "mysteryEncounters/fieldTrip"; @@ -89,8 +89,8 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(0); }); @@ -101,8 +101,8 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe( i18next.t("modifierType:TempStatStageBoosterItem.x_attack"), @@ -150,8 +150,8 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(0); }); @@ -162,8 +162,8 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe( i18next.t("modifierType:TempStatStageBoosterItem.x_sp_atk"), @@ -211,8 +211,8 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(0); }); @@ -224,8 +224,8 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe( i18next.t("modifierType:TempStatStageBoosterItem.x_accuracy"), diff --git a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts index 28db869004c..020fed90361 100644 --- a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts +++ b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts @@ -13,7 +13,7 @@ import { MoveId } from "#enums/move-id"; import type BattleScene from "#app/battle-scene"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { UiMode } from "#enums/ui-mode"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; @@ -128,8 +128,8 @@ describe("Fight or Flight - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(1); expect(item.type.name).toBe(modifierSelectHandler.options[0].modifierTypeOption.type.name); }); @@ -188,8 +188,8 @@ describe("Fight or Flight - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(1); expect(item.type.name).toBe(modifierSelectHandler.options[0].modifierTypeOption.type.name); diff --git a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts index ae7e269ea00..f25528e0566 100644 --- a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts +++ b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts @@ -14,7 +14,7 @@ import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { Nature } from "#enums/nature"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; @@ -189,8 +189,8 @@ describe("Fun And Games! - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(0); }); @@ -218,8 +218,8 @@ describe("Fun And Games! - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(1); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("WIDE_LENS"); }); @@ -248,8 +248,8 @@ describe("Fun And Games! - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(1); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("SCOPE_LENS"); }); @@ -278,8 +278,8 @@ describe("Fun And Games! - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(1); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MULTI_LENS"); }); diff --git a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index 8a878441e7e..fb7e770e7a3 100644 --- a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -13,7 +13,7 @@ import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encount import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { UiMode } from "#enums/ui-mode"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { RewardTier } from "#enums/reward-tier"; import * as Utils from "#app/utils/common"; import { HeldItemId } from "#enums/held-item-id"; @@ -225,8 +225,8 @@ describe("Global Trade System - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(1); expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier).toBe(RewardTier.MASTER); const soulDewAfter = scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SOUL_DEW); diff --git a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index 0b9d3f2a5f3..5f35d3732ac 100644 --- a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -21,7 +21,7 @@ import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTem import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { CommandPhase } from "#app/phases/command-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; @@ -168,8 +168,8 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toContain("TM_COMMON"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toContain("TM_GREAT"); @@ -212,8 +212,8 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(4); expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - @@ -269,8 +269,8 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(4); expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - diff --git a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index 2138298ee2b..f5ee301b021 100644 --- a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -9,7 +9,7 @@ import { CommandPhase } from "#app/phases/command-phase"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import GameManager from "#test/testUtils/gameManager"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -305,8 +305,8 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect( modifierSelectHandler.options.some( opt => opt.modifierTypeOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.metal_coat"), diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index 39aa4c41757..6ad52049bac 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -18,7 +18,7 @@ import { Nature } from "#enums/nature"; import { BattlerTagType } from "#enums/battler-tag-type"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { UiMode } from "#enums/ui-mode"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; @@ -228,8 +228,8 @@ describe("The Strong Stuff - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("SOUL_DEW"); }); diff --git a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts index 012b88bcd73..e1167abf2d8 100644 --- a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts @@ -12,7 +12,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { TrainerType } from "#enums/trainer-type"; import { Nature } from "#enums/nature"; @@ -301,8 +301,8 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(1); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MYSTERY_ENCOUNTER_MACHO_BRACE"); }); @@ -343,8 +343,8 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(1); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("RARER_CANDY"); }); diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index dad60c642ce..55de399710e 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -13,7 +13,7 @@ import { RewardTier } from "#enums/reward-tier"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { UiMode } from "#enums/ui-mode"; import * as Utils from "#app/utils/common"; import { MoveId } from "#enums/move-id"; @@ -210,8 +210,8 @@ describe("Trash to Treasure - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(4); expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - diff --git a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index 3690a64cab4..49343122a69 100644 --- a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -11,7 +11,7 @@ import { } from "#test/mystery-encounter/encounter-test-utils"; import type BattleScene from "#app/battle-scene"; import { UiMode } from "#enums/ui-mode"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; @@ -145,8 +145,8 @@ describe("Weird Dream - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("ROGUE_BALL"); @@ -201,8 +201,8 @@ describe("Weird Dream - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(6); expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - diff --git a/test/phases/select-modifier-phase.test.ts b/test/phases/select-modifier-phase.test.ts index 5f7d9759d66..619ec89a2a1 100644 --- a/test/phases/select-modifier-phase.test.ts +++ b/test/phases/select-modifier-phase.test.ts @@ -6,7 +6,7 @@ import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { ModifierTypeOption } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { UiMode } from "#enums/ui-mode"; import { shiftCharCodes } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; @@ -61,8 +61,8 @@ describe("SelectModifierPhase", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); }); @@ -99,8 +99,8 @@ describe("SelectModifierPhase", () => { //const smphase = scene.phaseManager.getCurrentPhase() as SelectModifierPhase; expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); modifierSelectHandler.processInput(Button.ACTION); @@ -127,8 +127,8 @@ describe("SelectModifierPhase", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); const firstRollTiers: RewardTier[] = modifierSelectHandler.options.map(o => o.modifierTypeOption.type.tier); @@ -170,8 +170,8 @@ describe("SelectModifierPhase", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("TM_ULTRA"); @@ -207,8 +207,8 @@ describe("SelectModifierPhase", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); expect( modifierSelectHandler.options[0].modifierTypeOption.type.tier - @@ -246,8 +246,8 @@ describe("SelectModifierPhase", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(4); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("TM_COMMON"); @@ -270,8 +270,8 @@ describe("SelectModifierPhase", () => { expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof ModifierSelectUiHandler, - ) as ModifierSelectUiHandler; + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.tier).toEqual(RewardTier.MASTER); diff --git a/test/testUtils/gameManager.ts b/test/testUtils/gameManager.ts index e8ce3e430b7..7230a137982 100644 --- a/test/testUtils/gameManager.ts +++ b/test/testUtils/gameManager.ts @@ -25,7 +25,7 @@ import { TurnStartPhase } from "#app/phases/turn-start-phase"; import type BallUiHandler from "#app/ui/ball-ui-handler"; import type BattleMessageUiHandler from "#app/ui/battle-message-ui-handler"; import type CommandUiHandler from "#app/ui/command-ui-handler"; -import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import type RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import type PartyUiHandler from "#app/ui/party-ui-handler"; import type StarterSelectUiHandler from "#app/ui/starter-select-ui-handler"; import type TargetSelectUiHandler from "#app/ui/target-select-ui-handler"; @@ -317,7 +317,7 @@ export default class GameManager { "SelectModifierPhase", UiMode.MODIFIER_SELECT, () => { - const handler = this.scene.ui.getHandler() as ModifierSelectUiHandler; + const handler = this.scene.ui.getHandler() as RewardSelectUiHandler; handler.processInput(Button.CANCEL); }, () => @@ -331,7 +331,7 @@ export default class GameManager { "SelectModifierPhase", UiMode.CONFIRM, () => { - const handler = this.scene.ui.getHandler() as ModifierSelectUiHandler; + const handler = this.scene.ui.getHandler() as RewardSelectUiHandler; handler.processInput(Button.ACTION); }, () => diff --git a/test/ui/transfer-item.test.ts b/test/ui/transfer-item.test.ts index febcf3ee5ca..d9bd4b76f20 100644 --- a/test/ui/transfer-item.test.ts +++ b/test/ui/transfer-item.test.ts @@ -1,7 +1,7 @@ import { Button } from "#app/enums/buttons"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; import { UiMode } from "#enums/ui-mode"; import GameManager from "#test/testUtils/gameManager"; @@ -44,9 +44,9 @@ describe("UI - Transfer Items", () => { game.move.select(MoveId.DRAGON_CLAW); game.onNextPrompt("SelectModifierPhase", UiMode.MODIFIER_SELECT, () => { - expect(game.scene.ui.getHandler()).toBeInstanceOf(ModifierSelectUiHandler); + expect(game.scene.ui.getHandler()).toBeInstanceOf(RewardSelectUiHandler); - const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler; + const handler = game.scene.ui.getHandler() as RewardSelectUiHandler; handler.setCursor(1); handler.processInput(Button.ACTION); From 5b14a5899c837d299b4744c9f72075c460617f6e Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 12 Jul 2025 17:20:27 +0200 Subject: [PATCH 18/39] Renamed various ModifierRewardPhase classes --- src/@types/held-modifier-config.ts | 8 -------- .../an-offer-you-cant-refuse-encounter.ts | 2 +- .../encounters/dark-deal-encounter.ts | 2 +- .../encounters/delibirdy-encounter.ts | 12 ++++++------ src/phase-manager.ts | 12 ++++++------ src/phases/game-over-phase.ts | 4 ++-- ...reward-phase.ts => game-over-reward-phase.ts} | 6 +++--- ...{modifier-reward-phase.ts => reward-phase.ts} | 7 +++---- ...er-reward-phase.ts => ribbon-reward-phase.ts} | 6 +++--- src/phases/trainer-item-reward-phase.ts | 7 +++---- src/phases/trainer-victory-phase.ts | 6 +++--- src/phases/victory-phase.ts | 16 ++++++++-------- test/phases/game-over-phase.test.ts | 6 +++--- test/testUtils/phaseInterceptor.ts | 12 ++++++------ 14 files changed, 48 insertions(+), 58 deletions(-) delete mode 100644 src/@types/held-modifier-config.ts rename src/phases/{game-over-modifier-reward-phase.ts => game-over-reward-phase.ts} (81%) rename src/phases/{modifier-reward-phase.ts => reward-phase.ts} (80%) rename src/phases/{ribbon-modifier-reward-phase.ts => ribbon-reward-phase.ts} (84%) diff --git a/src/@types/held-modifier-config.ts b/src/@types/held-modifier-config.ts deleted file mode 100644 index 5617cf2446a..00000000000 --- a/src/@types/held-modifier-config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; - -export default interface HeldModifierConfig { - modifier: PokemonHeldItemModifierType | PokemonHeldItemModifier; - stackCount?: number; - isTransferable?: boolean; -} 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 37ddeaea3d2..89cfa1f0d79 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -137,7 +137,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB }) .withOptionPhase(async () => { // Give the player a Shiny Charm - globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.SHINY_CHARM); + globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.SHINY_CHARM); leaveEncounterWithoutBattle(true); }) .build(), diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 236b4fad9d7..166baef2537 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -163,7 +163,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE .withOptionPhase(async () => { // Give the player 5 Rogue Balls const encounter = globalScene.currentBattle.mysteryEncounter!; - globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.ROGUE_BALL); + globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.ROGUE_BALL); // Start encounter with random legendary (7-10 starter strength) that has level additive // If this is a mono-type challenge, always ensure the required type is filtered for diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 92c6dfaf761..c8e6433f285 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -63,10 +63,10 @@ const doEventReward = () => { return !fullStack; }); if (candidates.length > 0) { - globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes[randSeedItem(candidates)]); + globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes[randSeedItem(candidates)]); } else { // At max stacks, give a Voucher instead - globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.VOUCHER); + globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.VOUCHER); } } }; @@ -170,7 +170,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with // At max stacks, give the first party pokemon a Shell Bell instead backupOption(); } else { - globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.AMULET_COIN); + globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.AMULET_COIN); doEventReward(); } @@ -240,7 +240,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with // At max stacks, give the first party pokemon a Shell Bell instead backupOption(); } else { - globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.CANDY_JAR); + globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.CANDY_JAR); doEventReward(); } } else { @@ -251,7 +251,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with // At max stacks, give the first party pokemon a Shell Bell instead backupOption(); } else { - globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.BERRY_POUCH); + globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.BERRY_POUCH); doEventReward(); } } @@ -323,7 +323,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with // At max stacks, give the first party pokemon a Shell Bell instead backupOption(); } else { - globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.HEALING_CHARM); + globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.HEALING_CHARM); doEventReward(); } diff --git a/src/phase-manager.ts b/src/phase-manager.ts index a4256f110ef..ebfdff6bbe6 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -26,7 +26,7 @@ import { EvolutionPhase } from "#app/phases/evolution-phase"; import { ExpPhase } from "#app/phases/exp-phase"; import { FaintPhase } from "#app/phases/faint-phase"; import { FormChangePhase } from "#app/phases/form-change-phase"; -import { GameOverModifierRewardPhase } from "#app/phases/game-over-modifier-reward-phase"; +import { GameOverRewardPhase } from "#app/phases/game-over-reward-phase"; import { GameOverPhase } from "#app/phases/game-over-phase"; import { HideAbilityPhase } from "#app/phases/hide-ability-phase"; import { HidePartyExpBarPhase } from "#app/phases/hide-party-exp-bar-phase"; @@ -36,7 +36,7 @@ import { LevelUpPhase } from "#app/phases/level-up-phase"; import { LoadMoveAnimPhase } from "#app/phases/load-move-anim-phase"; import { LoginPhase } from "#app/phases/login-phase"; import { MessagePhase } from "#app/phases/message-phase"; -import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; +import { RewardPhase } from "#app/phases/reward-phase"; import { MoneyRewardPhase } from "#app/phases/money-reward-phase"; import { MoveAnimPhase } from "#app/phases/move-anim-phase"; import { MoveChargePhase } from "#app/phases/move-charge-phase"; @@ -70,7 +70,7 @@ import { ReloadSessionPhase } from "#app/phases/reload-session-phase"; import { ResetStatusPhase } from "#app/phases/reset-status-phase"; import { ReturnPhase } from "#app/phases/return-phase"; import { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase"; -import { RibbonModifierRewardPhase } from "#app/phases/ribbon-modifier-reward-phase"; +import { RibbonRewardPhase } from "#app/phases/ribbon-reward-phase"; import { ScanIvsPhase } from "#app/phases/scan-ivs-phase"; import { SelectBiomePhase } from "#app/phases/select-biome-phase"; import { SelectChallengePhase } from "#app/phases/select-challenge-phase"; @@ -138,7 +138,7 @@ const PHASES = Object.freeze({ FaintPhase, FormChangePhase, GameOverPhase, - GameOverModifierRewardPhase, + GameOverRewardPhase, HideAbilityPhase, HidePartyExpBarPhase, LearnMovePhase, @@ -147,7 +147,7 @@ const PHASES = Object.freeze({ LoadMoveAnimPhase, LoginPhase, MessagePhase, - ModifierRewardPhase, + RewardPhase, MoneyRewardPhase, MoveAnimPhase, MoveChargePhase, @@ -178,7 +178,7 @@ const PHASES = Object.freeze({ ResetStatusPhase, ReturnPhase, RevivalBlessingPhase, - RibbonModifierRewardPhase, + RibbonRewardPhase, ScanIvsPhase, SelectBiomePhase, SelectChallengePhase, diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index eaee1e7da23..4899a9bdc7d 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -151,10 +151,10 @@ export class GameOverPhase extends BattlePhase { this.handleUnlocks(); for (const species of this.firstRibbons) { - globalScene.phaseManager.unshiftNew("RibbonModifierRewardPhase", modifierTypes.VOUCHER_PLUS, species); + globalScene.phaseManager.unshiftNew("RibbonRewardPhase", modifierTypes.VOUCHER_PLUS, species); } if (!firstClear) { - globalScene.phaseManager.unshiftNew("GameOverModifierRewardPhase", modifierTypes.VOUCHER_PREMIUM); + globalScene.phaseManager.unshiftNew("GameOverRewardPhase", modifierTypes.VOUCHER_PREMIUM); } } this.getRunHistoryEntry().then(runHistoryEntry => { diff --git a/src/phases/game-over-modifier-reward-phase.ts b/src/phases/game-over-reward-phase.ts similarity index 81% rename from src/phases/game-over-modifier-reward-phase.ts rename to src/phases/game-over-reward-phase.ts index 13c8f48abad..f29ac28a116 100644 --- a/src/phases/game-over-modifier-reward-phase.ts +++ b/src/phases/game-over-reward-phase.ts @@ -1,10 +1,10 @@ import { globalScene } from "#app/global-scene"; import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; -import { ModifierRewardPhase } from "./modifier-reward-phase"; +import { RewardPhase } from "./reward-phase"; -export class GameOverModifierRewardPhase extends ModifierRewardPhase { - public readonly phaseName = "GameOverModifierRewardPhase"; +export class GameOverRewardPhase extends RewardPhase { + public readonly phaseName = "GameOverRewardPhase"; doReward(): Promise { return new Promise(resolve => { const newModifier = this.modifierType.newModifier(); diff --git a/src/phases/modifier-reward-phase.ts b/src/phases/reward-phase.ts similarity index 80% rename from src/phases/modifier-reward-phase.ts rename to src/phases/reward-phase.ts index 18f0ba0ea17..65a301cb1f2 100644 --- a/src/phases/modifier-reward-phase.ts +++ b/src/phases/reward-phase.ts @@ -5,11 +5,10 @@ import { getModifierType } from "#app/utils/modifier-utils"; import i18next from "i18next"; import { BattlePhase } from "./battle-phase"; -export class ModifierRewardPhase extends BattlePhase { - // RibbonModifierRewardPhase extends ModifierRewardPhase and to make typescript happy +export class RewardPhase extends BattlePhase { + // RibbonRewardPhase extends RewardPhase and to make typescript happy // we need to use a union type here - public readonly phaseName: "ModifierRewardPhase" | "RibbonModifierRewardPhase" | "GameOverModifierRewardPhase" = - "ModifierRewardPhase"; + public readonly phaseName: "RewardPhase" | "RibbonRewardPhase" | "GameOverRewardPhase" = "RewardPhase"; protected modifierType: ModifierType; constructor(modifierTypeFunc: ModifierTypeFunc) { diff --git a/src/phases/ribbon-modifier-reward-phase.ts b/src/phases/ribbon-reward-phase.ts similarity index 84% rename from src/phases/ribbon-modifier-reward-phase.ts rename to src/phases/ribbon-reward-phase.ts index 10d63ba707f..cbd03c3d067 100644 --- a/src/phases/ribbon-modifier-reward-phase.ts +++ b/src/phases/ribbon-reward-phase.ts @@ -3,10 +3,10 @@ import type PokemonSpecies from "#app/data/pokemon-species"; import type { ModifierTypeFunc } from "#app/@types/modifier-types"; import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; -import { ModifierRewardPhase } from "./modifier-reward-phase"; +import { RewardPhase } from "./reward-phase"; -export class RibbonModifierRewardPhase extends ModifierRewardPhase { - public readonly phaseName = "RibbonModifierRewardPhase"; +export class RibbonRewardPhase extends RewardPhase { + public readonly phaseName = "RibbonRewardPhase"; private species: PokemonSpecies; constructor(modifierTypeFunc: ModifierTypeFunc, species: PokemonSpecies) { diff --git a/src/phases/trainer-item-reward-phase.ts b/src/phases/trainer-item-reward-phase.ts index 29bbe215fc0..31aa93612de 100644 --- a/src/phases/trainer-item-reward-phase.ts +++ b/src/phases/trainer-item-reward-phase.ts @@ -5,11 +5,10 @@ import { getModifierType } from "#app/utils/modifier-utils"; import i18next from "i18next"; import { BattlePhase } from "./battle-phase"; -export class ModifierRewardPhase extends BattlePhase { - // RibbonModifierRewardPhase extends ModifierRewardPhase and to make typescript happy +export class RewardPhase extends BattlePhase { + // RibbonRewardPhase extends RewardPhase and to make typescript happy // we need to use a union type here - public readonly phaseName: "ModifierRewardPhase" | "RibbonModifierRewardPhase" | "GameOverModifierRewardPhase" = - "ModifierRewardPhase"; + public readonly phaseName: "RewardPhase" | "RibbonRewardPhase" | "GameOverRewardPhase" = "RewardPhase"; protected modifierType: ModifierType; constructor(modifierTypeFunc: ModifierTypeFunc) { diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index 554b8109f02..392ceadc00d 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -22,7 +22,7 @@ export class TrainerVictoryPhase extends BattlePhase { const modifierRewardFuncs = globalScene.currentBattle.trainer?.config.modifierRewardFuncs!; // TODO: is this bang correct? for (const modifierRewardFunc of modifierRewardFuncs) { - globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierRewardFunc); + globalScene.phaseManager.unshiftNew("RewardPhase", modifierRewardFunc); } const trainerType = globalScene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? @@ -34,7 +34,7 @@ export class TrainerVictoryPhase extends BattlePhase { ) { if (timedEventManager.getUpgradeUnlockedVouchers()) { globalScene.phaseManager.unshiftNew( - "ModifierRewardPhase", + "RewardPhase", [ modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PLUS, @@ -44,7 +44,7 @@ export class TrainerVictoryPhase extends BattlePhase { ); } else { globalScene.phaseManager.unshiftNew( - "ModifierRewardPhase", + "RewardPhase", [modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][ vouchers[TrainerType[trainerType]].voucherType ], diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index ae5b727c2a6..663d7d9b90c 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -55,11 +55,11 @@ export class VictoryPhase extends PokemonPhase { // Get event modifiers for this wave timedEventManager .getFixedBattleEventRewards(globalScene.currentBattle.waveIndex) - .map(r => globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes[r])); + .map(r => globalScene.phaseManager.pushNew("RewardPhase", modifierTypes[r])); break; case ClassicFixedBossWaves.EVIL_BOSS_2: // Should get Lock Capsule on 165 before shop phase so it can be used in the rewards shop - globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.LOCK_CAPSULE); + globalScene.phaseManager.pushNew("RewardPhase", modifierTypes.LOCK_CAPSULE); break; } } @@ -71,35 +71,35 @@ export class VictoryPhase extends PokemonPhase { this.getFixedBattleCustomModifiers(), ); } else if (globalScene.gameMode.isDaily) { - globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.EXP_CHARM); + globalScene.phaseManager.pushNew("RewardPhase", modifierTypes.EXP_CHARM); if ( globalScene.currentBattle.waveIndex > 10 && !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex) ) { - globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.GOLDEN_POKEBALL); + globalScene.phaseManager.pushNew("RewardPhase", modifierTypes.GOLDEN_POKEBALL); } } else { const superExpWave = !globalScene.gameMode.isEndless ? (globalScene.offsetGym ? 0 : 20) : 10; if (globalScene.gameMode.isEndless && globalScene.currentBattle.waveIndex === 10) { - globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.EXP_SHARE); + globalScene.phaseManager.pushNew("RewardPhase", modifierTypes.EXP_SHARE); } if ( globalScene.currentBattle.waveIndex <= 750 && (globalScene.currentBattle.waveIndex <= 500 || globalScene.currentBattle.waveIndex % 30 === superExpWave) ) { globalScene.phaseManager.pushNew( - "ModifierRewardPhase", + "RewardPhase", globalScene.currentBattle.waveIndex % 30 !== superExpWave || globalScene.currentBattle.waveIndex > 250 ? modifierTypes.EXP_CHARM : modifierTypes.SUPER_EXP_CHARM, ); } if (globalScene.currentBattle.waveIndex <= 150 && !(globalScene.currentBattle.waveIndex % 50)) { - globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.GOLDEN_POKEBALL); + globalScene.phaseManager.pushNew("RewardPhase", modifierTypes.GOLDEN_POKEBALL); } if (globalScene.gameMode.isEndless && !(globalScene.currentBattle.waveIndex % 50)) { globalScene.phaseManager.pushNew( - "ModifierRewardPhase", + "RewardPhase", !(globalScene.currentBattle.waveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS, ); globalScene.phaseManager.pushNew("AddEnemyBuffModifierPhase"); diff --git a/test/phases/game-over-phase.test.ts b/test/phases/game-over-phase.test.ts index 008f9fb68e8..7b8e95f7de2 100644 --- a/test/phases/game-over-phase.test.ts +++ b/test/phases/game-over-phase.test.ts @@ -53,7 +53,7 @@ describe("Game Over Phase", () => { // so the best we can do is to check that their reward phases occurred. expect(game.phaseInterceptor.log.includes("GameOverPhase")).toBe(true); expect(game.phaseInterceptor.log.includes("UnlockPhase")).toBe(true); - expect(game.phaseInterceptor.log.includes("RibbonModifierRewardPhase")).toBe(true); + expect(game.phaseInterceptor.log.includes("RibbonRewardPhase")).toBe(true); expect(game.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]).toBe(true); expect(game.scene.validateAchv).toHaveBeenCalledWith(achvs.CLASSIC_VICTORY); expect(game.scene.gameData.achvUnlocks[achvs.CLASSIC_VICTORY.id]).toBeTruthy(); @@ -68,8 +68,8 @@ describe("Game Over Phase", () => { expect(game.phaseInterceptor.log.includes("GameOverPhase")).toBe(true); expect(game.phaseInterceptor.log.includes("UnlockPhase")).toBe(false); - expect(game.phaseInterceptor.log.includes("RibbonModifierRewardPhase")).toBe(false); - expect(game.phaseInterceptor.log.includes("GameOverModifierRewardPhase")).toBe(false); + expect(game.phaseInterceptor.log.includes("RibbonRewardPhase")).toBe(false); + expect(game.phaseInterceptor.log.includes("GameOverRewardPhase")).toBe(false); expect(game.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]).toBe(false); expect(game.scene.validateAchv).not.toHaveBeenCalledWith(achvs.CLASSIC_VICTORY); expect(game.scene.gameData.achvUnlocks[achvs.CLASSIC_VICTORY.id]).toBeFalsy(); diff --git a/test/testUtils/phaseInterceptor.ts b/test/testUtils/phaseInterceptor.ts index 9d046fc85ba..9e0f5774eea 100644 --- a/test/testUtils/phaseInterceptor.ts +++ b/test/testUtils/phaseInterceptor.ts @@ -53,12 +53,12 @@ import { MysteryEncounterRewardsPhase, PostMysteryEncounterPhase, } from "#app/phases/mystery-encounter-phases"; -import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; +import { RewardPhase } from "#app/phases/reward-phase"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; import { ExpPhase } from "#app/phases/exp-phase"; import { GameOverPhase } from "#app/phases/game-over-phase"; -import { RibbonModifierRewardPhase } from "#app/phases/ribbon-modifier-reward-phase"; -import { GameOverModifierRewardPhase } from "#app/phases/game-over-modifier-reward-phase"; +import { RibbonRewardPhase } from "#app/phases/ribbon-reward-phase"; +import { GameOverRewardPhase } from "#app/phases/game-over-reward-phase"; import { UnlockPhase } from "#app/phases/unlock-phase"; import { PostGameOverPhase } from "#app/phases/post-game-over-phase"; import { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase"; @@ -147,9 +147,9 @@ export default class PhaseInterceptor { [MysteryEncounterBattlePhase, this.startPhase], [MysteryEncounterRewardsPhase, this.startPhase], [PostMysteryEncounterPhase, this.startPhase], - [RibbonModifierRewardPhase, this.startPhase], - [GameOverModifierRewardPhase, this.startPhase], - [ModifierRewardPhase, this.startPhase], + [RibbonRewardPhase, this.startPhase], + [GameOverRewardPhase, this.startPhase], + [RewardPhase, this.startPhase], [PartyExpPhase, this.startPhase], [ExpPhase, this.startPhase], [EncounterPhase, this.startPhase], From 0d2bae0fcc4dc0f1307335cd9655f3efc261ced8 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 12 Jul 2025 20:23:11 +0200 Subject: [PATCH 19/39] Rename SelectModifierPhase to SelectRewardPhase --- .../utils/encounter-phase-utils.ts | 2 +- src/modifier/modifier-type.ts | 2 +- src/phase-manager.ts | 4 +- src/phases/learn-move-phase.ts | 4 +- src/phases/mystery-encounter-phases.ts | 4 +- ...difier-phase.ts => select-reward-phase.ts} | 12 +-- src/phases/victory-phase.ts | 4 +- src/ui/menu-ui-handler.ts | 2 +- src/ui/party-ui-handler.ts | 4 +- src/ui/ui.ts | 2 +- test/abilities/wimp_out.test.ts | 2 +- test/battle/battle.test.ts | 2 +- test/daily_mode.test.ts | 4 +- test/items/dire_hit.test.ts | 2 +- .../double_battle_chance_booster.test.ts | 2 +- test/items/lock_capsule.test.ts | 10 +-- test/items/temp_stat_stage_booster.test.ts | 2 +- .../a-trainers-test-encounter.test.ts | 10 +-- .../absolute-avarice-encounter.test.ts | 6 +- ...an-offer-you-cant-refuse-encounter.test.ts | 6 +- .../berries-abound-encounter.test.ts | 24 +++--- .../bug-type-superfan-encounter.test.ts | 22 +++--- .../clowning-around-encounter.test.ts | 74 ++++++------------- .../dancing-lessons-encounter.test.ts | 8 +- .../department-store-sale-encounter.test.ts | 18 ++--- .../encounters/field-trip-encounter.test.ts | 14 ++-- .../fiery-fallout-encounter.test.ts | 10 +-- .../fight-or-flight-encounter.test.ts | 14 ++-- .../fun-and-games-encounter.test.ts | 30 ++++---- .../global-trade-system-encounter.test.ts | 6 +- .../mysterious-challengers-encounter.test.ts | 20 ++--- .../teleporting-hijinks-encounter.test.ts | 8 +- .../the-expert-breeder-encounter.test.ts | 14 ++-- .../the-strong-stuff-encounter.test.ts | 8 +- .../the-winstrate-challenge-encounter.test.ts | 12 +-- .../trash-to-treasure-encounter.test.ts | 12 +-- .../encounters/weird-dream-encounter.test.ts | 18 ++--- test/phases/select-modifier-phase.test.ts | 36 ++++----- test/testUtils/gameManager.ts | 8 +- test/testUtils/helpers/modifiersHelper.ts | 6 +- test/testUtils/phaseInterceptor.ts | 6 +- test/ui/transfer-item.test.ts | 10 +-- 42 files changed, 214 insertions(+), 250 deletions(-) rename src/phases/{select-modifier-phase.ts => select-reward-phase.ts} (98%) diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 71d02ced5d2..96f90705232 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -745,7 +745,7 @@ export function setEncounterRewards( } if (customShopRewards) { - globalScene.phaseManager.unshiftNew("SelectModifierPhase", 0, undefined, customShopRewards); + globalScene.phaseManager.unshiftNew("SelectRewardPhase", 0, undefined, customShopRewards); } else { globalScene.phaseManager.tryRemovePhase(p => p.is("MysteryEncounterRewardsPhase")); } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index f9bf653c978..6e42922cce5 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1677,7 +1677,7 @@ export function getModifierTypeFuncById(id: string): ModifierTypeFunc { } /** - * Generates modifier options for a {@linkcode SelectModifierPhase} + * Generates modifier options for a {@linkcode SelectRewardPhase} * @param count - Determines the number of items to generate * @param party - Party is required for generating proper modifier pools * @param modifierTiers - (Optional) If specified, rolls items in the specified tiers. Commonly used for tier-locking with Lock Capsule. diff --git a/src/phase-manager.ts b/src/phase-manager.ts index ebfdff6bbe6..f2d37b4ff68 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -75,7 +75,7 @@ import { ScanIvsPhase } from "#app/phases/scan-ivs-phase"; import { SelectBiomePhase } from "#app/phases/select-biome-phase"; import { SelectChallengePhase } from "#app/phases/select-challenge-phase"; import { SelectGenderPhase } from "#app/phases/select-gender-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { SelectStarterPhase } from "#app/phases/select-starter-phase"; import { SelectTargetPhase } from "#app/phases/select-target-phase"; import { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase"; @@ -183,7 +183,7 @@ const PHASES = Object.freeze({ SelectBiomePhase, SelectChallengePhase, SelectGenderPhase, - SelectModifierPhase, + SelectRewardPhase, SelectStarterPhase, SelectTargetPhase, ShinySparklePhase, diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index e24efa63b5a..2947afda694 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -187,7 +187,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { pokemon.usedTMs = []; } pokemon.usedTMs.push(this.moveId); - globalScene.phaseManager.tryRemovePhase(phase => phase.is("SelectModifierPhase")); + globalScene.phaseManager.tryRemovePhase(phase => phase.is("SelectRewardPhase")); } else if (this.learnMoveType === LearnMoveType.MEMORY) { if (this.cost !== -1) { if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { @@ -197,7 +197,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { } globalScene.playSound("se/buy"); } else { - globalScene.phaseManager.tryRemovePhase(phase => phase.is("SelectModifierPhase")); + globalScene.phaseManager.tryRemovePhase(phase => phase.is("SelectRewardPhase")); } } pokemon.setMove(index, this.moveId); diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index 1adb8f4d5ab..dc37da7c9d9 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -551,8 +551,8 @@ export class MysteryEncounterRewardsPhase extends Phase { if (encounter.doEncounterRewards) { encounter.doEncounterRewards(); } else if (this.addHealPhase) { - globalScene.phaseManager.tryRemovePhase(p => p.is("SelectModifierPhase")); - globalScene.phaseManager.unshiftNew("SelectModifierPhase", 0, undefined, { + globalScene.phaseManager.tryRemovePhase(p => p.is("SelectRewardPhase")); + globalScene.phaseManager.unshiftNew("SelectRewardPhase", 0, undefined, { fillRemaining: false, rerollMultiplier: -1, }); diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-reward-phase.ts similarity index 98% rename from src/phases/select-modifier-phase.ts rename to src/phases/select-reward-phase.ts index f2cf249af08..06ca52b5b9d 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-reward-phase.ts @@ -31,8 +31,8 @@ import { TrainerItemEffect } from "#app/items/trainer-item"; export type ModifierSelectCallback = (rowCursor: number, cursor: number) => boolean; -export class SelectModifierPhase extends BattlePhase { - public readonly phaseName = "SelectModifierPhase"; +export class SelectRewardPhase extends BattlePhase { + public readonly phaseName = "SelectRewardPhase"; private rerollCount: number; private modifierTiers?: RewardTier[]; private customModifierSettings?: CustomModifierSettings; @@ -199,7 +199,7 @@ export class SelectModifierPhase extends BattlePhase { } globalScene.reroll = true; globalScene.phaseManager.unshiftNew( - "SelectModifierPhase", + "SelectRewardPhase", this.rerollCount + 1, this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as RewardTier[], ); @@ -288,7 +288,7 @@ export class SelectModifierPhase extends BattlePhase { globalScene.animateMoneyChanged(false); } globalScene.playSound("se/buy"); - (globalScene.ui.getHandler() as ModifierSelectUiHandler).updateCostText(); + (globalScene.ui.getHandler() as RewardSelectUiHandler).updateCostText(); } else { globalScene.ui.playError(); } @@ -491,9 +491,9 @@ export class SelectModifierPhase extends BattlePhase { ); } - copy(): SelectModifierPhase { + copy(): SelectRewardPhase { return globalScene.phaseManager.create( - "SelectModifierPhase", + "SelectRewardPhase", this.rerollCount, this.modifierTiers, { diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index 663d7d9b90c..1f91fa81418 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -65,7 +65,7 @@ export class VictoryPhase extends PokemonPhase { } if (globalScene.currentBattle.waveIndex % 10) { globalScene.phaseManager.pushNew( - "SelectModifierPhase", + "SelectRewardPhase", undefined, undefined, this.getFixedBattleCustomModifiers(), @@ -124,7 +124,7 @@ export class VictoryPhase extends PokemonPhase { /** * If this wave is a fixed battle with special custom modifier rewards, - * will pass those settings to the upcoming {@linkcode SelectModifierPhase}`. + * will pass those settings to the upcoming {@linkcode SelectRewardPhase}`. */ getFixedBattleCustomModifiers(): CustomModifierSettings | undefined { const gameMode = globalScene.gameMode; diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index 5ab1d4f9e96..cff5db729f9 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -125,7 +125,7 @@ export default class MenuUiHandler extends MessageUiHandler { const ui = this.getUi(); this.excludedMenus = () => [ { - condition: !!globalScene.phaseManager.getCurrentPhase()?.is("SelectModifierPhase"), + condition: !!globalScene.phaseManager.getCurrentPhase()?.is("SelectRewardPhase"), options: [MenuOptions.EGG_GACHA], }, { condition: bypassLogin, options: [MenuOptions.LOG_OUT] }, diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 69b1db01cf6..38ce3deaf85 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -737,7 +737,7 @@ export default class PartyUiHandler extends MessageUiHandler { // TODO: This risks hitting the other options (.MOVE_i and ALL) so does it? Do we need an extra check? if ( option >= PartyOption.FORM_CHANGE_ITEM && - globalScene.phaseManager.getCurrentPhase()?.is("SelectModifierPhase") && + globalScene.phaseManager.getCurrentPhase()?.is("SelectRewardPhase") && this.partyUiMode === PartyUiMode.CHECK ) { const formChangeItems = this.getFormChangeItems(pokemon); @@ -1309,7 +1309,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.addCommonOptions(pokemon); break; case PartyUiMode.CHECK: - if (globalScene.phaseManager.getCurrentPhase()?.is("SelectModifierPhase")) { + if (globalScene.phaseManager.getCurrentPhase()?.is("SelectRewardPhase")) { const formChangeItems = this.getFormChangeItems(pokemon); for (let i = 0; i < formChangeItems.length; i++) { this.options.push(PartyOption.FORM_CHANGE_ITEM + i); diff --git a/src/ui/ui.ts b/src/ui/ui.ts index c2e085b243a..aa65ebaa66a 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -6,7 +6,7 @@ import PartyUiHandler from "./party-ui-handler"; import FightUiHandler from "./fight-ui-handler"; import MessageUiHandler from "./message-ui-handler"; import ConfirmUiHandler from "./confirm-ui-handler"; -import RewardSelectUiHandler from "./modifier-select-ui-handler"; +import RewardSelectUiHandler from "./reward-select-ui-handler"; import BallUiHandler from "./ball-ui-handler"; import SummaryUiHandler from "./summary-ui-handler"; import StarterSelectUiHandler from "./starter-select-ui-handler"; diff --git a/test/abilities/wimp_out.test.ts b/test/abilities/wimp_out.test.ts index 8e97618d46f..f9f824402df 100644 --- a/test/abilities/wimp_out.test.ts +++ b/test/abilities/wimp_out.test.ts @@ -547,7 +547,7 @@ describe("Abilities - Wimp Out", () => { await game.move.selectEnemyMove(MoveId.SPLASH); await game.move.selectEnemyMove(MoveId.ENDURE); - await game.phaseInterceptor.to("SelectModifierPhase"); + await game.phaseInterceptor.to("SelectRewardPhase"); expect(game.scene.currentBattle.waveIndex).toBe(wave + 1); }); }); diff --git a/test/battle/battle.test.ts b/test/battle/battle.test.ts index bf2c3968aa6..ff658a4a0b1 100644 --- a/test/battle/battle.test.ts +++ b/test/battle/battle.test.ts @@ -93,7 +93,7 @@ describe("Test Battle Phase", () => { game.override.enemySpecies(SpeciesId.RATTATA).startingLevel(2000).battleStyle("single").startingWave(3); await game.classicMode.startBattle([SpeciesId.MEWTWO]); game.move.use(MoveId.TACKLE); - await game.phaseInterceptor.to("SelectModifierPhase"); + await game.phaseInterceptor.to("SelectRewardPhase"); }); it("do attack wave 3 - single battle - regular - NO OHKO with opponent using non damage attack", async () => { diff --git a/test/daily_mode.test.ts b/test/daily_mode.test.ts index 4a408bd6c2c..5933bf236cd 100644 --- a/test/daily_mode.test.ts +++ b/test/daily_mode.test.ts @@ -76,7 +76,7 @@ describe("Shop modifications", async () => { game.move.select(MoveId.SPLASH); await game.doKillOpponents(); await game.phaseInterceptor.to("BattleEndPhase"); - game.onNextPrompt("SelectModifierPhase", UiMode.MODIFIER_SELECT, () => { + game.onNextPrompt("SelectRewardPhase", UiMode.MODIFIER_SELECT, () => { expect(game.scene.ui.getHandler()).toBeInstanceOf(RewardSelectUiHandler); game.modifiers.testCheck("EVIOLITE", false).testCheck("MINI_BLACK_HOLE", false); }); @@ -87,7 +87,7 @@ describe("Shop modifications", async () => { game.move.select(MoveId.SPLASH); await game.doKillOpponents(); await game.phaseInterceptor.to("BattleEndPhase"); - game.onNextPrompt("SelectModifierPhase", UiMode.MODIFIER_SELECT, () => { + game.onNextPrompt("SelectRewardPhase", UiMode.MODIFIER_SELECT, () => { expect(game.scene.ui.getHandler()).toBeInstanceOf(RewardSelectUiHandler); game.modifiers.testCheck("EVIOLITE", true).testCheck("MINI_BLACK_HOLE", true); }); diff --git a/test/items/dire_hit.test.ts b/test/items/dire_hit.test.ts index 5407f9abdc9..851628e6208 100644 --- a/test/items/dire_hit.test.ts +++ b/test/items/dire_hit.test.ts @@ -68,7 +68,7 @@ describe("Items - Dire Hit", () => { // Forced DIRE_HIT to spawn in the first slot with override game.onNextPrompt( - "SelectModifierPhase", + "SelectRewardPhase", UiMode.MODIFIER_SELECT, () => { const handler = game.scene.ui.getHandler() as RewardSelectUiHandler; diff --git a/test/items/double_battle_chance_booster.test.ts b/test/items/double_battle_chance_booster.test.ts index 3e59f1ebcad..7e6e51a9c02 100644 --- a/test/items/double_battle_chance_booster.test.ts +++ b/test/items/double_battle_chance_booster.test.ts @@ -74,7 +74,7 @@ describe("Items - Double Battle Chance Boosters", () => { // Forced LURE to spawn in the first slot with override game.onNextPrompt( - "SelectModifierPhase", + "SelectRewardPhase", UiMode.MODIFIER_SELECT, () => { const handler = game.scene.ui.getHandler() as RewardSelectUiHandler; diff --git a/test/items/lock_capsule.test.ts b/test/items/lock_capsule.test.ts index 0395d775019..368b57221e2 100644 --- a/test/items/lock_capsule.test.ts +++ b/test/items/lock_capsule.test.ts @@ -1,7 +1,7 @@ import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { RewardTier } from "#enums/reward-tier"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { UiMode } from "#enums/ui-mode"; import GameManager from "#test/testUtils/gameManager"; import Phase from "phaser"; @@ -36,19 +36,19 @@ describe("Items - Lock Capsule", () => { it("doesn't set the cost of common tier items to 0", async () => { await game.classicMode.startBattle(); game.scene.phaseManager.overridePhase( - new SelectModifierPhase(0, undefined, { + new SelectRewardPhase(0, undefined, { guaranteedModifierTiers: [RewardTier.COMMON, RewardTier.COMMON, RewardTier.COMMON], fillRemaining: false, }), ); - game.onNextPrompt("SelectModifierPhase", UiMode.MODIFIER_SELECT, () => { - const selectModifierPhase = game.scene.phaseManager.getCurrentPhase() as SelectModifierPhase; + game.onNextPrompt("SelectRewardPhase", UiMode.MODIFIER_SELECT, () => { + const selectModifierPhase = game.scene.phaseManager.getCurrentPhase() as SelectRewardPhase; const rerollCost = selectModifierPhase.getRerollCost(true); expect(rerollCost).toBe(150); }); game.doSelectModifier(); - await game.phaseInterceptor.to("SelectModifierPhase"); + await game.phaseInterceptor.to("SelectRewardPhase"); }); }); diff --git a/test/items/temp_stat_stage_booster.test.ts b/test/items/temp_stat_stage_booster.test.ts index 2d445b3f370..1a3f3723a0b 100644 --- a/test/items/temp_stat_stage_booster.test.ts +++ b/test/items/temp_stat_stage_booster.test.ts @@ -135,7 +135,7 @@ describe("Items - Temporary Stat Stage Boosters", () => { // Forced X_ATTACK to spawn in the first slot with override game.onNextPrompt( - "SelectModifierPhase", + "SelectRewardPhase", UiMode.MODIFIER_SELECT, () => { const handler = game.scene.ui.getHandler() as RewardSelectUiHandler; diff --git a/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts b/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts index 82cac197fe9..15ce92de488 100644 --- a/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts +++ b/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts @@ -17,7 +17,7 @@ import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils import { ATrainersTestEncounter } from "#app/data/mystery-encounters/encounters/a-trainers-test-encounter"; import { EggTier } from "#enums/egg-type"; import { CommandPhase } from "#app/phases/command-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { PartyHealPhase } from "#app/phases/party-heal-phase"; import i18next from "i18next"; @@ -130,8 +130,8 @@ describe("A Trainer's Test - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); const eggsAfter = scene.gameData.eggs; expect(eggsAfter).toBeDefined(); @@ -178,8 +178,8 @@ describe("A Trainer's Test - Mystery Encounter", () => { const eggsBeforeLength = eggsBefore.length; await runMysteryEncounterToEnd(game, 2); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); const eggsAfter = scene.gameData.eggs; expect(eggsAfter).toBeDefined(); diff --git a/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts b/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts index dc695089e6c..cfe144284fc 100644 --- a/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts +++ b/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts @@ -16,7 +16,7 @@ import { AbsoluteAvariceEncounter } from "#app/data/mystery-encounters/encounter import { MoveId } from "#enums/move-id"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { HeldItemId } from "#enums/held-item-id"; const namespace = "mysteryEncounters/absoluteAvarice"; @@ -143,8 +143,8 @@ describe("Absolute Avarice - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); for (const partyPokemon of scene.getPlayerParty()) { expect(partyPokemon.heldItemManager.getStack(HeldItemId.REVIVER_SEED)).toBe(1); diff --git a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index 4f986f58b88..fc92b9cadda 100644 --- a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -16,7 +16,7 @@ import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; import { ShinyRateBoosterModifier } from "#app/modifier/modifier"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import i18next from "i18next"; import { AbilityId } from "#enums/ability-id"; @@ -197,7 +197,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { const expBefore = gyarados.exp; await runMysteryEncounterToEnd(game, 2); - await game.phaseInterceptor.to(SelectModifierPhase, false); + await game.phaseInterceptor.to(SelectRewardPhase, false); expect(gyarados.exp).toBe( expBefore + Math.floor((getPokemonSpecies(SpeciesId.LIEPARD).baseExp * defaultWave) / 5 + 1), @@ -213,7 +213,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { const expBefore = abra.exp; await runMysteryEncounterToEnd(game, 2); - await game.phaseInterceptor.to(SelectModifierPhase, false); + await game.phaseInterceptor.to(SelectRewardPhase, false); expect(abra.exp).toBe( expBefore + Math.floor((getPokemonSpecies(SpeciesId.LIEPARD).baseExp * defaultWave) / 5 + 1), diff --git a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts index ebd394fe379..226e962e6a5 100644 --- a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts +++ b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts @@ -11,7 +11,6 @@ import { import type BattleScene from "#app/battle-scene"; import { UiMode } from "#enums/ui-mode"; import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; -import { BerryModifier } from "#app/modifier/modifier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; @@ -19,7 +18,7 @@ import { BerriesAboundEncounter } from "#app/data/mystery-encounters/encounters/ import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import * as EncounterDialogueUtils from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { CommandPhase } from "#app/phases/command-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { AbilityId } from "#enums/ability-id"; const namespace = "mysteryEncounters/berriesAbound"; @@ -45,7 +44,6 @@ describe("Berries Abound - Mystery Encounter", () => { .startingWave(defaultWave) .startingBiome(defaultBiome) .disableTrainerWaves() - .startingModifier([]) .startingHeldItems([]) .enemyAbility(AbilityId.BALL_FETCH) .enemyPassiveAbility(AbilityId.BALL_FETCH); @@ -128,14 +126,12 @@ describe("Berries Abound - Mystery Encounter", () => { const numBerries = game.scene.currentBattle.mysteryEncounter!.misc.numBerries; // Clear out any pesky mods that slipped through test spin-up - scene.modifiers.forEach(mod => { - scene.removeModifier(mod); - }); + scene.clearAllItems(); await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); const berriesAfter = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; const berriesAfterCount = berriesAfter.reduce((a, b) => a + b.stackCount, 0); @@ -147,9 +143,9 @@ describe("Berries Abound - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -232,9 +228,9 @@ describe("Berries Abound - Mystery Encounter", () => { }); await runMysteryEncounterToEnd(game, 2); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index 82be1749cac..85fc4532c20 100644 --- a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -21,7 +21,7 @@ import { MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases import { CommandPhase } from "#app/phases/command-phase"; import { BugTypeSuperfanEncounter } from "#app/data/mystery-encounters/encounters/bug-type-superfan-encounter"; import * as encounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { HeldItemId } from "#enums/held-item-id"; @@ -416,8 +416,8 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, defaultParty); await runMysteryEncounterToEnd(game, 2); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -435,8 +435,8 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { ]); await runMysteryEncounterToEnd(game, 2); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -457,8 +457,8 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { ]); await runMysteryEncounterToEnd(game, 2); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -481,8 +481,8 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { ]); await runMysteryEncounterToEnd(game, 2); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -556,8 +556,8 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 213fea75081..536d171d17a 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -7,20 +7,17 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import * as BattleAnims from "#app/data/battle-anims"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase, } from "#test/mystery-encounter/encounter-test-utils"; import { MoveId } from "#enums/move-id"; import type BattleScene from "#app/battle-scene"; -import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { UiMode } from "#enums/ui-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; -import { RewardTier } from "#enums/reward-tier"; import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowning-around-encounter"; import { TrainerType } from "#enums/trainer-type"; import { AbilityId } from "#enums/ability-id"; @@ -28,15 +25,14 @@ import { PostMysteryEncounterPhase } from "#app/phases/mystery-encounter-phases" import { Button } from "#enums/buttons"; import type PartyUiHandler from "#app/ui/party-ui-handler"; import type OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; -import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { modifierTypes } from "#app/data/data-lists"; -import { BerryType } from "#enums/berry-type"; -import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { PokemonType } from "#enums/pokemon-type"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { NewBattlePhase } from "#app/phases/new-battle-phase"; +import { HeldItemId } from "#enums/held-item-id"; +import { getHeldItemTier } from "#app/items/held-item-tiers"; +import { RewardTier } from "#enums/reward-tier"; const namespace = "mysteryEncounters/clowningAround"; const defaultParty = [SpeciesId.LAPRAS, SpeciesId.GENGAR, SpeciesId.ABRA]; @@ -200,9 +196,9 @@ describe("Clowning Around - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); const abilityToTrain = scene.currentBattle.mysteryEncounter?.misc.ability; game.onNextPrompt("PostMysteryEncounterPhase", UiMode.MESSAGE, () => { @@ -266,48 +262,32 @@ describe("Clowning Around - Mystery Encounter", () => { scene.getPlayerParty()[0].moveset = [new PokemonMove(MoveId.TACKLE), new PokemonMove(MoveId.THIEF)]; // 2 Sitrus Berries on lead - scene.modifiers = []; - let itemType = generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType; - await addItemToPokemon(scene, scene.getPlayerParty()[0], 2, itemType); - // 2 Ganlon Berries on lead - itemType = generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType; - await addItemToPokemon(scene, scene.getPlayerParty()[0], 2, itemType); - // 5 Golden Punch on lead (ultra) - itemType = generateModifierType(modifierTypes.GOLDEN_PUNCH) as PokemonHeldItemModifierType; - await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType); - // 5 Lucky Egg on lead (ultra) - itemType = generateModifierType(modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType; - await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType); - // 3 Soothe Bell on lead (great tier, but counted as ultra by this ME) - itemType = generateModifierType(modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType; - await addItemToPokemon(scene, scene.getPlayerParty()[0], 3, itemType); - // 5 Soul Dew on lead (rogue) - itemType = generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType; - await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType); - // 2 Golden Egg on lead (rogue) - itemType = generateModifierType(modifierTypes.GOLDEN_EGG) as PokemonHeldItemModifierType; - await addItemToPokemon(scene, scene.getPlayerParty()[0], 2, itemType); + scene.clearAllItems(); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.SITRUS_BERRY, 2); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.GANLON_BERRY, 2); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.GOLDEN_PUNCH, 5); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.LUCKY_EGG, 5); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.SOOTHE_BELL, 3); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.SOUL_DEW, 5); + scene.getPlayerParty()[0].heldItemManager.add(HeldItemId.GOLDEN_EGG, 2); - // 5 Soul Dew on second party pokemon (these should not change) - itemType = generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType; - await addItemToPokemon(scene, scene.getPlayerParty()[1], 5, itemType); + scene.getPlayerParty()[1].heldItemManager.add(HeldItemId.SOUL_DEW, 5); await runMysteryEncounterToEnd(game, 2); const leadItemsAfter = scene.getPlayerParty()[0].getHeldItems(); const ultraCountAfter = leadItemsAfter - .filter(m => m.type.tier === RewardTier.ULTRA) - .reduce((a, b) => a + b.stackCount, 0); + .filter(m => getHeldItemTier(m) === RewardTier.ULTRA) + .reduce((a, b) => a + scene.getPlayerParty()[0].heldItemManager.getStack(b), 0); const rogueCountAfter = leadItemsAfter - .filter(m => m.type.tier === RewardTier.ROGUE) - .reduce((a, b) => a + b.stackCount, 0); + .filter(m => getHeldItemTier(m) === RewardTier.ROGUE) + .reduce((a, b) => a + scene.getPlayerParty()[0].heldItemManager.getStack(b), 0); expect(ultraCountAfter).toBe(13); expect(rogueCountAfter).toBe(7); const secondItemsAfter = scene.getPlayerParty()[1].getHeldItems(); expect(secondItemsAfter.length).toBe(1); - expect(secondItemsAfter[0].type.id).toBe("SOUL_DEW"); - expect(secondItemsAfter[0]?.stackCount).toBe(5); + expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SOUL_DEW)).toBe(5); }); it("should leave encounter without battle", async () => { @@ -381,15 +361,3 @@ describe("Clowning Around - Mystery Encounter", () => { }); }); }); - -async function addItemToPokemon( - scene: BattleScene, - pokemon: Pokemon, - stackCount: number, - itemType: PokemonHeldItemModifierType, -) { - const itemMod = itemType.newModifier(pokemon) as PokemonHeldItemModifier; - itemMod.stackCount = stackCount; - scene.addModifier(itemMod, true, false, false, true); - await scene.updateItems(true); -} diff --git a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index 78ccec4f1a1..f2d150a9463 100644 --- a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -21,7 +21,7 @@ import { PokemonMove } from "#app/data/moves/pokemon-move"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; const namespace = "mysteryEncounters/dancingLessons"; @@ -126,9 +126,9 @@ describe("Dancing Lessons - Mystery Encounter", () => { partyLead.calculateStats(); await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts index 9eeb31ba0da..14b7cb3e8d1 100644 --- a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts +++ b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts @@ -13,7 +13,7 @@ import { DepartmentStoreSaleEncounter } from "#app/data/mystery-encounters/encou import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; const namespace = "mysteryEncounters/departmentStoreSale"; const defaultParty = [SpeciesId.LAPRAS, SpeciesId.GENGAR, SpeciesId.ABRA]; @@ -93,8 +93,8 @@ describe("Department Store Sale - Mystery Encounter", () => { it("should have shop with only TMs", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await runMysteryEncounterToEnd(game, 1); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -130,8 +130,8 @@ describe("Department Store Sale - Mystery Encounter", () => { it("should have shop with only Vitamins", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await runMysteryEncounterToEnd(game, 2); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -170,8 +170,8 @@ describe("Department Store Sale - Mystery Encounter", () => { it("should have shop with only X Items", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await runMysteryEncounterToEnd(game, 3); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -210,8 +210,8 @@ describe("Department Store Sale - Mystery Encounter", () => { it("should have shop with only Pokeballs", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await runMysteryEncounterToEnd(game, 4); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/mystery-encounter/encounters/field-trip-encounter.test.ts b/test/mystery-encounter/encounters/field-trip-encounter.test.ts index a8bcc39c4eb..af3e9454261 100644 --- a/test/mystery-encounter/encounters/field-trip-encounter.test.ts +++ b/test/mystery-encounter/encounters/field-trip-encounter.test.ts @@ -11,7 +11,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; import { FieldTripEncounter } from "#app/data/mystery-encounters/encounters/field-trip-encounter"; import { MoveId } from "#enums/move-id"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { UiMode } from "#enums/ui-mode"; import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import i18next from "i18next"; @@ -85,7 +85,7 @@ describe("Field Trip - Mystery Encounter", () => { it("Should give no reward on incorrect option", async () => { await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1, optionNo: 2 }); - await game.phaseInterceptor.to(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -97,7 +97,7 @@ describe("Field Trip - Mystery Encounter", () => { it("Should give proper rewards on correct Physical move option", async () => { await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1, optionNo: 1 }); - await game.phaseInterceptor.to(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -146,7 +146,7 @@ describe("Field Trip - Mystery Encounter", () => { it("Should give no reward on incorrect option", async () => { await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); - await game.phaseInterceptor.to(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -158,7 +158,7 @@ describe("Field Trip - Mystery Encounter", () => { it("Should give proper rewards on correct Special move option", async () => { await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 2 }); - await game.phaseInterceptor.to(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -207,7 +207,7 @@ describe("Field Trip - Mystery Encounter", () => { it("Should give no reward on incorrect option", async () => { await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); - await game.phaseInterceptor.to(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -220,7 +220,7 @@ describe("Field Trip - Mystery Encounter", () => { vi.spyOn(i18next, "t"); await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 3 }); - await game.phaseInterceptor.to(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index 54153246327..700f500a169 100644 --- a/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -24,7 +24,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { BattlerTagType } from "#enums/battler-tag-type"; import { AbilityId } from "#enums/ability-id"; import i18next from "i18next"; @@ -176,8 +176,8 @@ describe("Fiery Fallout - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); const hasAttackBooster = scene .getPlayerParty()[0] @@ -262,8 +262,8 @@ describe("Fiery Fallout - Mystery Encounter", () => { it("should give attack type boosting item to lead pokemon", async () => { await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); await runMysteryEncounterToEnd(game, 3); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); const hasAttackBooster = scene .getPlayerParty()[0] diff --git a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts index 020fed90361..6a7b22348a5 100644 --- a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts +++ b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts @@ -21,7 +21,7 @@ import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encount import { FightOrFlightEncounter } from "#app/data/mystery-encounters/encounters/fight-or-flight-encounter"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { CommandPhase } from "#app/phases/command-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; const namespace = "mysteryEncounters/fightOrFlight"; const defaultParty = [SpeciesId.LAPRAS, SpeciesId.GENGAR, SpeciesId.ABRA]; @@ -122,9 +122,9 @@ describe("Fight or Flight - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -182,9 +182,9 @@ describe("Fight or Flight - Mystery Encounter", () => { const item = game.scene.currentBattle.mysteryEncounter!.misc; await runMysteryEncounterToEnd(game, 2); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts index f25528e0566..90c71136338 100644 --- a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts +++ b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts @@ -19,7 +19,7 @@ import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { Nature } from "#enums/nature"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { CommandPhase } from "#app/phases/command-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fun-and-games-encounter"; import { MoveId } from "#enums/move-id"; import { Command } from "#enums/command"; @@ -162,10 +162,10 @@ describe("Fun And Games! - Mystery Encounter", () => { // Turn 3 (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL); - await game.phaseInterceptor.to(SelectModifierPhase, false); + await game.phaseInterceptor.to(SelectRewardPhase, false); // Rewards - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); }); it("should have no items in rewards if Wubboffet doesn't take enough damage", async () => { @@ -181,11 +181,11 @@ describe("Fun And Games! - Mystery Encounter", () => { // Skip minigame scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0; (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL); - await game.phaseInterceptor.to(SelectModifierPhase, false); + await game.phaseInterceptor.to(SelectRewardPhase, false); // Rewards - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -210,11 +210,11 @@ describe("Fun And Games! - Mystery Encounter", () => { wobbuffet.hp = Math.floor(0.2 * wobbuffet.getMaxHp()); scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0; (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL); - await game.phaseInterceptor.to(SelectModifierPhase, false); + await game.phaseInterceptor.to(SelectRewardPhase, false); // Rewards - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -240,11 +240,11 @@ describe("Fun And Games! - Mystery Encounter", () => { wobbuffet.hp = Math.floor(0.1 * wobbuffet.getMaxHp()); scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0; (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL); - await game.phaseInterceptor.to(SelectModifierPhase, false); + await game.phaseInterceptor.to(SelectRewardPhase, false); // Rewards - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -270,11 +270,11 @@ describe("Fun And Games! - Mystery Encounter", () => { wobbuffet.hp = 1; scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0; (game.scene.phaseManager.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, MoveUseMode.NORMAL); - await game.phaseInterceptor.to(SelectModifierPhase, false); + await game.phaseInterceptor.to(SelectRewardPhase, false); // Rewards - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index fb7e770e7a3..fecc68e60ce 100644 --- a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -11,7 +11,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter"; import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { UiMode } from "#enums/ui-mode"; import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { RewardTier } from "#enums/reward-tier"; @@ -220,8 +220,8 @@ describe("Global Trade System - Mystery Encounter", () => { await scene.updateItems(true); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index 5f35d3732ac..752b8aee1e4 100644 --- a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -24,7 +24,7 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { CommandPhase } from "#app/phases/command-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; const namespace = "mysteryEncounters/mysteriousChallengers"; const defaultParty = [SpeciesId.LAPRAS, SpeciesId.GENGAR, SpeciesId.ABRA]; @@ -162,9 +162,9 @@ describe("Mysterious Challengers - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -206,9 +206,9 @@ describe("Mysterious Challengers - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty); await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -263,9 +263,9 @@ describe("Mysterious Challengers - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty); await runMysteryEncounterToEnd(game, 3, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index f5ee301b021..43257b1e922 100644 --- a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -7,7 +7,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { CommandPhase } from "#app/phases/command-phase"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import GameManager from "#test/testUtils/gameManager"; import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { UiMode } from "#enums/ui-mode"; @@ -299,9 +299,9 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, defaultParty); await runMysteryEncounterToEnd(game, 3, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts b/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts index c9d6f540191..dc8f5d7178e 100644 --- a/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts @@ -16,7 +16,7 @@ import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { CommandPhase } from "#app/phases/command-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter"; import { TrainerType } from "#enums/trainer-type"; import { EggTier } from "#enums/egg-type"; @@ -176,8 +176,8 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); const eggsAfter = scene.gameData.eggs; const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon1CommonEggs; @@ -261,8 +261,8 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); const eggsAfter = scene.gameData.eggs; const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon2CommonEggs; @@ -343,8 +343,8 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 3, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); const eggsAfter = scene.gameData.eggs; const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon3CommonEggs; diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index 6ad52049bac..a4cda7aa3e8 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -25,7 +25,7 @@ import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils import { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { AbilityId } from "#enums/ability-id"; import { applyHeldItems } from "#app/items/all-held-items"; import { HeldItemEffect } from "#app/items/held-item"; @@ -222,9 +222,9 @@ describe("The Strong Stuff - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty); await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts index e1167abf2d8..eabe8af0d78 100644 --- a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts @@ -22,7 +22,7 @@ import { TheWinstrateChallengeEncounter } from "#app/data/mystery-encounters/enc import { Status } from "#app/data/status-effect"; import { MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; import { CommandPhase } from "#app/phases/command-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { VictoryPhase } from "#app/phases/victory-phase"; import { StatusEffect } from "#enums/status-effect"; @@ -295,9 +295,9 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { // Should have Macho Brace in the rewards await skipBattleToNextBattle(game, true); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -338,8 +338,8 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { it("should have a Rarer Candy in the rewards", async () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty); await runMysteryEncounterToEnd(game, 2); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 55de399710e..a6721a946e1 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -12,7 +12,7 @@ import { PokemonMove } from "#app/data/moves/pokemon-move"; import { RewardTier } from "#enums/reward-tier"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { UiMode } from "#enums/ui-mode"; import * as Utils from "#app/utils/common"; @@ -140,8 +140,8 @@ describe("Trash to Treasure - Mystery Encounter", () => { it("should give 2 Leftovers, 1 Shell Bell, and Black Sludge", async () => { await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty); await runMysteryEncounterToEnd(game, 1); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); expect(scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.LEFTOVERS)).toBe(2); @@ -204,9 +204,9 @@ describe("Trash to Treasure - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty); await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index 49343122a69..1d15443867e 100644 --- a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -17,7 +17,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; import { WeirdDreamEncounter } from "#app/data/mystery-encounters/encounters/weird-dream-encounter"; import * as EncounterTransformationSequence from "#app/data/mystery-encounters/utils/encounter-transformation-sequence"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { CommandPhase } from "#app/phases/command-phase"; import { RewardTier } from "#enums/reward-tier"; @@ -116,8 +116,8 @@ describe("Weird Dream - Mystery Encounter", () => { const bstsPrior = pokemonPrior.map(species => species.getSpeciesForm().getBaseStatTotal()); await runMysteryEncounterToEnd(game, 1); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); const pokemonAfter = scene.getPlayerParty(); const bstsAfter = pokemonAfter.map(pokemon => pokemon.getSpeciesForm().getBaseStatTotal()); @@ -139,9 +139,9 @@ describe("Weird Dream - Mystery Encounter", () => { it("should have 1 Memory Mushroom, 5 Rogue Balls, and 3 Mints in rewards", async () => { await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); await runMysteryEncounterToEnd(game, 1); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -195,9 +195,9 @@ describe("Weird Dream - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); - await game.phaseInterceptor.to(SelectModifierPhase, false); - expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase, false); + expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/phases/select-modifier-phase.test.ts b/test/phases/select-modifier-phase.test.ts index 619ec89a2a1..74629f7de0f 100644 --- a/test/phases/select-modifier-phase.test.ts +++ b/test/phases/select-modifier-phase.test.ts @@ -5,7 +5,7 @@ import { RewardTier } from "#enums/reward-tier"; import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { ModifierTypeOption } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { UiMode } from "#enums/ui-mode"; import { shiftCharCodes } from "#app/utils/common"; @@ -19,7 +19,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { TrainerItemId } from "#enums/trainer-item-id"; -describe("SelectModifierPhase", () => { +describe("SelectRewardPhase", () => { let phaserGame: Phaser.Game; let game: GameManager; let scene: BattleScene; @@ -47,9 +47,9 @@ describe("SelectModifierPhase", () => { it("should start a select modifier phase", async () => { initSceneWithoutEncounterPhase(scene, [SpeciesId.ABRA, SpeciesId.VOLCARONA]); - const selectModifierPhase = new SelectModifierPhase(); + const selectModifierPhase = new SelectRewardPhase(); scene.phaseManager.unshiftPhase(selectModifierPhase); - await game.phaseInterceptor.to(SelectModifierPhase); + await game.phaseInterceptor.to(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); }); @@ -57,7 +57,7 @@ describe("SelectModifierPhase", () => { it("should generate random modifiers", async () => { await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); game.move.select(MoveId.FISSURE); - await game.phaseInterceptor.to("SelectModifierPhase"); + await game.phaseInterceptor.to("SelectRewardPhase"); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -74,10 +74,10 @@ describe("SelectModifierPhase", () => { new ModifierTypeOption(modifierTypes.REVIVE(), 0, 1000), ]; - const selectModifierPhase1 = new SelectModifierPhase(0, undefined, { + const selectModifierPhase1 = new SelectRewardPhase(0, undefined, { guaranteedModifierTypeOptions: options, }); - const selectModifierPhase2 = new SelectModifierPhase(0, undefined, { + const selectModifierPhase2 = new SelectRewardPhase(0, undefined, { guaranteedModifierTypeOptions: options, rerollMultiplier: 2, }); @@ -93,10 +93,10 @@ describe("SelectModifierPhase", () => { scene.shopCursorTarget = 0; game.move.select(MoveId.FISSURE); - await game.phaseInterceptor.to("SelectModifierPhase"); + await game.phaseInterceptor.to("SelectRewardPhase"); // TODO: nagivate the ui to reroll somehow - //const smphase = scene.phaseManager.getCurrentPhase() as SelectModifierPhase; + //const smphase = scene.phaseManager.getCurrentPhase() as SelectRewardPhase; expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, @@ -123,7 +123,7 @@ describe("SelectModifierPhase", () => { }); game.move.select(MoveId.FISSURE); - await game.phaseInterceptor.to("SelectModifierPhase"); + await game.phaseInterceptor.to("SelectRewardPhase"); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -163,10 +163,10 @@ describe("SelectModifierPhase", () => { modifierTypes.GOLDEN_PUNCH, ], }; - const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); + const selectModifierPhase = new SelectRewardPhase(0, undefined, customModifiers); scene.phaseManager.unshiftPhase(selectModifierPhase); game.move.select(MoveId.SPLASH); - await game.phaseInterceptor.to("SelectModifierPhase"); + await game.phaseInterceptor.to("SelectRewardPhase"); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -200,10 +200,10 @@ describe("SelectModifierPhase", () => { } scene.getPlayerParty().push(pokemon, pokemon, pokemon, pokemon, pokemon, pokemon); - const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); + const selectModifierPhase = new SelectRewardPhase(0, undefined, customModifiers); scene.phaseManager.unshiftPhase(selectModifierPhase); game.move.select(MoveId.SPLASH); - await game.phaseInterceptor.to("SelectModifierPhase"); + await game.phaseInterceptor.to("SelectRewardPhase"); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -239,10 +239,10 @@ describe("SelectModifierPhase", () => { guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM, modifierTypes.TM_COMMON], guaranteedModifierTiers: [RewardTier.MASTER, RewardTier.MASTER], }; - const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); + const selectModifierPhase = new SelectRewardPhase(0, undefined, customModifiers); scene.phaseManager.unshiftPhase(selectModifierPhase); game.move.select(MoveId.SPLASH); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( @@ -263,10 +263,10 @@ describe("SelectModifierPhase", () => { guaranteedModifierTiers: [RewardTier.MASTER], fillRemaining: true, }; - const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); + const selectModifierPhase = new SelectRewardPhase(0, undefined, customModifiers); scene.phaseManager.unshiftPhase(selectModifierPhase); game.move.select(MoveId.SPLASH); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.run(SelectRewardPhase); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find( diff --git a/test/testUtils/gameManager.ts b/test/testUtils/gameManager.ts index 7230a137982..6ee9b381d2c 100644 --- a/test/testUtils/gameManager.ts +++ b/test/testUtils/gameManager.ts @@ -311,10 +311,10 @@ export default class GameManager { } } - /** Queue up button presses to skip taking an item on the next {@linkcode SelectModifierPhase} */ + /** Queue up button presses to skip taking an item on the next {@linkcode SelectRewardPhase} */ doSelectModifier() { this.onNextPrompt( - "SelectModifierPhase", + "SelectRewardPhase", UiMode.MODIFIER_SELECT, () => { const handler = this.scene.ui.getHandler() as RewardSelectUiHandler; @@ -328,7 +328,7 @@ export default class GameManager { ); this.onNextPrompt( - "SelectModifierPhase", + "SelectRewardPhase", UiMode.CONFIRM, () => { const handler = this.scene.ui.getHandler() as RewardSelectUiHandler; @@ -369,7 +369,7 @@ export default class GameManager { } /** - * Queue up button presses to skip taking an item on the next {@linkcode SelectModifierPhase}, + * Queue up button presses to skip taking an item on the next {@linkcode SelectRewardPhase}, * and then transition to the next {@linkcode CommandPhase}. */ async toNextWave() { diff --git a/test/testUtils/helpers/modifiersHelper.ts b/test/testUtils/helpers/modifiersHelper.ts index 22500c87906..3a070b57ef9 100644 --- a/test/testUtils/helpers/modifiersHelper.ts +++ b/test/testUtils/helpers/modifiersHelper.ts @@ -7,7 +7,7 @@ export class ModifierHelper extends GameManagerHelper { /** * Adds a Modifier to the list of modifiers to check for. * - * Note that all modifiers are updated during the start of `SelectModifierPhase`. + * Note that all modifiers are updated during the start of `SelectRewardPhase`. * @param modifier The Modifier to add. * @returns `this` */ @@ -23,7 +23,7 @@ export class ModifierHelper extends GameManagerHelper { * * If the item is *not* in the Modifier Pool, will return `false`. * - * If a `SelectModifierPhase` has not occurred, and we do not know if the item is in the Modifier Pool or not, will return `undefined`. + * If a `SelectRewardPhase` has not occurred, and we do not know if the item is in the Modifier Pool or not, will return `undefined`. * @param modifier * @returns */ @@ -34,7 +34,7 @@ export class ModifierHelper extends GameManagerHelper { /** * `expect`s a Modifier `toBeTruthy` (in the Modifier Pool) or `Falsy` (unobtainable on this floor). Use during a test. * - * Note that if a `SelectModifierPhase` has not been run yet, these values will be `undefined`, and the check will fail. + * Note that if a `SelectRewardPhase` has not been run yet, these values will be `undefined`, and the check will fail. * @param modifier The modifier to check. * @param expectToBePreset Whether the Modifier should be in the Modifier Pool. Set to `false` to expect it to be absent instead. * @returns `this` diff --git a/test/testUtils/phaseInterceptor.ts b/test/testUtils/phaseInterceptor.ts index 9e0f5774eea..395f6293305 100644 --- a/test/testUtils/phaseInterceptor.ts +++ b/test/testUtils/phaseInterceptor.ts @@ -26,7 +26,7 @@ import { NextEncounterPhase } from "#app/phases/next-encounter-phase"; import { PostSummonPhase } from "#app/phases/post-summon-phase"; import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; import { SelectGenderPhase } from "#app/phases/select-gender-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import { SelectStarterPhase } from "#app/phases/select-starter-phase"; import { SelectTargetPhase } from "#app/phases/select-target-phase"; import { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase"; @@ -122,7 +122,7 @@ export default class PhaseInterceptor { [TurnEndPhase, this.startPhase], [BattleEndPhase, this.startPhase], [EggLapsePhase, this.startPhase], - [SelectModifierPhase, this.startPhase], + [SelectRewardPhase, this.startPhase], [NextEncounterPhase, this.startPhase], [NewBattlePhase, this.startPhase], [VictoryPhase, this.startPhase], @@ -163,7 +163,7 @@ export default class PhaseInterceptor { TitlePhase, SelectGenderPhase, CommandPhase, - SelectModifierPhase, + SelectRewardPhase, MysteryEncounterPhase, PostMysteryEncounterPhase, ]; diff --git a/test/ui/transfer-item.test.ts b/test/ui/transfer-item.test.ts index d9bd4b76f20..3788feaf0b6 100644 --- a/test/ui/transfer-item.test.ts +++ b/test/ui/transfer-item.test.ts @@ -43,7 +43,7 @@ describe("UI - Transfer Items", () => { game.move.select(MoveId.DRAGON_CLAW); - game.onNextPrompt("SelectModifierPhase", UiMode.MODIFIER_SELECT, () => { + game.onNextPrompt("SelectRewardPhase", UiMode.MODIFIER_SELECT, () => { expect(game.scene.ui.getHandler()).toBeInstanceOf(RewardSelectUiHandler); const handler = game.scene.ui.getHandler() as RewardSelectUiHandler; @@ -57,7 +57,7 @@ describe("UI - Transfer Items", () => { }); it("check red tint for held item limit in transfer menu", async () => { - game.onNextPrompt("SelectModifierPhase", UiMode.PARTY, () => { + game.onNextPrompt("SelectRewardPhase", UiMode.PARTY, () => { expect(game.scene.ui.getHandler()).toBeInstanceOf(PartyUiHandler); const handler = game.scene.ui.getHandler() as PartyUiHandler; @@ -76,11 +76,11 @@ describe("UI - Transfer Items", () => { game.phaseInterceptor.unlock(); }); - await game.phaseInterceptor.to("SelectModifierPhase"); + await game.phaseInterceptor.to("SelectRewardPhase"); }); it("check transfer option for pokemon to transfer to", async () => { - game.onNextPrompt("SelectModifierPhase", UiMode.PARTY, () => { + game.onNextPrompt("SelectRewardPhase", UiMode.PARTY, () => { expect(game.scene.ui.getHandler()).toBeInstanceOf(PartyUiHandler); const handler = game.scene.ui.getHandler() as PartyUiHandler; @@ -97,6 +97,6 @@ describe("UI - Transfer Items", () => { game.phaseInterceptor.unlock(); }); - await game.phaseInterceptor.to("SelectModifierPhase"); + await game.phaseInterceptor.to("SelectRewardPhase"); }); }); From 9925b0c35881bef190089dac70bb95c68f0de6b6 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 12 Jul 2025 20:36:13 +0200 Subject: [PATCH 20/39] Some minor fixes --- src/data/balance/pokemon-evolutions.ts | 7 ++----- .../encounters/dancing-lessons-encounter.ts | 2 +- src/items/apply-trainer-items.ts | 2 +- src/items/held-item-pool.ts | 2 +- src/items/held-items/item-steal.ts | 2 +- src/items/trainer-item-pool.ts | 2 +- src/phases/berry-phase.ts | 3 ++- 7 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index fb3d0e119bf..cb30cc28bde 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -13,7 +13,7 @@ import { SpeciesFormKey } from "#enums/species-form-key"; import { TimeOfDay } from "#enums/time-of-day"; import { speciesStarterCosts } from "./starters"; import i18next from "i18next"; -import { allMoves } from "#app/data/data-lists"; +import { allHeldItems, allMoves } from "#app/data/data-lists"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { HeldItemId } from "#enums/held-item-id"; @@ -177,10 +177,7 @@ export class SpeciesEvolutionCondition { case EvoCondKey.PARTY_TYPE: return globalScene.getPlayerParty().some(p => p.getTypes(false, false, true).includes(cond.pkmnType)) case EvoCondKey.EVO_TREASURE_TRACKER: - return pokemon.getHeldItems().some(m => - m.is("EvoTrackerModifier") && - m.getStackCount() + pokemon.getPersistentTreasureCount() >= cond.value - ); + return allHeldItems[HeldItemId.GIMMIGHOUL_EVO_TRACKER].getStackCount(pokemon) >= cond.value; case EvoCondKey.GENDER: return pokemon.gender === cond.gender; case EvoCondKey.SHEDINJA: // Shedinja cannot be evolved into directly diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index cf24d1dd7e0..b0d8a197287 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -154,7 +154,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder } const oricorioData = new PokemonData(enemyPokemon); - const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData); + const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, [], oricorioData); // Adds a real Pokemon sprite to the field (required for the animation) for (const enemyPokemon of globalScene.getEnemyParty()) { diff --git a/src/items/apply-trainer-items.ts b/src/items/apply-trainer-items.ts index 7135edd0ff9..898c4067a68 100644 --- a/src/items/apply-trainer-items.ts +++ b/src/items/apply-trainer-items.ts @@ -1,4 +1,4 @@ -import { allTrainerItems } from "./all-trainer-items"; +import { allTrainerItems } from "#app/data/data-lists"; import { type BooleanHolderParams, type NumberHolderParams, diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index 1cdd95ae744..9ad26e077af 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -7,7 +7,7 @@ import { HeldItemPoolType } from "#enums/modifier-pool-type"; import type { PokemonType } from "#enums/pokemon-type"; import { RewardTier } from "#enums/reward-tier"; import { PERMANENT_STATS } from "#enums/stat"; -import { allHeldItems } from "./all-held-items"; +import { allHeldItems } from "#app/data/data-lists"; import { type HeldItemConfiguration, type HeldItemPool, diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts index f9dce8cc75b..02b46750ebd 100644 --- a/src/items/held-items/item-steal.ts +++ b/src/items/held-items/item-steal.ts @@ -4,7 +4,7 @@ import type { HeldItemId } from "#enums/held-item-id"; import i18next from "i18next"; import { HeldItemEffect, HeldItem } from "../held-item"; import { getPokemonNameWithAffix } from "#app/messages"; -import { allHeldItems } from "../all-held-items"; +import { allHeldItems } from "#app/data/data-lists"; import { globalScene } from "#app/global-scene"; export interface ItemStealParams { diff --git a/src/items/trainer-item-pool.ts b/src/items/trainer-item-pool.ts index 86a87925325..72e38d4750b 100644 --- a/src/items/trainer-item-pool.ts +++ b/src/items/trainer-item-pool.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { isNullOrUndefined, pickWeightedIndex } from "#app/utils/common"; import { RewardTier } from "#enums/reward-tier"; import type { TrainerItemId } from "#enums/trainer-item-id"; -import { allTrainerItems } from "./all-trainer-items"; +import { allTrainerItems } from "#app/data/data-lists"; import type { TrainerItemPool, TrainerItemTieredPool } from "./trainer-item-data-types"; import type { TrainerItemManager } from "./trainer-item-manager"; diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index 1fe547d7469..524cfa5c698 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -6,7 +6,8 @@ import { BooleanHolder } from "#app/utils/common"; import { FieldPhase } from "./field-phase"; import { globalScene } from "#app/global-scene"; import type Pokemon from "#app/field/pokemon"; -import { allHeldItems, applyHeldItems } from "#app/items/all-held-items"; +import { applyHeldItems } from "#app/items/all-held-items"; +import { allHeldItems } from "#app/data/data-lists"; import { HeldItemEffect } from "#app/items/held-item"; import { HeldItemCategoryId, isItemInCategory } from "#enums/held-item-id"; From 414a6b10ead686c9132ccf7aca0e2fb71c178ceb Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 13 Jul 2025 00:42:29 +0200 Subject: [PATCH 21/39] Cleaned up more tests --- test/battle/battle.test.ts | 4 +-- test/battle/damage_calculation.test.ts | 7 ++--- test/daily_mode.test.ts | 4 +-- test/final_boss.test.ts | 10 +++---- test/items/multi_lens.test.ts | 13 +++++----- test/items/reviver_seed.test.ts | 11 ++++---- test/items/temp_stat_stage_booster.test.ts | 26 +++++-------------- test/items/thick_club.test.ts | 4 +-- test/moves/beak_blast.test.ts | 3 ++- test/moves/ceaseless_edge.test.ts | 5 ++-- test/moves/destiny_bond.test.ts | 9 +++---- test/moves/dragon_tail.test.ts | 5 ++-- test/moves/fell_stinger.test.ts | 3 ++- test/moves/fusion_flare_bolt.test.ts | 2 -- test/moves/glaive_rush.test.ts | 3 ++- test/moves/heal_block.test.ts | 3 ++- test/moves/safeguard.test.ts | 3 ++- test/moves/substitute.test.ts | 8 +++--- ...an-offer-you-cant-refuse-encounter.test.ts | 7 ++--- 19 files changed, 56 insertions(+), 74 deletions(-) diff --git a/test/battle/battle.test.ts b/test/battle/battle.test.ts index ff658a4a0b1..75c84e82da4 100644 --- a/test/battle/battle.test.ts +++ b/test/battle/battle.test.ts @@ -1,5 +1,4 @@ import { allSpecies } from "#app/data/data-lists"; -import { Stat } from "#enums/stat"; import { getGameMode } from "#app/game-mode"; import { GameModes } from "#enums/game-modes"; import { BattleEndPhase } from "#app/phases/battle-end-phase"; @@ -25,6 +24,7 @@ import { SpeciesId } from "#enums/species-id"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { BiomeId } from "#enums/biome-id"; +import { TrainerItemId } from "#enums/trainer-item-id"; describe("Test Battle Phase", () => { let phaserGame: Phaser.Game; @@ -312,7 +312,7 @@ describe("Test Battle Phase", () => { .startingLevel(100) .moveset([moveToUse]) .enemyMoveset(MoveId.SPLASH) - .startingHeldItems([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }]); + .startingTrainerItems([{ entry: TrainerItemId.X_ACCURACY }]); await game.classicMode.startBattle(); game.scene.getPlayerPokemon()!.hp = 1; diff --git a/test/battle/damage_calculation.test.ts b/test/battle/damage_calculation.test.ts index 19cdf6b9237..d78cdfceae8 100644 --- a/test/battle/damage_calculation.test.ts +++ b/test/battle/damage_calculation.test.ts @@ -1,10 +1,9 @@ import { allMoves } from "#app/data/data-lists"; -import type { EnemyPersistentModifier } from "#app/modifier/modifier"; -import { modifierTypes } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; +import { TrainerItemId } from "#enums/trainer-item-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; @@ -72,9 +71,7 @@ describe("Battle Mechanics - Damage Calculation", () => { await game.classicMode.startBattle([SpeciesId.SHUCKLE]); - const dmg_redux_modifier = modifierTypes.ENEMY_DAMAGE_REDUCTION().newModifier() as EnemyPersistentModifier; - dmg_redux_modifier.stackCount = 1000; - await game.scene.addEnemyModifier(modifierTypes.ENEMY_DAMAGE_REDUCTION().newModifier() as EnemyPersistentModifier); + game.scene.enemyTrainerItems.add(TrainerItemId.ENEMY_DAMAGE_REDUCTION, 1000); const aggron = game.scene.getEnemyPokemon()!; diff --git a/test/daily_mode.test.ts b/test/daily_mode.test.ts index 5933bf236cd..2bb413a2a01 100644 --- a/test/daily_mode.test.ts +++ b/test/daily_mode.test.ts @@ -1,12 +1,12 @@ import { BiomeId } from "#enums/biome-id"; import { MoveId } from "#enums/move-id"; -import { MapModifier } from "#app/modifier/modifier"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import GameManager from "#test/testUtils/gameManager"; +import { TrainerItemId } from "#enums/trainer-item-id"; //const TIMEOUT = 20 * 1000; @@ -38,7 +38,7 @@ describe("Daily Mode", () => { expect(pkm.level).toBe(20); expect(pkm.moveset.length).toBeGreaterThan(0); }); - expect(game.scene.getModifiers(MapModifier).length).toBeGreaterThan(0); + expect(game.scene.trainerItems.getStack(TrainerItemId.MAP)).toBe(1); }); }); diff --git a/test/final_boss.test.ts b/test/final_boss.test.ts index 071f83285e7..44c7dbae461 100644 --- a/test/final_boss.test.ts +++ b/test/final_boss.test.ts @@ -1,5 +1,4 @@ import { GameModes } from "#enums/game-modes"; -import { TurnHeldItemTransferModifier } from "#app/modifier/modifier"; import { AbilityId } from "#enums/ability-id"; import { BiomeId } from "#enums/biome-id"; import { MoveId } from "#enums/move-id"; @@ -7,6 +6,7 @@ import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/testUtils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; const FinalWave = { Classic: 200, @@ -93,9 +93,7 @@ describe("Final Boss", () => { expect(eternatus.formIndex).toBe(1); expect(eternatus.bossSegments).toBe(5); expect(eternatus.bossSegmentIndex).toBe(4); - const miniBlackHole = eternatus.getHeldItems().find(m => m instanceof TurnHeldItemTransferModifier); - expect(miniBlackHole).toBeDefined(); - expect(miniBlackHole?.stackCount).toBe(1); + expect(eternatus.heldItemManager.getStack(HeldItemId.MINI_BLACK_HOLE)).toBe(1); }); it("should change form on status damage down to last boss fragment", async () => { @@ -136,8 +134,6 @@ describe("Final Boss", () => { expect(eternatus.formIndex).toBe(1); expect(eternatus.bossSegments).toBe(5); expect(eternatus.bossSegmentIndex).toBe(4); - const miniBlackHole = eternatus.getHeldItems().find(m => m instanceof TurnHeldItemTransferModifier); - expect(miniBlackHole).toBeDefined(); - expect(miniBlackHole?.stackCount).toBe(1); + expect(eternatus.heldItemManager.getStack(HeldItemId.MINI_BLACK_HOLE)).toBe(1); }); }); diff --git a/test/items/multi_lens.test.ts b/test/items/multi_lens.test.ts index 5d4732a8bcd..7337922a8f1 100644 --- a/test/items/multi_lens.test.ts +++ b/test/items/multi_lens.test.ts @@ -6,6 +6,7 @@ import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; describe("Items - Multi Lens", () => { let phaserGame: Phaser.Game; @@ -26,7 +27,7 @@ describe("Items - Multi Lens", () => { game.override .moveset([MoveId.TACKLE, MoveId.TRAILBLAZE, MoveId.TACHYON_CUTTER, MoveId.FUTURE_SIGHT]) .ability(AbilityId.BALL_FETCH) - .startingHeldItems([{ name: "MULTI_LENS" }]) + .startingHeldItems([{ entry: HeldItemId.MULTI_LENS }]) .battleStyle("single") .criticalHits(false) .enemySpecies(SpeciesId.SNORLAX) @@ -42,7 +43,7 @@ describe("Items - Multi Lens", () => { ])( "$stackCount count: should deal {$firstHitDamage}x damage on the first hit, then hit $stackCount times for 0.25x", async ({ stackCount, firstHitDamage }) => { - game.override.startingHeldItems([{ name: "MULTI_LENS", count: stackCount }]); + game.override.startingHeldItems([{ entry: HeldItemId.MULTI_LENS, count: stackCount }]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -116,7 +117,7 @@ describe("Items - Multi Lens", () => { }); it("should enhance fixed-damage moves while also applying damage reduction", async () => { - game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]).moveset(MoveId.SEISMIC_TOSS); + game.override.startingHeldItems([{ entry: HeldItemId.MULTI_LENS }]).moveset(MoveId.SEISMIC_TOSS); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -137,7 +138,7 @@ describe("Items - Multi Lens", () => { it("should result in correct damage for hp% attacks with 1 lens", async () => { game.override - .startingHeldItems([{ name: "MULTI_LENS", count: 1 }]) + .startingHeldItems([{ entry: HeldItemId.MULTI_LENS }]) .moveset(MoveId.SUPER_FANG) .ability(AbilityId.COMPOUND_EYES) .enemyLevel(1000) @@ -155,7 +156,7 @@ describe("Items - Multi Lens", () => { it("should result in correct damage for hp% attacks with 2 lenses", async () => { game.override - .startingHeldItems([{ name: "MULTI_LENS", count: 2 }]) + .startingHeldItems([{ entry: HeldItemId.MULTI_LENS, count: 2 }]) .moveset(MoveId.SUPER_FANG) .ability(AbilityId.COMPOUND_EYES) .enemyMoveset(MoveId.SPLASH) @@ -174,7 +175,7 @@ describe("Items - Multi Lens", () => { it("should result in correct damage for hp% attacks with 2 lenses + Parental Bond", async () => { game.override - .startingHeldItems([{ name: "MULTI_LENS", count: 2 }]) + .startingHeldItems([{ entry: HeldItemId.MULTI_LENS, count: 2 }]) .moveset(MoveId.SUPER_FANG) .ability(AbilityId.PARENTAL_BOND) .passiveAbility(AbilityId.COMPOUND_EYES) diff --git a/test/items/reviver_seed.test.ts b/test/items/reviver_seed.test.ts index d977189789c..4ac47ae320b 100644 --- a/test/items/reviver_seed.test.ts +++ b/test/items/reviver_seed.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#enums/battler-index"; -import { allMoves } from "#app/data/data-lists"; +import { allHeldItems, allMoves } from "#app/data/data-lists"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; @@ -8,6 +8,7 @@ import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { HeldItemId } from "#enums/held-item-id"; +import type { InstantReviveHeldItem } from "#app/items/held-items/instant-revive"; describe("Items - Reviver Seed", () => { let phaserGame: Phaser.Game; @@ -54,7 +55,7 @@ describe("Items - Reviver Seed", () => { const player = game.scene.getPlayerPokemon()!; player.damageAndUpdate(player.hp - 1); - const reviverSeed = player.getHeldItems()[0] as PokemonInstantReviveModifier; + const reviverSeed = allHeldItems[HeldItemId.REVIVER_SEED] as InstantReviveHeldItem; vi.spyOn(reviverSeed, "apply"); game.move.select(MoveId.TACKLE); @@ -70,7 +71,7 @@ describe("Items - Reviver Seed", () => { player.damageAndUpdate(player.hp - 1); player.addTag(BattlerTagType.CONFUSED, 3); - const reviverSeed = player.getHeldItems()[0] as PokemonInstantReviveModifier; + const reviverSeed = allHeldItems[HeldItemId.REVIVER_SEED] as InstantReviveHeldItem; vi.spyOn(reviverSeed, "apply"); vi.spyOn(player, "randBattleSeedInt").mockReturnValue(0); // Force confusion self-hit @@ -122,8 +123,8 @@ describe("Items - Reviver Seed", () => { const player = game.scene.getPlayerPokemon()!; player.damageAndUpdate(player.hp - 1); - const playerSeed = player.getHeldItems()[0] as PokemonInstantReviveModifier; - vi.spyOn(playerSeed, "apply"); + const reviverSeed = allHeldItems[HeldItemId.REVIVER_SEED] as InstantReviveHeldItem; + vi.spyOn(reviverSeed, "apply"); game.move.select(move); await game.phaseInterceptor.to("TurnEndPhase"); diff --git a/test/items/temp_stat_stage_booster.test.ts b/test/items/temp_stat_stage_booster.test.ts index 1a3f3723a0b..cae4fcbece7 100644 --- a/test/items/temp_stat_stage_booster.test.ts +++ b/test/items/temp_stat_stage_booster.test.ts @@ -10,6 +10,7 @@ import { UiMode } from "#enums/ui-mode"; import { Button } from "#app/enums/buttons"; import type RewardSelectUiHandler from "#app/ui/reward-select-ui-handler"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; +import { TrainerItemId } from "#enums/trainer-item-id"; describe("Items - Temporary Stat Stage Boosters", () => { let phaserGame: Phaser.Game; @@ -34,7 +35,7 @@ describe("Items - Temporary Stat Stage Boosters", () => { .enemyMoveset(MoveId.SPLASH) .enemyAbility(AbilityId.BALL_FETCH) .moveset([MoveId.TACKLE, MoveId.SPLASH, MoveId.HONE_CLAWS, MoveId.BELLY_DRUM]) - .startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }]); + .startingTrainerItems([{ entry: TrainerItemId.X_ATTACK }]); }); it("should provide a x1.3 stat stage multiplier", async () => { @@ -52,7 +53,7 @@ describe("Items - Temporary Stat Stage Boosters", () => { }); it("should increase existing ACC stat stage by 1 for X_ACCURACY only", async () => { - game.override.startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }]).ability(AbilityId.SIMPLE); + game.override.startingTrainerItems([{ entry: TrainerItemId.X_ACCURACY }]).ability(AbilityId.SIMPLE); await game.classicMode.startBattle([SpeciesId.PIKACHU]); @@ -94,10 +95,7 @@ describe("Items - Temporary Stat Stage Boosters", () => { }); it("should not increase past maximum stat stage multiplier", async () => { - game.override.startingModifier([ - { name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }, - { name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }, - ]); + game.override.startingTrainerItems([{ entry: TrainerItemId.X_ATTACK }, { entry: TrainerItemId.X_ACCURACY }]); await game.classicMode.startBattle([SpeciesId.PIKACHU]); @@ -128,10 +126,7 @@ describe("Items - Temporary Stat Stage Boosters", () => { await game.phaseInterceptor.to("BattleEndPhase"); - const modifier = game.scene.findModifier( - m => m instanceof TempStatStageBoosterModifier, - ) as TempStatStageBoosterModifier; - expect(modifier.getBattleCount()).toBe(4); + expect(game.scene.trainerItems.getStack(TrainerItemId.X_ATTACK)).toBe(4); // Forced X_ATTACK to spawn in the first slot with override game.onNextPrompt( @@ -151,14 +146,7 @@ describe("Items - Temporary Stat Stage Boosters", () => { await game.phaseInterceptor.to("TurnInitPhase"); // Making sure only one booster is in the modifier list even after picking up another - let count = 0; - for (const m of game.scene.modifiers) { - if (m instanceof TempStatStageBoosterModifier) { - count++; - const modifierInstance = m as TempStatStageBoosterModifier; - expect(modifierInstance.getBattleCount()).toBe(modifierInstance.getMaxBattles()); - } - } - expect(count).toBe(1); + + expect(game.scene.trainerItems.getStack(TrainerItemId.X_ATTACK)).toBe(5); }); }); diff --git a/test/items/thick_club.test.ts b/test/items/thick_club.test.ts index bc019ee99f8..18910f6b5f5 100644 --- a/test/items/thick_club.test.ts +++ b/test/items/thick_club.test.ts @@ -1,5 +1,4 @@ import { Stat } from "#enums/stat"; -import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/data/data-lists"; import i18next from "#app/plugins/i18n"; import { NumberHolder, randInt } from "#app/utils/common"; @@ -7,6 +6,7 @@ import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; describe("Items - Thick Club", () => { let phaserGame: Phaser.Game; @@ -29,7 +29,7 @@ describe("Items - Thick Club", () => { }); it("THICK_CLUB activates in battle correctly", async () => { - game.override.startingHeldItems([{ name: "RARE_SPECIES_STAT_BOOSTER", type: "THICK_CLUB" }]); + game.override.startingHeldItems([{ entry: HeldItemId.THICK_CLUB }]); const consoleSpy = vi.spyOn(console, "log"); await game.classicMode.startBattle([SpeciesId.CUBONE]); diff --git a/test/moves/beak_blast.test.ts b/test/moves/beak_blast.test.ts index 2cb9f9bdd6f..621dd9f21dd 100644 --- a/test/moves/beak_blast.test.ts +++ b/test/moves/beak_blast.test.ts @@ -4,6 +4,7 @@ import { BerryPhase } from "#app/phases/berry-phase"; import { MovePhase } from "#app/phases/move-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { AbilityId } from "#enums/ability-id"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; @@ -87,7 +88,7 @@ describe("Moves - Beak Blast", () => { }); it("should only hit twice with Multi-Lens", async () => { - game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); + game.override.startingHeldItems([{ entry: HeldItemId.MULTI_LENS }]); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); diff --git a/test/moves/ceaseless_edge.test.ts b/test/moves/ceaseless_edge.test.ts index 1dec98fe3a8..3a12b65f4e0 100644 --- a/test/moves/ceaseless_edge.test.ts +++ b/test/moves/ceaseless_edge.test.ts @@ -10,6 +10,7 @@ import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; describe("Moves - Ceaseless Edge", () => { let phaserGame: Phaser.Game; @@ -61,7 +62,7 @@ describe("Moves - Ceaseless Edge", () => { }); test("move should hit twice with multi lens and apply two layers of spikes", async () => { - game.override.startingHeldItems([{ name: "MULTI_LENS" }]); + game.override.startingHeldItems([{ entry: HeldItemId.MULTI_LENS }]); await game.classicMode.startBattle([SpeciesId.ILLUMISE]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -83,7 +84,7 @@ describe("Moves - Ceaseless Edge", () => { }); test("trainer - move should hit twice, apply two layers of spikes, force switch opponent - opponent takes damage", async () => { - game.override.startingHeldItems([{ name: "MULTI_LENS" }]).startingWave(25); + game.override.startingHeldItems([{ entry: HeldItemId.MULTI_LENS }]).startingWave(25); await game.classicMode.startBattle([SpeciesId.ILLUMISE]); diff --git a/test/moves/destiny_bond.test.ts b/test/moves/destiny_bond.test.ts index a78d46b464b..184b4be67b8 100644 --- a/test/moves/destiny_bond.test.ts +++ b/test/moves/destiny_bond.test.ts @@ -10,7 +10,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { BattlerIndex } from "#enums/battler-index"; import { StatusEffect } from "#enums/status-effect"; -import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; +import { HeldItemId } from "#enums/held-item-id"; describe("Moves - Destiny Bond", () => { let phaserGame: Phaser.Game; @@ -232,7 +232,7 @@ describe("Moves - Destiny Bond", () => { it("should not allow the opponent to revive via Reviver Seed", async () => { const moveToUse = MoveId.TACKLE; - game.override.moveset(moveToUse).startingHeldItems([{ name: "REVIVER_SEED" }]); + game.override.moveset(moveToUse).startingHeldItems([{ entry: HeldItemId.REVIVER_SEED }]); await game.classicMode.startBattle(defaultParty); const enemyPokemon = game.scene.getEnemyPokemon(); @@ -246,9 +246,6 @@ describe("Moves - Destiny Bond", () => { expect(playerPokemon?.isFainted()).toBe(true); // Check that the Tackle user's Reviver Seed did not activate - const revSeeds = game.scene - .getModifiers(PokemonInstantReviveModifier) - .filter(m => m.pokemonId === playerPokemon?.id); - expect(revSeeds.length).toBe(1); + expect(playerPokemon?.heldItemManager.getStack(HeldItemId.REVIVER_SEED)).toBe(1); }); }); diff --git a/test/moves/dragon_tail.test.ts b/test/moves/dragon_tail.test.ts index 8c456f27853..2404b517e77 100644 --- a/test/moves/dragon_tail.test.ts +++ b/test/moves/dragon_tail.test.ts @@ -10,6 +10,7 @@ import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; describe("Moves - Dragon Tail", () => { let phaserGame: Phaser.Game; @@ -162,7 +163,7 @@ describe("Moves - Dragon Tail", () => { it("should not cause a softlock when activating an opponent trainer's reviver seed", async () => { game.override .startingWave(5) - .enemyHeldItems([{ name: "REVIVER_SEED" }]) + .enemyHeldItems([{ entry: HeldItemId.REVIVER_SEED }]) .startingLevel(1000); // To make sure Dragon Tail KO's the opponent await game.classicMode.startBattle([SpeciesId.DRATINI]); @@ -179,7 +180,7 @@ describe("Moves - Dragon Tail", () => { it("should not cause a softlock when activating a player's reviver seed", async () => { game.override - .startingHeldItems([{ name: "REVIVER_SEED" }]) + .startingHeldItems([{ entry: HeldItemId.REVIVER_SEED }]) .enemyMoveset(MoveId.DRAGON_TAIL) .enemyLevel(1000); // To make sure Dragon Tail KO's the player await game.classicMode.startBattle([SpeciesId.DRATINI, SpeciesId.BULBASAUR]); diff --git a/test/moves/fell_stinger.test.ts b/test/moves/fell_stinger.test.ts index 0737db9105f..6b3e0e090cb 100644 --- a/test/moves/fell_stinger.test.ts +++ b/test/moves/fell_stinger.test.ts @@ -8,6 +8,7 @@ import { Stat } from "#enums/stat"; import { StatusEffect } from "#app/enums/status-effect"; import { WeatherType } from "#app/enums/weather-type"; import { allMoves } from "#app/data/data-lists"; +import { HeldItemId } from "#enums/held-item-id"; describe("Moves - Fell Stinger", () => { let phaserGame: Phaser.Game; @@ -88,7 +89,7 @@ describe("Moves - Fell Stinger", () => { }); it("should not grant stat boost if enemy is saved by Reviver Seed", async () => { - game.override.enemyAbility(AbilityId.BALL_FETCH).enemyHeldItems([{ name: "REVIVER_SEED" }]); + game.override.enemyAbility(AbilityId.BALL_FETCH).enemyHeldItems([{ entry: HeldItemId.REVIVER_SEED }]); await game.classicMode.startBattle([SpeciesId.LEAVANNY]); const leadPokemon = game.scene.getPlayerPokemon()!; diff --git a/test/moves/fusion_flare_bolt.test.ts b/test/moves/fusion_flare_bolt.test.ts index d7af700d39b..f65dddc1fc5 100644 --- a/test/moves/fusion_flare_bolt.test.ts +++ b/test/moves/fusion_flare_bolt.test.ts @@ -164,7 +164,6 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { const enemyParty = game.scene.getEnemyParty(); // Get rid of any modifiers that may alter power - game.scene.clearEnemyHeldItemModifiers(); game.scene.clearEnemyItems(); // Mock stats by replacing entries in copy with desired values for specific stats @@ -218,7 +217,6 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { const enemyParty = game.scene.getEnemyParty(); // Get rid of any modifiers that may alter power - game.scene.clearEnemyHeldItemModifiers(); game.scene.clearEnemyItems(); // Mock stats by replacing entries in copy with desired values for specific stats diff --git a/test/moves/glaive_rush.test.ts b/test/moves/glaive_rush.test.ts index 0b6f30da71a..ac2df2baefe 100644 --- a/test/moves/glaive_rush.test.ts +++ b/test/moves/glaive_rush.test.ts @@ -1,5 +1,6 @@ import { allMoves } from "#app/data/data-lists"; import { AbilityId } from "#enums/ability-id"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import GameManager from "#test/testUtils/gameManager"; @@ -60,7 +61,7 @@ describe("Moves - Glaive Rush", () => { }); it("interacts properly with multi-lens", async () => { - game.override.startingHeldItems([{ name: "MULTI_LENS", count: 2 }]).enemyMoveset([MoveId.AVALANCHE]); + game.override.startingHeldItems([{ entry: HeldItemId.MULTI_LENS, count: 2 }]).enemyMoveset([MoveId.AVALANCHE]); await game.classicMode.startBattle([SpeciesId.KLINK]); const player = game.scene.getPlayerPokemon()!; diff --git a/test/moves/heal_block.test.ts b/test/moves/heal_block.test.ts index dc69b5c2974..c5696dab473 100644 --- a/test/moves/heal_block.test.ts +++ b/test/moves/heal_block.test.ts @@ -9,6 +9,7 @@ import { SpeciesId } from "#enums/species-id"; import { WeatherType } from "#enums/weather-type"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; // Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Heal_Block_(move) describe("Moves - Heal Block", () => { @@ -132,7 +133,7 @@ describe("Moves - Heal Block", () => { }); it("should stop healing from items", async () => { - game.override.startingHeldItems([{ name: "LEFTOVERS" }]); + game.override.startingHeldItems([{ entry: HeldItemId.LEFTOVERS }]); await game.classicMode.startBattle([SpeciesId.CHARIZARD]); diff --git a/test/moves/safeguard.test.ts b/test/moves/safeguard.test.ts index 91aa298a8ca..9abf074ebd1 100644 --- a/test/moves/safeguard.test.ts +++ b/test/moves/safeguard.test.ts @@ -7,6 +7,7 @@ import { SpeciesId } from "#enums/species-id"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { AbilityId } from "#enums/ability-id"; +import { HeldItemId } from "#enums/held-item-id"; describe("Moves - Safeguard", () => { let phaserGame: Phaser.Game; @@ -113,7 +114,7 @@ describe("Moves - Safeguard", () => { }); it("doesn't protect from self-inflicted status from Rest or Flame Orb", async () => { - game.override.enemyHeldItems([{ name: "FLAME_ORB" }]); + game.override.enemyHeldItems([{ entry: HeldItemId.FLAME_ORB }]); await game.classicMode.startBattle(); const enemyPokemon = game.scene.getEnemyPokemon()!; enemyPokemon.hp = 1; diff --git a/test/moves/substitute.test.ts b/test/moves/substitute.test.ts index 182fefb74d7..eb1f3a776fd 100644 --- a/test/moves/substitute.test.ts +++ b/test/moves/substitute.test.ts @@ -11,13 +11,13 @@ import { UiMode } from "#enums/ui-mode"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { BerryType } from "#enums/berry-type"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { HeldItemId } from "#enums/held-item-id"; describe("Moves - Substitute", () => { let phaserGame: Phaser.Game; @@ -296,7 +296,7 @@ describe("Moves - Substitute", () => { }); it("should prevent the user's items from being stolen", async () => { - game.override.enemyMoveset(MoveId.THIEF).startingHeldItems([{ name: "BERRY", type: BerryType.SITRUS }]); + game.override.enemyMoveset(MoveId.THIEF).startingHeldItems([{ entry: HeldItemId.SITRUS_BERRY }]); vi.spyOn(allMoves[MoveId.THIEF], "attrs", "get").mockReturnValue([new StealHeldItemChanceAttr(1.0)]); // give Thief 100% steal rate await game.classicMode.startBattle([SpeciesId.BLASTOISE]); @@ -313,7 +313,7 @@ describe("Moves - Substitute", () => { }); it("should prevent the user's items from being removed", async () => { - game.override.moveset([MoveId.KNOCK_OFF]).enemyHeldItems([{ name: "BERRY", type: BerryType.SITRUS }]); + game.override.moveset([MoveId.KNOCK_OFF]).enemyHeldItems([{ entry: HeldItemId.SITRUS_BERRY }]); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); @@ -330,7 +330,7 @@ describe("Moves - Substitute", () => { }); it("move effect should prevent the user's berries from being stolen and eaten", async () => { - game.override.enemyMoveset(MoveId.BUG_BITE).startingHeldItems([{ name: "BERRY", type: BerryType.SITRUS }]); + game.override.enemyMoveset(MoveId.BUG_BITE).startingHeldItems([{ entry: HeldItemId.SITRUS_BERRY }]); await game.classicMode.startBattle([SpeciesId.BLASTOISE]); diff --git a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index fc92b9cadda..f7f09189f0c 100644 --- a/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -15,10 +15,10 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { MoveId } from "#enums/move-id"; -import { ShinyRateBoosterModifier } from "#app/modifier/modifier"; import { SelectRewardPhase } from "#app/phases/select-reward-phase"; import i18next from "i18next"; import { AbilityId } from "#enums/ability-id"; +import { TrainerItemId } from "#enums/trainer-item-id"; const namespace = "mysteryEncounters/anOfferYouCantRefuse"; /** Gyarados for Indimidate */ @@ -144,10 +144,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty); await runMysteryEncounterToEnd(game, 1); - const itemModifier = scene.findModifier(m => m instanceof ShinyRateBoosterModifier) as ShinyRateBoosterModifier; - - expect(itemModifier).toBeDefined(); - expect(itemModifier?.stackCount).toBe(1); + expect(scene.trainerItems.getStack(TrainerItemId.SHINY_CHARM)).toBe(1); }); it("Should remove the Pokemon from the party", async () => { From c697a47f5ba9d1a50f7811d2a3d88f37a21df599 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 13 Jul 2025 02:34:03 -0700 Subject: [PATCH 22/39] Apply Biome --- .../an-offer-you-cant-refuse-encounter.ts | 4 +- .../encounters/delibirdy-encounter.ts | 6 ++- src/field/pokemon-held-item-manager.ts | 12 ++--- src/items/all-held-items.ts | 44 +++++++++---------- src/items/all-trainer-items.ts | 2 +- src/items/held-item-pool.ts | 4 +- src/items/held-item.ts | 2 +- src/items/held-items/accuracy-booster.ts | 2 +- src/items/held-items/attack-type-booster.ts | 8 ++-- src/items/held-items/base-stat-booster.ts | 2 +- src/items/held-items/base-stat-flat.ts | 2 +- src/items/held-items/base-stat-total.ts | 4 +- src/items/held-items/baton.ts | 2 +- src/items/held-items/bypass-speed-chance.ts | 6 +-- src/items/held-items/crit-booster.ts | 2 +- src/items/held-items/damage-money-reward.ts | 2 +- src/items/held-items/evo-tracker.ts | 4 +- src/items/held-items/exp-booster.ts | 2 +- src/items/held-items/field-effect.ts | 2 +- src/items/held-items/flinch-chance.ts | 2 +- src/items/held-items/friendship-booster.ts | 2 +- src/items/held-items/hit-heal.ts | 6 +-- src/items/held-items/incrementing-stat.ts | 4 +- src/items/held-items/instant-revive.ts | 6 +-- src/items/held-items/item-steal.ts | 8 ++-- src/items/held-items/multi-hit.ts | 4 +- src/items/held-items/nature-weight-booster.ts | 2 +- .../held-items/reset-negative-stat-stage.ts | 2 +- src/items/held-items/stat-booster.ts | 2 +- src/items/held-items/survive-chance.ts | 6 +-- src/items/held-items/turn-end-heal.ts | 6 +-- src/items/held-items/turn-end-status.ts | 4 +- src/items/modifier-to-item-migrator-utils.ts | 4 +- src/items/trainer-item-manager.ts | 2 +- src/items/trainer-item-pool.ts | 2 +- src/items/trainer-item.ts | 14 +++--- src/phases/reward-phase.ts | 4 +- test/items/multi_lens.test.ts | 2 +- test/items/mystical_rock.test.ts | 2 +- test/moves/ceaseless_edge.test.ts | 2 +- test/moves/dragon_tail.test.ts | 2 +- test/moves/heal_block.test.ts | 2 +- test/testUtils/helpers/overridesHelper.ts | 4 +- 43 files changed, 105 insertions(+), 101 deletions(-) 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 6f1a2716919..0ec25f537f5 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 @@ -1,4 +1,5 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; +import { allTrainerItems } from "#app/data/data-lists"; import { globalScene } from "#app/global-scene"; import { speciesStarterCosts } from "#balance/starters"; import { modifierTypes } from "#data/data-lists"; @@ -6,6 +7,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; +import { TrainerItemId } from "#enums/trainer-item-id"; import { leaveEncounterWithoutBattle, setEncounterExp, @@ -23,8 +25,6 @@ import { import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#mystery-encounters/requirement-groups"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; -import { allTrainerItems } from "#app/data/data-lists"; -import { TrainerItemId } from "#enums/trainer-item-id"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/anOfferYouCantRefuse"; diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 17935d814e1..8508b4644af 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -18,7 +18,11 @@ import { import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; -import { CombinationPokemonRequirement, HoldingItemRequirement, MoneyRequirement } from "#mystery-encounters/mystery-encounter-requirements"; +import { + CombinationPokemonRequirement, + HoldingItemRequirement, + MoneyRequirement, +} from "#mystery-encounters/mystery-encounter-requirements"; import i18next from "#plugins/i18n"; import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; import { randSeedItem } from "#utils/common"; diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 6185b5fc2c6..5f506ff0763 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -1,22 +1,22 @@ import { allHeldItems } from "#app/data/data-lists"; import { type HeldItemCategoryId, + type HeldItemId, isCategoryId, isItemInCategory, isItemInRequested, - type HeldItemId, } from "#app/enums/held-item-id"; -import type { FormChangeItem } from "#enums/form-change-item"; import { - type HeldItemConfiguration, - isHeldItemSpecs, - type HeldItemDataMap, - type HeldItemSpecs, type FormChangeItemPropertyMap, type FormChangeItemSpecs, + type HeldItemConfiguration, + type HeldItemDataMap, type HeldItemSaveData, + type HeldItemSpecs, + isHeldItemSpecs, } from "#app/items/held-item-data-types"; import { getTypedEntries, getTypedKeys } from "#app/utils/common"; +import type { FormChangeItem } from "#enums/form-change-item"; export class PokemonItemManager { public heldItems: HeldItemDataMap; diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index f44563e6dbb..2b18fe5399a 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -4,50 +4,50 @@ import { BerryType } from "#enums/berry-type"; import { HeldItemId } from "#enums/held-item-id"; import type { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; -import { Stat, type PermanentStat } from "#enums/stat"; +import { type PermanentStat, Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { HeldItemEffect } from "./held-item"; -import { type AccuracyBoostParams, AccuracyBoosterHeldItem } from "./held-items/accuracy-booster"; +import { AccuracyBoosterHeldItem, type AccuracyBoostParams } from "./held-items/accuracy-booster"; import { - type AttackTypeBoostParams, AttackTypeBoosterHeldItem, + type AttackTypeBoostParams, attackTypeToHeldItem, } from "./held-items/attack-type-booster"; import { - type BaseStatBoosterParams, BaseStatBoosterHeldItem, + type BaseStatBoosterParams, permanentStatToHeldItem, } from "./held-items/base-stat-booster"; -import { type BaseStatFlatParams, BaseStatFlatHeldItem } from "./held-items/base-stat-flat"; -import { type BaseStatTotalParams, BaseStatTotalHeldItem } from "./held-items/base-stat-total"; -import { type BatonParams, BatonHeldItem } from "./held-items/baton"; -import { type BerryParams, BerryHeldItem, berryTypeToHeldItem } from "./held-items/berry"; -import { type BypassSpeedChanceParams, BypassSpeedChanceHeldItem } from "./held-items/bypass-speed-chance"; -import { type CritBoostParams, CritBoostHeldItem, SpeciesCritBoostHeldItem } from "./held-items/crit-booster"; -import { type DamageMoneyRewardParams, DamageMoneyRewardHeldItem } from "./held-items/damage-money-reward"; +import { BaseStatFlatHeldItem, type BaseStatFlatParams } from "./held-items/base-stat-flat"; +import { BaseStatTotalHeldItem, type BaseStatTotalParams } from "./held-items/base-stat-total"; +import { BatonHeldItem, type BatonParams } from "./held-items/baton"; +import { BerryHeldItem, type BerryParams, berryTypeToHeldItem } from "./held-items/berry"; +import { BypassSpeedChanceHeldItem, type BypassSpeedChanceParams } from "./held-items/bypass-speed-chance"; +import { CritBoostHeldItem, type CritBoostParams, SpeciesCritBoostHeldItem } from "./held-items/crit-booster"; +import { DamageMoneyRewardHeldItem, type DamageMoneyRewardParams } from "./held-items/damage-money-reward"; import { type EvoTrackerParams, GimmighoulEvoTrackerHeldItem } from "./held-items/evo-tracker"; -import { type ExpBoostParams, ExpBoosterHeldItem } from "./held-items/exp-booster"; -import { type FieldEffectParams, FieldEffectHeldItem } from "./held-items/field-effect"; -import { type FlinchChanceParams, FlinchChanceHeldItem } from "./held-items/flinch-chance"; -import { type FriendshipBoostParams, FriendshipBoosterHeldItem } from "./held-items/friendship-booster"; -import { type HitHealParams, HitHealHeldItem } from "./held-items/hit-heal"; -import { type IncrementingStatParams, IncrementingStatHeldItem } from "./held-items/incrementing-stat"; +import { ExpBoosterHeldItem, type ExpBoostParams } from "./held-items/exp-booster"; +import { FieldEffectHeldItem, type FieldEffectParams } from "./held-items/field-effect"; +import { FlinchChanceHeldItem, type FlinchChanceParams } from "./held-items/flinch-chance"; +import { FriendshipBoosterHeldItem, type FriendshipBoostParams } from "./held-items/friendship-booster"; +import { HitHealHeldItem, type HitHealParams } from "./held-items/hit-heal"; +import { IncrementingStatHeldItem, type IncrementingStatParams } from "./held-items/incrementing-stat"; import { InstantReviveHeldItem, type InstantReviveParams } from "./held-items/instant-revive"; import { ContactItemStealChanceHeldItem, type ItemStealParams, TurnEndItemStealHeldItem, } from "./held-items/item-steal"; -import { type MultiHitParams, MultiHitHeldItem } from "./held-items/multi-hit"; -import { type NatureWeightBoostParams, NatureWeightBoosterHeldItem } from "./held-items/nature-weight-booster"; +import { MultiHitHeldItem, type MultiHitParams } from "./held-items/multi-hit"; +import { NatureWeightBoosterHeldItem, type NatureWeightBoostParams } from "./held-items/nature-weight-booster"; import { ResetNegativeStatStageHeldItem, type ResetNegativeStatStageParams, } from "./held-items/reset-negative-stat-stage"; import { EvolutionStatBoostHeldItem, SpeciesStatBoostHeldItem, type StatBoostParams } from "./held-items/stat-booster"; -import { type SurviveChanceParams, SurviveChanceHeldItem } from "./held-items/survive-chance"; -import { type TurnEndHealParams, TurnEndHealHeldItem } from "./held-items/turn-end-heal"; -import { type TurnEndStatusParams, TurnEndStatusHeldItem } from "./held-items/turn-end-status"; +import { SurviveChanceHeldItem, type SurviveChanceParams } from "./held-items/survive-chance"; +import { TurnEndHealHeldItem, type TurnEndHealParams } from "./held-items/turn-end-heal"; +import { TurnEndStatusHeldItem, type TurnEndStatusParams } from "./held-items/turn-end-status"; export function initHeldItems() { for (const berry of getEnumValues(BerryType)) { diff --git a/src/items/all-trainer-items.ts b/src/items/all-trainer-items.ts index cd047a6bfa2..afb15aca431 100644 --- a/src/items/all-trainer-items.ts +++ b/src/items/all-trainer-items.ts @@ -24,8 +24,8 @@ import { TempAccuracyBoosterTrainerItem, TempCritBoosterTrainerItem, TempStatStageBoosterTrainerItem, - tempStatToTrainerItem, TrainerItem, + tempStatToTrainerItem, } from "./trainer-item"; export function initTrainerItems() { diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index 9ad26e077af..8f8a761474d 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -1,5 +1,6 @@ -import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import { allHeldItems } from "#app/data/data-lists"; import type Pokemon from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { coerceArray, getEnumValues, isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#app/utils/common"; import { BerryType } from "#enums/berry-type"; import { HeldItemCategoryId, HeldItemId, HeldItemNames, isCategoryId } from "#enums/held-item-id"; @@ -7,7 +8,6 @@ import { HeldItemPoolType } from "#enums/modifier-pool-type"; import type { PokemonType } from "#enums/pokemon-type"; import { RewardTier } from "#enums/reward-tier"; import { PERMANENT_STATS } from "#enums/stat"; -import { allHeldItems } from "#app/data/data-lists"; import { type HeldItemConfiguration, type HeldItemPool, diff --git a/src/items/held-item.ts b/src/items/held-item.ts index 6fdfc0dd6e7..e701a9b69c6 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -1,7 +1,7 @@ import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import { HeldItemNames, type HeldItemId } from "#enums/held-item-id"; +import { type HeldItemId, HeldItemNames } from "#enums/held-item-id"; import i18next from "i18next"; // TODO: this should be moved to its own file diff --git a/src/items/held-items/accuracy-booster.ts b/src/items/held-items/accuracy-booster.ts index fde2a06d351..f8aa332ab27 100644 --- a/src/items/held-items/accuracy-booster.ts +++ b/src/items/held-items/accuracy-booster.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; -import { HeldItemEffect, HeldItem } from "../held-item"; +import { HeldItem, HeldItemEffect } from "../held-item"; export interface AccuracyBoostParams { /** The pokemon with the item */ diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index aad80b3c1d0..a1d93250d37 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -1,9 +1,9 @@ -import { HeldItemNames, HeldItemId } from "#enums/held-item-id"; +import type Pokemon from "#app/field/pokemon"; +import { HeldItem, HeldItemEffect } from "#app/items/held-item"; +import type { NumberHolder } from "#app/utils/common"; +import { HeldItemId, HeldItemNames } from "#enums/held-item-id"; import { PokemonType } from "#enums/pokemon-type"; import i18next from "i18next"; -import type { NumberHolder } from "#app/utils/common"; -import type Pokemon from "#app/field/pokemon"; -import { HeldItemEffect, HeldItem } from "#app/items/held-item"; export interface AttackTypeBoostParams { /** The pokemon with the item */ diff --git a/src/items/held-items/base-stat-booster.ts b/src/items/held-items/base-stat-booster.ts index 8ca87da7732..6f452a12996 100644 --- a/src/items/held-items/base-stat-booster.ts +++ b/src/items/held-items/base-stat-booster.ts @@ -2,7 +2,7 @@ import type Pokemon from "#app/field/pokemon"; import { HeldItemId } from "#enums/held-item-id"; import { getStatKey, type PermanentStat, Stat } from "#enums/stat"; import i18next from "i18next"; -import { HeldItemEffect, HeldItem } from "../held-item"; +import { HeldItem, HeldItemEffect } from "../held-item"; export interface BaseStatBoosterParams { /** The pokemon with the item */ diff --git a/src/items/held-items/base-stat-flat.ts b/src/items/held-items/base-stat-flat.ts index 999e0788faa..30c8b5b9f80 100644 --- a/src/items/held-items/base-stat-flat.ts +++ b/src/items/held-items/base-stat-flat.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItemEffect, HeldItem } from "../held-item"; import { Stat } from "#enums/stat"; import i18next from "i18next"; +import { HeldItem, HeldItemEffect } from "../held-item"; export interface BaseStatFlatParams { /** The pokemon with the item */ diff --git a/src/items/held-items/base-stat-total.ts b/src/items/held-items/base-stat-total.ts index c862d56e176..643c6ef26a0 100644 --- a/src/items/held-items/base-stat-total.ts +++ b/src/items/held-items/base-stat-total.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; -import i18next from "i18next"; -import { HeldItemEffect, HeldItem } from "../held-item"; import type { HeldItemId } from "#enums/held-item-id"; +import i18next from "i18next"; +import { HeldItem, HeldItemEffect } from "../held-item"; export interface BaseStatTotalParams { /** The pokemon with the item */ diff --git a/src/items/held-items/baton.ts b/src/items/held-items/baton.ts index 9e2c31f2cf0..5b34fa3d519 100644 --- a/src/items/held-items/baton.ts +++ b/src/items/held-items/baton.ts @@ -1,6 +1,6 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; -import { HeldItemEffect, HeldItem } from "../held-item"; +import { HeldItem, HeldItemEffect } from "../held-item"; export interface BatonParams { /** The pokemon with the item */ diff --git a/src/items/held-items/bypass-speed-chance.ts b/src/items/held-items/bypass-speed-chance.ts index c99f1022114..99bc861dc3e 100644 --- a/src/items/held-items/bypass-speed-chance.ts +++ b/src/items/held-items/bypass-speed-chance.ts @@ -1,10 +1,10 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItemEffect, HeldItem } from "#app/items/held-item"; -import type { BooleanHolder } from "#app/utils/common"; import { globalScene } from "#app/global-scene"; -import i18next from "i18next"; +import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import { getPokemonNameWithAffix } from "#app/messages"; +import type { BooleanHolder } from "#app/utils/common"; import { Command } from "#enums/command"; +import i18next from "i18next"; export interface BypassSpeedChanceParams { /** The pokemon with the item */ diff --git a/src/items/held-items/crit-booster.ts b/src/items/held-items/crit-booster.ts index 10bf5f0b1dd..53698a2b26d 100644 --- a/src/items/held-items/crit-booster.ts +++ b/src/items/held-items/crit-booster.ts @@ -2,7 +2,7 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; -import { HeldItemEffect, HeldItem } from "../held-item"; +import { HeldItem, HeldItemEffect } from "../held-item"; export interface CritBoostParams { /** The pokemon with the item */ diff --git a/src/items/held-items/damage-money-reward.ts b/src/items/held-items/damage-money-reward.ts index cd97c61938f..5f00a751b78 100644 --- a/src/items/held-items/damage-money-reward.ts +++ b/src/items/held-items/damage-money-reward.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { NumberHolder } from "#app/utils/common"; -import { HeldItemEffect, HeldItem } from "../held-item"; +import { HeldItem, HeldItemEffect } from "../held-item"; import { TrainerItemEffect } from "../trainer-item"; export interface DamageMoneyRewardParams { diff --git a/src/items/held-items/evo-tracker.ts b/src/items/held-items/evo-tracker.ts index 9aa397bb421..4f2985a9e2f 100644 --- a/src/items/held-items/evo-tracker.ts +++ b/src/items/held-items/evo-tracker.ts @@ -2,9 +2,9 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; -import i18next from "i18next"; -import { HeldItemEffect, HeldItem } from "../held-item"; import { TrainerItemId } from "#enums/trainer-item-id"; +import i18next from "i18next"; +import { HeldItem, HeldItemEffect } from "../held-item"; export interface EvoTrackerParams { /** The pokemon with the item */ diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts index 4c38398e78b..98eed27392f 100644 --- a/src/items/held-items/exp-booster.ts +++ b/src/items/held-items/exp-booster.ts @@ -2,7 +2,7 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; import i18next from "i18next"; -import { HeldItemEffect, HeldItem } from "../held-item"; +import { HeldItem, HeldItemEffect } from "../held-item"; export interface ExpBoostParams { /** The pokemon with the item */ diff --git a/src/items/held-items/field-effect.ts b/src/items/held-items/field-effect.ts index 95ef5e0ab08..2b25a9d3ec7 100644 --- a/src/items/held-items/field-effect.ts +++ b/src/items/held-items/field-effect.ts @@ -1,5 +1,5 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItemEffect, HeldItem } from "#app/items/held-item"; +import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import type { NumberHolder } from "#app/utils/common"; export interface FieldEffectParams { diff --git a/src/items/held-items/flinch-chance.ts b/src/items/held-items/flinch-chance.ts index a8b65aa0db5..7a795226860 100644 --- a/src/items/held-items/flinch-chance.ts +++ b/src/items/held-items/flinch-chance.ts @@ -1,5 +1,5 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItemEffect, HeldItem } from "#app/items/held-item"; +import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import type { BooleanHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; diff --git a/src/items/held-items/friendship-booster.ts b/src/items/held-items/friendship-booster.ts index 5d187033fe6..3ad6e48e63e 100644 --- a/src/items/held-items/friendship-booster.ts +++ b/src/items/held-items/friendship-booster.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; import i18next from "i18next"; -import { HeldItemEffect, HeldItem } from "../held-item"; +import { HeldItem, HeldItemEffect } from "../held-item"; export interface FriendshipBoostParams { /** The pokemon with the item */ diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts index 74e5168ee62..ede9f196ecb 100644 --- a/src/items/held-items/hit-heal.ts +++ b/src/items/held-items/hit-heal.ts @@ -1,10 +1,10 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import i18next from "i18next"; -import { HeldItemEffect, HeldItem } from "#app/items/held-item"; +import { HeldItem, HeldItemEffect } from "#app/items/held-item"; +import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { toDmgValue } from "#app/utils/common"; -import { getPokemonNameWithAffix } from "#app/messages"; +import i18next from "i18next"; export interface HitHealParams { /** The pokemon with the item */ diff --git a/src/items/held-items/incrementing-stat.ts b/src/items/held-items/incrementing-stat.ts index d95d2f81044..a29a2f2a877 100644 --- a/src/items/held-items/incrementing-stat.ts +++ b/src/items/held-items/incrementing-stat.ts @@ -1,8 +1,8 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItemEffect, HeldItem } from "../held-item"; -import { Stat } from "#enums/stat"; import type { NumberHolder } from "#app/utils/common"; +import { Stat } from "#enums/stat"; import i18next from "i18next"; +import { HeldItem, HeldItemEffect } from "../held-item"; export interface IncrementingStatParams { /** The pokemon with the item */ diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts index 765e3a611e5..dd97a28ccca 100644 --- a/src/items/held-items/instant-revive.ts +++ b/src/items/held-items/instant-revive.ts @@ -1,11 +1,11 @@ +import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import i18next from "i18next"; -import { ConsumableHeldItem, HeldItemEffect } from "../held-item"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { toDmgValue } from "#app/utils/common"; -import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; +import i18next from "i18next"; +import { ConsumableHeldItem, HeldItemEffect } from "../held-item"; export interface InstantReviveParams { /** The pokemon with the item */ diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts index 02b46750ebd..68548a6c77a 100644 --- a/src/items/held-items/item-steal.ts +++ b/src/items/held-items/item-steal.ts @@ -1,11 +1,11 @@ +import { allHeldItems } from "#app/data/data-lists"; import Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { getPokemonNameWithAffix } from "#app/messages"; import { randSeedFloat } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; import i18next from "i18next"; -import { HeldItemEffect, HeldItem } from "../held-item"; -import { getPokemonNameWithAffix } from "#app/messages"; -import { allHeldItems } from "#app/data/data-lists"; -import { globalScene } from "#app/global-scene"; +import { HeldItem, HeldItemEffect } from "../held-item"; export interface ItemStealParams { /** The pokemon with the item */ diff --git a/src/items/held-items/multi-hit.ts b/src/items/held-items/multi-hit.ts index d4773630739..3353c2b596b 100644 --- a/src/items/held-items/multi-hit.ts +++ b/src/items/held-items/multi-hit.ts @@ -1,8 +1,8 @@ +import { allMoves } from "#app/data/data-lists"; import type Pokemon from "#app/field/pokemon"; -import { HeldItemEffect, HeldItem } from "#app/items/held-item"; +import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import { isNullOrUndefined, type NumberHolder } from "#app/utils/common"; import type { MoveId } from "#enums/move-id"; -import { allMoves } from "#app/data/data-lists"; import i18next from "i18next"; export interface MultiHitParams { diff --git a/src/items/held-items/nature-weight-booster.ts b/src/items/held-items/nature-weight-booster.ts index f24646ee4ea..983c9173ab8 100644 --- a/src/items/held-items/nature-weight-booster.ts +++ b/src/items/held-items/nature-weight-booster.ts @@ -1,6 +1,6 @@ import type Pokemon from "#app/field/pokemon"; import type { NumberHolder } from "#app/utils/common"; -import { HeldItemEffect, HeldItem } from "../held-item"; +import { HeldItem, HeldItemEffect } from "../held-item"; export interface NatureWeightBoostParams { /** The pokemon with the item */ diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index fbda4a27e14..d0be04849be 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -1,9 +1,9 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; +import { getPokemonNameWithAffix } from "#app/messages"; import { BATTLE_STATS } from "#enums/stat"; import i18next from "i18next"; import { ConsumableHeldItem, HeldItemEffect } from "../held-item"; -import { getPokemonNameWithAffix } from "#app/messages"; export interface ResetNegativeStatStageParams { /** The pokemon with the item */ diff --git a/src/items/held-items/stat-booster.ts b/src/items/held-items/stat-booster.ts index 43a655b364d..f64770ffce4 100644 --- a/src/items/held-items/stat-booster.ts +++ b/src/items/held-items/stat-booster.ts @@ -4,7 +4,7 @@ import type { NumberHolder } from "#app/utils/common"; import { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import type { Stat } from "#enums/stat"; -import { HeldItemEffect, HeldItem } from "../held-item"; +import { HeldItem, HeldItemEffect } from "../held-item"; export interface StatBoostParams { /** The pokemon with the item */ diff --git a/src/items/held-items/survive-chance.ts b/src/items/held-items/survive-chance.ts index 2b9717163f3..d18247350fa 100644 --- a/src/items/held-items/survive-chance.ts +++ b/src/items/held-items/survive-chance.ts @@ -1,9 +1,9 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItemEffect, HeldItem } from "#app/items/held-item"; -import type { BooleanHolder } from "#app/utils/common"; import { globalScene } from "#app/global-scene"; -import i18next from "i18next"; +import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import { getPokemonNameWithAffix } from "#app/messages"; +import type { BooleanHolder } from "#app/utils/common"; +import i18next from "i18next"; export interface SurviveChanceParams { /** The pokemon with the item */ diff --git a/src/items/held-items/turn-end-heal.ts b/src/items/held-items/turn-end-heal.ts index f78f3e91d91..b2e267ffedd 100644 --- a/src/items/held-items/turn-end-heal.ts +++ b/src/items/held-items/turn-end-heal.ts @@ -1,10 +1,10 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import i18next from "i18next"; -import { HeldItemEffect, HeldItem } from "#app/items/held-item"; +import { HeldItem, HeldItemEffect } from "#app/items/held-item"; +import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { toDmgValue } from "#app/utils/common"; -import { getPokemonNameWithAffix } from "#app/messages"; +import i18next from "i18next"; export interface TurnEndHealParams { /** The pokemon with the item */ diff --git a/src/items/held-items/turn-end-status.ts b/src/items/held-items/turn-end-status.ts index ae6b9f6dffc..d346d54dca4 100644 --- a/src/items/held-items/turn-end-status.ts +++ b/src/items/held-items/turn-end-status.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItemEffect, HeldItem } from "#app/items/held-item"; -import type { StatusEffect } from "#enums/status-effect"; +import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import type { HeldItemId } from "#enums/held-item-id"; +import type { StatusEffect } from "#enums/status-effect"; export interface TurnEndStatusParams { /** The pokemon with the item */ diff --git a/src/items/modifier-to-item-migrator-utils.ts b/src/items/modifier-to-item-migrator-utils.ts index 0927099bac8..929c1229bb0 100644 --- a/src/items/modifier-to-item-migrator-utils.ts +++ b/src/items/modifier-to-item-migrator-utils.ts @@ -2,12 +2,12 @@ import type ModifierData from "#app/system/modifier-data"; import type { BerryType } from "#enums/berry-type"; import { HeldItemId } from "#enums/held-item-id"; import type { PokemonType } from "#enums/pokemon-type"; -import { Stat, type PermanentStat } from "#enums/stat"; +import { SpeciesId } from "#enums/species-id"; +import { type PermanentStat, Stat } from "#enums/stat"; import type { PokemonItemMap } from "./held-item-data-types"; import { attackTypeToHeldItem } from "./held-items/attack-type-booster"; import { permanentStatToHeldItem } from "./held-items/base-stat-booster"; import { berryTypeToHeldItem } from "./held-items/berry"; -import { SpeciesId } from "#enums/species-id"; const uniqueModifierToItem = { EvoTrackerModifier: HeldItemId.GIMMIGHOUL_EVO_TRACKER, diff --git a/src/items/trainer-item-manager.ts b/src/items/trainer-item-manager.ts index f8329ac6341..0bb49be2e12 100644 --- a/src/items/trainer-item-manager.ts +++ b/src/items/trainer-item-manager.ts @@ -2,9 +2,9 @@ import { allTrainerItems } from "#app/data/data-lists"; import type { TrainerItemId } from "#app/enums/trainer-item-id"; import { isTrainerItemSpecs, - type TrainerItemSaveData, type TrainerItemConfiguration, type TrainerItemDataMap, + type TrainerItemSaveData, type TrainerItemSpecs, } from "#app/items/trainer-item-data-types"; import { getTypedEntries, getTypedKeys } from "#app/utils/common"; diff --git a/src/items/trainer-item-pool.ts b/src/items/trainer-item-pool.ts index 72e38d4750b..12fecd88eaf 100644 --- a/src/items/trainer-item-pool.ts +++ b/src/items/trainer-item-pool.ts @@ -1,8 +1,8 @@ +import { allTrainerItems } from "#app/data/data-lists"; import { globalScene } from "#app/global-scene"; import { isNullOrUndefined, pickWeightedIndex } from "#app/utils/common"; import { RewardTier } from "#enums/reward-tier"; import type { TrainerItemId } from "#enums/trainer-item-id"; -import { allTrainerItems } from "#app/data/data-lists"; import type { TrainerItemPool, TrainerItemTieredPool } from "./trainer-item-data-types"; import type { TrainerItemManager } from "./trainer-item-manager"; diff --git a/src/items/trainer-item.ts b/src/items/trainer-item.ts index bc7ce86644a..4858809db55 100644 --- a/src/items/trainer-item.ts +++ b/src/items/trainer-item.ts @@ -1,15 +1,15 @@ +import { getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect"; import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import { hslToHex, randSeedFloat, toDmgValue, type BooleanHolder, type NumberHolder } from "#app/utils/common"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { addTextObject, TextStyle } from "#app/ui/text"; +import { type BooleanHolder, hslToHex, type NumberHolder, randSeedFloat, toDmgValue } from "#app/utils/common"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { getStatKey, Stat, type TempBattleStat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; import { TrainerItemId, TrainerItemNames } from "#enums/trainer-item-id"; import i18next from "i18next"; import type { TrainerItemManager } from "./trainer-item-manager"; -import { addTextObject, TextStyle } from "#app/ui/text"; -import { getStatKey, Stat, type TempBattleStat } from "#enums/stat"; -import { BattlerTagType } from "#enums/battler-tag-type"; -import { getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect"; -import { getPokemonNameWithAffix } from "#app/messages"; -import { StatusEffect } from "#enums/status-effect"; export const TrainerItemEffect = { LEVEL_INCREMENT_BOOSTER: 1, diff --git a/src/phases/reward-phase.ts b/src/phases/reward-phase.ts index 65a301cb1f2..3c490f6c9cb 100644 --- a/src/phases/reward-phase.ts +++ b/src/phases/reward-phase.ts @@ -1,6 +1,6 @@ -import { globalScene } from "#app/global-scene"; -import { TrainerItemReward, type ModifierType } from "#app/modifier/modifier-type"; import type { ModifierTypeFunc } from "#app/@types/modifier-types"; +import { globalScene } from "#app/global-scene"; +import { type ModifierType, TrainerItemReward } from "#app/modifier/modifier-type"; import { getModifierType } from "#app/utils/modifier-utils"; import i18next from "i18next"; import { BattlePhase } from "./battle-phase"; diff --git a/test/items/multi_lens.test.ts b/test/items/multi_lens.test.ts index de786555d90..96e38dce1fd 100644 --- a/test/items/multi_lens.test.ts +++ b/test/items/multi_lens.test.ts @@ -1,12 +1,12 @@ import { AbilityId } from "#enums/ability-id"; import { BattlerIndex } from "#enums/battler-index"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import { GameManager } from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { HeldItemId } from "#enums/held-item-id"; describe("Items - Multi Lens", () => { let phaserGame: Phaser.Game; diff --git a/test/items/mystical_rock.test.ts b/test/items/mystical_rock.test.ts index 2ca842a54b9..164feeed444 100644 --- a/test/items/mystical_rock.test.ts +++ b/test/items/mystical_rock.test.ts @@ -1,11 +1,11 @@ import { globalScene } from "#app/global-scene"; import { AbilityId } from "#enums/ability-id"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { GameManager } from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { HeldItemId } from "#enums/held-item-id"; describe("Items - Mystical Rock", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/ceaseless_edge.test.ts b/test/moves/ceaseless_edge.test.ts index f98b3345d46..ca2009cb21a 100644 --- a/test/moves/ceaseless_edge.test.ts +++ b/test/moves/ceaseless_edge.test.ts @@ -3,6 +3,7 @@ import { allMoves } from "#data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagType } from "#enums/arena-tag-type"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { MoveEffectPhase } from "#phases/move-effect-phase"; @@ -10,7 +11,6 @@ import { TurnEndPhase } from "#phases/turn-end-phase"; import { GameManager } from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; -import { HeldItemId } from "#enums/held-item-id"; describe("Moves - Ceaseless Edge", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/dragon_tail.test.ts b/test/moves/dragon_tail.test.ts index 0ea3ba373b4..b1ee9c5e3bb 100644 --- a/test/moves/dragon_tail.test.ts +++ b/test/moves/dragon_tail.test.ts @@ -3,6 +3,7 @@ import { Status } from "#data/status-effect"; import { AbilityId } from "#enums/ability-id"; import { BattlerIndex } from "#enums/battler-index"; import { Challenges } from "#enums/challenges"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; @@ -10,7 +11,6 @@ import { StatusEffect } from "#enums/status-effect"; import { GameManager } from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { HeldItemId } from "#enums/held-item-id"; describe("Moves - Dragon Tail", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/heal_block.test.ts b/test/moves/heal_block.test.ts index 9e2af554323..661720e7a4d 100644 --- a/test/moves/heal_block.test.ts +++ b/test/moves/heal_block.test.ts @@ -3,13 +3,13 @@ import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { WeatherType } from "#enums/weather-type"; import { GameManager } from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { HeldItemId } from "#enums/held-item-id"; // Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Heal_Block_(move) describe("Moves - Heal Block", () => { diff --git a/test/testUtils/helpers/overridesHelper.ts b/test/testUtils/helpers/overridesHelper.ts index e50d892484b..44ae5ef163d 100644 --- a/test/testUtils/helpers/overridesHelper.ts +++ b/test/testUtils/helpers/overridesHelper.ts @@ -2,6 +2,8 @@ import type { NewArenaEvent } from "#events/battle-scene"; /** biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ +import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; +import type { TrainerItemConfiguration } from "#app/items/trainer-item-data-types"; import type { BattleStyle, RandomTrainerOverride } from "#app/overrides"; import Overrides, { defaultOverrides } from "#app/overrides"; import { AbilityId } from "#enums/ability-id"; @@ -20,8 +22,6 @@ import type { Variant } from "#sprites/variant"; import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper"; import { coerceArray, shiftCharCodes } from "#utils/common"; import { expect, vi } from "vitest"; -import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; -import type { TrainerItemConfiguration } from "#app/items/trainer-item-data-types"; /** * Helper to handle overrides in tests From 882c256933528eafae08f2e050870c720339cee9 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 13 Jul 2025 02:42:22 -0700 Subject: [PATCH 23/39] Create and apply `#items` import alias --- src/@types/trainer-funcs.ts | 2 +- src/battle-scene.ts | 28 +++++++++---------- src/battle.ts | 2 +- src/data/abilities/ability.ts | 2 +- src/data/data-lists.ts | 4 +-- src/data/moves/move.ts | 10 +++---- .../encounters/absolute-avarice-encounter.ts | 2 +- .../encounters/berries-abound-encounter.ts | 2 +- .../encounters/clowning-around-encounter.ts | 4 +-- .../encounters/dark-deal-encounter.ts | 2 +- .../encounters/fiery-fallout-encounter.ts | 2 +- .../global-trade-system-encounter.ts | 4 +-- .../encounters/safari-zone-encounter.ts | 2 +- .../shady-vitamin-dealer-encounter.ts | 2 +- .../encounters/uncommon-breed-encounter.ts | 2 +- .../encounters/weird-dream-encounter.ts | 4 +-- .../utils/encounter-phase-utils.ts | 2 +- src/data/pokeball.ts | 2 +- src/field/arena.ts | 4 +-- src/field/pokemon-held-item-manager.ts | 6 ++-- src/field/pokemon.ts | 10 +++---- src/field/trainer.ts | 2 +- src/items/held-items/attack-type-booster.ts | 2 +- src/items/held-items/berry.ts | 2 +- src/items/held-items/bypass-speed-chance.ts | 2 +- src/items/held-items/field-effect.ts | 2 +- src/items/held-items/flinch-chance.ts | 2 +- src/items/held-items/hit-heal.ts | 2 +- src/items/held-items/multi-hit.ts | 2 +- src/items/held-items/survive-chance.ts | 2 +- src/items/held-items/turn-end-heal.ts | 2 +- src/items/held-items/turn-end-status.ts | 2 +- src/items/trainer-item-manager.ts | 4 +-- src/loading-scene.ts | 8 +++--- src/modifier/init-modifier-pools.ts | 2 +- src/modifier/modifier-type.ts | 20 ++++++------- src/modifier/modifier.ts | 8 +++--- src/overrides.ts | 4 +-- src/phases/add-enemy-buff-modifier-phase.ts | 2 +- src/phases/berry-phase.ts | 4 +-- src/phases/exp-phase.ts | 2 +- src/phases/faint-phase.ts | 4 +-- src/phases/money-reward-phase.ts | 2 +- src/phases/move-effect-phase.ts | 6 ++-- src/phases/pokemon-heal-phase.ts | 2 +- src/phases/select-reward-phase.ts | 2 +- src/phases/show-party-exp-bar-phase.ts | 2 +- src/phases/stat-stage-change-phase.ts | 4 +-- src/phases/title-phase.ts | 2 +- src/phases/turn-end-phase.ts | 6 ++-- src/phases/turn-start-phase.ts | 4 +-- src/system/game-data.ts | 2 +- src/system/pokemon-data.ts | 4 +-- src/ui/item-bar-ui.ts | 4 +-- src/ui/reward-select-ui-handler.ts | 2 +- src/ui/run-info-ui-handler.ts | 2 +- src/ui/summary-ui-handler.ts | 2 +- test/items/eviolite.test.ts | 4 +-- test/items/exp_booster.test.ts | 4 +-- test/items/light_ball.test.ts | 4 +-- test/items/reviver_seed.test.ts | 2 +- .../clowning-around-encounter.test.ts | 2 +- .../the-strong-stuff-encounter.test.ts | 4 +-- test/testUtils/helpers/overridesHelper.ts | 4 +-- test/testUtils/testFileInitialization.ts | 8 +++--- tsconfig.json | 1 + 66 files changed, 129 insertions(+), 128 deletions(-) diff --git a/src/@types/trainer-funcs.ts b/src/@types/trainer-funcs.ts index 3107068cf50..9a8b7df23de 100644 --- a/src/@types/trainer-funcs.ts +++ b/src/@types/trainer-funcs.ts @@ -1,7 +1,7 @@ -import type { TrainerItemConfiguration } from "#app/items/trainer-item-data-types"; import type { PartyMemberStrength } from "#enums/party-member-strength"; import type { SpeciesId } from "#enums/species-id"; import type { EnemyPokemon } from "#field/pokemon"; +import type { TrainerItemConfiguration } from "#items/trainer-item-data-types"; import type { TrainerPartyTemplate } from "#trainers/TrainerPartyTemplate"; import type { TrainerConfig } from "#trainers/trainer-config"; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index bcee93d2e2b..711e2c06d83 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -13,20 +13,6 @@ import { timedEventManager } from "#app/global-event-manager"; import { initGlobalScene } from "#app/global-scene"; import { starterColors } from "#app/global-vars/starter-colors"; import { InputsController } from "#app/inputs-controller"; -import { applyHeldItems } from "#app/items/all-held-items"; -import { type ApplyTrainerItemsParams, applyTrainerItems } from "#app/items/apply-trainer-items"; -import { HeldItemEffect } from "#app/items/held-item"; -import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; -import { assignEnemyHeldItemsForWave, assignItemsFromConfiguration } from "#app/items/held-item-pool"; -import { type EnemyAttackStatusEffectChanceTrainerItem, TrainerItemEffect } from "#app/items/trainer-item"; -import { - isTrainerItemPool, - isTrainerItemSpecs, - type TrainerItemConfiguration, - type TrainerItemSaveData, -} from "#app/items/trainer-item-data-types"; -import { TrainerItemManager } from "#app/items/trainer-item-manager"; -import { getNewTrainerItemFromPool } from "#app/items/trainer-item-pool"; import { LoadingScene } from "#app/loading-scene"; import Overrides from "#app/overrides"; import type { Phase } from "#app/phase"; @@ -94,6 +80,20 @@ import type { Pokemon } from "#field/pokemon"; import { EnemyPokemon, PlayerPokemon } from "#field/pokemon"; import { PokemonSpriteSparkleHandler } from "#field/pokemon-sprite-sparkle-handler"; import { Trainer } from "#field/trainer"; +import { applyHeldItems } from "#items/all-held-items"; +import { type ApplyTrainerItemsParams, applyTrainerItems } from "#items/apply-trainer-items"; +import { HeldItemEffect } from "#items/held-item"; +import type { HeldItemConfiguration } from "#items/held-item-data-types"; +import { assignEnemyHeldItemsForWave, assignItemsFromConfiguration } from "#items/held-item-pool"; +import { type EnemyAttackStatusEffectChanceTrainerItem, TrainerItemEffect } from "#items/trainer-item"; +import { + isTrainerItemPool, + isTrainerItemSpecs, + type TrainerItemConfiguration, + type TrainerItemSaveData, +} from "#items/trainer-item-data-types"; +import { TrainerItemManager } from "#items/trainer-item-manager"; +import { getNewTrainerItemFromPool } from "#items/trainer-item-pool"; import type { Modifier } from "#modifiers/modifier"; import { ConsumableModifier, diff --git a/src/battle.ts b/src/battle.ts index 448304b17e7..6f4394e5460 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -1,6 +1,5 @@ import type { GameMode } from "#app/game-mode"; import { globalScene } from "#app/global-scene"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattleSpec } from "#enums/battle-spec"; import { BattleType } from "#enums/battle-type"; @@ -20,6 +19,7 @@ import { TrainerType } from "#enums/trainer-type"; import { TrainerVariant } from "#enums/trainer-variant"; import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; import { Trainer } from "#field/trainer"; +import { TrainerItemEffect } from "#items/trainer-item"; import type { CustomModifierSettings } from "#modifiers/modifier-type"; import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import i18next from "#plugins/i18n"; diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 2b397264a79..893c13c348e 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -5,7 +5,6 @@ import type { SpeciesFormChangeRevertWeatherFormTrigger } from "#data/form-chang import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; -import { type BerryHeldItem, berryTypeToHeldItem } from "#app/items/held-items/berry"; import { getPokemonNameWithAffix } from "#app/messages"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#data/arena-tag"; import type { BattlerTag } from "#data/battler-tags"; @@ -47,6 +46,7 @@ import { SwitchType } from "#enums/switch-type"; import { WeatherType } from "#enums/weather-type"; import { BerryUsedEvent } from "#events/battle-scene"; import type { EnemyPokemon, Pokemon } from "#field/pokemon"; +import { type BerryHeldItem, berryTypeToHeldItem } from "#items/berry"; import { applyMoveAttrs } from "#moves/apply-attrs"; import { noAbilityTypeOverrideMoves } from "#moves/invalid-moves"; import type { Move } from "#moves/move"; diff --git a/src/data/data-lists.ts b/src/data/data-lists.ts index 4c36b76fc97..786adb35e5c 100644 --- a/src/data/data-lists.ts +++ b/src/data/data-lists.ts @@ -1,9 +1,9 @@ import type { Ability } from "#abilities/ability"; -import type { HeldItem } from "#app/items/held-item"; -import type { TrainerItem } from "#app/items/trainer-item"; import type { PokemonSpecies } from "#data/pokemon-species"; import type { HeldItemId } from "#enums/held-item-id"; import type { TrainerItemId } from "#enums/trainer-item-id"; +import type { HeldItem } from "#items/held-item"; +import type { TrainerItem } from "#items/trainer-item"; import type { ModifierTypes } from "#modifiers/modifier-type"; import type { Move } from "#moves/move"; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 6ab5dce70b3..b7298273bb9 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -5,10 +5,6 @@ import { import { loggedInUser } from "#app/account"; import type { GameMode } from "#app/game-mode"; import { globalScene } from "#app/global-scene"; -import { applyHeldItems } from "#app/items/all-held-items"; -import { HeldItemEffect } from "#app/items/held-item"; -import { berryTypeToHeldItem, BerryHeldItem } from "#app/items/held-items/berry"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import { getPokemonNameWithAffix } from "#app/messages"; import type { ArenaTrapTag } from "#data/arena-tag"; import { WeakenMoveTypeTag } from "#data/arena-tag"; @@ -46,7 +42,7 @@ import { BiomeId } from "#enums/biome-id"; import { ChallengeType } from "#enums/challenge-type"; import { Command } from "#enums/command"; import { FieldPosition } from "#enums/field-position"; -import { HeldItemId, isItemInCategory, HeldItemCategoryId } from "#enums/held-item-id"; +import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id"; import { HitResult } from "#enums/hit-result"; import { ChargeAnim } from "#enums/move-anims-common"; import { MoveId } from "#enums/move-id"; @@ -71,6 +67,10 @@ import { SwitchType } from "#enums/switch-type"; import { WeatherType } from "#enums/weather-type"; import { MoveUsedEvent } from "#events/battle-scene"; import type { EnemyPokemon, Pokemon } from "#field/pokemon"; +import { applyHeldItems } from "#items/all-held-items"; +import { BerryHeldItem, berryTypeToHeldItem } from "#items/berry"; +import { HeldItemEffect } from "#items/held-item"; +import { TrainerItemEffect } from "#items/trainer-item"; import { applyMoveAttrs } from "#moves/apply-attrs"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSketchMoves, invalidSleepTalkMoves } from "#moves/invalid-moves"; import { frenzyMissFunc, getMoveTargets } from "#moves/move-utils"; diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 922a63ad299..0c304927b28 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -1,5 +1,4 @@ import { globalScene } from "#app/global-scene"; -import type { HeldItemConfiguration, PokemonItemMap } from "#app/items/held-item-data-types"; import { allHeldItems } from "#data/data-lists"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -16,6 +15,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import type { MysteryEncounterSpriteConfig } from "#field/mystery-encounter-intro"; import type { Pokemon } from "#field/pokemon"; import { EnemyPokemon } from "#field/pokemon"; +import type { HeldItemConfiguration, PokemonItemMap } from "#items/held-item-data-types"; import { PokemonMove } from "#moves/pokemon-move"; import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index d4e4ce12f6a..c79ff9d8896 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -1,6 +1,5 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { berryTypeToHeldItem } from "#app/items/held-items/berry"; import { getPokemonNameWithAffix } from "#app/messages"; import { modifierTypes } from "#data/data-lists"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -11,6 +10,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PERMANENT_STATS, Stat } from "#enums/stat"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; +import { berryTypeToHeldItem } from "#items/berry"; import type { ModifierTypeOption } from "#modifiers/modifier-type"; import { regenerateModifierPoolThresholds } from "#modifiers/modifier-type"; import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index a8edbe0fa98..837e4cafc90 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -1,7 +1,5 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { assignItemsFromConfiguration } from "#app/items/held-item-pool"; -import { getHeldItemTier } from "#app/items/held-item-tiers"; import { EncounterBattleAnim } from "#data/battle-anims"; import { allAbilities } from "#data/data-lists"; import { CustomPokemonData } from "#data/pokemon-data"; @@ -23,6 +21,8 @@ import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon } from "#field/pokemon"; +import { assignItemsFromConfiguration } from "#items/held-item-pool"; +import { getHeldItemTier } from "#items/held-item-tiers"; import { PokemonMove } from "#moves/pokemon-move"; import { showEncounterDialogue, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index ae12586a6e2..2d6d0deeed0 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -1,6 +1,5 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; import { modifierTypes } from "#data/data-lists"; import { Challenges } from "#enums/challenges"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -8,6 +7,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; +import type { HeldItemConfiguration } from "#items/held-item-data-types"; import type { EnemyPartyConfig, EnemyPokemonConfig } from "#mystery-encounters/encounter-phase-utils"; import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle } from "#mystery-encounters/encounter-phase-utils"; import { getRandomPlayerPokemon, getRandomSpeciesByStarterCost } from "#mystery-encounters/encounter-pokemon-utils"; diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 2d1e6d8fdab..94422518b25 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -1,6 +1,5 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { getNewHeldItemFromCategory } from "#app/items/held-item-pool"; import { EncounterBattleAnim } from "#data/battle-anims"; import { allAbilities, allHeldItems } from "#data/data-lists"; import { Gender } from "#data/gender"; @@ -20,6 +19,7 @@ import { Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { WeatherType } from "#enums/weather-type"; import type { Pokemon } from "#field/pokemon"; +import { getNewHeldItemFromCategory } from "#items/held-item-pool"; import { PokemonMove } from "#moves/pokemon-move"; import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 7a11c11be6d..0cccba42a49 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -1,8 +1,6 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { timedEventManager } from "#app/global-event-manager"; import { globalScene } from "#app/global-scene"; -import { getHeldItemTier } from "#app/items/held-item-tiers"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import { allHeldItems, allSpecies } from "#data/data-lists"; import { Gender, getGenderSymbol } from "#data/gender"; import { getNatureName } from "#data/nature"; @@ -22,6 +20,8 @@ import { TrainerType } from "#enums/trainer-type"; import { doShinySparkleAnim } from "#field/anims"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; import { EnemyPokemon } from "#field/pokemon"; +import { getHeldItemTier } from "#items/held-item-tiers"; +import { TrainerItemEffect } from "#items/trainer-item"; import type { ModifierTypeOption } from "#modifiers/modifier-type"; import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#modifiers/modifier-type"; import { PokemonMove } from "#moves/pokemon-move"; diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index f0080c9ee02..7da0adcb106 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -1,6 +1,5 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import { getPokemonNameWithAffix } from "#app/messages"; import { NON_LEGEND_PARADOX_POKEMON } from "#balance/special-species-groups"; import type { PokemonSpecies } from "#data/pokemon-species"; @@ -12,6 +11,7 @@ import { PokeballType } from "#enums/pokeball"; import { TrainerItemId } from "#enums/trainer-item-id"; import { TrainerSlot } from "#enums/trainer-slot"; import type { EnemyPokemon } from "#field/pokemon"; +import { TrainerItemEffect } from "#items/trainer-item"; import { getEncounterText, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import { initSubsequentOptionSelect, diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index 29a177648c1..6e6fe35779d 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -1,7 +1,6 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { allHeldItems } from "#app/data/data-lists"; import { globalScene } from "#app/global-scene"; -import { getNewVitaminHeldItem } from "#app/items/held-item-pool"; import { getNatureName } from "#data/nature"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -9,6 +8,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { Nature } from "#enums/nature"; import { SpeciesId } from "#enums/species-id"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; +import { getNewVitaminHeldItem } from "#items/held-item-pool"; import { getEncounterText, queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils"; import { leaveEncounterWithoutBattle, diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 75a95e9fd45..bcbbb860623 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -1,6 +1,5 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import type { PokemonItemMap } from "#app/items/held-item-data-types"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; import { HeldItemCategoryId, type HeldItemId } from "#enums/held-item-id"; @@ -12,6 +11,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PokeballType } from "#enums/pokeball"; import { Stat } from "#enums/stat"; import type { EnemyPokemon, Pokemon } from "#field/pokemon"; +import type { PokemonItemMap } from "#items/held-item-data-types"; import { PokemonMove } from "#moves/pokemon-move"; import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 110d197b8a2..d20b43c8065 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -1,6 +1,4 @@ import { globalScene } from "#app/global-scene"; -import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import { allSpecies, modifierTypes } from "#data/data-lists"; import { getLevelTotalExp } from "#data/exp"; import type { PokemonSpecies } from "#data/pokemon-species"; @@ -17,6 +15,8 @@ import { RewardTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; +import type { HeldItemConfiguration } from "#items/held-item-data-types"; +import { TrainerItemEffect } from "#items/trainer-item"; import { PokemonMove } from "#moves/pokemon-move"; import { showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig, EnemyPokemonConfig } from "#mystery-encounters/encounter-phase-utils"; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index fc8470727fa..74b81d3df1b 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -2,7 +2,6 @@ import type { Battle } from "#app/battle"; import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/constants"; import { timedEventManager } from "#app/global-event-manager"; import { globalScene } from "#app/global-scene"; -import type { HeldItemConfiguration, PokemonItemMap } from "#app/items/held-item-data-types"; import { getPokemonNameWithAffix } from "#app/messages"; import { BiomePoolTier, biomeLinks } from "#balance/biomes"; import { initMoveAnim, loadMoveAnimAssets } from "#data/battle-anims"; @@ -32,6 +31,7 @@ import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; import { EnemyPokemon } from "#field/pokemon"; import { Trainer } from "#field/trainer"; +import type { HeldItemConfiguration, PokemonItemMap } from "#items/held-item-data-types"; import type { CustomModifierSettings, ModifierType } from "#modifiers/modifier-type"; import { getPartyLuckValue, ModifierTypeGenerator, ModifierTypeOption } from "#modifiers/modifier-type"; import { PokemonMove } from "#moves/pokemon-move"; diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index ce440826ec2..967928f4d43 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import { PokeballType } from "#enums/pokeball"; +import { TrainerItemEffect } from "#items/trainer-item"; import { NumberHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/field/arena.ts b/src/field/arena.ts index 1a6e7da3f66..3c498567598 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -1,7 +1,5 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; -import { applyHeldItems } from "#app/items/all-held-items"; -import { HeldItemEffect } from "#app/items/held-item"; import Overrides from "#app/overrides"; import type { BiomeTierTrainerPools, PokemonPools } from "#balance/biomes"; import { BiomePoolTier, biomePokemonPools, biomeTrainerPools } from "#balance/biomes"; @@ -30,6 +28,8 @@ import { TrainerType } from "#enums/trainer-type"; import { WeatherType } from "#enums/weather-type"; import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#events/arena"; import type { Pokemon } from "#field/pokemon"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; import type { Move } from "#moves/move"; import { type Constructor, isNullOrUndefined, NumberHolder, randSeedInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index 5f506ff0763..b6daed00920 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -6,6 +6,8 @@ import { isItemInCategory, isItemInRequested, } from "#app/enums/held-item-id"; +import { getTypedEntries, getTypedKeys } from "#app/utils/common"; +import type { FormChangeItem } from "#enums/form-change-item"; import { type FormChangeItemPropertyMap, type FormChangeItemSpecs, @@ -14,9 +16,7 @@ import { type HeldItemSaveData, type HeldItemSpecs, isHeldItemSpecs, -} from "#app/items/held-item-data-types"; -import { getTypedEntries, getTypedKeys } from "#app/utils/common"; -import type { FormChangeItem } from "#enums/form-change-item"; +} from "#items/held-item-data-types"; export class PokemonItemManager { public heldItems: HeldItemDataMap; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 88974c498dc..bb7d3f84e05 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4,11 +4,6 @@ import type { AnySound, BattleScene } from "#app/battle-scene"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { timedEventManager } from "#app/global-event-manager"; import { globalScene } from "#app/global-scene"; -import { applyHeldItems } from "#app/items/all-held-items"; -import { HeldItemEffect } from "#app/items/held-item"; -import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; -import { assignItemsFromConfiguration } from "#app/items/held-item-pool"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { speciesEggMoves } from "#balance/egg-moves"; @@ -116,6 +111,11 @@ import { UiMode } from "#enums/ui-mode"; import { WeatherType } from "#enums/weather-type"; import { doShinySparkleAnim } from "#field/anims"; import { PokemonItemManager } from "#field/pokemon-held-item-manager"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; +import type { HeldItemConfiguration } from "#items/held-item-data-types"; +import { assignItemsFromConfiguration } from "#items/held-item-pool"; +import { TrainerItemEffect } from "#items/trainer-item"; import { applyMoveAttrs } from "#moves/apply-attrs"; import type { Move } from "#moves/move"; import { getMoveTargets } from "#moves/move-utils"; diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 06eefb3af50..469a1d37e04 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -1,5 +1,4 @@ import { globalScene } from "#app/global-scene"; -import type { TrainerItemConfiguration } from "#app/items/trainer-item-data-types"; import { pokemonPrevolutions } from "#balance/pokemon-evolutions"; import { signatureSpecies } from "#balance/signature-species"; import { ArenaTrapTag } from "#data/arena-tag"; @@ -13,6 +12,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerType } from "#enums/trainer-type"; import { TrainerVariant } from "#enums/trainer-variant"; import type { EnemyPokemon } from "#field/pokemon"; +import type { TrainerItemConfiguration } from "#items/trainer-item-data-types"; import { getIsInitialized, initI18n } from "#plugins/i18n"; import type { TrainerPartyTemplate } from "#trainers/TrainerPartyTemplate"; import { TrainerPartyCompoundTemplate, trainerPartyTemplates } from "#trainers/TrainerPartyTemplate"; diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index a1d93250d37..d2f08426e96 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -1,8 +1,8 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import type { NumberHolder } from "#app/utils/common"; import { HeldItemId, HeldItemNames } from "#enums/held-item-id"; import { PokemonType } from "#enums/pokemon-type"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import i18next from "i18next"; export interface AttackTypeBoostParams { diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts index 4387009761e..336eca8e880 100644 --- a/src/items/held-items/berry.ts +++ b/src/items/held-items/berry.ts @@ -2,10 +2,10 @@ import { getBerryEffectDescription, getBerryEffectFunc, getBerryName, getBerryPr import { BerryUsedEvent } from "#app/events/battle-scene"; import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import { ConsumableHeldItem, HeldItemEffect } from "#app/items/held-item"; import { BooleanHolder } from "#app/utils/common"; import { BerryType } from "#enums/berry-type"; import { HeldItemId } from "#enums/held-item-id"; +import { ConsumableHeldItem, HeldItemEffect } from "#items/held-item"; import { TrainerItemEffect } from "../trainer-item"; interface BerryTypeToHeldItemMap { diff --git a/src/items/held-items/bypass-speed-chance.ts b/src/items/held-items/bypass-speed-chance.ts index 99bc861dc3e..b61186ba9d8 100644 --- a/src/items/held-items/bypass-speed-chance.ts +++ b/src/items/held-items/bypass-speed-chance.ts @@ -1,9 +1,9 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import { getPokemonNameWithAffix } from "#app/messages"; import type { BooleanHolder } from "#app/utils/common"; import { Command } from "#enums/command"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import i18next from "i18next"; export interface BypassSpeedChanceParams { diff --git a/src/items/held-items/field-effect.ts b/src/items/held-items/field-effect.ts index 2b25a9d3ec7..de680226623 100644 --- a/src/items/held-items/field-effect.ts +++ b/src/items/held-items/field-effect.ts @@ -1,6 +1,6 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import type { NumberHolder } from "#app/utils/common"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; export interface FieldEffectParams { pokemon: Pokemon; diff --git a/src/items/held-items/flinch-chance.ts b/src/items/held-items/flinch-chance.ts index 7a795226860..904fceb1484 100644 --- a/src/items/held-items/flinch-chance.ts +++ b/src/items/held-items/flinch-chance.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import type { BooleanHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; export interface FlinchChanceParams { /** The pokemon with the item */ diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts index ede9f196ecb..e8bf46d09dd 100644 --- a/src/items/held-items/hit-heal.ts +++ b/src/items/held-items/hit-heal.ts @@ -1,9 +1,9 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { toDmgValue } from "#app/utils/common"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import i18next from "i18next"; export interface HitHealParams { diff --git a/src/items/held-items/multi-hit.ts b/src/items/held-items/multi-hit.ts index 3353c2b596b..1a25b394287 100644 --- a/src/items/held-items/multi-hit.ts +++ b/src/items/held-items/multi-hit.ts @@ -1,8 +1,8 @@ import { allMoves } from "#app/data/data-lists"; import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import { isNullOrUndefined, type NumberHolder } from "#app/utils/common"; import type { MoveId } from "#enums/move-id"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import i18next from "i18next"; export interface MultiHitParams { diff --git a/src/items/held-items/survive-chance.ts b/src/items/held-items/survive-chance.ts index d18247350fa..fd2d7876793 100644 --- a/src/items/held-items/survive-chance.ts +++ b/src/items/held-items/survive-chance.ts @@ -1,8 +1,8 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import { getPokemonNameWithAffix } from "#app/messages"; import type { BooleanHolder } from "#app/utils/common"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import i18next from "i18next"; export interface SurviveChanceParams { diff --git a/src/items/held-items/turn-end-heal.ts b/src/items/held-items/turn-end-heal.ts index b2e267ffedd..5316b728e40 100644 --- a/src/items/held-items/turn-end-heal.ts +++ b/src/items/held-items/turn-end-heal.ts @@ -1,9 +1,9 @@ import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { toDmgValue } from "#app/utils/common"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import i18next from "i18next"; export interface TurnEndHealParams { diff --git a/src/items/held-items/turn-end-status.ts b/src/items/held-items/turn-end-status.ts index d346d54dca4..630347c6a54 100644 --- a/src/items/held-items/turn-end-status.ts +++ b/src/items/held-items/turn-end-status.ts @@ -1,7 +1,7 @@ import type Pokemon from "#app/field/pokemon"; -import { HeldItem, HeldItemEffect } from "#app/items/held-item"; import type { HeldItemId } from "#enums/held-item-id"; import type { StatusEffect } from "#enums/status-effect"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; export interface TurnEndStatusParams { /** The pokemon with the item */ diff --git a/src/items/trainer-item-manager.ts b/src/items/trainer-item-manager.ts index 0bb49be2e12..4170761665f 100644 --- a/src/items/trainer-item-manager.ts +++ b/src/items/trainer-item-manager.ts @@ -1,13 +1,13 @@ import { allTrainerItems } from "#app/data/data-lists"; import type { TrainerItemId } from "#app/enums/trainer-item-id"; +import { getTypedEntries, getTypedKeys } from "#app/utils/common"; import { isTrainerItemSpecs, type TrainerItemConfiguration, type TrainerItemDataMap, type TrainerItemSaveData, type TrainerItemSpecs, -} from "#app/items/trainer-item-data-types"; -import { getTypedEntries, getTypedKeys } from "#app/utils/common"; +} from "#items/trainer-item-data-types"; export class TrainerItemManager { public trainerItems: TrainerItemDataMap; diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 082287407b4..99229915da1 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -1,9 +1,5 @@ import { initAbilities } from "#abilities/ability"; import { timedEventManager } from "#app/global-event-manager"; -import { initHeldItems } from "#app/items/all-held-items"; -import { initTrainerItems } from "#app/items/all-trainer-items"; -import { initHeldItemPools } from "#app/items/init-held-item-pools"; -import { initTrainerItemPools } from "#app/items/init-trainer-item-pools"; import { SceneBase } from "#app/scene-base"; import { isMobile } from "#app/touch-controls"; import { initBiomes } from "#balance/biomes"; @@ -16,6 +12,10 @@ import { initSpecies } from "#data/pokemon-species"; import { BiomeId } from "#enums/biome-id"; import { GachaType } from "#enums/gacha-types"; import { getBiomeHasProps } from "#field/arena"; +import { initHeldItems } from "#items/all-held-items"; +import { initTrainerItems } from "#items/all-trainer-items"; +import { initHeldItemPools } from "#items/init-held-item-pools"; +import { initTrainerItemPools } from "#items/init-trainer-item-pools"; import { initModifierPools } from "#modifiers/init-modifier-pools"; import { initModifierTypes } from "#modifiers/modifier-type"; import { initMoves } from "#moves/move"; diff --git a/src/modifier/init-modifier-pools.ts b/src/modifier/init-modifier-pools.ts index 37809fbc371..88ebfec5a4d 100644 --- a/src/modifier/init-modifier-pools.ts +++ b/src/modifier/init-modifier-pools.ts @@ -4,7 +4,6 @@ import type { initModifierTypes } from "#modifiers/modifier-type"; import { timedEventManager } from "#app/global-event-manager"; import { globalScene } from "#app/global-scene"; -import type { TurnEndStatusHeldItem } from "#app/items/held-items/turn-end-status"; import { pokemonEvolutions } from "#balance/pokemon-evolutions"; import { allHeldItems, allTrainerItems, modifierTypes } from "#data/data-lists"; import { MAX_PER_TYPE_POKEBALLS } from "#data/pokeball"; @@ -18,6 +17,7 @@ import { StatusEffect } from "#enums/status-effect"; import { TrainerItemId } from "#enums/trainer-item-id"; import { Unlockables } from "#enums/unlockables"; import type { Pokemon } from "#field/pokemon"; +import type { TurnEndStatusHeldItem } from "#items/turn-end-status"; import { modifierPool } from "#modifiers/modifier-pools"; import { WeightedModifierType } from "#modifiers/modifier-type"; import type { WeightedModifierTypeWeightFunc } from "#types/modifier-types"; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 68117b126de..da589fb70b3 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1,16 +1,6 @@ import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; import { timedEventManager } from "#app/global-event-manager"; import { globalScene } from "#app/global-scene"; -import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#app/items/held-item-pool"; -import { attackTypeToHeldItem } from "#app/items/held-items/attack-type-booster"; -import { permanentStatToHeldItem, statBoostItems } from "#app/items/held-items/base-stat-booster"; -import { berryTypeToHeldItem } from "#app/items/held-items/berry"; -import { - SPECIES_STAT_BOOSTER_ITEMS, - type SpeciesStatBoosterItemId, - type SpeciesStatBoostHeldItem, -} from "#app/items/held-items/stat-booster"; -import { TrainerItemEffect, tempStatToTrainerItem } from "#app/items/trainer-item"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { EvolutionItem, pokemonEvolutions } from "#balance/pokemon-evolutions"; @@ -36,6 +26,16 @@ import type { PermanentStat, TempBattleStat } from "#enums/stat"; import { Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { TrainerItemId } from "#enums/trainer-item-id"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; +import { attackTypeToHeldItem } from "#items/attack-type-booster"; +import { permanentStatToHeldItem, statBoostItems } from "#items/base-stat-booster"; +import { berryTypeToHeldItem } from "#items/berry"; +import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#items/held-item-pool"; +import { + SPECIES_STAT_BOOSTER_ITEMS, + type SpeciesStatBoosterItemId, + type SpeciesStatBoostHeldItem, +} from "#items/stat-booster"; +import { TrainerItemEffect, tempStatToTrainerItem } from "#items/trainer-item"; import { AddPokeballModifier, AddVoucherModifier, diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 9d6ebfbcc33..15c42fa15e8 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,8 +1,4 @@ import { globalScene } from "#app/global-scene"; -import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; -import { assignItemsFromConfiguration } from "#app/items/held-item-pool"; -import { TrainerItemEffect } from "#app/items/trainer-item"; -import type { TrainerItemConfiguration } from "#app/items/trainer-item-data-types"; import Overrides from "#app/overrides"; import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#balance/pokemon-evolutions"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#balance/starters"; @@ -15,6 +11,10 @@ import type { PokeballType } from "#enums/pokeball"; import type { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; +import type { HeldItemConfiguration } from "#items/held-item-data-types"; +import { assignItemsFromConfiguration } from "#items/held-item-pool"; +import { TrainerItemEffect } from "#items/trainer-item"; +import type { TrainerItemConfiguration } from "#items/trainer-item-data-types"; import type { EvolutionItemModifierType, ModifierType, diff --git a/src/overrides.ts b/src/overrides.ts index 527a24ab5fe..0bf44d59329 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -1,6 +1,4 @@ import { type PokeballCounts } from "#app/battle-scene"; -import { HeldItemConfiguration } from "#app/items/held-item-data-types"; -import { TrainerItemConfiguration } from "#app/items/trainer-item-data-types"; import { EvolutionItem } from "#balance/pokemon-evolutions"; import { Gender } from "#data/gender"; import { AbilityId } from "#enums/ability-id"; @@ -23,6 +21,8 @@ import { TrainerType } from "#enums/trainer-type"; import { Unlockables } from "#enums/unlockables"; import { VariantTier } from "#enums/variant-tier"; import { WeatherType } from "#enums/weather-type"; +import { HeldItemConfiguration } from "#items/held-item-data-types"; +import { TrainerItemConfiguration } from "#items/trainer-item-data-types"; import { type ModifierOverride } from "#modifiers/modifier-type"; import { Variant } from "#sprites/variant"; diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-buff-modifier-phase.ts index d05437c42dd..5d3d529420b 100644 --- a/src/phases/add-enemy-buff-modifier-phase.ts +++ b/src/phases/add-enemy-buff-modifier-phase.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; -import { assignEnemyBuffTokenForWave } from "#app/items/trainer-item-pool"; import { Phase } from "#app/phase"; import { RewardTier } from "#enums/reward-tier"; +import { assignEnemyBuffTokenForWave } from "#items/trainer-item-pool"; export class AddEnemyBuffModifierPhase extends Phase { public readonly phaseName = "AddEnemyBuffModifierPhase"; diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index de66d143e85..e968201be73 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -1,12 +1,12 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; -import { applyHeldItems } from "#app/items/all-held-items"; -import { HeldItemEffect } from "#app/items/held-item"; import { getPokemonNameWithAffix } from "#app/messages"; import { allHeldItems } from "#data/data-lists"; import { HeldItemCategoryId, isItemInCategory } from "#enums/held-item-id"; import { CommonAnim } from "#enums/move-anims-common"; import type { Pokemon } from "#field/pokemon"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; import { FieldPhase } from "#phases/field-phase"; import { BooleanHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/phases/exp-phase.ts b/src/phases/exp-phase.ts index 3f3b04fed2f..4ab58bdbf9c 100644 --- a/src/phases/exp-phase.ts +++ b/src/phases/exp-phase.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import { getPokemonNameWithAffix } from "#app/messages"; +import { TrainerItemEffect } from "#items/trainer-item"; import { PlayerPartyMemberPokemonPhase } from "#phases/player-party-member-pokemon-phase"; import { NumberHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 12428c7b9ce..1cea69a6baf 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -1,7 +1,5 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; -import { applyHeldItems } from "#app/items/all-held-items"; -import { HeldItemEffect } from "#app/items/held-item"; import { getPokemonNameWithAffix } from "#app/messages"; import { FRIENDSHIP_LOSS_FROM_FAINT } from "#balance/starters"; import { allMoves } from "#data/data-lists"; @@ -16,6 +14,8 @@ import { HitResult } from "#enums/hit-result"; import { StatusEffect } from "#enums/status-effect"; import { SwitchType } from "#enums/switch-type"; import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; import { PokemonMove } from "#moves/pokemon-move"; import { PokemonPhase } from "#phases/pokemon-phase"; import { isNullOrUndefined } from "#utils/common"; diff --git a/src/phases/money-reward-phase.ts b/src/phases/money-reward-phase.ts index 48df878dee0..1b6b4ded6b1 100644 --- a/src/phases/money-reward-phase.ts +++ b/src/phases/money-reward-phase.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import { ArenaTagType } from "#enums/arena-tag-type"; +import { TrainerItemEffect } from "#items/trainer-item"; import { BattlePhase } from "#phases/battle-phase"; import { NumberHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 4fd866f83dd..51af8040a76 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -1,8 +1,5 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; -import { applyHeldItems } from "#app/items/all-held-items"; -import { HeldItemEffect } from "#app/items/held-item"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import { getPokemonNameWithAffix } from "#app/messages"; import type { Phase } from "#app/phase"; import { ConditionalProtectTag } from "#data/arena-tag"; @@ -25,6 +22,9 @@ import { MoveResult } from "#enums/move-result"; import { isReflected, isVirtual, MoveUseMode } from "#enums/move-use-mode"; import { PokemonType } from "#enums/pokemon-type"; import type { Pokemon } from "#field/pokemon"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; +import { TrainerItemEffect } from "#items/trainer-item"; import { applyFilteredMoveAttrs, applyMoveAttrs } from "#moves/apply-attrs"; import type { Move, MoveAttr } from "#moves/move"; import { getMoveTargets, isFieldTargeted } from "#moves/move-utils"; diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts index 684bdd41130..4892bf685fe 100644 --- a/src/phases/pokemon-heal-phase.ts +++ b/src/phases/pokemon-heal-phase.ts @@ -1,5 +1,4 @@ import { globalScene } from "#app/global-scene"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import { getPokemonNameWithAffix } from "#app/messages"; import type { HealBlockTag } from "#data/battler-tags"; import { getStatusEffectHealText } from "#data/status-effect"; @@ -8,6 +7,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { HitResult } from "#enums/hit-result"; import { CommonAnim } from "#enums/move-anims-common"; import { StatusEffect } from "#enums/status-effect"; +import { TrainerItemEffect } from "#items/trainer-item"; import { CommonAnimPhase } from "#phases/common-anim-phase"; import { HealAchv } from "#system/achv"; import { NumberHolder } from "#utils/common"; diff --git a/src/phases/select-reward-phase.ts b/src/phases/select-reward-phase.ts index 2c70d357686..b86631c8ec2 100644 --- a/src/phases/select-reward-phase.ts +++ b/src/phases/select-reward-phase.ts @@ -1,9 +1,9 @@ import { globalScene } from "#app/global-scene"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import Overrides from "#app/overrides"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import type { RewardTier } from "#enums/reward-tier"; import { UiMode } from "#enums/ui-mode"; +import { TrainerItemEffect } from "#items/trainer-item"; import type { Modifier } from "#modifiers/modifier"; import type { CustomModifierSettings, ModifierType, ModifierTypeOption } from "#modifiers/modifier-type"; import { diff --git a/src/phases/show-party-exp-bar-phase.ts b/src/phases/show-party-exp-bar-phase.ts index 23b44d27b32..4599c4b37cc 100644 --- a/src/phases/show-party-exp-bar-phase.ts +++ b/src/phases/show-party-exp-bar-phase.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { ExpNotification } from "#enums/exp-notification"; +import { TrainerItemEffect } from "#items/trainer-item"; import { PlayerPartyMemberPokemonPhase } from "#phases/player-party-member-pokemon-phase"; import { NumberHolder } from "#utils/common"; diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 8d63daef471..c47314e268e 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -1,7 +1,5 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; -import { applyHeldItems } from "#app/items/all-held-items"; -import { HeldItemEffect } from "#app/items/held-item"; import { getPokemonNameWithAffix } from "#app/messages"; import { handleTutorial, Tutorial } from "#app/tutorial"; import type { ArenaTag } from "#data/arena-tag"; @@ -12,6 +10,8 @@ import { ArenaTagType } from "#enums/arena-tag-type"; import type { BattlerIndex } from "#enums/battler-index"; import { type BattleStat, getStatKey, getStatStageChangeDescriptionKey, Stat } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; import { PokemonPhase } from "#phases/pokemon-phase"; import type { ConditionalUserFieldProtectStatAbAttrParams, PreStatStageChangeAbAttrParams } from "#types/ability-types"; import { BooleanHolder, isNullOrUndefined, NumberHolder } from "#utils/common"; diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 7139d4f9b46..383011b0265 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -1,7 +1,6 @@ import { loggedInUser } from "#app/account"; import { GameMode, getGameMode } from "#app/game-mode"; import { globalScene } from "#app/global-scene"; -import { assignDailyRunStarterHeldItems } from "#app/items/held-item-pool"; import Overrides from "#app/overrides"; import { Phase } from "#app/phase"; import { fetchDailyRunSeed, getDailyRunStarters } from "#data/daily-run"; @@ -12,6 +11,7 @@ import { TrainerItemId } from "#enums/trainer-item-id"; import { UiMode } from "#enums/ui-mode"; import { Unlockables } from "#enums/unlockables"; import { getBiomeKey } from "#field/arena"; +import { assignDailyRunStarterHeldItems } from "#items/held-item-pool"; import type { SessionSaveData } from "#system/game-data"; import { vouchers } from "#system/voucher"; import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 3216d3d1062..af34011780b 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -1,14 +1,14 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; -import { applyHeldItems } from "#app/items/all-held-items"; -import { HeldItemEffect } from "#app/items/held-item"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import { getPokemonNameWithAffix } from "#app/messages"; import { TerrainType } from "#data/terrain"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { WeatherType } from "#enums/weather-type"; import { TurnEndEvent } from "#events/battle-scene"; import type { Pokemon } from "#field/pokemon"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; +import { TrainerItemEffect } from "#items/trainer-item"; import { FieldPhase } from "#phases/field-phase"; import i18next from "i18next"; diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index b24ca87dee4..044987808a5 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -1,7 +1,5 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; -import { applyHeldItems } from "#app/items/all-held-items"; -import { HeldItemEffect } from "#app/items/held-item"; import { TrickRoomTag } from "#data/arena-tag"; import { allMoves } from "#data/data-lists"; import { BattlerIndex } from "#enums/battler-index"; @@ -9,6 +7,8 @@ import { Command } from "#enums/command"; import { Stat } from "#enums/stat"; import { SwitchType } from "#enums/switch-type"; import type { Pokemon } from "#field/pokemon"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; import { PokemonMove } from "#moves/pokemon-move"; import { FieldPhase } from "#phases/field-phase"; import { BooleanHolder, randSeedShuffle } from "#utils/common"; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index f080ea29af9..07645e2c3bf 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -5,7 +5,6 @@ import { defaultStarterSpecies, saveKey } from "#app/constants"; import { getGameMode } from "#app/game-mode"; import { globalScene } from "#app/global-scene"; import { bypassLogin } from "#app/global-vars/bypass-login"; -import type { TrainerItemConfiguration, TrainerItemSaveData } from "#app/items/trainer-item-data-types"; import Overrides from "#app/overrides"; import { Tutorial } from "#app/tutorial"; import { speciesEggMoves } from "#balance/egg-moves"; @@ -37,6 +36,7 @@ import { Unlockables } from "#enums/unlockables"; import { WeatherType } from "#enums/weather-type"; import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#events/arena"; import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; +import type { TrainerItemConfiguration, TrainerItemSaveData } from "#items/trainer-item-data-types"; import { MysteryEncounterSaveData } from "#mystery-encounters/mystery-encounter-save-data"; import type { Variant } from "#sprites/variant"; import { achvs } from "#system/achv"; diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 10a9ac4286a..d5706c34dc7 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -1,6 +1,4 @@ import { globalScene } from "#app/global-scene"; -import type { HeldItemSaveData } from "#app/items/held-item-data-types"; -import { saveDataToConfig } from "#app/items/held-item-pool"; import type { Gender } from "#data/gender"; import { CustomPokemonData, PokemonBattleData, PokemonSummonData } from "#data/pokemon-data"; import { getPokemonSpeciesForm } from "#data/pokemon-species"; @@ -14,6 +12,8 @@ import type { PokemonType } from "#enums/pokemon-type"; import type { SpeciesId } from "#enums/species-id"; import { TrainerSlot } from "#enums/trainer-slot"; import { EnemyPokemon, Pokemon } from "#field/pokemon"; +import type { HeldItemSaveData } from "#items/held-item-data-types"; +import { saveDataToConfig } from "#items/held-item-pool"; import { PokemonMove } from "#moves/pokemon-move"; import type { Variant } from "#sprites/variant"; import { getPokemonSpecies } from "#utils/pokemon-utils"; diff --git a/src/ui/item-bar-ui.ts b/src/ui/item-bar-ui.ts index 012a9760038..e446b7c0bc0 100644 --- a/src/ui/item-bar-ui.ts +++ b/src/ui/item-bar-ui.ts @@ -1,8 +1,8 @@ import { allHeldItems, allTrainerItems } from "#app/data/data-lists"; import type { Pokemon } from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import { heldItemSortFunc, trainerItemSortFunc } from "#app/items/item-utility"; -import type { TrainerItemManager } from "#app/items/trainer-item-manager"; +import { heldItemSortFunc, trainerItemSortFunc } from "#items/item-utility"; +import type { TrainerItemManager } from "#items/trainer-item-manager"; const iconOverflowIndex = 24; diff --git a/src/ui/reward-select-ui-handler.ts b/src/ui/reward-select-ui-handler.ts index 1420da3a7fa..6a630323361 100644 --- a/src/ui/reward-select-ui-handler.ts +++ b/src/ui/reward-select-ui-handler.ts @@ -1,5 +1,4 @@ import { globalScene } from "#app/global-scene"; -import { TrainerItemEffect } from "#app/items/trainer-item"; import Overrides from "#app/overrides"; import { handleTutorial, Tutorial } from "#app/tutorial"; import { allMoves } from "#data/data-lists"; @@ -9,6 +8,7 @@ import type { PokeballType } from "#enums/pokeball"; import { ShopCursorTarget } from "#enums/shop-cursor-target"; import { TrainerItemId } from "#enums/trainer-item-id"; import { UiMode } from "#enums/ui-mode"; +import { TrainerItemEffect } from "#items/trainer-item"; import type { ModifierTypeOption } from "#modifiers/modifier-type"; import { getPlayerShopModifierTypeOptionsForWave, TmModifierType } from "#modifiers/modifier-type"; import { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index a3e8d3ae36c..96b1500f7dd 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -1,5 +1,4 @@ import { globalScene } from "#app/global-scene"; -import { heldItemSortFunc } from "#app/items/item-utility"; import { getBiomeName } from "#balance/biomes"; import { allHeldItems, allTrainerItems } from "#data/data-lists"; import { getNatureName, getNatureStatMultiplier } from "#data/nature"; @@ -16,6 +15,7 @@ import { PokemonType } from "#enums/pokemon-type"; import type { SpeciesId } from "#enums/species-id"; import { TrainerVariant } from "#enums/trainer-variant"; import { UiMode } from "#enums/ui-mode"; +import { heldItemSortFunc } from "#items/item-utility"; import { getLuckString, getLuckTextTint } from "#modifiers/modifier-type"; import { getVariantTint } from "#sprites/variant"; import type { SessionSaveData } from "#system/game-data"; diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index a3b3538bdd7..3a8d9e316d4 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -2,7 +2,6 @@ import type { Ability } from "#abilities/ability"; import { loggedInUser } from "#app/account"; import { globalScene } from "#app/global-scene"; import { starterColors } from "#app/global-vars/starter-colors"; -import { heldItemSortFunc } from "#app/items/item-utility"; import { getBiomeName } from "#balance/biomes"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#balance/starters"; import { allHeldItems } from "#data/data-lists"; @@ -20,6 +19,7 @@ import { getStatKey, PERMANENT_STATS, Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon } from "#field/pokemon"; +import { heldItemSortFunc } from "#items/item-utility"; import type { Move } from "#moves/move"; import type { PokemonMove } from "#moves/pokemon-move"; import type { Variant } from "#sprites/variant"; diff --git a/test/items/eviolite.test.ts b/test/items/eviolite.test.ts index 66dbc2742f5..e38cba1ca50 100644 --- a/test/items/eviolite.test.ts +++ b/test/items/eviolite.test.ts @@ -1,8 +1,8 @@ -import { applyHeldItems } from "#app/items/all-held-items"; -import { HeldItemEffect } from "#app/items/held-item"; import { HeldItemId } from "#enums/held-item-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; import { GameManager } from "#test/testUtils/gameManager"; import { NumberHolder, randItem } from "#utils/common"; import Phaser from "phaser"; diff --git a/test/items/exp_booster.test.ts b/test/items/exp_booster.test.ts index 52a4b4ee2bb..cea7dadaef0 100644 --- a/test/items/exp_booster.test.ts +++ b/test/items/exp_booster.test.ts @@ -1,7 +1,7 @@ -import { applyHeldItems } from "#app/items/all-held-items"; -import { HeldItemEffect } from "#app/items/held-item"; import { AbilityId } from "#enums/ability-id"; import { HeldItemId } from "#enums/held-item-id"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; import { GameManager } from "#test/testUtils/gameManager"; import { NumberHolder } from "#utils/common"; import Phaser from "phaser"; diff --git a/test/items/light_ball.test.ts b/test/items/light_ball.test.ts index 03baa62a1a5..44a0a50fe9e 100644 --- a/test/items/light_ball.test.ts +++ b/test/items/light_ball.test.ts @@ -1,8 +1,8 @@ -import { applyHeldItems } from "#app/items/all-held-items"; -import { HeldItemEffect } from "#app/items/held-item"; import { HeldItemId } from "#enums/held-item-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; import i18next from "#plugins/i18n"; import { GameManager } from "#test/testUtils/gameManager"; import { NumberHolder } from "#utils/common"; diff --git a/test/items/reviver_seed.test.ts b/test/items/reviver_seed.test.ts index f796e56ff56..eab06287744 100644 --- a/test/items/reviver_seed.test.ts +++ b/test/items/reviver_seed.test.ts @@ -1,4 +1,3 @@ -import type { InstantReviveHeldItem } from "#app/items/held-items/instant-revive"; import { allHeldItems, allMoves } from "#data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { BattlerIndex } from "#enums/battler-index"; @@ -6,6 +5,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; +import type { InstantReviveHeldItem } from "#items/instant-revive"; import { GameManager } from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 4384edee442..6264882f8cd 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -1,5 +1,4 @@ import type { BattleScene } from "#app/battle-scene"; -import { getHeldItemTier } from "#app/items/held-item-tiers"; import * as BattleAnims from "#data/battle-anims"; import { AbilityId } from "#enums/ability-id"; import { BiomeId } from "#enums/biome-id"; @@ -14,6 +13,7 @@ import { RewardTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import { UiMode } from "#enums/ui-mode"; +import { getHeldItemTier } from "#items/held-item-tiers"; import { PokemonMove } from "#moves/pokemon-move"; import { ClowningAroundEncounter } from "#mystery-encounters/clowning-around-encounter"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index 6ab5b9b6467..c3988ffe18f 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -1,6 +1,4 @@ import type { BattleScene } from "#app/battle-scene"; -import { applyHeldItems } from "#app/items/all-held-items"; -import { HeldItemEffect } from "#app/items/held-item"; import * as BattleAnims from "#data/battle-anims"; import { CustomPokemonData } from "#data/pokemon-data"; import { AbilityId } from "#enums/ability-id"; @@ -14,6 +12,8 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Nature } from "#enums/nature"; import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; import { PokemonMove } from "#moves/pokemon-move"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; diff --git a/test/testUtils/helpers/overridesHelper.ts b/test/testUtils/helpers/overridesHelper.ts index 44ae5ef163d..9fc13407844 100644 --- a/test/testUtils/helpers/overridesHelper.ts +++ b/test/testUtils/helpers/overridesHelper.ts @@ -2,8 +2,6 @@ import type { NewArenaEvent } from "#events/battle-scene"; /** biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ -import type { HeldItemConfiguration } from "#app/items/held-item-data-types"; -import type { TrainerItemConfiguration } from "#app/items/trainer-item-data-types"; import type { BattleStyle, RandomTrainerOverride } from "#app/overrides"; import Overrides, { defaultOverrides } from "#app/overrides"; import { AbilityId } from "#enums/ability-id"; @@ -17,6 +15,8 @@ import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; import type { Unlockables } from "#enums/unlockables"; import { WeatherType } from "#enums/weather-type"; +import type { HeldItemConfiguration } from "#items/held-item-data-types"; +import type { TrainerItemConfiguration } from "#items/trainer-item-data-types"; import type { ModifierOverride } from "#modifiers/modifier-type"; import type { Variant } from "#sprites/variant"; import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper"; diff --git a/test/testUtils/testFileInitialization.ts b/test/testUtils/testFileInitialization.ts index 9dce5458194..15dba3ea78d 100644 --- a/test/testUtils/testFileInitialization.ts +++ b/test/testUtils/testFileInitialization.ts @@ -1,15 +1,15 @@ import { initAbilities } from "#abilities/ability"; import { initLoggedInUser } from "#app/account"; import { SESSION_ID_COOKIE_NAME } from "#app/constants"; -import { initHeldItems } from "#app/items/all-held-items"; -import { initTrainerItems } from "#app/items/all-trainer-items"; -import { initHeldItemPools } from "#app/items/init-held-item-pools"; -import { initTrainerItemPools } from "#app/items/init-trainer-item-pools"; import { initBiomes } from "#balance/biomes"; import { initEggMoves } from "#balance/egg-moves"; import { initPokemonPrevolutions, initPokemonStarters } from "#balance/pokemon-evolutions"; import { initPokemonForms } from "#data/pokemon-forms"; import { initSpecies } from "#data/pokemon-species"; +import { initHeldItems } from "#items/all-held-items"; +import { initTrainerItems } from "#items/all-trainer-items"; +import { initHeldItemPools } from "#items/init-held-item-pools"; +import { initTrainerItemPools } from "#items/init-trainer-item-pools"; import { initModifierPools } from "#modifiers/init-modifier-pools"; import { initModifierTypes } from "#modifiers/modifier-type"; import { initMoves } from "#moves/move"; diff --git a/tsconfig.json b/tsconfig.json index f41b3e5895a..5bbbf317bc4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,7 @@ "#events/*": ["./events/*.ts"], "#field/*": ["./field/*.ts"], "#inputs/*": ["./configs/inputs/*.ts"], + "#items/*": ["./items/held-items/*.ts", "./items/*.ts"], "#modifiers/*": ["./modifier/*.ts"], "#moves/*": ["./data/moves/*.ts"], "#mystery-encounters/*": [ From ff5d891c6e021ea611ad921fb2f453eca17e423e Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 13 Jul 2025 02:52:02 -0700 Subject: [PATCH 24/39] Fix some imports --- .../encounters/an-offer-you-cant-refuse-encounter.ts | 3 +-- .../encounters/shady-vitamin-dealer-encounter.ts | 2 +- src/field/pokemon-held-item-manager.ts | 8 ++++---- src/items/all-held-items.ts | 4 ++-- src/items/all-trainer-items.ts | 2 +- src/items/apply-trainer-items.ts | 2 +- src/items/held-item-data-types.ts | 3 ++- src/items/held-item-pool.ts | 7 +++---- src/items/held-item.ts | 4 ++-- src/items/held-items/accuracy-booster.ts | 4 ++-- src/items/held-items/attack-type-booster.ts | 4 ++-- src/items/held-items/base-stat-booster.ts | 2 +- src/items/held-items/base-stat-flat.ts | 2 +- src/items/held-items/base-stat-total.ts | 2 +- src/items/held-items/baton.ts | 4 ++-- src/items/held-items/berry.ts | 8 ++++---- src/items/held-items/bypass-speed-chance.ts | 4 ++-- src/items/held-items/crit-booster.ts | 4 ++-- src/items/held-items/damage-money-reward.ts | 4 ++-- src/items/held-items/evo-tracker.ts | 2 +- src/items/held-items/exp-booster.ts | 4 ++-- src/items/held-items/field-effect.ts | 4 ++-- src/items/held-items/flinch-chance.ts | 4 ++-- src/items/held-items/friendship-booster.ts | 4 ++-- src/items/held-items/hit-heal.ts | 8 ++++---- src/items/held-items/incrementing-stat.ts | 4 ++-- src/items/held-items/instant-revive.ts | 8 ++++---- src/items/held-items/item-steal.ts | 6 +++--- src/items/held-items/multi-hit.ts | 6 +++--- src/items/held-items/nature-weight-booster.ts | 4 ++-- src/items/held-items/reset-negative-stat-stage.ts | 2 +- src/items/held-items/stat-booster.ts | 6 +++--- src/items/held-items/survive-chance.ts | 4 ++-- src/items/held-items/turn-end-heal.ts | 6 +++--- src/items/held-items/turn-end-status.ts | 2 +- src/items/item-utility.ts | 3 +-- src/items/modifier-to-item-migrator-utils.ts | 1 - src/items/trainer-item-manager.ts | 6 +++--- src/items/trainer-item-pool.ts | 4 ++-- src/items/trainer-item.ts | 8 ++++---- src/phases/reward-phase.ts | 6 +++--- src/ui/item-bar-ui.ts | 4 ++-- .../encounters/trash-to-treasure-encounter.test.ts | 2 +- 43 files changed, 89 insertions(+), 92 deletions(-) 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 0ec25f537f5..0053bb6b9ff 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 @@ -1,8 +1,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; -import { allTrainerItems } from "#app/data/data-lists"; import { globalScene } from "#app/global-scene"; import { speciesStarterCosts } from "#balance/starters"; -import { modifierTypes } from "#data/data-lists"; +import { allTrainerItems, modifierTypes } from "#data/data-lists"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index 6e6fe35779d..7d052eaadd7 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -1,6 +1,6 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; -import { allHeldItems } from "#app/data/data-lists"; import { globalScene } from "#app/global-scene"; +import { allHeldItems } from "#data/data-lists"; import { getNatureName } from "#data/nature"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/src/field/pokemon-held-item-manager.ts b/src/field/pokemon-held-item-manager.ts index b6daed00920..ae4509b71b1 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/field/pokemon-held-item-manager.ts @@ -1,13 +1,12 @@ -import { allHeldItems } from "#app/data/data-lists"; +import { allHeldItems } from "#data/data-lists"; +import type { FormChangeItem } from "#enums/form-change-item"; import { type HeldItemCategoryId, type HeldItemId, isCategoryId, isItemInCategory, isItemInRequested, -} from "#app/enums/held-item-id"; -import { getTypedEntries, getTypedKeys } from "#app/utils/common"; -import type { FormChangeItem } from "#enums/form-change-item"; +} from "#enums/held-item-id"; import { type FormChangeItemPropertyMap, type FormChangeItemSpecs, @@ -17,6 +16,7 @@ import { type HeldItemSpecs, isHeldItemSpecs, } from "#items/held-item-data-types"; +import { getTypedEntries, getTypedKeys } from "#utils/common"; export class PokemonItemManager { public heldItems: HeldItemDataMap; diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 2b18fe5399a..8641dbfe652 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -1,11 +1,11 @@ -import { allHeldItems } from "#app/data/data-lists"; -import { getEnumValues } from "#app/utils/common"; +import { allHeldItems } from "#data/data-lists"; import { BerryType } from "#enums/berry-type"; import { HeldItemId } from "#enums/held-item-id"; import type { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; import { type PermanentStat, Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; +import { getEnumValues } from "#utils/common"; import { HeldItemEffect } from "./held-item"; import { AccuracyBoosterHeldItem, type AccuracyBoostParams } from "./held-items/accuracy-booster"; import { diff --git a/src/items/all-trainer-items.ts b/src/items/all-trainer-items.ts index afb15aca431..60abf3b676e 100644 --- a/src/items/all-trainer-items.ts +++ b/src/items/all-trainer-items.ts @@ -1,4 +1,4 @@ -import { allTrainerItems } from "#app/data/data-lists"; +import { allTrainerItems } from "#data/data-lists"; import { Stat, type TempBattleStat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { TrainerItemId } from "#enums/trainer-item-id"; diff --git a/src/items/apply-trainer-items.ts b/src/items/apply-trainer-items.ts index 898c4067a68..8a1c0145095 100644 --- a/src/items/apply-trainer-items.ts +++ b/src/items/apply-trainer-items.ts @@ -1,4 +1,4 @@ -import { allTrainerItems } from "#app/data/data-lists"; +import { allTrainerItems } from "#data/data-lists"; import { type BooleanHolderParams, type NumberHolderParams, diff --git a/src/items/held-item-data-types.ts b/src/items/held-item-data-types.ts index 1670b2bb6d0..58d3f25d20c 100644 --- a/src/items/held-item-data-types.ts +++ b/src/items/held-item-data-types.ts @@ -1,8 +1,9 @@ // TODO: move to `src/@types/` -import type Pokemon from "#app/field/pokemon"; + import type { FormChangeItem } from "#enums/form-change-item"; import type { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; import type { RewardTier } from "#enums/reward-tier"; +import type { Pokemon } from "#field/pokemon"; export type HeldItemData = { stack: number; diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index 8f8a761474d..3d769cc389d 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -1,13 +1,12 @@ -import { allHeldItems } from "#app/data/data-lists"; -import type Pokemon from "#app/field/pokemon"; -import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; -import { coerceArray, getEnumValues, isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#app/utils/common"; +import { allHeldItems } from "#data/data-lists"; import { BerryType } from "#enums/berry-type"; import { HeldItemCategoryId, HeldItemId, HeldItemNames, isCategoryId } from "#enums/held-item-id"; import { HeldItemPoolType } from "#enums/modifier-pool-type"; import type { PokemonType } from "#enums/pokemon-type"; import { RewardTier } from "#enums/reward-tier"; import { PERMANENT_STATS } from "#enums/stat"; +import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; +import { coerceArray, getEnumValues, isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common"; import { type HeldItemConfiguration, type HeldItemPool, diff --git a/src/items/held-item.ts b/src/items/held-item.ts index e701a9b69c6..10efe2aae8e 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -1,7 +1,7 @@ -import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; -import type Pokemon from "#app/field/pokemon"; +import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import { type HeldItemId, HeldItemNames } from "#enums/held-item-id"; +import type { Pokemon } from "#field/pokemon"; import i18next from "i18next"; // TODO: this should be moved to its own file diff --git a/src/items/held-items/accuracy-booster.ts b/src/items/held-items/accuracy-booster.ts index f8aa332ab27..8d7d55bac69 100644 --- a/src/items/held-items/accuracy-booster.ts +++ b/src/items/held-items/accuracy-booster.ts @@ -1,6 +1,6 @@ -import type Pokemon from "#app/field/pokemon"; -import type { NumberHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; +import type { Pokemon } from "#field/pokemon"; +import type { NumberHolder } from "#utils/common"; import { HeldItem, HeldItemEffect } from "../held-item"; export interface AccuracyBoostParams { diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index d2f08426e96..ddfd5373be5 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -1,8 +1,8 @@ -import type Pokemon from "#app/field/pokemon"; -import type { NumberHolder } from "#app/utils/common"; import { HeldItemId, HeldItemNames } from "#enums/held-item-id"; import { PokemonType } from "#enums/pokemon-type"; +import type { Pokemon } from "#field/pokemon"; import { HeldItem, HeldItemEffect } from "#items/held-item"; +import type { NumberHolder } from "#utils/common"; import i18next from "i18next"; export interface AttackTypeBoostParams { diff --git a/src/items/held-items/base-stat-booster.ts b/src/items/held-items/base-stat-booster.ts index 6f452a12996..284844fd6f9 100644 --- a/src/items/held-items/base-stat-booster.ts +++ b/src/items/held-items/base-stat-booster.ts @@ -1,6 +1,6 @@ -import type Pokemon from "#app/field/pokemon"; import { HeldItemId } from "#enums/held-item-id"; import { getStatKey, type PermanentStat, Stat } from "#enums/stat"; +import type { Pokemon } from "#field/pokemon"; import i18next from "i18next"; import { HeldItem, HeldItemEffect } from "../held-item"; diff --git a/src/items/held-items/base-stat-flat.ts b/src/items/held-items/base-stat-flat.ts index 30c8b5b9f80..4b0992f6dd9 100644 --- a/src/items/held-items/base-stat-flat.ts +++ b/src/items/held-items/base-stat-flat.ts @@ -1,5 +1,5 @@ -import type Pokemon from "#app/field/pokemon"; import { Stat } from "#enums/stat"; +import type { Pokemon } from "#field/pokemon"; import i18next from "i18next"; import { HeldItem, HeldItemEffect } from "../held-item"; diff --git a/src/items/held-items/base-stat-total.ts b/src/items/held-items/base-stat-total.ts index 643c6ef26a0..7e861a07a1a 100644 --- a/src/items/held-items/base-stat-total.ts +++ b/src/items/held-items/base-stat-total.ts @@ -1,5 +1,5 @@ -import type Pokemon from "#app/field/pokemon"; import type { HeldItemId } from "#enums/held-item-id"; +import type { Pokemon } from "#field/pokemon"; import i18next from "i18next"; import { HeldItem, HeldItemEffect } from "../held-item"; diff --git a/src/items/held-items/baton.ts b/src/items/held-items/baton.ts index 5b34fa3d519..63f1f173240 100644 --- a/src/items/held-items/baton.ts +++ b/src/items/held-items/baton.ts @@ -1,5 +1,5 @@ -import type Pokemon from "#app/field/pokemon"; -import type { NumberHolder } from "#app/utils/common"; +import type { Pokemon } from "#field/pokemon"; +import type { NumberHolder } from "#utils/common"; import { HeldItem, HeldItemEffect } from "../held-item"; export interface BatonParams { diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts index 336eca8e880..7796ef586c0 100644 --- a/src/items/held-items/berry.ts +++ b/src/items/held-items/berry.ts @@ -1,11 +1,11 @@ -import { getBerryEffectDescription, getBerryEffectFunc, getBerryName, getBerryPredicate } from "#app/data/berry"; -import { BerryUsedEvent } from "#app/events/battle-scene"; -import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import { BooleanHolder } from "#app/utils/common"; +import { getBerryEffectDescription, getBerryEffectFunc, getBerryName, getBerryPredicate } from "#data/berry"; import { BerryType } from "#enums/berry-type"; import { HeldItemId } from "#enums/held-item-id"; +import { BerryUsedEvent } from "#events/battle-scene"; +import type { Pokemon } from "#field/pokemon"; import { ConsumableHeldItem, HeldItemEffect } from "#items/held-item"; +import { BooleanHolder } from "#utils/common"; import { TrainerItemEffect } from "../trainer-item"; interface BerryTypeToHeldItemMap { diff --git a/src/items/held-items/bypass-speed-chance.ts b/src/items/held-items/bypass-speed-chance.ts index b61186ba9d8..df14b07dce0 100644 --- a/src/items/held-items/bypass-speed-chance.ts +++ b/src/items/held-items/bypass-speed-chance.ts @@ -1,9 +1,9 @@ -import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import type { BooleanHolder } from "#app/utils/common"; import { Command } from "#enums/command"; +import type { Pokemon } from "#field/pokemon"; import { HeldItem, HeldItemEffect } from "#items/held-item"; +import type { BooleanHolder } from "#utils/common"; import i18next from "i18next"; export interface BypassSpeedChanceParams { diff --git a/src/items/held-items/crit-booster.ts b/src/items/held-items/crit-booster.ts index 53698a2b26d..399d3fae060 100644 --- a/src/items/held-items/crit-booster.ts +++ b/src/items/held-items/crit-booster.ts @@ -1,7 +1,7 @@ -import type Pokemon from "#app/field/pokemon"; -import type { NumberHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; +import type { Pokemon } from "#field/pokemon"; +import type { NumberHolder } from "#utils/common"; import { HeldItem, HeldItemEffect } from "../held-item"; export interface CritBoostParams { diff --git a/src/items/held-items/damage-money-reward.ts b/src/items/held-items/damage-money-reward.ts index 5f00a751b78..962fd14a580 100644 --- a/src/items/held-items/damage-money-reward.ts +++ b/src/items/held-items/damage-money-reward.ts @@ -1,6 +1,6 @@ -import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; -import { NumberHolder } from "#app/utils/common"; +import type { Pokemon } from "#field/pokemon"; +import { NumberHolder } from "#utils/common"; import { HeldItem, HeldItemEffect } from "../held-item"; import { TrainerItemEffect } from "../trainer-item"; diff --git a/src/items/held-items/evo-tracker.ts b/src/items/held-items/evo-tracker.ts index 4f2985a9e2f..c4bab46b1d8 100644 --- a/src/items/held-items/evo-tracker.ts +++ b/src/items/held-items/evo-tracker.ts @@ -1,8 +1,8 @@ -import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import { TrainerItemId } from "#enums/trainer-item-id"; +import type { Pokemon } from "#field/pokemon"; import i18next from "i18next"; import { HeldItem, HeldItemEffect } from "../held-item"; diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts index 98eed27392f..0d3d8b7fcb7 100644 --- a/src/items/held-items/exp-booster.ts +++ b/src/items/held-items/exp-booster.ts @@ -1,6 +1,6 @@ -import type Pokemon from "#app/field/pokemon"; -import type { NumberHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; +import type { Pokemon } from "#field/pokemon"; +import type { NumberHolder } from "#utils/common"; import i18next from "i18next"; import { HeldItem, HeldItemEffect } from "../held-item"; diff --git a/src/items/held-items/field-effect.ts b/src/items/held-items/field-effect.ts index de680226623..c248143578d 100644 --- a/src/items/held-items/field-effect.ts +++ b/src/items/held-items/field-effect.ts @@ -1,6 +1,6 @@ -import type Pokemon from "#app/field/pokemon"; -import type { NumberHolder } from "#app/utils/common"; +import type { Pokemon } from "#field/pokemon"; import { HeldItem, HeldItemEffect } from "#items/held-item"; +import type { NumberHolder } from "#utils/common"; export interface FieldEffectParams { pokemon: Pokemon; diff --git a/src/items/held-items/flinch-chance.ts b/src/items/held-items/flinch-chance.ts index 904fceb1484..290743fac00 100644 --- a/src/items/held-items/flinch-chance.ts +++ b/src/items/held-items/flinch-chance.ts @@ -1,7 +1,7 @@ -import type Pokemon from "#app/field/pokemon"; -import type { BooleanHolder } from "#app/utils/common"; import type { HeldItemId } from "#enums/held-item-id"; +import type { Pokemon } from "#field/pokemon"; import { HeldItem, HeldItemEffect } from "#items/held-item"; +import type { BooleanHolder } from "#utils/common"; export interface FlinchChanceParams { /** The pokemon with the item */ diff --git a/src/items/held-items/friendship-booster.ts b/src/items/held-items/friendship-booster.ts index 3ad6e48e63e..6aade547b27 100644 --- a/src/items/held-items/friendship-booster.ts +++ b/src/items/held-items/friendship-booster.ts @@ -1,5 +1,5 @@ -import type Pokemon from "#app/field/pokemon"; -import type { NumberHolder } from "#app/utils/common"; +import type { Pokemon } from "#field/pokemon"; +import type { NumberHolder } from "#utils/common"; import i18next from "i18next"; import { HeldItem, HeldItemEffect } from "../held-item"; diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts index e8bf46d09dd..522c3e018d7 100644 --- a/src/items/held-items/hit-heal.ts +++ b/src/items/held-items/hit-heal.ts @@ -1,9 +1,9 @@ -import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; -import { toDmgValue } from "#app/utils/common"; +import type { Pokemon } from "#field/pokemon"; import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { PokemonHealPhase } from "#phases/pokemon-heal-phase"; +import { toDmgValue } from "#utils/common"; import i18next from "i18next"; export interface HitHealParams { @@ -36,7 +36,7 @@ export class HitHealHeldItem extends HeldItem { const stackCount = pokemon.heldItemManager.getStack(this.type); if (pokemon.turnData.totalDamageDealt > 0 && !pokemon.isFullHp()) { // TODO: this shouldn't be undefined AFAIK - globalScene.unshiftPhase( + globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( pokemon.getBattlerIndex(), toDmgValue(pokemon.turnData.totalDamageDealt / 8) * stackCount, diff --git a/src/items/held-items/incrementing-stat.ts b/src/items/held-items/incrementing-stat.ts index a29a2f2a877..e20e4a97f30 100644 --- a/src/items/held-items/incrementing-stat.ts +++ b/src/items/held-items/incrementing-stat.ts @@ -1,6 +1,6 @@ -import type Pokemon from "#app/field/pokemon"; -import type { NumberHolder } from "#app/utils/common"; import { Stat } from "#enums/stat"; +import type { Pokemon } from "#field/pokemon"; +import type { NumberHolder } from "#utils/common"; import i18next from "i18next"; import { HeldItem, HeldItemEffect } from "../held-item"; diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts index dd97a28ccca..368cd5e51e8 100644 --- a/src/items/held-items/instant-revive.ts +++ b/src/items/held-items/instant-revive.ts @@ -1,9 +1,9 @@ -import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; -import type Pokemon from "#app/field/pokemon"; +import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; -import { toDmgValue } from "#app/utils/common"; +import type { Pokemon } from "#field/pokemon"; +import { PokemonHealPhase } from "#phases/pokemon-heal-phase"; +import { toDmgValue } from "#utils/common"; import i18next from "i18next"; import { ConsumableHeldItem, HeldItemEffect } from "../held-item"; diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts index 68548a6c77a..e3c1361d62f 100644 --- a/src/items/held-items/item-steal.ts +++ b/src/items/held-items/item-steal.ts @@ -1,9 +1,9 @@ -import { allHeldItems } from "#app/data/data-lists"; -import Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { randSeedFloat } from "#app/utils/common"; +import { allHeldItems } from "#data/data-lists"; import type { HeldItemId } from "#enums/held-item-id"; +import { Pokemon } from "#field/pokemon"; +import { randSeedFloat } from "#utils/common"; import i18next from "i18next"; import { HeldItem, HeldItemEffect } from "../held-item"; diff --git a/src/items/held-items/multi-hit.ts b/src/items/held-items/multi-hit.ts index 1a25b394287..d288039bceb 100644 --- a/src/items/held-items/multi-hit.ts +++ b/src/items/held-items/multi-hit.ts @@ -1,8 +1,8 @@ -import { allMoves } from "#app/data/data-lists"; -import type Pokemon from "#app/field/pokemon"; -import { isNullOrUndefined, type NumberHolder } from "#app/utils/common"; +import { allMoves } from "#data/data-lists"; import type { MoveId } from "#enums/move-id"; +import type { Pokemon } from "#field/pokemon"; import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { isNullOrUndefined, type NumberHolder } from "#utils/common"; import i18next from "i18next"; export interface MultiHitParams { diff --git a/src/items/held-items/nature-weight-booster.ts b/src/items/held-items/nature-weight-booster.ts index 983c9173ab8..41458564510 100644 --- a/src/items/held-items/nature-weight-booster.ts +++ b/src/items/held-items/nature-weight-booster.ts @@ -1,5 +1,5 @@ -import type Pokemon from "#app/field/pokemon"; -import type { NumberHolder } from "#app/utils/common"; +import type { Pokemon } from "#field/pokemon"; +import type { NumberHolder } from "#utils/common"; import { HeldItem, HeldItemEffect } from "../held-item"; export interface NatureWeightBoostParams { diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index d0be04849be..e9926ff4955 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -1,7 +1,7 @@ -import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { BATTLE_STATS } from "#enums/stat"; +import type { Pokemon } from "#field/pokemon"; import i18next from "i18next"; import { ConsumableHeldItem, HeldItemEffect } from "../held-item"; diff --git a/src/items/held-items/stat-booster.ts b/src/items/held-items/stat-booster.ts index f64770ffce4..410ef244fe4 100644 --- a/src/items/held-items/stat-booster.ts +++ b/src/items/held-items/stat-booster.ts @@ -1,9 +1,9 @@ -import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; -import type Pokemon from "#app/field/pokemon"; -import type { NumberHolder } from "#app/utils/common"; +import { pokemonEvolutions } from "#balance/pokemon-evolutions"; import { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import type { Stat } from "#enums/stat"; +import type { Pokemon } from "#field/pokemon"; +import type { NumberHolder } from "#utils/common"; import { HeldItem, HeldItemEffect } from "../held-item"; export interface StatBoostParams { diff --git a/src/items/held-items/survive-chance.ts b/src/items/held-items/survive-chance.ts index fd2d7876793..ef9256f030f 100644 --- a/src/items/held-items/survive-chance.ts +++ b/src/items/held-items/survive-chance.ts @@ -1,8 +1,8 @@ -import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import type { BooleanHolder } from "#app/utils/common"; +import type { Pokemon } from "#field/pokemon"; import { HeldItem, HeldItemEffect } from "#items/held-item"; +import type { BooleanHolder } from "#utils/common"; import i18next from "i18next"; export interface SurviveChanceParams { diff --git a/src/items/held-items/turn-end-heal.ts b/src/items/held-items/turn-end-heal.ts index 5316b728e40..3acc5861a8e 100644 --- a/src/items/held-items/turn-end-heal.ts +++ b/src/items/held-items/turn-end-heal.ts @@ -1,9 +1,9 @@ -import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; -import { toDmgValue } from "#app/utils/common"; +import type { Pokemon } from "#field/pokemon"; import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { PokemonHealPhase } from "#phases/pokemon-heal-phase"; +import { toDmgValue } from "#utils/common"; import i18next from "i18next"; export interface TurnEndHealParams { diff --git a/src/items/held-items/turn-end-status.ts b/src/items/held-items/turn-end-status.ts index 630347c6a54..d746b59419d 100644 --- a/src/items/held-items/turn-end-status.ts +++ b/src/items/held-items/turn-end-status.ts @@ -1,6 +1,6 @@ -import type Pokemon from "#app/field/pokemon"; import type { HeldItemId } from "#enums/held-item-id"; import type { StatusEffect } from "#enums/status-effect"; +import type { Pokemon } from "#field/pokemon"; import { HeldItem, HeldItemEffect } from "#items/held-item"; export interface TurnEndStatusParams { diff --git a/src/items/item-utility.ts b/src/items/item-utility.ts index 540406acdf2..db858122bcf 100644 --- a/src/items/item-utility.ts +++ b/src/items/item-utility.ts @@ -1,5 +1,4 @@ -import { allHeldItems, allTrainerItems } from "#app/data/data-lists"; -import { formChangeItemName } from "#app/data/pokemon-forms"; +import { allHeldItems, allTrainerItems } from "#data/data-lists"; import type { FormChangeItem } from "#enums/form-change-item"; import type { HeldItemId } from "#enums/held-item-id"; import type { TrainerItemId } from "#enums/trainer-item-id"; diff --git a/src/items/modifier-to-item-migrator-utils.ts b/src/items/modifier-to-item-migrator-utils.ts index 929c1229bb0..b6f6571fedc 100644 --- a/src/items/modifier-to-item-migrator-utils.ts +++ b/src/items/modifier-to-item-migrator-utils.ts @@ -1,4 +1,3 @@ -import type ModifierData from "#app/system/modifier-data"; import type { BerryType } from "#enums/berry-type"; import { HeldItemId } from "#enums/held-item-id"; import type { PokemonType } from "#enums/pokemon-type"; diff --git a/src/items/trainer-item-manager.ts b/src/items/trainer-item-manager.ts index 4170761665f..6579df69ca5 100644 --- a/src/items/trainer-item-manager.ts +++ b/src/items/trainer-item-manager.ts @@ -1,6 +1,5 @@ -import { allTrainerItems } from "#app/data/data-lists"; -import type { TrainerItemId } from "#app/enums/trainer-item-id"; -import { getTypedEntries, getTypedKeys } from "#app/utils/common"; +import { allTrainerItems } from "#data/data-lists"; +import type { TrainerItemId } from "#enums/trainer-item-id"; import { isTrainerItemSpecs, type TrainerItemConfiguration, @@ -8,6 +7,7 @@ import { type TrainerItemSaveData, type TrainerItemSpecs, } from "#items/trainer-item-data-types"; +import { getTypedEntries, getTypedKeys } from "#utils/common"; export class TrainerItemManager { public trainerItems: TrainerItemDataMap; diff --git a/src/items/trainer-item-pool.ts b/src/items/trainer-item-pool.ts index 12fecd88eaf..5ebe6a2d9a3 100644 --- a/src/items/trainer-item-pool.ts +++ b/src/items/trainer-item-pool.ts @@ -1,8 +1,8 @@ -import { allTrainerItems } from "#app/data/data-lists"; import { globalScene } from "#app/global-scene"; -import { isNullOrUndefined, pickWeightedIndex } from "#app/utils/common"; +import { allTrainerItems } from "#data/data-lists"; import { RewardTier } from "#enums/reward-tier"; import type { TrainerItemId } from "#enums/trainer-item-id"; +import { isNullOrUndefined, pickWeightedIndex } from "#utils/common"; import type { TrainerItemPool, TrainerItemTieredPool } from "./trainer-item-data-types"; import type { TrainerItemManager } from "./trainer-item-manager"; diff --git a/src/items/trainer-item.ts b/src/items/trainer-item.ts index 4858809db55..6ab92ae1827 100644 --- a/src/items/trainer-item.ts +++ b/src/items/trainer-item.ts @@ -1,13 +1,13 @@ -import { getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect"; -import type Pokemon from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { addTextObject, TextStyle } from "#app/ui/text"; -import { type BooleanHolder, hslToHex, type NumberHolder, randSeedFloat, toDmgValue } from "#app/utils/common"; +import { getStatusEffectDescriptor, getStatusEffectHealText } from "#data/status-effect"; import { BattlerTagType } from "#enums/battler-tag-type"; import { getStatKey, Stat, type TempBattleStat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { TrainerItemId, TrainerItemNames } from "#enums/trainer-item-id"; +import type { Pokemon } from "#field/pokemon"; +import { addTextObject, TextStyle } from "#ui/text"; +import { type BooleanHolder, hslToHex, type NumberHolder, randSeedFloat, toDmgValue } from "#utils/common"; import i18next from "i18next"; import type { TrainerItemManager } from "./trainer-item-manager"; diff --git a/src/phases/reward-phase.ts b/src/phases/reward-phase.ts index 3c490f6c9cb..07ff9926521 100644 --- a/src/phases/reward-phase.ts +++ b/src/phases/reward-phase.ts @@ -1,7 +1,7 @@ -import type { ModifierTypeFunc } from "#app/@types/modifier-types"; import { globalScene } from "#app/global-scene"; -import { type ModifierType, TrainerItemReward } from "#app/modifier/modifier-type"; -import { getModifierType } from "#app/utils/modifier-utils"; +import { type ModifierType, TrainerItemReward } from "#modifiers/modifier-type"; +import type { ModifierTypeFunc } from "#types/modifier-types"; +import { getModifierType } from "#utils/modifier-utils"; import i18next from "i18next"; import { BattlePhase } from "./battle-phase"; diff --git a/src/ui/item-bar-ui.ts b/src/ui/item-bar-ui.ts index e446b7c0bc0..4cfe5ccd86d 100644 --- a/src/ui/item-bar-ui.ts +++ b/src/ui/item-bar-ui.ts @@ -1,6 +1,6 @@ -import { allHeldItems, allTrainerItems } from "#app/data/data-lists"; -import type { Pokemon } from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; +import { allHeldItems, allTrainerItems } from "#data/data-lists"; +import type { Pokemon } from "#field/pokemon"; import { heldItemSortFunc, trainerItemSortFunc } from "#items/item-utility"; import type { TrainerItemManager } from "#items/trainer-item-manager"; diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index a2fb54974b8..f4cafa9bb2d 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -1,5 +1,4 @@ import type { BattleScene } from "#app/battle-scene"; -import { randSeedInt } from "#app/utils/common"; import * as BattleAnims from "#data/battle-anims"; import { BiomeId } from "#enums/biome-id"; import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; @@ -27,6 +26,7 @@ import { GameManager } from "#test/testUtils/gameManager"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; import { RewardSelectUiHandler } from "#ui/reward-select-ui-handler"; import * as Utils from "#utils/common"; +import { randSeedInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; From 8118d00a0749975a4de92bed853d855d047e3a1d Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 13 Jul 2025 02:57:47 -0700 Subject: [PATCH 25/39] Remove relative imports --- src/items/all-held-items.ts | 67 ++++++++----------- src/items/all-trainer-items.ts | 2 +- src/items/apply-trainer-items.ts | 4 +- src/items/held-item-pool.ts | 10 +-- src/items/held-items/accuracy-booster.ts | 2 +- src/items/held-items/base-stat-booster.ts | 2 +- src/items/held-items/base-stat-flat.ts | 2 +- src/items/held-items/base-stat-total.ts | 2 +- src/items/held-items/baton.ts | 2 +- src/items/held-items/berry.ts | 2 +- src/items/held-items/crit-booster.ts | 2 +- src/items/held-items/damage-money-reward.ts | 4 +- src/items/held-items/evo-tracker.ts | 2 +- src/items/held-items/exp-booster.ts | 2 +- src/items/held-items/friendship-booster.ts | 2 +- src/items/held-items/incrementing-stat.ts | 2 +- src/items/held-items/instant-revive.ts | 2 +- src/items/held-items/item-steal.ts | 2 +- src/items/held-items/nature-weight-booster.ts | 2 +- .../held-items/reset-negative-stat-stage.ts | 2 +- src/items/held-items/stat-booster.ts | 2 +- src/items/init-held-item-pools.ts | 2 +- src/items/init-trainer-item-pools.ts | 2 +- src/items/modifier-to-item-migrator-utils.ts | 8 +-- src/items/trainer-item-pool.ts | 4 +- src/items/trainer-item.ts | 2 +- src/phases/reward-phase.ts | 2 +- 27 files changed, 64 insertions(+), 75 deletions(-) diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index 8641dbfe652..c5572182add 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -5,49 +5,38 @@ import type { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; import { type PermanentStat, Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; -import { getEnumValues } from "#utils/common"; -import { HeldItemEffect } from "./held-item"; -import { AccuracyBoosterHeldItem, type AccuracyBoostParams } from "./held-items/accuracy-booster"; +import { AccuracyBoosterHeldItem, type AccuracyBoostParams } from "#items/accuracy-booster"; import { AttackTypeBoosterHeldItem, type AttackTypeBoostParams, attackTypeToHeldItem, -} from "./held-items/attack-type-booster"; -import { - BaseStatBoosterHeldItem, - type BaseStatBoosterParams, - permanentStatToHeldItem, -} from "./held-items/base-stat-booster"; -import { BaseStatFlatHeldItem, type BaseStatFlatParams } from "./held-items/base-stat-flat"; -import { BaseStatTotalHeldItem, type BaseStatTotalParams } from "./held-items/base-stat-total"; -import { BatonHeldItem, type BatonParams } from "./held-items/baton"; -import { BerryHeldItem, type BerryParams, berryTypeToHeldItem } from "./held-items/berry"; -import { BypassSpeedChanceHeldItem, type BypassSpeedChanceParams } from "./held-items/bypass-speed-chance"; -import { CritBoostHeldItem, type CritBoostParams, SpeciesCritBoostHeldItem } from "./held-items/crit-booster"; -import { DamageMoneyRewardHeldItem, type DamageMoneyRewardParams } from "./held-items/damage-money-reward"; -import { type EvoTrackerParams, GimmighoulEvoTrackerHeldItem } from "./held-items/evo-tracker"; -import { ExpBoosterHeldItem, type ExpBoostParams } from "./held-items/exp-booster"; -import { FieldEffectHeldItem, type FieldEffectParams } from "./held-items/field-effect"; -import { FlinchChanceHeldItem, type FlinchChanceParams } from "./held-items/flinch-chance"; -import { FriendshipBoosterHeldItem, type FriendshipBoostParams } from "./held-items/friendship-booster"; -import { HitHealHeldItem, type HitHealParams } from "./held-items/hit-heal"; -import { IncrementingStatHeldItem, type IncrementingStatParams } from "./held-items/incrementing-stat"; -import { InstantReviveHeldItem, type InstantReviveParams } from "./held-items/instant-revive"; -import { - ContactItemStealChanceHeldItem, - type ItemStealParams, - TurnEndItemStealHeldItem, -} from "./held-items/item-steal"; -import { MultiHitHeldItem, type MultiHitParams } from "./held-items/multi-hit"; -import { NatureWeightBoosterHeldItem, type NatureWeightBoostParams } from "./held-items/nature-weight-booster"; -import { - ResetNegativeStatStageHeldItem, - type ResetNegativeStatStageParams, -} from "./held-items/reset-negative-stat-stage"; -import { EvolutionStatBoostHeldItem, SpeciesStatBoostHeldItem, type StatBoostParams } from "./held-items/stat-booster"; -import { SurviveChanceHeldItem, type SurviveChanceParams } from "./held-items/survive-chance"; -import { TurnEndHealHeldItem, type TurnEndHealParams } from "./held-items/turn-end-heal"; -import { TurnEndStatusHeldItem, type TurnEndStatusParams } from "./held-items/turn-end-status"; +} from "#items/attack-type-booster"; +import { BaseStatBoosterHeldItem, type BaseStatBoosterParams, permanentStatToHeldItem } from "#items/base-stat-booster"; +import { BaseStatFlatHeldItem, type BaseStatFlatParams } from "#items/base-stat-flat"; +import { BaseStatTotalHeldItem, type BaseStatTotalParams } from "#items/base-stat-total"; +import { BatonHeldItem, type BatonParams } from "#items/baton"; +import { BerryHeldItem, type BerryParams, berryTypeToHeldItem } from "#items/berry"; +import { BypassSpeedChanceHeldItem, type BypassSpeedChanceParams } from "#items/bypass-speed-chance"; +import { CritBoostHeldItem, type CritBoostParams, SpeciesCritBoostHeldItem } from "#items/crit-booster"; +import { DamageMoneyRewardHeldItem, type DamageMoneyRewardParams } from "#items/damage-money-reward"; +import { type EvoTrackerParams, GimmighoulEvoTrackerHeldItem } from "#items/evo-tracker"; +import { ExpBoosterHeldItem, type ExpBoostParams } from "#items/exp-booster"; +import { FieldEffectHeldItem, type FieldEffectParams } from "#items/field-effect"; +import { FlinchChanceHeldItem, type FlinchChanceParams } from "#items/flinch-chance"; +import { FriendshipBoosterHeldItem, type FriendshipBoostParams } from "#items/friendship-booster"; +import { HeldItemEffect } from "#items/held-item"; +import { HitHealHeldItem, type HitHealParams } from "#items/hit-heal"; +import { IncrementingStatHeldItem, type IncrementingStatParams } from "#items/incrementing-stat"; +import { InstantReviveHeldItem, type InstantReviveParams } from "#items/instant-revive"; +import { ContactItemStealChanceHeldItem, type ItemStealParams, TurnEndItemStealHeldItem } from "#items/item-steal"; +import { MultiHitHeldItem, type MultiHitParams } from "#items/multi-hit"; +import { NatureWeightBoosterHeldItem, type NatureWeightBoostParams } from "#items/nature-weight-booster"; +import { ResetNegativeStatStageHeldItem, type ResetNegativeStatStageParams } from "#items/reset-negative-stat-stage"; +import { EvolutionStatBoostHeldItem, SpeciesStatBoostHeldItem, type StatBoostParams } from "#items/stat-booster"; +import { SurviveChanceHeldItem, type SurviveChanceParams } from "#items/survive-chance"; +import { TurnEndHealHeldItem, type TurnEndHealParams } from "#items/turn-end-heal"; +import { TurnEndStatusHeldItem, type TurnEndStatusParams } from "#items/turn-end-status"; +import { getEnumValues } from "#utils/common"; export function initHeldItems() { for (const berry of getEnumValues(BerryType)) { diff --git a/src/items/all-trainer-items.ts b/src/items/all-trainer-items.ts index 60abf3b676e..0b4901ca6b9 100644 --- a/src/items/all-trainer-items.ts +++ b/src/items/all-trainer-items.ts @@ -26,7 +26,7 @@ import { TempStatStageBoosterTrainerItem, TrainerItem, tempStatToTrainerItem, -} from "./trainer-item"; +} from "#items/trainer-item"; export function initTrainerItems() { allTrainerItems[TrainerItemId.MAP] = new TrainerItem(TrainerItemId.MAP, 1); diff --git a/src/items/apply-trainer-items.ts b/src/items/apply-trainer-items.ts index 8a1c0145095..7abeee18df9 100644 --- a/src/items/apply-trainer-items.ts +++ b/src/items/apply-trainer-items.ts @@ -5,8 +5,8 @@ import { type PokemonParams, type PreserveBerryParams, TrainerItemEffect, -} from "./trainer-item"; -import type { TrainerItemManager } from "./trainer-item-manager"; +} from "#items/trainer-item"; +import type { TrainerItemManager } from "#items/trainer-item-manager"; export type ApplyTrainerItemsParams = { [TrainerItemEffect.LEVEL_INCREMENT_BOOSTER]: NumberHolderParams; diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index 3d769cc389d..01755624cfe 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -6,7 +6,9 @@ import type { PokemonType } from "#enums/pokemon-type"; import { RewardTier } from "#enums/reward-tier"; import { PERMANENT_STATS } from "#enums/stat"; import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; -import { coerceArray, getEnumValues, isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common"; +import { attackTypeToHeldItem } from "#items/attack-type-booster"; +import { permanentStatToHeldItem } from "#items/base-stat-booster"; +import { berryTypeToHeldItem } from "#items/berry"; import { type HeldItemConfiguration, type HeldItemPool, @@ -18,10 +20,8 @@ import { isHeldItemCategoryEntry, isHeldItemPool, isHeldItemSpecs, -} from "./held-item-data-types"; -import { attackTypeToHeldItem } from "./held-items/attack-type-booster"; -import { permanentStatToHeldItem } from "./held-items/base-stat-booster"; -import { berryTypeToHeldItem } from "./held-items/berry"; +} from "#items/held-item-data-types"; +import { coerceArray, getEnumValues, isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common"; export const wildHeldItemPool: HeldItemTieredPool = {}; diff --git a/src/items/held-items/accuracy-booster.ts b/src/items/held-items/accuracy-booster.ts index 8d7d55bac69..1373d4d7151 100644 --- a/src/items/held-items/accuracy-booster.ts +++ b/src/items/held-items/accuracy-booster.ts @@ -1,7 +1,7 @@ import type { HeldItemId } from "#enums/held-item-id"; import type { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; -import { HeldItem, HeldItemEffect } from "../held-item"; export interface AccuracyBoostParams { /** The pokemon with the item */ diff --git a/src/items/held-items/base-stat-booster.ts b/src/items/held-items/base-stat-booster.ts index 284844fd6f9..b47d8bf850f 100644 --- a/src/items/held-items/base-stat-booster.ts +++ b/src/items/held-items/base-stat-booster.ts @@ -1,8 +1,8 @@ import { HeldItemId } from "#enums/held-item-id"; import { getStatKey, type PermanentStat, Stat } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import i18next from "i18next"; -import { HeldItem, HeldItemEffect } from "../held-item"; export interface BaseStatBoosterParams { /** The pokemon with the item */ diff --git a/src/items/held-items/base-stat-flat.ts b/src/items/held-items/base-stat-flat.ts index 4b0992f6dd9..b622a88fe14 100644 --- a/src/items/held-items/base-stat-flat.ts +++ b/src/items/held-items/base-stat-flat.ts @@ -1,7 +1,7 @@ import { Stat } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import i18next from "i18next"; -import { HeldItem, HeldItemEffect } from "../held-item"; export interface BaseStatFlatParams { /** The pokemon with the item */ diff --git a/src/items/held-items/base-stat-total.ts b/src/items/held-items/base-stat-total.ts index 7e861a07a1a..43ba1c697d6 100644 --- a/src/items/held-items/base-stat-total.ts +++ b/src/items/held-items/base-stat-total.ts @@ -1,7 +1,7 @@ import type { HeldItemId } from "#enums/held-item-id"; import type { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import i18next from "i18next"; -import { HeldItem, HeldItemEffect } from "../held-item"; export interface BaseStatTotalParams { /** The pokemon with the item */ diff --git a/src/items/held-items/baton.ts b/src/items/held-items/baton.ts index 63f1f173240..02356a995c5 100644 --- a/src/items/held-items/baton.ts +++ b/src/items/held-items/baton.ts @@ -1,6 +1,6 @@ import type { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; -import { HeldItem, HeldItemEffect } from "../held-item"; export interface BatonParams { /** The pokemon with the item */ diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts index 7796ef586c0..eeae8261273 100644 --- a/src/items/held-items/berry.ts +++ b/src/items/held-items/berry.ts @@ -5,8 +5,8 @@ import { HeldItemId } from "#enums/held-item-id"; import { BerryUsedEvent } from "#events/battle-scene"; import type { Pokemon } from "#field/pokemon"; import { ConsumableHeldItem, HeldItemEffect } from "#items/held-item"; +import { TrainerItemEffect } from "#items/trainer-item"; import { BooleanHolder } from "#utils/common"; -import { TrainerItemEffect } from "../trainer-item"; interface BerryTypeToHeldItemMap { [key: number]: HeldItemId; diff --git a/src/items/held-items/crit-booster.ts b/src/items/held-items/crit-booster.ts index 399d3fae060..a388684dd09 100644 --- a/src/items/held-items/crit-booster.ts +++ b/src/items/held-items/crit-booster.ts @@ -1,8 +1,8 @@ import type { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import type { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; -import { HeldItem, HeldItemEffect } from "../held-item"; export interface CritBoostParams { /** The pokemon with the item */ diff --git a/src/items/held-items/damage-money-reward.ts b/src/items/held-items/damage-money-reward.ts index 962fd14a580..46ce90796bc 100644 --- a/src/items/held-items/damage-money-reward.ts +++ b/src/items/held-items/damage-money-reward.ts @@ -1,8 +1,8 @@ import { globalScene } from "#app/global-scene"; import type { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { TrainerItemEffect } from "#items/trainer-item"; import { NumberHolder } from "#utils/common"; -import { HeldItem, HeldItemEffect } from "../held-item"; -import { TrainerItemEffect } from "../trainer-item"; export interface DamageMoneyRewardParams { /** The pokemon with the item */ diff --git a/src/items/held-items/evo-tracker.ts b/src/items/held-items/evo-tracker.ts index c4bab46b1d8..a52ac286f06 100644 --- a/src/items/held-items/evo-tracker.ts +++ b/src/items/held-items/evo-tracker.ts @@ -3,8 +3,8 @@ import { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import { TrainerItemId } from "#enums/trainer-item-id"; import type { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import i18next from "i18next"; -import { HeldItem, HeldItemEffect } from "../held-item"; export interface EvoTrackerParams { /** The pokemon with the item */ diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts index 0d3d8b7fcb7..5644546132b 100644 --- a/src/items/held-items/exp-booster.ts +++ b/src/items/held-items/exp-booster.ts @@ -1,8 +1,8 @@ import type { HeldItemId } from "#enums/held-item-id"; import type { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; import i18next from "i18next"; -import { HeldItem, HeldItemEffect } from "../held-item"; export interface ExpBoostParams { /** The pokemon with the item */ diff --git a/src/items/held-items/friendship-booster.ts b/src/items/held-items/friendship-booster.ts index 6aade547b27..904f98c0929 100644 --- a/src/items/held-items/friendship-booster.ts +++ b/src/items/held-items/friendship-booster.ts @@ -1,7 +1,7 @@ import type { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; import i18next from "i18next"; -import { HeldItem, HeldItemEffect } from "../held-item"; export interface FriendshipBoostParams { /** The pokemon with the item */ diff --git a/src/items/held-items/incrementing-stat.ts b/src/items/held-items/incrementing-stat.ts index e20e4a97f30..9b11a51944b 100644 --- a/src/items/held-items/incrementing-stat.ts +++ b/src/items/held-items/incrementing-stat.ts @@ -1,8 +1,8 @@ import { Stat } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; import i18next from "i18next"; -import { HeldItem, HeldItemEffect } from "../held-item"; export interface IncrementingStatParams { /** The pokemon with the item */ diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts index 368cd5e51e8..6c9743e3632 100644 --- a/src/items/held-items/instant-revive.ts +++ b/src/items/held-items/instant-revive.ts @@ -2,10 +2,10 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import type { Pokemon } from "#field/pokemon"; +import { ConsumableHeldItem, HeldItemEffect } from "#items/held-item"; import { PokemonHealPhase } from "#phases/pokemon-heal-phase"; import { toDmgValue } from "#utils/common"; import i18next from "i18next"; -import { ConsumableHeldItem, HeldItemEffect } from "../held-item"; export interface InstantReviveParams { /** The pokemon with the item */ diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts index e3c1361d62f..64734ddc84e 100644 --- a/src/items/held-items/item-steal.ts +++ b/src/items/held-items/item-steal.ts @@ -3,9 +3,9 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { allHeldItems } from "#data/data-lists"; import type { HeldItemId } from "#enums/held-item-id"; import { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import { randSeedFloat } from "#utils/common"; import i18next from "i18next"; -import { HeldItem, HeldItemEffect } from "../held-item"; export interface ItemStealParams { /** The pokemon with the item */ diff --git a/src/items/held-items/nature-weight-booster.ts b/src/items/held-items/nature-weight-booster.ts index 41458564510..2bf6e8133f4 100644 --- a/src/items/held-items/nature-weight-booster.ts +++ b/src/items/held-items/nature-weight-booster.ts @@ -1,6 +1,6 @@ import type { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; -import { HeldItem, HeldItemEffect } from "../held-item"; export interface NatureWeightBoostParams { /** The pokemon with the item */ diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index e9926ff4955..152f0431a96 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -2,8 +2,8 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { BATTLE_STATS } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; +import { ConsumableHeldItem, HeldItemEffect } from "#items/held-item"; import i18next from "i18next"; -import { ConsumableHeldItem, HeldItemEffect } from "../held-item"; export interface ResetNegativeStatStageParams { /** The pokemon with the item */ diff --git a/src/items/held-items/stat-booster.ts b/src/items/held-items/stat-booster.ts index 410ef244fe4..435ca331825 100644 --- a/src/items/held-items/stat-booster.ts +++ b/src/items/held-items/stat-booster.ts @@ -3,8 +3,8 @@ import { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import type { Stat } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; +import { HeldItem, HeldItemEffect } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; -import { HeldItem, HeldItemEffect } from "../held-item"; export interface StatBoostParams { /** The pokemon with the item */ diff --git a/src/items/init-held-item-pools.ts b/src/items/init-held-item-pools.ts index 0e6dc7b99e6..2a335246b73 100644 --- a/src/items/init-held-item-pools.ts +++ b/src/items/init-held-item-pools.ts @@ -1,6 +1,6 @@ import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; import { RewardTier } from "#enums/reward-tier"; -import { dailyStarterHeldItemPool, trainerHeldItemPool, wildHeldItemPool } from "./held-item-pool"; +import { dailyStarterHeldItemPool, trainerHeldItemPool, wildHeldItemPool } from "#items/held-item-pool"; /** * Initialize the wild held item pool diff --git a/src/items/init-trainer-item-pools.ts b/src/items/init-trainer-item-pools.ts index 40439764b0c..1a3e5ccf27c 100644 --- a/src/items/init-trainer-item-pools.ts +++ b/src/items/init-trainer-item-pools.ts @@ -1,6 +1,6 @@ import { RewardTier } from "#enums/reward-tier"; import { TrainerItemId } from "#enums/trainer-item-id"; -import { enemyBuffTokenPool } from "./trainer-item-pool"; +import { enemyBuffTokenPool } from "#items/trainer-item-pool"; /** * Initialize the enemy buff modifier pool diff --git a/src/items/modifier-to-item-migrator-utils.ts b/src/items/modifier-to-item-migrator-utils.ts index b6f6571fedc..d19fb52cfc6 100644 --- a/src/items/modifier-to-item-migrator-utils.ts +++ b/src/items/modifier-to-item-migrator-utils.ts @@ -3,10 +3,10 @@ import { HeldItemId } from "#enums/held-item-id"; import type { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; import { type PermanentStat, Stat } from "#enums/stat"; -import type { PokemonItemMap } from "./held-item-data-types"; -import { attackTypeToHeldItem } from "./held-items/attack-type-booster"; -import { permanentStatToHeldItem } from "./held-items/base-stat-booster"; -import { berryTypeToHeldItem } from "./held-items/berry"; +import { attackTypeToHeldItem } from "#items/attack-type-booster"; +import { permanentStatToHeldItem } from "#items/base-stat-booster"; +import { berryTypeToHeldItem } from "#items/berry"; +import type { PokemonItemMap } from "#items/held-item-data-types"; const uniqueModifierToItem = { EvoTrackerModifier: HeldItemId.GIMMIGHOUL_EVO_TRACKER, diff --git a/src/items/trainer-item-pool.ts b/src/items/trainer-item-pool.ts index 5ebe6a2d9a3..80e85224241 100644 --- a/src/items/trainer-item-pool.ts +++ b/src/items/trainer-item-pool.ts @@ -2,9 +2,9 @@ import { globalScene } from "#app/global-scene"; import { allTrainerItems } from "#data/data-lists"; import { RewardTier } from "#enums/reward-tier"; import type { TrainerItemId } from "#enums/trainer-item-id"; +import type { TrainerItemPool, TrainerItemTieredPool } from "#items/trainer-item-data-types"; +import type { TrainerItemManager } from "#items/trainer-item-manager"; import { isNullOrUndefined, pickWeightedIndex } from "#utils/common"; -import type { TrainerItemPool, TrainerItemTieredPool } from "./trainer-item-data-types"; -import type { TrainerItemManager } from "./trainer-item-manager"; export const enemyBuffTokenPool: TrainerItemTieredPool = {}; diff --git a/src/items/trainer-item.ts b/src/items/trainer-item.ts index 6ab92ae1827..2553a01b795 100644 --- a/src/items/trainer-item.ts +++ b/src/items/trainer-item.ts @@ -6,10 +6,10 @@ import { getStatKey, Stat, type TempBattleStat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { TrainerItemId, TrainerItemNames } from "#enums/trainer-item-id"; import type { Pokemon } from "#field/pokemon"; +import type { TrainerItemManager } from "#items/trainer-item-manager"; import { addTextObject, TextStyle } from "#ui/text"; import { type BooleanHolder, hslToHex, type NumberHolder, randSeedFloat, toDmgValue } from "#utils/common"; import i18next from "i18next"; -import type { TrainerItemManager } from "./trainer-item-manager"; export const TrainerItemEffect = { LEVEL_INCREMENT_BOOSTER: 1, diff --git a/src/phases/reward-phase.ts b/src/phases/reward-phase.ts index 07ff9926521..b3c243c3779 100644 --- a/src/phases/reward-phase.ts +++ b/src/phases/reward-phase.ts @@ -1,9 +1,9 @@ import { globalScene } from "#app/global-scene"; import { type ModifierType, TrainerItemReward } from "#modifiers/modifier-type"; +import { BattlePhase } from "#phases/battle-phase"; import type { ModifierTypeFunc } from "#types/modifier-types"; import { getModifierType } from "#utils/modifier-utils"; import i18next from "i18next"; -import { BattlePhase } from "./battle-phase"; export class RewardPhase extends BattlePhase { // RibbonRewardPhase extends RewardPhase and to make typescript happy From d411702716d05125e7bf70180397cfd83e1df7dc Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 13 Jul 2025 12:34:18 +0200 Subject: [PATCH 26/39] Cleaned up more files --- .../ability_activation_order.test.ts | 6 ++--- test/abilities/arena_trap.test.ts | 3 ++- test/abilities/competitive.test.ts | 3 ++- test/abilities/defiant.test.ts | 3 ++- test/abilities/flower_veil.test.ts | 3 ++- test/abilities/illuminate.test.ts | 3 ++- test/abilities/illusion.test.ts | 5 ++-- test/abilities/no_guard.test.ts | 3 ++- .../abilities/normal-move-type-change.test.ts | 6 +++-- test/abilities/normalize.test.ts | 9 +++---- test/abilities/protosynthesis.test.ts | 3 ++- test/abilities/unburden.test.ts | 2 +- test/abilities/wimp_out.test.ts | 5 ++-- test/items/metal_powder.test.ts | 22 +++++++++------- test/items/quick_powder.test.ts | 22 +++++++++------- test/items/thick_club.test.ts | 26 ++++++++++--------- test/moves/electro_shot.test.ts | 3 ++- test/moves/tera_blast.test.ts | 5 ++-- test/moves/transform-imposter.test.ts | 4 +-- 19 files changed, 75 insertions(+), 61 deletions(-) diff --git a/test/abilities/ability_activation_order.test.ts b/test/abilities/ability_activation_order.test.ts index 8344ba6be11..0f92bd974a9 100644 --- a/test/abilities/ability_activation_order.test.ts +++ b/test/abilities/ability_activation_order.test.ts @@ -1,7 +1,7 @@ import { AbilityId } from "#enums/ability-id"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import { Stat } from "#enums/stat"; import { WeatherType } from "#enums/weather-type"; import { GameManager } from "#test/testUtils/gameManager"; import Phaser from "phaser"; @@ -48,7 +48,7 @@ describe("Ability Activation Order", () => { .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.DROUGHT) .ability(AbilityId.DRIZZLE) - .startingHeldItems([{ name: "BASE_STAT_BOOSTER", type: Stat.SPD, count: 100 }]); + .startingHeldItems([{ entry: HeldItemId.CARBOS, count: 100 }]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SUNNY); @@ -61,7 +61,7 @@ describe("Ability Activation Order", () => { .enemySpecies(SpeciesId.DITTO) .enemyAbility(AbilityId.DROUGHT) .ability(AbilityId.DRIZZLE) - .startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "QUICK_POWDER" }]); + .startingHeldItems([{ entry: HeldItemId.QUICK_POWDER }]); await game.classicMode.startBattle([SpeciesId.DITTO]); expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SUNNY); diff --git a/test/abilities/arena_trap.test.ts b/test/abilities/arena_trap.test.ts index c47cf3cd327..e0e1fc60a97 100644 --- a/test/abilities/arena_trap.test.ts +++ b/test/abilities/arena_trap.test.ts @@ -2,6 +2,7 @@ import { allAbilities } from "#data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; +import { TrainerItemId } from "#enums/trainer-item-id"; import { GameManager } from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; @@ -46,7 +47,7 @@ describe("Abilities - Arena Trap", () => { }); it("should guarantee double battle with any one LURE", async () => { - game.override.startingModifier([{ name: "LURE" }]).startingWave(2); + game.override.startingTrainerItems([{ entry: TrainerItemId.LURE }]).startingWave(2); await game.classicMode.startBattle(); diff --git a/test/abilities/competitive.test.ts b/test/abilities/competitive.test.ts index 7dcc677a74e..78508e486cd 100644 --- a/test/abilities/competitive.test.ts +++ b/test/abilities/competitive.test.ts @@ -1,4 +1,5 @@ import { AbilityId } from "#enums/ability-id"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; @@ -59,7 +60,7 @@ describe("Abilities - Competitive", () => { }); it("white herb should remove only the negative effects", async () => { - game.override.startingHeldItems([{ name: "WHITE_HERB" }]); + game.override.startingHeldItems([{ entry: HeldItemId.WHITE_HERB }]); await game.classicMode.startBattle([SpeciesId.FLYGON]); const playerPokemon = game.scene.getPlayerPokemon()!; diff --git a/test/abilities/defiant.test.ts b/test/abilities/defiant.test.ts index 44187429e2b..2e202d43fe5 100644 --- a/test/abilities/defiant.test.ts +++ b/test/abilities/defiant.test.ts @@ -1,4 +1,5 @@ import { AbilityId } from "#enums/ability-id"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; @@ -58,7 +59,7 @@ describe("Abilities - Defiant", () => { }); it("white herb should remove only the negative effects", async () => { - game.override.startingHeldItems([{ name: "WHITE_HERB" }]); + game.override.startingHeldItems([{ entry: HeldItemId.WHITE_HERB }]); await game.classicMode.startBattle([SpeciesId.FLYGON]); const playerPokemon = game.scene.getPlayerPokemon()!; diff --git a/test/abilities/flower_veil.test.ts b/test/abilities/flower_veil.test.ts index e890ea24fad..b8844ce1025 100644 --- a/test/abilities/flower_veil.test.ts +++ b/test/abilities/flower_veil.test.ts @@ -2,6 +2,7 @@ import { allAbilities, allMoves } from "#data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; @@ -44,7 +45,7 @@ describe("Abilities - Flower Veil", () => { game.override .enemyMoveset([MoveId.TACKLE, MoveId.SPLASH]) .moveset([MoveId.REST, MoveId.SPLASH]) - .startingHeldItems([{ name: "FLAME_ORB" }]); + .startingHeldItems([{ entry: HeldItemId.FLAME_ORB }]); await game.classicMode.startBattle([SpeciesId.BULBASAUR]); const user = game.scene.getPlayerPokemon()!; game.move.select(MoveId.REST); diff --git a/test/abilities/illuminate.test.ts b/test/abilities/illuminate.test.ts index 0fb31379542..6e84df7bfd8 100644 --- a/test/abilities/illuminate.test.ts +++ b/test/abilities/illuminate.test.ts @@ -1,6 +1,7 @@ import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { Stat } from "#enums/stat"; +import { TrainerItemId } from "#enums/trainer-item-id"; import { GameManager } from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -45,7 +46,7 @@ describe("Abilities - Illuminate", () => { }); it("should guarantee double battle with any one LURE", async () => { - game.override.startingModifier([{ name: "LURE" }]).startingWave(2); + game.override.startingTrainerItems([{ entry: TrainerItemId.LURE }]).startingWave(2); await game.classicMode.startBattle(); diff --git a/test/abilities/illusion.test.ts b/test/abilities/illusion.test.ts index 0624c5e19f4..01aae92ed6f 100644 --- a/test/abilities/illusion.test.ts +++ b/test/abilities/illusion.test.ts @@ -1,5 +1,6 @@ import { Gender } from "#data/gender"; import { AbilityId } from "#enums/ability-id"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { PokeballType } from "#enums/pokeball"; import { SpeciesId } from "#enums/species-id"; @@ -28,9 +29,9 @@ describe("Abilities - Illusion", () => { .enemySpecies(SpeciesId.ZORUA) .enemyAbility(AbilityId.ILLUSION) .enemyMoveset(MoveId.TACKLE) - .enemyHeldItems([{ name: "WIDE_LENS", count: 3 }]) + .enemyHeldItems([{ entry: HeldItemId.WIDE_LENS, count: 3 }]) .moveset([MoveId.WORRY_SEED, MoveId.SOAK, MoveId.TACKLE]) - .startingHeldItems([{ name: "WIDE_LENS", count: 3 }]); + .startingHeldItems([{ entry: HeldItemId.WIDE_LENS, count: 3 }]); }); it("creates illusion at the start", async () => { diff --git a/test/abilities/no_guard.test.ts b/test/abilities/no_guard.test.ts index f6f55ce3b80..614f043508f 100644 --- a/test/abilities/no_guard.test.ts +++ b/test/abilities/no_guard.test.ts @@ -3,6 +3,7 @@ import { BattlerIndex } from "#enums/battler-index"; import { HitCheckResult } from "#enums/hit-check-result"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; +import { TrainerItemId } from "#enums/trainer-item-id"; import { MoveEffectPhase } from "#phases/move-effect-phase"; import { MoveEndPhase } from "#phases/move-end-phase"; import { GameManager } from "#test/testUtils/gameManager"; @@ -54,7 +55,7 @@ describe("Abilities - No Guard", () => { }); it("should guarantee double battle with any one LURE", async () => { - game.override.startingModifier([{ name: "LURE" }]).startingWave(2); + game.override.startingTrainerItems([{ entry: TrainerItemId.LURE }]).startingWave(2); await game.classicMode.startBattle(); diff --git a/test/abilities/normal-move-type-change.test.ts b/test/abilities/normal-move-type-change.test.ts index aa673ddde29..2d393218a9b 100644 --- a/test/abilities/normal-move-type-change.test.ts +++ b/test/abilities/normal-move-type-change.test.ts @@ -2,9 +2,11 @@ import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; import { allAbilities, allMoves } from "#data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { BattlerIndex } from "#enums/battler-index"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; +import { attackTypeToHeldItem } from "#items/attack-type-booster"; import { GameManager } from "#test/testUtils/gameManager"; import { toDmgValue } from "#utils/common"; import Phaser from "phaser"; @@ -151,7 +153,7 @@ describe.each([ }); it("should not be affected by silk scarf after changing the move's type", async () => { - game.override.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.NORMAL }]); + game.override.startingHeldItems([{ entry: HeldItemId.SILK_SCARF }]); await game.classicMode.startBattle(); const testMoveInstance = allMoves[MoveId.TACKLE]; @@ -170,7 +172,7 @@ describe.each([ }); it("should be affected by the type boosting item after changing the move's type", async () => { - game.override.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: ty }]); + game.override.startingHeldItems([{ entry: attackTypeToHeldItem[ty] }]); await game.classicMode.startBattle(); // get the power boost from the ability so we can compare it to the item diff --git a/test/abilities/normalize.test.ts b/test/abilities/normalize.test.ts index 1128dd1a562..6c19f8168e5 100644 --- a/test/abilities/normalize.test.ts +++ b/test/abilities/normalize.test.ts @@ -1,6 +1,7 @@ import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; import { allMoves } from "#data/data-lists"; import { AbilityId } from "#enums/ability-id"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; @@ -45,9 +46,7 @@ describe("Abilities - Normalize", () => { }); it("should not apply the old type boost item after changing a move's type", async () => { - game.override - .startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.GRASS }]) - .moveset([MoveId.LEAFAGE]); + game.override.startingHeldItems([{ entry: HeldItemId.MIRACLE_SEED }]).moveset([MoveId.LEAFAGE]); const powerSpy = vi.spyOn(allMoves[MoveId.LEAFAGE], "calculateBattlePower"); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); @@ -59,9 +58,7 @@ describe("Abilities - Normalize", () => { }); it("should apply silk scarf's power boost after changing a move's type", async () => { - game.override - .startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.NORMAL }]) - .moveset([MoveId.LEAFAGE]); + game.override.startingHeldItems([{ entry: HeldItemId.SILK_SCARF }]).moveset([MoveId.LEAFAGE]); const powerSpy = vi.spyOn(allMoves[MoveId.LEAFAGE], "calculateBattlePower"); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); diff --git a/test/abilities/protosynthesis.test.ts b/test/abilities/protosynthesis.test.ts index 5629ea503d7..e02e8fdcf2b 100644 --- a/test/abilities/protosynthesis.test.ts +++ b/test/abilities/protosynthesis.test.ts @@ -4,6 +4,7 @@ import { MoveId } from "#enums/move-id"; import { Nature } from "#enums/nature"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; +import { TrainerItemId } from "#enums/trainer-item-id"; import { GameManager } from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -37,7 +38,7 @@ describe("Abilities - Protosynthesis", () => { it("should not consider temporary items when determining which stat to boost", async () => { // Mew has uniform base stats game.override - .startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.DEF }]) + .startingTrainerItems([{ entry: TrainerItemId.X_DEFENSE }]) .enemyMoveset(MoveId.SUNNY_DAY) .startingLevel(100) .enemyLevel(100); diff --git a/test/abilities/unburden.test.ts b/test/abilities/unburden.test.ts index 647cd28855d..2ce6841ca3e 100644 --- a/test/abilities/unburden.test.ts +++ b/test/abilities/unburden.test.ts @@ -76,7 +76,7 @@ describe("Abilities - Unburden", () => { it("should activate when a berry is eaten, even if Berry Pouch preserves the berry", async () => { game.override .enemyMoveset(MoveId.FALSE_SWIPE) - .startingHeldItems([{ entry: TrainerItemId.BERRY_POUCH, count: 5850 }]); + .startingTrainerItems([{ entry: TrainerItemId.BERRY_POUCH, count: 5850 }]); await game.classicMode.startBattle([SpeciesId.TREECKO]); const playerPokemon = game.scene.getPlayerPokemon()!; diff --git a/test/abilities/wimp_out.test.ts b/test/abilities/wimp_out.test.ts index fc27bf10c86..57f9d786654 100644 --- a/test/abilities/wimp_out.test.ts +++ b/test/abilities/wimp_out.test.ts @@ -4,6 +4,7 @@ import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; @@ -205,7 +206,7 @@ describe("Abilities - Wimp Out", () => { game.override .moveset([MoveId.DOUBLE_EDGE]) .enemyMoveset([MoveId.SPLASH]) - .startingHeldItems([{ name: "SHELL_BELL", count: 4 }]); + .startingHeldItems([{ entry: HeldItemId.SHELL_BELL, count: 4 }]); await game.classicMode.startBattle([SpeciesId.WIMPOD, SpeciesId.TYRUNT]); const wimpod = game.scene.getPlayerPokemon()!; @@ -430,7 +431,7 @@ describe("Abilities - Wimp Out", () => { }); it("triggers after last hit of multi hit move (multi lens)", async () => { - game.override.enemyMoveset(MoveId.TACKLE).enemyHeldItems([{ name: "MULTI_LENS", count: 1 }]); + game.override.enemyMoveset(MoveId.TACKLE).enemyHeldItems([{ entry: HeldItemId.MULTI_LENS, count: 1 }]); await game.classicMode.startBattle([SpeciesId.WIMPOD, SpeciesId.TYRUNT]); game.scene.getPlayerPokemon()!.hp *= 0.51; diff --git a/test/items/metal_powder.test.ts b/test/items/metal_powder.test.ts index 17baa057fd5..3aa35d1b983 100644 --- a/test/items/metal_powder.test.ts +++ b/test/items/metal_powder.test.ts @@ -1,7 +1,9 @@ import { modifierTypes } from "#data/data-lists"; +import { HeldItemId } from "#enums/held-item-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; -import { SpeciesStatBoosterModifier } from "#modifiers/modifier"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; import i18next from "#plugins/i18n"; import { GameManager } from "#test/testUtils/gameManager"; import { NumberHolder } from "#utils/common"; @@ -29,7 +31,7 @@ describe("Items - Metal Powder", () => { }); it("METAL_POWDER activates in battle correctly", async () => { - game.override.startingHeldItems([{ name: "RARE_SPECIES_STAT_BOOSTER", type: "METAL_POWDER" }]); + game.override.startingHeldItems([{ entry: HeldItemId.METAL_POWDER }]); const consoleSpy = vi.spyOn(console, "log"); await game.classicMode.startBattle([SpeciesId.DITTO]); @@ -90,7 +92,7 @@ describe("Items - Metal Powder", () => { // Making sure modifier is not applied without holding item const defValue = new NumberHolder(defStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: defValue }); expect(defValue.value / defStat).toBe(1); @@ -99,7 +101,7 @@ describe("Items - Metal Powder", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["METAL_POWDER"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: defValue }); expect(defValue.value / defStat).toBe(2); }); @@ -123,7 +125,7 @@ describe("Items - Metal Powder", () => { // Making sure modifier is not applied without holding item const defValue = new NumberHolder(defStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: defValue }); expect(defValue.value / defStat).toBe(1); @@ -132,7 +134,7 @@ describe("Items - Metal Powder", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["METAL_POWDER"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: defValue }); expect(defValue.value / defStat).toBe(2); }); @@ -156,7 +158,7 @@ describe("Items - Metal Powder", () => { // Making sure modifier is not applied without holding item const defValue = new NumberHolder(defStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: defValue }); expect(defValue.value / defStat).toBe(1); @@ -165,7 +167,7 @@ describe("Items - Metal Powder", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["METAL_POWDER"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: defValue }); expect(defValue.value / defStat).toBe(2); }); @@ -179,7 +181,7 @@ describe("Items - Metal Powder", () => { // Making sure modifier is not applied without holding item const defValue = new NumberHolder(defStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: defValue }); expect(defValue.value / defStat).toBe(1); @@ -188,7 +190,7 @@ describe("Items - Metal Powder", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["METAL_POWDER"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: defValue }); expect(defValue.value / defStat).toBe(1); }); diff --git a/test/items/quick_powder.test.ts b/test/items/quick_powder.test.ts index 6c8cb0751c3..4148a7df92c 100644 --- a/test/items/quick_powder.test.ts +++ b/test/items/quick_powder.test.ts @@ -1,7 +1,9 @@ import { modifierTypes } from "#data/data-lists"; +import { HeldItemId } from "#enums/held-item-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; -import { SpeciesStatBoosterModifier } from "#modifiers/modifier"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; import i18next from "#plugins/i18n"; import { GameManager } from "#test/testUtils/gameManager"; import { NumberHolder } from "#utils/common"; @@ -29,7 +31,7 @@ describe("Items - Quick Powder", () => { }); it("QUICK_POWDER activates in battle correctly", async () => { - game.override.startingHeldItems([{ name: "RARE_SPECIES_STAT_BOOSTER", type: "QUICK_POWDER" }]); + game.override.startingHeldItems([{ entry: HeldItemId.QUICK_POWDER }]); const consoleSpy = vi.spyOn(console, "log"); await game.classicMode.startBattle([SpeciesId.DITTO]); @@ -90,7 +92,7 @@ describe("Items - Quick Powder", () => { // Making sure modifier is not applied without holding item const spdValue = new NumberHolder(spdStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPD, statValue: spdValue }); expect(spdValue.value / spdStat).toBe(1); @@ -99,7 +101,7 @@ describe("Items - Quick Powder", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["QUICK_POWDER"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPD, statValue: spdValue }); expect(spdValue.value / spdStat).toBe(2); }); @@ -123,7 +125,7 @@ describe("Items - Quick Powder", () => { // Making sure modifier is not applied without holding item const spdValue = new NumberHolder(spdStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPD, statValue: spdValue }); expect(spdValue.value / spdStat).toBe(1); @@ -132,7 +134,7 @@ describe("Items - Quick Powder", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["QUICK_POWDER"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPD, statValue: spdValue }); expect(spdValue.value / spdStat).toBe(2); }); @@ -156,7 +158,7 @@ describe("Items - Quick Powder", () => { // Making sure modifier is not applied without holding item const spdValue = new NumberHolder(spdStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPD, statValue: spdValue }); expect(spdValue.value / spdStat).toBe(1); @@ -165,7 +167,7 @@ describe("Items - Quick Powder", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["QUICK_POWDER"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPD, statValue: spdValue }); expect(spdValue.value / spdStat).toBe(2); }); @@ -179,7 +181,7 @@ describe("Items - Quick Powder", () => { // Making sure modifier is not applied without holding item const spdValue = new NumberHolder(spdStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPD, statValue: spdValue }); expect(spdValue.value / spdStat).toBe(1); @@ -188,7 +190,7 @@ describe("Items - Quick Powder", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["QUICK_POWDER"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPD, statValue: spdValue }); expect(spdValue.value / spdStat).toBe(1); }); diff --git a/test/items/thick_club.test.ts b/test/items/thick_club.test.ts index dd262600823..6fb1e6fbab7 100644 --- a/test/items/thick_club.test.ts +++ b/test/items/thick_club.test.ts @@ -2,6 +2,8 @@ import { modifierTypes } from "#data/data-lists"; import { HeldItemId } from "#enums/held-item-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; +import { applyHeldItems } from "#items/all-held-items"; +import { HeldItemEffect } from "#items/held-item"; import i18next from "#plugins/i18n"; import { GameManager } from "#test/testUtils/gameManager"; import { NumberHolder, randInt } from "#utils/common"; @@ -90,7 +92,7 @@ describe("Items - Thick Club", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(1); @@ -99,7 +101,7 @@ describe("Items - Thick Club", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(2); }); @@ -113,7 +115,7 @@ describe("Items - Thick Club", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(1); @@ -122,7 +124,7 @@ describe("Items - Thick Club", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(2); }); @@ -136,7 +138,7 @@ describe("Items - Thick Club", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(1); @@ -145,7 +147,7 @@ describe("Items - Thick Club", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(2); }); @@ -173,7 +175,7 @@ describe("Items - Thick Club", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(1); @@ -182,7 +184,7 @@ describe("Items - Thick Club", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(2); }); @@ -210,7 +212,7 @@ describe("Items - Thick Club", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(1); @@ -219,7 +221,7 @@ describe("Items - Thick Club", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(2); }); @@ -233,7 +235,7 @@ describe("Items - Thick Club", () => { // Making sure modifier is not applied without holding item const atkValue = new NumberHolder(atkStat); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(1); @@ -242,7 +244,7 @@ describe("Items - Thick Club", () => { modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), true, ); - game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(1); }); diff --git a/test/moves/electro_shot.test.ts b/test/moves/electro_shot.test.ts index cb696b162d5..16ae47890e4 100644 --- a/test/moves/electro_shot.test.ts +++ b/test/moves/electro_shot.test.ts @@ -1,5 +1,6 @@ import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { MoveResult } from "#enums/move-result"; import { SpeciesId } from "#enums/species-id"; @@ -87,7 +88,7 @@ describe("Moves - Electro Shot", () => { }); it("should only increase Sp. Atk once with Multi-Lens", async () => { - game.override.weather(WeatherType.RAIN).startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); + game.override.weather(WeatherType.RAIN).startingHeldItems([{ entry: HeldItemId.MULTI_LENS }]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); diff --git a/test/moves/tera_blast.test.ts b/test/moves/tera_blast.test.ts index d6c16c31544..618e9e66d0a 100644 --- a/test/moves/tera_blast.test.ts +++ b/test/moves/tera_blast.test.ts @@ -1,6 +1,7 @@ import { allMoves } from "#data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { BattlerIndex } from "#enums/battler-index"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; @@ -141,9 +142,7 @@ describe("Moves - Tera Blast", () => { }); it("does not change its move category from stat changes due to held items", async () => { - game.override - .startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "THICK_CLUB" }]) - .starterSpecies(SpeciesId.CUBONE); + game.override.startingHeldItems([{ entry: HeldItemId.THICK_CLUB }]).starterSpecies(SpeciesId.CUBONE); await game.classicMode.startBattle(); const playerPokemon = game.scene.getPlayerPokemon()!; diff --git a/test/moves/transform-imposter.test.ts b/test/moves/transform-imposter.test.ts index c12542e64e9..cf582d482ce 100644 --- a/test/moves/transform-imposter.test.ts +++ b/test/moves/transform-imposter.test.ts @@ -3,7 +3,7 @@ import { AbilityId } from "#enums/ability-id"; import { BattleType } from "#enums/battle-type"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { BerryType } from "#enums/berry-type"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { MoveResult } from "#enums/move-result"; import { PokemonType } from "#enums/pokemon-type"; @@ -128,7 +128,7 @@ describe("Transforming Effects", () => { it.todo("should copy the target's rage fist hit count"); it("should not copy friendship, held items, nickname, level or non-volatile status effects", async () => { - game.override.enemyHeldItems([{ name: "BERRY", count: 1, type: BerryType.SITRUS }]); + game.override.enemyHeldItems([{ entry: HeldItemId.SITRUS_BERRY }]); await game.classicMode.startBattle([SpeciesId.DITTO]); const ditto = game.field.getPlayerPokemon(); From e7bb3a74439a2b1a5886ea32888e19d53cdf0eb5 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 13 Jul 2025 12:45:11 +0200 Subject: [PATCH 27/39] formChangeItemName function --- src/items/item-utility.ts | 7 ++++++- src/modifier/modifier-type.ts | 1 + src/ui/party-ui-handler.ts | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/items/item-utility.ts b/src/items/item-utility.ts index db858122bcf..91788f90d1e 100644 --- a/src/items/item-utility.ts +++ b/src/items/item-utility.ts @@ -1,7 +1,12 @@ import { allHeldItems, allTrainerItems } from "#data/data-lists"; -import type { FormChangeItem } from "#enums/form-change-item"; +import { FormChangeItem } from "#enums/form-change-item"; import type { HeldItemId } from "#enums/held-item-id"; import type { TrainerItemId } from "#enums/trainer-item-id"; +import i18next from "i18next"; + +export function formChangeItemName(id: FormChangeItem) { + return i18next.t(`modifierType:FormChangeItem.${FormChangeItem[id]}`); +} export const trainerItemSortFunc = (a: TrainerItemId, b: TrainerItemId): number => { const itemNameMatch = allTrainerItems[a].name.localeCompare(allTrainerItems[b].name); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index da589fb70b3..cd0c5c096b6 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -30,6 +30,7 @@ import { attackTypeToHeldItem } from "#items/attack-type-booster"; import { permanentStatToHeldItem, statBoostItems } from "#items/base-stat-booster"; import { berryTypeToHeldItem } from "#items/berry"; import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#items/held-item-pool"; +import { formChangeItemName } from "#items/item-utility"; import { SPECIES_STAT_BOOSTER_ITEMS, type SpeciesStatBoosterItemId, diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index aefbb85ccad..bf3818927ca 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -5,7 +5,6 @@ import { applyChallenges } from "#data/challenge"; import { allHeldItems, allMoves } from "#data/data-lists"; import { SpeciesFormChangeItemTrigger } from "#data/form-change-triggers"; import { Gender, getGenderColor, getGenderSymbol } from "#data/gender"; -import { formChangeItemName } from "#data/pokemon-forms"; import { Button } from "#enums/buttons"; import { ChallengeType } from "#enums/challenge-type"; import { Command } from "#enums/command"; @@ -17,6 +16,7 @@ import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; +import { formChangeItemName } from "#items/item-utility"; import type { PokemonMove } from "#moves/pokemon-move"; import type { CommandPhase } from "#phases/command-phase"; import { getVariantTint } from "#sprites/variant"; From eaead476e86907775b515d57e25760db3b351c39 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 13 Jul 2025 12:55:07 +0200 Subject: [PATCH 28/39] Add some type specifications so biome is happy --- src/enums/held-item-id.ts | 2 +- src/items/held-item-pool.ts | 2 +- src/phases/berry-phase.ts | 3 ++- src/ui/item-bar-ui.ts | 9 +++++++-- src/ui/target-select-ui-handler.ts | 5 +++-- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index d8fc58e28eb..f714f332d2a 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -128,7 +128,7 @@ export function getHeldItemCategory(itemId: HeldItemId): HeldItemCategoryId { return (itemId & ITEM_CATEGORY_MASK) as HeldItemCategoryId; } -export function isCategoryId(id: number): boolean { +export function isCategoryId(id: number): id is HeldItemCategoryId { return (Object.values(HeldItemCategoryId) as number[]).includes(id); } diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index 01755624cfe..077c5be730b 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -287,7 +287,7 @@ export function assignItemsFromConfiguration(config: HeldItemConfiguration, poke if (isCategoryId(entry)) { assignItemsFromCategory(entry, pokemon, actualCount); } - pokemon.heldItemManager.add(entry, actualCount); + pokemon.heldItemManager.add(entry as HeldItemId, actualCount); } if (isHeldItemSpecs(entry)) { diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index e968201be73..0649d6d7208 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -6,6 +6,7 @@ import { HeldItemCategoryId, isItemInCategory } from "#enums/held-item-id"; import { CommonAnim } from "#enums/move-anims-common"; import type { Pokemon } from "#field/pokemon"; import { applyHeldItems } from "#items/all-held-items"; +import type { BerryHeldItem } from "#items/berry"; import { HeldItemEffect } from "#items/held-item"; import { FieldPhase } from "#phases/field-phase"; import { BooleanHolder } from "#utils/common"; @@ -34,7 +35,7 @@ export class BerryPhase extends FieldPhase { */ eatBerries(pokemon: Pokemon): void { const hasUsableBerry = pokemon.getHeldItems().some(m => { - return isItemInCategory(m, HeldItemCategoryId.BERRY) && allHeldItems[m].shouldApply(pokemon); + return isItemInCategory(m, HeldItemCategoryId.BERRY) && (allHeldItems[m] as BerryHeldItem).shouldApply(pokemon); }); if (!hasUsableBerry) { diff --git a/src/ui/item-bar-ui.ts b/src/ui/item-bar-ui.ts index 4cfe5ccd86d..85c81bb204b 100644 --- a/src/ui/item-bar-ui.ts +++ b/src/ui/item-bar-ui.ts @@ -1,5 +1,7 @@ import { globalScene } from "#app/global-scene"; import { allHeldItems, allTrainerItems } from "#data/data-lists"; +import type { HeldItemId } from "#enums/held-item-id"; +import type { TrainerItemId } from "#enums/trainer-item-id"; import type { Pokemon } from "#field/pokemon"; import { heldItemSortFunc, trainerItemSortFunc } from "#items/item-utility"; import type { TrainerItemManager } from "#items/trainer-item-manager"; @@ -8,7 +10,7 @@ const iconOverflowIndex = 24; export class ItemBar extends Phaser.GameObjects.Container { private player: boolean; - private itemCache: number[]; + private itemCache: (HeldItemId | TrainerItemId)[]; public totalVisibleLength = 0; constructor(enemy?: boolean) { @@ -60,7 +62,10 @@ export class ItemBar extends Phaser.GameObjects.Container { this.sendToBack(icon); } - this.itemCache = sortedTrainerItems.concat(heldItemsA).concat(heldItemsB); + this.itemCache = ([] as (TrainerItemId | HeldItemId)[]) + .concat(sortedTrainerItems) + .concat(heldItemsA) + .concat(heldItemsB); } addIcon(icon: Phaser.GameObjects.Container, i: number, name: string, description: string) { diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index dcef1bd1fe6..f4f8e3af8dd 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -8,6 +8,7 @@ import type { Pokemon } from "#field/pokemon"; import { getMoveTargets } from "#moves/move-utils"; import { UiHandler } from "#ui/ui-handler"; import { fixedInt, isNullOrUndefined } from "#utils/common"; +import type { ItemBar } from "./item-bar-ui"; export type TargetSelectCallback = (targets: BattlerIndex[]) => void; @@ -22,7 +23,7 @@ export class TargetSelectUiHandler extends UiHandler { private targets: BattlerIndex[]; private targetsHighlighted: Pokemon[]; private targetFlashTween: Phaser.Tweens.Tween | null; - private enemyModifiers: ModifierBar; + private enemyModifiers: ItemBar; private targetBattleInfoMoveTween: Phaser.Tweens.Tween[] = []; constructor() { @@ -53,7 +54,7 @@ export class TargetSelectUiHandler extends UiHandler { return false; } - this.enemyModifiers = globalScene.getModifierBar(true); + this.enemyModifiers = globalScene.getItemBar(true); if (this.fieldIndex === BattlerIndex.PLAYER) { this.resetCursor(this.cursor0, user); From e034d7e04ef0f5c66e3febe297b14560dd81d4fc Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 13 Jul 2025 13:04:46 +0200 Subject: [PATCH 29/39] Added test override for pokeballs --- test/moves/payback.test.ts | 2 +- test/testUtils/helpers/overridesHelper.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/test/moves/payback.test.ts b/test/moves/payback.test.ts index 20e832288a3..a92d22d7b8f 100644 --- a/test/moves/payback.test.ts +++ b/test/moves/payback.test.ts @@ -33,7 +33,7 @@ describe("Move - Payback", () => { .enemySpecies(SpeciesId.DRACOVISH) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) - .startingModifier([{ name: "POKEBALL", count: 5 }]); + .startingPokeballs({ [PokeballType.POKEBALL]: 5 }); powerSpy = vi.spyOn(allMoves[MoveId.PAYBACK], "calculateBattlePower"); }); diff --git a/test/testUtils/helpers/overridesHelper.ts b/test/testUtils/helpers/overridesHelper.ts index 9fc13407844..d8e6bf0d9d9 100644 --- a/test/testUtils/helpers/overridesHelper.ts +++ b/test/testUtils/helpers/overridesHelper.ts @@ -1,7 +1,9 @@ /** biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ import type { NewArenaEvent } from "#events/battle-scene"; + /** biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ +import type { PokeballCounts } from "#app/battle-scene"; import type { BattleStyle, RandomTrainerOverride } from "#app/overrides"; import Overrides, { defaultOverrides } from "#app/overrides"; import { AbilityId } from "#enums/ability-id"; @@ -134,6 +136,17 @@ export class OverridesHelper extends GameManagerHelper { return this; } + /** + * Override the player's starting pokeballs + * @param items - The items to hold + * @returns `this` + */ + public startingPokeballs(pokeballs: PokeballCounts): this { + vi.spyOn(Overrides, "POKEBALL_OVERRIDE", "get").mockReturnValue({ active: true, pokeballs: pokeballs }); + this.log("Player Pokemon starting held items set to:", { active: true, pokeballs: pokeballs }); + return this; + } + /** * Override the player pokemon's {@linkcode SpeciesId | species} * @param species - The {@linkcode SpeciesId | species} to set From b8324e85f76b106f5b6ca7bf238f944e0f7c4d17 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 13 Jul 2025 13:20:42 +0200 Subject: [PATCH 30/39] Moved getPartyBerries() utility function --- .../encounters/absolute-avarice-encounter.ts | 2 +- .../encounters/uncommon-breed-encounter.ts | 2 +- .../utils/encounter-phase-utils.ts | 31 ++----------------- src/items/item-utility.ts | 31 ++++++++++++++++++- .../berries-abound-encounter.test.ts | 6 ++-- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 0c304927b28..8a5b0821033 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -16,11 +16,11 @@ import type { MysteryEncounterSpriteConfig } from "#field/mystery-encounter-intr import type { Pokemon } from "#field/pokemon"; import { EnemyPokemon } from "#field/pokemon"; import type { HeldItemConfiguration, PokemonItemMap } from "#items/held-item-data-types"; +import { getPartyBerries } from "#items/item-utility"; import { PokemonMove } from "#moves/pokemon-move"; import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; import { - getPartyBerries, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index bcbbb860623..18e5f604f44 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -12,11 +12,11 @@ import { PokeballType } from "#enums/pokeball"; import { Stat } from "#enums/stat"; import type { EnemyPokemon, Pokemon } from "#field/pokemon"; import type { PokemonItemMap } from "#items/held-item-data-types"; +import { getPartyBerries } from "#items/item-utility"; import { PokemonMove } from "#moves/pokemon-move"; import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; import { - getPartyBerries, getRandomEncounterSpecies, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 74b81d3df1b..d08dcbf9582 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -5,7 +5,7 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { BiomePoolTier, biomeLinks } from "#balance/biomes"; import { initMoveAnim, loadMoveAnimAssets } from "#data/battle-anims"; -import { allHeldItems, modifierTypes } from "#data/data-lists"; +import { modifierTypes } from "#data/data-lists"; import type { IEggOptions } from "#data/egg"; import { Egg } from "#data/egg"; import type { Gender } from "#data/gender"; @@ -17,7 +17,6 @@ import type { AiType } from "#enums/ai-type"; import type { BattlerTagType } from "#enums/battler-tag-type"; import { BiomeId } from "#enums/biome-id"; import { FieldPosition } from "#enums/field-position"; -import { HeldItemCategoryId, type HeldItemId, isItemInCategory } from "#enums/held-item-id"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import type { MoveId } from "#enums/move-id"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; @@ -31,7 +30,7 @@ import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; import { EnemyPokemon } from "#field/pokemon"; import { Trainer } from "#field/trainer"; -import type { HeldItemConfiguration, PokemonItemMap } from "#items/held-item-data-types"; +import type { HeldItemConfiguration } from "#items/held-item-data-types"; import type { CustomModifierSettings, ModifierType } from "#modifiers/modifier-type"; import { getPartyLuckValue, ModifierTypeGenerator, ModifierTypeOption } from "#modifiers/modifier-type"; import { PokemonMove } from "#moves/pokemon-move"; @@ -1294,29 +1293,3 @@ export function calculateRareSpawnAggregateStats(luckValue: number) { console.log(stats); } - -// Iterate over the party until an item is successfully given -export function assignItemToFirstFreePokemon(item: HeldItemId, party: Pokemon[]): void { - for (const pokemon of party) { - const stack = pokemon.heldItemManager.getStack(item); - if (stack < allHeldItems[item].getMaxStackCount()) { - pokemon.heldItemManager.add(item); - return; - } - } -} - -// Creates an item map of berries to pokemon, storing each berry separately (splitting up stacks) -export function getPartyBerries(): PokemonItemMap[] { - const pokemonItems: PokemonItemMap[] = []; - globalScene.getPlayerParty().forEach(pokemon => { - const berries = pokemon.getHeldItems().filter(item => isItemInCategory(item, HeldItemCategoryId.BERRY)); - berries.forEach(berryId => { - const berryStack = pokemon.heldItemManager.getStack(berryId); - for (let i = 1; i <= berryStack; i++) { - pokemonItems.push({ item: { id: berryId, stack: 1 }, pokemonId: pokemon.id }); - } - }); - }); - return pokemonItems; -} diff --git a/src/items/item-utility.ts b/src/items/item-utility.ts index 91788f90d1e..89e6b6c3233 100644 --- a/src/items/item-utility.ts +++ b/src/items/item-utility.ts @@ -1,8 +1,11 @@ +import { globalScene } from "#app/global-scene"; import { allHeldItems, allTrainerItems } from "#data/data-lists"; import { FormChangeItem } from "#enums/form-change-item"; -import type { HeldItemId } from "#enums/held-item-id"; +import { HeldItemCategoryId, type HeldItemId, isItemInCategory } from "#enums/held-item-id"; import type { TrainerItemId } from "#enums/trainer-item-id"; +import type { Pokemon } from "#field/pokemon"; import i18next from "i18next"; +import type { PokemonItemMap } from "./held-item-data-types"; export function formChangeItemName(id: FormChangeItem) { return i18next.t(`modifierType:FormChangeItem.${FormChangeItem[id]}`); @@ -43,3 +46,29 @@ export const formChangeItemSortFunc = (a: FormChangeItem, b: FormChangeItem): nu } return itemIdMatch; }; + +// Iterate over the party until an item is successfully given +export function assignItemToFirstFreePokemon(item: HeldItemId, party: Pokemon[]): void { + for (const pokemon of party) { + const stack = pokemon.heldItemManager.getStack(item); + if (stack < allHeldItems[item].getMaxStackCount()) { + pokemon.heldItemManager.add(item); + return; + } + } +} + +// Creates an item map of berries to pokemon, storing each berry separately (splitting up stacks) +export function getPartyBerries(): PokemonItemMap[] { + const pokemonItems: PokemonItemMap[] = []; + globalScene.getPlayerParty().forEach(pokemon => { + const berries = pokemon.getHeldItems().filter(item => isItemInCategory(item, HeldItemCategoryId.BERRY)); + berries.forEach(berryId => { + const berryStack = pokemon.heldItemManager.getStack(berryId); + for (let i = 1; i <= berryStack; i++) { + pokemonItems.push({ item: { id: berryId, stack: 1 }, pokemonId: pokemon.id }); + } + }); + }); + return pokemonItems; +} diff --git a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts index 7af08dcd883..0c9064b3955 100644 --- a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts +++ b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts @@ -6,6 +6,8 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; +import type { HeldItemSpecs } from "#items/held-item-data-types"; +import { getPartyBerries } from "#items/item-utility"; import { BerriesAboundEncounter } from "#mystery-encounters/berries-abound-encounter"; import * as EncounterDialogueUtils from "#mystery-encounters/encounter-dialogue-utils"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; @@ -133,8 +135,8 @@ describe("Berries Abound - Mystery Encounter", () => { await game.phaseInterceptor.to(SelectRewardPhase, false); expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); - const berriesAfter = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; - const berriesAfterCount = berriesAfter.reduce((a, b) => a + b.stackCount, 0); + const berriesAfter = getPartyBerries(); + const berriesAfterCount = berriesAfter.reduce((a, b) => a + (b.item as HeldItemSpecs).stack, 0); expect(numBerries).toBe(berriesAfterCount); }); From c7a1b0fac568bb3f4c397ecdc7301c086756e900 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 13 Jul 2025 15:59:53 +0200 Subject: [PATCH 31/39] Reworked getPartyBerries function, fixed harvest test --- .../encounters/absolute-avarice-encounter.ts | 15 +-- .../encounters/uncommon-breed-encounter.ts | 13 +-- src/items/item-utility.ts | 4 +- test/abilities/harvest.test.ts | 101 +++++++++--------- 4 files changed, 68 insertions(+), 65 deletions(-) diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 8a5b0821033..8a6d2daebb0 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -15,7 +15,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import type { MysteryEncounterSpriteConfig } from "#field/mystery-encounter-intro"; import type { Pokemon } from "#field/pokemon"; import { EnemyPokemon } from "#field/pokemon"; -import type { HeldItemConfiguration, PokemonItemMap } from "#items/held-item-data-types"; +import type { HeldItemConfiguration, HeldItemSpecs, PokemonItemMap } from "#items/held-item-data-types"; import { getPartyBerries } from "#items/item-utility"; import { PokemonMove } from "#moves/pokemon-move"; import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils"; @@ -31,7 +31,7 @@ import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; import { HeldItemRequirement } from "#mystery-encounters/mystery-encounter-requirements"; -import { randInt } from "#utils/common"; +import { pickWeightedIndex, randInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; @@ -231,14 +231,17 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde const party = globalScene.getPlayerParty(); party.forEach(pokemon => { const stolenBerries = berryMap.filter(map => map.pokemonId === pokemon.id); - const returnedBerryCount = Math.floor(((stolenBerries.length ?? 0) * 2) / 5); + const stolenBerryCount = stolenBerries.reduce((a, b) => a + (b.item as HeldItemSpecs).stack, 0); + const returnedBerryCount = Math.floor(((stolenBerryCount ?? 0) * 2) / 5); if (returnedBerryCount > 0) { for (let i = 0; i < returnedBerryCount; i++) { // Shuffle remaining berry types and pop - Phaser.Math.RND.shuffle(stolenBerries); - const randBerryType = stolenBerries.pop(); - pokemon.heldItemManager.add(randBerryType?.item.id as HeldItemId); + const berryWeights = stolenBerries.map(b => (b.item as HeldItemSpecs).stack); + const which = pickWeightedIndex(berryWeights) ?? 0; + const randBerry = stolenBerries[which]; + pokemon.heldItemManager.add(randBerry.item.id as HeldItemId); + (randBerry.item as HeldItemSpecs).stack -= 1; } } }); diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 18e5f604f44..52986bec62e 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -11,7 +11,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PokeballType } from "#enums/pokeball"; import { Stat } from "#enums/stat"; import type { EnemyPokemon, Pokemon } from "#field/pokemon"; -import type { PokemonItemMap } from "#items/held-item-data-types"; +import type { HeldItemSpecs } from "#items/held-item-data-types"; import { getPartyBerries } from "#items/item-utility"; import { PokemonMove } from "#moves/pokemon-move"; import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils"; @@ -34,7 +34,7 @@ import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encou import { HeldItemRequirement, MoveRequirement } from "#mystery-encounters/mystery-encounter-requirements"; import { CHARMING_MOVES } from "#mystery-encounters/requirement-groups"; import { PokemonData } from "#system/pokemon-data"; -import { isNullOrUndefined, randSeedInt } from "#utils/common"; +import { isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/uncommonBreed"; @@ -204,17 +204,14 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder. // Give it some food // Remove 4 random berries from player's party - // Get all player berry items, remove from party, and store reference - const berryMap = getPartyBerries(); - const stolenBerryMap: PokemonItemMap[] = []; for (let i = 0; i < 4; i++) { - const index = randSeedInt(berryMap.length); + const berryWeights = berryMap.map(b => (b.item as HeldItemSpecs).stack); + const index = pickWeightedIndex(berryWeights) ?? 0; const randBerry = berryMap[index]; globalScene.getPokemonById(randBerry.pokemonId)?.heldItemManager.remove(randBerry.item.id as HeldItemId); - stolenBerryMap.push(randBerry); - berryMap.splice(index, 1); + (randBerry.item as HeldItemSpecs).stack -= 1; } await globalScene.updateItems(true); diff --git a/src/items/item-utility.ts b/src/items/item-utility.ts index 89e6b6c3233..4108ab7e636 100644 --- a/src/items/item-utility.ts +++ b/src/items/item-utility.ts @@ -65,9 +65,7 @@ export function getPartyBerries(): PokemonItemMap[] { const berries = pokemon.getHeldItems().filter(item => isItemInCategory(item, HeldItemCategoryId.BERRY)); berries.forEach(berryId => { const berryStack = pokemon.heldItemManager.getStack(berryId); - for (let i = 1; i <= berryStack; i++) { - pokemonItems.push({ item: { id: berryId, stack: 1 }, pokemonId: pokemon.id }); - } + pokemonItems.push({ item: { id: berryId, stack: berryStack }, pokemonId: pokemon.id }); }); }); return pokemonItems; diff --git a/test/abilities/harvest.test.ts b/test/abilities/harvest.test.ts index 7959c89663e..d5452835116 100644 --- a/test/abilities/harvest.test.ts +++ b/test/abilities/harvest.test.ts @@ -6,11 +6,11 @@ import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; +import { TrainerItemId } from "#enums/trainer-item-id"; import { WeatherType } from "#enums/weather-type"; -import type { Pokemon } from "#field/pokemon"; -import type { ModifierOverride } from "#modifiers/modifier-type"; +import type { PokemonItemMap } from "#items/held-item-data-types"; +import { getPartyBerries } from "#items/item-utility"; import { GameManager } from "#test/testUtils/gameManager"; -import type { BooleanHolder } from "#utils/common"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; @@ -18,15 +18,9 @@ describe("Abilities - Harvest", () => { let phaserGame: Phaser.Game; let game: GameManager; - const getPlayerBerries = () => - game.scene.getModifiers(BerryModifier, true).filter(b => b.pokemonId === game.scene.getPlayerPokemon()?.id); - /** Check whether the player's Modifiers contains the specified berries and nothing else. */ - function expectBerriesContaining(...berries: ModifierOverride[]): void { - const actualBerries: ModifierOverride[] = getPlayerBerries().map( - // only grab berry type and quantity since that's literally all we care about - b => ({ name: "BERRY", type: b.berryType, count: b.getStackCount() }), - ); + function expectBerriesContaining(berries: PokemonItemMap[]): void { + const actualBerries = getPartyBerries(); expect(actualBerries).toEqual(berries); } @@ -63,11 +57,13 @@ describe("Abilities - Harvest", () => { game.move.select(MoveId.SPLASH); await game.move.selectEnemyMove(MoveId.NUZZLE); await game.phaseInterceptor.to("BerryPhase"); - expect(getPlayerBerries()).toHaveLength(0); + expect(getPartyBerries()).toHaveLength(0); expect(game.scene.getPlayerPokemon()?.battleData.berriesEaten).toHaveLength(1); await game.phaseInterceptor.to("TurnEndPhase"); - expectBerriesContaining({ name: "BERRY", type: BerryType.LUM, count: 1 }); + expectBerriesContaining([ + { item: { id: HeldItemId.LUM_BERRY, stack: 1 }, pokemonId: game.scene.getPlayerPokemon()?.id! }, + ]); expect(game.scene.getPlayerPokemon()?.battleData.berriesEaten).toEqual([]); }); @@ -76,8 +72,8 @@ describe("Abilities - Harvest", () => { // the game consider all other pokemon to *not* have their respective abilities. game.override .startingHeldItems([ - { name: "BERRY", type: BerryType.ENIGMA, count: 2 }, - { name: "BERRY", type: BerryType.LUM, count: 2 }, + { entry: HeldItemId.ENIGMA_BERRY, count: 2 }, + { entry: HeldItemId.LUM_BERRY, count: 2 }, ]) .enemyAbility(AbilityId.NEUTRALIZING_GAS); await game.classicMode.startBattle([SpeciesId.MILOTIC]); @@ -91,7 +87,7 @@ describe("Abilities - Harvest", () => { await game.toNextTurn(); expect(milotic.battleData.berriesEaten).toEqual(expect.arrayContaining([BerryType.ENIGMA, BerryType.LUM])); - expect(getPlayerBerries()).toHaveLength(2); + expect(getPartyBerries()).toHaveLength(2); // Give ourselves harvest and disable enemy neut gas, // but force our roll to fail so we don't accidentally recover anything @@ -105,7 +101,7 @@ describe("Abilities - Harvest", () => { expect(milotic.battleData.berriesEaten).toEqual( expect.arrayContaining([BerryType.ENIGMA, BerryType.LUM, BerryType.ENIGMA, BerryType.LUM]), ); - expect(getPlayerBerries()).toHaveLength(0); + expect(getPartyBerries()).toHaveLength(0); // proc a high roll and we _should_ get a berry back! game.move.select(MoveId.SPLASH); @@ -113,7 +109,7 @@ describe("Abilities - Harvest", () => { await game.toNextTurn(); expect(milotic.battleData.berriesEaten).toHaveLength(3); - expect(getPlayerBerries()).toHaveLength(1); + expect(getPartyBerries()).toHaveLength(1); }); it("remembers berries eaten array across waves", async () => { @@ -131,13 +127,13 @@ describe("Abilities - Harvest", () => { // ate 1 berry without recovering (no harvest) expect(regieleki.battleData.berriesEaten).toEqual([BerryType.PETAYA]); - expectBerriesContaining({ name: "BERRY", count: 1, type: BerryType.PETAYA }); + expectBerriesContaining([{ item: { id: HeldItemId.PETAYA_BERRY, stack: 1 }, pokemonId: regieleki.id }]); expect(regieleki.getStatStage(Stat.SPATK)).toBe(1); await game.toNextWave(); expect(regieleki.battleData.berriesEaten).toEqual([BerryType.PETAYA]); - expectBerriesContaining({ name: "BERRY", count: 1, type: BerryType.PETAYA }); + expectBerriesContaining([{ item: { id: HeldItemId.PETAYA_BERRY, stack: 1 }, pokemonId: regieleki.id }]); expect(regieleki.getStatStage(Stat.SPATK)).toBe(1); }); @@ -159,7 +155,9 @@ describe("Abilities - Harvest", () => { // ate 1 berry and recovered it expect(regieleki.battleData.berriesEaten).toEqual([]); - expect(getPlayerBerries()).toEqual([expect.objectContaining({ berryType: BerryType.PETAYA, stackCount: 1 })]); + expectBerriesContaining([ + { item: { id: HeldItemId.PETAYA_BERRY, stack: 1 }, pokemonId: game.scene.getPlayerPokemon()?.id! }, + ]); expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.SPATK)).toBe(1); // heal up so harvest doesn't proc and kill enemy @@ -168,13 +166,17 @@ describe("Abilities - Harvest", () => { await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextWave(); - expectBerriesContaining({ name: "BERRY", count: 1, type: BerryType.PETAYA }); + expectBerriesContaining([ + { item: { id: HeldItemId.PETAYA_BERRY, stack: 1 }, pokemonId: game.scene.getPlayerPokemon()?.id! }, + ]); expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.SPATK)).toBe(1); await game.reload.reloadSession(); expect(regieleki.battleData.berriesEaten).toEqual([]); - expectBerriesContaining({ name: "BERRY", count: 1, type: BerryType.PETAYA }); + expectBerriesContaining([ + { item: { id: HeldItemId.PETAYA_BERRY, stack: 1 }, pokemonId: game.scene.getPlayerPokemon()?.id! }, + ]); expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.SPATK)).toBe(1); }); @@ -199,16 +201,16 @@ describe("Abilities - Harvest", () => { await game.phaseInterceptor.to("TurnEndPhase"); // recovered a starf - expectBerriesContaining( - { name: "BERRY", type: BerryType.LUM, count: 2 }, - { name: "BERRY", type: BerryType.STARF, count: 3 }, - ); + expectBerriesContaining([ + { item: { id: HeldItemId.LUM_BERRY, stack: 2 }, pokemonId: feebas.id }, + { item: { id: HeldItemId.STARF_BERRY, stack: 3 }, pokemonId: feebas.id }, + ]); }); it("does nothing if all berries are capped", async () => { - const initBerries: ModifierOverride[] = [ - { name: "BERRY", type: BerryType.LUM, count: 2 }, - { name: "BERRY", type: BerryType.STARF, count: 3 }, + const initBerries = [ + { entry: HeldItemId.LUM_BERRY, count: 2 }, + { entry: HeldItemId.STARF_BERRY, count: 3 }, ]; game.override.startingHeldItems(initBerries); await game.classicMode.startBattle([SpeciesId.FEEBAS]); @@ -220,7 +222,10 @@ describe("Abilities - Harvest", () => { await game.move.selectEnemyMove(MoveId.SPLASH); await game.phaseInterceptor.to("TurnEndPhase"); - expectBerriesContaining(...initBerries); + expectBerriesContaining([ + { item: { id: HeldItemId.LUM_BERRY, stack: 2 }, pokemonId: player.id }, + { item: { id: HeldItemId.STARF_BERRY, stack: 3 }, pokemonId: player.id }, + ]); }); describe("move/ability interactions", () => { @@ -236,7 +241,7 @@ describe("Abilities - Harvest", () => { }); it("cannot restore knocked off berries", async () => { - game.override.startingHeldItems([{ name: "BERRY", type: BerryType.STARF, count: 3 }]); + game.override.startingHeldItems([{ entry: HeldItemId.STARF_BERRY, count: 3 }]); await game.classicMode.startBattle([SpeciesId.FEEBAS]); game.move.select(MoveId.SPLASH); @@ -255,7 +260,9 @@ describe("Abilities - Harvest", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(game.scene.getPlayerPokemon()?.battleData.berriesEaten).toEqual([]); - expectBerriesContaining(...initBerries); + expectBerriesContaining([ + { item: { id: HeldItemId.STARF_BERRY, stack: 1 }, pokemonId: game.scene.getPlayerPokemon()!.id }, + ]); }); it("cannot restore Plucked berries for either side", async () => { @@ -272,21 +279,13 @@ describe("Abilities - Harvest", () => { // pluck triggers harvest for neither side expect(game.scene.getPlayerPokemon()?.battleData.berriesEaten).toEqual([]); expect(game.scene.getEnemyPokemon()?.battleData.berriesEaten).toEqual([]); - expect(getPlayerBerries()).toEqual([]); + expect(getPartyBerries()).toEqual([]); }); it("cannot restore berries preserved via Berry Pouch", async () => { - // mock berry pouch to have a 100% success rate - vi.spyOn(PreserveBerryModifier.prototype, "apply").mockImplementation( - (_pokemon: Pokemon, doPreserve: BooleanHolder): boolean => { - doPreserve.value = false; - return true; - }, - ); - game.override .startingHeldItems([{ entry: HeldItemId.PETAYA_BERRY }]) - .startingModifier([{ name: "BERRY_POUCH", count: 1 }]); + .startingTrainerItems([{ entry: TrainerItemId.BERRY_POUCH, count: 5850 }]); await game.classicMode.startBattle([SpeciesId.FEEBAS]); game.move.select(MoveId.SPLASH); @@ -294,11 +293,13 @@ describe("Abilities - Harvest", () => { // won't trigger harvest since we didn't lose the berry (it just doesn't ever add it to the array) expect(game.scene.getPlayerPokemon()?.battleData.berriesEaten).toEqual([]); - expectBerriesContaining(...initBerries); + expectBerriesContaining([ + { item: { id: HeldItemId.PETAYA_BERRY, stack: 1 }, pokemonId: game.scene.getPlayerPokemon()!.id }, + ]); }); it("can restore stolen berries", async () => { - const initBerries: ModifierOverride[] = [{ name: "BERRY", type: BerryType.SITRUS, count: 1 }]; + const initBerries = [{ entry: HeldItemId.SITRUS_BERRY }]; game.override.enemyHeldItems(initBerries).passiveAbility(AbilityId.MAGICIAN).hasPassiveAbility(true); await game.classicMode.startBattle([SpeciesId.MEOWSCARADA]); @@ -315,7 +316,9 @@ describe("Abilities - Harvest", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(player.battleData.berriesEaten).toEqual([]); - expectBerriesContaining(...initBerries); + expectBerriesContaining([ + { item: { id: HeldItemId.SITRUS_BERRY, stack: 1 }, pokemonId: game.scene.getPlayerPokemon()!.id }, + ]); }); // TODO: Enable once fling actually works...??? @@ -327,7 +330,7 @@ describe("Abilities - Harvest", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(game.scene.getPlayerPokemon()?.battleData.berriesEaten).toBe([]); - expect(getPlayerBerries()).toEqual([]); + expect(getPartyBerries()).toEqual([]); }); // TODO: Enable once Nat Gift gets implemented...??? @@ -339,7 +342,9 @@ describe("Abilities - Harvest", () => { await game.phaseInterceptor.to("TurnEndPhase"); expect(game.scene.getPlayerPokemon()?.battleData.berriesEaten).toHaveLength(0); - expectBerriesContaining(...initBerries); + expectBerriesContaining([ + { item: { id: HeldItemId.STARF_BERRY, stack: 1 }, pokemonId: game.scene.getPlayerPokemon()!.id }, + ]); }); }); }); From 4ed0fd0384890fb67a1946fbb68b22551dd32654 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 13 Jul 2025 16:12:27 +0200 Subject: [PATCH 32/39] More minor fixes --- .../encounters/trash-to-treasure-encounter.ts | 2 +- src/items/held-items/instant-revive.ts | 2 +- test/achievements/achievement.test.ts | 34 +++++++++---------- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index b572b0fa5f1..e752ba09ba7 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -11,11 +11,11 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { RewardTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerItemId } from "#enums/trainer-item-id"; +import { assignItemToFirstFreePokemon } from "#items/item-utility"; import { PokemonMove } from "#moves/pokemon-move"; import { showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig, EnemyPokemonConfig } from "#mystery-encounters/encounter-phase-utils"; import { - assignItemToFirstFreePokemon, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts index 6c9743e3632..7dd6b359577 100644 --- a/src/items/held-items/instant-revive.ts +++ b/src/items/held-items/instant-revive.ts @@ -61,7 +61,7 @@ export class InstantReviveHeldItem extends ConsumableHeldItem { // Reapply Commander on the Pokemon's side of the field, if applicable const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); for (const p of field) { - applyAbAttrs("CommanderAbAttr", p, null, false); + applyAbAttrs("CommanderAbAttr", { pokemon: p }); } return true; } diff --git a/test/achievements/achievement.test.ts b/test/achievements/achievement.test.ts index d736485b25f..1f9b2d95ec9 100644 --- a/test/achievements/achievement.test.ts +++ b/test/achievements/achievement.test.ts @@ -1,11 +1,15 @@ import type { BattleScene } from "#app/battle-scene"; -import { TurnHeldItemTransferModifier } from "#modifiers/modifier"; +import { allSpecies } from "#data/data-lists"; +import { HeldItemId } from "#enums/held-item-id"; +import { SpeciesId } from "#enums/species-id"; +import { PlayerPokemon } from "#field/pokemon"; import { Achv, AchvTier, achvs, DamageAchv, HealAchv, + HeldItemAchv, LevelAchv, ModifierAchv, MoneyAchv, @@ -204,32 +208,26 @@ describe("LevelAchv", () => { }); }); -describe("ModifierAchv", () => { +describe("HeldItemAchv", () => { it("should create an instance of ModifierAchv", () => { - const modifierAchv = new ModifierAchv( + const heldItemAchv = new HeldItemAchv( "", - "Test Modifier Achievement", + "Test Held Item Achievement", "Test Description", "modifier_icon", 10, () => true, ); - expect(modifierAchv).toBeInstanceOf(ModifierAchv); - expect(modifierAchv instanceof Achv).toBe(true); + expect(heldItemAchv).toBeInstanceOf(HeldItemAchv); + expect(heldItemAchv instanceof Achv).toBe(true); }); - it("should validate the achievement based on the modifier function", () => { - const modifierAchv = new ModifierAchv( - "", - "Test Modifier Achievement", - "Test Description", - "modifier_icon", - 10, - () => true, - ); - const modifier = new TurnHeldItemTransferModifier(null!, 3, 1); - - expect(modifierAchv.validate([modifier])).toBe(true); + it("should validate the mini black hole achievement", () => { + const heldItemAchv = achvs.MINI_BLACK_HOLE; + const pokemon = new PlayerPokemon(allSpecies[SpeciesId.BULBASAUR], 1); + expect(heldItemAchv.validate([pokemon])).toBe(false); + pokemon.heldItemManager.add(HeldItemId.MINI_BLACK_HOLE); + expect(heldItemAchv.validate([pokemon])).toBe(true); }); }); From 97e5ab882a758b097ac36872e58767ced428d199 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 13 Jul 2025 16:36:17 +0200 Subject: [PATCH 33/39] Minor lint --- src/items/all-held-items.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index c5572182add..ea9db959892 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -40,16 +40,10 @@ import { getEnumValues } from "#utils/common"; export function initHeldItems() { for (const berry of getEnumValues(BerryType)) { - let maxStackCount: number; - if ([BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(berry)) { - maxStackCount = 2; - } else { - maxStackCount = 3; - } + const maxStackCount = [BerryType.LUM, BerryType.LEPPA, BerryType.SITRUS, BerryType.ENIGMA].includes(berry) ? 2 : 3; const berryId = berryTypeToHeldItem[berry]; allHeldItems[berryId] = new BerryHeldItem(berry, maxStackCount); } - console.log(allHeldItems); allHeldItems[HeldItemId.REVIVER_SEED] = new InstantReviveHeldItem(HeldItemId.REVIVER_SEED, 1); allHeldItems[HeldItemId.WHITE_HERB] = new ResetNegativeStatStageHeldItem(HeldItemId.WHITE_HERB, 2); From 315d4cf408ceff23e5170d0f31e7b36050e4a95d Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:19:24 -0600 Subject: [PATCH 34/39] Add parameter destructuring (#6092) --- src/items/held-item.ts | 2 +- src/items/held-items/accuracy-booster.ts | 18 ++++++------- src/items/held-items/attack-type-booster.ts | 5 +--- src/items/held-items/base-stat-booster.ts | 12 +++------ src/items/held-items/base-stat-flat.ts | 22 ++++++---------- src/items/held-items/base-stat-total.ts | 5 ++-- src/items/held-items/baton.ts | 2 +- src/items/held-items/berry.ts | 5 +--- src/items/held-items/bypass-speed-chance.ts | 12 +++------ src/items/held-items/crit-booster.ts | 5 ++-- src/items/held-items/damage-money-reward.ts | 8 ++---- src/items/held-items/exp-booster.ts | 8 ++---- src/items/held-items/field-effect.ts | 12 ++++----- src/items/held-items/flinch-chance.ts | 7 ++---- src/items/held-items/friendship-booster.ts | 8 ++---- src/items/held-items/hit-heal.ts | 4 +-- src/items/held-items/incrementing-stat.ts | 11 +++----- src/items/held-items/instant-revive.ts | 4 +-- src/items/held-items/item-steal.ts | 16 ++++++------ src/items/held-items/multi-hit.ts | 25 +++++++++---------- src/items/held-items/nature-weight-booster.ts | 8 ++---- .../held-items/reset-negative-stat-stage.ts | 5 +--- src/items/held-items/stat-booster.ts | 10 ++------ src/items/held-items/survive-chance.ts | 6 +---- src/items/held-items/turn-end-heal.ts | 3 +-- src/items/held-items/turn-end-status.ts | 5 ++-- src/items/trainer-item.ts | 18 ++++++------- 27 files changed, 88 insertions(+), 158 deletions(-) diff --git a/src/items/held-item.ts b/src/items/held-item.ts index 10efe2aae8e..9ca3fbd616d 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -41,7 +41,7 @@ export type HeldItemEffect = (typeof HeldItemEffect)[keyof typeof HeldItemEffect export class HeldItem { // public pokemonId: number; public type: HeldItemId; - public maxStackCount: number; + public readonly maxStackCount: number; public isTransferable = true; public isStealable = true; public isSuppressable = true; diff --git a/src/items/held-items/accuracy-booster.ts b/src/items/held-items/accuracy-booster.ts index 1373d4d7151..09abb723300 100644 --- a/src/items/held-items/accuracy-booster.ts +++ b/src/items/held-items/accuracy-booster.ts @@ -6,10 +6,13 @@ import type { NumberHolder } from "#utils/common"; export interface AccuracyBoostParams { /** The pokemon with the item */ pokemon: Pokemon; - /** The amount of exp to gain */ + /** Holds the move's accuracy, which may be modified after item application */ moveAccuracy: NumberHolder; } +/** + * @sealed + */ export class AccuracyBoosterHeldItem extends HeldItem { public effects: HeldItemEffect[] = [HeldItemEffect.ACCURACY_BOOSTER]; @@ -22,8 +25,8 @@ export class AccuracyBoosterHeldItem extends HeldItem { /** * 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 + * @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 { @@ -32,15 +35,10 @@ export class AccuracyBoosterHeldItem extends HeldItem { /** * 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` */ - apply(params: AccuracyBoostParams): boolean { - const pokemon = params.pokemon; - const moveAccuracy = params.moveAccuracy; + apply({ pokemon, moveAccuracy }: AccuracyBoostParams): true { const stackCount = pokemon.heldItemManager.getStack(this.type); - moveAccuracy.value = moveAccuracy.value + this.accuracyAmount * stackCount; + moveAccuracy.value += this.accuracyAmount * stackCount; return true; } diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index ddfd5373be5..7e5f1f10141 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -66,10 +66,7 @@ export class AttackTypeBoosterHeldItem extends HeldItem { return `${HeldItemNames[this.type]?.toLowerCase()}`; } - apply(params: AttackTypeBoostParams): void { - const pokemon = params.pokemon; - const moveType = params.moveType; - const movePower = params.movePower; + apply({ pokemon, moveType, movePower }: AttackTypeBoostParams): void { const stackCount = pokemon.heldItemManager.getStack(this.type); if (moveType === this.moveType && movePower.value >= 1) { movePower.value = Math.floor(movePower.value * (1 + stackCount * this.powerBoost)); diff --git a/src/items/held-items/base-stat-booster.ts b/src/items/held-items/base-stat-booster.ts index b47d8bf850f..7525974ca92 100644 --- a/src/items/held-items/base-stat-booster.ts +++ b/src/items/held-items/base-stat-booster.ts @@ -7,6 +7,7 @@ import i18next from "i18next"; export interface BaseStatBoosterParams { /** The pokemon with the item */ pokemon: Pokemon; + /** The base stats of the {@linkcode pokemon} */ baseStats: number[]; } @@ -57,8 +58,8 @@ export class BaseStatBoosterHeldItem extends HeldItem { /** * 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} + * @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 { @@ -67,14 +68,9 @@ export class BaseStatBoosterHeldItem extends HeldItem { /** * 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` */ - apply(params: BaseStatBoosterParams): boolean { - const pokemon = params.pokemon; + apply({ pokemon, baseStats }: BaseStatBoosterParams): boolean { const stackCount = pokemon.heldItemManager.getStack(this.type); - const baseStats = params.baseStats; baseStats[this.stat] = Math.floor(baseStats[this.stat] * (1 + stackCount * 0.1)); return true; } diff --git a/src/items/held-items/base-stat-flat.ts b/src/items/held-items/base-stat-flat.ts index b622a88fe14..ae10f6d7d1d 100644 --- a/src/items/held-items/base-stat-flat.ts +++ b/src/items/held-items/base-stat-flat.ts @@ -33,13 +33,8 @@ export class BaseStatFlatHeldItem extends HeldItem { /** * 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` */ - apply(params: BaseStatFlatParams): boolean { - const pokemon = params.pokemon; - const baseStats = params.baseStats; + apply({ pokemon, baseStats }: BaseStatFlatParams): true { const stats = this.getStats(pokemon); const statModifier = 20; // Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats @@ -57,15 +52,12 @@ export class BaseStatFlatHeldItem extends HeldItem { * Get the lowest of HP/Spd, lowest of Atk/SpAtk, and lowest of Def/SpDef * @returns Array of 3 {@linkcode Stat}s to boost */ - getStats(pokemon: Pokemon): Stat[] { - const stats: Stat[] = []; + getStats(pokemon: Pokemon): [HpOrSpeed: Stat, AtkOrSpAtk: Stat, DefOrSpDef: Stat] { const baseStats = pokemon.getSpeciesForm().baseStats.slice(0); - // HP or Speed - stats.push(baseStats[Stat.HP] < baseStats[Stat.SPD] ? Stat.HP : Stat.SPD); - // Attack or SpAtk - stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK); - // Def or SpDef - stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF); - return stats; + return [ + baseStats[Stat.HP] < baseStats[Stat.SPD] ? Stat.HP : Stat.SPD, + baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK, + baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF, + ]; } } diff --git a/src/items/held-items/base-stat-total.ts b/src/items/held-items/base-stat-total.ts index 43ba1c697d6..1c1eb154cb3 100644 --- a/src/items/held-items/base-stat-total.ts +++ b/src/items/held-items/base-stat-total.ts @@ -6,7 +6,7 @@ import i18next from "i18next"; export interface BaseStatTotalParams { /** The pokemon with the item */ pokemon: Pokemon; - /** The amount of exp to gain */ + /** Array of the pokemon's base stat; modified in place after item application */ baseStats: number[]; } @@ -56,8 +56,7 @@ export class BaseStatTotalHeldItem extends HeldItem { * @param baseStats the base stats of the {@linkcode Pokemon} * @returns always `true` */ - apply(params: BaseStatTotalParams): boolean { - const baseStats = params.baseStats; + apply({ baseStats }: BaseStatTotalParams): true { // Modifies the passed in baseStats[] array baseStats.forEach((v, i) => { // HP is affected by half as much as other stats diff --git a/src/items/held-items/baton.ts b/src/items/held-items/baton.ts index 02356a995c5..e518f8f0c5d 100644 --- a/src/items/held-items/baton.ts +++ b/src/items/held-items/baton.ts @@ -16,7 +16,7 @@ export class BatonHeldItem extends HeldItem { * Applies {@linkcode SwitchEffectTransferModifier} * @returns always `true` */ - apply(): boolean { + apply(): true { return true; } } diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts index eeae8261273..3b0b3bcf2f1 100644 --- a/src/items/held-items/berry.ts +++ b/src/items/held-items/berry.ts @@ -66,12 +66,9 @@ export class BerryHeldItem extends ConsumableHeldItem { /** * Applies {@linkcode BerryHeldItem} - * @param pokemon The {@linkcode Pokemon} that holds the berry * @returns always `true` */ - apply(params: BerryParams): boolean { - const pokemon = params.pokemon; - + apply({ pokemon }: BerryParams): boolean { if (!this.shouldApply(pokemon)) { return false; } diff --git a/src/items/held-items/bypass-speed-chance.ts b/src/items/held-items/bypass-speed-chance.ts index df14b07dce0..ba3800d2635 100644 --- a/src/items/held-items/bypass-speed-chance.ts +++ b/src/items/held-items/bypass-speed-chance.ts @@ -9,14 +9,12 @@ import i18next from "i18next"; export interface BypassSpeedChanceParams { /** The pokemon with the item */ pokemon: Pokemon; + /** Holder for whether the speed should be bypassed */ doBypassSpeed: BooleanHolder; } /** - * Modifier used for held items, namely Toxic Orb and Flame Orb, that apply a - * set {@linkcode StatusEffect} at the end of a turn. - * @extends PokemonHeldItemModifier - * @see {@linkcode apply} + * Modifier used for items that allow a Pokémon to bypass the speed chance (Quick Claw). */ export class BypassSpeedChanceHeldItem extends HeldItem { public effects: HeldItemEffect[] = [HeldItemEffect.BYPASS_SPEED_CHANCE]; @@ -33,13 +31,9 @@ export class BypassSpeedChanceHeldItem extends HeldItem { /** * 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 */ - apply(params: BypassSpeedChanceParams): boolean { - const pokemon = params.pokemon; - const doBypassSpeed = params.doBypassSpeed; + apply({ pokemon, doBypassSpeed }: BypassSpeedChanceParams): boolean { const stackCount = pokemon.heldItemManager.getStack(this.type); if (!doBypassSpeed.value && pokemon.randBattleSeedInt(10) < stackCount) { doBypassSpeed.value = true; diff --git a/src/items/held-items/crit-booster.ts b/src/items/held-items/crit-booster.ts index a388684dd09..d1ba07c90c9 100644 --- a/src/items/held-items/crit-booster.ts +++ b/src/items/held-items/crit-booster.ts @@ -7,6 +7,7 @@ import type { NumberHolder } from "#utils/common"; export interface CritBoostParams { /** The pokemon with the item */ pokemon: Pokemon; + /** The critical hit stage */ critStage: NumberHolder; } @@ -34,8 +35,8 @@ export class CritBoostHeldItem extends HeldItem { * @param critStage {@linkcode NumberHolder} that holds the resulting critical-hit level * @returns always `true` */ - apply(params: CritBoostParams): boolean { - params.critStage.value += this.stageIncrement; + apply({ critStage }: CritBoostParams): boolean { + critStage.value += this.stageIncrement; return true; } } diff --git a/src/items/held-items/damage-money-reward.ts b/src/items/held-items/damage-money-reward.ts index 46ce90796bc..b158640cb7f 100644 --- a/src/items/held-items/damage-money-reward.ts +++ b/src/items/held-items/damage-money-reward.ts @@ -7,7 +7,7 @@ import { NumberHolder } from "#utils/common"; export interface DamageMoneyRewardParams { /** The pokemon with the item */ pokemon: Pokemon; - /** The amount of exp to gain */ + /** The damage, used to calculate the money reward */ damage: number; } @@ -16,13 +16,9 @@ export class DamageMoneyRewardHeldItem extends HeldItem { /** * Applies {@linkcode DamageMoneyRewardModifier} - * @param pokemon The {@linkcode Pokemon} attacking - * @param multiplier {@linkcode NumberHolder} holding the multiplier value * @returns always `true` */ - apply(params: DamageMoneyRewardParams): boolean { - const pokemon = params.pokemon; - const damage = params.damage; + apply({ pokemon, damage }: DamageMoneyRewardParams): boolean { const stackCount = pokemon.heldItemManager.getStack(this.type); const moneyAmount = new NumberHolder(Math.floor(damage * (0.5 * stackCount))); globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts index 5644546132b..d257fc8d774 100644 --- a/src/items/held-items/exp-booster.ts +++ b/src/items/held-items/exp-booster.ts @@ -7,7 +7,7 @@ import i18next from "i18next"; export interface ExpBoostParams { /** The pokemon with the item */ pokemon: Pokemon; - /** The amount of exp to gain */ + /** Holds the amount of experience gained, which may be modified after item application */ expAmount: NumberHolder; } @@ -41,13 +41,9 @@ export class ExpBoosterHeldItem extends HeldItem { /** * 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` */ - apply(params: ExpBoostParams): boolean { - const pokemon = params.pokemon; - const expAmount = params.expAmount; + apply({ pokemon, expAmount }: ExpBoostParams): true { const stackCount = pokemon.heldItemManager.getStack(this.type); expAmount.value = Math.floor(expAmount.value * (1 + stackCount * this.boostMultiplier)); diff --git a/src/items/held-items/field-effect.ts b/src/items/held-items/field-effect.ts index c248143578d..a076a2fc7dd 100644 --- a/src/items/held-items/field-effect.ts +++ b/src/items/held-items/field-effect.ts @@ -3,8 +3,9 @@ import { HeldItem, HeldItemEffect } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; export interface FieldEffectParams { - pokemon: Pokemon; /** The pokemon with the item */ + pokemon: Pokemon; + /** Holder for the field effect duration*/ fieldDuration: NumberHolder; } @@ -20,14 +21,11 @@ export class FieldEffectHeldItem extends HeldItem { /** * Provides two more turns per stack to any weather or terrain effect caused * by the holder. - * @param pokemon {@linkcode Pokemon} that holds the held item - * @param fieldDuration {@linkcode NumberHolder} that stores the current field effect duration - * @returns `true` if the field effect extension was applied successfully + * @returns always `true` */ - apply(params: FieldEffectParams): boolean { - const pokemon = params.pokemon; + apply({ pokemon, fieldDuration }: FieldEffectParams): true { const stackCount = pokemon.heldItemManager.getStack(this.type); - params.fieldDuration.value += 2 * stackCount; + fieldDuration.value += 2 * stackCount; return true; } } diff --git a/src/items/held-items/flinch-chance.ts b/src/items/held-items/flinch-chance.ts index 290743fac00..82d716368aa 100644 --- a/src/items/held-items/flinch-chance.ts +++ b/src/items/held-items/flinch-chance.ts @@ -6,6 +6,7 @@ import type { BooleanHolder } from "#utils/common"; export interface FlinchChanceParams { /** The pokemon with the item */ pokemon: Pokemon; + /** Holds whether the attack will cause a flinch */ flinched: BooleanHolder; } @@ -37,13 +38,9 @@ export class FlinchChanceHeldItem extends HeldItem { /** * Applies {@linkcode FlinchChanceModifier} to randomly flinch targets hit. - * @param pokemon - The {@linkcode Pokemon} that holds the item - * @param flinched - A {@linkcode BooleanHolder} holding whether the pokemon has flinched * @returns `true` if {@linkcode FlinchChanceModifier} was applied successfully */ - apply(params: FlinchChanceParams): boolean { - const pokemon = params.pokemon; - const flinched = params.flinched; + apply({ pokemon, flinched }: FlinchChanceParams): boolean { const stackCount = pokemon.heldItemManager.getStack(this.type); // The check for pokemon.summonData is to ensure that a crash doesn't occur when a Pokemon with King's Rock procs a flinch // TODO: Since summonData is always defined now, we can probably remove this diff --git a/src/items/held-items/friendship-booster.ts b/src/items/held-items/friendship-booster.ts index 904f98c0929..960838f8925 100644 --- a/src/items/held-items/friendship-booster.ts +++ b/src/items/held-items/friendship-booster.ts @@ -6,7 +6,7 @@ import i18next from "i18next"; export interface FriendshipBoostParams { /** The pokemon with the item */ pokemon: Pokemon; - /** The amount of exp to gain */ + /** Holder for the friendship amount to be changed by the item */ friendship: NumberHolder; } @@ -19,13 +19,9 @@ export class FriendshipBoosterHeldItem extends HeldItem { /** * 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` */ - apply(params: FriendshipBoostParams): boolean { - const pokemon = params.pokemon; - const friendship = params.friendship; + apply({ pokemon, friendship }: FriendshipBoostParams): true { const stackCount = pokemon.heldItemManager.getStack(this.type); friendship.value = Math.floor(friendship.value * (1 + 0.5 * stackCount)); diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts index 522c3e018d7..5d4a05582ec 100644 --- a/src/items/held-items/hit-heal.ts +++ b/src/items/held-items/hit-heal.ts @@ -28,11 +28,9 @@ export class HitHealHeldItem extends HeldItem { /** * Applies {@linkcode HitHealModifier} - * @param pokemon The {@linkcode Pokemon} that holds the item * @returns `true` if the {@linkcode Pokemon} was healed */ - apply(params: HitHealParams): boolean { - const pokemon = params.pokemon; + apply({ pokemon }: HitHealParams): boolean { const stackCount = pokemon.heldItemManager.getStack(this.type); if (pokemon.turnData.totalDamageDealt > 0 && !pokemon.isFullHp()) { // TODO: this shouldn't be undefined AFAIK diff --git a/src/items/held-items/incrementing-stat.ts b/src/items/held-items/incrementing-stat.ts index 9b11a51944b..a462b70326e 100644 --- a/src/items/held-items/incrementing-stat.ts +++ b/src/items/held-items/incrementing-stat.ts @@ -7,7 +7,9 @@ import i18next from "i18next"; export interface IncrementingStatParams { /** The pokemon with the item */ pokemon: Pokemon; + /** The stat whose value is being impacted */ stat: Stat; + /** Holds the stat's value, which may be modified after item application */ // TODO: https://github.com/pagefaultgames/pokerogue/pull/5656#discussion_r2135612276 statHolder: NumberHolder; } @@ -40,19 +42,14 @@ export class IncrementingStatHeldItem extends HeldItem { /** * 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` */ - apply(params: IncrementingStatParams): boolean { - const pokemon = params.pokemon; + apply({ pokemon, statHolder, stat }: IncrementingStatParams): boolean { const stackCount = pokemon.heldItemManager.getStack(this.type); - const statHolder = params.statHolder; // Modifies the passed in stat number holder by +2 per stack for HP, +1 per stack for other stats // If the Macho Brace is at max stacks (50), adds additional 10% to total HP and 5% to other stats - const isHp = params.stat === Stat.HP; + const isHp = stat === Stat.HP; if (isHp) { statHolder.value += 2 * stackCount; diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts index 7dd6b359577..10137efe61d 100644 --- a/src/items/held-items/instant-revive.ts +++ b/src/items/held-items/instant-revive.ts @@ -35,11 +35,9 @@ export class InstantReviveHeldItem extends ConsumableHeldItem { /** * Goes through the holder's stat stages and, if any are negative, resets that * stat stage back to 0. - * @param pokemon {@linkcode Pokemon} that holds the item * @returns `true` if any stat stages were reset, false otherwise */ - apply(params: InstantReviveParams): boolean { - const pokemon = params.pokemon; + apply({ pokemon }: InstantReviveParams): boolean { // Restore the Pokemon to half HP globalScene.phaseManager.unshiftPhase( new PokemonHealPhase( diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts index 64734ddc84e..acba8d6930f 100644 --- a/src/items/held-items/item-steal.ts +++ b/src/items/held-items/item-steal.ts @@ -4,7 +4,7 @@ import { allHeldItems } from "#data/data-lists"; import type { HeldItemId } from "#enums/held-item-id"; import { Pokemon } from "#field/pokemon"; import { HeldItem, HeldItemEffect } from "#items/held-item"; -import { randSeedFloat } from "#utils/common"; +import { coerceArray, randSeedFloat } from "#utils/common"; import i18next from "i18next"; export interface ItemStealParams { @@ -148,20 +148,20 @@ export class ContactItemStealChanceHeldItem extends ItemTransferHeldItem { * @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(params: ItemStealParams): Pokemon[] { - return params.target ? [params.target] : []; + getTargets({ target }: ItemStealParams): Pokemon[] { + return target ? coerceArray(target) : []; } - getTransferredItemCount(params: ItemStealParams): number { - const stackCount = params.pokemon.heldItemManager.getStack(this.type); + getTransferredItemCount({ pokemon }: ItemStealParams): number { + const stackCount = pokemon.heldItemManager.getStack(this.type); return randSeedFloat() <= this.chance * stackCount ? 1 : 0; } - getTransferMessage(params: ItemStealParams, itemId: HeldItemId): string { + getTransferMessage({ pokemon, target }: ItemStealParams, itemId: HeldItemId): string { return i18next.t("modifier:contactHeldItemTransferApply", { - pokemonNameWithAffix: getPokemonNameWithAffix(params.target), + pokemonNameWithAffix: getPokemonNameWithAffix(target), itemName: allHeldItems[itemId].name, - pokemonName: params.pokemon.getNameToRender(), + pokemonName: pokemon.getNameToRender(), typeName: this.name, }); } diff --git a/src/items/held-items/multi-hit.ts b/src/items/held-items/multi-hit.ts index d288039bceb..66cb7cdfa2b 100644 --- a/src/items/held-items/multi-hit.ts +++ b/src/items/held-items/multi-hit.ts @@ -6,9 +6,13 @@ import { isNullOrUndefined, type NumberHolder } from "#utils/common"; import i18next from "i18next"; export interface MultiHitParams { + /** The pokemon with the item */ pokemon: Pokemon; + /** The move being used */ moveId: MoveId; + /** Holder for the move's hit count for the turn */ count?: NumberHolder; + /** Holder for the damage multiplier applied to a strike of the move */ damageMultiplier?: NumberHolder; } @@ -27,16 +31,11 @@ export class MultiHitHeldItem extends HeldItem { /** * For each stack, converts 25 percent of attack damage into an additional strike. - * @param pokemon The {@linkcode Pokemon} using the move - * @param moveId The {@linkcode MoveId | identifier} for the move being used - * @param count {@linkcode NumberHolder} holding the move's hit count for this turn - * @param damageMultiplier {@linkcode NumberHolder} holding a damage multiplier applied to a strike of this move - * @returns always `true` + * @returns Whether the item applies its effects to move */ - apply(params: MultiHitParams): boolean { - const pokemon = params.pokemon; - const move = allMoves[params.moveId]; - /** + apply({ pokemon, count, moveId, damageMultiplier }: MultiHitParams): boolean { + const move = allMoves[moveId]; + /* * The move must meet Parental Bond's restrictions for this item * to apply. This means * - Only attacks are boosted @@ -49,11 +48,11 @@ export class MultiHitHeldItem extends HeldItem { return false; } - if (!isNullOrUndefined(params.count)) { - return this.applyHitCountBoost(pokemon, params.count); + if (!isNullOrUndefined(count)) { + return this.applyHitCountBoost(pokemon, count); } - if (!isNullOrUndefined(params.damageMultiplier)) { - return this.applyDamageModifier(pokemon, params.damageMultiplier); + if (!isNullOrUndefined(damageMultiplier)) { + return this.applyDamageModifier(pokemon, damageMultiplier); } return false; diff --git a/src/items/held-items/nature-weight-booster.ts b/src/items/held-items/nature-weight-booster.ts index 2bf6e8133f4..ec6e91eb437 100644 --- a/src/items/held-items/nature-weight-booster.ts +++ b/src/items/held-items/nature-weight-booster.ts @@ -5,7 +5,7 @@ import type { NumberHolder } from "#utils/common"; export interface NatureWeightBoostParams { /** The pokemon with the item */ pokemon: Pokemon; - /** The amount of exp to gain */ + /** Holder for the multiplier */ multiplier: NumberHolder; } @@ -14,13 +14,9 @@ export class NatureWeightBoosterHeldItem extends HeldItem { /** * 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 */ - apply(params: NatureWeightBoostParams): boolean { - const pokemon = params.pokemon; - const multiplier = params.multiplier; + apply({ pokemon, multiplier }: NatureWeightBoostParams): boolean { const stackCount = pokemon.heldItemManager.getStack(this.type); if (multiplier.value !== 1) { multiplier.value += 0.1 * stackCount * (multiplier.value > 1 ? 1 : -1); diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index 152f0431a96..0714270cc51 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -35,12 +35,9 @@ export class ResetNegativeStatStageHeldItem extends ConsumableHeldItem { /** * Goes through the holder's stat stages and, if any are negative, resets that * stat stage back to 0. - * @param pokemon {@linkcode Pokemon} that holds the item * @returns `true` if any stat stages were reset, false otherwise */ - apply(params: ResetNegativeStatStageParams): boolean { - const pokemon = params.pokemon; - const isPlayer = params.isPlayer; + apply({ pokemon, isPlayer }: ResetNegativeStatStageParams): boolean { let statRestored = false; for (const s of BATTLE_STATS) { diff --git a/src/items/held-items/stat-booster.ts b/src/items/held-items/stat-booster.ts index 435ca331825..aa7eaea93ec 100644 --- a/src/items/held-items/stat-booster.ts +++ b/src/items/held-items/stat-booster.ts @@ -47,14 +47,11 @@ export class StatBoostHeldItem extends HeldItem { /** * Boosts the incoming stat by a {@linkcode multiplier} if the stat is listed * in {@linkcode stats}. - * @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(params: StatBoostParams): boolean { - params.statValue.value *= this.multiplier; + apply({ statValue }: StatBoostParams): boolean { + statValue.value *= this.multiplier; return true; } @@ -88,9 +85,6 @@ export class EvolutionStatBoostHeldItem extends StatBoostHeldItem { * 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 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 */ diff --git a/src/items/held-items/survive-chance.ts b/src/items/held-items/survive-chance.ts index ef9256f030f..4120e452d1a 100644 --- a/src/items/held-items/survive-chance.ts +++ b/src/items/held-items/survive-chance.ts @@ -32,13 +32,9 @@ export class SurviveChanceHeldItem extends HeldItem { /** * 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 */ - apply(params: SurviveChanceParams): boolean { - const pokemon = params.pokemon; - const surviveDamage = params.surviveDamage; + apply({ pokemon, surviveDamage }: SurviveChanceParams): boolean { const stackCount = pokemon.heldItemManager.getStack(this.type); if (!surviveDamage.value && pokemon.randBattleSeedInt(10) < stackCount) { surviveDamage.value = true; diff --git a/src/items/held-items/turn-end-heal.ts b/src/items/held-items/turn-end-heal.ts index 3acc5861a8e..598b9bc61fa 100644 --- a/src/items/held-items/turn-end-heal.ts +++ b/src/items/held-items/turn-end-heal.ts @@ -14,8 +14,7 @@ export interface TurnEndHealParams { export class TurnEndHealHeldItem extends HeldItem { public effects: HeldItemEffect[] = [HeldItemEffect.TURN_END_HEAL]; - apply(params: TurnEndHealParams): boolean { - const pokemon = params.pokemon; + apply({ pokemon }: TurnEndHealParams): boolean { const stackCount = pokemon.heldItemManager.getStack(this.type); if (pokemon.isFullHp()) { return false; diff --git a/src/items/held-items/turn-end-status.ts b/src/items/held-items/turn-end-status.ts index d746b59419d..d6cbe240fd1 100644 --- a/src/items/held-items/turn-end-status.ts +++ b/src/items/held-items/turn-end-status.ts @@ -27,11 +27,10 @@ export class TurnEndStatusHeldItem extends HeldItem { /** * Tries to inflicts the holder with the associated {@linkcode StatusEffect}. - * @param pokemon {@linkcode Pokemon} that holds the held item * @returns `true` if the status effect was applied successfully */ - apply(params: TurnEndStatusParams): boolean { - return params.pokemon.trySetStatus(this.effect, true, undefined, undefined, this.name); + apply({ pokemon }: TurnEndStatusParams): boolean { + return pokemon.trySetStatus(this.effect, true, undefined, undefined, this.name); } getStatusEffect(): StatusEffect { diff --git a/src/items/trainer-item.ts b/src/items/trainer-item.ts index 2553a01b795..2c2200b38be 100644 --- a/src/items/trainer-item.ts +++ b/src/items/trainer-item.ts @@ -67,15 +67,15 @@ export class TrainerItem { this.maxStackCount = maxStackCount; } - get name(): string { + public get name(): string { return i18next.t(`modifierType:ModifierType.${TrainerItemNames[this.type]}.name`); } - get description(): string { + public get description(): string { return i18next.t(`modifierType:ModifierType.${TrainerItemNames[this.type]}.description`); } - get iconName(): string { + public get iconName(): string { return `${TrainerItemNames[this.type]?.toLowerCase()}`; } @@ -84,10 +84,9 @@ export class TrainerItem { } createIcon(stackCount: number): Phaser.GameObjects.Container { - const container = globalScene.add.container(0, 0); + const container = globalScene.add.container(); - const item = globalScene.add.sprite(0, 12, "items").setFrame(this.iconName).setOrigin(0, 0.5); - container.add(item); + container.add(globalScene.add.sprite(0, 12, "items").setFrame(this.iconName).setOrigin(0, 0.5)); const stackText = this.getIconStackText(stackCount); if (stackText) { @@ -102,12 +101,13 @@ export class TrainerItem { return null; } - const text = globalScene.add.bitmapText(10, 15, "item-count", stackCount.toString(), 11); - text.letterSpacing = -0.5; + const text = globalScene.add + .bitmapText(10, 15, "item-count", stackCount.toString(), 11) + .setLetterSpacing(-0.5) + .setOrigin(0); if (stackCount >= this.getMaxStackCount()) { text.setTint(0xf89890); } - text.setOrigin(0); return text; } From 985d352be849dad7ad495d7148d2e9e7ea632094 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Thu, 24 Jul 2025 20:39:56 +0200 Subject: [PATCH 35/39] Moved held item manager to item folder, some name changes --- src/field/pokemon.ts | 6 +++--- .../held-item-manager.ts} | 14 ++++---------- 2 files changed, 7 insertions(+), 13 deletions(-) rename src/{field/pokemon-held-item-manager.ts => items/held-item-manager.ts} (94%) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6725867f656..ac7a77cf92c 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -110,10 +110,10 @@ import type { TrainerSlot } from "#enums/trainer-slot"; import { UiMode } from "#enums/ui-mode"; import { WeatherType } from "#enums/weather-type"; import { doShinySparkleAnim } from "#field/anims"; -import { PokemonItemManager } from "#field/pokemon-held-item-manager"; import { applyHeldItems } from "#items/all-held-items"; import { HeldItemEffect } from "#items/held-item"; import type { HeldItemConfiguration } from "#items/held-item-data-types"; +import { HeldItemManager } from "#items/held-item-manager"; import { assignItemsFromConfiguration } from "#items/held-item-pool"; import { TrainerItemEffect } from "#items/trainer-item"; import { applyMoveAttrs } from "#moves/apply-attrs"; @@ -273,7 +273,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { private shinySparkle: Phaser.GameObjects.Sprite; - public heldItemManager: PokemonItemManager; + public heldItemManager: HeldItemManager; // TODO: Rework this eventually constructor( @@ -318,7 +318,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { this.exp = dataSource?.exp || getLevelTotalExp(this.level, species.growthRate); this.levelExp = dataSource?.levelExp || 0; - this.heldItemManager = new PokemonItemManager(); + this.heldItemManager = new HeldItemManager(); if (heldItemConfig) { assignItemsFromConfiguration(heldItemConfig, this); } diff --git a/src/field/pokemon-held-item-manager.ts b/src/items/held-item-manager.ts similarity index 94% rename from src/field/pokemon-held-item-manager.ts rename to src/items/held-item-manager.ts index ae4509b71b1..2c2a084ed15 100644 --- a/src/field/pokemon-held-item-manager.ts +++ b/src/items/held-item-manager.ts @@ -18,7 +18,7 @@ import { } from "#items/held-item-data-types"; import { getTypedEntries, getTypedKeys } from "#utils/common"; -export class PokemonItemManager { +export class HeldItemManager { public heldItems: HeldItemDataMap; public formChangeItems: FormChangeItemPropertyMap; @@ -78,21 +78,15 @@ export class PokemonItemManager { } getTransferableHeldItems(): HeldItemId[] { - return getTypedKeys(this.heldItems) - .filter(k => allHeldItems[k].isTransferable) - .map(k => k); + return getTypedKeys(this.heldItems).filter(k => allHeldItems[k].isTransferable); } getStealableHeldItems(): HeldItemId[] { - return getTypedKeys(this.heldItems) - .filter(k => allHeldItems[k].isStealable) - .map(k => k); + return getTypedKeys(this.heldItems).filter(k => allHeldItems[k].isStealable); } getSuppressableHeldItems(): HeldItemId[] { - return getTypedKeys(this.heldItems) - .filter(k => allHeldItems[k].isSuppressable) - .map(k => k); + return getTypedKeys(this.heldItems).filter(k => allHeldItems[k].isSuppressable); } hasItem(itemType: HeldItemId | HeldItemCategoryId): boolean { From 466c4aede2025fe56d412d562d7af6ff96e158ce Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 28 Jul 2025 02:09:21 +0200 Subject: [PATCH 36/39] Replace remaining Modifiers with Rewards (#6091) * Changing remaining Modifiers to Consumables, and renaming ModifierType to Reward * Renamed modifier files and moved them into items folder * Using rewards in most places * Removed consumables in favor of using rewards directly * Renamed RewardTier to RarityTier * Reward ids, function to match rewards * Getting reward tiers from player pool still * Messing around with parameters of Reward.apply() * Always requiring player pokemon in rewards * Fixing some functions in select-reward-phase and battle-scene * Fixed various post-merge issues * Fixed most localization strings (accidentally broken by replacing modifierType with reward) * Fixed tests for select reward phase * Using Pokemon.hasSpecies() * Zero weight for trainer items rewards which are already max stack * Cleaning up SelectRewardPhase, held item rewards behave the same as any PokemonReward * Cleaned up some functions * Introduced RewardCategoryId, distributed RewardIds * Utility `is` functions for rewards * Minor fixes * Moved `HeldItemEffect` to its own file * rmade some todo comments * Adding a big comment * Added tsdocs and removed `RewardClass` * undid breaking changes * added TODO * Moved matchingRewards function to reward-utils.ts * Added RewardGenerator classes for mints and tera shards * Introducing default rarity tiers for trainer items and rewards * RewardFunc now can return RewardGenerator * Moved pool reward functions to their own file, plus other utility files * Fixed WeightedModifier to work with the new RewardFunc * Fixed wrong type import * Shifting trainer item and reward ids to avoid overlaps * Added some types * Updated comment in reward.ts * Added strong typing ot item maps * added type safety to held item name map --------- Co-authored-by: Bertie690 Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> --- index.css | 2 +- src/@types/locales.ts | 8 +- src/@types/modifier-types.ts | 31 - src/@types/rewards.ts | 23 + src/battle-scene.ts | 108 +- src/battle.ts | 9 +- src/data/balance/tms.ts | 636 ++--- src/data/challenge.ts | 32 +- src/data/data-lists.ts | 4 +- src/data/moves/move.ts | 24 +- .../encounters/a-trainers-test-encounter.ts | 10 +- .../an-offer-you-cant-refuse-encounter.ts | 6 +- .../encounters/berries-abound-encounter.ts | 32 +- .../encounters/bug-type-superfan-encounter.ts | 64 +- .../encounters/clowning-around-encounter.ts | 10 +- .../encounters/dancing-lessons-encounter.ts | 4 +- .../encounters/dark-deal-encounter.ts | 6 +- .../encounters/delibirdy-encounter.ts | 16 +- .../department-store-sale-encounter.ts | 42 +- .../encounters/field-trip-encounter.ts | 40 +- .../encounters/fiery-fallout-encounter.ts | 2 +- .../encounters/fight-or-flight-encounter.ts | 40 +- .../encounters/fun-and-games-encounter.ts | 10 +- .../global-trade-system-encounter.ts | 31 +- .../mysterious-challengers-encounter.ts | 10 +- .../encounters/mysterious-chest-encounter.ts | 20 +- .../encounters/safari-zone-encounter.ts | 2 +- .../shady-vitamin-dealer-encounter.ts | 2 +- .../slumbering-snorlax-encounter.ts | 6 +- .../teleporting-hijinks-encounter.ts | 12 +- .../the-expert-pokemon-breeder-encounter.ts | 16 +- .../the-pokemon-salesman-encounter.ts | 2 +- .../encounters/the-strong-stuff-encounter.ts | 4 +- .../the-winstrate-challenge-encounter.ts | 24 +- .../encounters/training-session-encounter.ts | 2 +- .../encounters/trash-to-treasure-encounter.ts | 4 +- .../encounters/weird-dream-encounter.ts | 34 +- .../mystery-encounters/mystery-encounter.ts | 30 +- .../utils/encounter-phase-utils.ts | 53 +- src/data/trainers/fixed-battle-configs.ts | 66 +- src/data/trainers/trainer-config.ts | 56 +- src/enums/held-item-effect.ts | 37 + src/enums/held-item-id.ts | 23 +- src/enums/reward-id.ts | 99 + ...ifier-pool-type.ts => reward-pool-type.ts} | 2 +- src/enums/reward-tier.ts | 2 +- src/enums/trainer-item-id.ts | 82 +- src/enums/ui-mode.ts | 2 +- src/field/arena.ts | 2 +- src/field/pokemon.ts | 22 +- src/field/trainer.ts | 8 +- src/game-mode.ts | 2 +- src/items/all-held-items.ts | 4 +- src/items/all-trainer-items.ts | 2 +- src/items/held-item-data-types.ts | 14 +- src/items/held-item-default-tiers.ts | 47 + src/items/held-item-pool.ts | 35 +- src/items/held-item-tiers.ts | 47 - src/items/held-item.ts | 35 +- src/items/held-items/accuracy-booster.ts | 9 +- src/items/held-items/attack-type-booster.ts | 3 +- src/items/held-items/base-stat-booster.ts | 9 +- src/items/held-items/base-stat-flat.ts | 3 +- src/items/held-items/base-stat-total.ts | 3 +- src/items/held-items/baton.ts | 3 +- src/items/held-items/berry.ts | 15 +- src/items/held-items/bypass-speed-chance.ts | 3 +- src/items/held-items/crit-booster.ts | 3 +- src/items/held-items/damage-money-reward.ts | 3 +- src/items/held-items/evo-tracker.ts | 3 +- src/items/held-items/exp-booster.ts | 3 +- src/items/held-items/field-effect.ts | 3 +- src/items/held-items/flinch-chance.ts | 3 +- src/items/held-items/friendship-booster.ts | 3 +- src/items/held-items/hit-heal.ts | 3 +- src/items/held-items/incrementing-stat.ts | 3 +- src/items/held-items/instant-revive.ts | 3 +- src/items/held-items/item-steal.ts | 15 +- src/items/held-items/multi-hit.ts | 3 +- src/items/held-items/nature-weight-booster.ts | 3 +- .../held-items/reset-negative-stat-stage.ts | 3 +- src/items/held-items/stat-booster.ts | 3 +- src/items/held-items/survive-chance.ts | 3 +- src/items/held-items/turn-end-heal.ts | 3 +- src/items/held-items/turn-end-status.ts | 3 +- src/items/init-held-item-pools.ts | 36 +- .../init-reward-pools.ts} | 335 ++- src/items/init-trainer-item-pools.ts | 12 +- src/items/item-overrides.ts | 50 + src/items/reward-defaults-tiers.ts | 72 + src/items/reward-pool-utils.ts | 340 +++ src/items/reward-pools.ts | 9 + src/items/reward-utils.ts | 115 + src/items/reward.ts | 1852 +++++++++++++++ src/items/trainer-item-data-types.ts | 4 +- src/items/trainer-item-default-tiers.ts | 50 + src/items/trainer-item-pool.ts | 8 +- src/items/trainer-item.ts | 6 +- src/loading-scene.ts | 8 +- src/modifier/modifier-pools.ts | 8 - src/modifier/modifier-type.ts | 2058 ----------------- src/modifier/modifier.ts | 620 ----- src/overrides.ts | 22 +- src/phase-manager.ts | 4 +- ...fier-phase.ts => add-enemy-token-phase.ts} | 8 +- src/phases/berry-phase.ts | 2 +- src/phases/egg-hatch-phase.ts | 2 +- src/phases/egg-summary-phase.ts | 2 +- src/phases/encounter-phase.ts | 2 +- src/phases/faint-phase.ts | 2 +- src/phases/game-over-phase.ts | 6 +- src/phases/game-over-reward-phase.ts | 2 +- src/phases/move-effect-phase.ts | 2 +- src/phases/mystery-encounter-phases.ts | 4 +- src/phases/reward-phase.ts | 20 +- src/phases/ribbon-reward-phase.ts | 8 +- src/phases/select-reward-phase.ts | 411 ++-- src/phases/select-starter-phase.ts | 2 +- src/phases/stat-stage-change-phase.ts | 2 +- src/phases/trainer-item-reward-phase.ts | 16 +- src/phases/trainer-victory-phase.ts | 19 +- src/phases/turn-end-phase.ts | 2 +- src/phases/turn-start-phase.ts | 2 +- src/phases/victory-phase.ts | 34 +- src/system/achv.ts | 14 - src/system/settings/settings.ts | 2 +- .../version-migration/versions/v1_0_4.ts | 2 +- src/timed-event-manager.ts | 6 +- src/tutorial.ts | 2 +- src/ui-inputs.ts | 2 +- src/ui/egg-hatch-scene-handler.ts | 2 +- src/ui/party-ui-handler.ts | 122 +- src/ui/pokemon-info-container.ts | 6 +- src/ui/reward-select-ui-handler.ts | 89 +- src/ui/run-info-ui-handler.ts | 2 +- src/ui/text.ts | 24 +- src/utils/modifier-utils.ts | 21 - src/utils/party.ts | 56 + test/achievements/achievement.test.ts | 3 +- test/daily-mode.test.ts | 4 +- test/items/dire-hit.test.ts | 2 +- .../double-battle-chance-booster.test.ts | 2 +- test/items/eviolite.test.ts | 2 +- test/items/exp-booster.test.ts | 2 +- test/items/light-ball.test.ts | 2 +- test/items/lock-capsule.test.ts | 10 +- test/items/metal-powder.test.ts | 23 +- test/items/quick-powder.test.ts | 23 +- test/items/temp-stat-stage-booster.test.ts | 2 +- test/items/thick-club.test.ts | 33 +- .../berries-abound-encounter.test.ts | 20 +- .../bug-type-superfan-encounter.test.ts | 68 +- .../clowning-around-encounter.test.ts | 8 +- .../dancing-lessons-encounter.test.ts | 10 +- .../department-store-sale-encounter.test.ts | 43 +- .../encounters/field-trip-encounter.test.ts | 72 +- .../fight-or-flight-encounter.test.ts | 16 +- .../fun-and-games-encounter.test.ts | 38 +- .../global-trade-system-encounter.test.ts | 12 +- .../mysterious-challengers-encounter.test.ts | 80 +- .../teleporting-hijinks-encounter.test.ts | 14 +- .../the-strong-stuff-encounter.test.ts | 12 +- .../the-winstrate-challenge-encounter.test.ts | 20 +- .../trash-to-treasure-encounter.test.ts | 34 +- .../encounters/weird-dream-encounter.test.ts | 64 +- test/phases/form-change-phase.test.ts | 109 +- test/phases/game-over-phase.test.ts | 4 +- test/phases/select-modifier-phase.test.ts | 279 --- test/phases/select-reward-phase.test.ts | 281 +++ test/test-utils/game-manager.ts | 10 +- test/test-utils/helpers/modifiers-helper.ts | 12 +- test/test-utils/helpers/overrides-helper.ts | 8 +- test/test-utils/test-file-initialization.ts | 8 +- test/ui/transfer-item.test.ts | 4 +- tsconfig.json | 1 - 175 files changed, 4842 insertions(+), 5112 deletions(-) delete mode 100644 src/@types/modifier-types.ts create mode 100644 src/@types/rewards.ts create mode 100644 src/enums/held-item-effect.ts create mode 100644 src/enums/reward-id.ts rename src/enums/{modifier-pool-type.ts => reward-pool-type.ts} (80%) create mode 100644 src/items/held-item-default-tiers.ts delete mode 100644 src/items/held-item-tiers.ts rename src/{modifier/init-modifier-pools.ts => items/init-reward-pools.ts} (67%) create mode 100644 src/items/item-overrides.ts create mode 100644 src/items/reward-defaults-tiers.ts create mode 100644 src/items/reward-pool-utils.ts create mode 100644 src/items/reward-pools.ts create mode 100644 src/items/reward-utils.ts create mode 100644 src/items/reward.ts create mode 100644 src/items/trainer-item-default-tiers.ts delete mode 100644 src/modifier/modifier-pools.ts delete mode 100644 src/modifier/modifier-type.ts delete mode 100644 src/modifier/modifier.ts rename src/phases/{add-enemy-buff-modifier-phase.ts => add-enemy-token-phase.ts} (61%) delete mode 100644 src/utils/modifier-utils.ts create mode 100644 src/utils/party.ts delete mode 100644 test/phases/select-modifier-phase.test.ts create mode 100644 test/phases/select-reward-phase.test.ts diff --git a/index.css b/index.css index 62ad6266d30..e7439639d3a 100644 --- a/index.css +++ b/index.css @@ -183,7 +183,7 @@ input:-internal-autofill-selected { /* Show #apadStats only in battle and shop */ #touchControls:not([data-ui-mode="COMMAND"]):not([data-ui-mode="FIGHT"]):not( [data-ui-mode="BALL"] - ):not([data-ui-mode="TARGET_SELECT"]):not([data-ui-mode="MODIFIER_SELECT"]) + ):not([data-ui-mode="TARGET_SELECT"]):not([data-ui-mode="REWARD_SELECT"]) #apadStats { display: none; } diff --git a/src/@types/locales.ts b/src/@types/locales.ts index 3b5a1477e19..af3d5fcc596 100644 --- a/src/@types/locales.ts +++ b/src/@types/locales.ts @@ -24,15 +24,15 @@ export interface AbilityTranslationEntries { [key: string]: AbilityTranslationEntry; } -export interface ModifierTypeTranslationEntry { +export interface RewardTranslationEntry { name?: string; description?: string; extra?: SimpleTranslationEntries; } -export interface ModifierTypeTranslationEntries { - ModifierType: { [key: string]: ModifierTypeTranslationEntry }; - SpeciesBoosterItem: { [key: string]: ModifierTypeTranslationEntry }; +export interface RewardTranslationEntries { + Reward: { [key: string]: RewardTranslationEntry }; + SpeciesBoosterItem: { [key: string]: RewardTranslationEntry }; AttackTypeBoosterItem: SimpleTranslationEntries; TempStatStageBoosterItem: SimpleTranslationEntries; BaseStatBoosterItem: SimpleTranslationEntries; diff --git a/src/@types/modifier-types.ts b/src/@types/modifier-types.ts deleted file mode 100644 index 28b39d1a151..00000000000 --- a/src/@types/modifier-types.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Intentionally re-exports `ModifierConstructorMap` from `modifier.ts` - -import type { Pokemon } from "#field/pokemon"; -import type { ModifierConstructorMap } from "#modifiers/modifier"; -import type { ModifierType, WeightedModifierType } from "#modifiers/modifier-type"; - -export type ModifierTypeFunc = () => ModifierType; -export type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number; - -export type { ModifierConstructorMap } from "#modifiers/modifier"; - -/** - * Map of modifier names to their respective instance types - */ -export type ModifierInstanceMap = { - [K in keyof ModifierConstructorMap]: InstanceType; -}; - -/** - * Union type of all modifier constructors. - */ -export type ModifierClass = ModifierConstructorMap[keyof ModifierConstructorMap]; - -/** - * Union type of all modifier names as strings. - */ -export type ModifierString = keyof ModifierConstructorMap; - -export type ModifierPool = { - [tier: string]: WeightedModifierType[]; -}; diff --git a/src/@types/rewards.ts b/src/@types/rewards.ts new file mode 100644 index 00000000000..ad938a60164 --- /dev/null +++ b/src/@types/rewards.ts @@ -0,0 +1,23 @@ +import type { HeldItemId } from "#enums/held-item-id"; +import type { RewardId } from "#enums/reward-id"; +import type { TrainerItemId } from "#enums/trainer-item-id"; +import type { Pokemon } from "#field/pokemon"; +import type { Reward, RewardGenerator } from "#items/reward"; + +export type RewardFunc = () => Reward | RewardGenerator; +export type WeightedRewardWeightFunc = (party: Pokemon[], rerollCount?: number) => number; + +export type RewardPoolId = RewardId | HeldItemId | TrainerItemId; + +export type RewardPoolEntry = { + id: RewardPoolId; + weight: number | WeightedRewardWeightFunc; +}; + +export type RewardPool = { + [tier: string]: RewardPoolEntry[]; +}; + +export interface RewardPoolWeights { + [tier: string]: number[]; +} diff --git a/src/battle-scene.ts b/src/battle-scene.ts index b3a92da41e2..74906adfd72 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -53,8 +53,8 @@ import { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { ExpNotification } from "#enums/exp-notification"; import { FormChangeItem } from "#enums/form-change-item"; import { GameModes } from "#enums/game-modes"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; -import { HeldItemPoolType, ModifierPoolType } from "#enums/modifier-pool-type"; import { MoneyFormat } from "#enums/money-format"; import { MoveId } from "#enums/move-id"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; @@ -65,6 +65,7 @@ import { PlayerGender } from "#enums/player-gender"; import { PokeballType } from "#enums/pokeball"; import type { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PokemonType } from "#enums/pokemon-type"; +import { HeldItemPoolType, RewardPoolType } from "#enums/reward-pool-type"; import { ShopCursorTarget } from "#enums/shop-cursor-target"; import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; @@ -82,9 +83,10 @@ import { PokemonSpriteSparkleHandler } from "#field/pokemon-sprite-sparkle-handl import { Trainer } from "#field/trainer"; import { applyHeldItems } from "#items/all-held-items"; import { type ApplyTrainerItemsParams, applyTrainerItems } from "#items/apply-trainer-items"; -import { HeldItemEffect } from "#items/held-item"; import type { HeldItemConfiguration } from "#items/held-item-data-types"; import { assignEnemyHeldItemsForWave, assignItemsFromConfiguration } from "#items/held-item-pool"; +import type { Reward } from "#items/reward"; +import { getRewardPoolForType } from "#items/reward-pool-utils"; import { type EnemyAttackStatusEffectChanceTrainerItem, TrainerItemEffect } from "#items/trainer-item"; import { isTrainerItemPool, @@ -94,15 +96,6 @@ import { } from "#items/trainer-item-data-types"; import { TrainerItemManager } from "#items/trainer-item-manager"; import { getNewTrainerItemFromPool } from "#items/trainer-item-pool"; -import type { Modifier } from "#modifiers/modifier"; -import { - ConsumableModifier, - ConsumablePokemonModifier, - FusePokemonModifier, - PokemonHpRestoreModifier, - RememberMoveModifier, -} from "#modifiers/modifier"; -import { getLuckString, getLuckTextTint, getPartyLuckValue } from "#modifiers/modifier-type"; import { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterSaveData } from "#mystery-encounters/mystery-encounter-save-data"; import { allMysteryEncounters, mysteryEncountersByBiome } from "#mystery-encounters/mystery-encounters"; @@ -112,7 +105,7 @@ import { hasExpSprite } from "#sprites/sprite-utils"; import type { Variant } from "#sprites/variant"; import { clearVariantData, variantData } from "#sprites/variant"; import type { Achv } from "#system/achv"; -import { achvs, HeldItemAchv, ModifierAchv, MoneyAchv } from "#system/achv"; +import { achvs, HeldItemAchv, MoneyAchv } from "#system/achv"; import { GameData } from "#system/game-data"; import { initGameSpeed } from "#system/game-speed"; import type { PokemonData } from "#system/pokemon-data"; @@ -148,7 +141,7 @@ import { } from "#utils/common"; import { deepMergeSpriteData } from "#utils/data"; import { getEnumValues } from "#utils/enums"; -import { getModifierPoolForType } from "#utils/modifier-utils"; +import { getLuckString, getLuckTextTint, getPartyLuckValue } from "#utils/party"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; import Phaser from "phaser"; @@ -267,7 +260,7 @@ export class BattleScene extends SceneBase { public arena: Arena; public gameMode: GameMode; public score: number; - public lockModifierTiers: boolean; + public lockRarityTiers: boolean; public trainer: Phaser.GameObjects.Sprite; public lastEnemyTrainer: Trainer | null; public currentBattle: Battle; @@ -480,12 +473,12 @@ export class BattleScene extends SceneBase { this.enemyTrainerItems = new TrainerItemManager(); this.itemBar = new ItemBar(); - this.itemBar.setName("modifier-bar"); + this.itemBar.setName("item-bar"); this.add.existing(this.itemBar); uiContainer.add(this.itemBar); this.enemyItemBar = new ItemBar(true); - this.enemyItemBar.setName("enemy-modifier-bar"); + this.enemyItemBar.setName("enemy-item-bar"); this.add.existing(this.enemyItemBar); uiContainer.add(this.enemyItemBar); @@ -854,9 +847,9 @@ export class BattleScene extends SceneBase { } /** - * Returns the ModifierBar of this scene, which is declared private and therefore not accessible elsewhere + * Returns the ItemBar of this scene, which is declared private and therefore not accessible elsewhere * @param isEnemy - Whether to return the enemy modifier bar instead of the player bar; default `false` - * @returns The {@linkcode ModifierBar} for the given side of the field + * @returns The {@linkcode ItemBar} for the given side of the field */ getItemBar(isEnemy = false): ItemBar { return isEnemy ? this.enemyItemBar : this.itemBar; @@ -1167,7 +1160,7 @@ export class BattleScene extends SceneBase { this.score = 0; this.money = 0; - this.lockModifierTiers = false; + this.lockRarityTiers = false; this.pokeballCounts = Object.fromEntries( getEnumValues(PokeballType) @@ -1243,12 +1236,12 @@ export class BattleScene extends SceneBase { ...allSpecies, ...allMoves, ...allAbilities, - ...getEnumValues(ModifierPoolType) - .map(mpt => getModifierPoolForType(mpt)) + ...getEnumValues(RewardPoolType) + .map(mpt => getRewardPoolForType(mpt)) .flatMap(mp => Object.values(mp) .flat() - .map(mt => mt.modifierType) + .map(mt => mt.reward) .filter(mt => "localize" in mt) .map(lpb => lpb as unknown as Localizable), ), @@ -1987,11 +1980,11 @@ export class BattleScene extends SceneBase { }); } - showEnemyModifierBar(): void { + showEnemyItemBar(): void { this.enemyItemBar.setVisible(true); } - hideEnemyModifierBar(): void { + hideEnemyItemBar(): void { this.enemyItemBar.setVisible(false); } @@ -2085,11 +2078,11 @@ export class BattleScene extends SceneBase { } updateUIPositions(): void { - const enemyModifierCount = this.enemyItemBar.totalVisibleLength; + const enemyItemCount = this.enemyItemBar.totalVisibleLength; const biomeWaveTextHeight = this.biomeWaveText.getBottomLeft().y - this.biomeWaveText.getTopLeft().y; this.biomeWaveText.setY( -(this.game.canvas.height / 6) + - (enemyModifierCount ? (enemyModifierCount <= 12 ? 15 : 24) : 0) + + (enemyItemCount ? (enemyItemCount <= 12 ? 15 : 24) : 0) + biomeWaveTextHeight / 2, ); this.moneyText.setY(this.biomeWaveText.y + 10); @@ -2641,56 +2634,19 @@ export class BattleScene extends SceneBase { applyTrainerItems(effect, this.trainerItems, params); } - addModifier(modifier: Modifier | null, playSound?: boolean, instant?: boolean, cost?: number): boolean { - // We check against modifier.type to stop a bug related to loading in a pokemon that has a form change item, which prior to some patch - // that changed form change modifiers worked, had previously set the `type` field to null. - // TODO: This is not the right place to check for this; it should ideally go in a session migrator. - if (!modifier || !modifier.type) { + applyReward(reward: T, params: Parameters[0], playSound?: boolean): boolean { + const soundName = reward.soundName; + + if (playSound && !this.sound.get(soundName)) { + this.playSound(soundName); + } + + if (!reward.shouldApply(params)) { return false; } - let success = false; - const soundName = modifier.type.soundName; - this.validateAchvs(ModifierAchv, modifier); - if (modifier instanceof ConsumableModifier) { - if (playSound && !this.sound.get(soundName)) { - this.playSound(soundName); - } - if (modifier instanceof ConsumablePokemonModifier) { - for (const p in this.party) { - const pokemon = this.party[p]; - - const args: unknown[] = []; - if (modifier instanceof PokemonHpRestoreModifier) { - if (!(modifier as PokemonHpRestoreModifier).fainted) { - const hpRestoreMultiplier = new NumberHolder(1); - this.applyPlayerItems(TrainerItemEffect.HEALING_BOOSTER, { numberHolder: hpRestoreMultiplier }); - args.push(hpRestoreMultiplier.value); - } else { - args.push(1); - } - } else if (modifier instanceof FusePokemonModifier) { - args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon); - } else if (modifier instanceof RememberMoveModifier && !isNullOrUndefined(cost)) { - args.push(cost); - } - - if (modifier.shouldApply(pokemon, ...args)) { - const result = modifier.apply(pokemon, ...args); - success ||= result; - } - } - - this.party.map(p => p.updateInfo(instant)); - } else { - const args = [this]; - if (modifier.shouldApply(...args)) { - const result = modifier.apply(...args); - success ||= result; - } - } - } - return success; + reward.apply(params); + return true; } addHeldItem(heldItemId: HeldItemId, pokemon: Pokemon, amount = 1, playSound?: boolean, ignoreUpdate?: boolean) { @@ -2864,7 +2820,7 @@ export class BattleScene extends SceneBase { } let count = 0; for (let c = 0; c < chances; c++) { - if (!randSeedInt(this.gameMode.getEnemyModifierChance(isBoss))) { + if (!randSeedInt(this.gameMode.getEnemyItemChance(isBoss))) { count++; } } @@ -2887,7 +2843,7 @@ export class BattleScene extends SceneBase { } /** - * Removes all modifiers from enemy pokemon of {@linkcode PersistentModifier} type + * Removes all items from enemy pokemon and trainers */ clearEnemyItems(): void { this.enemyTrainerItems.clearItems(); @@ -2911,7 +2867,7 @@ export class BattleScene extends SceneBase { this.updateUIPositions(); } - setModifiersVisible(visible: boolean) { + setItemsVisible(visible: boolean) { [this.itemBar, this.enemyItemBar].map(m => m.setVisible(visible)); } diff --git a/src/battle.ts b/src/battle.ts index f2d2b5b78fb..709e7a23164 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -10,15 +10,14 @@ import type { MoveId } from "#enums/move-id"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { PokeballType } from "#enums/pokeball"; -import { RewardTier } from "#enums/reward-tier"; import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import { TrainerVariant } from "#enums/trainer-variant"; import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; import { Trainer } from "#field/trainer"; +import type { CustomRewardSettings } from "#items/reward-pool-utils"; import { TrainerItemEffect } from "#items/trainer-item"; -import type { CustomModifierSettings } from "#modifiers/modifier-type"; import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import i18next from "#plugins/i18n"; import { MusicPreference } from "#system/settings"; @@ -481,7 +480,7 @@ export class FixedBattleConfig { public getTrainer: GetTrainerFunc; public getEnemyParty: GetEnemyPartyFunc; public seedOffsetWaveIndex: number; - public customModifierRewardSettings?: CustomModifierSettings; + public customRewardSettings?: CustomRewardSettings; setBattleType(battleType: BattleType): FixedBattleConfig { this.battleType = battleType; @@ -508,8 +507,8 @@ export class FixedBattleConfig { return this; } - setCustomModifierRewards(customModifierRewardSettings: CustomModifierSettings) { - this.customModifierRewardSettings = customModifierRewardSettings; + setCustomRewards(customRewardSettings: CustomRewardSettings) { + this.customRewardSettings = customRewardSettings; return this; } } diff --git a/src/data/balance/tms.ts b/src/data/balance/tms.ts index b6317e8edd4..d24bd28de1a 100644 --- a/src/data/balance/tms.ts +++ b/src/data/balance/tms.ts @@ -1,4 +1,4 @@ -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; @@ -68591,324 +68591,324 @@ function transposeTmSpecies(): SpeciesTmMoves { export const speciesTmMoves: SpeciesTmMoves = transposeTmSpecies(); interface TmPoolTiers { - [key: number]: RewardTier + [key: number]: RarityTier } export const tmPoolTiers: TmPoolTiers = { - [MoveId.MEGA_PUNCH]: RewardTier.GREAT, - [MoveId.PAY_DAY]: RewardTier.ULTRA, - [MoveId.FIRE_PUNCH]: RewardTier.GREAT, - [MoveId.ICE_PUNCH]: RewardTier.GREAT, - [MoveId.THUNDER_PUNCH]: RewardTier.GREAT, - [MoveId.SWORDS_DANCE]: RewardTier.COMMON, - [MoveId.CUT]: RewardTier.COMMON, - [MoveId.FLY]: RewardTier.COMMON, - [MoveId.MEGA_KICK]: RewardTier.GREAT, - [MoveId.BODY_SLAM]: RewardTier.GREAT, - [MoveId.TAKE_DOWN]: RewardTier.GREAT, - [MoveId.DOUBLE_EDGE]: RewardTier.ULTRA, - [MoveId.PIN_MISSILE]: RewardTier.COMMON, - [MoveId.ROAR]: RewardTier.COMMON, - [MoveId.FLAMETHROWER]: RewardTier.ULTRA, - [MoveId.HYDRO_PUMP]: RewardTier.ULTRA, - [MoveId.SURF]: RewardTier.ULTRA, - [MoveId.ICE_BEAM]: RewardTier.ULTRA, - [MoveId.BLIZZARD]: RewardTier.ULTRA, - [MoveId.PSYBEAM]: RewardTier.GREAT, - [MoveId.HYPER_BEAM]: RewardTier.ULTRA, - [MoveId.LOW_KICK]: RewardTier.COMMON, - [MoveId.COUNTER]: RewardTier.COMMON, - [MoveId.STRENGTH]: RewardTier.GREAT, - [MoveId.SOLAR_BEAM]: RewardTier.ULTRA, - [MoveId.FIRE_SPIN]: RewardTier.COMMON, - [MoveId.THUNDERBOLT]: RewardTier.ULTRA, - [MoveId.THUNDER_WAVE]: RewardTier.COMMON, - [MoveId.THUNDER]: RewardTier.ULTRA, - [MoveId.EARTHQUAKE]: RewardTier.ULTRA, - [MoveId.DIG]: RewardTier.GREAT, - [MoveId.TOXIC]: RewardTier.GREAT, - [MoveId.PSYCHIC]: RewardTier.ULTRA, - [MoveId.AGILITY]: RewardTier.COMMON, - [MoveId.NIGHT_SHADE]: RewardTier.COMMON, - [MoveId.SCREECH]: RewardTier.COMMON, - [MoveId.DOUBLE_TEAM]: RewardTier.COMMON, - [MoveId.CONFUSE_RAY]: RewardTier.COMMON, - [MoveId.LIGHT_SCREEN]: RewardTier.COMMON, - [MoveId.HAZE]: RewardTier.COMMON, - [MoveId.REFLECT]: RewardTier.COMMON, - [MoveId.FOCUS_ENERGY]: RewardTier.COMMON, - [MoveId.METRONOME]: RewardTier.COMMON, - [MoveId.SELF_DESTRUCT]: RewardTier.GREAT, - [MoveId.FIRE_BLAST]: RewardTier.ULTRA, - [MoveId.WATERFALL]: RewardTier.GREAT, - [MoveId.SWIFT]: RewardTier.COMMON, - [MoveId.AMNESIA]: RewardTier.COMMON, - [MoveId.DREAM_EATER]: RewardTier.GREAT, - [MoveId.LEECH_LIFE]: RewardTier.ULTRA, - [MoveId.FLASH]: RewardTier.COMMON, - [MoveId.EXPLOSION]: RewardTier.GREAT, - [MoveId.REST]: RewardTier.COMMON, - [MoveId.ROCK_SLIDE]: RewardTier.GREAT, - [MoveId.TRI_ATTACK]: RewardTier.ULTRA, - [MoveId.SUPER_FANG]: RewardTier.COMMON, - [MoveId.SUBSTITUTE]: RewardTier.COMMON, - [MoveId.THIEF]: RewardTier.GREAT, - [MoveId.SNORE]: RewardTier.COMMON, - [MoveId.CURSE]: RewardTier.COMMON, - [MoveId.REVERSAL]: RewardTier.COMMON, - [MoveId.SPITE]: RewardTier.COMMON, - [MoveId.PROTECT]: RewardTier.COMMON, - [MoveId.SCARY_FACE]: RewardTier.COMMON, - [MoveId.SLUDGE_BOMB]: RewardTier.GREAT, - [MoveId.MUD_SLAP]: RewardTier.COMMON, - [MoveId.SPIKES]: RewardTier.COMMON, - [MoveId.ICY_WIND]: RewardTier.GREAT, - [MoveId.OUTRAGE]: RewardTier.ULTRA, - [MoveId.SANDSTORM]: RewardTier.COMMON, - [MoveId.GIGA_DRAIN]: RewardTier.ULTRA, - [MoveId.ENDURE]: RewardTier.COMMON, - [MoveId.CHARM]: RewardTier.COMMON, - [MoveId.FALSE_SWIPE]: RewardTier.COMMON, - [MoveId.SWAGGER]: RewardTier.COMMON, - [MoveId.STEEL_WING]: RewardTier.GREAT, - [MoveId.ATTRACT]: RewardTier.COMMON, - [MoveId.SLEEP_TALK]: RewardTier.COMMON, - [MoveId.HEAL_BELL]: RewardTier.COMMON, - [MoveId.RETURN]: RewardTier.ULTRA, - [MoveId.FRUSTRATION]: RewardTier.COMMON, - [MoveId.SAFEGUARD]: RewardTier.COMMON, - [MoveId.PAIN_SPLIT]: RewardTier.COMMON, - [MoveId.MEGAHORN]: RewardTier.ULTRA, - [MoveId.BATON_PASS]: RewardTier.COMMON, - [MoveId.ENCORE]: RewardTier.COMMON, - [MoveId.IRON_TAIL]: RewardTier.GREAT, - [MoveId.METAL_CLAW]: RewardTier.COMMON, - [MoveId.SYNTHESIS]: RewardTier.GREAT, - [MoveId.HIDDEN_POWER]: RewardTier.GREAT, - [MoveId.RAIN_DANCE]: RewardTier.COMMON, - [MoveId.SUNNY_DAY]: RewardTier.COMMON, - [MoveId.CRUNCH]: RewardTier.GREAT, - [MoveId.PSYCH_UP]: RewardTier.COMMON, - [MoveId.SHADOW_BALL]: RewardTier.ULTRA, - [MoveId.FUTURE_SIGHT]: RewardTier.GREAT, - [MoveId.ROCK_SMASH]: RewardTier.COMMON, - [MoveId.WHIRLPOOL]: RewardTier.COMMON, - [MoveId.BEAT_UP]: RewardTier.COMMON, - [MoveId.UPROAR]: RewardTier.GREAT, - [MoveId.HEAT_WAVE]: RewardTier.ULTRA, - [MoveId.HAIL]: RewardTier.COMMON, - [MoveId.TORMENT]: RewardTier.COMMON, - [MoveId.WILL_O_WISP]: RewardTier.COMMON, - [MoveId.FACADE]: RewardTier.GREAT, - [MoveId.FOCUS_PUNCH]: RewardTier.COMMON, - [MoveId.NATURE_POWER]: RewardTier.COMMON, - [MoveId.CHARGE]: RewardTier.COMMON, - [MoveId.TAUNT]: RewardTier.COMMON, - [MoveId.HELPING_HAND]: RewardTier.COMMON, - [MoveId.TRICK]: RewardTier.COMMON, - [MoveId.SUPERPOWER]: RewardTier.ULTRA, - [MoveId.RECYCLE]: RewardTier.COMMON, - [MoveId.REVENGE]: RewardTier.GREAT, - [MoveId.BRICK_BREAK]: RewardTier.GREAT, - [MoveId.KNOCK_OFF]: RewardTier.GREAT, - [MoveId.ENDEAVOR]: RewardTier.COMMON, - [MoveId.SKILL_SWAP]: RewardTier.COMMON, - [MoveId.IMPRISON]: RewardTier.COMMON, - [MoveId.SECRET_POWER]: RewardTier.COMMON, - [MoveId.DIVE]: RewardTier.GREAT, - [MoveId.FEATHER_DANCE]: RewardTier.COMMON, - [MoveId.BLAZE_KICK]: RewardTier.GREAT, - [MoveId.HYPER_VOICE]: RewardTier.ULTRA, - [MoveId.BLAST_BURN]: RewardTier.ULTRA, - [MoveId.HYDRO_CANNON]: RewardTier.ULTRA, - [MoveId.WEATHER_BALL]: RewardTier.COMMON, - [MoveId.FAKE_TEARS]: RewardTier.COMMON, - [MoveId.AIR_CUTTER]: RewardTier.GREAT, - [MoveId.OVERHEAT]: RewardTier.ULTRA, - [MoveId.ROCK_TOMB]: RewardTier.GREAT, - [MoveId.METAL_SOUND]: RewardTier.COMMON, - [MoveId.COSMIC_POWER]: RewardTier.COMMON, - [MoveId.SIGNAL_BEAM]: RewardTier.GREAT, - [MoveId.SAND_TOMB]: RewardTier.COMMON, - [MoveId.MUDDY_WATER]: RewardTier.GREAT, - [MoveId.BULLET_SEED]: RewardTier.GREAT, - [MoveId.AERIAL_ACE]: RewardTier.GREAT, - [MoveId.ICICLE_SPEAR]: RewardTier.GREAT, - [MoveId.IRON_DEFENSE]: RewardTier.GREAT, - [MoveId.DRAGON_CLAW]: RewardTier.ULTRA, - [MoveId.FRENZY_PLANT]: RewardTier.ULTRA, - [MoveId.BULK_UP]: RewardTier.COMMON, - [MoveId.BOUNCE]: RewardTier.GREAT, - [MoveId.MUD_SHOT]: RewardTier.GREAT, - [MoveId.POISON_TAIL]: RewardTier.GREAT, - [MoveId.COVET]: RewardTier.GREAT, - [MoveId.MAGICAL_LEAF]: RewardTier.GREAT, - [MoveId.CALM_MIND]: RewardTier.GREAT, - [MoveId.LEAF_BLADE]: RewardTier.ULTRA, - [MoveId.DRAGON_DANCE]: RewardTier.GREAT, - [MoveId.ROCK_BLAST]: RewardTier.GREAT, - [MoveId.WATER_PULSE]: RewardTier.GREAT, - [MoveId.ROOST]: RewardTier.GREAT, - [MoveId.GRAVITY]: RewardTier.COMMON, - [MoveId.GYRO_BALL]: RewardTier.COMMON, - [MoveId.BRINE]: RewardTier.GREAT, - [MoveId.PLUCK]: RewardTier.GREAT, - [MoveId.TAILWIND]: RewardTier.GREAT, - [MoveId.U_TURN]: RewardTier.GREAT, - [MoveId.CLOSE_COMBAT]: RewardTier.ULTRA, - [MoveId.PAYBACK]: RewardTier.COMMON, - [MoveId.ASSURANCE]: RewardTier.COMMON, - [MoveId.EMBARGO]: RewardTier.COMMON, - [MoveId.FLING]: RewardTier.COMMON, - [MoveId.GASTRO_ACID]: RewardTier.GREAT, - [MoveId.POWER_SWAP]: RewardTier.COMMON, - [MoveId.GUARD_SWAP]: RewardTier.COMMON, - [MoveId.WORRY_SEED]: RewardTier.GREAT, - [MoveId.TOXIC_SPIKES]: RewardTier.GREAT, - [MoveId.FLARE_BLITZ]: RewardTier.ULTRA, - [MoveId.AURA_SPHERE]: RewardTier.GREAT, - [MoveId.ROCK_POLISH]: RewardTier.COMMON, - [MoveId.POISON_JAB]: RewardTier.GREAT, - [MoveId.DARK_PULSE]: RewardTier.GREAT, - [MoveId.AQUA_TAIL]: RewardTier.GREAT, - [MoveId.SEED_BOMB]: RewardTier.GREAT, - [MoveId.AIR_SLASH]: RewardTier.GREAT, - [MoveId.X_SCISSOR]: RewardTier.GREAT, - [MoveId.BUG_BUZZ]: RewardTier.GREAT, - [MoveId.DRAGON_PULSE]: RewardTier.GREAT, - [MoveId.POWER_GEM]: RewardTier.GREAT, - [MoveId.DRAIN_PUNCH]: RewardTier.GREAT, - [MoveId.VACUUM_WAVE]: RewardTier.COMMON, - [MoveId.FOCUS_BLAST]: RewardTier.GREAT, - [MoveId.ENERGY_BALL]: RewardTier.GREAT, - [MoveId.BRAVE_BIRD]: RewardTier.ULTRA, - [MoveId.EARTH_POWER]: RewardTier.ULTRA, - [MoveId.GIGA_IMPACT]: RewardTier.GREAT, - [MoveId.NASTY_PLOT]: RewardTier.COMMON, - [MoveId.AVALANCHE]: RewardTier.GREAT, - [MoveId.SHADOW_CLAW]: RewardTier.GREAT, - [MoveId.THUNDER_FANG]: RewardTier.GREAT, - [MoveId.ICE_FANG]: RewardTier.GREAT, - [MoveId.FIRE_FANG]: RewardTier.GREAT, - [MoveId.PSYCHO_CUT]: RewardTier.GREAT, - [MoveId.ZEN_HEADBUTT]: RewardTier.GREAT, - [MoveId.FLASH_CANNON]: RewardTier.GREAT, - [MoveId.ROCK_CLIMB]: RewardTier.GREAT, - [MoveId.DEFOG]: RewardTier.COMMON, - [MoveId.TRICK_ROOM]: RewardTier.COMMON, - [MoveId.DRACO_METEOR]: RewardTier.ULTRA, - [MoveId.LEAF_STORM]: RewardTier.ULTRA, - [MoveId.POWER_WHIP]: RewardTier.ULTRA, - [MoveId.CROSS_POISON]: RewardTier.GREAT, - [MoveId.GUNK_SHOT]: RewardTier.ULTRA, - [MoveId.IRON_HEAD]: RewardTier.GREAT, - [MoveId.STONE_EDGE]: RewardTier.ULTRA, - [MoveId.STEALTH_ROCK]: RewardTier.COMMON, - [MoveId.GRASS_KNOT]: RewardTier.ULTRA, - [MoveId.BUG_BITE]: RewardTier.GREAT, - [MoveId.CHARGE_BEAM]: RewardTier.GREAT, - [MoveId.HONE_CLAWS]: RewardTier.COMMON, - [MoveId.WONDER_ROOM]: RewardTier.COMMON, - [MoveId.PSYSHOCK]: RewardTier.GREAT, - [MoveId.VENOSHOCK]: RewardTier.GREAT, - [MoveId.MAGIC_ROOM]: RewardTier.COMMON, - [MoveId.SMACK_DOWN]: RewardTier.COMMON, - [MoveId.SLUDGE_WAVE]: RewardTier.GREAT, - [MoveId.HEAVY_SLAM]: RewardTier.GREAT, - [MoveId.ELECTRO_BALL]: RewardTier.GREAT, - [MoveId.FLAME_CHARGE]: RewardTier.GREAT, - [MoveId.LOW_SWEEP]: RewardTier.GREAT, - [MoveId.ACID_SPRAY]: RewardTier.COMMON, - [MoveId.FOUL_PLAY]: RewardTier.ULTRA, - [MoveId.ROUND]: RewardTier.COMMON, - [MoveId.ECHOED_VOICE]: RewardTier.COMMON, - [MoveId.STORED_POWER]: RewardTier.COMMON, - [MoveId.ALLY_SWITCH]: RewardTier.COMMON, - [MoveId.SCALD]: RewardTier.GREAT, - [MoveId.HEX]: RewardTier.GREAT, - [MoveId.SKY_DROP]: RewardTier.GREAT, - [MoveId.INCINERATE]: RewardTier.GREAT, - [MoveId.QUASH]: RewardTier.COMMON, - [MoveId.ACROBATICS]: RewardTier.GREAT, - [MoveId.RETALIATE]: RewardTier.GREAT, - [MoveId.WATER_PLEDGE]: RewardTier.GREAT, - [MoveId.FIRE_PLEDGE]: RewardTier.GREAT, - [MoveId.GRASS_PLEDGE]: RewardTier.GREAT, - [MoveId.VOLT_SWITCH]: RewardTier.GREAT, - [MoveId.STRUGGLE_BUG]: RewardTier.COMMON, - [MoveId.BULLDOZE]: RewardTier.GREAT, - [MoveId.FROST_BREATH]: RewardTier.GREAT, - [MoveId.DRAGON_TAIL]: RewardTier.GREAT, - [MoveId.WORK_UP]: RewardTier.COMMON, - [MoveId.ELECTROWEB]: RewardTier.GREAT, - [MoveId.WILD_CHARGE]: RewardTier.GREAT, - [MoveId.DRILL_RUN]: RewardTier.GREAT, - [MoveId.RAZOR_SHELL]: RewardTier.GREAT, - [MoveId.HEAT_CRASH]: RewardTier.GREAT, - [MoveId.TAIL_SLAP]: RewardTier.GREAT, - [MoveId.HURRICANE]: RewardTier.ULTRA, - [MoveId.SNARL]: RewardTier.COMMON, - [MoveId.PHANTOM_FORCE]: RewardTier.ULTRA, - [MoveId.PETAL_BLIZZARD]: RewardTier.GREAT, - [MoveId.DISARMING_VOICE]: RewardTier.GREAT, - [MoveId.DRAINING_KISS]: RewardTier.GREAT, - [MoveId.GRASSY_TERRAIN]: RewardTier.COMMON, - [MoveId.MISTY_TERRAIN]: RewardTier.COMMON, - [MoveId.PLAY_ROUGH]: RewardTier.GREAT, - [MoveId.CONFIDE]: RewardTier.COMMON, - [MoveId.MYSTICAL_FIRE]: RewardTier.GREAT, - [MoveId.EERIE_IMPULSE]: RewardTier.COMMON, - [MoveId.VENOM_DRENCH]: RewardTier.COMMON, - [MoveId.ELECTRIC_TERRAIN]: RewardTier.COMMON, - [MoveId.DAZZLING_GLEAM]: RewardTier.ULTRA, - [MoveId.INFESTATION]: RewardTier.COMMON, - [MoveId.POWER_UP_PUNCH]: RewardTier.GREAT, - [MoveId.DARKEST_LARIAT]: RewardTier.GREAT, - [MoveId.HIGH_HORSEPOWER]: RewardTier.ULTRA, - [MoveId.SOLAR_BLADE]: RewardTier.GREAT, - [MoveId.THROAT_CHOP]: RewardTier.GREAT, - [MoveId.POLLEN_PUFF]: RewardTier.GREAT, - [MoveId.PSYCHIC_TERRAIN]: RewardTier.COMMON, - [MoveId.LUNGE]: RewardTier.GREAT, - [MoveId.SPEED_SWAP]: RewardTier.COMMON, - [MoveId.SMART_STRIKE]: RewardTier.GREAT, - [MoveId.BRUTAL_SWING]: RewardTier.GREAT, - [MoveId.AURORA_VEIL]: RewardTier.COMMON, - [MoveId.PSYCHIC_FANGS]: RewardTier.GREAT, - [MoveId.STOMPING_TANTRUM]: RewardTier.GREAT, - [MoveId.LIQUIDATION]: RewardTier.ULTRA, - [MoveId.BODY_PRESS]: RewardTier.ULTRA, - [MoveId.BREAKING_SWIPE]: RewardTier.GREAT, - [MoveId.STEEL_BEAM]: RewardTier.ULTRA, - [MoveId.EXPANDING_FORCE]: RewardTier.GREAT, - [MoveId.STEEL_ROLLER]: RewardTier.COMMON, - [MoveId.SCALE_SHOT]: RewardTier.ULTRA, - [MoveId.METEOR_BEAM]: RewardTier.GREAT, - [MoveId.MISTY_EXPLOSION]: RewardTier.COMMON, - [MoveId.GRASSY_GLIDE]: RewardTier.COMMON, - [MoveId.RISING_VOLTAGE]: RewardTier.COMMON, - [MoveId.TERRAIN_PULSE]: RewardTier.COMMON, - [MoveId.SKITTER_SMACK]: RewardTier.GREAT, - [MoveId.BURNING_JEALOUSY]: RewardTier.GREAT, - [MoveId.LASH_OUT]: RewardTier.GREAT, - [MoveId.POLTERGEIST]: RewardTier.ULTRA, - [MoveId.CORROSIVE_GAS]: RewardTier.COMMON, - [MoveId.COACHING]: RewardTier.COMMON, - [MoveId.FLIP_TURN]: RewardTier.COMMON, - [MoveId.TRIPLE_AXEL]: RewardTier.COMMON, - [MoveId.DUAL_WINGBEAT]: RewardTier.COMMON, - [MoveId.SCORCHING_SANDS]: RewardTier.GREAT, - [MoveId.TERA_BLAST]: RewardTier.GREAT, - [MoveId.ICE_SPINNER]: RewardTier.GREAT, - [MoveId.SNOWSCAPE]: RewardTier.COMMON, - [MoveId.POUNCE]: RewardTier.COMMON, - [MoveId.TRAILBLAZE]: RewardTier.COMMON, - [MoveId.CHILLING_WATER]: RewardTier.COMMON, - [MoveId.HARD_PRESS]: RewardTier.GREAT, - [MoveId.DRAGON_CHEER]: RewardTier.COMMON, - [MoveId.ALLURING_VOICE]: RewardTier.GREAT, - [MoveId.TEMPER_FLARE]: RewardTier.GREAT, - [MoveId.SUPERCELL_SLAM]: RewardTier.GREAT, - [MoveId.PSYCHIC_NOISE]: RewardTier.GREAT, - [MoveId.UPPER_HAND]: RewardTier.COMMON, + [MoveId.MEGA_PUNCH]: RarityTier.GREAT, + [MoveId.PAY_DAY]: RarityTier.ULTRA, + [MoveId.FIRE_PUNCH]: RarityTier.GREAT, + [MoveId.ICE_PUNCH]: RarityTier.GREAT, + [MoveId.THUNDER_PUNCH]: RarityTier.GREAT, + [MoveId.SWORDS_DANCE]: RarityTier.COMMON, + [MoveId.CUT]: RarityTier.COMMON, + [MoveId.FLY]: RarityTier.COMMON, + [MoveId.MEGA_KICK]: RarityTier.GREAT, + [MoveId.BODY_SLAM]: RarityTier.GREAT, + [MoveId.TAKE_DOWN]: RarityTier.GREAT, + [MoveId.DOUBLE_EDGE]: RarityTier.ULTRA, + [MoveId.PIN_MISSILE]: RarityTier.COMMON, + [MoveId.ROAR]: RarityTier.COMMON, + [MoveId.FLAMETHROWER]: RarityTier.ULTRA, + [MoveId.HYDRO_PUMP]: RarityTier.ULTRA, + [MoveId.SURF]: RarityTier.ULTRA, + [MoveId.ICE_BEAM]: RarityTier.ULTRA, + [MoveId.BLIZZARD]: RarityTier.ULTRA, + [MoveId.PSYBEAM]: RarityTier.GREAT, + [MoveId.HYPER_BEAM]: RarityTier.ULTRA, + [MoveId.LOW_KICK]: RarityTier.COMMON, + [MoveId.COUNTER]: RarityTier.COMMON, + [MoveId.STRENGTH]: RarityTier.GREAT, + [MoveId.SOLAR_BEAM]: RarityTier.ULTRA, + [MoveId.FIRE_SPIN]: RarityTier.COMMON, + [MoveId.THUNDERBOLT]: RarityTier.ULTRA, + [MoveId.THUNDER_WAVE]: RarityTier.COMMON, + [MoveId.THUNDER]: RarityTier.ULTRA, + [MoveId.EARTHQUAKE]: RarityTier.ULTRA, + [MoveId.DIG]: RarityTier.GREAT, + [MoveId.TOXIC]: RarityTier.GREAT, + [MoveId.PSYCHIC]: RarityTier.ULTRA, + [MoveId.AGILITY]: RarityTier.COMMON, + [MoveId.NIGHT_SHADE]: RarityTier.COMMON, + [MoveId.SCREECH]: RarityTier.COMMON, + [MoveId.DOUBLE_TEAM]: RarityTier.COMMON, + [MoveId.CONFUSE_RAY]: RarityTier.COMMON, + [MoveId.LIGHT_SCREEN]: RarityTier.COMMON, + [MoveId.HAZE]: RarityTier.COMMON, + [MoveId.REFLECT]: RarityTier.COMMON, + [MoveId.FOCUS_ENERGY]: RarityTier.COMMON, + [MoveId.METRONOME]: RarityTier.COMMON, + [MoveId.SELF_DESTRUCT]: RarityTier.GREAT, + [MoveId.FIRE_BLAST]: RarityTier.ULTRA, + [MoveId.WATERFALL]: RarityTier.GREAT, + [MoveId.SWIFT]: RarityTier.COMMON, + [MoveId.AMNESIA]: RarityTier.COMMON, + [MoveId.DREAM_EATER]: RarityTier.GREAT, + [MoveId.LEECH_LIFE]: RarityTier.ULTRA, + [MoveId.FLASH]: RarityTier.COMMON, + [MoveId.EXPLOSION]: RarityTier.GREAT, + [MoveId.REST]: RarityTier.COMMON, + [MoveId.ROCK_SLIDE]: RarityTier.GREAT, + [MoveId.TRI_ATTACK]: RarityTier.ULTRA, + [MoveId.SUPER_FANG]: RarityTier.COMMON, + [MoveId.SUBSTITUTE]: RarityTier.COMMON, + [MoveId.THIEF]: RarityTier.GREAT, + [MoveId.SNORE]: RarityTier.COMMON, + [MoveId.CURSE]: RarityTier.COMMON, + [MoveId.REVERSAL]: RarityTier.COMMON, + [MoveId.SPITE]: RarityTier.COMMON, + [MoveId.PROTECT]: RarityTier.COMMON, + [MoveId.SCARY_FACE]: RarityTier.COMMON, + [MoveId.SLUDGE_BOMB]: RarityTier.GREAT, + [MoveId.MUD_SLAP]: RarityTier.COMMON, + [MoveId.SPIKES]: RarityTier.COMMON, + [MoveId.ICY_WIND]: RarityTier.GREAT, + [MoveId.OUTRAGE]: RarityTier.ULTRA, + [MoveId.SANDSTORM]: RarityTier.COMMON, + [MoveId.GIGA_DRAIN]: RarityTier.ULTRA, + [MoveId.ENDURE]: RarityTier.COMMON, + [MoveId.CHARM]: RarityTier.COMMON, + [MoveId.FALSE_SWIPE]: RarityTier.COMMON, + [MoveId.SWAGGER]: RarityTier.COMMON, + [MoveId.STEEL_WING]: RarityTier.GREAT, + [MoveId.ATTRACT]: RarityTier.COMMON, + [MoveId.SLEEP_TALK]: RarityTier.COMMON, + [MoveId.HEAL_BELL]: RarityTier.COMMON, + [MoveId.RETURN]: RarityTier.ULTRA, + [MoveId.FRUSTRATION]: RarityTier.COMMON, + [MoveId.SAFEGUARD]: RarityTier.COMMON, + [MoveId.PAIN_SPLIT]: RarityTier.COMMON, + [MoveId.MEGAHORN]: RarityTier.ULTRA, + [MoveId.BATON_PASS]: RarityTier.COMMON, + [MoveId.ENCORE]: RarityTier.COMMON, + [MoveId.IRON_TAIL]: RarityTier.GREAT, + [MoveId.METAL_CLAW]: RarityTier.COMMON, + [MoveId.SYNTHESIS]: RarityTier.GREAT, + [MoveId.HIDDEN_POWER]: RarityTier.GREAT, + [MoveId.RAIN_DANCE]: RarityTier.COMMON, + [MoveId.SUNNY_DAY]: RarityTier.COMMON, + [MoveId.CRUNCH]: RarityTier.GREAT, + [MoveId.PSYCH_UP]: RarityTier.COMMON, + [MoveId.SHADOW_BALL]: RarityTier.ULTRA, + [MoveId.FUTURE_SIGHT]: RarityTier.GREAT, + [MoveId.ROCK_SMASH]: RarityTier.COMMON, + [MoveId.WHIRLPOOL]: RarityTier.COMMON, + [MoveId.BEAT_UP]: RarityTier.COMMON, + [MoveId.UPROAR]: RarityTier.GREAT, + [MoveId.HEAT_WAVE]: RarityTier.ULTRA, + [MoveId.HAIL]: RarityTier.COMMON, + [MoveId.TORMENT]: RarityTier.COMMON, + [MoveId.WILL_O_WISP]: RarityTier.COMMON, + [MoveId.FACADE]: RarityTier.GREAT, + [MoveId.FOCUS_PUNCH]: RarityTier.COMMON, + [MoveId.NATURE_POWER]: RarityTier.COMMON, + [MoveId.CHARGE]: RarityTier.COMMON, + [MoveId.TAUNT]: RarityTier.COMMON, + [MoveId.HELPING_HAND]: RarityTier.COMMON, + [MoveId.TRICK]: RarityTier.COMMON, + [MoveId.SUPERPOWER]: RarityTier.ULTRA, + [MoveId.RECYCLE]: RarityTier.COMMON, + [MoveId.REVENGE]: RarityTier.GREAT, + [MoveId.BRICK_BREAK]: RarityTier.GREAT, + [MoveId.KNOCK_OFF]: RarityTier.GREAT, + [MoveId.ENDEAVOR]: RarityTier.COMMON, + [MoveId.SKILL_SWAP]: RarityTier.COMMON, + [MoveId.IMPRISON]: RarityTier.COMMON, + [MoveId.SECRET_POWER]: RarityTier.COMMON, + [MoveId.DIVE]: RarityTier.GREAT, + [MoveId.FEATHER_DANCE]: RarityTier.COMMON, + [MoveId.BLAZE_KICK]: RarityTier.GREAT, + [MoveId.HYPER_VOICE]: RarityTier.ULTRA, + [MoveId.BLAST_BURN]: RarityTier.ULTRA, + [MoveId.HYDRO_CANNON]: RarityTier.ULTRA, + [MoveId.WEATHER_BALL]: RarityTier.COMMON, + [MoveId.FAKE_TEARS]: RarityTier.COMMON, + [MoveId.AIR_CUTTER]: RarityTier.GREAT, + [MoveId.OVERHEAT]: RarityTier.ULTRA, + [MoveId.ROCK_TOMB]: RarityTier.GREAT, + [MoveId.METAL_SOUND]: RarityTier.COMMON, + [MoveId.COSMIC_POWER]: RarityTier.COMMON, + [MoveId.SIGNAL_BEAM]: RarityTier.GREAT, + [MoveId.SAND_TOMB]: RarityTier.COMMON, + [MoveId.MUDDY_WATER]: RarityTier.GREAT, + [MoveId.BULLET_SEED]: RarityTier.GREAT, + [MoveId.AERIAL_ACE]: RarityTier.GREAT, + [MoveId.ICICLE_SPEAR]: RarityTier.GREAT, + [MoveId.IRON_DEFENSE]: RarityTier.GREAT, + [MoveId.DRAGON_CLAW]: RarityTier.ULTRA, + [MoveId.FRENZY_PLANT]: RarityTier.ULTRA, + [MoveId.BULK_UP]: RarityTier.COMMON, + [MoveId.BOUNCE]: RarityTier.GREAT, + [MoveId.MUD_SHOT]: RarityTier.GREAT, + [MoveId.POISON_TAIL]: RarityTier.GREAT, + [MoveId.COVET]: RarityTier.GREAT, + [MoveId.MAGICAL_LEAF]: RarityTier.GREAT, + [MoveId.CALM_MIND]: RarityTier.GREAT, + [MoveId.LEAF_BLADE]: RarityTier.ULTRA, + [MoveId.DRAGON_DANCE]: RarityTier.GREAT, + [MoveId.ROCK_BLAST]: RarityTier.GREAT, + [MoveId.WATER_PULSE]: RarityTier.GREAT, + [MoveId.ROOST]: RarityTier.GREAT, + [MoveId.GRAVITY]: RarityTier.COMMON, + [MoveId.GYRO_BALL]: RarityTier.COMMON, + [MoveId.BRINE]: RarityTier.GREAT, + [MoveId.PLUCK]: RarityTier.GREAT, + [MoveId.TAILWIND]: RarityTier.GREAT, + [MoveId.U_TURN]: RarityTier.GREAT, + [MoveId.CLOSE_COMBAT]: RarityTier.ULTRA, + [MoveId.PAYBACK]: RarityTier.COMMON, + [MoveId.ASSURANCE]: RarityTier.COMMON, + [MoveId.EMBARGO]: RarityTier.COMMON, + [MoveId.FLING]: RarityTier.COMMON, + [MoveId.GASTRO_ACID]: RarityTier.GREAT, + [MoveId.POWER_SWAP]: RarityTier.COMMON, + [MoveId.GUARD_SWAP]: RarityTier.COMMON, + [MoveId.WORRY_SEED]: RarityTier.GREAT, + [MoveId.TOXIC_SPIKES]: RarityTier.GREAT, + [MoveId.FLARE_BLITZ]: RarityTier.ULTRA, + [MoveId.AURA_SPHERE]: RarityTier.GREAT, + [MoveId.ROCK_POLISH]: RarityTier.COMMON, + [MoveId.POISON_JAB]: RarityTier.GREAT, + [MoveId.DARK_PULSE]: RarityTier.GREAT, + [MoveId.AQUA_TAIL]: RarityTier.GREAT, + [MoveId.SEED_BOMB]: RarityTier.GREAT, + [MoveId.AIR_SLASH]: RarityTier.GREAT, + [MoveId.X_SCISSOR]: RarityTier.GREAT, + [MoveId.BUG_BUZZ]: RarityTier.GREAT, + [MoveId.DRAGON_PULSE]: RarityTier.GREAT, + [MoveId.POWER_GEM]: RarityTier.GREAT, + [MoveId.DRAIN_PUNCH]: RarityTier.GREAT, + [MoveId.VACUUM_WAVE]: RarityTier.COMMON, + [MoveId.FOCUS_BLAST]: RarityTier.GREAT, + [MoveId.ENERGY_BALL]: RarityTier.GREAT, + [MoveId.BRAVE_BIRD]: RarityTier.ULTRA, + [MoveId.EARTH_POWER]: RarityTier.ULTRA, + [MoveId.GIGA_IMPACT]: RarityTier.GREAT, + [MoveId.NASTY_PLOT]: RarityTier.COMMON, + [MoveId.AVALANCHE]: RarityTier.GREAT, + [MoveId.SHADOW_CLAW]: RarityTier.GREAT, + [MoveId.THUNDER_FANG]: RarityTier.GREAT, + [MoveId.ICE_FANG]: RarityTier.GREAT, + [MoveId.FIRE_FANG]: RarityTier.GREAT, + [MoveId.PSYCHO_CUT]: RarityTier.GREAT, + [MoveId.ZEN_HEADBUTT]: RarityTier.GREAT, + [MoveId.FLASH_CANNON]: RarityTier.GREAT, + [MoveId.ROCK_CLIMB]: RarityTier.GREAT, + [MoveId.DEFOG]: RarityTier.COMMON, + [MoveId.TRICK_ROOM]: RarityTier.COMMON, + [MoveId.DRACO_METEOR]: RarityTier.ULTRA, + [MoveId.LEAF_STORM]: RarityTier.ULTRA, + [MoveId.POWER_WHIP]: RarityTier.ULTRA, + [MoveId.CROSS_POISON]: RarityTier.GREAT, + [MoveId.GUNK_SHOT]: RarityTier.ULTRA, + [MoveId.IRON_HEAD]: RarityTier.GREAT, + [MoveId.STONE_EDGE]: RarityTier.ULTRA, + [MoveId.STEALTH_ROCK]: RarityTier.COMMON, + [MoveId.GRASS_KNOT]: RarityTier.ULTRA, + [MoveId.BUG_BITE]: RarityTier.GREAT, + [MoveId.CHARGE_BEAM]: RarityTier.GREAT, + [MoveId.HONE_CLAWS]: RarityTier.COMMON, + [MoveId.WONDER_ROOM]: RarityTier.COMMON, + [MoveId.PSYSHOCK]: RarityTier.GREAT, + [MoveId.VENOSHOCK]: RarityTier.GREAT, + [MoveId.MAGIC_ROOM]: RarityTier.COMMON, + [MoveId.SMACK_DOWN]: RarityTier.COMMON, + [MoveId.SLUDGE_WAVE]: RarityTier.GREAT, + [MoveId.HEAVY_SLAM]: RarityTier.GREAT, + [MoveId.ELECTRO_BALL]: RarityTier.GREAT, + [MoveId.FLAME_CHARGE]: RarityTier.GREAT, + [MoveId.LOW_SWEEP]: RarityTier.GREAT, + [MoveId.ACID_SPRAY]: RarityTier.COMMON, + [MoveId.FOUL_PLAY]: RarityTier.ULTRA, + [MoveId.ROUND]: RarityTier.COMMON, + [MoveId.ECHOED_VOICE]: RarityTier.COMMON, + [MoveId.STORED_POWER]: RarityTier.COMMON, + [MoveId.ALLY_SWITCH]: RarityTier.COMMON, + [MoveId.SCALD]: RarityTier.GREAT, + [MoveId.HEX]: RarityTier.GREAT, + [MoveId.SKY_DROP]: RarityTier.GREAT, + [MoveId.INCINERATE]: RarityTier.GREAT, + [MoveId.QUASH]: RarityTier.COMMON, + [MoveId.ACROBATICS]: RarityTier.GREAT, + [MoveId.RETALIATE]: RarityTier.GREAT, + [MoveId.WATER_PLEDGE]: RarityTier.GREAT, + [MoveId.FIRE_PLEDGE]: RarityTier.GREAT, + [MoveId.GRASS_PLEDGE]: RarityTier.GREAT, + [MoveId.VOLT_SWITCH]: RarityTier.GREAT, + [MoveId.STRUGGLE_BUG]: RarityTier.COMMON, + [MoveId.BULLDOZE]: RarityTier.GREAT, + [MoveId.FROST_BREATH]: RarityTier.GREAT, + [MoveId.DRAGON_TAIL]: RarityTier.GREAT, + [MoveId.WORK_UP]: RarityTier.COMMON, + [MoveId.ELECTROWEB]: RarityTier.GREAT, + [MoveId.WILD_CHARGE]: RarityTier.GREAT, + [MoveId.DRILL_RUN]: RarityTier.GREAT, + [MoveId.RAZOR_SHELL]: RarityTier.GREAT, + [MoveId.HEAT_CRASH]: RarityTier.GREAT, + [MoveId.TAIL_SLAP]: RarityTier.GREAT, + [MoveId.HURRICANE]: RarityTier.ULTRA, + [MoveId.SNARL]: RarityTier.COMMON, + [MoveId.PHANTOM_FORCE]: RarityTier.ULTRA, + [MoveId.PETAL_BLIZZARD]: RarityTier.GREAT, + [MoveId.DISARMING_VOICE]: RarityTier.GREAT, + [MoveId.DRAINING_KISS]: RarityTier.GREAT, + [MoveId.GRASSY_TERRAIN]: RarityTier.COMMON, + [MoveId.MISTY_TERRAIN]: RarityTier.COMMON, + [MoveId.PLAY_ROUGH]: RarityTier.GREAT, + [MoveId.CONFIDE]: RarityTier.COMMON, + [MoveId.MYSTICAL_FIRE]: RarityTier.GREAT, + [MoveId.EERIE_IMPULSE]: RarityTier.COMMON, + [MoveId.VENOM_DRENCH]: RarityTier.COMMON, + [MoveId.ELECTRIC_TERRAIN]: RarityTier.COMMON, + [MoveId.DAZZLING_GLEAM]: RarityTier.ULTRA, + [MoveId.INFESTATION]: RarityTier.COMMON, + [MoveId.POWER_UP_PUNCH]: RarityTier.GREAT, + [MoveId.DARKEST_LARIAT]: RarityTier.GREAT, + [MoveId.HIGH_HORSEPOWER]: RarityTier.ULTRA, + [MoveId.SOLAR_BLADE]: RarityTier.GREAT, + [MoveId.THROAT_CHOP]: RarityTier.GREAT, + [MoveId.POLLEN_PUFF]: RarityTier.GREAT, + [MoveId.PSYCHIC_TERRAIN]: RarityTier.COMMON, + [MoveId.LUNGE]: RarityTier.GREAT, + [MoveId.SPEED_SWAP]: RarityTier.COMMON, + [MoveId.SMART_STRIKE]: RarityTier.GREAT, + [MoveId.BRUTAL_SWING]: RarityTier.GREAT, + [MoveId.AURORA_VEIL]: RarityTier.COMMON, + [MoveId.PSYCHIC_FANGS]: RarityTier.GREAT, + [MoveId.STOMPING_TANTRUM]: RarityTier.GREAT, + [MoveId.LIQUIDATION]: RarityTier.ULTRA, + [MoveId.BODY_PRESS]: RarityTier.ULTRA, + [MoveId.BREAKING_SWIPE]: RarityTier.GREAT, + [MoveId.STEEL_BEAM]: RarityTier.ULTRA, + [MoveId.EXPANDING_FORCE]: RarityTier.GREAT, + [MoveId.STEEL_ROLLER]: RarityTier.COMMON, + [MoveId.SCALE_SHOT]: RarityTier.ULTRA, + [MoveId.METEOR_BEAM]: RarityTier.GREAT, + [MoveId.MISTY_EXPLOSION]: RarityTier.COMMON, + [MoveId.GRASSY_GLIDE]: RarityTier.COMMON, + [MoveId.RISING_VOLTAGE]: RarityTier.COMMON, + [MoveId.TERRAIN_PULSE]: RarityTier.COMMON, + [MoveId.SKITTER_SMACK]: RarityTier.GREAT, + [MoveId.BURNING_JEALOUSY]: RarityTier.GREAT, + [MoveId.LASH_OUT]: RarityTier.GREAT, + [MoveId.POLTERGEIST]: RarityTier.ULTRA, + [MoveId.CORROSIVE_GAS]: RarityTier.COMMON, + [MoveId.COACHING]: RarityTier.COMMON, + [MoveId.FLIP_TURN]: RarityTier.COMMON, + [MoveId.TRIPLE_AXEL]: RarityTier.COMMON, + [MoveId.DUAL_WINGBEAT]: RarityTier.COMMON, + [MoveId.SCORCHING_SANDS]: RarityTier.GREAT, + [MoveId.TERA_BLAST]: RarityTier.GREAT, + [MoveId.ICE_SPINNER]: RarityTier.GREAT, + [MoveId.SNOWSCAPE]: RarityTier.COMMON, + [MoveId.POUNCE]: RarityTier.COMMON, + [MoveId.TRAILBLAZE]: RarityTier.COMMON, + [MoveId.CHILLING_WATER]: RarityTier.COMMON, + [MoveId.HARD_PRESS]: RarityTier.GREAT, + [MoveId.DRAGON_CHEER]: RarityTier.COMMON, + [MoveId.ALLURING_VOICE]: RarityTier.GREAT, + [MoveId.TEMPER_FLARE]: RarityTier.GREAT, + [MoveId.SUPERCELL_SLAM]: RarityTier.GREAT, + [MoveId.PSYCHIC_NOISE]: RarityTier.GREAT, + [MoveId.UPPER_HAND]: RarityTier.COMMON, }; diff --git a/src/data/challenge.ts b/src/data/challenge.ts index efd28f8ed5d..f4106726e79 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -16,7 +16,7 @@ import type { MoveId } from "#enums/move-id"; import type { MoveSourceType } from "#enums/move-source-type"; import { Nature } from "#enums/nature"; import { PokemonType } from "#enums/pokemon-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import { TrainerVariant } from "#enums/trainer-variant"; @@ -457,13 +457,13 @@ export class SingleGenerationChallenge extends Challenge { .setBattleType(BattleType.TRAINER) .setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1) .setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)) - .setCustomModifierRewards({ - guaranteedModifierTiers: [ - RewardTier.ROGUE, - RewardTier.ROGUE, - RewardTier.ULTRA, - RewardTier.ULTRA, - RewardTier.ULTRA, + .setCustomRewards({ + guaranteedRarityTiers: [ + RarityTier.ROGUE, + RarityTier.ROGUE, + RarityTier.ULTRA, + RarityTier.ULTRA, + RarityTier.ULTRA, ], allowLuckUpgrades: false, }); @@ -474,14 +474,14 @@ export class SingleGenerationChallenge extends Challenge { .setBattleType(BattleType.TRAINER) .setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1) .setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)) - .setCustomModifierRewards({ - guaranteedModifierTiers: [ - RewardTier.ROGUE, - RewardTier.ROGUE, - RewardTier.ULTRA, - RewardTier.ULTRA, - RewardTier.ULTRA, - RewardTier.ULTRA, + .setCustomRewards({ + guaranteedRarityTiers: [ + RarityTier.ROGUE, + RarityTier.ROGUE, + RarityTier.ULTRA, + RarityTier.ULTRA, + RarityTier.ULTRA, + RarityTier.ULTRA, ], allowLuckUpgrades: false, }); diff --git a/src/data/data-lists.ts b/src/data/data-lists.ts index 786adb35e5c..85f5a0e6b20 100644 --- a/src/data/data-lists.ts +++ b/src/data/data-lists.ts @@ -3,8 +3,8 @@ import type { PokemonSpecies } from "#data/pokemon-species"; import type { HeldItemId } from "#enums/held-item-id"; import type { TrainerItemId } from "#enums/trainer-item-id"; import type { HeldItem } from "#items/held-item"; +import type { Rewards } from "#items/reward"; import type { TrainerItem } from "#items/trainer-item"; -import type { ModifierTypes } from "#modifiers/modifier-type"; import type { Move } from "#moves/move"; export const allAbilities: Ability[] = []; @@ -15,4 +15,4 @@ export const allHeldItems: Record = {}; export const allTrainerItems: Record = {}; // TODO: Figure out what this is used for and provide an appropriate tsdoc comment -export const modifierTypes = {} as ModifierTypes; +export const allRewards = {} as Rewards; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 476d0455e5e..653fcc1d0e6 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -69,7 +69,7 @@ import { MoveUsedEvent } from "#events/battle-scene"; import type { EnemyPokemon, Pokemon } from "#field/pokemon"; import { applyHeldItems } from "#items/all-held-items"; import { BerryHeldItem, berryTypeToHeldItem } from "#items/berry"; -import { HeldItemEffect } from "#items/held-item"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { TrainerItemEffect } from "#items/trainer-item"; import { applyMoveAttrs } from "#moves/apply-attrs"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSketchMoves, invalidSleepTalkMoves } from "#moves/invalid-moves"; @@ -856,7 +856,7 @@ export abstract class Move implements Localizable { if (!this.hasAttr("TypelessAttr")) { globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power); applyHeldItems(HeldItemEffect.ATTACK_TYPE_BOOST, { - pokemon: source, + pokemon: source, moveType: typeChangeHolder.value, movePower: power, }); @@ -2636,14 +2636,14 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { } const stolenItem = heldItems[user.randBattleSeedInt(heldItems.length)]; - + if (!globalScene.tryTransferHeldItem(stolenItem, target, user, false)) { return false; } - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stoleItem", - { pokemonName: getPokemonNameWithAffix(user), - targetName: getPokemonNameWithAffix(target), + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stoleItem", + { pokemonName: getPokemonNameWithAffix(user), + targetName: getPokemonNameWithAffix(target), itemName: allHeldItems[stolenItem].name } )); @@ -2719,16 +2719,16 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { globalScene.updateItems(target.isPlayer()); if (this.berriesOnly) { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:incineratedItem", + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: allHeldItems[removedItem].name })); } else { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:knockedOffItem", + globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: allHeldItems[removedItem].name })); } return true; } - + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { const heldItems = target.getHeldItems(); return heldItems.length ? 5 : 0; @@ -2775,7 +2775,7 @@ export class EatBerryAttr extends MoveEffectAttr { // check for berry pouch preservation globalScene.applyPlayerItems(TrainerItemEffect.PRESERVE_BERRY, {pokemon: pokemon, doPreserve: preserve}); if (!preserve.value) { - this.reduceBerryModifier(pokemon); + this.reduceBerryItem(pokemon); } // Don't update harvest for berries preserved via Berry pouch (no item dupes lol) @@ -2788,7 +2788,7 @@ export class EatBerryAttr extends MoveEffectAttr { return target.getHeldItems().filter(m => isItemInCategory(m, HeldItemCategoryId.BERRY)); } - reduceBerryModifier(target: Pokemon) { + reduceBerryItem(target: Pokemon) { if (this.chosenBerry) { target.loseHeldItem(this.chosenBerry); } @@ -2850,7 +2850,7 @@ export class StealEatBerryAttr extends EatBerryAttr { applyAbAttrs("PostItemLostAbAttr", {pokemon: target}); const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: allHeldItems[this.chosenBerry].name }); globalScene.phaseManager.queueMessage(message); - this.reduceBerryModifier(target); + this.reduceBerryItem(target); this.eatBerry(user, target); return true; diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index c081c06b890..57a78f5f9d0 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -1,12 +1,12 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import type { IEggOptions } from "#data/egg"; import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; @@ -150,7 +150,7 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder. }, async () => { const encounter = globalScene.currentBattle.mysteryEncounter!; - // Battle the stat trainer for an Egg and great rewards + // Battle the stat trainer for an Egg and great allRewards const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; await transitionMysteryEncounterIntroVisuals(); @@ -164,8 +164,8 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder. encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`)); setEncounterRewards( { - guaranteedModifierTypeFuncs: [modifierTypes.SACRED_ASH], - guaranteedModifierTiers: [RewardTier.ROGUE, RewardTier.ULTRA], + guaranteedRewardFuncs: [allRewards.SACRED_ASH], + guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ULTRA], fillRemaining: true, }, [eggOptions], 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 0053bb6b9ff..0a9a776472e 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 @@ -1,7 +1,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; import { speciesStarterCosts } from "#balance/starters"; -import { allTrainerItems, modifierTypes } from "#data/data-lists"; +import { allRewards, allTrainerItems } from "#data/data-lists"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; @@ -136,7 +136,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB }) .withOptionPhase(async () => { // Give the player a Shiny Charm - globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.SHINY_CHARM); + globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.SHINY_CHARM); leaveEncounterWithoutBattle(true); }) .build(), @@ -184,7 +184,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB ], }, async () => { - // Leave encounter with no rewards or exp + // Leave encounter with no allRewards or exp leaveEncounterWithoutBattle(true); return true; }, diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 8f05b7031ee..ed581c8f544 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -1,22 +1,22 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { RewardPoolType } from "#enums/reward-pool-type"; import { PERMANENT_STATS, Stat } from "#enums/stat"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; import { berryTypeToHeldItem } from "#items/berry"; -import type { ModifierTypeOption } from "#modifiers/modifier-type"; -import { regenerateModifierPoolThresholds } from "#modifiers/modifier-type"; +import type { RewardOption } from "#items/reward"; +import { generateRewardPoolWeights, getRewardPoolForType } from "#items/reward-pool-utils"; +import { generateRewardOption } from "#items/reward-utils"; import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; import { - generateModifierTypeOption, getRandomEncounterSpecies, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, @@ -88,7 +88,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder. : globalScene.currentBattle.waveIndex > 40 ? 4 : 2; - regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0); + generateRewardPoolWeights(getRewardPoolForType(RewardPoolType.PLAYER), globalScene.getPlayerParty(), 0); encounter.misc = { numBerries }; const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon); @@ -159,20 +159,16 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder. } }; - const shopOptions: ModifierTypeOption[] = []; + const shopOptions: RewardOption[] = []; for (let i = 0; i < 5; i++) { // Generate shop berries - const mod = generateModifierTypeOption(modifierTypes.BERRY); + const mod = generateRewardOption(allRewards.BERRY); if (mod) { shopOptions.push(mod); } } - setEncounterRewards( - { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, - undefined, - doBerryRewards, - ); + setEncounterRewards({ guaranteedRewardOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards); await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); }, ) @@ -190,10 +186,10 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder. const speedDiff = fastestPokemon.getStat(Stat.SPD) / (enemySpeed * 1.1); const numBerries: number = encounter.misc.numBerries; - const shopOptions: ModifierTypeOption[] = []; + const shopOptions: RewardOption[] = []; for (let i = 0; i < 5; i++) { // Generate shop berries - const mod = generateModifierTypeOption(modifierTypes.BERRY); + const mod = generateRewardOption(allRewards.BERRY); if (mod) { shopOptions.push(mod); } @@ -246,7 +242,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder. }; setEncounterRewards( { - guaranteedModifierTypeOptions: shopOptions, + guaranteedRewardOptions: shopOptions, fillRemaining: false, }, undefined, @@ -279,7 +275,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder. setEncounterExp(fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp); setEncounterRewards( { - guaranteedModifierTypeOptions: shopOptions, + guaranteedRewardOptions: shopOptions, fillRemaining: false, }, undefined, @@ -301,7 +297,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder. ], }, async () => { - // Leave encounter with no rewards or exp + // Leave encounter with no allRewards or exp leaveEncounterWithoutBattle(true); return true; }, diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index b92891821df..b526be72ea0 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -1,6 +1,6 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { allHeldItems, allMoves, modifierTypes } from "#data/data-lists"; +import { allHeldItems, allMoves, allRewards } from "#data/data-lists"; import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -8,18 +8,18 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { PokemonType } from "#enums/pokemon-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerItemId } from "#enums/trainer-item-id"; import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerType } from "#enums/trainer-type"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; -import type { ModifierTypeOption } from "#modifiers/modifier-type"; +import type { RewardOption } from "#items/reward"; +import { generateRewardOption } from "#items/reward-utils"; import { PokemonMove } from "#moves/pokemon-move"; import { getEncounterText, showEncounterDialogue } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; import { - generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectOptionThenPokemon, @@ -285,7 +285,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde moveTutorOptions, }; - // Assigns callback that teaches move before continuing to rewards + // Assigns callback that teaches move before continuing to allRewards encounter.onRewards = doBugTypeMoveTutor; setEncounterRewards({ fillRemaining: true }); @@ -305,7 +305,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde // Player shows off their bug types const encounter = globalScene.currentBattle.mysteryEncounter!; - // Player gets different rewards depending on the number of bug types they have + // Player gets different allRewards depending on the number of bug types they have const numBugTypes = globalScene.getPlayerParty().filter(p => p.isOfType(PokemonType.BUG, true)).length; const numBugTypesText = i18next.t(`${namespace}:numBugTypes`, { count: numBugTypes, @@ -314,7 +314,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde if (numBugTypes < 2) { setEncounterRewards({ - guaranteedModifierTypeFuncs: [modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL], + guaranteedRewardFuncs: [allRewards.SUPER_LURE, allRewards.GREAT_BALL], fillRemaining: false, }); encounter.selectedOption!.dialogue!.selected = [ @@ -325,7 +325,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde ]; } else if (numBugTypes < 4) { setEncounterRewards({ - guaranteedModifierTypeFuncs: [modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL], + guaranteedRewardFuncs: [allRewards.QUICK_CLAW, allRewards.MAX_LURE, allRewards.ULTRA_BALL], fillRemaining: false, }); encounter.selectedOption!.dialogue!.selected = [ @@ -336,7 +336,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde ]; } else if (numBugTypes < 6) { setEncounterRewards({ - guaranteedModifierTypeFuncs: [modifierTypes.GRIP_CLAW, modifierTypes.MAX_LURE, modifierTypes.ROGUE_BALL], + guaranteedRewardFuncs: [allRewards.GRIP_CLAW, allRewards.MAX_LURE, allRewards.ROGUE_BALL], fillRemaining: false, }); encounter.selectedOption!.dialogue!.selected = [ @@ -348,38 +348,38 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde } else { // If the player has any evolution/form change items that are valid for their party, // spawn one of those items in addition to Dynamax Band, Mega Band, and Master Ball - const modifierOptions: ModifierTypeOption[] = [generateModifierTypeOption(modifierTypes.MASTER_BALL)!]; - const specialOptions: ModifierTypeOption[] = []; + const rewardOptions: RewardOption[] = [generateRewardOption(allRewards.MASTER_BALL)!]; + const specialOptions: RewardOption[] = []; if (!globalScene.trainerItems.hasItem(TrainerItemId.MEGA_BRACELET)) { - modifierOptions.push(generateModifierTypeOption(modifierTypes.MEGA_BRACELET)!); + rewardOptions.push(generateRewardOption(allRewards.MEGA_BRACELET)!); } if (!globalScene.trainerItems.hasItem(TrainerItemId.DYNAMAX_BAND)) { - modifierOptions.push(generateModifierTypeOption(modifierTypes.DYNAMAX_BAND)!); + rewardOptions.push(generateRewardOption(allRewards.DYNAMAX_BAND)!); } - const nonRareEvolutionModifier = generateModifierTypeOption(modifierTypes.EVOLUTION_ITEM); - if (nonRareEvolutionModifier) { - specialOptions.push(nonRareEvolutionModifier); + const nonRareEvolutionReward = generateRewardOption(allRewards.EVOLUTION_ITEM); + if (nonRareEvolutionReward) { + specialOptions.push(nonRareEvolutionReward); } - const rareEvolutionModifier = generateModifierTypeOption(modifierTypes.RARE_EVOLUTION_ITEM); - if (rareEvolutionModifier) { - specialOptions.push(rareEvolutionModifier); + const rareEvolutionReward = generateRewardOption(allRewards.RARE_EVOLUTION_ITEM); + if (rareEvolutionReward) { + specialOptions.push(rareEvolutionReward); } - const formChangeModifier = generateModifierTypeOption(modifierTypes.FORM_CHANGE_ITEM); - if (formChangeModifier) { - specialOptions.push(formChangeModifier); + const formChangeReward = generateRewardOption(allRewards.FORM_CHANGE_ITEM); + if (formChangeReward) { + specialOptions.push(formChangeReward); } - const rareFormChangeModifier = generateModifierTypeOption(modifierTypes.RARE_FORM_CHANGE_ITEM); - if (rareFormChangeModifier) { - specialOptions.push(rareFormChangeModifier); + const rareFormChangeReward = generateRewardOption(allRewards.RARE_FORM_CHANGE_ITEM); + if (rareFormChangeReward) { + specialOptions.push(rareFormChangeReward); } if (specialOptions.length > 0) { // TODO: should this use `randSeedItem`? - modifierOptions.push(specialOptions[randSeedInt(specialOptions.length)]); + rewardOptions.push(specialOptions[randSeedInt(specialOptions.length)]); } setEncounterRewards({ - guaranteedModifierTypeOptions: modifierOptions, + guaranteedRewardOptions: rewardOptions, fillRemaining: false, }); encounter.selectedOption!.dialogue!.selected = [ @@ -465,12 +465,12 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde chosenPokemon.loseHeldItem(lostItem, false); globalScene.updateItems(true); - const bugNet = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!; - bugNet.type.tier = RewardTier.ROGUE; + const bugNet = generateRewardOption(allRewards.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!; + bugNet.type.tier = RarityTier.ROGUE; setEncounterRewards({ - guaranteedModifierTypeOptions: [bugNet], - guaranteedModifierTypeFuncs: [modifierTypes.REVIVER_SEED], + guaranteedRewardOptions: [bugNet], + guaranteedRewardFuncs: [allRewards.REVIVER_SEED], fillRemaining: false, }); leaveEncounterWithoutBattle(true); @@ -744,7 +744,7 @@ function doBugTypeMoveTutor(): Promise { ); } - // Complete battle and go to rewards + // Complete battle and go to allRewards resolve(); }); } diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index b17f70d150f..de9e9b0f3b1 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -16,13 +16,13 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { PokemonType } from "#enums/pokemon-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon } from "#field/pokemon"; +import { getHeldItemTier } from "#items/held-item-default-tiers"; import { assignItemsFromConfiguration } from "#items/held-item-pool"; -import { getHeldItemTier } from "#items/held-item-tiers"; import { PokemonMove } from "#moves/pokemon-move"; import { showEncounterDialogue, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; @@ -329,11 +329,11 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder let numRogue = 0; for (const m of items) { - const tier = getHeldItemTier(m) ?? RewardTier.ULTRA; + const tier = getHeldItemTier(m) ?? RarityTier.ULTRA; const stack = mostHeldItemsPokemon.heldItemManager.getStack(m); - if (tier === RewardTier.ROGUE) { + if (tier === RarityTier.ROGUE) { numRogue += stack; - } else if (tier === RewardTier.ULTRA) { + } else if (tier === RarityTier.ULTRA) { numUltra += stack; } mostHeldItemsPokemon.heldItemManager.remove(m, stack); diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index febe171c8b8..56b21490f29 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -1,7 +1,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; import { EncounterBattleAnim } from "#data/battle-anims"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BiomeId } from "#enums/biome-id"; @@ -219,7 +219,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder await hideOricorioPokemon(); setEncounterRewards({ - guaranteedModifierTypeFuncs: [modifierTypes.BATON], + guaranteedRewardFuncs: [allRewards.BATON], fillRemaining: true, }); await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 2d6d0deeed0..aede0c92168 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -1,6 +1,6 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { Challenges } from "#enums/challenges"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -160,7 +160,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE .withOptionPhase(async () => { // Give the player 5 Rogue Balls const encounter = globalScene.currentBattle.mysteryEncounter!; - globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.ROGUE_BALL); + globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.ROGUE_BALL); // Start encounter with random legendary (7-10 starter strength) that has level additive // If this is a mono-type challenge, always ensure the required type is filtered for @@ -204,7 +204,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE ], }, async () => { - // Leave encounter with no rewards or exp + // Leave encounter with no allRewards or exp leaveEncounterWithoutBattle(true); return true; }, diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 8508b4644af..d0cd73f1bc6 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -1,7 +1,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { timedEventManager } from "#app/global-event-manager"; import { globalScene } from "#app/global-scene"; -import { allHeldItems, modifierTypes } from "#data/data-lists"; +import { allHeldItems, allRewards } from "#data/data-lists"; import { HeldItemCategoryId, HeldItemId, isItemInCategory } from "#enums/held-item-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -34,7 +34,7 @@ const namespace = "mysteryEncounters/delibirdy"; /** Berries only */ const OPTION_2_ALLOWED_HELD_ITEMS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED]; -/** Disallowed items are berries, Reviver Seeds, and Vitamins (form change items and fusion items are not PokemonHeldItemModifiers) */ +/** Disallowed items are berries, Reviver Seeds, and Vitamins */ const OPTION_3_DISALLOWED_HELD_ITEMS = [HeldItemCategoryId.BERRY, HeldItemId.REVIVER_SEED]; const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2; @@ -61,10 +61,10 @@ const doEventReward = () => { return !fullStack; }); if (candidates.length > 0) { - globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes[randSeedItem(candidates)]); + globalScene.phaseManager.unshiftNew("RewardPhase", allRewards[randSeedItem(candidates)]); } else { // At max stacks, give a Voucher instead - globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.VOUCHER); + globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.VOUCHER); } } }; @@ -168,7 +168,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with // At max stacks, give the first party pokemon a Shell Bell instead backupOption(); } else { - globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.AMULET_COIN); + globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.AMULET_COIN); doEventReward(); } @@ -238,7 +238,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with // At max stacks, give the first party pokemon a Shell Bell instead backupOption(); } else { - globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.CANDY_JAR); + globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.CANDY_JAR); doEventReward(); } } else { @@ -249,7 +249,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with // At max stacks, give the first party pokemon a Shell Bell instead backupOption(); } else { - globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.BERRY_POUCH); + globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.BERRY_POUCH); doEventReward(); } } @@ -321,7 +321,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with // At max stacks, give the first party pokemon a Shell Bell instead backupOption(); } else { - globalScene.phaseManager.unshiftNew("RewardPhase", modifierTypes.HEALING_CHARM); + globalScene.phaseManager.unshiftNew("RewardPhase", allRewards.HEALING_CHARM); doEventReward(); } diff --git a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts index a45c5301a3e..28de326cf46 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -1,12 +1,12 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { leaveEncounterWithoutBattle, setEncounterRewards } from "#mystery-encounters/encounter-phase-utils"; import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; -import type { ModifierTypeFunc } from "#types/modifier-types"; +import type { RewardFunc } from "#types/rewards"; import { randSeedInt } from "#utils/common"; /** i18n namespace for encounter */ @@ -59,23 +59,23 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu }, async () => { // Choose TMs - const modifiers: ModifierTypeFunc[] = []; + const rewards: RewardFunc[] = []; let i = 0; while (i < 5) { // 2/2/1 weight on TM rarity const roll = randSeedInt(5); if (roll < 2) { - modifiers.push(modifierTypes.TM_COMMON); + rewards.push(allRewards.TM_COMMON); } else if (roll < 4) { - modifiers.push(modifierTypes.TM_GREAT); + rewards.push(allRewards.TM_GREAT); } else { - modifiers.push(modifierTypes.TM_ULTRA); + rewards.push(allRewards.TM_ULTRA); } i++; } setEncounterRewards({ - guaranteedModifierTypeFuncs: modifiers, + guaranteedRewardFuncs: rewards, fillRemaining: false, }); leaveEncounterWithoutBattle(); @@ -88,21 +88,21 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu }, async () => { // Choose Vitamins - const modifiers: ModifierTypeFunc[] = []; + const rewards: RewardFunc[] = []; let i = 0; while (i < 3) { // 2/1 weight on base stat booster vs PP Up const roll = randSeedInt(3); if (roll === 0) { - modifiers.push(modifierTypes.PP_UP); + rewards.push(allRewards.PP_UP); } else { - modifiers.push(modifierTypes.BASE_STAT_BOOSTER); + rewards.push(allRewards.BASE_STAT_BOOSTER); } i++; } setEncounterRewards({ - guaranteedModifierTypeFuncs: modifiers, + guaranteedRewardFuncs: rewards, fillRemaining: false, }); leaveEncounterWithoutBattle(); @@ -115,21 +115,21 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu }, async () => { // Choose X Items - const modifiers: ModifierTypeFunc[] = []; + const rewards: RewardFunc[] = []; let i = 0; while (i < 5) { // 4/1 weight on base stat booster vs Dire Hit const roll = randSeedInt(5); if (roll === 0) { - modifiers.push(modifierTypes.DIRE_HIT); + rewards.push(allRewards.DIRE_HIT); } else { - modifiers.push(modifierTypes.TEMP_STAT_STAGE_BOOSTER); + rewards.push(allRewards.TEMP_STAT_STAGE_BOOSTER); } i++; } setEncounterRewards({ - guaranteedModifierTypeFuncs: modifiers, + guaranteedRewardFuncs: rewards, fillRemaining: false, }); leaveEncounterWithoutBattle(); @@ -142,25 +142,25 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu }, async () => { // Choose Pokeballs - const modifiers: ModifierTypeFunc[] = []; + const rewards: RewardFunc[] = []; let i = 0; while (i < 4) { // 10/30/20/5 weight on pokeballs const roll = randSeedInt(65); if (roll < 10) { - modifiers.push(modifierTypes.POKEBALL); + rewards.push(allRewards.POKEBALL); } else if (roll < 40) { - modifiers.push(modifierTypes.GREAT_BALL); + rewards.push(allRewards.GREAT_BALL); } else if (roll < 60) { - modifiers.push(modifierTypes.ULTRA_BALL); + rewards.push(allRewards.ULTRA_BALL); } else { - modifiers.push(modifierTypes.ROGUE_BALL); + rewards.push(allRewards.ROGUE_BALL); } i++; } setEncounterRewards({ - guaranteedModifierTypeFuncs: modifiers, + guaranteedRewardFuncs: rewards, fillRemaining: false, }); leaveEncounterWithoutBattle(); diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index 9c655e70b8c..35485fba083 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -1,15 +1,15 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { MoveCategory } from "#enums/move-category"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Stat } from "#enums/stat"; import type { PlayerPokemon } from "#field/pokemon"; +import { generateRewardOption } from "#items/reward-utils"; import type { PokemonMove } from "#moves/pokemon-move"; import { - generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, @@ -96,15 +96,15 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with const encounter = globalScene.currentBattle.mysteryEncounter!; if (encounter.misc.correctMove) { const modifiers = [ - generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.ATK])!, - generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.DEF])!, - generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!, - generateModifierTypeOption(modifierTypes.DIRE_HIT)!, - generateModifierTypeOption(modifierTypes.RARER_CANDY)!, + generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.ATK])!, + generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.DEF])!, + generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!, + generateRewardOption(allRewards.DIRE_HIT)!, + generateRewardOption(allRewards.RARER_CANDY)!, ]; setEncounterRewards({ - guaranteedModifierTypeOptions: modifiers, + guaranteedRewardOptions: modifiers, fillRemaining: false, }); } @@ -144,15 +144,15 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with const encounter = globalScene.currentBattle.mysteryEncounter!; if (encounter.misc.correctMove) { const modifiers = [ - generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPATK])!, - generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPDEF])!, - generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!, - generateModifierTypeOption(modifierTypes.DIRE_HIT)!, - generateModifierTypeOption(modifierTypes.RARER_CANDY)!, + generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.SPATK])!, + generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.SPDEF])!, + generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!, + generateRewardOption(allRewards.DIRE_HIT)!, + generateRewardOption(allRewards.RARER_CANDY)!, ]; setEncounterRewards({ - guaranteedModifierTypeOptions: modifiers, + guaranteedRewardOptions: modifiers, fillRemaining: false, }); } @@ -192,15 +192,15 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with const encounter = globalScene.currentBattle.mysteryEncounter!; if (encounter.misc.correctMove) { const modifiers = [ - generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.ACC])!, - generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!, - generateModifierTypeOption(modifierTypes.GREAT_BALL)!, - generateModifierTypeOption(modifierTypes.IV_SCANNER)!, - generateModifierTypeOption(modifierTypes.RARER_CANDY)!, + generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.ACC])!, + generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!, + generateRewardOption(allRewards.GREAT_BALL)!, + generateRewardOption(allRewards.IV_SCANNER)!, + generateRewardOption(allRewards.RARER_CANDY)!, ]; setEncounterRewards({ - guaranteedModifierTypeOptions: modifiers, + guaranteedRewardOptions: modifiers, fillRemaining: false, }); } diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 94422518b25..a3d1da01351 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -250,7 +250,7 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w } } - // No rewards + // No allRewards leaveEncounterWithoutBattle(true); }, ) diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index 49848918bd7..adc614fb9d6 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -1,14 +1,16 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RewardPoolType } from "#enums/reward-pool-type"; +import { RarityTier } from "#enums/reward-tier"; +import { TrainerItemId } from "#enums/trainer-item-id"; import type { Pokemon } from "#field/pokemon"; -import type { ModifierTypeOption } from "#modifiers/modifier-type"; -import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#modifiers/modifier-type"; +import type { RewardOption, TrainerItemReward } from "#items/reward"; +import { generatePlayerRewardOptions, generateRewardPoolWeights, getRewardPoolForType } from "#items/reward-pool-utils"; +import { isTmReward } from "#items/reward-utils"; import { queueEncounterMessage } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; import { @@ -89,18 +91,18 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder. // Waves 10-40 GREAT, 60-120 ULTRA, 120-160 ROGUE, 160-180 MASTER const tier = globalScene.currentBattle.waveIndex > 160 - ? RewardTier.MASTER + ? RarityTier.MASTER : globalScene.currentBattle.waveIndex > 120 - ? RewardTier.ROGUE + ? RarityTier.ROGUE : globalScene.currentBattle.waveIndex > 40 - ? RewardTier.ULTRA - : RewardTier.GREAT; - regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0); - let item: ModifierTypeOption | null = null; - // TMs and Candy Jar excluded from possible rewards as they're too swingy in value for a singular item reward - while (!item || item.type.id.includes("TM_") || item.type.id === "CANDY_JAR") { - item = getPlayerModifierTypeOptions(1, globalScene.getPlayerParty(), [], { - guaranteedModifierTiers: [tier], + ? RarityTier.ULTRA + : RarityTier.GREAT; + generateRewardPoolWeights(getRewardPoolForType(RewardPoolType.PLAYER), globalScene.getPlayerParty(), 0); + let item: RewardOption | null = null; + // TMs and Candy Jar excluded from possible allRewards as they're too swingy in value for a singular item reward + while (!item || isTmReward(item.type) || (item.type as TrainerItemReward).itemId === TrainerItemId.CANDY_JAR) { + item = generatePlayerRewardOptions(1, globalScene.getPlayerParty(), [], { + guaranteedRarityTiers: [tier], allowLuckUpgrades: false, })[0]; } @@ -151,9 +153,9 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder. async () => { // Pick battle // Pokemon will randomly boost 1 stat by 2 stages - const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption; + const item = globalScene.currentBattle.mysteryEncounter!.misc as RewardOption; setEncounterRewards({ - guaranteedModifierTypeOptions: [item], + guaranteedRewardOptions: [item], fillRemaining: false, }); await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); @@ -175,9 +177,9 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder. .withOptionPhase(async () => { // Pick steal const encounter = globalScene.currentBattle.mysteryEncounter!; - const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption; + const item = globalScene.currentBattle.mysteryEncounter!.misc as RewardOption; setEncounterRewards({ - guaranteedModifierTypeOptions: [item], + guaranteedRewardOptions: [item], fillRemaining: false, }); @@ -199,7 +201,7 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder. ], }, async () => { - // Leave encounter with no rewards or exp + // Leave encounter with no allRewards or exp leaveEncounterWithoutBattle(true); return true; }, diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 6aee41bc91b..8fb63b16085 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -1,7 +1,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { SpeciesFormChangeActiveTrigger } from "#data/form-change-triggers"; import { getPokeballAtlasKey, getPokeballTintColor } from "#data/pokeball"; import { FieldPosition } from "#enums/field-position"; @@ -160,7 +160,7 @@ export const FunAndGamesEncounter: MysteryEncounter = MysteryEncounterBuilder.wi ], }, async () => { - // Leave encounter with no rewards or exp + // Leave encounter with no allRewards or exp await transitionMysteryEncounterIntroVisuals(true, true); leaveEncounterWithoutBattle(true); return true; @@ -281,21 +281,21 @@ function handleNextTurn() { if (healthRatio < 0.03) { // Grand prize setEncounterRewards({ - guaranteedModifierTypeFuncs: [modifierTypes.MULTI_LENS], + guaranteedRewardFuncs: [allRewards.MULTI_LENS], fillRemaining: false, }); resultMessageKey = `${namespace}:best_result`; } else if (healthRatio < 0.15) { // 2nd prize setEncounterRewards({ - guaranteedModifierTypeFuncs: [modifierTypes.SCOPE_LENS], + guaranteedRewardFuncs: [allRewards.SCOPE_LENS], fillRemaining: false, }); resultMessageKey = `${namespace}:great_result`; } else if (healthRatio < 0.33) { // 3rd prize setEncounterRewards({ - guaranteedModifierTypeFuncs: [modifierTypes.WIDE_LENS], + guaranteedRewardFuncs: [allRewards.WIDE_LENS], fillRemaining: false, }); resultMessageKey = `${namespace}:good_result`; diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 0cccba42a49..c296bd36f73 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -8,22 +8,23 @@ import { getPokeballAtlasKey, getPokeballTintColor } from "#data/pokeball"; import type { PokemonSpecies } from "#data/pokemon-species"; import { getTypeRgb } from "#data/type"; import { HeldItemCategoryId, type HeldItemId, isItemInCategory } from "#enums/held-item-id"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { PokeballType } from "#enums/pokeball"; -import { RewardTier } from "#enums/reward-tier"; +import { RewardPoolType } from "#enums/reward-pool-type"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerType } from "#enums/trainer-type"; import { doShinySparkleAnim } from "#field/anims"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; import { EnemyPokemon } from "#field/pokemon"; -import { getHeldItemTier } from "#items/held-item-tiers"; +import { getHeldItemTier } from "#items/held-item-default-tiers"; +import type { RewardOption } from "#items/reward"; +import { generatePlayerRewardOptions, generateRewardPoolWeights, getRewardPoolForType } from "#items/reward-pool-utils"; +import { isTmReward } from "#items/reward-utils"; import { TrainerItemEffect } from "#items/trainer-item"; -import type { ModifierTypeOption } from "#modifiers/modifier-type"; -import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#modifiers/modifier-type"; import { PokemonMove } from "#moves/pokemon-move"; import { getEncounterText, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import { @@ -413,26 +414,26 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Check tier of the traded item, the received item will be one tier up - let tier = getHeldItemTier(heldItemId) ?? RewardTier.GREAT; + let tier = getHeldItemTier(heldItemId) ?? RarityTier.GREAT; // Increment tier by 1 - if (tier < RewardTier.MASTER) { + if (tier < RarityTier.MASTER) { tier++; } - regenerateModifierPoolThresholds(party, ModifierPoolType.PLAYER, 0); - let item: ModifierTypeOption | null = null; - // TMs excluded from possible rewards - while (!item || item.type.id.includes("TM_")) { - item = getPlayerModifierTypeOptions(1, party, [], { - guaranteedModifierTiers: [tier], + generateRewardPoolWeights(getRewardPoolForType(RewardPoolType.PLAYER), party, 0); + let item: RewardOption | null = null; + // TMs excluded from possible allRewards + while (!item || isTmReward(item.type)) { + item = generatePlayerRewardOptions(1, party, [], { + guaranteedRarityTiers: [tier], allowLuckUpgrades: false, })[0]; } encounter.setDialogueToken("itemName", item.type.name); setEncounterRewards({ - guaranteedModifierTypeOptions: [item], + guaranteedRewardOptions: [item], fillRemaining: false, }); @@ -458,7 +459,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil ], }, async () => { - // Leave encounter with no rewards or exp + // Leave encounter with no allRewards or exp leaveEncounterWithoutBattle(true); return true; }, diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index d8dddf5d8b5..ec26298bf93 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -1,10 +1,10 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; import { initBattleWithEnemyConfig, setEncounterRewards } from "#mystery-encounters/encounter-phase-utils"; import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; @@ -147,7 +147,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; setEncounterRewards({ - guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM], + guaranteedRewardFuncs: [allRewards.TM_COMMON, allRewards.TM_GREAT, allRewards.MEMORY_MUSHROOM], fillRemaining: true, }); @@ -175,7 +175,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1]; setEncounterRewards({ - guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.GREAT, RewardTier.GREAT], + guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.GREAT, RarityTier.GREAT], fillRemaining: true, }); @@ -206,7 +206,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter encounter.expMultiplier = 0.9; setEncounterRewards({ - guaranteedModifierTiers: [RewardTier.ROGUE, RewardTier.ROGUE, RewardTier.ULTRA, RewardTier.GREAT], + guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ULTRA, RarityTier.GREAT], fillRemaining: true, }); diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index a205b67dc00..ae526b6b4a3 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -4,7 +4,7 @@ import { MoveId } from "#enums/move-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; @@ -141,25 +141,25 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) { // Choose between 2 COMMON / 2 GREAT tier items (20%) setEncounterRewards({ - guaranteedModifierTiers: [RewardTier.COMMON, RewardTier.COMMON, RewardTier.GREAT, RewardTier.GREAT], + guaranteedRarityTiers: [RarityTier.COMMON, RarityTier.COMMON, RarityTier.GREAT, RarityTier.GREAT], }); - // Display result message then proceed to rewards + // Display result message then proceed to allRewards queueEncounterMessage(`${namespace}:option.1.normal`); leaveEncounterWithoutBattle(); } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) { // Choose between 3 ULTRA tier items (30%) setEncounterRewards({ - guaranteedModifierTiers: [RewardTier.ULTRA, RewardTier.ULTRA, RewardTier.ULTRA], + guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.ULTRA], }); - // Display result message then proceed to rewards + // Display result message then proceed to allRewards queueEncounterMessage(`${namespace}:option.1.good`); leaveEncounterWithoutBattle(); } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) { // Choose between 2 ROGUE tier items (10%) setEncounterRewards({ - guaranteedModifierTiers: [RewardTier.ROGUE, RewardTier.ROGUE], + guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE], }); - // Display result message then proceed to rewards + // Display result message then proceed to allRewards queueEncounterMessage(`${namespace}:option.1.great`); leaveEncounterWithoutBattle(); } else if ( @@ -168,9 +168,9 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde ) { // Choose 1 MASTER tier item (5%) setEncounterRewards({ - guaranteedModifierTiers: [RewardTier.MASTER], + guaranteedRarityTiers: [RarityTier.MASTER], }); - // Display result message then proceed to rewards + // Display result message then proceed to allRewards queueEncounterMessage(`${namespace}:option.1.amazing`); leaveEncounterWithoutBattle(); } else { @@ -208,7 +208,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde ], }, async () => { - // Leave encounter with no rewards or exp + // Leave encounter with no allRewards or exp leaveEncounterWithoutBattle(true); return true; }, diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index 7da0adcb106..fe8254804ee 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -125,7 +125,7 @@ export const SafariZoneEncounter: MysteryEncounter = MysteryEncounterBuilder.wit ], }, async () => { - // Leave encounter with no rewards or exp + // Leave encounter with no allRewards or exp leaveEncounterWithoutBattle(true); return true; }, diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index 7d052eaadd7..4c5a7e7a222 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -227,7 +227,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui ], }, async () => { - // Leave encounter with no rewards or exp + // Leave encounter with no allRewards or exp leaveEncounterWithoutBattle(true); return true; }, diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 04a16c9178e..e3d55b6724b 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { CustomPokemonData } from "#data/pokemon-data"; import { AiType } from "#enums/ai-type"; import { BattlerIndex } from "#enums/battler-index"; @@ -116,7 +116,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil // Pick battle const encounter = globalScene.currentBattle.mysteryEncounter!; setEncounterRewards({ - guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS], + guaranteedRewardFuncs: [allRewards.LEFTOVERS], fillRemaining: true, }); encounter.startOfBattleEffects.push({ @@ -163,7 +163,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil // Steal the Snorlax's Leftovers const instance = globalScene.currentBattle.mysteryEncounter!; setEncounterRewards({ - guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS], + guaranteedRewardFuncs: [allRewards.LEFTOVERS], fillRemaining: false, }); // Snorlax exp to Pokemon that did the stealing diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index b547064fd66..6ee1e198b39 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -1,7 +1,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BiomeId } from "#enums/biome-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -13,11 +13,10 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { getBiomeKey } from "#field/arena"; import type { Pokemon } from "#field/pokemon"; import { EnemyPokemon } from "#field/pokemon"; -import { getPartyLuckValue } from "#modifiers/modifier-type"; +import { generateRewardOption } from "#items/reward-utils"; import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; import { - generateModifierTypeOption, initBattleWithEnemyConfig, setEncounterExp, setEncounterRewards, @@ -34,6 +33,7 @@ import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encou import { MoneyRequirement, WaveModulusRequirement } from "#mystery-encounters/mystery-encounter-requirements"; import { PokemonData } from "#system/pokemon-data"; import { randSeedInt } from "#utils/common"; +import { getPartyLuckValue } from "#utils/party"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/teleportingHijinks"; @@ -173,10 +173,10 @@ export const TeleportingHijinksEncounter: MysteryEncounter = MysteryEncounterBui ], }; - const magnet = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.STEEL])!; - const metalCoat = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.ELECTRIC])!; + const magnet = generateRewardOption(allRewards.ATTACK_TYPE_BOOSTER, [PokemonType.STEEL])!; + const metalCoat = generateRewardOption(allRewards.ATTACK_TYPE_BOOSTER, [PokemonType.ELECTRIC])!; setEncounterRewards({ - guaranteedModifierTypeOptions: [magnet, metalCoat], + guaranteedRewardOptions: [magnet, metalCoat], fillRemaining: true, }); await transitionMysteryEncounterIntroVisuals(true, true); 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 cf5cf86eed1..4eb63d94af0 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 @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import { speciesStarterCosts } from "#balance/starters"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import type { IEggOptions } from "#data/egg"; import { getPokeballTintColor } from "#data/pokeball"; import { BiomeId } from "#enums/biome-id"; @@ -294,7 +294,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount const eggOptions = getEggOptions(pokemon1CommonEggs, pokemon1RareEggs); setEncounterRewards( { - guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL], + guaranteedRewardFuncs: [allRewards.SOOTHE_BELL], fillRemaining: true, }, eggOptions, @@ -304,7 +304,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(encounter, pokemon1); - // Configure outro dialogue for egg rewards + // Configure outro dialogue for egg allRewards encounter.dialogue.outro = [ { speaker: trainerNameKey, @@ -353,7 +353,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount const eggOptions = getEggOptions(pokemon2CommonEggs, pokemon2RareEggs); setEncounterRewards( { - guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL], + guaranteedRewardFuncs: [allRewards.SOOTHE_BELL], fillRemaining: true, }, eggOptions, @@ -363,7 +363,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(encounter, pokemon2); - // Configure outro dialogue for egg rewards + // Configure outro dialogue for egg allRewards encounter.dialogue.outro = [ { speaker: trainerNameKey, @@ -412,7 +412,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount const eggOptions = getEggOptions(pokemon3CommonEggs, pokemon3RareEggs); setEncounterRewards( { - guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL], + guaranteedRewardFuncs: [allRewards.SOOTHE_BELL], fillRemaining: true, }, eggOptions, @@ -422,7 +422,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(encounter, pokemon3); - // Configure outro dialogue for egg rewards + // Configure outro dialogue for egg allRewards encounter.dialogue.outro = [ { speaker: trainerNameKey, @@ -640,7 +640,7 @@ function onGameOver() { const chosenPokemon = encounter.misc.chosenPokemon; chosenPokemon.friendship = 0; - // Clear all rewards that would have been earned + // Clear all allRewards that would have been earned encounter.doEncounterRewards = undefined; // Set flag that encounter was failed 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 91662993a51..539e1a3670b 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -238,7 +238,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui ], }, async () => { - // Leave encounter with no rewards or exp + // Leave encounter with no allRewards or exp leaveEncounterWithoutBattle(true); return true; }, diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index f159873b89a..fd1e6268132 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -1,6 +1,6 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { CustomPokemonData } from "#data/pokemon-data"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -193,7 +193,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder // Pick battle const encounter = globalScene.currentBattle.mysteryEncounter!; setEncounterRewards({ - guaranteedModifierTypeFuncs: [modifierTypes.SOUL_DEW], + guaranteedRewardFuncs: [allRewards.SOUL_DEW], fillRemaining: true, }); encounter.startOfBattleEffects.push( diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 2b85cfde356..8c2e422aa35 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -1,7 +1,7 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { SpeciesFormChangeAbilityTrigger } from "#data/form-change-triggers"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -11,13 +11,13 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Nature } from "#enums/nature"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; +import { generateRewardOption } from "#items/reward-utils"; import { showEncounterDialogue, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; import { - generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, @@ -117,7 +117,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounter ], }, async () => { - // Spawn 5 trainer battles back to back with Macho Brace in rewards + // Spawn 5 trainer battles back to back with Macho Brace in allRewards globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = async () => { await endTrainerBattleAndShowDialogue(); }; @@ -140,7 +140,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounter // Refuse the challenge, they full heal the party and give the player a Rarer Candy globalScene.phaseManager.unshiftNew("PartyHealPhase", true); setEncounterRewards({ - guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY], + guaranteedRewardFuncs: [allRewards.RARER_CANDY], fillRemaining: false, }); leaveEncounterWithoutBattle(); @@ -156,17 +156,17 @@ async function spawnNextTrainerOrEndEncounter() { await showEncounterDialogue(`${namespace}:victory`, `${namespace}:speaker`); // Give 10x Voucher - const newModifier = modifierTypes.VOUCHER_PREMIUM().newModifier(); - globalScene.addModifier(newModifier); + const reward = allRewards.VOUCHER_PREMIUM(); + globalScene.applyReward(reward, {}); globalScene.playSound("item_fanfare"); - await showEncounterText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name })); + await showEncounterText(i18next.t("battle:rewardGain", { modifierName: reward.name })); await showEncounterDialogue(`${namespace}:victory_2`, `${namespace}:speaker`); - globalScene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in - const machoBrace = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!; - machoBrace.type.tier = RewardTier.MASTER; + globalScene.ui.clearText(); // Clears "Winstrate" title from screen as allRewards get animated in + const machoBrace = generateRewardOption(allRewards.MYSTERY_ENCOUNTER_MACHO_BRACE)!; + machoBrace.type.tier = RarityTier.MASTER; setEncounterRewards({ - guaranteedModifierTypeOptions: [machoBrace], + guaranteedRewardOptions: [machoBrace], fillRemaining: false, }); encounter.doContinueEncounter = undefined; diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 4a0db1614e0..af3766e08ae 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -346,7 +346,7 @@ export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilde ], }, async () => { - // Leave encounter with no rewards or exp + // Leave encounter with no allRewards or exp leaveEncounterWithoutBattle(true); return true; }, diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index e752ba09ba7..c0b87645219 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -8,7 +8,7 @@ import { MoveUseMode } from "#enums/move-use-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerItemId } from "#enums/trainer-item-id"; import { assignItemToFirstFreePokemon } from "#items/item-utility"; @@ -167,7 +167,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde const encounter = globalScene.currentBattle.mysteryEncounter!; setEncounterRewards({ - guaranteedModifierTiers: [RewardTier.ROGUE, RewardTier.ROGUE, RewardTier.ULTRA, RewardTier.GREAT], + guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ULTRA, RarityTier.GREAT], fillRemaining: true, }); encounter.startOfBattleEffects.push( diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 8f43abe8fa9..9876b47696c 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { allSpecies, modifierTypes } from "#data/data-lists"; +import { allRewards, allSpecies } from "#data/data-lists"; import { getLevelTotalExp } from "#data/exp"; import type { PokemonSpecies } from "#data/pokemon-species"; import { Challenges } from "#enums/challenges"; @@ -11,7 +11,7 @@ import { Nature } from "#enums/nature"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { PlayerGender } from "#enums/player-gender"; import { PokemonType } from "#enums/pokemon-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; @@ -218,12 +218,12 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit await doNewTeamPostProcess(transformations); setEncounterRewards({ - guaranteedModifierTypeFuncs: [ - modifierTypes.MEMORY_MUSHROOM, - modifierTypes.ROGUE_BALL, - modifierTypes.MINT, - modifierTypes.MINT, - modifierTypes.MINT, + guaranteedRewardFuncs: [ + allRewards.MEMORY_MUSHROOM, + allRewards.ROGUE_BALL, + allRewards.MINT, + allRewards.MINT, + allRewards.MINT, ], fillRemaining: false, }); @@ -242,7 +242,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit ], }, async () => { - // Battle your "future" team for some item rewards + // Battle your "future" team for some item allRewards const transformations: PokemonTransformation[] = globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations; @@ -293,7 +293,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit }; const onBeforeRewards = () => { - // Before battle rewards, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently) + // Before battle allRewards, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently) // One random pokemon will get its passive unlocked const passiveDisabledPokemon = globalScene.getPlayerParty().filter(p => !p.passive); if (passiveDisabledPokemon?.length > 0) { @@ -306,13 +306,13 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit setEncounterRewards( { - guaranteedModifierTiers: [ - RewardTier.ROGUE, - RewardTier.ROGUE, - RewardTier.ULTRA, - RewardTier.ULTRA, - RewardTier.GREAT, - RewardTier.GREAT, + guaranteedRarityTiers: [ + RarityTier.ROGUE, + RarityTier.ROGUE, + RarityTier.ULTRA, + RarityTier.ULTRA, + RarityTier.GREAT, + RarityTier.GREAT, ], fillRemaining: false, }, diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index a2ca2b20ce7..a3cd81db201 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -173,11 +173,11 @@ export class MysteryEncounter implements IMysteryEncounter { onVisualsStart?: () => boolean; /** Event triggered prior to {@linkcode CommandPhase}, during {@linkcode TurnInitPhase} */ onTurnStart?: () => boolean; - /** Event prior to any rewards logic in {@linkcode MysteryEncounterRewardsPhase} */ + /** Event prior to any allRewards logic in {@linkcode MysteryEncounterRewardsPhase} */ onRewards?: () => Promise; - /** Will provide the player party EXP before rewards are displayed for that wave */ + /** Will provide the player party EXP before allRewards are displayed for that wave */ doEncounterExp?: () => boolean; - /** Will provide the player a rewards shop for that wave */ + /** Will provide the player a allRewards shop for that wave */ doEncounterRewards?: () => boolean; /** Will execute callback during VictoryPhase of a continuousEncounter */ doContinueEncounter?: () => Promise; @@ -237,10 +237,10 @@ export class MysteryEncounter implements IMysteryEncounter { encounterMode: MysteryEncounterMode; /** * Flag for checking if it's the first time a shop is being shown for an encounter. - * Defaults to true so that the first shop does not override the specified rewards. + * Defaults to true so that the first shop does not override the specified allRewards. * Will be set to false after a shop is shown (so can't reroll same rarity items for free) */ - lockEncounterRewardTiers: boolean; + lockEncounterRarityTiers: boolean; /** * Will be set automatically, indicates special moves in startOfBattleEffects are complete (so will not repeat) */ @@ -295,7 +295,7 @@ export class MysteryEncounter implements IMysteryEncounter { // Reset any dirty flags or encounter data this.startOfBattleEffectsComplete = false; - this.lockEncounterRewardTiers = true; + this.lockEncounterRarityTiers = true; this.dialogueTokens = {}; this.enemyPartyConfigs = []; this.startOfBattleEffects = []; @@ -561,7 +561,7 @@ export class MysteryEncounterBuilder implements Partial { continuousEncounter = false; catchAllowed = false; fleeAllowed = true; - lockEncounterRewardTiers = false; + lockEncounterRarityTiers = false; startOfBattleEffectsComplete = false; hasBattleAnimationsWithoutTargets = false; skipEnemyBattleTurns = false; @@ -928,13 +928,13 @@ export class MysteryEncounterBuilder implements Partial { } /** - * Can set custom encounter rewards via this callback function - * If rewards are always deterministic for an encounter, this is a good way to set them + * Can set custom encounter allRewards via this callback function + * If allRewards are always deterministic for an encounter, this is a good way to set them * - * NOTE: If rewards are dependent on options selected, runtime data, etc., + * NOTE: If allRewards are dependent on options selected, runtime data, etc., * It may be better to programmatically set doEncounterRewards elsewhere. - * There is a helper function in mystery-encounter utils, setEncounterRewards(), which can be called programmatically to set rewards - * @param doEncounterRewards Synchronous callback function to perform during rewards phase of the encounter + * There is a helper function in mystery-encounter utils, setEncounterRewards(), which can be called programmatically to set allRewards + * @param doEncounterRewards Synchronous callback function to perform during allRewards phase of the encounter * @returns */ withRewards(doEncounterRewards: () => boolean): this & Required> { @@ -945,10 +945,10 @@ export class MysteryEncounterBuilder implements Partial { * Can set custom encounter exp via this callback function * If exp always deterministic for an encounter, this is a good way to set them * - * NOTE: If rewards are dependent on options selected, runtime data, etc., + * NOTE: If allRewards are dependent on options selected, runtime data, etc., * It may be better to programmatically set doEncounterExp elsewhere. - * There is a helper function in mystery-encounter utils, setEncounterExp(), which can be called programmatically to set rewards - * @param doEncounterExp Synchronous callback function to perform during rewards phase of the encounter + * There is a helper function in mystery-encounter utils, setEncounterExp(), which can be called programmatically to set allRewards + * @param doEncounterExp Synchronous callback function to perform during allRewards phase of the encounter * @returns */ withExp(doEncounterExp: () => boolean): this & Required> { diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 4fde6531ad3..48968a2ff82 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -5,7 +5,6 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { BiomePoolTier, biomeLinks } from "#balance/biomes"; import { initMoveAnim, loadMoveAnimAssets } from "#data/battle-anims"; -import { modifierTypes } from "#data/data-lists"; import type { IEggOptions } from "#data/egg"; import { Egg } from "#data/egg"; import type { Gender } from "#data/gender"; @@ -17,7 +16,6 @@ import type { AiType } from "#enums/ai-type"; import type { BattlerTagType } from "#enums/battler-tag-type"; import { BiomeId } from "#enums/biome-id"; import { FieldPosition } from "#enums/field-position"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; import type { MoveId } from "#enums/move-id"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import type { Nature } from "#enums/nature"; @@ -31,8 +29,7 @@ import type { PlayerPokemon, Pokemon } from "#field/pokemon"; import { EnemyPokemon } from "#field/pokemon"; import { Trainer } from "#field/trainer"; import type { HeldItemConfiguration } from "#items/held-item-data-types"; -import type { CustomModifierSettings, ModifierType } from "#modifiers/modifier-type"; -import { getPartyLuckValue, ModifierTypeGenerator, ModifierTypeOption } from "#modifiers/modifier-type"; +import type { CustomRewardSettings } from "#items/reward-pool-utils"; import { PokemonMove } from "#moves/pokemon-move"; import { showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { MysteryEncounterOption } from "#mystery-encounters/mystery-encounter-option"; @@ -44,6 +41,7 @@ import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstact-option-se import type { PartyOption, PokemonSelectFilter } from "#ui/party-ui-handler"; import { PartyUiMode } from "#ui/party-ui-handler"; import { coerceArray, isNullOrUndefined, randomString, randSeedInt, randSeedItem } from "#utils/common"; +import { getPartyLuckValue } from "#utils/party"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; @@ -474,45 +472,6 @@ export function updatePlayerMoney(changeValue: number, playSound = true, showMes } } -/** - * Converts modifier bullshit to an actual item - * @param modifier - * @param pregenArgs Can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. - */ -export function generateModifierType(modifier: () => ModifierType, pregenArgs?: any[]): ModifierType | null { - const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier); - if (!modifierId) { - return null; - } - - let result: ModifierType = modifierTypes[modifierId](); - - // Populates item id and tier (order matters) - result = result - .withIdFromFunc(modifierTypes[modifierId]) - .withTierFromPool(ModifierPoolType.PLAYER, globalScene.getPlayerParty()); - - return result instanceof ModifierTypeGenerator - ? result.generateType(globalScene.getPlayerParty(), pregenArgs) - : result; -} - -/** - * Converts modifier bullshit to an actual item - * @param modifier - * @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. - */ -export function generateModifierTypeOption( - modifier: () => ModifierType, - pregenArgs?: any[], -): ModifierTypeOption | null { - const result = generateModifierType(modifier, pregenArgs); - if (result) { - return new ModifierTypeOption(result, 0); - } - return result; -} - /** * This function is intended for use inside onPreOptionPhase() of an encounter option * @param onPokemonSelected - Any logic that needs to be performed when Pokemon is chosen @@ -727,12 +686,12 @@ export function selectOptionThenPokemon( /** * Will initialize reward phases to follow the mystery encounter * Can have shop displayed or skipped - * @param customShopRewards - adds a shop phase with the specified rewards / reward tiers + * @param customShopRewards - adds a shop phase with the specified allRewards / reward tiers * @param eggRewards * @param preRewardsCallback - can execute an arbitrary callback before the new phases if necessary (useful for updating items/party/injecting new phases before {@linkcode MysteryEncounterRewardsPhase}) */ export function setEncounterRewards( - customShopRewards?: CustomModifierSettings, + customShopRewards?: CustomRewardSettings, eggRewards?: IEggOptions[], preRewardsCallback?: Function, ) { @@ -809,8 +768,8 @@ export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSet /** * Can be used to exit an encounter without any battles or followup - * Will skip any shops and rewards, and queue the next encounter phase as normal - * @param addHealPhase - when true, will add a shop phase to end of encounter with 0 rewards but healing items are available + * Will skip any shops and allRewards, and queue the next encounter phase as normal + * @param addHealPhase - when true, will add a shop phase to end of encounter with 0 allRewards but healing items are available * @param encounterMode - Can set custom encounter mode if necessary (may be required for forcing Pokemon to return before next phase) */ export function leaveEncounterWithoutBattle( diff --git a/src/data/trainers/fixed-battle-configs.ts b/src/data/trainers/fixed-battle-configs.ts index bb6d591654b..903743b38f5 100644 --- a/src/data/trainers/fixed-battle-configs.ts +++ b/src/data/trainers/fixed-battle-configs.ts @@ -4,8 +4,8 @@ import { globalScene } from "#app/global-scene"; import { randSeedInt } from "#app/utils/common"; import { BattleType } from "#enums/battle-type"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; -import { ModifierTier } from "#enums/modifier-tier"; import { PlayerGender } from "#enums/player-gender"; +import { RarityTier } from "#enums/reward-tier"; import { TrainerType } from "#enums/trainer-type"; import { TrainerVariant } from "#enums/trainer-variant"; @@ -45,8 +45,8 @@ export const classicFixedBattles: FixedBattleConfigs = { globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT, ), ) - .setCustomModifierRewards({ - guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], + .setCustomRewards({ + guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.GREAT, RarityTier.GREAT], allowLuckUpgrades: false, }), [ClassicFixedBossWaves.EVIL_GRUNT_1]: new FixedBattleConfig() @@ -77,8 +77,8 @@ export const classicFixedBattles: FixedBattleConfigs = { globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT, ), ) - .setCustomModifierRewards({ - guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], + .setCustomRewards({ + guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.GREAT, RarityTier.GREAT], allowLuckUpgrades: false, }), [ClassicFixedBossWaves.EVIL_GRUNT_2]: new FixedBattleConfig() @@ -150,8 +150,8 @@ export const classicFixedBattles: FixedBattleConfigs = { globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT, ), ) - .setCustomModifierRewards({ - guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA], + .setCustomRewards({ + guaranteedRarityTiers: [RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.ULTRA], allowLuckUpgrades: false, }), [ClassicFixedBossWaves.EVIL_GRUNT_4]: new FixedBattleConfig() @@ -212,14 +212,8 @@ export const classicFixedBattles: FixedBattleConfigs = { TrainerType.PENNY, ]), ) - .setCustomModifierRewards({ - guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ], + .setCustomRewards({ + guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ULTRA, RarityTier.ULTRA, RarityTier.ULTRA], allowLuckUpgrades: false, }), [ClassicFixedBossWaves.RIVAL_5]: new FixedBattleConfig() @@ -231,14 +225,8 @@ export const classicFixedBattles: FixedBattleConfigs = { globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT, ), ) - .setCustomModifierRewards({ - guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ], + .setCustomRewards({ + guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ROGUE, RarityTier.ULTRA, RarityTier.ULTRA], allowLuckUpgrades: false, }), [ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig() @@ -258,14 +246,14 @@ export const classicFixedBattles: FixedBattleConfigs = { TrainerType.PENNY_2, ]), ) - .setCustomModifierRewards({ - guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.ULTRA, + .setCustomRewards({ + guaranteedRarityTiers: [ + RarityTier.ROGUE, + RarityTier.ROGUE, + RarityTier.ULTRA, + RarityTier.ULTRA, + RarityTier.ULTRA, + RarityTier.ULTRA, ], allowLuckUpgrades: false, }), @@ -362,14 +350,14 @@ export const classicFixedBattles: FixedBattleConfigs = { globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT, ), ) - .setCustomModifierRewards({ - guaranteedModifierTiers: [ - ModifierTier.ROGUE, - ModifierTier.ROGUE, - ModifierTier.ULTRA, - ModifierTier.ULTRA, - ModifierTier.GREAT, - ModifierTier.GREAT, + .setCustomRewards({ + guaranteedRarityTiers: [ + RarityTier.ROGUE, + RarityTier.ROGUE, + RarityTier.ULTRA, + RarityTier.ULTRA, + RarityTier.GREAT, + RarityTier.GREAT, ], allowLuckUpgrades: false, }), diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 3e1ad67918a..2cc5125c626 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -3,7 +3,7 @@ import { globalScene } from "#app/global-scene"; import { pokemonEvolutions, pokemonPrevolutions } from "#balance/pokemon-evolutions"; import { signatureSpecies } from "#balance/signature-species"; import { tmSpecies } from "#balance/tms"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { doubleBattleDialogue } from "#data/double-battle-dialogue"; import { Gender } from "#data/gender"; import type { PokemonSpecies, PokemonSpeciesFilter } from "#data/pokemon-species"; @@ -31,7 +31,7 @@ import { TrainerPartyTemplate, trainerPartyTemplates, } from "#trainers/trainer-party-template"; -import type { ModifierTypeFunc } from "#types/modifier-types"; +import type { RewardFunc } from "#types/rewards"; import type { GenAIFunc, GenTrainerItemsFunc, @@ -113,9 +113,9 @@ export class TrainerConfig { public femaleEncounterBgm: string; public doubleEncounterBgm: string; public victoryBgm: string; - public genModifiersFunc: GenTrainerItemsFunc; + public genTrainerItemsFunc: GenTrainerItemsFunc; public genAIFuncs: GenAIFunc[] = []; - public modifierRewardFuncs: ModifierTypeFunc[] = []; + public rewardFuncs: RewardFunc[] = []; public partyTemplates: TrainerPartyTemplate[]; public partyTemplateFunc: PartyTemplateFunc; public partyMemberFuncs: PartyMemberFuncs = {}; @@ -465,8 +465,8 @@ export class TrainerConfig { return this; } - setGenModifiersFunc(genModifiersFunc: GenTrainerItemsFunc): TrainerConfig { - this.genModifiersFunc = genModifiersFunc; + setGenTrainerItemsFunc(genTrainerItemsFunc: GenTrainerItemsFunc): TrainerConfig { + this.genTrainerItemsFunc = genTrainerItemsFunc; return this; } @@ -476,7 +476,7 @@ export class TrainerConfig { * @param slot Optional, a specified slot that should be terastallized. Wraps to match party size (-1 will get the last slot and so on). * @returns this */ - setRandomTeraModifiers(count: () => number, slot?: number): TrainerConfig { + setRandomTeraType(count: () => number, slot?: number): TrainerConfig { this.genAIFuncs.push((party: EnemyPokemon[]) => { const shedinjaCanTera = !this.hasSpecialtyType() || this.specialtyType === PokemonType.BUG; // Better to check one time than 6 const partyMemberIndexes = new Array(party.length) @@ -507,23 +507,11 @@ export class TrainerConfig { return this; } - // function getRandomTeraModifiers(party: EnemyPokemon[], count: integer, types?: Type[]): PersistentModifier[] { - // const ret: PersistentModifier[] = []; - // const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i); - // for (let t = 0; t < Math.min(count, party.length); t++) { - // const randomIndex = Utils.randSeedItem(partyMemberIndexes); - // partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1); - // ret.push(modifierTypes.TERA_SHARD().generateType([], [ Utils.randSeedItem(types ? types : party[randomIndex].getTypes()) ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(party[randomIndex]) as PersistentModifier); // TODO: is the bang correct? - // } - // return ret; - // } - - setModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig { - this.modifierRewardFuncs = modifierTypeFuncs.map(func => () => { - const modifierTypeFunc = func(); - const modifierType = modifierTypeFunc(); - modifierType.withIdFromFunc(modifierTypeFunc); - return modifierType; + setRewardFuncs(...rewardFuncs: (() => RewardFunc)[]): TrainerConfig { + this.rewardFuncs = rewardFuncs.map(func => () => { + const rewardFunc = func(); + const reward = rewardFunc(); + return reward; }); return this; } @@ -689,7 +677,7 @@ export class TrainerConfig { this.setHasVoucher(true); this.setBattleBgm("battle_unova_gym"); this.setVictoryBgm("victory_gym"); - this.setRandomTeraModifiers( + this.setRandomTeraType( () => (ignoreMinTeraWave || globalScene.currentBattle.waveIndex >= GYM_LEADER_TERA_WAVE ? 1 : 0), teraSlot, ); @@ -750,7 +738,7 @@ export class TrainerConfig { this.setHasVoucher(true); this.setBattleBgm("battle_unova_elite"); this.setVictoryBgm("victory_gym"); - this.setRandomTeraModifiers(() => 1, teraSlot); + this.setRandomTeraType(() => 1, teraSlot); return this; } @@ -927,11 +915,11 @@ export class TrainerConfig { clone = this.battleBgm ? clone.setBattleBgm(this.battleBgm) : clone; clone = this.encounterBgm ? clone.setEncounterBgm(this.encounterBgm) : clone; clone = this.victoryBgm ? clone.setVictoryBgm(this.victoryBgm) : clone; - clone = this.genModifiersFunc ? clone.setGenModifiersFunc(this.genModifiersFunc) : clone; + clone = this.genTrainerItemsFunc ? clone.setGenTrainerItemsFunc(this.genTrainerItemsFunc) : clone; - if (this.modifierRewardFuncs) { + if (this.rewardFuncs) { // Clones array instead of passing ref - clone.modifierRewardFuncs = this.modifierRewardFuncs.slice(0); + clone.rewardFuncs = this.rewardFuncs.slice(0); } if (this.partyTemplates) { @@ -4443,9 +4431,9 @@ export const trainerConfigs: TrainerConfigs = { .setBattleBgm("battle_rival") .setMixedBattleBgm("battle_rival") .setPartyTemplates(trainerPartyTemplates.RIVAL) - .setModifierRewardFuncs( - () => modifierTypes.SUPER_EXP_CHARM, - () => modifierTypes.EXP_SHARE, + .setRewardFuncs( + () => allRewards.SUPER_EXP_CHARM, + () => allRewards.EXP_SHARE, ) .setPartyMemberFunc( 0, @@ -4513,7 +4501,7 @@ export const trainerConfigs: TrainerConfigs = { .setBattleBgm("battle_rival") .setMixedBattleBgm("battle_rival") .setPartyTemplates(trainerPartyTemplates.RIVAL_2) - .setModifierRewardFuncs(() => modifierTypes.EXP_SHARE) + .setRewardFuncs(() => allRewards.EXP_SHARE) .setPartyMemberFunc( 0, getRandomPartyMemberFunc( @@ -4666,7 +4654,7 @@ export const trainerConfigs: TrainerConfigs = { .setBattleBgm("battle_rival_2") .setMixedBattleBgm("battle_rival_2") .setPartyTemplates(trainerPartyTemplates.RIVAL_4) - .setModifierRewardFuncs(() => modifierTypes.TERA_ORB) + .setRewardFuncs(() => allRewards.TERA_ORB) .setPartyMemberFunc( 0, getRandomPartyMemberFunc( diff --git a/src/enums/held-item-effect.ts b/src/enums/held-item-effect.ts new file mode 100644 index 00000000000..209a74b921e --- /dev/null +++ b/src/enums/held-item-effect.ts @@ -0,0 +1,37 @@ +import type { EnumValues } from "#types/enum-types"; + +/** + * Enum representing the various "classes" of item effects that can be applied. + */ +export const HeldItemEffect = { + ATTACK_TYPE_BOOST: 1, + TURN_END_HEAL: 2, + HIT_HEAL: 3, + RESET_NEGATIVE_STAT_STAGE: 4, + EXP_BOOSTER: 5, + // Should we actually distinguish different berry effects? + BERRY: 6, + BASE_STAT_BOOSTER: 7, + INSTANT_REVIVE: 8, + STAT_BOOST: 9, + CRIT_BOOST: 10, + TURN_END_STATUS: 11, + SURVIVE_CHANCE: 12, + BYPASS_SPEED_CHANCE: 13, + FLINCH_CHANCE: 14, + FIELD_EFFECT: 15, + FRIENDSHIP_BOOSTER: 16, + NATURE_WEIGHT_BOOSTER: 17, + ACCURACY_BOOSTER: 18, + MULTI_HIT: 19, + DAMAGE_MONEY_REWARD: 20, + BATON: 21, + TURN_END_ITEM_STEAL: 22, + CONTACT_ITEM_STEAL_CHANCE: 23, + EVO_TRACKER: 40, + BASE_STAT_TOTAL: 50, + BASE_STAT_FLAT: 51, + INCREMENTING_STAT: 52, +} as const; + +export type HeldItemEffect = EnumValues; diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index f714f332d2a..c112ab6f37a 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -1,3 +1,5 @@ +import type { EnumValues } from "#types/enum-types"; + // TODO: make category the lower 2 bytes export const HeldItemId = { NONE: 0x0000, @@ -92,19 +94,24 @@ export const HeldItemId = { GIMMIGHOUL_EVO_TRACKER: 0x0A01, } as const; -export type HeldItemId = (typeof HeldItemId)[keyof typeof HeldItemId]; +export type HeldItemId = EnumValues; + +type HeldItemNameMap = { + [k in HeldItemName as (typeof HeldItemId)[k]]: k +} type HeldItemName = keyof typeof HeldItemId; -type HeldItemValue = typeof HeldItemId[HeldItemName]; // equivalent to `HeldItemId` -// Use a type-safe reducer to force number keys and values -export const HeldItemNames: Record = Object.entries(HeldItemId).reduce( +/** `const object` mapping all held item IDs to their respective names. */ +// TODO: This stores names as UPPER_SNAKE_CASE, but the locales are in PascalCase... +export const HeldItemNames = Object.freeze(Object.entries(HeldItemId).reduce( + // Use a type-safe reducer to force number keys and values (acc, [key, value]) => { - acc[value as HeldItemValue] = key as HeldItemName; + acc[value] = key; return acc; }, - {} as Record -); + {} +)) as HeldItemNameMap; export const HeldItemCategoryId = { NONE: 0x0000, @@ -120,7 +127,7 @@ export const HeldItemCategoryId = { EVO_TRACKER: 0x0A00, } as const; -export type HeldItemCategoryId = (typeof HeldItemCategoryId)[keyof typeof HeldItemCategoryId]; +export type HeldItemCategoryId = EnumValues; const ITEM_CATEGORY_MASK = 0xFF00 diff --git a/src/enums/reward-id.ts b/src/enums/reward-id.ts new file mode 100644 index 00000000000..97a6a1f5747 --- /dev/null +++ b/src/enums/reward-id.ts @@ -0,0 +1,99 @@ +import type { EnumValues } from "#types/enum-types"; + +export const RewardId = { + NONE: 0x0000, + + POKEBALL: 0x2001, + GREAT_BALL: 0x2002, + ULTRA_BALL: 0x2003, + ROGUE_BALL: 0x2004, + MASTER_BALL: 0x2005, + + VOUCHER: 0x2101, + VOUCHER_PLUS: 0x2102, + VOUCHER_PREMIUM: 0x2103, + + NUGGET: 0x2201, + BIG_NUGGET: 0x2202, + RELIC_GOLD: 0x2203, + + RARE_CANDY: 0x2301, + RARER_CANDY: 0x2302, + + EVOLUTION_ITEM: 0x2401, + RARE_EVOLUTION_ITEM: 0x2402, + + POTION: 0x2501, + SUPER_POTION: 0x2502, + HYPER_POTION: 0x2503, + MAX_POTION: 0x2504, + FULL_HEAL: 0x2505, + FULL_RESTORE: 0x2506, + + REVIVE: 0x2601, + MAX_REVIVE: 0x2602, + SACRED_ASH: 0x2603, + + ETHER: 0x2701, + MAX_ETHER: 0x2702, + + ELIXIR: 0x2801, + MAX_ELIXIR: 0x2802, + + PP_UP: 0x2901, + PP_MAX: 0x2902, + + TM_COMMON: 0x2A01, + TM_GREAT: 0x2A02, + TM_ULTRA: 0x2A03, + + MINT: 0x2B01, + TERA_SHARD: 0x2B02, + MEMORY_MUSHROOM: 0x2B03, + DNA_SPLICERS: 0x2B04, + + HELD_ITEM: 0x2C01, + SPECIES_STAT_BOOSTER: 0x2C02, + RARE_SPECIES_STAT_BOOSTER: 0x2C03, + BASE_STAT_BOOSTER: 0x2C04, + ATTACK_TYPE_BOOSTER: 0x2C05, + BERRY: 0x2C06, + + TRAINER_ITEM: 0x2D01, + TEMP_STAT_STAGE_BOOSTER: 0x2D02, + LURE: 0x2D03, + SUPER_LURE: 0x2D04, + MAX_LURE: 0x2D05, + + FORM_CHANGE_ITEM: 0x2E01, + RARE_FORM_CHANGE_ITEM: 0x2E02, +} as const; + +export type RewardId = EnumValues; + +export const RewardCategoryId = { + NONE: 0x0000, + POKEBALL: 0x0100, + VOUCHER: 0x0200, + MONEY: 0x0300, + CANDY: 0x0400, + EVOLUTION_ITEM: 0x0500, + HEALING: 0x0600, + REVIVE: 0x0700, + ETHER: 0x0800, + ELIXIR: 0x0900, + PP_UP: 0x0A00, + TM: 0x0B00, + OTHER: 0x0C00, + HELD_ITEM: 0x0D00, + TRAINER_ITEM: 0x0E00, + FORM_CHANGE_ITEM: 0x0F00, +} as const; + +export type RewardCategoryId = EnumValues; + +const ITEM_CATEGORY_MASK = 0xFF00 + +export function getRewardCategory(itemId: RewardId): RewardCategoryId { + return (itemId & ITEM_CATEGORY_MASK) as RewardCategoryId; +} diff --git a/src/enums/modifier-pool-type.ts b/src/enums/reward-pool-type.ts similarity index 80% rename from src/enums/modifier-pool-type.ts rename to src/enums/reward-pool-type.ts index 6513dc45ac1..fa8c3834009 100644 --- a/src/enums/modifier-pool-type.ts +++ b/src/enums/reward-pool-type.ts @@ -1,4 +1,4 @@ -export enum ModifierPoolType { +export enum RewardPoolType { PLAYER, } diff --git a/src/enums/reward-tier.ts b/src/enums/reward-tier.ts index e7ccc1d9166..51b799a8a5c 100644 --- a/src/enums/reward-tier.ts +++ b/src/enums/reward-tier.ts @@ -1,4 +1,4 @@ -export enum RewardTier { +export enum RarityTier { COMMON, GREAT, ULTRA, diff --git a/src/enums/trainer-item-id.ts b/src/enums/trainer-item-id.ts index 032c8933eb8..9d07e5c3c50 100644 --- a/src/enums/trainer-item-id.ts +++ b/src/enums/trainer-item-id.ts @@ -1,56 +1,56 @@ export const TrainerItemId = { NONE: 0x0000, - MAP: 0x0B01, - IV_SCANNER: 0x0B02, - LOCK_CAPSULE: 0x0B03, - MEGA_BRACELET: 0x0B04, - DYNAMAX_BAND: 0x0B05, - TERA_ORB: 0x0B06, +MAP: 0x1001, +IV_SCANNER: 0x1002, +LOCK_CAPSULE: 0x1003, +MEGA_BRACELET: 0x1004, +DYNAMAX_BAND: 0x1005, +TERA_ORB: 0x1006, - GOLDEN_POKEBALL: 0x0B07, +GOLDEN_POKEBALL: 0x1007, - OVAL_CHARM: 0x0B08, - EXP_SHARE: 0x0B09, - EXP_BALANCE: 0x0B0A, +OVAL_CHARM: 0x1008, +EXP_SHARE: 0x1009, +EXP_BALANCE: 0x100A, - CANDY_JAR: 0x0B0B, - BERRY_POUCH: 0x0B0C, +CANDY_JAR: 0x100B, +BERRY_POUCH: 0x100C, - HEALING_CHARM: 0x0B0D, - EXP_CHARM: 0x0B0E, - SUPER_EXP_CHARM: 0x0B0F, - GOLDEN_EXP_CHARM: 0x0B10, - AMULET_COIN: 0x0B11, +HEALING_CHARM: 0x100D, +EXP_CHARM: 0x100E, +SUPER_EXP_CHARM: 0x100F, +GOLDEN_EXP_CHARM: 0x1010, +AMULET_COIN: 0x1011, - ABILITY_CHARM: 0x0B12, - SHINY_CHARM: 0x0B13, - CATCHING_CHARM: 0x0B14, +ABILITY_CHARM: 0x1012, +SHINY_CHARM: 0x1013, +CATCHING_CHARM: 0x1014, - BLACK_SLUDGE: 0x0B15, - GOLDEN_BUG_NET: 0x0B16, +BLACK_SLUDGE: 0x1015, +GOLDEN_BUG_NET: 0x1016, - LURE: 0x0C01, - SUPER_LURE: 0x0C02, - MAX_LURE: 0x0C03, +LURE: 0x1101, +SUPER_LURE: 0x1102, +MAX_LURE: 0x1103, - X_ATTACK: 0x0D01, - X_DEFENSE: 0x0D02, - X_SP_ATK: 0x0D03, - X_SP_DEF: 0x0D04, - X_SPEED: 0x0D05, - X_ACCURACY: 0x0D06, - DIRE_HIT: 0x0D07, +X_ATTACK: 0x1201, +X_DEFENSE: 0x1202, +X_SP_ATK: 0x1203, +X_SP_DEF: 0x1204, +X_SPEED: 0x1205, +X_ACCURACY: 0x1206, +DIRE_HIT: 0x1207, - ENEMY_DAMAGE_BOOSTER: 0x0E01, - ENEMY_DAMAGE_REDUCTION: 0x0E02, - ENEMY_HEAL: 0x0E03, - ENEMY_ATTACK_POISON_CHANCE: 0x0E04, - ENEMY_ATTACK_PARALYZE_CHANCE: 0x0E05, - ENEMY_ATTACK_BURN_CHANCE: 0x0E06, - ENEMY_STATUS_EFFECT_HEAL_CHANCE: 0x0E07, - ENEMY_ENDURE_CHANCE: 0x0E08, - ENEMY_FUSED_CHANCE: 0x0E09, +ENEMY_DAMAGE_BOOSTER: 0x1301, +ENEMY_DAMAGE_REDUCTION: 0x1302, +ENEMY_HEAL: 0x1303, +ENEMY_ATTACK_POISON_CHANCE: 0x1304, +ENEMY_ATTACK_PARALYZE_CHANCE: 0x1305, +ENEMY_ATTACK_BURN_CHANCE: 0x1306, +ENEMY_STATUS_EFFECT_HEAL_CHANCE: 0x1307, +ENEMY_ENDURE_CHANCE: 0x1308, +ENEMY_FUSED_CHANCE: 0x1309, } as const; export type TrainerItemId = (typeof TrainerItemId)[keyof typeof TrainerItemId]; diff --git a/src/enums/ui-mode.ts b/src/enums/ui-mode.ts index dcf6bd2a238..ffe742b9d8e 100644 --- a/src/enums/ui-mode.ts +++ b/src/enums/ui-mode.ts @@ -5,7 +5,7 @@ export enum UiMode { FIGHT, BALL, TARGET_SELECT, - MODIFIER_SELECT, + REWARD_SELECT, SAVE_SLOT, PARTY, SUMMARY, diff --git a/src/field/arena.ts b/src/field/arena.ts index 37bee016c76..883e7958d42 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -19,6 +19,7 @@ import { ArenaTagSide } from "#enums/arena-tag-side"; import type { ArenaTagType } from "#enums/arena-tag-type"; import type { BattlerIndex } from "#enums/battler-index"; import { BiomeId } from "#enums/biome-id"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { CommonAnim } from "#enums/move-anims-common"; import type { MoveId } from "#enums/move-id"; import type { PokemonType } from "#enums/pokemon-type"; @@ -29,7 +30,6 @@ import { WeatherType } from "#enums/weather-type"; import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#events/arena"; import type { Pokemon } from "#field/pokemon"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import type { Move } from "#moves/move"; import type { AbstractConstructor } from "#types/type-helpers"; import { type Constructor, isNullOrUndefined, NumberHolder, randSeedInt } from "#utils/common"; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 61463731089..e9b41cbaa52 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -80,6 +80,7 @@ import { ChallengeType } from "#enums/challenge-type"; import { Challenges } from "#enums/challenges"; import { DexAttr } from "#enums/dex-attr"; import { FieldPosition } from "#enums/field-position"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import { HitResult } from "#enums/hit-result"; import { LearnMoveSituation } from "#enums/learn-move-situation"; @@ -92,7 +93,7 @@ import { Nature } from "#enums/nature"; import { PokeballType } from "#enums/pokeball"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PokemonType } from "#enums/pokemon-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesId } from "#enums/species-id"; import { @@ -111,7 +112,6 @@ import { UiMode } from "#enums/ui-mode"; import { WeatherType } from "#enums/weather-type"; import { doShinySparkleAnim } from "#field/anims"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import type { HeldItemConfiguration } from "#items/held-item-data-types"; import { HeldItemManager } from "#items/held-item-manager"; import { assignItemsFromConfiguration } from "#items/held-item-pool"; @@ -2882,13 +2882,13 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * * The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536` * @param thresholdOverride number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm) - * @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Shiny Charm and event modifiers to {@linkcode thresholdOverride} + * @param applyItemsToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Shiny Charm and event modifiers to {@linkcode thresholdOverride} * @returns `true` if the Pokemon has been set as a shiny, `false` otherwise */ - public trySetShinySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean { + public trySetShinySeed(thresholdOverride?: number, applyItemsToOverride?: boolean): boolean { if (!this.shiny) { const shinyThreshold = new NumberHolder(thresholdOverride ?? BASE_SHINY_CHANCE); - if (applyModifiersToOverride) { + if (applyItemsToOverride) { if (timedEventManager.isEventActive()) { shinyThreshold.value *= timedEventManager.getShinyMultiplier(); } @@ -2954,15 +2954,15 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * * The base hidden ability odds are {@linkcode BASE_HIDDEN_ABILITY_CHANCE} / `65536` * @param thresholdOverride number that is divided by `2^16` (`65536`) to get the HA chance, overrides {@linkcode haThreshold} if set (bypassing HA rate modifiers such as Ability Charm) - * @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Ability Charm to {@linkcode thresholdOverride} + * @param applyItemsToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Ability Charm to {@linkcode thresholdOverride} * @returns `true` if the Pokemon has been set to have its hidden ability, `false` otherwise */ - public tryRerollHiddenAbilitySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean { + public tryRerollHiddenAbilitySeed(thresholdOverride?: number, applyItemsToOverride?: boolean): boolean { if (!this.species.abilityHidden) { return false; } const haThreshold = new NumberHolder(thresholdOverride ?? BASE_HIDDEN_ABILITY_CHANCE); - if (applyModifiersToOverride) { + if (applyItemsToOverride) { if (!this.hasTrainer()) { globalScene.applyPlayerItems(TrainerItemEffect.HIDDEN_ABILITY_CHANCE_BOOSTER, { numberHolder: haThreshold }); } @@ -3108,11 +3108,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { } } if (compatible && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) { - if (tmPoolTiers[moveId] === RewardTier.COMMON && this.level >= 15) { + if (tmPoolTiers[moveId] === RarityTier.COMMON && this.level >= 15) { movePool.push([moveId, 4]); - } else if (tmPoolTiers[moveId] === RewardTier.GREAT && this.level >= 30) { + } else if (tmPoolTiers[moveId] === RarityTier.GREAT && this.level >= 30) { movePool.push([moveId, 8]); - } else if (tmPoolTiers[moveId] === RewardTier.ULTRA && this.level >= 50) { + } else if (tmPoolTiers[moveId] === RarityTier.ULTRA && this.level >= 50) { movePool.push([moveId, 14]); } } diff --git a/src/field/trainer.ts b/src/field/trainer.ts index c19e96f24dd..dea610b4195 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -634,7 +634,7 @@ export class Trainer extends Phaser.GameObjects.Container { return maxScorePartyMemberIndexes[0]; } - getPartyMemberModifierChanceMultiplier(index: number): number { + getPartyMemberItemChanceMultiplier(index: number): number { switch (this.getPartyTemplate().getStrength(index)) { case PartyMemberStrength.WEAKER: return 0.75; @@ -647,14 +647,14 @@ export class Trainer extends Phaser.GameObjects.Container { case PartyMemberStrength.STRONGER: return 0.375; default: - console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0"); + console.warn("getPartyMemberItemChanceMultiplier not defined. Using default 0"); return 0; } } genTrainerItems(party: EnemyPokemon[]): TrainerItemConfiguration { - if (this.config.genModifiersFunc) { - return this.config.genModifiersFunc(party); + if (this.config.genTrainerItemsFunc) { + return this.config.genTrainerItemsFunc(party); } return []; } diff --git a/src/game-mode.ts b/src/game-mode.ts index c5ab120e218..d12e801b196 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -323,7 +323,7 @@ export class GameMode implements GameModeConfig { } } - getEnemyModifierChance(isBoss: boolean): number { + getEnemyItemChance(isBoss: boolean): number { switch (this.modeId) { case GameModes.CLASSIC: case GameModes.CHALLENGE: diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index ea9db959892..fa3c8d8cebe 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -24,7 +24,6 @@ import { ExpBoosterHeldItem, type ExpBoostParams } from "#items/exp-booster"; import { FieldEffectHeldItem, type FieldEffectParams } from "#items/field-effect"; import { FlinchChanceHeldItem, type FlinchChanceParams } from "#items/flinch-chance"; import { FriendshipBoosterHeldItem, type FriendshipBoostParams } from "#items/friendship-booster"; -import { HeldItemEffect } from "#items/held-item"; import { HitHealHeldItem, type HitHealParams } from "#items/hit-heal"; import { IncrementingStatHeldItem, type IncrementingStatParams } from "#items/incrementing-stat"; import { InstantReviveHeldItem, type InstantReviveParams } from "#items/instant-revive"; @@ -36,7 +35,8 @@ import { EvolutionStatBoostHeldItem, SpeciesStatBoostHeldItem, type StatBoostPar import { SurviveChanceHeldItem, type SurviveChanceParams } from "#items/survive-chance"; import { TurnEndHealHeldItem, type TurnEndHealParams } from "#items/turn-end-heal"; import { TurnEndStatusHeldItem, type TurnEndStatusParams } from "#items/turn-end-status"; -import { getEnumValues } from "#utils/common"; +import { getEnumValues } from "#utils/enums"; +import { HeldItemEffect } from "./HeldItemEffect"; export function initHeldItems() { for (const berry of getEnumValues(BerryType)) { diff --git a/src/items/all-trainer-items.ts b/src/items/all-trainer-items.ts index 0b4901ca6b9..487bd3224d0 100644 --- a/src/items/all-trainer-items.ts +++ b/src/items/all-trainer-items.ts @@ -43,7 +43,7 @@ export function initTrainerItems() { allTrainerItems[TrainerItemId.CANDY_JAR] = new LevelIncrementBoosterTrainerItem(TrainerItemId.CANDY_JAR, 99); allTrainerItems[TrainerItemId.BERRY_POUCH] = new PreserveBerryTrainerItem(TrainerItemId.BERRY_POUCH, 3); - allTrainerItems[TrainerItemId.HEALING_CHARM] = new HealingBoosterTrainerItem(TrainerItemId.HEALING_CHARM, 1.1, 5); + allTrainerItems[TrainerItemId.HEALING_CHARM] = new HealingBoosterTrainerItem(TrainerItemId.HEALING_CHARM, 0.1, 5); allTrainerItems[TrainerItemId.EXP_CHARM] = new ExpBoosterTrainerItem(TrainerItemId.EXP_CHARM, 25, 99); allTrainerItems[TrainerItemId.SUPER_EXP_CHARM] = new ExpBoosterTrainerItem(TrainerItemId.SUPER_EXP_CHARM, 60, 30); allTrainerItems[TrainerItemId.GOLDEN_EXP_CHARM] = new ExpBoosterTrainerItem(TrainerItemId.GOLDEN_EXP_CHARM, 100, 10); diff --git a/src/items/held-item-data-types.ts b/src/items/held-item-data-types.ts index 58d3f25d20c..af4a3054bac 100644 --- a/src/items/held-item-data-types.ts +++ b/src/items/held-item-data-types.ts @@ -1,13 +1,21 @@ -// TODO: move to `src/@types/` +// TODO: move all types to `src/@types/` and all functions to a utility place import type { FormChangeItem } from "#enums/form-change-item"; import type { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; -import type { RewardTier } from "#enums/reward-tier"; +import type { RarityTier } from "#enums/reward-tier"; import type { Pokemon } from "#field/pokemon"; export type HeldItemData = { stack: number; + /** + * Whether this item is currently disabled. + * @defaultValue `false` + */ disabled?: boolean; + /** + * The item's current cooldown. + * @defaultValue `0` + */ cooldown?: number; }; @@ -67,7 +75,7 @@ export function isHeldItemPool(value: any): value is HeldItemPool { } export type HeldItemTieredPool = { - [key in RewardTier]?: HeldItemPool; + [key in RarityTier]?: HeldItemPool; }; type HeldItemConfigurationEntry = { diff --git a/src/items/held-item-default-tiers.ts b/src/items/held-item-default-tiers.ts new file mode 100644 index 00000000000..8ca4c286343 --- /dev/null +++ b/src/items/held-item-default-tiers.ts @@ -0,0 +1,47 @@ +import { getHeldItemCategory, HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; +import { RarityTier } from "#enums/reward-tier"; + +export const heldItemRarities = { + [HeldItemCategoryId.BERRY]: RarityTier.COMMON, + + [HeldItemCategoryId.BASE_STAT_BOOST]: RarityTier.GREAT, + [HeldItemId.WHITE_HERB]: RarityTier.GREAT, + [HeldItemId.METAL_POWDER]: RarityTier.GREAT, + [HeldItemId.QUICK_POWDER]: RarityTier.GREAT, + [HeldItemId.DEEP_SEA_SCALE]: RarityTier.GREAT, + [HeldItemId.DEEP_SEA_TOOTH]: RarityTier.GREAT, + [HeldItemId.SOOTHE_BELL]: RarityTier.GREAT, + + [HeldItemCategoryId.TYPE_ATTACK_BOOSTER]: RarityTier.ULTRA, + [HeldItemId.REVIVER_SEED]: RarityTier.ULTRA, + [HeldItemId.LIGHT_BALL]: RarityTier.ULTRA, + [HeldItemId.EVIOLITE]: RarityTier.ULTRA, + [HeldItemId.QUICK_CLAW]: RarityTier.ULTRA, + [HeldItemId.MYSTICAL_ROCK]: RarityTier.ULTRA, + [HeldItemId.WIDE_LENS]: RarityTier.ULTRA, + [HeldItemId.GOLDEN_PUNCH]: RarityTier.ULTRA, + [HeldItemId.TOXIC_ORB]: RarityTier.ULTRA, + [HeldItemId.FLAME_ORB]: RarityTier.ULTRA, + [HeldItemId.LUCKY_EGG]: RarityTier.ULTRA, + + [HeldItemId.FOCUS_BAND]: RarityTier.ROGUE, + [HeldItemId.KINGS_ROCK]: RarityTier.ROGUE, + [HeldItemId.LEFTOVERS]: RarityTier.ROGUE, + [HeldItemId.SHELL_BELL]: RarityTier.ROGUE, + [HeldItemId.GRIP_CLAW]: RarityTier.ROGUE, + [HeldItemId.SOUL_DEW]: RarityTier.ROGUE, + [HeldItemId.BATON]: RarityTier.ROGUE, + [HeldItemId.GOLDEN_EGG]: RarityTier.ULTRA, + + [HeldItemId.MINI_BLACK_HOLE]: RarityTier.MASTER, + [HeldItemId.MULTI_LENS]: RarityTier.MASTER, +}; + +export function getHeldItemTier(item: HeldItemId): RarityTier { + let tier = heldItemRarities[item]; + if (!tier) { + const category = getHeldItemCategory(item); + tier = heldItemRarities[category]; + } + return tier ?? RarityTier.LUXURY; +} diff --git a/src/items/held-item-pool.ts b/src/items/held-item-pool.ts index 077c5be730b..55066a86ede 100644 --- a/src/items/held-item-pool.ts +++ b/src/items/held-item-pool.ts @@ -1,9 +1,9 @@ import { allHeldItems } from "#data/data-lists"; import { BerryType } from "#enums/berry-type"; import { HeldItemCategoryId, HeldItemId, HeldItemNames, isCategoryId } from "#enums/held-item-id"; -import { HeldItemPoolType } from "#enums/modifier-pool-type"; import type { PokemonType } from "#enums/pokemon-type"; -import { RewardTier } from "#enums/reward-tier"; +import { HeldItemPoolType } from "#enums/reward-pool-type"; +import { RarityTier } from "#enums/reward-tier"; import { PERMANENT_STATS } from "#enums/stat"; import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; import { attackTypeToHeldItem } from "#items/attack-type-booster"; @@ -21,7 +21,8 @@ import { isHeldItemPool, isHeldItemSpecs, } from "#items/held-item-data-types"; -import { coerceArray, getEnumValues, isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common"; +import { coerceArray, isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common"; +import { getEnumValues } from "#utils/enums"; export const wildHeldItemPool: HeldItemTieredPool = {}; @@ -34,7 +35,7 @@ export function assignDailyRunStarterHeldItems(party: PlayerPokemon[]) { for (let m = 0; m < 3; m++) { const tierValue = randSeedInt(64); - const tier = getDailyRewardTier(tierValue); + const tier = getDailyRarityTier(tierValue); const item = getNewHeldItemFromPool( getHeldItemPool(HeldItemPoolType.DAILY_STARTER)[tier] as HeldItemPool, @@ -46,20 +47,20 @@ export function assignDailyRunStarterHeldItems(party: PlayerPokemon[]) { } } -function getDailyRewardTier(tierValue: number): RewardTier { +function getDailyRarityTier(tierValue: number): RarityTier { if (tierValue > 25) { - return RewardTier.COMMON; + return RarityTier.COMMON; } if (tierValue > 12) { - return RewardTier.GREAT; + return RarityTier.GREAT; } if (tierValue > 4) { - return RewardTier.ULTRA; + return RarityTier.ULTRA; } if (tierValue > 0) { - return RewardTier.ROGUE; + return RarityTier.ROGUE; } - return RewardTier.MASTER; + return RarityTier.MASTER; } function getHeldItemPool(poolType: HeldItemPoolType): HeldItemTieredPool { @@ -101,25 +102,25 @@ export function assignEnemyHeldItemsForWave( } } -function getRandomTier(): RewardTier { +function getRandomTier(): RarityTier { const tierValue = randSeedInt(1024); if (tierValue > 255) { - return RewardTier.COMMON; + return RarityTier.COMMON; } if (tierValue > 60) { - return RewardTier.GREAT; + return RarityTier.GREAT; } if (tierValue > 12) { - return RewardTier.ULTRA; + return RarityTier.ULTRA; } if (tierValue) { - return RewardTier.ROGUE; + return RarityTier.ROGUE; } - return RewardTier.MASTER; + return RarityTier.MASTER; } -function determineItemPoolTier(pool: HeldItemTieredPool, upgradeCount?: number): RewardTier { +function determineItemPoolTier(pool: HeldItemTieredPool, upgradeCount?: number): RarityTier { let tier = getRandomTier(); if (!upgradeCount) { diff --git a/src/items/held-item-tiers.ts b/src/items/held-item-tiers.ts deleted file mode 100644 index fb340998198..00000000000 --- a/src/items/held-item-tiers.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { getHeldItemCategory, HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; -import { RewardTier } from "#enums/reward-tier"; - -export const heldItemTiers = { - [HeldItemCategoryId.BERRY]: RewardTier.COMMON, - - [HeldItemCategoryId.BASE_STAT_BOOST]: RewardTier.GREAT, - [HeldItemId.WHITE_HERB]: RewardTier.GREAT, - [HeldItemId.METAL_POWDER]: RewardTier.GREAT, - [HeldItemId.QUICK_POWDER]: RewardTier.GREAT, - [HeldItemId.DEEP_SEA_SCALE]: RewardTier.GREAT, - [HeldItemId.DEEP_SEA_TOOTH]: RewardTier.GREAT, - [HeldItemId.SOOTHE_BELL]: RewardTier.GREAT, - - [HeldItemCategoryId.TYPE_ATTACK_BOOSTER]: RewardTier.ULTRA, - [HeldItemId.REVIVER_SEED]: RewardTier.ULTRA, - [HeldItemId.LIGHT_BALL]: RewardTier.ULTRA, - [HeldItemId.EVIOLITE]: RewardTier.ULTRA, - [HeldItemId.QUICK_CLAW]: RewardTier.ULTRA, - [HeldItemId.MYSTICAL_ROCK]: RewardTier.ULTRA, - [HeldItemId.WIDE_LENS]: RewardTier.ULTRA, - [HeldItemId.GOLDEN_PUNCH]: RewardTier.ULTRA, - [HeldItemId.TOXIC_ORB]: RewardTier.ULTRA, - [HeldItemId.FLAME_ORB]: RewardTier.ULTRA, - [HeldItemId.LUCKY_EGG]: RewardTier.ULTRA, - - [HeldItemId.FOCUS_BAND]: RewardTier.ROGUE, - [HeldItemId.KINGS_ROCK]: RewardTier.ROGUE, - [HeldItemId.LEFTOVERS]: RewardTier.ROGUE, - [HeldItemId.SHELL_BELL]: RewardTier.ROGUE, - [HeldItemId.GRIP_CLAW]: RewardTier.ROGUE, - [HeldItemId.SOUL_DEW]: RewardTier.ROGUE, - [HeldItemId.BATON]: RewardTier.ROGUE, - [HeldItemId.GOLDEN_EGG]: RewardTier.ULTRA, - - [HeldItemId.MINI_BLACK_HOLE]: RewardTier.MASTER, - [HeldItemId.MULTI_LENS]: RewardTier.MASTER, -}; - -export function getHeldItemTier(item: HeldItemId): RewardTier | undefined { - let tier = heldItemTiers[item]; - if (!tier) { - const category = getHeldItemCategory(item); - tier = heldItemTiers[category]; - } - return tier; -} diff --git a/src/items/held-item.ts b/src/items/held-item.ts index 9ca3fbd616d..6dd76883e47 100644 --- a/src/items/held-item.ts +++ b/src/items/held-item.ts @@ -4,42 +4,9 @@ import { type HeldItemId, HeldItemNames } from "#enums/held-item-id"; import type { Pokemon } from "#field/pokemon"; import i18next from "i18next"; -// TODO: this should be moved to its own file -export const HeldItemEffect = { - ATTACK_TYPE_BOOST: 1, - TURN_END_HEAL: 2, - HIT_HEAL: 3, - RESET_NEGATIVE_STAT_STAGE: 4, - EXP_BOOSTER: 5, - // Should we actually distinguish different berry effects? - BERRY: 6, - BASE_STAT_BOOSTER: 7, - INSTANT_REVIVE: 8, - STAT_BOOST: 9, - CRIT_BOOST: 10, - TURN_END_STATUS: 11, - SURVIVE_CHANCE: 12, - BYPASS_SPEED_CHANCE: 13, - FLINCH_CHANCE: 14, - FIELD_EFFECT: 15, - FRIENDSHIP_BOOSTER: 16, - NATURE_WEIGHT_BOOSTER: 17, - ACCURACY_BOOSTER: 18, - MULTI_HIT: 19, - DAMAGE_MONEY_REWARD: 20, - BATON: 21, - TURN_END_ITEM_STEAL: 22, - CONTACT_ITEM_STEAL_CHANCE: 23, - EVO_TRACKER: 40, - BASE_STAT_TOTAL: 50, - BASE_STAT_FLAT: 51, - INCREMENTING_STAT: 52, -} as const; - -export type HeldItemEffect = (typeof HeldItemEffect)[keyof typeof HeldItemEffect]; - export class HeldItem { // public pokemonId: number; + // TODO: Should this be readonly? public type: HeldItemId; public readonly maxStackCount: number; public isTransferable = true; diff --git a/src/items/held-items/accuracy-booster.ts b/src/items/held-items/accuracy-booster.ts index 09abb723300..ff57a82b192 100644 --- a/src/items/held-items/accuracy-booster.ts +++ b/src/items/held-items/accuracy-booster.ts @@ -1,6 +1,7 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import type { HeldItemId } from "#enums/held-item-id"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; export interface AccuracyBoostParams { @@ -24,17 +25,17 @@ export class AccuracyBoosterHeldItem extends HeldItem { } /** - * Checks if {@linkcode PokemonMoveAccuracyBoosterModifier} should be applied + * Checks if {@linkcode PokemonMoveAccuracyBoosterHeldItem} 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 + * @returns `true` if {@linkcode PokemonMoveAccuracyBoosterHeldItem} should be applied */ // override shouldApply(pokemon?: Pokemon, moveAccuracy?: NumberHolder): boolean { // return super.shouldApply(pokemon, moveAccuracy) && !!moveAccuracy; // } /** - * Applies {@linkcode PokemonMoveAccuracyBoosterModifier} + * Applies {@linkcode PokemonMoveAccuracyBoosterHeldItem} */ apply({ pokemon, moveAccuracy }: AccuracyBoostParams): true { const stackCount = pokemon.heldItemManager.getStack(this.type); diff --git a/src/items/held-items/attack-type-booster.ts b/src/items/held-items/attack-type-booster.ts index 7e5f1f10141..f1558d095a8 100644 --- a/src/items/held-items/attack-type-booster.ts +++ b/src/items/held-items/attack-type-booster.ts @@ -1,7 +1,8 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId, HeldItemNames } from "#enums/held-item-id"; import { PokemonType } from "#enums/pokemon-type"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/base-stat-booster.ts b/src/items/held-items/base-stat-booster.ts index 7525974ca92..03be45ec4b9 100644 --- a/src/items/held-items/base-stat-booster.ts +++ b/src/items/held-items/base-stat-booster.ts @@ -1,7 +1,8 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import { getStatKey, type PermanentStat, Stat } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import i18next from "i18next"; export interface BaseStatBoosterParams { @@ -11,9 +12,9 @@ export interface BaseStatBoosterParams { baseStats: number[]; } -interface PermanentStatToHeldItemMap { - [key: number]: HeldItemId; -} +type PermanentStatToHeldItemMap = { + [key in PermanentStat]: HeldItemId; +}; export const permanentStatToHeldItem: PermanentStatToHeldItemMap = { [Stat.HP]: HeldItemId.HP_UP, diff --git a/src/items/held-items/base-stat-flat.ts b/src/items/held-items/base-stat-flat.ts index ae10f6d7d1d..d0c622af6ba 100644 --- a/src/items/held-items/base-stat-flat.ts +++ b/src/items/held-items/base-stat-flat.ts @@ -1,6 +1,7 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import { Stat } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import i18next from "i18next"; export interface BaseStatFlatParams { diff --git a/src/items/held-items/base-stat-total.ts b/src/items/held-items/base-stat-total.ts index 1c1eb154cb3..dce4355a009 100644 --- a/src/items/held-items/base-stat-total.ts +++ b/src/items/held-items/base-stat-total.ts @@ -1,6 +1,7 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import type { HeldItemId } from "#enums/held-item-id"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import i18next from "i18next"; export interface BaseStatTotalParams { diff --git a/src/items/held-items/baton.ts b/src/items/held-items/baton.ts index e518f8f0c5d..72c89727a73 100644 --- a/src/items/held-items/baton.ts +++ b/src/items/held-items/baton.ts @@ -1,5 +1,6 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; export interface BatonParams { diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts index 3b0b3bcf2f1..0ce886c41fe 100644 --- a/src/items/held-items/berry.ts +++ b/src/items/held-items/berry.ts @@ -1,18 +1,21 @@ import { globalScene } from "#app/global-scene"; import { getBerryEffectDescription, getBerryEffectFunc, getBerryName, getBerryPredicate } from "#data/berry"; import { BerryType } from "#enums/berry-type"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import { BerryUsedEvent } from "#events/battle-scene"; import type { Pokemon } from "#field/pokemon"; -import { ConsumableHeldItem, HeldItemEffect } from "#items/held-item"; +import { ConsumableHeldItem } from "#items/held-item"; import { TrainerItemEffect } from "#items/trainer-item"; +import type { EnumValues } from "#types/enum-types"; import { BooleanHolder } from "#utils/common"; -interface BerryTypeToHeldItemMap { - [key: number]: HeldItemId; -} +type BerryTypeToHeldItemMap = { + [key in EnumValues]: HeldItemId; +}; -export const berryTypeToHeldItem: BerryTypeToHeldItemMap = { +// TODO: Rework this to use a bitwise XOR +export const berryTypeToHeldItem = { [BerryType.SITRUS]: HeldItemId.SITRUS_BERRY, [BerryType.LUM]: HeldItemId.LUM_BERRY, [BerryType.ENIGMA]: HeldItemId.ENIGMA_BERRY, @@ -24,7 +27,7 @@ export const berryTypeToHeldItem: BerryTypeToHeldItemMap = { [BerryType.LANSAT]: HeldItemId.LANSAT_BERRY, [BerryType.STARF]: HeldItemId.STARF_BERRY, [BerryType.LEPPA]: HeldItemId.LEPPA_BERRY, -}; +} satisfies BerryTypeToHeldItemMap; export interface BerryParams { /** The pokemon with the berry */ diff --git a/src/items/held-items/bypass-speed-chance.ts b/src/items/held-items/bypass-speed-chance.ts index ba3800d2635..3d00e644df7 100644 --- a/src/items/held-items/bypass-speed-chance.ts +++ b/src/items/held-items/bypass-speed-chance.ts @@ -1,8 +1,9 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { Command } from "#enums/command"; +import { HeldItemEffect } from "#enums/held-item-effect"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import type { BooleanHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/crit-booster.ts b/src/items/held-items/crit-booster.ts index d1ba07c90c9..8a8c153e1a0 100644 --- a/src/items/held-items/crit-booster.ts +++ b/src/items/held-items/crit-booster.ts @@ -1,7 +1,8 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import type { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; export interface CritBoostParams { diff --git a/src/items/held-items/damage-money-reward.ts b/src/items/held-items/damage-money-reward.ts index b158640cb7f..1d57e61b697 100644 --- a/src/items/held-items/damage-money-reward.ts +++ b/src/items/held-items/damage-money-reward.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; +import { HeldItemEffect } from "#enums/held-item-effect"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import { TrainerItemEffect } from "#items/trainer-item"; import { NumberHolder } from "#utils/common"; diff --git a/src/items/held-items/evo-tracker.ts b/src/items/held-items/evo-tracker.ts index a52ac286f06..29e84958910 100644 --- a/src/items/held-items/evo-tracker.ts +++ b/src/items/held-items/evo-tracker.ts @@ -1,9 +1,10 @@ import { globalScene } from "#app/global-scene"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import { TrainerItemId } from "#enums/trainer-item-id"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import i18next from "i18next"; export interface EvoTrackerParams { diff --git a/src/items/held-items/exp-booster.ts b/src/items/held-items/exp-booster.ts index d257fc8d774..e4417e8ac95 100644 --- a/src/items/held-items/exp-booster.ts +++ b/src/items/held-items/exp-booster.ts @@ -1,6 +1,7 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import type { HeldItemId } from "#enums/held-item-id"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/field-effect.ts b/src/items/held-items/field-effect.ts index a076a2fc7dd..25baf9c889a 100644 --- a/src/items/held-items/field-effect.ts +++ b/src/items/held-items/field-effect.ts @@ -1,5 +1,6 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; export interface FieldEffectParams { diff --git a/src/items/held-items/flinch-chance.ts b/src/items/held-items/flinch-chance.ts index 82d716368aa..81320c55ba9 100644 --- a/src/items/held-items/flinch-chance.ts +++ b/src/items/held-items/flinch-chance.ts @@ -1,6 +1,7 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import type { HeldItemId } from "#enums/held-item-id"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import type { BooleanHolder } from "#utils/common"; export interface FlinchChanceParams { diff --git a/src/items/held-items/friendship-booster.ts b/src/items/held-items/friendship-booster.ts index 960838f8925..783b77c1a15 100644 --- a/src/items/held-items/friendship-booster.ts +++ b/src/items/held-items/friendship-booster.ts @@ -1,5 +1,6 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/hit-heal.ts b/src/items/held-items/hit-heal.ts index 5d4a05582ec..8e14509b429 100644 --- a/src/items/held-items/hit-heal.ts +++ b/src/items/held-items/hit-heal.ts @@ -1,7 +1,8 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; +import { HeldItemEffect } from "#enums/held-item-effect"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import { PokemonHealPhase } from "#phases/pokemon-heal-phase"; import { toDmgValue } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/incrementing-stat.ts b/src/items/held-items/incrementing-stat.ts index a462b70326e..3dc62d8ebb3 100644 --- a/src/items/held-items/incrementing-stat.ts +++ b/src/items/held-items/incrementing-stat.ts @@ -1,6 +1,7 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import { Stat } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/instant-revive.ts b/src/items/held-items/instant-revive.ts index 10137efe61d..839f660d09a 100644 --- a/src/items/held-items/instant-revive.ts +++ b/src/items/held-items/instant-revive.ts @@ -1,8 +1,9 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; +import { HeldItemEffect } from "#enums/held-item-effect"; import type { Pokemon } from "#field/pokemon"; -import { ConsumableHeldItem, HeldItemEffect } from "#items/held-item"; +import { ConsumableHeldItem } from "#items/held-item"; import { PokemonHealPhase } from "#phases/pokemon-heal-phase"; import { toDmgValue } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/item-steal.ts b/src/items/held-items/item-steal.ts index acba8d6930f..681a1d619b1 100644 --- a/src/items/held-items/item-steal.ts +++ b/src/items/held-items/item-steal.ts @@ -1,9 +1,10 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { allHeldItems } from "#data/data-lists"; +import { HeldItemEffect } from "#enums/held-item-effect"; import type { HeldItemId } from "#enums/held-item-id"; import { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import { coerceArray, randSeedFloat } from "#utils/common"; import i18next from "i18next"; @@ -47,7 +48,7 @@ export abstract class ItemTransferHeldItem extends HeldItem { } // TODO: Change this logic to use held items - const transferredModifierTypes: HeldItemId[] = []; + const transferredRewards: HeldItemId[] = []; const heldItems = targetPokemon.heldItemManager.getTransferableHeldItems(); for (let i = 0; i < transferredItemCount; i++) { @@ -58,16 +59,16 @@ export abstract class ItemTransferHeldItem extends HeldItem { const randItem = heldItems[randItemIndex]; // TODO: Fix this after updating the various methods in battle-scene.ts if (globalScene.tryTransferHeldItem(randItem, targetPokemon, pokemon, false)) { - transferredModifierTypes.push(randItem); + transferredRewards.push(randItem); heldItems.splice(randItemIndex, 1); } } - for (const mt of transferredModifierTypes) { + for (const mt of transferredRewards) { globalScene.phaseManager.queueMessage(this.getTransferMessage(params, mt)); } - return !!transferredModifierTypes.length; + return !!transferredRewards.length; } abstract getTargets(params: ItemStealParams): Pokemon[]; @@ -80,7 +81,7 @@ export abstract class ItemTransferHeldItem extends HeldItem { /** * Modifier for held items that steal items from the enemy at the end of * each turn. - * @see {@linkcode modifierTypes[MINI_BLACK_HOLE]} + * @see {@linkcode allRewards[MINI_BLACK_HOLE]} */ export class TurnEndItemStealHeldItem extends ItemTransferHeldItem { public effects: HeldItemEffect[] = [HeldItemEffect.TURN_END_ITEM_STEAL]; @@ -121,7 +122,7 @@ export class TurnEndItemStealHeldItem extends ItemTransferHeldItem { /** * Modifier for held items that add a chance to steal items from the target of a * successful attack. - * @see {@linkcode modifierTypes[GRIP_CLAW]} + * @see {@linkcode allRewards[GRIP_CLAW]} * @see {@linkcode HeldItemTransferModifier} */ export class ContactItemStealChanceHeldItem extends ItemTransferHeldItem { diff --git a/src/items/held-items/multi-hit.ts b/src/items/held-items/multi-hit.ts index 66cb7cdfa2b..1c2d34544e6 100644 --- a/src/items/held-items/multi-hit.ts +++ b/src/items/held-items/multi-hit.ts @@ -1,7 +1,8 @@ import { allMoves } from "#data/data-lists"; +import { HeldItemEffect } from "#enums/held-item-effect"; import type { MoveId } from "#enums/move-id"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import { isNullOrUndefined, type NumberHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/nature-weight-booster.ts b/src/items/held-items/nature-weight-booster.ts index ec6e91eb437..1e810798f73 100644 --- a/src/items/held-items/nature-weight-booster.ts +++ b/src/items/held-items/nature-weight-booster.ts @@ -1,5 +1,6 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; export interface NatureWeightBoostParams { diff --git a/src/items/held-items/reset-negative-stat-stage.ts b/src/items/held-items/reset-negative-stat-stage.ts index 0714270cc51..acb935cd971 100644 --- a/src/items/held-items/reset-negative-stat-stage.ts +++ b/src/items/held-items/reset-negative-stat-stage.ts @@ -1,8 +1,9 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { BATTLE_STATS } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; -import { ConsumableHeldItem, HeldItemEffect } from "#items/held-item"; +import { ConsumableHeldItem } from "#items/held-item"; import i18next from "i18next"; export interface ResetNegativeStatStageParams { diff --git a/src/items/held-items/stat-booster.ts b/src/items/held-items/stat-booster.ts index aa7eaea93ec..bb66e2e6d22 100644 --- a/src/items/held-items/stat-booster.ts +++ b/src/items/held-items/stat-booster.ts @@ -1,9 +1,10 @@ import { pokemonEvolutions } from "#balance/pokemon-evolutions"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import type { SpeciesId } from "#enums/species-id"; import type { Stat } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import type { NumberHolder } from "#utils/common"; export interface StatBoostParams { diff --git a/src/items/held-items/survive-chance.ts b/src/items/held-items/survive-chance.ts index 4120e452d1a..b46297170d1 100644 --- a/src/items/held-items/survive-chance.ts +++ b/src/items/held-items/survive-chance.ts @@ -1,7 +1,8 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; +import { HeldItemEffect } from "#enums/held-item-effect"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import type { BooleanHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/turn-end-heal.ts b/src/items/held-items/turn-end-heal.ts index 598b9bc61fa..f6660fab935 100644 --- a/src/items/held-items/turn-end-heal.ts +++ b/src/items/held-items/turn-end-heal.ts @@ -1,7 +1,8 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; +import { HeldItemEffect } from "#enums/held-item-effect"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; import { PokemonHealPhase } from "#phases/pokemon-heal-phase"; import { toDmgValue } from "#utils/common"; import i18next from "i18next"; diff --git a/src/items/held-items/turn-end-status.ts b/src/items/held-items/turn-end-status.ts index d6cbe240fd1..72ccf440d4c 100644 --- a/src/items/held-items/turn-end-status.ts +++ b/src/items/held-items/turn-end-status.ts @@ -1,7 +1,8 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import type { HeldItemId } from "#enums/held-item-id"; import type { StatusEffect } from "#enums/status-effect"; import type { Pokemon } from "#field/pokemon"; -import { HeldItem, HeldItemEffect } from "#items/held-item"; +import { HeldItem } from "#items/held-item"; export interface TurnEndStatusParams { /** The pokemon with the item */ diff --git a/src/items/init-held-item-pools.ts b/src/items/init-held-item-pools.ts index 2a335246b73..3957faed1ca 100644 --- a/src/items/init-held-item-pools.ts +++ b/src/items/init-held-item-pools.ts @@ -1,42 +1,42 @@ import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { dailyStarterHeldItemPool, trainerHeldItemPool, wildHeldItemPool } from "#items/held-item-pool"; /** * Initialize the wild held item pool */ function initWildHeldItemPool() { - wildHeldItemPool[RewardTier.COMMON] = [{ entry: HeldItemCategoryId.BERRY, weight: 1 }]; - wildHeldItemPool[RewardTier.GREAT] = [{ entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 1 }]; - wildHeldItemPool[RewardTier.ULTRA] = [ + wildHeldItemPool[RarityTier.COMMON] = [{ entry: HeldItemCategoryId.BERRY, weight: 1 }]; + wildHeldItemPool[RarityTier.GREAT] = [{ entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 1 }]; + wildHeldItemPool[RarityTier.ULTRA] = [ { entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 5 }, { entry: HeldItemId.WHITE_HERB, weight: 0 }, ]; - wildHeldItemPool[RewardTier.ROGUE] = [{ entry: HeldItemId.LUCKY_EGG, weight: 4 }]; - wildHeldItemPool[RewardTier.MASTER] = [{ entry: HeldItemId.GOLDEN_EGG, weight: 1 }]; + wildHeldItemPool[RarityTier.ROGUE] = [{ entry: HeldItemId.LUCKY_EGG, weight: 4 }]; + wildHeldItemPool[RarityTier.MASTER] = [{ entry: HeldItemId.GOLDEN_EGG, weight: 1 }]; } /** * Initialize the trainer pokemon held item pool */ function initTrainerHeldItemPool() { - trainerHeldItemPool[RewardTier.COMMON] = [ + trainerHeldItemPool[RarityTier.COMMON] = [ { entry: HeldItemCategoryId.BERRY, weight: 8 }, { entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 3 }, ]; - trainerHeldItemPool[RewardTier.GREAT] = [{ entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 3 }]; - trainerHeldItemPool[RewardTier.ULTRA] = [ + trainerHeldItemPool[RarityTier.GREAT] = [{ entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 3 }]; + trainerHeldItemPool[RarityTier.ULTRA] = [ { entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 10 }, { entry: HeldItemId.WHITE_HERB, weight: 0 }, ]; - trainerHeldItemPool[RewardTier.ROGUE] = [ + trainerHeldItemPool[RarityTier.ROGUE] = [ { entry: HeldItemId.FOCUS_BAND, weight: 2 }, { entry: HeldItemId.LUCKY_EGG, weight: 4 }, { entry: HeldItemId.QUICK_CLAW, weight: 1 }, { entry: HeldItemId.GRIP_CLAW, weight: 1 }, { entry: HeldItemId.WIDE_LENS, weight: 1 }, ]; - trainerHeldItemPool[RewardTier.MASTER] = [ + trainerHeldItemPool[RarityTier.MASTER] = [ { entry: HeldItemId.KINGS_ROCK, weight: 1 }, { entry: HeldItemId.LEFTOVERS, weight: 1 }, { entry: HeldItemId.SHELL_BELL, weight: 1 }, @@ -47,26 +47,26 @@ function initTrainerHeldItemPool() { /** * Initialize the daily starter held item pool */ -function initDailyStarterModifierPool() { - dailyStarterHeldItemPool[RewardTier.COMMON] = [ +function initDailyStarterRewardPool() { + dailyStarterHeldItemPool[RarityTier.COMMON] = [ { entry: HeldItemCategoryId.BASE_STAT_BOOST, weight: 1 }, { entry: HeldItemCategoryId.BERRY, weight: 3 }, ]; - dailyStarterHeldItemPool[RewardTier.GREAT] = [{ entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 5 }]; - dailyStarterHeldItemPool[RewardTier.ULTRA] = [ + dailyStarterHeldItemPool[RarityTier.GREAT] = [{ entry: HeldItemCategoryId.TYPE_ATTACK_BOOSTER, weight: 5 }]; + dailyStarterHeldItemPool[RarityTier.ULTRA] = [ { entry: HeldItemId.REVIVER_SEED, weight: 4 }, { entry: HeldItemId.SOOTHE_BELL, weight: 1 }, { entry: HeldItemId.SOUL_DEW, weight: 1 }, { entry: HeldItemId.GOLDEN_PUNCH, weight: 1 }, ]; - dailyStarterHeldItemPool[RewardTier.ROGUE] = [ + dailyStarterHeldItemPool[RarityTier.ROGUE] = [ { entry: HeldItemId.GRIP_CLAW, weight: 5 }, { entry: HeldItemId.BATON, weight: 2 }, { entry: HeldItemId.FOCUS_BAND, weight: 5 }, { entry: HeldItemId.QUICK_CLAW, weight: 3 }, { entry: HeldItemId.KINGS_ROCK, weight: 3 }, ]; - dailyStarterHeldItemPool[RewardTier.MASTER] = [ + dailyStarterHeldItemPool[RarityTier.MASTER] = [ { entry: HeldItemId.LEFTOVERS, weight: 1 }, { entry: HeldItemId.SHELL_BELL, weight: 1 }, ]; @@ -76,5 +76,5 @@ export function initHeldItemPools() { // Default held item pools for specific scenarios initWildHeldItemPool(); initTrainerHeldItemPool(); - initDailyStarterModifierPool(); + initDailyStarterRewardPool(); } diff --git a/src/modifier/init-modifier-pools.ts b/src/items/init-reward-pools.ts similarity index 67% rename from src/modifier/init-modifier-pools.ts rename to src/items/init-reward-pools.ts index 88ebfec5a4d..ec1f8ee50ff 100644 --- a/src/modifier/init-modifier-pools.ts +++ b/src/items/init-reward-pools.ts @@ -1,37 +1,37 @@ /* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ -import type { initModifierTypes } from "#modifiers/modifier-type"; +import type { initRewards, Reward } from "#items/reward"; /* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ import { timedEventManager } from "#app/global-event-manager"; import { globalScene } from "#app/global-scene"; import { pokemonEvolutions } from "#balance/pokemon-evolutions"; -import { allHeldItems, allTrainerItems, modifierTypes } from "#data/data-lists"; +import { allHeldItems, allRewards, allTrainerItems } from "#data/data-lists"; import { MAX_PER_TYPE_POKEBALLS } from "#data/pokeball"; import { AbilityId } from "#enums/ability-id"; import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { PokeballType } from "#enums/pokeball"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; import { TrainerItemId } from "#enums/trainer-item-id"; import { Unlockables } from "#enums/unlockables"; import type { Pokemon } from "#field/pokemon"; +import { WeightedReward } from "#items/reward"; +import { rewardPool } from "#items/reward-pools"; import type { TurnEndStatusHeldItem } from "#items/turn-end-status"; -import { modifierPool } from "#modifiers/modifier-pools"; -import { WeightedModifierType } from "#modifiers/modifier-type"; -import type { WeightedModifierTypeWeightFunc } from "#types/modifier-types"; +import type { WeightedRewardWeightFunc } from "#types/rewards"; import { isNullOrUndefined } from "#utils/common"; /** * Initialize the common modifier pool */ -function initCommonModifierPool() { - modifierPool[RewardTier.COMMON] = [ - new WeightedModifierType(modifierTypes.POKEBALL, () => (hasMaximumBalls(PokeballType.POKEBALL) ? 0 : 6), 6), - new WeightedModifierType(modifierTypes.RARE_CANDY, 2), - new WeightedModifierType( - modifierTypes.POTION, +function initCommonRewardPool() { + rewardPool[RarityTier.COMMON] = [ + new WeightedReward(allRewards.POKEBALL, () => (hasMaximumBalls(PokeballType.POKEBALL) ? 0 : 6), 6), + new WeightedReward(allRewards.RARE_CANDY, 2), + new WeightedReward( + allRewards.POTION, (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter(p => p.getInverseHp() >= 10 && p.getHpRatio() <= 0.875 && !p.isFainted()).length, @@ -41,8 +41,8 @@ function initCommonModifierPool() { }, 9, ), - new WeightedModifierType( - modifierTypes.SUPER_POTION, + new WeightedReward( + allRewards.SUPER_POTION, (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter(p => p.getInverseHp() >= 25 && p.getHpRatio() <= 0.75 && !p.isFainted()).length, @@ -52,8 +52,8 @@ function initCommonModifierPool() { }, 3, ), - new WeightedModifierType( - modifierTypes.ETHER, + new WeightedReward( + allRewards.ETHER, (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter( @@ -71,8 +71,8 @@ function initCommonModifierPool() { }, 9, ), - new WeightedModifierType( - modifierTypes.MAX_ETHER, + new WeightedReward( + allRewards.MAX_ETHER, (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter( @@ -90,25 +90,22 @@ function initCommonModifierPool() { }, 3, ), - new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(TrainerItemId.LURE, 2)), - new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4), - new WeightedModifierType(modifierTypes.BERRY, 2), - new WeightedModifierType(modifierTypes.TM_COMMON, 2), - ].map(m => { - m.setTier(RewardTier.COMMON); - return m; - }); + new WeightedReward(allRewards.LURE, lureWeightFunc(TrainerItemId.LURE, 2)), + new WeightedReward(allRewards.TEMP_STAT_STAGE_BOOSTER, 4), + new WeightedReward(allRewards.BERRY, 2), + new WeightedReward(allRewards.TM_COMMON, 2), + ]; } /** * Initialize the Great modifier pool */ -function initGreatModifierPool() { - modifierPool[RewardTier.GREAT] = [ - new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6), - new WeightedModifierType(modifierTypes.PP_UP, 2), - new WeightedModifierType( - modifierTypes.FULL_HEAL, +function initGreatRewardPool() { + rewardPool[RarityTier.GREAT] = [ + new WeightedReward(allRewards.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6), + new WeightedReward(allRewards.PP_UP, 2), + new WeightedReward( + allRewards.FULL_HEAL, (party: Pokemon[]) => { const statusEffectPartyMemberCount = Math.min( party.filter( @@ -126,31 +123,31 @@ function initGreatModifierPool() { }, 18, ), - new WeightedModifierType( - modifierTypes.REVIVE, + new WeightedReward( + allRewards.REVIVE, (party: Pokemon[]) => { const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); return faintedPartyMemberCount * 9; }, 27, ), - new WeightedModifierType( - modifierTypes.MAX_REVIVE, + new WeightedReward( + allRewards.MAX_REVIVE, (party: Pokemon[]) => { const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); return faintedPartyMemberCount * 3; }, 9, ), - new WeightedModifierType( - modifierTypes.SACRED_ASH, + new WeightedReward( + allRewards.SACRED_ASH, (party: Pokemon[]) => { return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0; }, 1, ), - new WeightedModifierType( - modifierTypes.HYPER_POTION, + new WeightedReward( + allRewards.HYPER_POTION, (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.625 && !p.isFainted()).length, @@ -160,8 +157,8 @@ function initGreatModifierPool() { }, 9, ), - new WeightedModifierType( - modifierTypes.MAX_POTION, + new WeightedReward( + allRewards.MAX_POTION, (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length, @@ -171,8 +168,8 @@ function initGreatModifierPool() { }, 3, ), - new WeightedModifierType( - modifierTypes.FULL_RESTORE, + new WeightedReward( + allRewards.FULL_RESTORE, (party: Pokemon[]) => { const statusEffectPartyMemberCount = Math.min( party.filter( @@ -195,8 +192,8 @@ function initGreatModifierPool() { }, 3, ), - new WeightedModifierType( - modifierTypes.ELIXIR, + new WeightedReward( + allRewards.ELIXIR, (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter( @@ -214,8 +211,8 @@ function initGreatModifierPool() { }, 9, ), - new WeightedModifierType( - modifierTypes.MAX_ELIXIR, + new WeightedReward( + allRewards.MAX_ELIXIR, (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter( @@ -233,26 +230,26 @@ function initGreatModifierPool() { }, 3, ), - new WeightedModifierType(modifierTypes.DIRE_HIT, 4), - new WeightedModifierType(modifierTypes.SUPER_LURE, lureWeightFunc(TrainerItemId.SUPER_LURE, 4)), - new WeightedModifierType(modifierTypes.NUGGET, skipInLastClassicWaveOrDefault(5)), - new WeightedModifierType(modifierTypes.SPECIES_STAT_BOOSTER, 4), - new WeightedModifierType( - modifierTypes.EVOLUTION_ITEM, + new WeightedReward(allRewards.DIRE_HIT, 4), + new WeightedReward(allRewards.SUPER_LURE, lureWeightFunc(TrainerItemId.SUPER_LURE, 4)), + new WeightedReward(allRewards.NUGGET, skipInLastClassicWaveOrDefault(5)), + new WeightedReward(allRewards.SPECIES_STAT_BOOSTER, 4), + new WeightedReward( + allRewards.EVOLUTION_ITEM, () => { return Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15), 8); }, 8, ), - new WeightedModifierType( - modifierTypes.MAP, + new WeightedReward( + allRewards.MAP, () => (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex < 180 ? 2 : 0), 2, ), - new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2), - new WeightedModifierType(modifierTypes.TM_GREAT, 3), - new WeightedModifierType( - modifierTypes.MEMORY_MUSHROOM, + new WeightedReward(allRewards.SOOTHE_BELL, 2), + new WeightedReward(allRewards.TM_GREAT, 3), + new WeightedReward( + allRewards.MEMORY_MUSHROOM, (party: Pokemon[]) => { if (!party.find(p => p.getLearnableLevelMoves().length)) { return 0; @@ -264,8 +261,8 @@ function initGreatModifierPool() { }, 4, ), - new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), - new WeightedModifierType(modifierTypes.TERA_SHARD, (party: Pokemon[]) => + new WeightedReward(allRewards.BASE_STAT_BOOSTER, 3), + new WeightedReward(allRewards.TERA_SHARD, (party: Pokemon[]) => party.filter( p => !(p.hasSpecies(SpeciesId.TERAPAGOS) || p.hasSpecies(SpeciesId.OGERPON) || p.hasSpecies(SpeciesId.SHEDINJA)), @@ -273,8 +270,8 @@ function initGreatModifierPool() { ? 1 : 0, ), - new WeightedModifierType( - modifierTypes.DNA_SPLICERS, + new WeightedReward( + allRewards.DNA_SPLICERS, (party: Pokemon[]) => { if (party.filter(p => !p.fusionSpecies).length > 1) { if (globalScene.gameMode.isSplicedOnly) { @@ -288,39 +285,36 @@ function initGreatModifierPool() { }, 4, ), - new WeightedModifierType( - modifierTypes.VOUCHER, + new WeightedReward( + allRewards.VOUCHER, (_party: Pokemon[], rerollCount: number) => (!globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0), 1, ), - ].map(m => { - m.setTier(RewardTier.GREAT); - return m; - }); + ]; } /** * Initialize the Ultra modifier pool */ -function initUltraModifierPool() { - modifierPool[RewardTier.ULTRA] = [ - new WeightedModifierType(modifierTypes.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15), - new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(TrainerItemId.MAX_LURE, 4)), - new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), - new WeightedModifierType(modifierTypes.PP_MAX, 3), - new WeightedModifierType(modifierTypes.MINT, 4), - new WeightedModifierType( - modifierTypes.RARE_EVOLUTION_ITEM, +function initUltraRewardPool() { + rewardPool[RarityTier.ULTRA] = [ + new WeightedReward(allRewards.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15), + new WeightedReward(allRewards.MAX_LURE, lureWeightFunc(TrainerItemId.MAX_LURE, 4)), + new WeightedReward(allRewards.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), + new WeightedReward(allRewards.PP_MAX, 3), + new WeightedReward(allRewards.MINT, 4), + new WeightedReward( + allRewards.RARE_EVOLUTION_ITEM, () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15) * 4, 32), 32, ), - new WeightedModifierType( - modifierTypes.FORM_CHANGE_ITEM, + new WeightedReward( + allRewards.FORM_CHANGE_ITEM, () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, 24, ), - new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)), - new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => { + new WeightedReward(allRewards.AMULET_COIN, skipInLastClassicWaveOrDefault(3)), + new WeightedReward(allRewards.EVIOLITE, (party: Pokemon[]) => { const { gameMode, gameData } = globalScene; if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) { return party.some(p => { @@ -340,9 +334,9 @@ function initUltraModifierPool() { } return 0; }), - new WeightedModifierType(modifierTypes.RARE_SPECIES_STAT_BOOSTER, 12), - new WeightedModifierType( - modifierTypes.LEEK, + new WeightedReward(allRewards.RARE_SPECIES_STAT_BOOSTER, 12), + new WeightedReward( + allRewards.LEEK, (party: Pokemon[]) => { const checkedSpecies = [SpeciesId.FARFETCHD, SpeciesId.GALAR_FARFETCHD, SpeciesId.SIRFETCHD]; // If a party member doesn't already have a Leek and is one of the relevant species, Leek can appear @@ -357,8 +351,8 @@ function initUltraModifierPool() { }, 12, ), - new WeightedModifierType( - modifierTypes.TOXIC_ORB, + new WeightedReward( + allRewards.TOXIC_ORB, (party: Pokemon[]) => { return party.some(p => { const isHoldingOrb = p.getHeldItems().some(i => i in [HeldItemId.FLAME_ORB, HeldItemId.TOXIC_ORB]); @@ -403,8 +397,8 @@ function initUltraModifierPool() { }, 10, ), - new WeightedModifierType( - modifierTypes.FLAME_ORB, + new WeightedReward( + allRewards.FLAME_ORB, (party: Pokemon[]) => { return party.some(p => { const isHoldingOrb = p.getHeldItems().some(i => i in [HeldItemId.FLAME_ORB, HeldItemId.TOXIC_ORB]); @@ -449,8 +443,8 @@ function initUltraModifierPool() { }, 10, ), - new WeightedModifierType( - modifierTypes.MYSTICAL_ROCK, + new WeightedReward( + allRewards.MYSTICAL_ROCK, (party: Pokemon[]) => { return party.some(p => { const stack = p.heldItemManager.getStack(HeldItemId.MYSTICAL_ROCK); @@ -496,94 +490,88 @@ function initUltraModifierPool() { }, 10, ), - new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), - new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), - new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9), - new WeightedModifierType(modifierTypes.TM_ULTRA, 11), - new WeightedModifierType(modifierTypes.RARER_CANDY, 4), - new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), - new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)), - new WeightedModifierType(modifierTypes.EXP_CHARM, skipInLastClassicWaveOrDefault(8)), - new WeightedModifierType(modifierTypes.EXP_SHARE, skipInLastClassicWaveOrDefault(10)), - new WeightedModifierType( - modifierTypes.TERA_ORB, + new WeightedReward(allRewards.REVIVER_SEED, 4), + new WeightedReward(allRewards.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), + new WeightedReward(allRewards.ATTACK_TYPE_BOOSTER, 9), + new WeightedReward(allRewards.TM_ULTRA, 11), + new WeightedReward(allRewards.RARER_CANDY, 4), + new WeightedReward(allRewards.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), + new WeightedReward(allRewards.IV_SCANNER, skipInLastClassicWaveOrDefault(4)), + new WeightedReward(allRewards.EXP_CHARM, skipInLastClassicWaveOrDefault(8)), + new WeightedReward(allRewards.EXP_SHARE, skipInLastClassicWaveOrDefault(10)), + new WeightedReward( + allRewards.TERA_ORB, () => !globalScene.gameMode.isClassic ? Math.min(Math.max(Math.floor(globalScene.currentBattle.waveIndex / 50) * 2, 1), 4) : 0, 4, ), - new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), - new WeightedModifierType(modifierTypes.WIDE_LENS, 7), - ].map(m => { - m.setTier(RewardTier.ULTRA); - return m; - }); + new WeightedReward(allRewards.QUICK_CLAW, 3), + new WeightedReward(allRewards.WIDE_LENS, 7), + ]; } -function initRogueModifierPool() { - modifierPool[RewardTier.ROGUE] = [ - new WeightedModifierType(modifierTypes.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16), - new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), - new WeightedModifierType(modifierTypes.LEFTOVERS, 3), - new WeightedModifierType(modifierTypes.SHELL_BELL, 3), - new WeightedModifierType(modifierTypes.BERRY_POUCH, 4), - new WeightedModifierType(modifierTypes.GRIP_CLAW, 5), - new WeightedModifierType(modifierTypes.SCOPE_LENS, 4), - new WeightedModifierType(modifierTypes.BATON, 2), - new WeightedModifierType(modifierTypes.SOUL_DEW, 7), - new WeightedModifierType(modifierTypes.CATCHING_CHARM, () => (!globalScene.gameMode.isClassic ? 4 : 0), 4), - new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)), - new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), - new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), - new WeightedModifierType(modifierTypes.LOCK_CAPSULE, () => (globalScene.gameMode.isClassic ? 0 : 3)), - new WeightedModifierType(modifierTypes.SUPER_EXP_CHARM, skipInLastClassicWaveOrDefault(8)), - new WeightedModifierType( - modifierTypes.RARE_FORM_CHANGE_ITEM, +function initRogueRewardPool() { + rewardPool[RarityTier.ROGUE] = [ + new WeightedReward(allRewards.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16), + new WeightedReward(allRewards.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), + new WeightedReward(allRewards.LEFTOVERS, 3), + new WeightedReward(allRewards.SHELL_BELL, 3), + new WeightedReward(allRewards.BERRY_POUCH, 4), + new WeightedReward(allRewards.GRIP_CLAW, 5), + new WeightedReward(allRewards.SCOPE_LENS, 4), + new WeightedReward(allRewards.BATON, 2), + new WeightedReward(allRewards.SOUL_DEW, 7), + new WeightedReward(allRewards.CATCHING_CHARM, () => (!globalScene.gameMode.isClassic ? 4 : 0), 4), + new WeightedReward(allRewards.ABILITY_CHARM, skipInClassicAfterWave(189, 6)), + new WeightedReward(allRewards.FOCUS_BAND, 5), + new WeightedReward(allRewards.KINGS_ROCK, 3), + new WeightedReward(allRewards.LOCK_CAPSULE, () => (globalScene.gameMode.isClassic ? 0 : 3)), + new WeightedReward(allRewards.SUPER_EXP_CHARM, skipInLastClassicWaveOrDefault(8)), + new WeightedReward( + allRewards.RARE_FORM_CHANGE_ITEM, () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, 24, ), - new WeightedModifierType( - modifierTypes.MEGA_BRACELET, + new WeightedReward( + allRewards.MEGA_BRACELET, () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, 36, ), - new WeightedModifierType( - modifierTypes.DYNAMAX_BAND, + new WeightedReward( + allRewards.DYNAMAX_BAND, () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, 36, ), - new WeightedModifierType( - modifierTypes.VOUCHER_PLUS, + new WeightedReward( + allRewards.VOUCHER_PLUS, (_party: Pokemon[], rerollCount: number) => !globalScene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0, 3, ), - ].map(m => { - m.setTier(RewardTier.ROGUE); - return m; - }); + ]; } /** * Initialize the Master modifier pool */ -function initMasterModifierPool() { - modifierPool[RewardTier.MASTER] = [ - new WeightedModifierType(modifierTypes.MASTER_BALL, () => (hasMaximumBalls(PokeballType.MASTER_BALL) ? 0 : 24), 24), - new WeightedModifierType(modifierTypes.SHINY_CHARM, 14), - new WeightedModifierType(modifierTypes.HEALING_CHARM, 18), - new WeightedModifierType(modifierTypes.MULTI_LENS, 18), - new WeightedModifierType( - modifierTypes.VOUCHER_PREMIUM, +function initMasterRewardPool() { + rewardPool[RarityTier.MASTER] = [ + new WeightedReward(allRewards.MASTER_BALL, () => (hasMaximumBalls(PokeballType.MASTER_BALL) ? 0 : 24), 24), + new WeightedReward(allRewards.SHINY_CHARM, 14), + new WeightedReward(allRewards.HEALING_CHARM, 18), + new WeightedReward(allRewards.MULTI_LENS, 18), + new WeightedReward( + allRewards.VOUCHER_PREMIUM, (_party: Pokemon[], rerollCount: number) => !globalScene.gameMode.isDaily && !globalScene.gameMode.isEndless && !globalScene.gameMode.isSplicedOnly ? Math.max(5 - rerollCount * 2, 0) : 0, 5, ), - new WeightedModifierType( - modifierTypes.DNA_SPLICERS, + new WeightedReward( + allRewards.DNA_SPLICERS, (party: Pokemon[]) => !(globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) && !globalScene.gameMode.isSplicedOnly && @@ -592,8 +580,8 @@ function initMasterModifierPool() { : 0, 24, ), - new WeightedModifierType( - modifierTypes.MINI_BLACK_HOLE, + new WeightedReward( + allRewards.MINI_BLACK_HOLE, () => globalScene.gameMode.isDaily || (!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE)) @@ -601,33 +589,30 @@ function initMasterModifierPool() { : 0, 1, ), - ].map(m => { - m.setTier(RewardTier.MASTER); - return m; - }); + ]; } /** - * Initialize {@linkcode modifierPool} with the initial set of modifier types. - * {@linkcode initModifierTypes} MUST be called before this function. + * Initialize {@linkcode rewardPool} with the initial set of modifier types. + * {@linkcode initRewards} MUST be called before this function. */ -export function initModifierPools() { +export function initRewardPools() { // The modifier pools the player chooses from during modifier selection - initCommonModifierPool(); - initGreatModifierPool(); - initUltraModifierPool(); - initRogueModifierPool(); - initMasterModifierPool(); + initCommonRewardPool(); + initGreatRewardPool(); + initUltraRewardPool(); + initRogueRewardPool(); + initMasterRewardPool(); } /** - * High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on - * classic and skip an ModifierType if current wave is greater or equal to the one passed down + * High order function that returns a WeightedRewardWeightFunc that will only be applied on + * classic and skip an Reward if current wave is greater or equal to the one passed down * @param wave - Wave where we should stop showing the modifier - * @param defaultWeight - ModifierType default weight - * @returns A WeightedModifierTypeWeightFunc + * @param defaultWeight - Reward default weight + * @returns A WeightedRewardWeightFunc */ -function skipInClassicAfterWave(wave: number, defaultWeight: number): WeightedModifierTypeWeightFunc { +function skipInClassicAfterWave(wave: number, defaultWeight: number): WeightedRewardWeightFunc { return () => { const gameMode = globalScene.gameMode; const currentWave = globalScene.currentBattle.waveIndex; @@ -636,23 +621,23 @@ function skipInClassicAfterWave(wave: number, defaultWeight: number): WeightedMo } /** - * High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on - * classic and it will skip a ModifierType if it is the last wave pull. - * @param defaultWeight ModifierType default weight - * @returns A WeightedModifierTypeWeightFunc + * High order function that returns a WeightedRewardWeightFunc that will only be applied on + * classic and it will skip a Reward if it is the last wave pull. + * @param defaultWeight Reward default weight + * @returns A WeightedRewardWeightFunc */ -function skipInLastClassicWaveOrDefault(defaultWeight: number): WeightedModifierTypeWeightFunc { +function skipInLastClassicWaveOrDefault(defaultWeight: number): WeightedRewardWeightFunc { return skipInClassicAfterWave(199, defaultWeight); } /** - * High order function that returns a WeightedModifierTypeWeightFunc to ensure Lures don't spawn on Classic 199 + * High order function that returns a WeightedRewardWeightFunc to ensure Lures don't spawn on Classic 199 * or if the lure still has over 60% of its duration left * @param lureId The id of the lure type in question. * @param weight The desired weight for the lure when it does spawn - * @returns A WeightedModifierTypeWeightFunc + * @returns A WeightedRewardWeightFunc */ -function lureWeightFunc(lureId: TrainerItemId, weight: number): WeightedModifierTypeWeightFunc { +function lureWeightFunc(lureId: TrainerItemId, weight: number): WeightedRewardWeightFunc { return () => { const lureCount = globalScene.trainerItems.getStack(lureId); return !(globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex === 199) && diff --git a/src/items/init-trainer-item-pools.ts b/src/items/init-trainer-item-pools.ts index 1a3e5ccf27c..3b8de62f0bc 100644 --- a/src/items/init-trainer-item-pools.ts +++ b/src/items/init-trainer-item-pools.ts @@ -1,4 +1,4 @@ -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { TrainerItemId } from "#enums/trainer-item-id"; import { enemyBuffTokenPool } from "#items/trainer-item-pool"; @@ -6,7 +6,7 @@ import { enemyBuffTokenPool } from "#items/trainer-item-pool"; * Initialize the enemy buff modifier pool */ function initEnemyBuffTokenPool() { - enemyBuffTokenPool[RewardTier.COMMON] = [ + enemyBuffTokenPool[RarityTier.COMMON] = [ { entry: TrainerItemId.ENEMY_DAMAGE_BOOSTER, weight: 9 }, { entry: TrainerItemId.ENEMY_DAMAGE_REDUCTION, weight: 9 }, { entry: TrainerItemId.ENEMY_ATTACK_POISON_CHANCE, weight: 3 }, @@ -16,14 +16,14 @@ function initEnemyBuffTokenPool() { { entry: TrainerItemId.ENEMY_ENDURE_CHANCE, weight: 4 }, { entry: TrainerItemId.ENEMY_FUSED_CHANCE, weight: 1 }, ]; - enemyBuffTokenPool[RewardTier.GREAT] = [ + enemyBuffTokenPool[RarityTier.GREAT] = [ { entry: TrainerItemId.ENEMY_DAMAGE_BOOSTER, weight: 5 }, { entry: TrainerItemId.ENEMY_DAMAGE_REDUCTION, weight: 5 }, { entry: TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE, weight: 5 }, { entry: TrainerItemId.ENEMY_ENDURE_CHANCE, weight: 5 }, { entry: TrainerItemId.ENEMY_FUSED_CHANCE, weight: 1 }, ]; - enemyBuffTokenPool[RewardTier.ULTRA] = [ + enemyBuffTokenPool[RarityTier.ULTRA] = [ { entry: TrainerItemId.ENEMY_DAMAGE_BOOSTER, weight: 10 }, { entry: TrainerItemId.ENEMY_DAMAGE_REDUCTION, weight: 10 }, { entry: TrainerItemId.ENEMY_HEAL, weight: 10 }, @@ -31,8 +31,8 @@ function initEnemyBuffTokenPool() { { entry: TrainerItemId.ENEMY_ENDURE_CHANCE, weight: 10 }, { entry: TrainerItemId.ENEMY_FUSED_CHANCE, weight: 5 }, ]; - enemyBuffTokenPool[RewardTier.ROGUE] = []; - enemyBuffTokenPool[RewardTier.MASTER] = []; + enemyBuffTokenPool[RarityTier.ROGUE] = []; + enemyBuffTokenPool[RarityTier.MASTER] = []; } export function initTrainerItemPools() { diff --git a/src/items/item-overrides.ts b/src/items/item-overrides.ts new file mode 100644 index 00000000000..8e179d618fa --- /dev/null +++ b/src/items/item-overrides.ts @@ -0,0 +1,50 @@ +import { globalScene } from "#app/global-scene"; +import Overrides from "#app/overrides"; +import type { Pokemon } from "#field/pokemon"; +import type { HeldItemConfiguration } from "#items/held-item-data-types"; +import { assignItemsFromConfiguration } from "#items/held-item-pool"; +import type { TrainerItemConfiguration } from "#items/trainer-item-data-types"; + +/** + * Uses either `MODIFIER_OVERRIDE` in overrides.ts to set {@linkcode PersistentModifier}s for either: + * - The player + * - The enemy + * @param isPlayer {@linkcode boolean} for whether the player (`true`) or enemy (`false`) is being overridden + */ +export function overrideTrainerItems(isPlayer = true): void { + const trainerItemsOverride: TrainerItemConfiguration = isPlayer + ? Overrides.STARTING_TRAINER_ITEMS_OVERRIDE + : Overrides.OPP_TRAINER_ITEMS_OVERRIDE; + if (!trainerItemsOverride || trainerItemsOverride.length === 0 || !globalScene) { + return; + } + + // If it's the opponent, clear all of their current modifiers to avoid stacking + if (!isPlayer) { + globalScene.clearEnemyItems(); + } + + globalScene.assignTrainerItemsFromConfiguration(trainerItemsOverride, isPlayer); +} + +/** + * Uses either `HELD_ITEMS_OVERRIDE` in overrides.ts to set {@linkcode PokemonHeldItemModifier}s for either: + * - The first member of the player's team when starting a new game + * - An enemy {@linkcode Pokemon} being spawned in + * @param pokemon {@linkcode Pokemon} whose held items are being overridden + * @param isPlayer {@linkcode boolean} for whether the {@linkcode pokemon} is the player's (`true`) or an enemy (`false`) + */ +export function overrideHeldItems(pokemon: Pokemon, isPlayer = true): void { + const heldItemsOverride: HeldItemConfiguration = isPlayer + ? Overrides.STARTING_HELD_ITEMS_OVERRIDE + : Overrides.OPP_HELD_ITEMS_OVERRIDE; + if (!heldItemsOverride || heldItemsOverride.length === 0 || !globalScene) { + return; + } + + if (!isPlayer) { + pokemon.heldItemManager.clearItems(); + } + + assignItemsFromConfiguration(heldItemsOverride, pokemon); +} diff --git a/src/items/reward-defaults-tiers.ts b/src/items/reward-defaults-tiers.ts new file mode 100644 index 00000000000..2c720d97405 --- /dev/null +++ b/src/items/reward-defaults-tiers.ts @@ -0,0 +1,72 @@ +import { RewardId } from "#enums/reward-id"; +import { RarityTier } from "#enums/reward-tier"; + +export const rewardRarities = { + [RewardId.POKEBALL]: RarityTier.COMMON, + [RewardId.GREAT_BALL]: RarityTier.GREAT, + [RewardId.ULTRA_BALL]: RarityTier.ULTRA, + [RewardId.ROGUE_BALL]: RarityTier.ROGUE, + [RewardId.MASTER_BALL]: RarityTier.MASTER, + + [RewardId.VOUCHER]: RarityTier.GREAT, + [RewardId.VOUCHER_PLUS]: RarityTier.ROGUE, + [RewardId.VOUCHER_PREMIUM]: RarityTier.MASTER, + + [RewardId.NUGGET]: RarityTier.GREAT, + [RewardId.BIG_NUGGET]: RarityTier.ULTRA, + [RewardId.RELIC_GOLD]: RarityTier.ROGUE, + + [RewardId.RARE_CANDY]: RarityTier.COMMON, + [RewardId.RARER_CANDY]: RarityTier.ULTRA, + + [RewardId.EVOLUTION_ITEM]: RarityTier.GREAT, + [RewardId.RARE_EVOLUTION_ITEM]: RarityTier.ULTRA, + + [RewardId.POTION]: RarityTier.COMMON, + [RewardId.SUPER_POTION]: RarityTier.COMMON, + [RewardId.HYPER_POTION]: RarityTier.GREAT, + [RewardId.MAX_POTION]: RarityTier.GREAT, + [RewardId.FULL_HEAL]: RarityTier.GREAT, + [RewardId.FULL_RESTORE]: RarityTier.GREAT, + + [RewardId.REVIVE]: RarityTier.GREAT, + [RewardId.MAX_REVIVE]: RarityTier.GREAT, + [RewardId.SACRED_ASH]: RarityTier.GREAT, + + [RewardId.ETHER]: RarityTier.COMMON, + [RewardId.MAX_ETHER]: RarityTier.COMMON, + + [RewardId.ELIXIR]: RarityTier.GREAT, + [RewardId.MAX_ELIXIR]: RarityTier.GREAT, + + [RewardId.PP_UP]: RarityTier.GREAT, + [RewardId.PP_MAX]: RarityTier.ULTRA, + + [RewardId.TM_COMMON]: RarityTier.COMMON, + [RewardId.TM_GREAT]: RarityTier.GREAT, + [RewardId.TM_ULTRA]: RarityTier.ULTRA, + + [RewardId.MINT]: RarityTier.ULTRA, + [RewardId.TERA_SHARD]: RarityTier.GREAT, + [RewardId.MEMORY_MUSHROOM]: RarityTier.GREAT, + [RewardId.DNA_SPLICERS]: RarityTier.MASTER, + + [RewardId.SPECIES_STAT_BOOSTER]: RarityTier.GREAT, + [RewardId.RARE_SPECIES_STAT_BOOSTER]: RarityTier.ULTRA, + [RewardId.BASE_STAT_BOOSTER]: RarityTier.GREAT, + [RewardId.ATTACK_TYPE_BOOSTER]: RarityTier.ULTRA, + [RewardId.BERRY]: RarityTier.COMMON, + + [RewardId.TEMP_STAT_STAGE_BOOSTER]: RarityTier.COMMON, + [RewardId.LURE]: RarityTier.COMMON, + [RewardId.SUPER_LURE]: RarityTier.GREAT, + [RewardId.MAX_LURE]: RarityTier.ULTRA, + + [RewardId.FORM_CHANGE_ITEM]: RarityTier.ULTRA, + [RewardId.RARE_FORM_CHANGE_ITEM]: RarityTier.ROGUE, +}; + +export function getRewardTier(reward: RewardId): RarityTier { + const tier = rewardRarities[reward]; + return tier ?? RarityTier.LUXURY; +} diff --git a/src/items/reward-pool-utils.ts b/src/items/reward-pool-utils.ts new file mode 100644 index 00000000000..7a9886a76f2 --- /dev/null +++ b/src/items/reward-pool-utils.ts @@ -0,0 +1,340 @@ +import { globalScene } from "#app/global-scene"; +import Overrides from "#app/overrides"; +import { allRewards } from "#data/data-lists"; +import { RewardPoolType } from "#enums/reward-pool-type"; +import { RarityTier } from "#enums/reward-tier"; +import type { PlayerPokemon, Pokemon } from "#field/pokemon"; +import type { RewardFunc, RewardPool, RewardPoolWeights } from "#types/rewards"; +import { isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common"; +import { getPartyLuckValue } from "#utils/party"; +import { type Reward, RewardGenerator, RewardOption, type RewardOverride, TrainerItemReward } from "./reward"; +import { rewardPool, rewardPoolWeights } from "./reward-pools"; +import { getRewardDefaultTier } from "./reward-utils"; + +/* +This file still contains several functions to generate rewards from pools. The hierarchy of these functions is explained here. + +At the top of the food chain is `generatePlayerRewardOptions`, which is responsible for creating item rewards for the player. +It can take a `CustomRewardSettings` to fix any number of rewards or tiers, then fills the remaining spots randomly. +Note that this function generates `RewardOption` instances, not yet `Reward`s. +Currently, there is only one reward pool, but in the future we will want to allow for custom pools. + +The function `getNewRewardOption` is responsible for generating a single RewardOption from a given pool and set of weights. +Note that, in the previous system, this function could in principle generate rewards for enemies, which was used in some +cases to assign modifiers. This usage is now deprecated, as we have separate pools for held items and trainer items for enemies. + +However, `getNewRewardOption` is not called directly by `generatePlayerRewardOptions`. Instead, it is filtered +by `getRewardOptionWithRetry`, which also checks existing rewards to minimize the chance of duplicates. + +Note that the pool contains `WeightedReward` instances, which contain either a `Reward` or a `RewardGenerator`. +Once a pool entry is chosen, a specific `Reward` is generated accordingly and put in the returned `RewardOption`. + +This will allow more customization in creating pools for challenges, MEs etc. +*/ + +export interface CustomRewardSettings { + guaranteedRarityTiers?: RarityTier[]; + guaranteedRewardOptions?: RewardOption[]; + /** If specified, will override the next X items to be auto-generated from specific reward functions (these don't have to be pre-genned). */ + guaranteedRewardFuncs?: RewardFunc[]; + /** + * If set to `true`, will fill the remainder of shop items that were not overridden by the 3 options above, up to the `count` param value. + * @example + * ```ts + * count = 4; + * customRewardSettings = { guaranteedRarityTiers: [RarityTier.GREAT], fillRemaining: true }; + * ``` + * The first item in the shop will be `GREAT` tier, and the remaining `3` items will be generated normally. + * + * If `fillRemaining: false` in the same scenario, only 1 `GREAT` tier item will appear in the shop (regardless of the value of `count`). + * @defaultValue `false` + */ + fillRemaining?: boolean; + /** If specified, can adjust the amount of money required for a shop reroll. If set to a negative value, the shop will not allow rerolls at all. */ + rerollMultiplier?: number; + /** + * If `false`, will prevent set item tiers from upgrading via luck. + * @defaultValue `true` + */ + allowLuckUpgrades?: boolean; +} + +/** + * Generates weights for a {@linkcode RewardPool}. An array of weights is generated for each rarity tier. Weights can be 0. + * @param pool - The pool for which weights must be generated + * @param party - Party is required for generating the weights + * @param rerollCount - (Optional) Needed for weights of vouchers. + */ +export function generateRewardPoolWeights(pool: RewardPool, party: Pokemon[], rerollCount = 0) { + for (const tier of Object.keys(pool)) { + const poolWeights = pool[tier].map(w => { + if (w.reward instanceof TrainerItemReward) { + const id = w.reward.itemId; + if (globalScene.trainerItems.isMaxStack(id)) { + return 0; + } + } + if (typeof w.weight === "number") { + return w.weight; + } + return w.weight(party, rerollCount); + }); + rewardPoolWeights[tier] = poolWeights; + } +} + +/** + * Generates a random RarityTier to draw rewards from the pool. The probabilities are: + * 1/1024 (Master tier) + * 12/1024 (Rogue tier) + * 48/1024 (Ultra tier) + * 195/1024 (Great tier) + * 768/1024 (Common tier) + * return {@linkcode RarityTier} + */ +function randomBaseTier(): RarityTier { + const tierValue = randSeedInt(1024); + + if (tierValue > 255) { + return RarityTier.COMMON; + } + if (tierValue > 60) { + return RarityTier.GREAT; + } + if (tierValue > 12) { + return RarityTier.ULTRA; + } + if (tierValue) { + return RarityTier.ROGUE; + } + return RarityTier.MASTER; +} + +/** + * Determines the upgrade count for a given rarity tier, based on the party luck. Will not update + * if the pool would have no entries at the new rarity. + * @param pool - RewardPool from which the reward will be generated + * @param baseTier - The initial tier to upgrade + * @param party - Party of the trainer using the item + * return {@linkcode RarityTier} + */ +function getRarityUpgradeCount(pool: RewardPool, baseTier: RarityTier, party: Pokemon[]): RarityTier { + let upgradeCount = 0; + if (baseTier < RarityTier.MASTER) { + const partyLuckValue = getPartyLuckValue(party); + const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4)); + while (pool.hasOwnProperty(baseTier + upgradeCount + 1) && pool[baseTier + upgradeCount + 1].length) { + if (randSeedInt(upgradeOdds) < 4) { + upgradeCount++; + } else { + break; + } + } + } + return upgradeCount; +} + +/** + * Generates reward options for a {@linkcode SelectRewardPhase} + * @param count - Determines the number of items to generate + * @param party - Party is required for generating proper reward pools + * @param rarityTiers - (Optional) If specified, rolls items in the specified tiers. Commonly used for tier-locking with Lock Capsule. + * @param customRewardSettings - (Optional) See {@linkcode CustomRewardSettings} + */ +export function generatePlayerRewardOptions( + count: number, + party: PlayerPokemon[], + rarityTiers?: RarityTier[], + customRewardSettings?: CustomRewardSettings, +): RewardOption[] { + const options: RewardOption[] = []; + const retryCount = Math.min(count * 5, 50); + // TODO: Change this to allow for custom reward pools + const pool = getRewardPoolForType(RewardPoolType.PLAYER); + const weights = getRewardWeightsForType(RewardPoolType.PLAYER); + if (!customRewardSettings) { + for (let i = 0; i < count; i++) { + const tier = rarityTiers && rarityTiers.length > i ? rarityTiers[i] : undefined; + options.push(getRewardOptionWithRetry(pool, weights, options, retryCount, party, tier)); + } + } else { + // Guaranteed mod options first + if (customRewardSettings?.guaranteedRewardOptions && customRewardSettings.guaranteedRewardOptions.length > 0) { + options.push(...customRewardSettings.guaranteedRewardOptions!); + } + + // Guaranteed mod functions second + if (customRewardSettings.guaranteedRewardFuncs && customRewardSettings.guaranteedRewardFuncs.length > 0) { + customRewardSettings.guaranteedRewardFuncs!.forEach((mod, _i) => { + const rewardId = Object.keys(allRewards).find(k => allRewards[k] === mod) as string; + const guaranteedMod: Reward = allRewards[rewardId]?.(); + + // Populates item id and tier + const guaranteedModTier = getRewardDefaultTier(guaranteedMod); + + const modType = guaranteedMod instanceof RewardGenerator ? guaranteedMod.generateReward(party) : guaranteedMod; + if (modType) { + const option = new RewardOption(modType, 0, guaranteedModTier); + options.push(option); + } + }); + } + + // Guaranteed tiers third + if (customRewardSettings.guaranteedRarityTiers && customRewardSettings.guaranteedRarityTiers.length > 0) { + const allowLuckUpgrades = customRewardSettings.allowLuckUpgrades ?? true; + for (const tier of customRewardSettings.guaranteedRarityTiers) { + options.push(getRewardOptionWithRetry(pool, weights, options, retryCount, party, tier, allowLuckUpgrades)); + } + } + + // Fill remaining + if (options.length < count && customRewardSettings.fillRemaining) { + while (options.length < count) { + options.push(getRewardOptionWithRetry(pool, weights, options, retryCount, party, undefined)); + } + } + } + + // Applies overrides for testing + overridePlayerRewardOptions(options, party); + + return options; +} + +/** + * Will generate a {@linkcode RewardOption} from the {@linkcode RewardPoolType.PLAYER} pool, attempting to retry duplicated items up to retryCount + * @param pool - {@linkcode RewardPool} to generate items from + * @param weights - {@linkcode RewardPoolWeights} to use when generating items + * @param existingOptions Currently generated options + * @param retryCount How many times to retry before allowing a dupe item + * @param party Current player party, used to calculate items in the pool + * @param tier If specified will generate item of tier + * @param allowLuckUpgrades `true` to allow items to upgrade tiers (the little animation that plays and is affected by luck) + */ +function getRewardOptionWithRetry( + pool: RewardPool, + weights: RewardPoolWeights, + existingOptions: RewardOption[], + retryCount: number, + party: PlayerPokemon[], + tier?: RarityTier, + allowLuckUpgrades?: boolean, +): RewardOption { + allowLuckUpgrades = allowLuckUpgrades ?? true; + let candidate = getNewRewardOption(pool, weights, party, tier, undefined, 0, allowLuckUpgrades); + let r = 0; + while ( + existingOptions.length && + ++r < retryCount && + //TODO: Improve this condition to refine what counts as a dupe + existingOptions.filter(o => o.type.name === candidate?.type.name || o.type.group === candidate?.type.group).length + ) { + console.log("Retry count:", r); + console.log(candidate?.type.group); + console.log(candidate?.type.name); + console.log(existingOptions.filter(o => o.type.name === candidate?.type.name).length); + console.log(existingOptions.filter(o => o.type.group === candidate?.type.group).length); + candidate = getNewRewardOption( + pool, + weights, + party, + candidate?.type.tier ?? tier, + candidate?.upgradeCount, + 0, + allowLuckUpgrades, + ); + } + return candidate!; +} + +/** + * Generates a Reward from the specified pool + * @param pool - {@linkcode RewardPool} to generate items from + * @param weights - {@linkcode RewardPoolWeights} to use when generating items + * @param party - party of the trainer using the item + * @param baseTier - If specified, will override the initial tier of an item (can still upgrade with luck) + * @param upgradeCount - If defined, means that this is a new Reward being generated to override another via luck upgrade. Used for recursive logic + * @param retryCount - Max allowed tries before the next tier down is checked for a valid Reward + * @param allowLuckUpgrades - Default true. If false, will not allow Reward to randomly upgrade to next tier + */ +function getNewRewardOption( + pool: RewardPool, + weights: RewardPoolWeights, + party: PlayerPokemon[], + baseTier?: RarityTier, + upgradeCount?: number, + retryCount = 0, + allowLuckUpgrades = true, +): RewardOption | null { + let tier = 0; + if (isNullOrUndefined(baseTier)) { + baseTier = randomBaseTier(); + } + if (isNullOrUndefined(upgradeCount)) { + upgradeCount = allowLuckUpgrades ? getRarityUpgradeCount(pool, baseTier, party) : 0; + tier = baseTier + upgradeCount; + } else { + tier = baseTier; + } + + const tierWeights = weights[tier]; + const index = pickWeightedIndex(tierWeights); + + if (index === undefined) { + return null; + } + + let reward: Reward | RewardGenerator | null = pool[tier][index].reward; + if (reward instanceof RewardGenerator) { + reward = (reward as RewardGenerator).generateReward(party); + if (reward === null) { + console.log(RarityTier[tier], upgradeCount); + return getNewRewardOption(pool, weights, party, tier, upgradeCount, ++retryCount); + } + } + + console.log(reward); + + return new RewardOption(reward as Reward, upgradeCount!, tier); // TODO: is this bang correct? +} + +/** + * Replaces the {@linkcode Reward} of the entries within {@linkcode options} with any + * {@linkcode RewardOverride} entries listed in {@linkcode Overrides.REWARD_OVERRIDE} + * up to the smallest amount of entries between {@linkcode options} and the override array. + * @param options Array of naturally rolled {@linkcode RewardOption}s + * @param party Array of the player's current party + */ +export function overridePlayerRewardOptions(options: RewardOption[], party: PlayerPokemon[]) { + const minLength = Math.min(options.length, Overrides.REWARD_OVERRIDE.length); + for (let i = 0; i < minLength; i++) { + const override: RewardOverride = Overrides.REWARD_OVERRIDE[i]; + const rewardFunc = allRewards[override.name]; + let reward: Reward | RewardGenerator | null = rewardFunc(); + + if (reward instanceof RewardGenerator) { + const pregenArgs = "type" in override && override.type !== null ? [override.type] : undefined; + reward = reward.generateReward(party, pregenArgs); + } + + if (reward) { + options[i].type = reward; + options[i].tier = getRewardDefaultTier(reward); + } + } +} + +export function getRewardPoolForType(poolType: RewardPoolType): RewardPool { + switch (poolType) { + case RewardPoolType.PLAYER: + return rewardPool; + } +} + +export function getRewardWeightsForType(poolType: RewardPoolType): RewardPoolWeights { + switch (poolType) { + case RewardPoolType.PLAYER: + return rewardPoolWeights; + } +} diff --git a/src/items/reward-pools.ts b/src/items/reward-pools.ts new file mode 100644 index 00000000000..12affd1c5df --- /dev/null +++ b/src/items/reward-pools.ts @@ -0,0 +1,9 @@ +/* + * Contains modifier pools for different contexts in the game. + * Can be safely imported without worrying about circular dependencies. + */ + +import type { RewardPool, RewardPoolWeights } from "#types/rewards"; + +export const rewardPool: RewardPool = {}; +export const rewardPoolWeights: RewardPoolWeights = {}; diff --git a/src/items/reward-utils.ts b/src/items/reward-utils.ts new file mode 100644 index 00000000000..f9872738fe9 --- /dev/null +++ b/src/items/reward-utils.ts @@ -0,0 +1,115 @@ +import { globalScene } from "#app/global-scene"; +import { allRewards } from "#data/data-lists"; +import type { HeldItemId } from "#enums/held-item-id"; +import { getRewardCategory, RewardCategoryId, RewardId } from "#enums/reward-id"; +import type { RarityTier } from "#enums/reward-tier"; +import type { TrainerItemId } from "#enums/trainer-item-id"; +import type { RewardFunc, RewardPoolId } from "#types/rewards"; +import { getHeldItemTier } from "./held-item-default-tiers"; +import { + type HeldItemReward, + type PokemonMoveReward, + type RememberMoveReward, + type Reward, + RewardGenerator, + RewardOption, + type TmReward, + type TrainerItemReward, +} from "./reward"; +import { getRewardTier } from "./reward-defaults-tiers"; +import { getTrainerItemTier } from "./trainer-item-default-tiers"; + +export function isTmReward(reward: Reward): reward is TmReward { + return getRewardCategory(reward.id) === RewardCategoryId.TM; +} + +export function isMoveReward(reward: Reward): reward is PokemonMoveReward { + const categoryId = getRewardCategory(reward.id); + return categoryId === RewardCategoryId.ETHER || categoryId === RewardCategoryId.PP_UP; +} + +export function isRememberMoveReward(reward: Reward): reward is RememberMoveReward { + return reward.id === RewardId.MEMORY_MUSHROOM; +} + +/** + * Generates a Reward from a given function + * @param rewardFunc + * @param pregenArgs Can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. + */ +export function generateReward(rewardFunc: RewardFunc, pregenArgs?: any[]): Reward | null { + const reward = rewardFunc(); + return reward instanceof RewardGenerator ? reward.generateReward(globalScene.getPlayerParty(), pregenArgs) : reward; +} + +/** + * Generates a Reward Option from a given function + * @param rewardFunc + * @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. + */ +export function generateRewardOption(rewardFunc: RewardFunc, pregenArgs?: any[]): RewardOption | null { + const reward = generateReward(rewardFunc, pregenArgs); + if (reward) { + const tier = getRewardDefaultTier(reward); + return new RewardOption(reward, 0, tier); + } + return null; +} + +/** + * Finds the default rarity tier for a given reward. For unique held item or trainer item rewards, + * falls back to the default rarity tier for the item. + * @param reward The {@linkcode Reward} to determine the tier for. + */ +export function getRewardDefaultTier(reward: Reward): RarityTier { + if (reward.id === RewardId.HELD_ITEM) { + return getHeldItemTier((reward as HeldItemReward).itemId); + } + if (reward.id === RewardId.TRAINER_ITEM) { + return getTrainerItemTier((reward as TrainerItemReward).itemId); + } + return getRewardTier(reward.id); +} + +export function getPlayerShopRewardOptionsForWave(waveIndex: number, baseCost: number): RewardOption[] { + if (!(waveIndex % 10)) { + return []; + } + + const options = [ + [ + new RewardOption(allRewards.POTION(), 0, baseCost * 0.2), + new RewardOption(allRewards.ETHER(), 0, baseCost * 0.4), + new RewardOption(allRewards.REVIVE(), 0, baseCost * 2), + ], + [ + new RewardOption(allRewards.SUPER_POTION(), 0, baseCost * 0.45), + new RewardOption(allRewards.FULL_HEAL(), 0, baseCost), + ], + [new RewardOption(allRewards.ELIXIR(), 0, baseCost), new RewardOption(allRewards.MAX_ETHER(), 0, baseCost)], + [ + new RewardOption(allRewards.HYPER_POTION(), 0, baseCost * 0.8), + new RewardOption(allRewards.MAX_REVIVE(), 0, baseCost * 2.75), + new RewardOption(allRewards.MEMORY_MUSHROOM(), 0, baseCost * 4), + ], + [ + new RewardOption(allRewards.MAX_POTION(), 0, baseCost * 1.5), + new RewardOption(allRewards.MAX_ELIXIR(), 0, baseCost * 2.5), + ], + [new RewardOption(allRewards.FULL_RESTORE(), 0, baseCost * 2.25)], + [new RewardOption(allRewards.SACRED_ASH(), 0, baseCost * 10)], + ]; + return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat(); +} + +export function isRewardId(id: RewardPoolId): id is RewardId { + return id > 0x2000; +} + +export function isTrainerItemId(id: RewardPoolId): id is TrainerItemId { + return id > 0x1000 && id < 0x2000; +} + +export function isHeldItemId(id: RewardPoolId): id is HeldItemId { + return id < 0x1000; +} diff --git a/src/items/reward.ts b/src/items/reward.ts new file mode 100644 index 00000000000..244b80e1314 --- /dev/null +++ b/src/items/reward.ts @@ -0,0 +1,1852 @@ +import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; +import { globalScene } from "#app/global-scene"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { EvolutionItem, FusionSpeciesFormEvolution, pokemonEvolutions } from "#balance/pokemon-evolutions"; +import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#balance/starters"; +import { tmPoolTiers, tmSpecies } from "#balance/tms"; +import { allHeldItems, allMoves, allRewards, allTrainerItems } from "#data/data-lists"; +import { getLevelTotalExp } from "#data/exp"; +import { SpeciesFormChangeItemTrigger } from "#data/form-change-triggers"; +import { getNatureName, getNatureStatMultiplier } from "#data/nature"; +import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#data/pokeball"; +import { pokemonFormChanges, SpeciesFormChangeCondition } from "#data/pokemon-forms"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { BerryType } from "#enums/berry-type"; +import { FormChangeItem } from "#enums/form-change-item"; +import { HeldItemId } from "#enums/held-item-id"; +import { LearnMoveType } from "#enums/learn-move-type"; +import { MoveId } from "#enums/move-id"; +import { Nature } from "#enums/nature"; +import { PokeballType } from "#enums/pokeball"; +import { PokemonType } from "#enums/pokemon-type"; +import { RewardId } from "#enums/reward-id"; +import { RarityTier } from "#enums/reward-tier"; +import { SpeciesFormKey } from "#enums/species-form-key"; +import { SpeciesId } from "#enums/species-id"; +import type { PermanentStat, TempBattleStat } from "#enums/stat"; +import { Stat, TEMP_BATTLE_STATS } from "#enums/stat"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import type { PlayerPokemon, Pokemon } from "#field/pokemon"; +import { attackTypeToHeldItem } from "#items/attack-type-booster"; +import { permanentStatToHeldItem, statBoostItems } from "#items/base-stat-booster"; +import { berryTypeToHeldItem } from "#items/berry"; +import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#items/held-item-pool"; +import { formChangeItemName } from "#items/item-utility"; +import { + SPECIES_STAT_BOOSTER_ITEMS, + type SpeciesStatBoosterItemId, + type SpeciesStatBoostHeldItem, +} from "#items/stat-booster"; +import { TrainerItemEffect, tempStatToTrainerItem } from "#items/trainer-item"; +import type { PokemonMove } from "#moves/pokemon-move"; +import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#system/voucher"; +import type { RewardFunc, WeightedRewardWeightFunc } from "#types/rewards"; +import type { Exact } from "#types/type-helpers"; +import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#ui/party-ui-handler"; +import { PartyUiHandler } from "#ui/party-ui-handler"; +import { formatMoney, NumberHolder, padInt, randSeedInt, randSeedItem } from "#utils/common"; +import { getEnumKeys, getEnumValues } from "#utils/enums"; +import i18next from "i18next"; + +/* +The term "Reward" refers to items the player can access in the post-battle screen (although +they may be used in other places of the code as well). + +Examples include (but are not limited to): +- Potions and other healing items +- Held items and trainer items +- Money items such as nugget and ancient relic + +Rewards have a basic structure with a name, description, and icon. These are used to display +the reward in the reward select screen. All rewards have an .apply() method, which applies the +effect, for example: +- Apply healing to a pokemon +- Assign a held item to a pokemon, or a trainer item to the player +- Add money + +Some rewards, once clicked, simply have their effect---these are Rewards that add money, pokéball, +vouchers, or global effect such as Sacred Ash. +Most rewards require extra parameters. They are divided into subclasses depending on the parameters +that they need, in particular: +- PokemonReward requires to pass a Pokemon (to apply healing, assign item...) +- PokemonMoveReward requires to pass a Pokemon and a move (for Elixir, or PP Up) +Plus some edge cases for Memory Mushroom and DNA Splicers. + +The parameters to be passed are generated by the .applyReward() function in SelectRewardPhase. +This function takes care of opening the party screen and letting the player select a party pokemon, +a move, etc. depending on what is required. Once the parameters are generated, instead of calling +.apply() directly, we call the .applyReward() method in BattleScene, which also plays the sound. +[This method could perhaps be removed]. + +Rewards are assigned RewardId, and there are also RewardCategoryId. For example, TM is a RewardCategoryId, +while CommonTM, RareTM etc are RewardIds. There is _not_ a RewardId for _each_ move. Similarly, +some specific categories of held items are assigned their own RewardId, but they all fall under a single +RewardCategoryId. + +rewardInitObj plays a similar role to allHeldItems, except instead of containing all possible reward +instances, it instead contains functions that generate those rewards. Here, the keys used are strings +rather than RewardId, the difference exists because here we want to distinguish unique held items +for example. The entries of rewardInitObj are used in the RewardPool. + +There are some more derived classes, in particular: +RewardGenerator, which creates Reward instances from a certain group (e.g. TMs, nature mints, or berries); +WeightedReward, which is a Reward with an attached weight or weight function to be used in pools; +and RewardOption, which is displayed during the select reward phase at the end of each encounter. +*/ + +export abstract class Reward { + // TODO: If all we care about for categorization is the reward's ID's _category_, why not do it there? + // TODO: Make abstract and readonly + public id: RewardId; + public localeKey: string; + public iconImage: string; + public group: string; // TODO: Make a union type of all groups + public soundName: string; + public tier: RarityTier; + + constructor(localeKey: string | null, iconImage: string | null, group?: string, soundName?: string) { + this.localeKey = localeKey!; // TODO: is this bang correct? + this.iconImage = iconImage!; // TODO: is this bang correct? + this.group = group!; // TODO: is this bang correct? + this.soundName = soundName ?? "se/restore"; + } + + get name(): string { + return i18next.t(`${this.localeKey}.name`); + } + + getDescription(): string { + return i18next.t(`${this.localeKey}.description`); + } + + getIcon(): string { + return this.iconImage; + } + + // TODO: Should this be abstract? + /** + * Check whether this reward should be applied. + */ + // TODO: This is erroring on stuff of typ + shouldApply(_params: Exact[0]>): boolean { + return true; + } + + /** Apply this Reward's effects. */ + // TODO: Remove `boolean` return from all superclasses' type signatures + abstract apply(_params?: unknown): void; +} + +// TODO: Can this return null? +// TODO: Make this generic based on T +type RewardGeneratorFunc = (party: Pokemon[], pregenArgs?: any[]) => T | null; + +export abstract class RewardGenerator { + private genRewardFunc: RewardGeneratorFunc; + public id: RewardId; + + constructor(genRewardFunc: RewardGeneratorFunc) { + this.genRewardFunc = genRewardFunc; + } + + generateReward(party: Pokemon[], pregenArgs?: any[]) { + const ret = this.genRewardFunc(party, pregenArgs); + if (ret && this.id) { + ret.id = this.id; + } + return ret; + } +} + +export class AddPokeballReward extends Reward { + private pokeballType: PokeballType; + private count: number; + + constructor(iconImage: string, pokeballType: PokeballType, count: number, id: RewardId) { + super("", iconImage, "pb", "se/pb_bounce_1"); + this.pokeballType = pokeballType; + this.count = count; + this.id = id; + } + + get name(): string { + return i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", { + modifierCount: this.count, + pokeballName: getPokeballName(this.pokeballType), + }); + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.AddPokeballModifierType.description", { + modifierCount: this.count, + pokeballName: getPokeballName(this.pokeballType), + catchRate: + getPokeballCatchMultiplier(this.pokeballType) > -1 + ? `${getPokeballCatchMultiplier(this.pokeballType)}x` + : "100%", + pokeballAmount: `${globalScene.pokeballCounts[this.pokeballType]}`, + }); + } + + /** + * Applies {@linkcode AddPokeballReward} + * @returns always `true` + */ + apply(): boolean { + const pokeballCounts = globalScene.pokeballCounts; + pokeballCounts[this.pokeballType] = Math.min( + pokeballCounts[this.pokeballType] + this.count, + MAX_PER_TYPE_POKEBALLS, + ); + + return true; + } +} + +export class AddVoucherReward extends Reward { + private voucherType: VoucherType; + private count: number; + + constructor(voucherType: VoucherType, count: number, id: RewardId) { + super("", getVoucherTypeIcon(voucherType), "voucher"); + this.count = count; + this.voucherType = voucherType; + this.id = id; + } + + get name(): string { + return i18next.t("modifierType:ModifierType.AddVoucherConsumableType.name", { + modifierCount: this.count, + voucherTypeName: getVoucherTypeName(this.voucherType), + }); + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.AddVoucherConsumableType.description", { + modifierCount: this.count, + voucherTypeName: getVoucherTypeName(this.voucherType), + }); + } + + /** + * Applies {@linkcode AddVoucherReward} + * @param battleScene {@linkcode BattleScene} + * @returns always `true` + */ + apply(): boolean { + const voucherCounts = globalScene.gameData.voucherCounts; + voucherCounts[this.voucherType] += this.count; + + return true; + } +} + +export class AddMoneyReward extends Reward { + private moneyMultiplier: number; + private moneyMultiplierDescriptorKey: string; + + constructor( + localeKey: string, + iconImage: string, + moneyMultiplier: number, + moneyMultiplierDescriptorKey: string, + id: RewardId, + ) { + super(localeKey, iconImage, "money", "se/buy"); + + this.moneyMultiplier = moneyMultiplier; + this.moneyMultiplierDescriptorKey = moneyMultiplierDescriptorKey; + this.id = id; + } + + getDescription(): string { + const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); + globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); + const formattedMoney = formatMoney(globalScene.moneyFormat, moneyAmount.value); + + return i18next.t("modifierType:ModifierType.MoneyRewardModifierType.description", { + moneyMultiplier: i18next.t(this.moneyMultiplierDescriptorKey as any), + moneyAmount: formattedMoney, + }); + } + + /** + * Applies {@linkcode AddMoneyReward} + * @returns always `true` + */ + apply(): boolean { + const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); + + globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); + + globalScene.addMoney(moneyAmount.value); + + for (const p of globalScene.getPlayerParty()) { + if (p.hasSpecies(SpeciesId.GIMMIGHOUL)) { + const factor = Math.min(Math.floor(this.moneyMultiplier), 3); + p.heldItemManager.add(HeldItemId.GIMMIGHOUL_EVO_TRACKER, factor); + } + } + + return true; + } +} + +/** Rewards that are applied to individual Pokemon. */ +export abstract class PokemonReward extends Reward { + public selectFilter: PokemonSelectFilter | undefined; + + constructor( + localeKey: string, + iconImage: string, + selectFilter?: PokemonSelectFilter, + group?: string, + soundName?: string, + ) { + super(localeKey, iconImage, group, soundName); + this.selectFilter = selectFilter; + } + + abstract override apply(_params: PokemonRewardParams): void; +} + +export interface PokemonRewardParams { + pokemon: PlayerPokemon; +} + +export interface PokemonMoveRewardParams { + pokemon: PlayerPokemon; + moveIndex: number; +} + +export interface PokemonMoveRecallRewardParams { + pokemon: PlayerPokemon; + moveIndex: number; + cost?: number; +} + +export interface PokemonFusionRewardParams { + pokemon: PlayerPokemon; + pokemon2: PlayerPokemon; +} + +export class HeldItemReward extends PokemonReward { + public itemId: HeldItemId; + constructor(itemId: HeldItemId, group?: string, soundName?: string) { + super( + "", + "", + (pokemon: PlayerPokemon) => { + const hasItem = pokemon.heldItemManager.hasItem(this.itemId); + const maxStackCount = allHeldItems[this.itemId].getMaxStackCount(); + if (!maxStackCount) { + return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.inoperable", { + pokemonName: getPokemonNameWithAffix(pokemon), + }); + } + if (hasItem && pokemon.heldItemManager.getStack(this.itemId) === maxStackCount) { + return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.tooMany", { + pokemonName: getPokemonNameWithAffix(pokemon), + }); + } + return null; + }, + group, + soundName, + ); + this.itemId = itemId; + this.id = RewardId.HELD_ITEM; + } + + get name(): string { + return allHeldItems[this.itemId].name; + } + + getDescription(): string { + return allHeldItems[this.itemId].description; + } + + getIcon(): string { + return allHeldItems[this.itemId].iconName; + } + + apply({ pokemon }: PokemonRewardParams): boolean { + return pokemon.heldItemManager.add(this.itemId); + } +} + +export class TrainerItemReward extends Reward { + public itemId: TrainerItemId; + constructor(itemId: TrainerItemId, group?: string, soundName?: string) { + super("", "", group, soundName); + this.itemId = itemId; + this.id = RewardId.TRAINER_ITEM; + } + + get name(): string { + return allTrainerItems[this.itemId].name; + } + + getDescription(): string { + return allTrainerItems[this.itemId].description; + } + + getIcon(): string { + return allTrainerItems[this.itemId].iconName; + } + + apply(): boolean { + return globalScene.trainerItems.add(this.itemId); + } +} + +export class LapsingTrainerItemReward extends TrainerItemReward { + constructor(itemId: TrainerItemId, id?: RewardId) { + super(itemId); + this.id = id ?? RewardId.TRAINER_ITEM; + } + + apply(): boolean { + return globalScene.trainerItems.add(this.itemId, allTrainerItems[this.itemId].getMaxStackCount()); + } +} + +export class ChangeTeraTypeReward extends PokemonReward { + private teraType: PokemonType; + + constructor(teraType: PokemonType) { + super( + "", + `${PokemonType[teraType].toLowerCase()}_tera_shard`, + (pokemon: PlayerPokemon) => { + if ( + [pokemon.species.speciesId, pokemon.fusionSpecies?.speciesId].filter( + s => s === SpeciesId.TERAPAGOS || s === SpeciesId.OGERPON || s === SpeciesId.SHEDINJA, + ).length > 0 + ) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "tera_shard", + ); + + this.teraType = teraType; + } + + get name(): string { + return i18next.t("modifierType:ModifierType.ChangeTeraTypeModifierType.name", { + teraType: i18next.t(`pokemonInfo:Type.${PokemonType[this.teraType]}`), + }); + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.ChangeTeraTypeModifierType.description", { + teraType: i18next.t(`pokemonInfo:Type.${PokemonType[this.teraType]}`), + }); + } + + getPregenArgs(): any[] { + return [this.teraType]; + } + + /** + * Checks if {@linkcode TerrastalizeConsumable} should be applied + * @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item + * @returns `true` if the {@linkcode TerrastalizeConsumable} should be applied + */ + shouldApply({ pokemon }: PokemonRewardParams): boolean { + return ( + pokemon.teraType !== this.teraType && + ![SpeciesId.SHEDINJA, SpeciesId.OGERPON, SpeciesId.TERAPAGOS].some(s => pokemon.hasSpecies(s)) + ); + } + + /** + * Applies {@linkcode TerrastalizeConsumable} + * @param pokemon The {@linkcode PlayerPokemon} that consumes the item + * @returns `true` if hp was restored + */ + apply({ pokemon }: PokemonRewardParams): boolean { + pokemon.teraType = this.teraType; + return true; + } +} + +function restorePokemonHp( + pokemon: Pokemon, + percentToRestore: number, + pointsToRestore = 0, + healStatus = false, + fainted = false, +): boolean { + if (!pokemon.hp === fainted) { + if (fainted || healStatus) { + pokemon.resetStatus(true, true, false, false); + } + // Apply HealingCharm + let multiplier = 1; + if (!fainted) { + const hpRestoreMultiplier = new NumberHolder(1); + this.applyPlayerItems(TrainerItemEffect.HEALING_BOOSTER, { numberHolder: hpRestoreMultiplier }); + multiplier = hpRestoreMultiplier.value; + } + const restorePoints = Math.floor(pointsToRestore * multiplier); + const restorePercent = Math.floor(percentToRestore * 0.01 * multiplier * pokemon.getMaxHp()); + pokemon.heal(Math.max(restorePercent, restorePoints, 1)); + return true; + } + return false; +} + +export class PokemonHpRestoreReward extends PokemonReward { + protected restorePoints: number; + protected restorePercent: number; + protected healStatus: boolean; + + constructor( + localeKey: string, + iconImage: string, + id: RewardId, + restorePoints: number, + restorePercent: number, + healStatus = false, + selectFilter?: PokemonSelectFilter, + group?: string, + ) { + super( + localeKey, + iconImage, + selectFilter || + ((pokemon: PlayerPokemon) => { + if ( + !pokemon.hp || + (pokemon.isFullHp() && (!this.healStatus || (!pokemon.status && !pokemon.getTag(BattlerTagType.CONFUSED)))) + ) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }), + group || "potion", + ); + + this.restorePoints = restorePoints; + this.restorePercent = restorePercent; + this.healStatus = healStatus; + this.id = id; + } + + getDescription(): string { + return this.restorePoints + ? i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.description", { + restorePoints: this.restorePoints, + restorePercent: this.restorePercent, + }) + : this.healStatus + ? i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.extra.fullyWithStatus") + : i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.extra.fully"); + } + + apply({ pokemon }: PokemonRewardParams): boolean { + return restorePokemonHp(pokemon, this.restorePercent, this.restorePoints, this.healStatus, false); + } +} + +export class PokemonReviveReward extends PokemonHpRestoreReward { + constructor(localeKey: string, iconImage: string, id: RewardId, restorePercent: number) { + super( + localeKey, + iconImage, + id, + 0, + restorePercent, + false, + (pokemon: PlayerPokemon) => { + if (!pokemon.isFainted()) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "revive", + ); + + this.selectFilter = (pokemon: PlayerPokemon) => { + if (pokemon.hp) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }; + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.PokemonReviveModifierType.description", { + restorePercent: this.restorePercent, + }); + } + + apply({ pokemon }: PokemonRewardParams): boolean { + return restorePokemonHp(pokemon, this.restorePercent, 0, false, true); + } +} + +class AllPokemonFullReviveReward extends Reward { + constructor(localeKey: string, iconImage: string) { + super(localeKey, iconImage, "modifierType:ModifierType.AllPokemonFullReviveModifierType"); + this.id = RewardId.SACRED_ASH; + } + + apply(): boolean { + for (const pokemon of globalScene.getPlayerParty()) { + restorePokemonHp(pokemon, 100, 0, false, true); + } + + return true; + } +} + +export class PokemonStatusHealReward extends PokemonReward { + constructor(localeKey: string, iconImage: string) { + super(localeKey, iconImage, (pokemon: PlayerPokemon) => { + if (!pokemon.hp || (!pokemon.status && !pokemon.getTag(BattlerTagType.CONFUSED))) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }); + this.id = RewardId.FULL_HEAL; + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.PokemonStatusHealModifierType.description"); + } + + apply({ pokemon }: PokemonRewardParams): boolean { + pokemon.resetStatus(true, true, false, false); + return true; + } +} + +export abstract class PokemonMoveReward extends PokemonReward { + public moveSelectFilter: PokemonMoveSelectFilter | undefined; + + constructor( + localeKey: string, + iconImage: string, + id: RewardId, + selectFilter?: PokemonSelectFilter, + moveSelectFilter?: PokemonMoveSelectFilter, + group?: string, + ) { + super(localeKey, iconImage, selectFilter, group); + this.moveSelectFilter = moveSelectFilter; + this.id = id; + } + + apply(_params: PokemonMoveRewardParams): boolean { + return false; + } +} + +export class PokemonPpRestoreReward extends PokemonMoveReward { + protected restorePoints: number; + + constructor(localeKey: string, iconImage: string, id: RewardId, restorePoints: number) { + super( + localeKey, + iconImage, + id, + (_pokemon: PlayerPokemon) => { + return null; + }, + (pokemonMove: PokemonMove) => { + if (!pokemonMove.ppUsed) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "ether", + ); + + this.restorePoints = restorePoints; + } + + getDescription(): string { + return this.restorePoints > -1 + ? i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.description", { + restorePoints: this.restorePoints, + }) + : i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.extra.fully"); + } + + /** + * Applies {@linkcode PokemonPpRestoreConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that should get move pp restored + * @returns always `true` + */ + apply({ pokemon, moveIndex }: PokemonMoveRewardParams): boolean { + const move = pokemon.getMoveset()[moveIndex]; + + if (move) { + move.ppUsed = this.restorePoints > -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0; + } + + return true; + } +} + +export class PokemonAllMovePpRestoreReward extends PokemonReward { + protected restorePoints: number; + + constructor(localeKey: string, iconImage: string, id: RewardId, restorePoints: number) { + super( + localeKey, + iconImage, + (pokemon: PlayerPokemon) => { + if (!pokemon.getMoveset().filter(m => m.ppUsed).length) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "elixir", + ); + + this.restorePoints = restorePoints; + this.id = id; + } + + getDescription(): string { + return this.restorePoints > -1 + ? i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.description", { + restorePoints: this.restorePoints, + }) + : i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.extra.fully"); + } + + /** + * Applies {@linkcode PokemonAllMovePpRestoreConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that should get all move pp restored + * @returns always `true` + */ + apply({ pokemon }: PokemonRewardParams): boolean { + for (const move of pokemon.getMoveset()) { + if (move) { + move.ppUsed = this.restorePoints > -1 ? Math.max(move.ppUsed - this.restorePoints, 0) : 0; + } + } + + return true; + } +} + +export class PokemonPpUpReward extends PokemonMoveReward { + protected upPoints: number; + + constructor(localeKey: string, iconImage: string, id: RewardId, upPoints: number) { + super( + localeKey, + iconImage, + id, + (_pokemon: PlayerPokemon) => { + return null; + }, + (pokemonMove: PokemonMove) => { + if (pokemonMove.getMove().pp < 5 || pokemonMove.ppUp >= 3 || pokemonMove.maxPpOverride) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "ppUp", + ); + + this.upPoints = upPoints; + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.PokemonPpUpModifierType.description", { upPoints: this.upPoints }); + } + + /** + * Applies {@linkcode PokemonPpUpConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that gets a pp up on move-slot {@linkcode moveIndex} + * @returns + */ + apply({ pokemon, moveIndex }: PokemonMoveRewardParams): boolean { + const move = pokemon.getMoveset()[moveIndex]; + + if (move && !move.maxPpOverride) { + move.ppUp = Math.min(move.ppUp + this.upPoints, 3); + } + + return true; + } +} + +export class PokemonNatureChangeReward extends PokemonReward { + protected nature: Nature; + + constructor(nature: Nature) { + super( + "", + `mint_${ + getEnumKeys(Stat) + .find(s => getNatureStatMultiplier(nature, Stat[s]) > 1) + ?.toLowerCase() || "neutral" + }`, + (pokemon: PlayerPokemon) => { + if (pokemon.getNature() === this.nature) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "mint", + ); + + this.nature = nature; + this.id = RewardId.MINT; + } + + get name(): string { + return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.name", { + natureName: getNatureName(this.nature), + }); + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.description", { + natureName: getNatureName(this.nature, true, true, true), + }); + } + + /** + * Applies {@linkcode PokemonNatureChangeConsumable} + * @param playerPokemon {@linkcode PlayerPokemon} to apply the {@linkcode Nature} change to + * @returns + */ + apply({ pokemon }: PokemonRewardParams): boolean { + pokemon.setCustomNature(this.nature); + globalScene.gameData.unlockSpeciesNature(pokemon.species, this.nature); + + return true; + } +} + +export class RememberMoveReward extends PokemonReward { + constructor(localeKey: string, iconImage: string, group?: string) { + super( + localeKey, + iconImage, + (pokemon: PlayerPokemon) => { + if (!pokemon.getLearnableLevelMoves().length) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + group, + ); + this.id = RewardId.MEMORY_MUSHROOM; + } + + /** + * Applies {@linkcode RememberMoveConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that should remember the move + * @returns always `true` + */ + apply({ pokemon, moveIndex, cost }: PokemonMoveRecallRewardParams): boolean { + globalScene.phaseManager.unshiftNew( + "LearnMovePhase", + globalScene.getPlayerParty().indexOf(pokemon as PlayerPokemon), + pokemon.getLearnableLevelMoves()[moveIndex], + LearnMoveType.MEMORY, + cost, + ); + + return true; + } +} + +class BerryRewardGenerator extends RewardGenerator { + constructor() { + super((_party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { + const item = berryTypeToHeldItem[pregenArgs[0] as BerryType]; + return new HeldItemReward(item); + } + const item = getNewBerryHeldItem(); + return new HeldItemReward(item); + }); + this.id = RewardId.BERRY; + } +} + +class MintRewardGenerator extends RewardGenerator { + constructor() { + super((_party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in Nature) { + return new PokemonNatureChangeReward(pregenArgs[0] as Nature); + } + return new PokemonNatureChangeReward(randSeedItem(getEnumValues(Nature))); + }); + this.id = RewardId.MINT; + } +} + +class TeraTypeRewardGenerator extends RewardGenerator { + constructor() { + super((party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { + return new ChangeTeraTypeReward(pregenArgs[0] as PokemonType); + } + if (!globalScene.trainerItems.hasItem(TrainerItemId.TERA_ORB)) { + return null; + } + const teraTypes: PokemonType[] = []; + for (const p of party) { + if ( + !(p.hasSpecies(SpeciesId.TERAPAGOS) || p.hasSpecies(SpeciesId.OGERPON) || p.hasSpecies(SpeciesId.SHEDINJA)) + ) { + teraTypes.push(p.teraType); + } + } + let excludedType = PokemonType.UNKNOWN; + if (teraTypes.length > 0 && teraTypes.filter(t => t === teraTypes[0]).length === teraTypes.length) { + excludedType = teraTypes[0]; + } + let shardType = randSeedInt(64) ? (randSeedInt(18) as PokemonType) : PokemonType.STELLAR; + while (shardType === excludedType) { + shardType = randSeedInt(64) ? (randSeedInt(18) as PokemonType) : PokemonType.STELLAR; + } + return new ChangeTeraTypeReward(shardType); + }); + this.id = RewardId.TERA_SHARD; + } +} + +export class AttackTypeBoosterReward extends HeldItemReward { + public moveType: PokemonType; + public boostPercent: number; + + constructor(moveType: PokemonType, boostPercent: number) { + const itemId = attackTypeToHeldItem[moveType]; + super(itemId); + this.moveType = moveType; + this.boostPercent = boostPercent; + } + + getPregenArgs(): any[] { + return [this.moveType]; + } +} + +function incrementLevelWithCandy(pokemon: Pokemon): boolean { + const levelCount = new NumberHolder(1); + globalScene.applyPlayerItems(TrainerItemEffect.LEVEL_INCREMENT_BOOSTER, { numberHolder: levelCount }); + + pokemon.level += levelCount.value; + if (pokemon.level <= globalScene.getMaxExpLevel(true)) { + pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate); + pokemon.levelExp = 0; + } + + if (pokemon.isPlayer()) { + pokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY); + + globalScene.phaseManager.unshiftNew( + "LevelUpPhase", + globalScene.getPlayerParty().indexOf(pokemon), + pokemon.level - levelCount.value, + pokemon.level, + ); + } + return true; +} + +export class PokemonLevelIncrementReward extends PokemonReward { + constructor(localeKey: string, iconImage: string) { + super(localeKey, iconImage, (_pokemon: PlayerPokemon) => null); + this.id = RewardId.RARE_CANDY; + } + + getDescription(): string { + let levels = 1; + const candyJarStack = globalScene.trainerItems.getStack(TrainerItemId.CANDY_JAR); + levels += candyJarStack; + return i18next.t("modifierType:ModifierType.PokemonLevelIncrementModifierType.description", { levels }); + } + + /** + * Applies {@linkcode PokemonLevelIncrementConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that should get levels incremented + * @param levelCount The amount of levels to increment + * @returns always `true` + */ + apply({ pokemon }: PokemonRewardParams): boolean { + return incrementLevelWithCandy(pokemon); + } +} + +export class AllPokemonLevelIncrementReward extends Reward { + id = RewardId.RARER_CANDY; + + getDescription(): string { + let levels = 1; + const candyJarStack = globalScene.trainerItems.getStack(TrainerItemId.CANDY_JAR); + levels += candyJarStack; + return i18next.t("modifierType:ModifierType.AllPokemonLevelIncrementModifierType.description", { levels }); + } + + apply(): boolean { + for (const pokemon of globalScene.getPlayerParty()) { + incrementLevelWithCandy(pokemon); + } + + return true; + } +} + +export class BaseStatBoosterReward extends HeldItemReward { + private stat: PermanentStat; + private key: string; + + constructor(stat: PermanentStat) { + const key = statBoostItems[stat]; + const itemId = permanentStatToHeldItem[stat]; + super(itemId); + + this.stat = stat; + this.key = key; + } +} + +export class TmReward extends PokemonReward { + public moveId: MoveId; + + constructor(moveId: MoveId) { + super( + "", + `tm_${PokemonType[allMoves[moveId].type].toLowerCase()}`, + (pokemon: PlayerPokemon) => { + if ( + pokemon.compatibleTms.indexOf(moveId) === -1 || + pokemon.getMoveset().filter(m => m.moveId === moveId).length + ) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }, + "tm", + ); + + this.moveId = moveId; + } + + get name(): string { + return i18next.t("modifierType:ModifierType.TmModifierType.name", { + moveId: padInt(Object.keys(tmSpecies).indexOf(this.moveId.toString()) + 1, 3), + moveName: allMoves[this.moveId].name, + }); + } + + getDescription(): string { + return i18next.t( + globalScene.enableMoveInfo + ? "modifierType:ModifierType.TmModifierTypeWithInfo.description" + : "modifierType:ModifierType.TmModifierType.description", + { moveName: allMoves[this.moveId].name }, + ); + } + + /** + * Applies {@linkcode TmConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that should learn the TM + * @returns always `true` + */ + apply({ pokemon }: PokemonRewardParams): boolean { + globalScene.phaseManager.unshiftNew( + "LearnMovePhase", + globalScene.getPlayerParty().indexOf(pokemon), + this.moveId, + LearnMoveType.TM, + ); + + return true; + } +} + +export class EvolutionItemReward extends PokemonReward { + public evolutionItem: EvolutionItem; + + constructor(evolutionItem: EvolutionItem) { + super("", EvolutionItem[evolutionItem].toLowerCase(), (pokemon: PlayerPokemon) => { + if ( + pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) && + pokemonEvolutions[pokemon.species.speciesId].filter(e => e.validate(pokemon, false, this.evolutionItem)) + .length && + pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX + ) { + return null; + } + if ( + pokemon.isFusion() && + pokemon.fusionSpecies && + pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && + pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(e => e.validate(pokemon, true, this.evolutionItem)) + .length && + pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX + ) { + return null; + } + + return PartyUiHandler.NoEffectMessage; + }); + + this.evolutionItem = evolutionItem; + } + + get name(): string { + return i18next.t(`modifierType:EvolutionItem.${EvolutionItem[this.evolutionItem]}`); + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.EvolutionItemModifierType.description"); + } + + getPregenArgs(): any[] { + return [this.evolutionItem]; + } + + /** + * Applies {@linkcode EvolutionItemConsumable} + * @param playerPokemon The {@linkcode PlayerPokemon} that should evolve via item + * @returns `true` if the evolution was successful + */ + apply({ pokemon }: PokemonRewardParams): boolean { + let matchingEvolution = pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) + ? pokemonEvolutions[pokemon.species.speciesId].find( + e => e.evoItem === this.evolutionItem && e.validate(pokemon, false, e.item!), + ) + : null; + + if (!matchingEvolution && pokemon.isFusion()) { + matchingEvolution = pokemonEvolutions[pokemon.fusionSpecies!.speciesId].find( + e => e.evoItem === this.evolutionItem && e.validate(pokemon, true, e.item!), + ); + if (matchingEvolution) { + matchingEvolution = new FusionSpeciesFormEvolution(pokemon.species.speciesId, matchingEvolution); + } + } + + if (matchingEvolution) { + globalScene.phaseManager.unshiftNew("EvolutionPhase", pokemon, matchingEvolution, pokemon.level - 1); + return true; + } + + return false; + } +} + +/** + * Class that represents form changing items + */ +export class FormChangeItemReward extends PokemonReward { + public formChangeItem: FormChangeItem; + + constructor(formChangeItem: FormChangeItem) { + super("", FormChangeItem[formChangeItem].toLowerCase(), (pokemon: PlayerPokemon) => { + // Make sure the Pokemon has alternate forms + if ( + pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId) && + // Get all form changes for this species with an item trigger, including any compound triggers + pokemonFormChanges[pokemon.species.speciesId] + .filter( + fc => fc.trigger.hasTriggerType(SpeciesFormChangeItemTrigger) && fc.preFormKey === pokemon.getFormKey(), + ) + // Returns true if any form changes match this item + .flatMap(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger) + .flatMap(fc => fc.item) + .includes(this.formChangeItem) + ) { + return null; + } + + return PartyUiHandler.NoEffectMessage; + }); + + this.formChangeItem = formChangeItem; + this.id = RewardId.FORM_CHANGE_ITEM; + } + + get name(): string { + return formChangeItemName(this.formChangeItem); + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.FormChangeItemModifierType.description"); + } + + apply({ pokemon }: PokemonRewardParams): boolean { + if (pokemon.heldItemManager.hasFormChangeItem(this.formChangeItem)) { + return false; + } + + pokemon.heldItemManager.addFormChangeItem(this.formChangeItem); + pokemon.heldItemManager.toggleActive(this.formChangeItem); + + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger); + + globalScene.updateItems(true); + + return true; + } +} + +export class FusePokemonReward extends PokemonReward { + constructor(localeKey: string, iconImage: string) { + super(localeKey, iconImage, (pokemon: PlayerPokemon) => { + if (pokemon.isFusion()) { + return PartyUiHandler.NoEffectMessage; + } + return null; + }); + this.id = RewardId.DNA_SPLICERS; + } + + getDescription(): string { + return i18next.t("modifierType:ModifierType.FusePokemonModifierType.description"); + } + + /** + * Applies {@linkcode FusePokemonConsumable} + * @param playerPokemon {@linkcode PlayerPokemon} that should be fused + * @param playerPokemon2 {@linkcode PlayerPokemon} that should be fused with {@linkcode playerPokemon} + * @returns always Promise + */ + apply({ pokemon, pokemon2 }: PokemonFusionRewardParams): boolean { + pokemon.fuse(pokemon2); + return true; + } +} + +class AttackTypeBoosterRewardGenerator extends RewardGenerator { + constructor() { + super((party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { + return new AttackTypeBoosterReward(pregenArgs[0] as PokemonType, TYPE_BOOST_ITEM_BOOST_PERCENT); + } + + const item = getNewAttackTypeBoosterHeldItem(party); + + return item ? new HeldItemReward(item) : null; + }); + this.id = RewardId.ATTACK_TYPE_BOOSTER; + } +} + +class BaseStatBoosterRewardGenerator extends RewardGenerator { + constructor() { + super((_party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs) { + return new BaseStatBoosterReward(pregenArgs[0]); + } + return new HeldItemReward(getNewVitaminHeldItem()); + }); + this.id = RewardId.BASE_STAT_BOOSTER; + } +} + +class TempStatStageBoosterRewardGenerator extends RewardGenerator { + public static readonly items: Record = { + [Stat.ATK]: "x_attack", + [Stat.DEF]: "x_defense", + [Stat.SPATK]: "x_sp_atk", + [Stat.SPDEF]: "x_sp_def", + [Stat.SPD]: "x_speed", + [Stat.ACC]: "x_accuracy", + }; + + constructor() { + super((_party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && pregenArgs.length === 1 && TEMP_BATTLE_STATS.includes(pregenArgs[0])) { + return new LapsingTrainerItemReward(tempStatToTrainerItem[pregenArgs[0]]); + } + const randStat: TempBattleStat = randSeedInt(Stat.ACC, Stat.ATK); + return new LapsingTrainerItemReward(tempStatToTrainerItem[randStat]); + }); + this.id = RewardId.TEMP_STAT_STAGE_BOOSTER; + } +} + +/** + * Consumable type generator for {@linkcode SpeciesStatBoosterReward}, which + * encapsulates the logic for weighting the most useful held item from + * the current list of {@linkcode items}. + * @extends RewardGenerator + */ +class SpeciesStatBoosterRewardGenerator extends RewardGenerator { + /** Object comprised of the currently available species-based stat boosting held items */ + + constructor(rare: boolean) { + super((party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in SPECIES_STAT_BOOSTER_ITEMS) { + return new HeldItemReward(pregenArgs[0] as HeldItemId); + } + + // Get a pool of items based on the rarity. + const tierItems = rare + ? [HeldItemId.LIGHT_BALL, HeldItemId.THICK_CLUB, HeldItemId.METAL_POWDER, HeldItemId.QUICK_POWDER] + : [HeldItemId.DEEP_SEA_SCALE, HeldItemId.DEEP_SEA_TOOTH]; + + const weights = new Array(tierItems.length).fill(0); + + for (const p of party) { + const speciesId = p.getSpeciesForm(true).speciesId; + const fusionSpeciesId = p.isFusion() ? p.getFusionSpeciesForm(true).speciesId : null; + // TODO: Use commented boolean when Fling is implemented + const hasFling = false; /* p.getMoveset(true).some(m => m.moveId === MoveId.FLING) */ + + for (const i in tierItems) { + const checkedSpecies = (allHeldItems[tierItems[i]] as SpeciesStatBoostHeldItem).species; + + // If party member already has the item being weighted currently, skip to the next item + const hasItem = p.heldItemManager.hasItem(tierItems[i]); + + if (!hasItem) { + if (checkedSpecies.includes(speciesId) || (!!fusionSpeciesId && checkedSpecies.includes(fusionSpeciesId))) { + // Add weight if party member has a matching species or, if applicable, a matching fusion species + weights[i]++; + } else if (checkedSpecies.includes(SpeciesId.PIKACHU) && hasFling) { + // Add weight to Light Ball if party member has Fling + weights[i]++; + } + } + } + } + + // TODO: Replace this with a helper function + let totalWeight = 0; + for (const weight of weights) { + totalWeight += weight; + } + + if (totalWeight !== 0) { + const randInt = randSeedInt(totalWeight, 1); + let weight = 0; + + for (const i in weights) { + if (weights[i] !== 0) { + const curWeight = weight + weights[i]; + if (randInt <= weight + weights[i]) { + return new HeldItemReward(tierItems[i]); + } + weight = curWeight; + } + } + } + + return null; + }); + this.id = rare ? RewardId.SPECIES_STAT_BOOSTER : RewardId.RARE_SPECIES_STAT_BOOSTER; + } +} + +class TmRewardGenerator extends RewardGenerator { + constructor(tier: RarityTier) { + super((party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in MoveId) { + return new TmReward(pregenArgs[0] as MoveId); + } + const partyMemberCompatibleTms = party.map(p => { + const previousLevelMoves = p.getLearnableLevelMoves(); + return (p as PlayerPokemon).compatibleTms.filter( + tm => !p.moveset.find(m => m.moveId === tm) && !previousLevelMoves.find(lm => lm === tm), + ); + }); + const tierUniqueCompatibleTms = partyMemberCompatibleTms + .flat() + .filter(tm => tmPoolTiers[tm] === tier) + .filter(tm => !allMoves[tm].name.endsWith(" (N)")) + .filter((tm, i, array) => array.indexOf(tm) === i); + if (!tierUniqueCompatibleTms.length) { + return null; + } + // TODO: should this use `randSeedItem`? + const randTmIndex = randSeedInt(tierUniqueCompatibleTms.length); + return new TmReward(tierUniqueCompatibleTms[randTmIndex]); + }); + this.id = + tier === RarityTier.COMMON + ? RewardId.TM_COMMON + : tier === RarityTier.GREAT + ? RewardId.TM_GREAT + : RewardId.TM_ULTRA; + } +} + +class EvolutionItemRewardGenerator extends RewardGenerator { + constructor(rare: boolean, id: RewardId) { + super((party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in EvolutionItem) { + return new EvolutionItemReward(pregenArgs[0] as EvolutionItem); + } + + const evolutionItemPool = [ + party + .filter( + p => + pokemonEvolutions.hasOwnProperty(p.species.speciesId) && + (!p.pauseEvolutions || + p.species.speciesId === SpeciesId.SLOWPOKE || + p.species.speciesId === SpeciesId.EEVEE || + p.species.speciesId === SpeciesId.KIRLIA || + p.species.speciesId === SpeciesId.SNORUNT), + ) + .flatMap(p => { + const evolutions = pokemonEvolutions[p.species.speciesId]; + return evolutions.filter(e => e.isValidItemEvolution(p)); + }), + party + .filter( + p => + p.isFusion() && + p.fusionSpecies && + pokemonEvolutions.hasOwnProperty(p.fusionSpecies.speciesId) && + (!p.pauseEvolutions || + p.fusionSpecies.speciesId === SpeciesId.SLOWPOKE || + p.fusionSpecies.speciesId === SpeciesId.EEVEE || + p.fusionSpecies.speciesId === SpeciesId.KIRLIA || + p.fusionSpecies.speciesId === SpeciesId.SNORUNT), + ) + .flatMap(p => { + const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId]; + return evolutions.filter(e => e.isValidItemEvolution(p, true)); + }), + ] + .flat() + .flatMap(e => e.evoItem) + .filter(i => !!i && i > 50 === rare); + + if (!evolutionItemPool.length) { + return null; + } + + // TODO: should this use `randSeedItem`? + return new EvolutionItemReward(evolutionItemPool[randSeedInt(evolutionItemPool.length)]!); // TODO: is the bang correct? + }); + this.id = id; + } +} + +export class FormChangeItemRewardGenerator extends RewardGenerator { + constructor(isRareFormChangeItem: boolean, id: RewardId) { + super((party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in FormChangeItem) { + return new FormChangeItemReward(pregenArgs[0] as FormChangeItem); + } + + const formChangeItemPool = [ + ...new Set( + party + .filter(p => pokemonFormChanges.hasOwnProperty(p.species.speciesId)) + .flatMap(p => { + const formChanges = pokemonFormChanges[p.species.speciesId]; + let formChangeItemTriggers = formChanges + .filter( + fc => + ((fc.formKey.indexOf(SpeciesFormKey.MEGA) === -1 && + fc.formKey.indexOf(SpeciesFormKey.PRIMAL) === -1) || + globalScene.trainerItems.hasItem(TrainerItemId.MEGA_BRACELET)) && + ((fc.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) === -1 && + fc.formKey.indexOf(SpeciesFormKey.ETERNAMAX) === -1) || + globalScene.trainerItems.hasItem(TrainerItemId.DYNAMAX_BAND)) && + (!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?.active && !p.heldItemManager.hasFormChangeItem(t.item)); + + if (p.species.speciesId === SpeciesId.NECROZMA) { + // technically we could use a simplified version and check for formChanges.length > 3, but in case any code changes later, this might break... + let foundULTRA_Z = false, + foundN_LUNA = false, + foundN_SOLAR = false; + formChangeItemTriggers.forEach((fc, _i) => { + console.log("Checking ", fc.item); + switch (fc.item) { + case FormChangeItem.ULTRANECROZIUM_Z: + foundULTRA_Z = true; + break; + case FormChangeItem.N_LUNARIZER: + foundN_LUNA = true; + break; + case FormChangeItem.N_SOLARIZER: + foundN_SOLAR = true; + break; + } + }); + if (foundULTRA_Z && foundN_LUNA && foundN_SOLAR) { + // all three items are present -> user hasn't acquired any of the N_*ARIZERs -> block ULTRANECROZIUM_Z acquisition. + formChangeItemTriggers = formChangeItemTriggers.filter( + fc => fc.item !== FormChangeItem.ULTRANECROZIUM_Z, + ); + } else { + console.log("DID NOT FIND "); + } + } + return formChangeItemTriggers; + }), + ), + ] + .flat() + .flatMap(fc => fc.item) + .filter(i => (i && i < 100) === isRareFormChangeItem); + // convert it into a set to remove duplicate values, which can appear when the same species with a potential form change is in the party. + + if (!formChangeItemPool.length) { + return null; + } + + // TODO: should this use `randSeedItem`? + return new FormChangeItemReward(formChangeItemPool[randSeedInt(formChangeItemPool.length)]); + }); + this.id = id; + } +} + +export class WeightedReward { + public reward: Reward | RewardGenerator; + public weight: number | WeightedRewardWeightFunc; + public maxWeight: number | WeightedRewardWeightFunc; + + constructor( + rewardFunc: RewardFunc, + weight: number | WeightedRewardWeightFunc, + maxWeight?: number | WeightedRewardWeightFunc, + ) { + this.reward = rewardFunc(); + this.weight = weight; + this.maxWeight = maxWeight || (!(weight instanceof Function) ? weight : 0); + } +} + +type BaseRewardOverride = { + name: Exclude; + count?: number; +}; + +/** Type for modifiers and held items that are constructed via {@linkcode RewardGenerator}. */ +export type GeneratorRewardOverride = { + count?: number; +} & ( + | { + name: keyof Pick; + type?: SpeciesStatBoosterItemId; + } + | { + name: keyof Pick; + type?: TempBattleStat; + } + | { + name: keyof Pick; + type?: Stat; + } + | { + name: keyof Pick; + type?: Nature; + } + | { + name: keyof Pick; + type?: PokemonType; + } + | { + name: keyof Pick; + type?: BerryType; + } + | { + name: keyof Pick; + type?: EvolutionItem; + } + | { + name: keyof Pick; + type?: FormChangeItem; + } + | { + name: keyof Pick; + type?: MoveId; + } +); + +/** Type used to construct modifiers and held items for overriding purposes. */ +export type RewardOverride = GeneratorRewardOverride | BaseRewardOverride; + +const rewardInitObj = Object.freeze({ + // Pokeball rewards + POKEBALL: () => new AddPokeballReward("pb", PokeballType.POKEBALL, 5, RewardId.POKEBALL), + GREAT_BALL: () => new AddPokeballReward("gb", PokeballType.GREAT_BALL, 5, RewardId.GREAT_BALL), + ULTRA_BALL: () => new AddPokeballReward("ub", PokeballType.ULTRA_BALL, 5, RewardId.ULTRA_BALL), + ROGUE_BALL: () => new AddPokeballReward("rb", PokeballType.ROGUE_BALL, 5, RewardId.ROGUE_BALL), + MASTER_BALL: () => new AddPokeballReward("mb", PokeballType.MASTER_BALL, 1, RewardId.MASTER_BALL), + + // Voucher rewards + VOUCHER: () => new AddVoucherReward(VoucherType.REGULAR, 1, RewardId.VOUCHER), + VOUCHER_PLUS: () => new AddVoucherReward(VoucherType.PLUS, 1, RewardId.VOUCHER_PLUS), + VOUCHER_PREMIUM: () => new AddVoucherReward(VoucherType.PREMIUM, 1, RewardId.VOUCHER_PREMIUM), + + // Money rewards + NUGGET: () => + new AddMoneyReward( + "modifierType:ModifierType.NUGGET", + "nugget", + 1, + "modifierType:ModifierType.MoneyRewardModifierType.extra.small", + RewardId.NUGGET, + ), + BIG_NUGGET: () => + new AddMoneyReward( + "modifierType:ModifierType.BIG_NUGGET", + "big_nugget", + 2.5, + "modifierType:ModifierType.MoneyRewardModifierType.extra.moderate", + RewardId.BIG_NUGGET, + ), + RELIC_GOLD: () => + new AddMoneyReward( + "modifierType:ModifierType.RELIC_GOLD", + "relic_gold", + 10, + "modifierType:ModifierType.MoneyRewardModifierType.extra.large", + RewardId.RELIC_GOLD, + ), + + // Party-wide consumables + RARER_CANDY: () => new AllPokemonLevelIncrementReward("modifierType:ModifierType.RARER_CANDY", "rarer_candy"), + SACRED_ASH: () => new AllPokemonFullReviveReward("modifierType:ModifierType.SACRED_ASH", "sacred_ash"), + + // Pokemon consumables + RARE_CANDY: () => new PokemonLevelIncrementReward("modifierType:ModifierType.RARE_CANDY", "rare_candy"), + + EVOLUTION_ITEM: () => new EvolutionItemRewardGenerator(false, RewardId.EVOLUTION_ITEM), + RARE_EVOLUTION_ITEM: () => new EvolutionItemRewardGenerator(true, RewardId.RARE_EVOLUTION_ITEM), + + POTION: () => new PokemonHpRestoreReward("modifierType:ModifierType.POTION", "potion", RewardId.POTION, 20, 10), + SUPER_POTION: () => + new PokemonHpRestoreReward("modifierType:ModifierType.SUPER_POTION", "super_potion", RewardId.SUPER_POTION, 50, 25), + HYPER_POTION: () => + new PokemonHpRestoreReward( + "modifierType:ModifierType.HYPER_POTION", + "hyper_potion", + RewardId.HYPER_POTION, + 200, + 50, + ), + MAX_POTION: () => + new PokemonHpRestoreReward("modifierType:ModifierType.MAX_POTION", "max_potion", RewardId.MAX_POTION, 0, 100), + FULL_RESTORE: () => + new PokemonHpRestoreReward( + "modifierType:ModifierType.FULL_RESTORE", + "full_restore", + RewardId.FULL_RESTORE, + 0, + 100, + true, + ), + + REVIVE: () => new PokemonReviveReward("modifierType:ModifierType.REVIVE", "revive", RewardId.REVIVE, 50), + MAX_REVIVE: () => + new PokemonReviveReward("modifierType:ModifierType.MAX_REVIVE", "max_revive", RewardId.MAX_REVIVE, 100), + + FULL_HEAL: () => new PokemonStatusHealReward("modifierType:ModifierType.FULL_HEAL", "full_heal"), + + ETHER: () => new PokemonPpRestoreReward("modifierType:ModifierType.ETHER", "ether", RewardId.ETHER, 10), + MAX_ETHER: () => + new PokemonPpRestoreReward("modifierType:ModifierType.MAX_ETHER", "max_ether", RewardId.MAX_ETHER, -1), + + ELIXIR: () => new PokemonAllMovePpRestoreReward("modifierType:ModifierType.ELIXIR", "elixir", RewardId.ELIXIR, 10), + MAX_ELIXIR: () => + new PokemonAllMovePpRestoreReward("modifierType:ModifierType.MAX_ELIXIR", "max_elixir", RewardId.MAX_ELIXIR, -1), + + PP_UP: () => new PokemonPpUpReward("modifierType:ModifierType.PP_UP", "pp_up", RewardId.PP_UP, 1), + PP_MAX: () => new PokemonPpUpReward("modifierType:ModifierType.PP_MAX", "pp_max", RewardId.PP_MAX, 3), + + /*REPEL: () => new DoubleBattleChanceBoosterReward('Repel', 5), + SUPER_REPEL: () => new DoubleBattleChanceBoosterReward('Super Repel', 10), + MAX_REPEL: () => new DoubleBattleChanceBoosterReward('Max Repel', 25),*/ + + MINT: () => new MintRewardGenerator(), + + TERA_SHARD: () => new TeraTypeRewardGenerator(), + + TM_COMMON: () => new TmRewardGenerator(RarityTier.COMMON), + TM_GREAT: () => new TmRewardGenerator(RarityTier.GREAT), + TM_ULTRA: () => new TmRewardGenerator(RarityTier.ULTRA), + + MEMORY_MUSHROOM: () => new RememberMoveReward("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom"), + + DNA_SPLICERS: () => new FusePokemonReward("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers"), + + // Form change items + FORM_CHANGE_ITEM: () => new FormChangeItemRewardGenerator(false, RewardId.FORM_CHANGE_ITEM), + RARE_FORM_CHANGE_ITEM: () => new FormChangeItemRewardGenerator(true, RewardId.RARE_FORM_CHANGE_ITEM), + + // Held items + REVIVER_SEED: () => new HeldItemReward(HeldItemId.REVIVER_SEED), + + WHITE_HERB: () => new HeldItemReward(HeldItemId.WHITE_HERB), + + SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterRewardGenerator(false), + RARE_SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterRewardGenerator(true), + + BASE_STAT_BOOSTER: () => new BaseStatBoosterRewardGenerator(), + + ATTACK_TYPE_BOOSTER: () => new AttackTypeBoosterRewardGenerator(), + + MYSTICAL_ROCK: () => new HeldItemReward(HeldItemId.MYSTICAL_ROCK), + + BERRY: () => new BerryRewardGenerator(), + + LUCKY_EGG: () => new HeldItemReward(HeldItemId.LUCKY_EGG), + GOLDEN_EGG: () => new HeldItemReward(HeldItemId.GOLDEN_EGG), + + SOOTHE_BELL: () => new HeldItemReward(HeldItemId.SOOTHE_BELL), + + SCOPE_LENS: () => new HeldItemReward(HeldItemId.SCOPE_LENS), + LEEK: () => new HeldItemReward(HeldItemId.LEEK), + + EVIOLITE: () => new HeldItemReward(HeldItemId.EVIOLITE), + + SOUL_DEW: () => new HeldItemReward(HeldItemId.SOUL_DEW), + + GOLDEN_PUNCH: () => new HeldItemReward(HeldItemId.GOLDEN_PUNCH), + + GRIP_CLAW: () => new HeldItemReward(HeldItemId.GRIP_CLAW), + WIDE_LENS: () => new HeldItemReward(HeldItemId.WIDE_LENS), + + MULTI_LENS: () => new HeldItemReward(HeldItemId.MULTI_LENS), + + FOCUS_BAND: () => new HeldItemReward(HeldItemId.FOCUS_BAND), + + QUICK_CLAW: () => new HeldItemReward(HeldItemId.QUICK_CLAW), + + KINGS_ROCK: () => new HeldItemReward(HeldItemId.KINGS_ROCK), + + LEFTOVERS: () => new HeldItemReward(HeldItemId.LEFTOVERS), + + SHELL_BELL: () => new HeldItemReward(HeldItemId.SHELL_BELL), + + TOXIC_ORB: () => new HeldItemReward(HeldItemId.TOXIC_ORB), + + FLAME_ORB: () => new HeldItemReward(HeldItemId.FLAME_ORB), + + BATON: () => new HeldItemReward(HeldItemId.BATON), + + MINI_BLACK_HOLE: () => new HeldItemReward(HeldItemId.MINI_BLACK_HOLE), + + // Trainer items + MEGA_BRACELET: () => new TrainerItemReward(TrainerItemId.MEGA_BRACELET), + DYNAMAX_BAND: () => new TrainerItemReward(TrainerItemId.DYNAMAX_BAND), + TERA_ORB: () => new TrainerItemReward(TrainerItemId.TERA_ORB), + + MAP: () => new TrainerItemReward(TrainerItemId.MAP), + + LURE: () => new LapsingTrainerItemReward(TrainerItemId.LURE, RewardId.LURE), + SUPER_LURE: () => new LapsingTrainerItemReward(TrainerItemId.SUPER_LURE, RewardId.SUPER_LURE), + MAX_LURE: () => new LapsingTrainerItemReward(TrainerItemId.MAX_LURE, RewardId.MAX_LURE), + + TEMP_STAT_STAGE_BOOSTER: () => new TempStatStageBoosterRewardGenerator(), + + DIRE_HIT: () => new LapsingTrainerItemReward(TrainerItemId.DIRE_HIT, RewardId.TEMP_STAT_STAGE_BOOSTER), + + EXP_SHARE: () => new TrainerItemReward(TrainerItemId.EXP_SHARE), + EXP_BALANCE: () => new TrainerItemReward(TrainerItemId.EXP_BALANCE), + + OVAL_CHARM: () => new TrainerItemReward(TrainerItemId.OVAL_CHARM), + + EXP_CHARM: () => new TrainerItemReward(TrainerItemId.EXP_CHARM), + SUPER_EXP_CHARM: () => new TrainerItemReward(TrainerItemId.SUPER_EXP_CHARM), + + AMULET_COIN: () => new TrainerItemReward(TrainerItemId.AMULET_COIN), + + LOCK_CAPSULE: () => new TrainerItemReward(TrainerItemId.LOCK_CAPSULE), + + HEALING_CHARM: () => new TrainerItemReward(TrainerItemId.HEALING_CHARM), + CANDY_JAR: () => new TrainerItemReward(TrainerItemId.CANDY_JAR), + + BERRY_POUCH: () => new TrainerItemReward(TrainerItemId.BERRY_POUCH), + + SHINY_CHARM: () => new TrainerItemReward(TrainerItemId.SHINY_CHARM), + ABILITY_CHARM: () => new TrainerItemReward(TrainerItemId.ABILITY_CHARM), + CATCHING_CHARM: () => new TrainerItemReward(TrainerItemId.CATCHING_CHARM), + + IV_SCANNER: () => new TrainerItemReward(TrainerItemId.IV_SCANNER), + + GOLDEN_POKEBALL: () => new TrainerItemReward(TrainerItemId.GOLDEN_POKEBALL), + + // Tokens //TODO: do we even need them here? + ENEMY_DAMAGE_BOOSTER: () => new TrainerItemReward(TrainerItemId.ENEMY_DAMAGE_BOOSTER), + ENEMY_DAMAGE_REDUCTION: () => new TrainerItemReward(TrainerItemId.ENEMY_DAMAGE_REDUCTION), + //ENEMY_SUPER_EFFECT_BOOSTER: () => new Reward('Type Advantage Token', 'Increases damage of super effective attacks by 30%', (type, _args) => new EnemySuperEffectiveDamageBoosterModifier(type, 30), 'wl_custom_super_effective'), + ENEMY_HEAL: () => new TrainerItemReward(TrainerItemId.ENEMY_HEAL), + ENEMY_ATTACK_POISON_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ATTACK_POISON_CHANCE), + ENEMY_ATTACK_PARALYZE_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ATTACK_PARALYZE_CHANCE), + ENEMY_ATTACK_BURN_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ATTACK_BURN_CHANCE), + ENEMY_STATUS_EFFECT_HEAL_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE), + ENEMY_ENDURE_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ENDURE_CHANCE), + ENEMY_FUSED_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_FUSED_CHANCE), + + // Items from mystery encounters + MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD: () => new HeldItemReward(HeldItemId.SHUCKLE_JUICE_GOOD), + MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD: () => new HeldItemReward(HeldItemId.SHUCKLE_JUICE_BAD), + + MYSTERY_ENCOUNTER_OLD_GATEAU: () => new HeldItemReward(HeldItemId.OLD_GATEAU), + + MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new TrainerItemReward(TrainerItemId.BLACK_SLUDGE), + + MYSTERY_ENCOUNTER_MACHO_BRACE: () => new HeldItemReward(HeldItemId.MACHO_BRACE), + + MYSTERY_ENCOUNTER_GOLDEN_BUG_NET: () => new TrainerItemReward(TrainerItemId.GOLDEN_BUG_NET), +}); + +/** + * The initial set of modifier types, used to generate the modifier pool. + */ +export type Rewards = typeof rewardInitObj; +export type RewardKeys = keyof typeof rewardInitObj; + +export class RewardOption { + public type: Reward; + public upgradeCount: number; + public tier: RarityTier; + public cost: number; + + constructor(type: Reward, upgradeCount: number, tier: RarityTier, cost = 0) { + this.type = type; + this.upgradeCount = upgradeCount; + this.tier = tier; + this.cost = Math.min(Math.round(cost), Number.MAX_SAFE_INTEGER); + } +} + +export function initRewards() { + for (const [key, value] of Object.entries(rewardInitObj)) { + allRewards[key] = value; + } +} + +// TODO: If necessary, add the rest of the modifier types here. +// For now, doing the minimal work until the modifier rework lands. +const RewardConstructorMap = Object.freeze({ + RewardGenerator, +}); + +/** + * Map of of modifier type strings to their constructor type + */ +export type RewardConstructorMap = typeof RewardConstructorMap; + +/** + * Map of modifier type strings to their instance type + */ +export type RewardInstanceMap = { + [K in keyof RewardConstructorMap]: InstanceType; +}; + +export type RewardString = keyof RewardConstructorMap; diff --git a/src/items/trainer-item-data-types.ts b/src/items/trainer-item-data-types.ts index 179c4de3eaa..305b3f1ebb3 100644 --- a/src/items/trainer-item-data-types.ts +++ b/src/items/trainer-item-data-types.ts @@ -1,5 +1,5 @@ // TODO: move to `src/@types/` -import type { RewardTier } from "#enums/reward-tier"; +import type { RarityTier } from "#enums/reward-tier"; import type { TrainerItemId } from "#enums/trainer-item-id"; export type TrainerItemData = { @@ -28,7 +28,7 @@ type TrainerItemPoolEntry = { export type TrainerItemPool = TrainerItemPoolEntry[]; export type TrainerItemTieredPool = { - [key in RewardTier]?: TrainerItemPool; + [key in RarityTier]?: TrainerItemPool; }; export function isTrainerItemPool(value: any): value is TrainerItemPool { diff --git a/src/items/trainer-item-default-tiers.ts b/src/items/trainer-item-default-tiers.ts new file mode 100644 index 00000000000..aab1d0a6f02 --- /dev/null +++ b/src/items/trainer-item-default-tiers.ts @@ -0,0 +1,50 @@ +import { RarityTier } from "#enums/reward-tier"; +import { TrainerItemId } from "#enums/trainer-item-id"; + +export const trainerItemRarities = { + [TrainerItemId.MAP]: RarityTier.COMMON, + [TrainerItemId.IV_SCANNER]: RarityTier.ULTRA, + [TrainerItemId.LOCK_CAPSULE]: RarityTier.ROGUE, + [TrainerItemId.MEGA_BRACELET]: RarityTier.ROGUE, + [TrainerItemId.DYNAMAX_BAND]: RarityTier.ROGUE, + [TrainerItemId.TERA_ORB]: RarityTier.ULTRA, + + [TrainerItemId.GOLDEN_POKEBALL]: RarityTier.LUXURY, + + [TrainerItemId.OVAL_CHARM]: RarityTier.LUXURY, + [TrainerItemId.EXP_SHARE]: RarityTier.ULTRA, + [TrainerItemId.EXP_BALANCE]: RarityTier.LUXURY, + + [TrainerItemId.CANDY_JAR]: RarityTier.ULTRA, + [TrainerItemId.BERRY_POUCH]: RarityTier.ROGUE, + + [TrainerItemId.HEALING_CHARM]: RarityTier.MASTER, + [TrainerItemId.EXP_CHARM]: RarityTier.ULTRA, + [TrainerItemId.SUPER_EXP_CHARM]: RarityTier.ROGUE, + [TrainerItemId.GOLDEN_EXP_CHARM]: RarityTier.LUXURY, + [TrainerItemId.AMULET_COIN]: RarityTier.ULTRA, + + [TrainerItemId.ABILITY_CHARM]: RarityTier.ULTRA, + [TrainerItemId.SHINY_CHARM]: RarityTier.MASTER, + [TrainerItemId.CATCHING_CHARM]: RarityTier.ULTRA, + + [TrainerItemId.BLACK_SLUDGE]: RarityTier.LUXURY, + [TrainerItemId.GOLDEN_BUG_NET]: RarityTier.LUXURY, + + [TrainerItemId.LURE]: RarityTier.COMMON, + [TrainerItemId.SUPER_LURE]: RarityTier.GREAT, + [TrainerItemId.MAX_LURE]: RarityTier.ULTRA, + + [TrainerItemId.X_ATTACK]: RarityTier.COMMON, + [TrainerItemId.X_DEFENSE]: RarityTier.COMMON, + [TrainerItemId.X_SP_ATK]: RarityTier.COMMON, + [TrainerItemId.X_SP_DEF]: RarityTier.COMMON, + [TrainerItemId.X_SPEED]: RarityTier.COMMON, + [TrainerItemId.X_ACCURACY]: RarityTier.COMMON, + [TrainerItemId.DIRE_HIT]: RarityTier.GREAT, +}; + +export function getTrainerItemTier(item: TrainerItemId): RarityTier { + const tier = trainerItemRarities[item]; + return tier ?? RarityTier.LUXURY; +} diff --git a/src/items/trainer-item-pool.ts b/src/items/trainer-item-pool.ts index 80e85224241..e75f1cec8e1 100644 --- a/src/items/trainer-item-pool.ts +++ b/src/items/trainer-item-pool.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import { allTrainerItems } from "#data/data-lists"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import type { TrainerItemId } from "#enums/trainer-item-id"; import type { TrainerItemPool, TrainerItemTieredPool } from "#items/trainer-item-data-types"; import type { TrainerItemManager } from "#items/trainer-item-manager"; @@ -29,13 +29,13 @@ export function getNewTrainerItemFromPool(pool: TrainerItemPool, manager: Traine return entry as TrainerItemId; } -export function assignEnemyBuffTokenForWave(tier: RewardTier) { +export function assignEnemyBuffTokenForWave(tier: RarityTier) { let tierStackCount: number; switch (tier) { - case RewardTier.ULTRA: + case RarityTier.ULTRA: tierStackCount = 5; break; - case RewardTier.GREAT: + case RarityTier.GREAT: tierStackCount = 3; break; default: diff --git a/src/items/trainer-item.ts b/src/items/trainer-item.ts index 2c2200b38be..b2a6c15389c 100644 --- a/src/items/trainer-item.ts +++ b/src/items/trainer-item.ts @@ -297,9 +297,9 @@ export class DoubleBattleChanceBoosterTrainerItem extends LapsingTrainerItem { } } -interface TempStatToTrainerItemMap { - [key: number]: TrainerItemId; -} +type TempStatToTrainerItemMap = { + [key in TempBattleStat]: TrainerItemId; +}; export const tempStatToTrainerItem: TempStatToTrainerItemMap = { [Stat.ATK]: TrainerItemId.X_ATTACK, diff --git a/src/loading-scene.ts b/src/loading-scene.ts index fe581f9f5ec..de5e8a6515b 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -15,9 +15,9 @@ import { getBiomeHasProps } from "#field/arena"; import { initHeldItems } from "#items/all-held-items"; import { initTrainerItems } from "#items/all-trainer-items"; import { initHeldItemPools } from "#items/init-held-item-pools"; +import { initRewardPools } from "#items/init-reward-pools"; import { initTrainerItemPools } from "#items/init-trainer-item-pools"; -import { initModifierPools } from "#modifiers/init-modifier-pools"; -import { initModifierTypes } from "#modifiers/modifier-type"; +import { initRewards } from "#items/reward"; import { initMoves } from "#moves/move"; import { initMysteryEncounters } from "#mystery-encounters/mystery-encounters"; import { CacheBustedLoaderPlugin } from "#plugins/cache-busted-loader-plugin"; @@ -370,8 +370,8 @@ export class LoadingScene extends SceneBase { this.loadLoadingScreen(); - initModifierTypes(); - initModifierPools(); + initRewards(); + initRewardPools(); initHeldItemPools(); initTrainerItemPools(); diff --git a/src/modifier/modifier-pools.ts b/src/modifier/modifier-pools.ts deleted file mode 100644 index aa5dff39094..00000000000 --- a/src/modifier/modifier-pools.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Contains modifier pools for different contexts in the game. - * Can be safely imported without worrying about circular dependencies. - */ - -import type { ModifierPool } from "#types/modifier-types"; - -export const modifierPool: ModifierPool = {}; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts deleted file mode 100644 index 9c57d0aa6ce..00000000000 --- a/src/modifier/modifier-type.ts +++ /dev/null @@ -1,2058 +0,0 @@ -import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; -import { timedEventManager } from "#app/global-event-manager"; -import { globalScene } from "#app/global-scene"; -import { getPokemonNameWithAffix } from "#app/messages"; -import Overrides from "#app/overrides"; -import { EvolutionItem, pokemonEvolutions } from "#balance/pokemon-evolutions"; -import { tmPoolTiers, tmSpecies } from "#balance/tms"; -import { allHeldItems, allMoves, allTrainerItems, modifierTypes } from "#data/data-lists"; -import { SpeciesFormChangeItemTrigger } from "#data/form-change-triggers"; -import { getNatureName, getNatureStatMultiplier } from "#data/nature"; -import { getPokeballCatchMultiplier, getPokeballName } from "#data/pokeball"; -import { pokemonFormChanges, SpeciesFormChangeCondition } from "#data/pokemon-forms"; -import { BattlerTagType } from "#enums/battler-tag-type"; -import { BerryType } from "#enums/berry-type"; -import { FormChangeItem } from "#enums/form-change-item"; -import { HeldItemId } from "#enums/held-item-id"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; -import { MoveId } from "#enums/move-id"; -import { Nature } from "#enums/nature"; -import { PokeballType } from "#enums/pokeball"; -import { PokemonType } from "#enums/pokemon-type"; -import { RewardTier } from "#enums/reward-tier"; -import { SpeciesFormKey } from "#enums/species-form-key"; -import { SpeciesId } from "#enums/species-id"; -import type { PermanentStat, TempBattleStat } from "#enums/stat"; -import { Stat, TEMP_BATTLE_STATS } from "#enums/stat"; -import { TrainerItemId } from "#enums/trainer-item-id"; -import type { PlayerPokemon, Pokemon } from "#field/pokemon"; -import { attackTypeToHeldItem } from "#items/attack-type-booster"; -import { permanentStatToHeldItem, statBoostItems } from "#items/base-stat-booster"; -import { berryTypeToHeldItem } from "#items/berry"; -import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#items/held-item-pool"; -import { formChangeItemName } from "#items/item-utility"; -import { - SPECIES_STAT_BOOSTER_ITEMS, - type SpeciesStatBoosterItemId, - type SpeciesStatBoostHeldItem, -} from "#items/stat-booster"; -import { TrainerItemEffect, tempStatToTrainerItem } from "#items/trainer-item"; -import { - AddPokeballModifier, - AddVoucherModifier, - EvolutionItemModifier, - FusePokemonModifier, - type Modifier, - MoneyRewardModifier, - PokemonAllMovePpRestoreModifier, - PokemonHpRestoreModifier, - PokemonLevelIncrementModifier, - PokemonNatureChangeModifier, - PokemonPpRestoreModifier, - PokemonPpUpModifier, - PokemonStatusHealModifier, - RememberMoveModifier, - TerrastalizeModifier, - TmModifier, -} from "#modifiers/modifier"; -import type { PokemonMove } from "#moves/pokemon-move"; -import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#system/voucher"; -import type { ModifierTypeFunc, WeightedModifierTypeWeightFunc } from "#types/modifier-types"; -import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#ui/party-ui-handler"; -import { PartyUiHandler } from "#ui/party-ui-handler"; -import { getModifierTierTextTint } from "#ui/text"; -import { formatMoney, isNullOrUndefined, NumberHolder, padInt, randSeedInt, randSeedItem } from "#utils/common"; -import { getEnumKeys, getEnumValues } from "#utils/enums"; -import { getModifierPoolForType } from "#utils/modifier-utils"; -import i18next from "i18next"; - -type NewModifierFunc = (type: ModifierType, args: any[]) => Modifier | null; - -export class ModifierType { - public id: string; - public localeKey: string; - public iconImage: string; - public group: string; - public soundName: string; - public tier: RewardTier; - protected newModifierFunc: NewModifierFunc | null; - - /** - * Checks if the modifier type is of a specific type - * @param modifierType - The type to check against - * @return Whether the modifier type is of the specified type - */ - public is(modifierType: K): this is ModifierTypeInstanceMap[K] { - const targetType = ModifierTypeConstructorMap[modifierType]; - if (!targetType) { - return false; - } - return this instanceof targetType; - } - - constructor( - localeKey: string | null, - iconImage: string | null, - newModifierFunc: NewModifierFunc | null, - group?: string, - soundName?: string, - ) { - this.localeKey = localeKey!; // TODO: is this bang correct? - this.iconImage = iconImage!; // TODO: is this bang correct? - this.group = group!; // TODO: is this bang correct? - this.soundName = soundName ?? "se/restore"; - this.newModifierFunc = newModifierFunc; - } - - get name(): string { - return i18next.t(`${this.localeKey}.name` as any); - } - - getDescription(): string { - return i18next.t(`${this.localeKey}.description` as any); - } - - getIcon(): string { - return this.iconImage; - } - - setTier(tier: RewardTier): void { - this.tier = tier; - } - - /** - * Populates item id for ModifierType instance - * @param func - */ - withIdFromFunc(func: ModifierTypeFunc): ModifierType { - this.id = Object.keys(modifierTypeInitObj).find(k => modifierTypeInitObj[k] === func)!; // TODO: is this bang correct? - return this; - } - - /** - * Populates item tier for ModifierType instance - * Tier is a necessary field for items that appear in player shop (determines the Pokeball visual they use) - * To find the tier, this function performs a reverse lookup of the item type in modifier pools - * It checks the weight of the item and will use the first tier for which the weight is greater than 0 - * This is to allow items to be in multiple item pools depending on the conditions, for example for events - * If all tiers have a weight of 0 for the item, the first tier where the item was found is used - * @param poolType Default 'ModifierPoolType.PLAYER'. Which pool to lookup item tier from - * @param party optional. Needed to check the weight of modifiers with conditional weight (see {@linkcode WeightedModifierTypeWeightFunc}) - * if not provided or empty, the weight check will be ignored - * @param rerollCount Default `0`. Used to check the weight of modifiers with conditional weight (see {@linkcode WeightedModifierTypeWeightFunc}) - */ - withTierFromPool( - poolType: ModifierPoolType = ModifierPoolType.PLAYER, - party?: PlayerPokemon[], - rerollCount = 0, - ): ModifierType { - let defaultTier: undefined | RewardTier; - for (const tier of Object.values(getModifierPoolForType(poolType))) { - for (const modifier of tier) { - if (this.id === modifier.modifierType.id) { - let weight: number; - if (modifier.weight instanceof Function) { - weight = party ? modifier.weight(party, rerollCount) : 0; - } else { - weight = modifier.weight; - } - if (weight > 0) { - this.tier = modifier.modifierType.tier; - return this; - } - if (isNullOrUndefined(defaultTier)) { - // If weight is 0, keep track of the first tier where the item was found - defaultTier = modifier.modifierType.tier; - } - } - } - } - - // Didn't find a pool with weight > 0, fallback to first tier where the item was found, if any - if (defaultTier) { - this.tier = defaultTier; - } - - return this; - } - - newModifier(...args: any[]): Modifier | null { - // biome-ignore lint/complexity/useOptionalChain: Changing to optional would coerce null return into undefined - return this.newModifierFunc && this.newModifierFunc(this, args); - } -} - -type ModifierTypeGeneratorFunc = (party: Pokemon[], pregenArgs?: any[]) => ModifierType | null; - -export class ModifierTypeGenerator extends ModifierType { - private genTypeFunc: ModifierTypeGeneratorFunc; - - constructor(genTypeFunc: ModifierTypeGeneratorFunc) { - super(null, null, null); - this.genTypeFunc = genTypeFunc; - } - - generateType(party: Pokemon[], pregenArgs?: any[]) { - const ret = this.genTypeFunc(party, pregenArgs); - if (ret) { - ret.id = this.id; - ret.setTier(this.tier); - } - return ret; - } -} - -export interface GeneratedPersistentModifierType { - getPregenArgs(): any[]; -} - -export class AddPokeballModifierType extends ModifierType { - private pokeballType: PokeballType; - private count: number; - - constructor(iconImage: string, pokeballType: PokeballType, count: number) { - super("", iconImage, (_type, _args) => new AddPokeballModifier(this, pokeballType, count), "pb", "se/pb_bounce_1"); - this.pokeballType = pokeballType; - this.count = count; - } - - get name(): string { - return i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", { - modifierCount: this.count, - pokeballName: getPokeballName(this.pokeballType), - }); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.AddPokeballModifierType.description", { - modifierCount: this.count, - pokeballName: getPokeballName(this.pokeballType), - catchRate: - getPokeballCatchMultiplier(this.pokeballType) > -1 - ? `${getPokeballCatchMultiplier(this.pokeballType)}x` - : "100%", - pokeballAmount: `${globalScene.pokeballCounts[this.pokeballType]}`, - }); - } -} - -export class AddVoucherModifierType extends ModifierType { - private voucherType: VoucherType; - private count: number; - - constructor(voucherType: VoucherType, count: number) { - super( - "", - getVoucherTypeIcon(voucherType), - (_type, _args) => new AddVoucherModifier(this, voucherType, count), - "voucher", - ); - this.count = count; - this.voucherType = voucherType; - } - - get name(): string { - return i18next.t("modifierType:ModifierType.AddVoucherModifierType.name", { - modifierCount: this.count, - voucherTypeName: getVoucherTypeName(this.voucherType), - }); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.AddVoucherModifierType.description", { - modifierCount: this.count, - voucherTypeName: getVoucherTypeName(this.voucherType), - }); - } -} - -export class PokemonModifierType extends ModifierType { - public selectFilter: PokemonSelectFilter | undefined; - - constructor( - localeKey: string, - iconImage: string, - newModifierFunc: NewModifierFunc, - selectFilter?: PokemonSelectFilter, - group?: string, - soundName?: string, - ) { - super(localeKey, iconImage, newModifierFunc, group, soundName); - - this.selectFilter = selectFilter; - } -} - -export class HeldItemReward extends PokemonModifierType { - public itemId: HeldItemId; - constructor(itemId: HeldItemId, group?: string, soundName?: string) { - super( - "", - "", - () => null, - (pokemon: PlayerPokemon) => { - const hasItem = pokemon.heldItemManager.hasItem(this.itemId); - const maxStackCount = allHeldItems[this.itemId].getMaxStackCount(); - if (!maxStackCount) { - return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.inoperable", { - pokemonName: getPokemonNameWithAffix(pokemon), - }); - } - if (hasItem && pokemon.heldItemManager.getStack(this.itemId) === maxStackCount) { - return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.tooMany", { - pokemonName: getPokemonNameWithAffix(pokemon), - }); - } - return null; - }, - group, - soundName, - ); - this.itemId = itemId; - } - - get name(): string { - return allHeldItems[this.itemId].name; - } - - getDescription(): string { - return allHeldItems[this.itemId].description; - } - - getIcon(): string { - return allHeldItems[this.itemId].iconName; - } - - apply(pokemon: Pokemon) { - pokemon.heldItemManager.add(this.itemId); - } -} - -export class TrainerItemReward extends ModifierType { - public itemId: TrainerItemId; - constructor(itemId: TrainerItemId, group?: string, soundName?: string) { - super("", "", () => null, group, soundName); - this.itemId = itemId; - } - - get name(): string { - return allTrainerItems[this.itemId].name; - } - - getDescription(): string { - return allTrainerItems[this.itemId].description; - } - - getIcon(): string { - return allTrainerItems[this.itemId].iconName; - } - - apply() { - globalScene.trainerItems.add(this.itemId); - } -} - -export class LapsingTrainerItemReward extends TrainerItemReward { - apply() { - globalScene.trainerItems.add(this.itemId, allTrainerItems[this.itemId].getMaxStackCount()); - console.log("WE GOT HERE WE ADDED IT"); - } -} - -export class TerastallizeModifierType extends PokemonModifierType { - private teraType: PokemonType; - - constructor(teraType: PokemonType) { - super( - "", - `${PokemonType[teraType].toLowerCase()}_tera_shard`, - (type, args) => new TerrastalizeModifier(type as TerastallizeModifierType, (args[0] as Pokemon).id, teraType), - (pokemon: PlayerPokemon) => { - if ( - [pokemon.species.speciesId, pokemon.fusionSpecies?.speciesId].filter( - s => s === SpeciesId.TERAPAGOS || s === SpeciesId.OGERPON || s === SpeciesId.SHEDINJA, - ).length > 0 - ) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }, - "tera_shard", - ); - - this.teraType = teraType; - } - - get name(): string { - return i18next.t("modifierType:ModifierType.TerastallizeModifierType.name", { - teraType: i18next.t(`pokemonInfo:Type.${PokemonType[this.teraType]}`), - }); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.TerastallizeModifierType.description", { - teraType: i18next.t(`pokemonInfo:Type.${PokemonType[this.teraType]}`), - }); - } - - getPregenArgs(): any[] { - return [this.teraType]; - } -} - -export class PokemonHpRestoreModifierType extends PokemonModifierType { - protected restorePoints: number; - protected restorePercent: number; - protected healStatus: boolean; - - constructor( - localeKey: string, - iconImage: string, - restorePoints: number, - restorePercent: number, - healStatus = false, - newModifierFunc?: NewModifierFunc, - selectFilter?: PokemonSelectFilter, - group?: string, - ) { - 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; - } - return null; - }), - group || "potion", - ); - - this.restorePoints = restorePoints; - this.restorePercent = restorePercent; - this.healStatus = healStatus; - } - - getDescription(): string { - return this.restorePoints - ? i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.description", { - restorePoints: this.restorePoints, - restorePercent: this.restorePercent, - }) - : this.healStatus - ? i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.extra.fullyWithStatus") - : i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.extra.fully"); - } -} - -export class PokemonReviveModifierType extends PokemonHpRestoreModifierType { - constructor(localeKey: string, iconImage: string, restorePercent: number) { - 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; - } - return null; - }, - "revive", - ); - - this.selectFilter = (pokemon: PlayerPokemon) => { - if (pokemon.hp) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }; - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonReviveModifierType.description", { - restorePercent: this.restorePercent, - }); - } -} - -export class PokemonStatusHealModifierType extends PokemonModifierType { - constructor(localeKey: string, iconImage: string) { - 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; - } - return null; - }, - ); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonStatusHealModifierType.description"); - } -} - -export abstract class PokemonMoveModifierType extends PokemonModifierType { - public moveSelectFilter: PokemonMoveSelectFilter | undefined; - - constructor( - localeKey: string, - iconImage: string, - newModifierFunc: NewModifierFunc, - selectFilter?: PokemonSelectFilter, - moveSelectFilter?: PokemonMoveSelectFilter, - group?: string, - ) { - super(localeKey, iconImage, newModifierFunc, selectFilter, group); - - this.moveSelectFilter = moveSelectFilter; - } -} - -export class PokemonPpRestoreModifierType extends PokemonMoveModifierType { - protected restorePoints: number; - - constructor(localeKey: string, iconImage: string, restorePoints: number) { - super( - localeKey, - iconImage, - (_type, args) => - new PokemonPpRestoreModifier(this, (args[0] as PlayerPokemon).id, args[1] as number, this.restorePoints), - (_pokemon: PlayerPokemon) => { - return null; - }, - (pokemonMove: PokemonMove) => { - if (!pokemonMove.ppUsed) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }, - "ether", - ); - - this.restorePoints = restorePoints; - } - - getDescription(): string { - return this.restorePoints > -1 - ? i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.description", { - restorePoints: this.restorePoints, - }) - : i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.extra.fully"); - } -} - -export class PokemonAllMovePpRestoreModifierType extends PokemonModifierType { - protected restorePoints: number; - - constructor(localeKey: string, iconImage: string, restorePoints: number) { - 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; - } - return null; - }, - "elixir", - ); - - this.restorePoints = restorePoints; - } - - getDescription(): string { - return this.restorePoints > -1 - ? i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.description", { - restorePoints: this.restorePoints, - }) - : i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.extra.fully"); - } -} - -export class PokemonPpUpModifierType extends PokemonMoveModifierType { - protected upPoints: number; - - constructor(localeKey: string, iconImage: string, upPoints: number) { - super( - localeKey, - iconImage, - (_type, args) => new PokemonPpUpModifier(this, (args[0] as PlayerPokemon).id, args[1] as number, this.upPoints), - (_pokemon: PlayerPokemon) => { - return null; - }, - (pokemonMove: PokemonMove) => { - if (pokemonMove.getMove().pp < 5 || pokemonMove.ppUp >= 3 || pokemonMove.maxPpOverride) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }, - "ppUp", - ); - - this.upPoints = upPoints; - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonPpUpModifierType.description", { upPoints: this.upPoints }); - } -} - -export class PokemonNatureChangeModifierType extends PokemonModifierType { - protected nature: Nature; - - constructor(nature: 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; - } - return null; - }, - "mint", - ); - - this.nature = nature; - } - - get name(): string { - return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.name", { - natureName: getNatureName(this.nature), - }); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.description", { - natureName: getNatureName(this.nature, true, true, true), - }); - } -} - -export class RememberMoveModifierType extends PokemonModifierType { - constructor(localeKey: string, iconImage: string, group?: string) { - super( - localeKey, - iconImage, - (type, args) => new RememberMoveModifier(type, (args[0] as PlayerPokemon).id, args[1] as number), - (pokemon: PlayerPokemon) => { - if (!pokemon.getLearnableLevelMoves().length) { - return PartyUiHandler.NoEffectMessage; - } - return null; - }, - group, - ); - } -} - -class BerryRewardGenerator extends ModifierTypeGenerator { - constructor() { - super((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { - const item = berryTypeToHeldItem[pregenArgs[0] as BerryType]; - return new HeldItemReward(item); - } - const item = getNewBerryHeldItem(); - return new HeldItemReward(item); - }); - } -} - -export class AttackTypeBoosterReward extends HeldItemReward implements GeneratedPersistentModifierType { - public moveType: PokemonType; - public boostPercent: number; - - constructor(moveType: PokemonType, boostPercent: number) { - const itemId = attackTypeToHeldItem[moveType]; - super(itemId); - this.moveType = moveType; - this.boostPercent = boostPercent; - } - - getPregenArgs(): any[] { - return [this.moveType]; - } -} - -export class PokemonLevelIncrementModifierType extends PokemonModifierType { - constructor(localeKey: string, iconImage: string) { - super( - localeKey, - iconImage, - (_type, args) => new PokemonLevelIncrementModifier(this, (args[0] as PlayerPokemon).id), - (_pokemon: PlayerPokemon) => null, - ); - } - - getDescription(): string { - let levels = 1; - const candyJarStack = globalScene.trainerItems.getStack(TrainerItemId.CANDY_JAR); - levels += candyJarStack; - return i18next.t("modifierType:ModifierType.PokemonLevelIncrementModifierType.description", { levels }); - } -} - -export class AllPokemonLevelIncrementModifierType extends ModifierType { - constructor(localeKey: string, iconImage: string) { - super(localeKey, iconImage, (_type, _args) => new PokemonLevelIncrementModifier(this, -1)); - } - - getDescription(): string { - let levels = 1; - const candyJarStack = globalScene.trainerItems.getStack(TrainerItemId.CANDY_JAR); - levels += candyJarStack; - return i18next.t("modifierType:ModifierType.AllPokemonLevelIncrementModifierType.description", { levels }); - } -} - -export class BaseStatBoosterReward extends HeldItemReward { - private stat: PermanentStat; - private key: string; - - constructor(stat: PermanentStat) { - const key = statBoostItems[stat]; - const itemId = permanentStatToHeldItem[stat]; - super(itemId); - - this.stat = stat; - this.key = key; - } -} - -/** - * Shuckle Juice item - */ -export class BaseStatTotalHeldItemReward extends HeldItemReward { - private readonly statModifier: number; - - constructor(itemId: HeldItemId, statModifier: number) { - super(itemId); - this.statModifier = statModifier; - } - - apply(pokemon: Pokemon) { - super.apply(pokemon); - pokemon.heldItemManager[this.itemId].data.statModifier = this.statModifier; - } -} - -class AllPokemonFullHpRestoreModifierType extends ModifierType { - private descriptionKey: string; - - constructor(localeKey: string, iconImage: string, descriptionKey?: string, newModifierFunc?: NewModifierFunc) { - super( - localeKey, - iconImage, - newModifierFunc || ((_type, _args) => new PokemonHpRestoreModifier(this, -1, 0, 100, false)), - ); - - this.descriptionKey = descriptionKey!; // TODO: is this bang correct? - } - - getDescription(): string { - return i18next.t( - `${this.descriptionKey || "modifierType:ModifierType.AllPokemonFullHpRestoreModifierType"}.description` as any, - ); - } -} - -class AllPokemonFullReviveModifierType extends AllPokemonFullHpRestoreModifierType { - constructor(localeKey: string, iconImage: string) { - super( - localeKey, - iconImage, - "modifierType:ModifierType.AllPokemonFullReviveModifierType", - (_type, _args) => new PokemonHpRestoreModifier(this, -1, 0, 100, false, true), - ); - } -} - -export class MoneyRewardModifierType extends ModifierType { - private moneyMultiplier: number; - private moneyMultiplierDescriptorKey: string; - - constructor(localeKey: string, iconImage: string, moneyMultiplier: number, moneyMultiplierDescriptorKey: string) { - super(localeKey, iconImage, (_type, _args) => new MoneyRewardModifier(this, moneyMultiplier), "money", "se/buy"); - - this.moneyMultiplier = moneyMultiplier; - this.moneyMultiplierDescriptorKey = moneyMultiplierDescriptorKey; - } - - getDescription(): string { - const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); - globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); - const formattedMoney = formatMoney(globalScene.moneyFormat, moneyAmount.value); - - return i18next.t("modifierType:ModifierType.MoneyRewardModifierType.description", { - moneyMultiplier: i18next.t(this.moneyMultiplierDescriptorKey as any), - moneyAmount: formattedMoney, - }); - } -} - -export class TmModifierType extends PokemonModifierType { - public moveId: MoveId; - - constructor(moveId: MoveId) { - super( - "", - `tm_${PokemonType[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; - } - return null; - }, - "tm", - ); - - this.moveId = moveId; - } - - get name(): string { - return i18next.t("modifierType:ModifierType.TmModifierType.name", { - moveId: padInt(Object.keys(tmSpecies).indexOf(this.moveId.toString()) + 1, 3), - moveName: allMoves[this.moveId].name, - }); - } - - getDescription(): string { - return i18next.t( - globalScene.enableMoveInfo - ? "modifierType:ModifierType.TmModifierTypeWithInfo.description" - : "modifierType:ModifierType.TmModifierType.description", - { moveName: allMoves[this.moveId].name }, - ); - } -} - -export class EvolutionItemModifierType extends PokemonModifierType implements GeneratedPersistentModifierType { - public evolutionItem: EvolutionItem; - - constructor(evolutionItem: EvolutionItem) { - 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.validate(pokemon, false, this.evolutionItem)) - .length && - pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX - ) { - return null; - } - if ( - pokemon.isFusion() && - pokemon.fusionSpecies && - pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && - pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(e => e.validate(pokemon, true, this.evolutionItem)) - .length && - pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX - ) { - return null; - } - - return PartyUiHandler.NoEffectMessage; - }, - ); - - this.evolutionItem = evolutionItem; - } - - get name(): string { - return i18next.t(`modifierType:EvolutionItem.${EvolutionItem[this.evolutionItem]}`); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.EvolutionItemModifierType.description"); - } - - getPregenArgs(): any[] { - return [this.evolutionItem]; - } -} - -/** - * Class that represents form changing items - */ -export class FormChangeItemReward extends PokemonModifierType { - public formChangeItem: FormChangeItem; - - constructor(formChangeItem: FormChangeItem) { - super( - "", - FormChangeItem[formChangeItem].toLowerCase(), - () => null, - (pokemon: PlayerPokemon) => { - // Make sure the Pokemon has alternate forms - if ( - pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId) && - // Get all form changes for this species with an item trigger, including any compound triggers - pokemonFormChanges[pokemon.species.speciesId] - .filter( - fc => fc.trigger.hasTriggerType(SpeciesFormChangeItemTrigger) && fc.preFormKey === pokemon.getFormKey(), - ) - // Returns true if any form changes match this item - .flatMap(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger) - .flatMap(fc => fc.item) - .includes(this.formChangeItem) - ) { - return null; - } - - return PartyUiHandler.NoEffectMessage; - }, - ); - - this.formChangeItem = formChangeItem; - } - - get name(): string { - return formChangeItemName(this.formChangeItem); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.FormChangeItemModifierType.description"); - } - - apply(pokemon: Pokemon) { - if (pokemon.heldItemManager.hasFormChangeItem(this.formChangeItem)) { - return; - } - - pokemon.heldItemManager.addFormChangeItem(this.formChangeItem); - pokemon.heldItemManager.toggleActive(this.formChangeItem); - - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger); - - globalScene.updateItems(true); - } -} - -export class FusePokemonModifierType extends PokemonModifierType { - constructor(localeKey: string, iconImage: string) { - 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; - } - return null; - }, - ); - } - - getDescription(): string { - return i18next.t("modifierType:ModifierType.FusePokemonModifierType.description"); - } -} - -class AttackTypeBoosterRewardGenerator extends ModifierTypeGenerator { - constructor() { - super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { - return new AttackTypeBoosterReward(pregenArgs[0] as PokemonType, TYPE_BOOST_ITEM_BOOST_PERCENT); - } - - const item = getNewAttackTypeBoosterHeldItem(party); - - return item ? new HeldItemReward(item) : null; - }); - } -} - -class BaseStatBoosterRewardGenerator extends ModifierTypeGenerator { - constructor() { - super((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs) { - return new BaseStatBoosterReward(pregenArgs[0]); - } - return new HeldItemReward(getNewVitaminHeldItem()); - }); - } -} - -class TempStatStageBoosterRewardGenerator extends ModifierTypeGenerator { - public static readonly items: Record = { - [Stat.ATK]: "x_attack", - [Stat.DEF]: "x_defense", - [Stat.SPATK]: "x_sp_atk", - [Stat.SPDEF]: "x_sp_def", - [Stat.SPD]: "x_speed", - [Stat.ACC]: "x_accuracy", - }; - - constructor() { - super((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && TEMP_BATTLE_STATS.includes(pregenArgs[0])) { - return new LapsingTrainerItemReward(tempStatToTrainerItem[pregenArgs[0]]); - } - const randStat: TempBattleStat = randSeedInt(Stat.ACC, Stat.ATK); - return new LapsingTrainerItemReward(tempStatToTrainerItem[randStat]); - }); - } -} - -/** - * Modifier type generator for {@linkcode SpeciesStatBoosterModifierType}, which - * encapsulates the logic for weighting the most useful held item from - * the current list of {@linkcode items}. - * @extends ModifierTypeGenerator - */ -class SpeciesStatBoosterRewardGenerator extends ModifierTypeGenerator { - /** Object comprised of the currently available species-based stat boosting held items */ - - constructor(rare: boolean) { - super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in SPECIES_STAT_BOOSTER_ITEMS) { - return new HeldItemReward(pregenArgs[0] as HeldItemId); - } - - // Get a pool of items based on the rarity. - const tierItems = rare - ? [HeldItemId.LIGHT_BALL, HeldItemId.THICK_CLUB, HeldItemId.METAL_POWDER, HeldItemId.QUICK_POWDER] - : [HeldItemId.DEEP_SEA_SCALE, HeldItemId.DEEP_SEA_TOOTH]; - - const weights = new Array(tierItems.length).fill(0); - - for (const p of party) { - const speciesId = p.getSpeciesForm(true).speciesId; - const fusionSpeciesId = p.isFusion() ? p.getFusionSpeciesForm(true).speciesId : null; - // TODO: Use commented boolean when Fling is implemented - const hasFling = false; /* p.getMoveset(true).some(m => m.moveId === MoveId.FLING) */ - - for (const i in tierItems) { - const checkedSpecies = (allHeldItems[tierItems[i]] as SpeciesStatBoostHeldItem).species; - - // If party member already has the item being weighted currently, skip to the next item - const hasItem = p.heldItemManager.hasItem(tierItems[i]); - - if (!hasItem) { - if (checkedSpecies.includes(speciesId) || (!!fusionSpeciesId && checkedSpecies.includes(fusionSpeciesId))) { - // Add weight if party member has a matching species or, if applicable, a matching fusion species - weights[i]++; - } else if (checkedSpecies.includes(SpeciesId.PIKACHU) && hasFling) { - // Add weight to Light Ball if party member has Fling - weights[i]++; - } - } - } - } - - // TODO: Replace this with a helper function - let totalWeight = 0; - for (const weight of weights) { - totalWeight += weight; - } - - if (totalWeight !== 0) { - const randInt = randSeedInt(totalWeight, 1); - let weight = 0; - - for (const i in weights) { - if (weights[i] !== 0) { - const curWeight = weight + weights[i]; - if (randInt <= weight + weights[i]) { - return new HeldItemReward(tierItems[i]); - } - weight = curWeight; - } - } - } - - return null; - }); - } -} - -class TmModifierTypeGenerator extends ModifierTypeGenerator { - constructor(tier: RewardTier) { - super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in MoveId) { - return new TmModifierType(pregenArgs[0] as MoveId); - } - const partyMemberCompatibleTms = party.map(p => { - const previousLevelMoves = p.getLearnableLevelMoves(); - return (p as PlayerPokemon).compatibleTms.filter( - tm => !p.moveset.find(m => m.moveId === tm) && !previousLevelMoves.find(lm => lm === tm), - ); - }); - const tierUniqueCompatibleTms = partyMemberCompatibleTms - .flat() - .filter(tm => tmPoolTiers[tm] === tier) - .filter(tm => !allMoves[tm].name.endsWith(" (N)")) - .filter((tm, i, array) => array.indexOf(tm) === i); - if (!tierUniqueCompatibleTms.length) { - return null; - } - // TODO: should this use `randSeedItem`? - const randTmIndex = randSeedInt(tierUniqueCompatibleTms.length); - return new TmModifierType(tierUniqueCompatibleTms[randTmIndex]); - }); - } -} - -class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { - constructor(rare: boolean) { - super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in EvolutionItem) { - return new EvolutionItemModifierType(pregenArgs[0] as EvolutionItem); - } - - const evolutionItemPool = [ - party - .filter( - p => - pokemonEvolutions.hasOwnProperty(p.species.speciesId) && - (!p.pauseEvolutions || - p.species.speciesId === SpeciesId.SLOWPOKE || - p.species.speciesId === SpeciesId.EEVEE || - p.species.speciesId === SpeciesId.KIRLIA || - p.species.speciesId === SpeciesId.SNORUNT), - ) - .flatMap(p => { - const evolutions = pokemonEvolutions[p.species.speciesId]; - return evolutions.filter(e => e.isValidItemEvolution(p)); - }), - party - .filter( - p => - p.isFusion() && - p.fusionSpecies && - pokemonEvolutions.hasOwnProperty(p.fusionSpecies.speciesId) && - (!p.pauseEvolutions || - p.fusionSpecies.speciesId === SpeciesId.SLOWPOKE || - p.fusionSpecies.speciesId === SpeciesId.EEVEE || - p.fusionSpecies.speciesId === SpeciesId.KIRLIA || - p.fusionSpecies.speciesId === SpeciesId.SNORUNT), - ) - .flatMap(p => { - const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId]; - return evolutions.filter(e => e.isValidItemEvolution(p, true)); - }), - ] - .flat() - .flatMap(e => e.evoItem) - .filter(i => !!i && i > 50 === rare); - - if (!evolutionItemPool.length) { - return null; - } - - // TODO: should this use `randSeedItem`? - return new EvolutionItemModifierType(evolutionItemPool[randSeedInt(evolutionItemPool.length)]!); // TODO: is the bang correct? - }); - } -} - -export class FormChangeItemRewardGenerator extends ModifierTypeGenerator { - constructor(isRareFormChangeItem: boolean) { - super((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in FormChangeItem) { - return new FormChangeItemReward(pregenArgs[0] as FormChangeItem); - } - - const formChangeItemPool = [ - ...new Set( - party - .filter(p => pokemonFormChanges.hasOwnProperty(p.species.speciesId)) - .flatMap(p => { - const formChanges = pokemonFormChanges[p.species.speciesId]; - let formChangeItemTriggers = formChanges - .filter( - fc => - ((fc.formKey.indexOf(SpeciesFormKey.MEGA) === -1 && - fc.formKey.indexOf(SpeciesFormKey.PRIMAL) === -1) || - globalScene.trainerItems.hasItem(TrainerItemId.MEGA_BRACELET)) && - ((fc.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) === -1 && - fc.formKey.indexOf(SpeciesFormKey.ETERNAMAX) === -1) || - globalScene.trainerItems.hasItem(TrainerItemId.DYNAMAX_BAND)) && - (!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?.active && !p.heldItemManager.hasFormChangeItem(t.item)); - - if (p.species.speciesId === SpeciesId.NECROZMA) { - // technically we could use a simplified version and check for formChanges.length > 3, but in case any code changes later, this might break... - let foundULTRA_Z = false, - foundN_LUNA = false, - foundN_SOLAR = false; - formChangeItemTriggers.forEach((fc, _i) => { - console.log("Checking ", fc.item); - switch (fc.item) { - case FormChangeItem.ULTRANECROZIUM_Z: - foundULTRA_Z = true; - break; - case FormChangeItem.N_LUNARIZER: - foundN_LUNA = true; - break; - case FormChangeItem.N_SOLARIZER: - foundN_SOLAR = true; - break; - } - }); - if (foundULTRA_Z && foundN_LUNA && foundN_SOLAR) { - // all three items are present -> user hasn't acquired any of the N_*ARIZERs -> block ULTRANECROZIUM_Z acquisition. - formChangeItemTriggers = formChangeItemTriggers.filter( - fc => fc.item !== FormChangeItem.ULTRANECROZIUM_Z, - ); - } else { - console.log("DID NOT FIND "); - } - } - return formChangeItemTriggers; - }), - ), - ] - .flat() - .flatMap(fc => fc.item) - .filter(i => (i && i < 100) === isRareFormChangeItem); - // convert it into a set to remove duplicate values, which can appear when the same species with a potential form change is in the party. - - if (!formChangeItemPool.length) { - return null; - } - - // TODO: should this use `randSeedItem`? - return new FormChangeItemReward(formChangeItemPool[randSeedInt(formChangeItemPool.length)]); - }); - } -} - -export class WeightedModifierType { - public modifierType: ModifierType; - public weight: number | WeightedModifierTypeWeightFunc; - public maxWeight: number | WeightedModifierTypeWeightFunc; - - constructor( - modifierTypeFunc: ModifierTypeFunc, - weight: number | WeightedModifierTypeWeightFunc, - maxWeight?: number | WeightedModifierTypeWeightFunc, - ) { - this.modifierType = modifierTypeFunc(); - this.modifierType.id = Object.keys(modifierTypeInitObj).find(k => modifierTypeInitObj[k] === modifierTypeFunc)!; // TODO: is this bang correct? - this.weight = weight; - this.maxWeight = maxWeight || (!(weight instanceof Function) ? weight : 0); - } - - setTier(tier: RewardTier) { - this.modifierType.setTier(tier); - } -} - -type BaseModifierOverride = { - name: Exclude; - count?: number; -}; - -/** Type for modifiers and held items that are constructed via {@linkcode ModifierTypeGenerator}. */ -export type GeneratorModifierOverride = { - count?: number; -} & ( - | { - name: keyof Pick; - type?: SpeciesStatBoosterItemId; - } - | { - name: keyof Pick; - type?: TempBattleStat; - } - | { - name: keyof Pick; - type?: Stat; - } - | { - name: keyof Pick; - type?: Nature; - } - | { - name: keyof Pick; - type?: PokemonType; - } - | { - name: keyof Pick; - type?: BerryType; - } - | { - name: keyof Pick; - type?: EvolutionItem; - } - | { - name: keyof Pick; - type?: FormChangeItem; - } - | { - name: keyof Pick; - type?: MoveId; - } -); - -/** Type used to construct modifiers and held items for overriding purposes. */ -export type ModifierOverride = GeneratorModifierOverride | BaseModifierOverride; - -export type ModifierTypeKeys = keyof typeof modifierTypeInitObj; - -const modifierTypeInitObj = Object.freeze({ - POKEBALL: () => new AddPokeballModifierType("pb", PokeballType.POKEBALL, 5), - GREAT_BALL: () => new AddPokeballModifierType("gb", PokeballType.GREAT_BALL, 5), - ULTRA_BALL: () => new AddPokeballModifierType("ub", PokeballType.ULTRA_BALL, 5), - ROGUE_BALL: () => new AddPokeballModifierType("rb", PokeballType.ROGUE_BALL, 5), - MASTER_BALL: () => new AddPokeballModifierType("mb", PokeballType.MASTER_BALL, 1), - - RARE_CANDY: () => new PokemonLevelIncrementModifierType("modifierType:ModifierType.RARE_CANDY", "rare_candy"), - RARER_CANDY: () => new AllPokemonLevelIncrementModifierType("modifierType:ModifierType.RARER_CANDY", "rarer_candy"), - - EVOLUTION_ITEM: () => new EvolutionItemModifierTypeGenerator(false), - RARE_EVOLUTION_ITEM: () => new EvolutionItemModifierTypeGenerator(true), - - FORM_CHANGE_ITEM: () => new FormChangeItemRewardGenerator(false), - RARE_FORM_CHANGE_ITEM: () => new FormChangeItemRewardGenerator(true), - - EVOLUTION_TRACKER_GIMMIGHOUL: () => new HeldItemReward(HeldItemId.GIMMIGHOUL_EVO_TRACKER), - - MEGA_BRACELET: () => new TrainerItemReward(TrainerItemId.MEGA_BRACELET), - DYNAMAX_BAND: () => new TrainerItemReward(TrainerItemId.DYNAMAX_BAND), - TERA_ORB: () => new TrainerItemReward(TrainerItemId.TERA_ORB), - - MAP: () => new TrainerItemReward(TrainerItemId.MAP), - - POTION: () => new PokemonHpRestoreModifierType("modifierType:ModifierType.POTION", "potion", 20, 10), - SUPER_POTION: () => - new PokemonHpRestoreModifierType("modifierType:ModifierType.SUPER_POTION", "super_potion", 50, 25), - HYPER_POTION: () => - new PokemonHpRestoreModifierType("modifierType:ModifierType.HYPER_POTION", "hyper_potion", 200, 50), - MAX_POTION: () => new PokemonHpRestoreModifierType("modifierType:ModifierType.MAX_POTION", "max_potion", 0, 100), - FULL_RESTORE: () => - new PokemonHpRestoreModifierType("modifierType:ModifierType.FULL_RESTORE", "full_restore", 0, 100, true), - - REVIVE: () => new PokemonReviveModifierType("modifierType:ModifierType.REVIVE", "revive", 50), - MAX_REVIVE: () => new PokemonReviveModifierType("modifierType:ModifierType.MAX_REVIVE", "max_revive", 100), - - FULL_HEAL: () => new PokemonStatusHealModifierType("modifierType:ModifierType.FULL_HEAL", "full_heal"), - - SACRED_ASH: () => new AllPokemonFullReviveModifierType("modifierType:ModifierType.SACRED_ASH", "sacred_ash"), - - REVIVER_SEED: () => new HeldItemReward(HeldItemId.REVIVER_SEED), - - WHITE_HERB: () => new HeldItemReward(HeldItemId.WHITE_HERB), - - ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.ETHER", "ether", 10), - MAX_ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.MAX_ETHER", "max_ether", -1), - - ELIXIR: () => new PokemonAllMovePpRestoreModifierType("modifierType:ModifierType.ELIXIR", "elixir", 10), - MAX_ELIXIR: () => new PokemonAllMovePpRestoreModifierType("modifierType:ModifierType.MAX_ELIXIR", "max_elixir", -1), - - PP_UP: () => new PokemonPpUpModifierType("modifierType:ModifierType.PP_UP", "pp_up", 1), - PP_MAX: () => new PokemonPpUpModifierType("modifierType:ModifierType.PP_MAX", "pp_max", 3), - - /*REPEL: () => new DoubleBattleChanceBoosterModifierType('Repel', 5), - SUPER_REPEL: () => new DoubleBattleChanceBoosterModifierType('Super Repel', 10), - MAX_REPEL: () => new DoubleBattleChanceBoosterModifierType('Max Repel', 25),*/ - - LURE: () => new LapsingTrainerItemReward(TrainerItemId.LURE), - SUPER_LURE: () => new LapsingTrainerItemReward(TrainerItemId.SUPER_LURE), - MAX_LURE: () => new LapsingTrainerItemReward(TrainerItemId.MAX_LURE), - - SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterRewardGenerator(false), - RARE_SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterRewardGenerator(true), - - TEMP_STAT_STAGE_BOOSTER: () => new TempStatStageBoosterRewardGenerator(), - - DIRE_HIT: () => new LapsingTrainerItemReward(TrainerItemId.DIRE_HIT), - - BASE_STAT_BOOSTER: () => new BaseStatBoosterRewardGenerator(), - - ATTACK_TYPE_BOOSTER: () => new AttackTypeBoosterRewardGenerator(), - - MINT: () => - new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in Nature) { - return new PokemonNatureChangeModifierType(pregenArgs[0] as Nature); - } - return new PokemonNatureChangeModifierType(randSeedItem(getEnumValues(Nature))); - }), - - MYSTICAL_ROCK: () => new HeldItemReward(HeldItemId.MYSTICAL_ROCK), - - TERA_SHARD: () => - new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { - if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { - return new TerastallizeModifierType(pregenArgs[0] as PokemonType); - } - if (!globalScene.trainerItems.hasItem(TrainerItemId.TERA_ORB)) { - return null; - } - const teraTypes: PokemonType[] = []; - for (const p of party) { - if ( - !(p.hasSpecies(SpeciesId.TERAPAGOS) || p.hasSpecies(SpeciesId.OGERPON) || p.hasSpecies(SpeciesId.SHEDINJA)) - ) { - teraTypes.push(p.teraType); - } - } - let excludedType = PokemonType.UNKNOWN; - if (teraTypes.length > 0 && teraTypes.filter(t => t === teraTypes[0]).length === teraTypes.length) { - excludedType = teraTypes[0]; - } - let shardType = randSeedInt(64) ? (randSeedInt(18) as PokemonType) : PokemonType.STELLAR; - while (shardType === excludedType) { - shardType = randSeedInt(64) ? (randSeedInt(18) as PokemonType) : PokemonType.STELLAR; - } - return new TerastallizeModifierType(shardType); - }), - - BERRY: () => new BerryRewardGenerator(), - - TM_COMMON: () => new TmModifierTypeGenerator(RewardTier.COMMON), - TM_GREAT: () => new TmModifierTypeGenerator(RewardTier.GREAT), - TM_ULTRA: () => new TmModifierTypeGenerator(RewardTier.ULTRA), - - MEMORY_MUSHROOM: () => new RememberMoveModifierType("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom"), - - EXP_SHARE: () => new TrainerItemReward(TrainerItemId.EXP_SHARE), - EXP_BALANCE: () => new TrainerItemReward(TrainerItemId.EXP_BALANCE), - - OVAL_CHARM: () => new TrainerItemReward(TrainerItemId.OVAL_CHARM), - - EXP_CHARM: () => new TrainerItemReward(TrainerItemId.EXP_CHARM), - SUPER_EXP_CHARM: () => new TrainerItemReward(TrainerItemId.SUPER_EXP_CHARM), - - LUCKY_EGG: () => new HeldItemReward(HeldItemId.LUCKY_EGG), - GOLDEN_EGG: () => new HeldItemReward(HeldItemId.GOLDEN_EGG), - - SOOTHE_BELL: () => new HeldItemReward(HeldItemId.SOOTHE_BELL), - - SCOPE_LENS: () => new HeldItemReward(HeldItemId.SCOPE_LENS), - LEEK: () => new HeldItemReward(HeldItemId.LEEK), - - EVIOLITE: () => new HeldItemReward(HeldItemId.EVIOLITE), - - SOUL_DEW: () => new HeldItemReward(HeldItemId.SOUL_DEW), - - 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 TrainerItemReward(TrainerItemId.AMULET_COIN), - GOLDEN_PUNCH: () => new HeldItemReward(HeldItemId.GOLDEN_PUNCH), - - LOCK_CAPSULE: () => new TrainerItemReward(TrainerItemId.LOCK_CAPSULE), - - GRIP_CLAW: () => new HeldItemReward(HeldItemId.GRIP_CLAW), - WIDE_LENS: () => new HeldItemReward(HeldItemId.WIDE_LENS), - - MULTI_LENS: () => new HeldItemReward(HeldItemId.MULTI_LENS), - - HEALING_CHARM: () => new TrainerItemReward(TrainerItemId.HEALING_CHARM), - CANDY_JAR: () => new TrainerItemReward(TrainerItemId.CANDY_JAR), - - BERRY_POUCH: () => new TrainerItemReward(TrainerItemId.BERRY_POUCH), - - FOCUS_BAND: () => new HeldItemReward(HeldItemId.FOCUS_BAND), - - QUICK_CLAW: () => new HeldItemReward(HeldItemId.QUICK_CLAW), - - KINGS_ROCK: () => new HeldItemReward(HeldItemId.KINGS_ROCK), - - LEFTOVERS: () => new HeldItemReward(HeldItemId.LEFTOVERS), - - SHELL_BELL: () => new HeldItemReward(HeldItemId.SHELL_BELL), - - TOXIC_ORB: () => new HeldItemReward(HeldItemId.TOXIC_ORB), - - FLAME_ORB: () => new HeldItemReward(HeldItemId.FLAME_ORB), - - BATON: () => new HeldItemReward(HeldItemId.BATON), - - SHINY_CHARM: () => new TrainerItemReward(TrainerItemId.SHINY_CHARM), - ABILITY_CHARM: () => new TrainerItemReward(TrainerItemId.ABILITY_CHARM), - CATCHING_CHARM: () => new TrainerItemReward(TrainerItemId.CATCHING_CHARM), - - IV_SCANNER: () => new TrainerItemReward(TrainerItemId.IV_SCANNER), - - DNA_SPLICERS: () => new FusePokemonModifierType("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers"), - - MINI_BLACK_HOLE: () => new HeldItemReward(HeldItemId.MINI_BLACK_HOLE), - - VOUCHER: () => new AddVoucherModifierType(VoucherType.REGULAR, 1), - VOUCHER_PLUS: () => new AddVoucherModifierType(VoucherType.PLUS, 1), - VOUCHER_PREMIUM: () => new AddVoucherModifierType(VoucherType.PREMIUM, 1), - - GOLDEN_POKEBALL: () => new TrainerItemReward(TrainerItemId.GOLDEN_POKEBALL), - - ENEMY_DAMAGE_BOOSTER: () => new TrainerItemReward(TrainerItemId.ENEMY_DAMAGE_BOOSTER), - ENEMY_DAMAGE_REDUCTION: () => new TrainerItemReward(TrainerItemId.ENEMY_DAMAGE_REDUCTION), - //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 TrainerItemReward(TrainerItemId.ENEMY_HEAL), - ENEMY_ATTACK_POISON_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ATTACK_POISON_CHANCE), - ENEMY_ATTACK_PARALYZE_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ATTACK_PARALYZE_CHANCE), - ENEMY_ATTACK_BURN_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ATTACK_BURN_CHANCE), - ENEMY_STATUS_EFFECT_HEAL_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE), - ENEMY_ENDURE_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ENDURE_CHANCE), - ENEMY_FUSED_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_FUSED_CHANCE), - - MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD: () => new HeldItemReward(HeldItemId.SHUCKLE_JUICE_GOOD), - MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD: () => new HeldItemReward(HeldItemId.SHUCKLE_JUICE_BAD), - - MYSTERY_ENCOUNTER_OLD_GATEAU: () => new HeldItemReward(HeldItemId.OLD_GATEAU), - - MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new TrainerItemReward(TrainerItemId.BLACK_SLUDGE), - - MYSTERY_ENCOUNTER_MACHO_BRACE: () => new HeldItemReward(HeldItemId.MACHO_BRACE), - - MYSTERY_ENCOUNTER_GOLDEN_BUG_NET: () => new TrainerItemReward(TrainerItemId.GOLDEN_BUG_NET), -}); - -/** - * The initial set of modifier types, used to generate the modifier pool. - */ -export type ModifierTypes = typeof modifierTypeInitObj; - -export interface ModifierPool { - [tier: string]: WeightedModifierType[]; -} - -let modifierPoolThresholds = {}; -let ignoredPoolIndexes = {}; - -/** - * Allows a unit test to check if an item exists in the Modifier Pool. Checks the pool directly, rather than attempting to reroll for the item. - */ -export const itemPoolChecks: Map = new Map(); - -export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: ModifierPoolType, rerollCount = 0) { - const pool = getModifierPoolForType(poolType); - itemPoolChecks.forEach((_v, k) => { - itemPoolChecks.set(k, false); - }); - - const ignoredIndexes = {}; - const thresholds = Object.fromEntries( - new Map( - Object.keys(pool).map(t => { - ignoredIndexes[t] = []; - const thresholds = new Map(); - const tierModifierIds: string[] = []; - let i = 0; - pool[t].reduce((total: number, modifierType: WeightedModifierType) => { - const weightedModifierType = modifierType as WeightedModifierType; - const itemModifierType = - weightedModifierType.modifierType instanceof ModifierTypeGenerator - ? weightedModifierType.modifierType.generateType(party) - : weightedModifierType.modifierType; - const trainerItemfullStack = - itemModifierType instanceof TrainerItemReward - ? globalScene.trainerItems.isMaxStack(itemModifierType.itemId) - : false; - const weight = - !trainerItemfullStack || - itemModifierType instanceof HeldItemReward || - itemModifierType instanceof FormChangeItemReward - ? weightedModifierType.weight instanceof Function - ? // biome-ignore lint/complexity/noBannedTypes: TODO: refactor to not use Function type - (weightedModifierType.weight as Function)(party, rerollCount) - : (weightedModifierType.weight as number) - : 0; - if (weightedModifierType.maxWeight) { - const modifierId = weightedModifierType.modifierType.id; - tierModifierIds.push(modifierId); - } - if (weight) { - total += weight; - } else { - ignoredIndexes[t].push(i++); - return total; - } - if (itemPoolChecks.has(modifierType.modifierType.id as ModifierTypeKeys)) { - itemPoolChecks.set(modifierType.modifierType.id as ModifierTypeKeys, true); - } - thresholds.set(total, i++); - return total; - }, 0); - return [t, Object.fromEntries(thresholds)]; - }), - ), - ); - switch (poolType) { - case ModifierPoolType.PLAYER: - modifierPoolThresholds = thresholds; - ignoredPoolIndexes = ignoredIndexes; - break; - } -} - -export interface CustomModifierSettings { - guaranteedModifierTiers?: RewardTier[]; - guaranteedModifierTypeOptions?: ModifierTypeOption[]; - /** If specified, will override the next X items to be auto-generated from specific modifier functions (these don't have to be pre-genned). */ - guaranteedModifierTypeFuncs?: ModifierTypeFunc[]; - /** - * If set to `true`, will fill the remainder of shop items that were not overridden by the 3 options above, up to the `count` param value. - * @example - * ```ts - * count = 4; - * customModifierSettings = { guaranteedModifierTiers: [ModifierTier.GREAT], fillRemaining: true }; - * ``` - * The first item in the shop will be `GREAT` tier, and the remaining `3` items will be generated normally. - * - * If `fillRemaining: false` in the same scenario, only 1 `GREAT` tier item will appear in the shop (regardless of the value of `count`). - * @defaultValue `false` - */ - fillRemaining?: boolean; - /** If specified, can adjust the amount of money required for a shop reroll. If set to a negative value, the shop will not allow rerolls at all. */ - rerollMultiplier?: number; - /** - * If `false`, will prevent set item tiers from upgrading via luck. - * @defaultValue `true` - */ - allowLuckUpgrades?: boolean; -} - -export function getModifierTypeFuncById(id: string): ModifierTypeFunc { - return modifierTypeInitObj[id]; -} - -/** - * Generates modifier options for a {@linkcode SelectRewardPhase} - * @param count - Determines the number of items to generate - * @param party - Party is required for generating proper modifier pools - * @param modifierTiers - (Optional) If specified, rolls items in the specified tiers. Commonly used for tier-locking with Lock Capsule. - * @param customModifierSettings - See {@linkcode CustomModifierSettings} - */ -export function getPlayerModifierTypeOptions( - count: number, - party: PlayerPokemon[], - modifierTiers?: RewardTier[], - customModifierSettings?: CustomModifierSettings, -): ModifierTypeOption[] { - const options: ModifierTypeOption[] = []; - const retryCount = Math.min(count * 5, 50); - if (!customModifierSettings) { - for (let i = 0; i < count; i++) { - const tier = modifierTiers && modifierTiers.length > i ? modifierTiers[i] : undefined; - options.push(getModifierTypeOptionWithRetry(options, retryCount, party, tier)); - } - } else { - // Guaranteed mod options first - if ( - customModifierSettings?.guaranteedModifierTypeOptions && - customModifierSettings.guaranteedModifierTypeOptions.length > 0 - ) { - options.push(...customModifierSettings.guaranteedModifierTypeOptions!); - } - - // Guaranteed mod functions second - if ( - customModifierSettings.guaranteedModifierTypeFuncs && - customModifierSettings.guaranteedModifierTypeFuncs.length > 0 - ) { - customModifierSettings.guaranteedModifierTypeFuncs!.forEach((mod, _i) => { - const modifierId = Object.keys(modifierTypeInitObj).find(k => modifierTypeInitObj[k] === mod) as string; - let guaranteedMod: ModifierType = modifierTypeInitObj[modifierId]?.(); - - // Populates item id and tier - guaranteedMod = guaranteedMod - .withIdFromFunc(modifierTypeInitObj[modifierId]) - .withTierFromPool(ModifierPoolType.PLAYER, party); - - const modType = - guaranteedMod instanceof ModifierTypeGenerator ? guaranteedMod.generateType(party) : guaranteedMod; - if (modType) { - const option = new ModifierTypeOption(modType, 0); - options.push(option); - } - }); - } - - // Guaranteed tiers third - if (customModifierSettings.guaranteedModifierTiers && customModifierSettings.guaranteedModifierTiers.length > 0) { - const allowLuckUpgrades = customModifierSettings.allowLuckUpgrades ?? true; - for (const tier of customModifierSettings.guaranteedModifierTiers) { - options.push(getModifierTypeOptionWithRetry(options, retryCount, party, tier, allowLuckUpgrades)); - } - } - - // Fill remaining - if (options.length < count && customModifierSettings.fillRemaining) { - while (options.length < count) { - options.push(getModifierTypeOptionWithRetry(options, retryCount, party, undefined)); - } - } - } - - overridePlayerModifierTypeOptions(options, party); - - return options; -} - -/** - * Will generate a {@linkcode ModifierType} from the {@linkcode ModifierPoolType.PLAYER} pool, attempting to retry duplicated items up to retryCount - * @param existingOptions Currently generated options - * @param retryCount How many times to retry before allowing a dupe item - * @param party Current player party, used to calculate items in the pool - * @param tier If specified will generate item of tier - * @param allowLuckUpgrades `true` to allow items to upgrade tiers (the little animation that plays and is affected by luck) - */ -function getModifierTypeOptionWithRetry( - existingOptions: ModifierTypeOption[], - retryCount: number, - party: PlayerPokemon[], - tier?: RewardTier, - allowLuckUpgrades?: boolean, -): ModifierTypeOption { - allowLuckUpgrades = allowLuckUpgrades ?? true; - let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, tier, undefined, 0, allowLuckUpgrades); - let r = 0; - while ( - existingOptions.length && - ++r < retryCount && - existingOptions.filter(o => o.type.name === candidate?.type.name || o.type.group === candidate?.type.group).length - ) { - console.log("Retry count:", r); - console.log(candidate?.type.group); - console.log(candidate?.type.name); - console.log(existingOptions.filter(o => o.type.name === candidate?.type.name).length); - console.log(existingOptions.filter(o => o.type.group === candidate?.type.group).length); - candidate = getNewModifierTypeOption( - party, - ModifierPoolType.PLAYER, - candidate?.type.tier ?? tier, - candidate?.upgradeCount, - 0, - allowLuckUpgrades, - ); - } - return candidate!; -} - -/** - * Replaces the {@linkcode ModifierType} of the entries within {@linkcode options} with any - * {@linkcode ModifierOverride} entries listed in {@linkcode Overrides.ITEM_REWARD_OVERRIDE} - * up to the smallest amount of entries between {@linkcode options} and the override array. - * @param options Array of naturally rolled {@linkcode ModifierTypeOption}s - * @param party Array of the player's current party - */ -export function overridePlayerModifierTypeOptions(options: ModifierTypeOption[], party: PlayerPokemon[]) { - const minLength = Math.min(options.length, Overrides.ITEM_REWARD_OVERRIDE.length); - for (let i = 0; i < minLength; i++) { - const override: ModifierOverride = Overrides.ITEM_REWARD_OVERRIDE[i]; - const modifierFunc = modifierTypeInitObj[override.name]; - let modifierType: ModifierType | null = modifierFunc(); - - if (modifierType instanceof ModifierTypeGenerator) { - const pregenArgs = "type" in override && override.type !== null ? [override.type] : undefined; - modifierType = modifierType.generateType(party, pregenArgs); - } - - if (modifierType) { - options[i].type = modifierType.withIdFromFunc(modifierFunc).withTierFromPool(ModifierPoolType.PLAYER, party); - } - } -} - -export function getPlayerShopModifierTypeOptionsForWave(waveIndex: number, baseCost: number): ModifierTypeOption[] { - if (!(waveIndex % 10)) { - return []; - } - - const options = [ - [ - new ModifierTypeOption(modifierTypeInitObj.POTION(), 0, baseCost * 0.2), - new ModifierTypeOption(modifierTypeInitObj.ETHER(), 0, baseCost * 0.4), - new ModifierTypeOption(modifierTypeInitObj.REVIVE(), 0, baseCost * 2), - ], - [ - new ModifierTypeOption(modifierTypeInitObj.SUPER_POTION(), 0, baseCost * 0.45), - new ModifierTypeOption(modifierTypeInitObj.FULL_HEAL(), 0, baseCost), - ], - [ - new ModifierTypeOption(modifierTypeInitObj.ELIXIR(), 0, baseCost), - new ModifierTypeOption(modifierTypeInitObj.MAX_ETHER(), 0, baseCost), - ], - [ - new ModifierTypeOption(modifierTypeInitObj.HYPER_POTION(), 0, baseCost * 0.8), - new ModifierTypeOption(modifierTypeInitObj.MAX_REVIVE(), 0, baseCost * 2.75), - new ModifierTypeOption(modifierTypeInitObj.MEMORY_MUSHROOM(), 0, baseCost * 4), - ], - [ - new ModifierTypeOption(modifierTypeInitObj.MAX_POTION(), 0, baseCost * 1.5), - new ModifierTypeOption(modifierTypeInitObj.MAX_ELIXIR(), 0, baseCost * 2.5), - ], - [new ModifierTypeOption(modifierTypeInitObj.FULL_RESTORE(), 0, baseCost * 2.25)], - [new ModifierTypeOption(modifierTypeInitObj.SACRED_ASH(), 0, baseCost * 10)], - ]; - return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat(); -} - -/** - * Generates a ModifierType from the specified pool - * @param party party of the trainer using the item - * @param poolType PLAYER/WILD/TRAINER - * @param tier If specified, will override the initial tier of an item (can still upgrade with luck) - * @param upgradeCount If defined, means that this is a new ModifierType being generated to override another via luck upgrade. Used for recursive logic - * @param retryCount Max allowed tries before the next tier down is checked for a valid ModifierType - * @param allowLuckUpgrades Default true. If false, will not allow ModifierType to randomly upgrade to next tier - */ -function getNewModifierTypeOption( - party: Pokemon[], - poolType: ModifierPoolType, - baseTier?: RewardTier, - upgradeCount?: number, - retryCount = 0, - allowLuckUpgrades = true, -): ModifierTypeOption | null { - const player = !poolType; - const pool = getModifierPoolForType(poolType); - const thresholds = getPoolThresholds(poolType); - - let tier = 0; - if (isNullOrUndefined(baseTier)) { - baseTier = randomBaseTier(); - } - if (isNullOrUndefined(upgradeCount)) { - upgradeCount = allowLuckUpgrades ? getUpgradeCount(party, player, baseTier) : 0; - tier = baseTier + upgradeCount; - } else { - tier = baseTier; - } - - const tierThresholds = Object.keys(thresholds[tier]); - const totalWeight = Number.parseInt(tierThresholds[tierThresholds.length - 1]); - const value = randSeedInt(totalWeight); - let index: number | undefined; - for (const t of tierThresholds) { - const threshold = Number.parseInt(t); - if (value < threshold) { - index = thresholds[tier][threshold]; - break; - } - } - - if (index === undefined) { - return null; - } - - if (player) { - console.log(index, ignoredPoolIndexes[tier].filter(i => i <= index).length, ignoredPoolIndexes[tier]); - } - let modifierType: ModifierType | null = pool[tier][index].modifierType; - if (modifierType instanceof ModifierTypeGenerator) { - modifierType = (modifierType as ModifierTypeGenerator).generateType(party); - if (modifierType === null) { - if (player) { - console.log(RewardTier[tier], upgradeCount); - } - return getNewModifierTypeOption(party, poolType, tier, upgradeCount, ++retryCount); - } - } - - console.log(modifierType, !player ? "(enemy)" : ""); - - return new ModifierTypeOption(modifierType as ModifierType, upgradeCount!); // TODO: is this bang correct? -} - -function getPoolThresholds(poolType: ModifierPoolType) { - let thresholds: object; - switch (poolType) { - case ModifierPoolType.PLAYER: - thresholds = modifierPoolThresholds; - break; - } - return thresholds; -} - -function randomBaseTier(): RewardTier { - const tierValue = randSeedInt(1024); - - if (tierValue > 255) { - return RewardTier.COMMON; - } - if (tierValue > 60) { - return RewardTier.GREAT; - } - if (tierValue > 12) { - return RewardTier.ULTRA; - } - if (tierValue) { - return RewardTier.ROGUE; - } - return RewardTier.MASTER; -} - -function getUpgradeCount( - party: Pokemon[], - player: boolean, - baseTier: RewardTier, - allowLuckUpgrades = true, -): RewardTier { - const pool = getModifierPoolForType(ModifierPoolType.PLAYER); - let upgradeCount = 0; - if (player) { - if (baseTier < RewardTier.MASTER && allowLuckUpgrades) { - const partyLuckValue = getPartyLuckValue(party); - const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4)); - while (pool.hasOwnProperty(baseTier + upgradeCount + 1) && pool[baseTier + upgradeCount + 1].length) { - if (randSeedInt(upgradeOdds) < 4) { - upgradeCount++; - } else { - break; - } - } - } - } - return upgradeCount; -} - -export function getDefaultModifierTypeForTier(tier: RewardTier): ModifierType { - const modifierPool = getModifierPoolForType(ModifierPoolType.PLAYER); - let modifierType: ModifierType | WeightedModifierType = modifierPool[tier || RewardTier.COMMON][0]; - if (modifierType instanceof WeightedModifierType) { - modifierType = (modifierType as WeightedModifierType).modifierType; - } - return modifierType; -} - -export class ModifierTypeOption { - public type: ModifierType; - public upgradeCount: number; - public cost: number; - - constructor(type: ModifierType, upgradeCount: number, cost = 0) { - this.type = type; - this.upgradeCount = upgradeCount; - this.cost = Math.min(Math.round(cost), Number.MAX_SAFE_INTEGER); - } -} - -/** - * Calculates the team's luck value. - * @param party The player's party. - * @returns A number between 0 and 14 based on the party's total luck value, or a random number between 0 and 14 if the player is in Daily Run mode. - */ -export function getPartyLuckValue(party: Pokemon[]): number { - if (globalScene.gameMode.isDaily) { - const DailyLuck = new NumberHolder(0); - globalScene.executeWithSeedOffset( - () => { - DailyLuck.value = randSeedInt(15); // Random number between 0 and 14 - }, - 0, - globalScene.seed, - ); - return DailyLuck.value; - } - const eventSpecies = timedEventManager.getEventLuckBoostedSpecies(); - const luck = Phaser.Math.Clamp( - party - .map(p => (p.isAllowedInBattle() ? p.getLuck() + (eventSpecies.includes(p.species.speciesId) ? 1 : 0) : 0)) - .reduce((total: number, value: number) => (total += value), 0), - 0, - 14, - ); - return Math.min(timedEventManager.getEventLuckBoost() + (luck ?? 0), 14); -} - -export function getLuckString(luckValue: number): string { - return ["D", "C", "C+", "B-", "B", "B+", "A-", "A", "A+", "A++", "S", "S+", "SS", "SS+", "SSS"][luckValue]; -} - -export function getLuckTextTint(luckValue: number): number { - let modifierTier: RewardTier; - if (luckValue > 11) { - modifierTier = RewardTier.LUXURY; - } else if (luckValue > 9) { - modifierTier = RewardTier.MASTER; - } else if (luckValue > 5) { - modifierTier = RewardTier.ROGUE; - } else if (luckValue > 2) { - modifierTier = RewardTier.ULTRA; - } else if (luckValue) { - modifierTier = RewardTier.GREAT; - } else { - modifierTier = RewardTier.COMMON; - } - return getModifierTierTextTint(modifierTier); -} - -export function initModifierTypes() { - for (const [key, value] of Object.entries(modifierTypeInitObj)) { - modifierTypes[key] = value; - } -} - -// TODO: If necessary, add the rest of the modifier types here. -// For now, doing the minimal work until the modifier rework lands. -const ModifierTypeConstructorMap = Object.freeze({ - ModifierTypeGenerator, -}); - -/** - * Map of of modifier type strings to their constructor type - */ -export type ModifierTypeConstructorMap = typeof ModifierTypeConstructorMap; - -/** - * Map of modifier type strings to their instance type - */ -export type ModifierTypeInstanceMap = { - [K in keyof ModifierTypeConstructorMap]: InstanceType; -}; - -export type ModifierTypeString = keyof ModifierTypeConstructorMap; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts deleted file mode 100644 index 15c42fa15e8..00000000000 --- a/src/modifier/modifier.ts +++ /dev/null @@ -1,620 +0,0 @@ -import { globalScene } from "#app/global-scene"; -import Overrides from "#app/overrides"; -import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#balance/pokemon-evolutions"; -import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#balance/starters"; -import { getLevelTotalExp } from "#data/exp"; -import { MAX_PER_TYPE_POKEBALLS } from "#data/pokeball"; -import { HeldItemId } from "#enums/held-item-id"; -import { LearnMoveType } from "#enums/learn-move-type"; -import type { Nature } from "#enums/nature"; -import type { PokeballType } from "#enums/pokeball"; -import type { PokemonType } from "#enums/pokemon-type"; -import { SpeciesId } from "#enums/species-id"; -import type { PlayerPokemon, Pokemon } from "#field/pokemon"; -import type { HeldItemConfiguration } from "#items/held-item-data-types"; -import { assignItemsFromConfiguration } from "#items/held-item-pool"; -import { TrainerItemEffect } from "#items/trainer-item"; -import type { TrainerItemConfiguration } from "#items/trainer-item-data-types"; -import type { - EvolutionItemModifierType, - ModifierType, - TerastallizeModifierType, - TmModifierType, -} from "#modifiers/modifier-type"; -import type { VoucherType } from "#system/voucher"; -import type { ModifierInstanceMap, ModifierString } from "#types/modifier-types"; -import { isNullOrUndefined, NumberHolder } from "#utils/common"; - -export type ModifierPredicate = (modifier: Modifier) => boolean; - -export abstract class Modifier { - public type: ModifierType; - - constructor(type: ModifierType) { - this.type = type; - } - - /** - * Return whether this modifier is of the given class - * - * @remarks - * Used to avoid requiring the caller to have imported the specific modifier class, avoiding circular dependencies. - * - * @param modifier - The modifier to check against - * @returns Whether the modiifer is an instance of the given type - */ - public is(modifier: T): this is ModifierInstanceMap[T] { - const targetModifier = ModifierClassMap[modifier]; - if (!targetModifier) { - return false; - } - return this instanceof targetModifier; - } - - match(_modifier: Modifier): boolean { - return false; - } - - /** - * 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; - } - - /** - * Handles applying of {@linkcode Modifier} - * @param args collection of all passed parameters - */ - abstract apply(...args: unknown[]): boolean; -} - -export abstract class ConsumableModifier extends Modifier { - add(_modifiers: Modifier[]): boolean { - return true; - } -} - -export class AddPokeballModifier extends ConsumableModifier { - private pokeballType: PokeballType; - private count: number; - - constructor(type: ModifierType, pokeballType: PokeballType, count: number) { - super(type); - - this.pokeballType = pokeballType; - this.count = count; - } - - /** - * Applies {@linkcode AddPokeballModifier} - * @param battleScene {@linkcode BattleScene} - * @returns always `true` - */ - override apply(): boolean { - const pokeballCounts = globalScene.pokeballCounts; - pokeballCounts[this.pokeballType] = Math.min( - pokeballCounts[this.pokeballType] + this.count, - MAX_PER_TYPE_POKEBALLS, - ); - - return true; - } -} - -export class AddVoucherModifier extends ConsumableModifier { - private voucherType: VoucherType; - private count: number; - - constructor(type: ModifierType, voucherType: VoucherType, count: number) { - super(type); - - this.voucherType = voucherType; - this.count = count; - } - - /** - * Applies {@linkcode AddVoucherModifier} - * @param battleScene {@linkcode BattleScene} - * @returns always `true` - */ - override apply(): boolean { - const voucherCounts = globalScene.gameData.voucherCounts; - voucherCounts[this.voucherType] += this.count; - - return true; - } -} - -export abstract class ConsumablePokemonModifier extends ConsumableModifier { - public pokemonId: number; - - constructor(type: ModifierType, pokemonId: number) { - super(type); - - this.pokemonId = 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; - - getPokemon() { - return globalScene.getPlayerParty().find(p => p.id === this.pokemonId); - } -} - -export class TerrastalizeModifier extends ConsumablePokemonModifier { - public override type: TerastallizeModifierType; - public teraType: PokemonType; - - constructor(type: TerastallizeModifierType, pokemonId: number, teraType: PokemonType) { - super(type, pokemonId); - - this.teraType = teraType; - } - - /** - * Checks if {@linkcode TerrastalizeModifier} should be applied - * @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item - * @returns `true` if the {@linkcode TerrastalizeModifier} should be applied - */ - override shouldApply(playerPokemon?: PlayerPokemon): boolean { - return ( - super.shouldApply(playerPokemon) && - [playerPokemon?.species.speciesId, playerPokemon?.fusionSpecies?.speciesId].filter( - s => s === SpeciesId.TERAPAGOS || s === SpeciesId.OGERPON || s === SpeciesId.SHEDINJA, - ).length === 0 - ); - } - - /** - * Applies {@linkcode TerrastalizeModifier} - * @param pokemon The {@linkcode PlayerPokemon} that consumes the item - * @returns `true` if hp was restored - */ - override apply(pokemon: Pokemon): boolean { - pokemon.teraType = this.teraType; - return true; - } -} - -export class PokemonHpRestoreModifier extends ConsumablePokemonModifier { - private restorePoints: number; - private restorePercent: number; - private healStatus: boolean; - public fainted: boolean; - - constructor( - type: ModifierType, - pokemonId: number, - restorePoints: number, - restorePercent: number, - healStatus: boolean, - fainted?: boolean, - ) { - super(type, pokemonId); - - this.restorePoints = restorePoints; - this.restorePercent = restorePercent; - this.healStatus = healStatus; - this.fainted = !!fainted; - } - - /** - * 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")) - ); - } - - /** - * 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 * multiplier); - } - if (this.fainted || this.healStatus) { - pokemon.resetStatus(true, true, false, false); - } - pokemon.hp = Math.min( - pokemon.hp + - Math.max(Math.ceil(Math.max(Math.floor(this.restorePercent * 0.01 * pokemon.getMaxHp()), restorePoints)), 1), - pokemon.getMaxHp(), - ); - return true; - } - return false; - } -} - -export class PokemonStatusHealModifier extends ConsumablePokemonModifier { - /** - * 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, false, false); - return true; - } -} - -export abstract class ConsumablePokemonMoveModifier extends ConsumablePokemonModifier { - public moveIndex: number; - - constructor(type: ModifierType, pokemonId: number, moveIndex: number) { - super(type, pokemonId); - - this.moveIndex = moveIndex; - } -} - -export class PokemonPpRestoreModifier extends ConsumablePokemonMoveModifier { - private restorePoints: number; - - constructor(type: ModifierType, pokemonId: number, moveIndex: number, restorePoints: number) { - super(type, pokemonId, moveIndex); - - this.restorePoints = restorePoints; - } - - /** - * 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: number; - - constructor(type: ModifierType, pokemonId: number, restorePoints: number) { - super(type, pokemonId); - - this.restorePoints = restorePoints; - } - - /** - * 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; - } -} - -export class PokemonPpUpModifier extends ConsumablePokemonMoveModifier { - private upPoints: number; - - constructor(type: ModifierType, pokemonId: number, moveIndex: number, upPoints: number) { - super(type, pokemonId, moveIndex); - - this.upPoints = upPoints; - } - - /** - * 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.maxPpOverride) { - move.ppUp = Math.min(move.ppUp + this.upPoints, 3); - } - - return true; - } -} - -export class PokemonNatureChangeModifier extends ConsumablePokemonModifier { - public nature: Nature; - - constructor(type: ModifierType, pokemonId: number, nature: Nature) { - super(type, pokemonId); - - this.nature = nature; - } - - /** - * Applies {@linkcode PokemonNatureChangeModifier} - * @param playerPokemon {@linkcode PlayerPokemon} to apply the {@linkcode Nature} change to - * @returns - */ - override apply(playerPokemon: PlayerPokemon): boolean { - playerPokemon.setCustomNature(this.nature); - globalScene.gameData.unlockSpeciesNature(playerPokemon.species, this.nature); - - return true; - } -} - -export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier { - /** - * 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 { - globalScene.applyPlayerItems(TrainerItemEffect.LEVEL_INCREMENT_BOOSTER, { numberHolder: levelCount }); - - playerPokemon.level += levelCount.value; - if (playerPokemon.level <= globalScene.getMaxExpLevel(true)) { - playerPokemon.exp = getLevelTotalExp(playerPokemon.level, playerPokemon.species.growthRate); - playerPokemon.levelExp = 0; - } - - playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY); - - globalScene.phaseManager.unshiftNew( - "LevelUpPhase", - globalScene.getPlayerParty().indexOf(playerPokemon), - playerPokemon.level - levelCount.value, - playerPokemon.level, - ); - - return true; - } -} - -export class TmModifier extends ConsumablePokemonModifier { - public override type: TmModifierType; - - /** - * Applies {@linkcode TmModifier} - * @param playerPokemon The {@linkcode PlayerPokemon} that should learn the TM - * @returns always `true` - */ - override apply(playerPokemon: PlayerPokemon): boolean { - globalScene.phaseManager.unshiftNew( - "LearnMovePhase", - globalScene.getPlayerParty().indexOf(playerPokemon), - this.type.moveId, - LearnMoveType.TM, - ); - - return true; - } -} - -export class RememberMoveModifier extends ConsumablePokemonModifier { - public levelMoveIndex: number; - - constructor(type: ModifierType, pokemonId: number, levelMoveIndex: number) { - super(type, pokemonId); - - this.levelMoveIndex = levelMoveIndex; - } - - /** - * Applies {@linkcode RememberMoveModifier} - * @param playerPokemon The {@linkcode PlayerPokemon} that should remember the move - * @returns always `true` - */ - override apply(playerPokemon: PlayerPokemon, cost?: number): boolean { - globalScene.phaseManager.unshiftNew( - "LearnMovePhase", - globalScene.getPlayerParty().indexOf(playerPokemon), - playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], - LearnMoveType.MEMORY, - cost, - ); - - return true; - } -} - -export class EvolutionItemModifier extends ConsumablePokemonModifier { - public override type: EvolutionItemModifierType; - /** - * 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.evoItem === this.type.evolutionItem && e.validate(playerPokemon, false, e.item!), - ) - : null; - - if (!matchingEvolution && playerPokemon.isFusion()) { - matchingEvolution = pokemonEvolutions[playerPokemon.fusionSpecies!.speciesId].find( - e => e.evoItem === this.type.evolutionItem && e.validate(playerPokemon, true, e.item!), - ); - if (matchingEvolution) { - matchingEvolution = new FusionSpeciesFormEvolution(playerPokemon.species.speciesId, matchingEvolution); - } - } - - if (matchingEvolution) { - globalScene.phaseManager.unshiftNew("EvolutionPhase", playerPokemon, matchingEvolution, playerPokemon.level - 1); - return true; - } - - return false; - } -} - -export class FusePokemonModifier extends ConsumablePokemonModifier { - public fusePokemonId: number; - - constructor(type: ModifierType, pokemonId: number, fusePokemonId: number) { - super(type, pokemonId); - - this.fusePokemonId = fusePokemonId; - } - - /** - * 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 - ); - } - - /** - * 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 apply(playerPokemon: PlayerPokemon, playerPokemon2: PlayerPokemon): boolean { - playerPokemon.fuse(playerPokemon2); - return true; - } -} - -export class MoneyRewardModifier extends ConsumableModifier { - private moneyMultiplier: number; - - constructor(type: ModifierType, moneyMultiplier: number) { - super(type); - - this.moneyMultiplier = moneyMultiplier; - } - - /** - * Applies {@linkcode MoneyRewardModifier} - * @returns always `true` - */ - override apply(): boolean { - const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); - - globalScene.applyPlayerItems(TrainerItemEffect.MONEY_MULTIPLIER, { numberHolder: moneyAmount }); - - globalScene.addMoney(moneyAmount.value); - - for (const p of globalScene.getPlayerParty()) { - if (p.species?.speciesId === SpeciesId.GIMMIGHOUL || p.fusionSpecies?.speciesId === SpeciesId.GIMMIGHOUL) { - const factor = Math.min(Math.floor(this.moneyMultiplier), 3); - p.heldItemManager.add(HeldItemId.GIMMIGHOUL_EVO_TRACKER, factor); - } - } - - return true; - } -} - -/** - * Uses either `MODIFIER_OVERRIDE` in overrides.ts to set {@linkcode PersistentModifier}s for either: - * - The player - * - The enemy - * @param isPlayer {@linkcode boolean} for whether the player (`true`) or enemy (`false`) is being overridden - */ -export function overrideTrainerItems(isPlayer = true): void { - const trainerItemsOverride: TrainerItemConfiguration = isPlayer - ? Overrides.STARTING_TRAINER_ITEMS_OVERRIDE - : Overrides.OPP_TRAINER_ITEMS_OVERRIDE; - if (!trainerItemsOverride || trainerItemsOverride.length === 0 || !globalScene) { - return; - } - - // If it's the opponent, clear all of their current modifiers to avoid stacking - if (!isPlayer) { - globalScene.clearEnemyItems(); - } - - globalScene.assignTrainerItemsFromConfiguration(trainerItemsOverride, isPlayer); -} - -/** - * Uses either `HELD_ITEMS_OVERRIDE` in overrides.ts to set {@linkcode PokemonHeldItemModifier}s for either: - * - The first member of the player's team when starting a new game - * - An enemy {@linkcode Pokemon} being spawned in - * @param pokemon {@linkcode Pokemon} whose held items are being overridden - * @param isPlayer {@linkcode boolean} for whether the {@linkcode pokemon} is the player's (`true`) or an enemy (`false`) - */ -export function overrideHeldItems(pokemon: Pokemon, isPlayer = true): void { - const heldItemsOverride: HeldItemConfiguration = isPlayer - ? Overrides.STARTING_HELD_ITEMS_OVERRIDE - : Overrides.OPP_HELD_ITEMS_OVERRIDE; - if (!heldItemsOverride || heldItemsOverride.length === 0 || !globalScene) { - return; - } - - if (!isPlayer) { - pokemon.heldItemManager.clearItems(); - } - - assignItemsFromConfiguration(heldItemsOverride, pokemon); -} - -/** - * Private map from modifier strings to their constructors. - * - * @remarks - * Used for {@linkcode Modifier.is} to check if a modifier is of a certain type without - * requiring modifier types to be imported in every file. - */ -const ModifierClassMap = Object.freeze({ - ConsumableModifier, - AddPokeballModifier, - AddVoucherModifier, - ConsumablePokemonModifier, - TerrastalizeModifier, - PokemonHpRestoreModifier, - PokemonStatusHealModifier, - ConsumablePokemonMoveModifier, - PokemonPpRestoreModifier, - PokemonAllMovePpRestoreModifier, - PokemonPpUpModifier, - PokemonNatureChangeModifier, - PokemonLevelIncrementModifier, - TmModifier, - RememberMoveModifier, - EvolutionItemModifier, - FusePokemonModifier, -}); - -export type ModifierConstructorMap = typeof ModifierClassMap; diff --git a/src/overrides.ts b/src/overrides.ts index 0bf44d59329..3a96a1c7bea 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -22,8 +22,8 @@ import { Unlockables } from "#enums/unlockables"; import { VariantTier } from "#enums/variant-tier"; import { WeatherType } from "#enums/weather-type"; import { HeldItemConfiguration } from "#items/held-item-data-types"; +import { type RewardOverride } from "#items/reward"; import { TrainerItemConfiguration } from "#items/trainer-item-data-types"; -import { type ModifierOverride } from "#modifiers/modifier-type"; import { Variant } from "#sprites/variant"; /** @@ -248,16 +248,16 @@ class DefaultOverrides { // ------------------------- /** * Overrides labeled `MODIFIER` deal with any modifier so long as it doesn't require a party - * member to hold it (typically this is, extends, or generates a {@linkcode ModifierType}), + * member to hold it (typically this is, extends, or generates a {@linkcode Reward}), * like `EXP_SHARE`, `CANDY_JAR`, etc. * - * Overrides labeled `HELD_ITEM` specifically pertain to any entry in {@linkcode modifierTypes} that - * extends, or generates a {@linkcode PokemonHeldItemModifierType}, like `SOUL_DEW`, `TOXIC_ORB`, etc. + * Overrides labeled `HELD_ITEM` specifically pertain to any entry in {@linkcode allRewards} that + * extends, or generates a {@linkcode PokemonHeldItemReward}, like `SOUL_DEW`, `TOXIC_ORB`, etc. * * Note that, if count is not provided, it will default to 1. * * Additionally, note that some held items and modifiers are grouped together via - * a {@linkcode ModifierTypeGenerator} and require pre-generation arguments to get + * a {@linkcode RewardGenerator} and require pre-generation arguments to get * a specific item from that group. If a type is not set, the generator will either * use the party to weight item choice or randomly pick an item. * @@ -273,25 +273,25 @@ class DefaultOverrides { * STARTING_HELD_ITEM_OVERRIDE = [{name: "BERRY"}] * ``` */ - /** Override array of {@linkcode ModifierOverride}s used to provide held items to first party member when starting a new game. */ + /** Override array of {@linkcode RewardOverride}s used to provide held items to first party member when starting a new game. */ readonly STARTING_TRAINER_ITEMS_OVERRIDE: TrainerItemConfiguration = []; - /** Override array of {@linkcode ModifierOverride}s used to provide held items to enemies on spawn. */ + /** Override array of {@linkcode RewardOverride}s used to provide held items to enemies on spawn. */ readonly OPP_TRAINER_ITEMS_OVERRIDE: TrainerItemConfiguration = []; - /** Override array of {@linkcode ModifierOverride}s used to provide held items to first party member when starting a new game. */ + /** Override array of {@linkcode RewardOverride}s used to provide held items to first party member when starting a new game. */ readonly STARTING_HELD_ITEMS_OVERRIDE: HeldItemConfiguration = []; - /** Override array of {@linkcode ModifierOverride}s used to provide held items to enemies on spawn. */ + /** Override array of {@linkcode RewardOverride}s used to provide held items to enemies on spawn. */ readonly OPP_HELD_ITEMS_OVERRIDE: HeldItemConfiguration = []; /** - * Override array of {@linkcode ModifierOverride}s used to replace the generated item rolls after a wave. + * Override array of {@linkcode RewardOverride}s used to replace the generated reward rolls after a wave. * * If less entries are listed than rolled, only those entries will be used to replace the corresponding items while the rest randomly generated. * If more entries are listed than rolled, only the first X entries will be used, where X is the number of items rolled. * * Note that, for all items in the array, `count` is not used. */ - readonly ITEM_REWARD_OVERRIDE: ModifierOverride[] = []; + readonly REWARD_OVERRIDE: RewardOverride[] = []; /** * If `true`, disable all non-scripted opponent trainer encounters. diff --git a/src/phase-manager.ts b/src/phase-manager.ts index 5fb56aff54e..db488e1583f 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -4,7 +4,7 @@ import { type PhasePriorityQueue, PostSummonPhasePriorityQueue } from "#data/pha import type { DynamicPhaseType } from "#enums/dynamic-phase-type"; import type { Pokemon } from "#field/pokemon"; import { ActivatePriorityQueuePhase } from "#phases/activate-priority-queue-phase"; -import { AddEnemyBuffModifierPhase } from "#phases/add-enemy-buff-modifier-phase"; +import { AddEnemyTokenPhase } from "#phases/add-enemy-token-phase"; import { AttemptCapturePhase } from "#phases/attempt-capture-phase"; import { AttemptRunPhase } from "#phases/attempt-run-phase"; import { BattleEndPhase } from "#phases/battle-end-phase"; @@ -116,7 +116,7 @@ import { type Constructor, coerceArray } from "#utils/common"; */ const PHASES = Object.freeze({ ActivatePriorityQueuePhase, - AddEnemyBuffModifierPhase, + AddEnemyTokenPhase, AttemptCapturePhase, AttemptRunPhase, BattleEndPhase, diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-token-phase.ts similarity index 61% rename from src/phases/add-enemy-buff-modifier-phase.ts rename to src/phases/add-enemy-token-phase.ts index 5d3d529420b..af617b49bb4 100644 --- a/src/phases/add-enemy-buff-modifier-phase.ts +++ b/src/phases/add-enemy-token-phase.ts @@ -1,15 +1,15 @@ import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { assignEnemyBuffTokenForWave } from "#items/trainer-item-pool"; -export class AddEnemyBuffModifierPhase extends Phase { - public readonly phaseName = "AddEnemyBuffModifierPhase"; +export class AddEnemyTokenPhase extends Phase { + public readonly phaseName = "AddEnemyTokenPhase"; start() { super.start(); const waveIndex = globalScene.currentBattle.waveIndex; - const tier = !(waveIndex % 1000) ? RewardTier.ULTRA : !(waveIndex % 250) ? RewardTier.GREAT : RewardTier.COMMON; + const tier = !(waveIndex % 1000) ? RarityTier.ULTRA : !(waveIndex % 250) ? RarityTier.GREAT : RarityTier.COMMON; const count = Math.ceil(waveIndex / 250); for (let i = 0; i < count; i++) { diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index 0649d6d7208..fd8783a9e28 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -2,12 +2,12 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { allHeldItems } from "#data/data-lists"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemCategoryId, isItemInCategory } from "#enums/held-item-id"; import { CommonAnim } from "#enums/move-anims-common"; import type { Pokemon } from "#field/pokemon"; import { applyHeldItems } from "#items/all-held-items"; import type { BerryHeldItem } from "#items/berry"; -import { HeldItemEffect } from "#items/held-item"; import { FieldPhase } from "#phases/field-phase"; import { BooleanHolder } from "#utils/common"; import i18next from "i18next"; diff --git a/src/phases/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts index 94923ae8c1f..30dca435637 100644 --- a/src/phases/egg-hatch-phase.ts +++ b/src/phases/egg-hatch-phase.ts @@ -228,7 +228,7 @@ export class EggHatchPhase extends Phase { if (globalScene.phaseManager.findPhase(p => p.is("EggHatchPhase"))) { this.eggHatchHandler.clear(); } else { - globalScene.time.delayedCall(250, () => globalScene.setModifiersVisible(true)); + globalScene.time.delayedCall(250, () => globalScene.setItemsVisible(true)); } super.end(); } diff --git a/src/phases/egg-summary-phase.ts b/src/phases/egg-summary-phase.ts index c236c5c3abc..94df2d1143c 100644 --- a/src/phases/egg-summary-phase.ts +++ b/src/phases/egg-summary-phase.ts @@ -39,7 +39,7 @@ export class EggSummaryPhase extends Phase { } end() { - globalScene.time.delayedCall(250, () => globalScene.setModifiersVisible(true)); + globalScene.time.delayedCall(250, () => globalScene.setItemsVisible(true)); globalScene.ui.setModeForceTransition(UiMode.MESSAGE).then(() => { super.end(); }); diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 9596ed9a6b0..81a9ef649ab 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -21,7 +21,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { UiMode } from "#enums/ui-mode"; import { EncounterPhaseEvent } from "#events/battle-scene"; import type { Pokemon } from "#field/pokemon"; -import { overrideHeldItems, overrideTrainerItems } from "#modifiers/modifier"; +import { overrideHeldItems, overrideTrainerItems } from "#items/item-overrides"; import { getEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import { doTrainerExclamation } from "#mystery-encounters/encounter-phase-utils"; import { getGoldenBugNetSpecies } from "#mystery-encounters/encounter-pokemon-utils"; diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 1cea69a6baf..504d2bc2b0d 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -10,12 +10,12 @@ import { BattleType } from "#enums/battle-type"; import type { BattlerIndex } from "#enums/battler-index"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { BattlerTagType } from "#enums/battler-tag-type"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HitResult } from "#enums/hit-result"; import { StatusEffect } from "#enums/status-effect"; import { SwitchType } from "#enums/switch-type"; import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import { PokemonMove } from "#moves/pokemon-move"; import { PokemonPhase } from "#phases/pokemon-phase"; import { isNullOrUndefined } from "#utils/common"; diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index dafb2f5693e..debb9aacb86 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -2,7 +2,7 @@ import { pokerogueApi } from "#api/pokerogue-api"; import { clientSessionId } from "#app/account"; import { globalScene } from "#app/global-scene"; import { pokemonEvolutions } from "#balance/pokemon-evolutions"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { getCharVariantFromDialogue } from "#data/dialogue"; import type { PokemonSpecies } from "#data/pokemon-species"; import { BattleType } from "#enums/battle-type"; @@ -151,10 +151,10 @@ export class GameOverPhase extends BattlePhase { this.handleUnlocks(); for (const species of this.firstRibbons) { - globalScene.phaseManager.unshiftNew("RibbonRewardPhase", modifierTypes.VOUCHER_PLUS, species); + globalScene.phaseManager.unshiftNew("RibbonRewardPhase", allRewards.VOUCHER_PLUS, species); } if (!firstClear) { - globalScene.phaseManager.unshiftNew("GameOverRewardPhase", modifierTypes.VOUCHER_PREMIUM); + globalScene.phaseManager.unshiftNew("GameOverRewardPhase", allRewards.VOUCHER_PREMIUM); } } this.getRunHistoryEntry().then(runHistoryEntry => { diff --git a/src/phases/game-over-reward-phase.ts b/src/phases/game-over-reward-phase.ts index 0fe2eae5a61..f8a15e05185 100644 --- a/src/phases/game-over-reward-phase.ts +++ b/src/phases/game-over-reward-phase.ts @@ -7,7 +7,7 @@ export class GameOverRewardPhase extends RewardPhase { public readonly phaseName = "GameOverRewardPhase"; doReward(): Promise { return new Promise(resolve => { - const newModifier = this.modifierType.newModifier(); + const newModifier = this.reward.newModifier(); globalScene.addModifier(newModifier); // Sound loaded into game as is globalScene.playSound("level_up_fanfare"); diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index e47a6e0e14d..804b99122b9 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -11,6 +11,7 @@ import { ArenaTagSide } from "#enums/arena-tag-side"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { BattlerTagType } from "#enums/battler-tag-type"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HitCheckResult } from "#enums/hit-check-result"; import { HitResult } from "#enums/hit-result"; import { MoveCategory } from "#enums/move-category"; @@ -23,7 +24,6 @@ import { isReflected, isVirtual, MoveUseMode } from "#enums/move-use-mode"; import { PokemonType } from "#enums/pokemon-type"; import type { Pokemon } from "#field/pokemon"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import { TrainerItemEffect } from "#items/trainer-item"; import { applyFilteredMoveAttrs, applyMoveAttrs } from "#moves/apply-attrs"; import type { Move, MoveAttr } from "#moves/move"; diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index 9de008f0af8..4f9c51974a1 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -532,13 +532,13 @@ export class MysteryEncounterRewardsPhase extends Phase { } else { this.doEncounterRewardsAndContinue(); } - // Do not use ME's seedOffset for rewards, these should always be consistent with waveIndex (once per wave) + // Do not use ME's seedOffset for allRewards, these should always be consistent with waveIndex (once per wave) }, globalScene.currentBattle.waveIndex * 1000); } } /** - * Queues encounter EXP and rewards phases, {@linkcode PostMysteryEncounterPhase}, and ends phase + * Queues encounter EXP and allRewards phases, {@linkcode PostMysteryEncounterPhase}, and ends phase */ doEncounterRewardsAndContinue() { const encounter = globalScene.currentBattle.mysteryEncounter!; diff --git a/src/phases/reward-phase.ts b/src/phases/reward-phase.ts index b3c243c3779..033a022958f 100644 --- a/src/phases/reward-phase.ts +++ b/src/phases/reward-phase.ts @@ -1,20 +1,19 @@ import { globalScene } from "#app/global-scene"; -import { type ModifierType, TrainerItemReward } from "#modifiers/modifier-type"; +import type { Reward } from "#items/reward"; import { BattlePhase } from "#phases/battle-phase"; -import type { ModifierTypeFunc } from "#types/modifier-types"; -import { getModifierType } from "#utils/modifier-utils"; +import type { RewardFunc } from "#types/rewards"; import i18next from "i18next"; export class RewardPhase extends BattlePhase { // RibbonRewardPhase extends RewardPhase and to make typescript happy // we need to use a union type here public readonly phaseName: "RewardPhase" | "RibbonRewardPhase" | "GameOverRewardPhase" = "RewardPhase"; - protected modifierType: ModifierType; + protected reward: Reward; - constructor(modifierTypeFunc: ModifierTypeFunc) { + constructor(rewardFunc: RewardFunc) { super(); - this.modifierType = getModifierType(modifierTypeFunc); + this.reward = rewardFunc(); } start() { @@ -25,16 +24,11 @@ export class RewardPhase extends BattlePhase { doReward(): Promise { return new Promise(resolve => { - if (this.modifierType instanceof TrainerItemReward) { - this.modifierType.apply(); - } else { - const newModifier = this.modifierType.newModifier(); - globalScene.addModifier(newModifier); - } + globalScene.applyReward(this.reward); globalScene.playSound("item_fanfare"); globalScene.ui.showText( i18next.t("battle:rewardGain", { - modifierName: this.modifierType.name, + modifierName: this.reward.name, }), null, () => resolve(), diff --git a/src/phases/ribbon-reward-phase.ts b/src/phases/ribbon-reward-phase.ts index 9c1737db2fc..42e5bd826a3 100644 --- a/src/phases/ribbon-reward-phase.ts +++ b/src/phases/ribbon-reward-phase.ts @@ -2,22 +2,22 @@ import { globalScene } from "#app/global-scene"; import type { PokemonSpecies } from "#data/pokemon-species"; import { UiMode } from "#enums/ui-mode"; import { RewardPhase } from "#phases/reward-phase"; -import type { ModifierTypeFunc } from "#types/modifier-types"; +import type { RewardFunc } from "#types/rewards"; import i18next from "i18next"; export class RibbonRewardPhase extends RewardPhase { public readonly phaseName = "RibbonRewardPhase"; private species: PokemonSpecies; - constructor(modifierTypeFunc: ModifierTypeFunc, species: PokemonSpecies) { - super(modifierTypeFunc); + constructor(rewardFunc: RewardFunc, species: PokemonSpecies) { + super(rewardFunc); this.species = species; } doReward(): Promise { return new Promise(resolve => { - const newModifier = this.modifierType.newModifier(); + const newModifier = this.reward.newModifier(); globalScene.addModifier(newModifier); globalScene.playSound("level_up_fanfare"); globalScene.ui.setMode(UiMode.MESSAGE); diff --git a/src/phases/select-reward-phase.ts b/src/phases/select-reward-phase.ts index 5cbad1134eb..4c5e080733c 100644 --- a/src/phases/select-reward-phase.ts +++ b/src/phases/select-reward-phase.ts @@ -1,54 +1,53 @@ import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; -import type { RewardTier } from "#enums/reward-tier"; +import type { MoveId } from "#enums/move-id"; +import { RewardPoolType } from "#enums/reward-pool-type"; +import type { RarityTier } from "#enums/reward-tier"; import { UiMode } from "#enums/ui-mode"; -import { TrainerItemEffect } from "#items/trainer-item"; -import type { Modifier } from "#modifiers/modifier"; -import type { CustomModifierSettings, ModifierType, ModifierTypeOption } from "#modifiers/modifier-type"; +import type { + PokemonMoveRecallRewardParams, + PokemonMoveRewardParams, + PokemonRewardParams, + Reward, + RewardOption, +} from "#items/reward"; +import { FusePokemonReward, type PokemonMoveReward, PokemonReward, RememberMoveReward, TmReward } from "#items/reward"; import { - FormChangeItemReward, - FusePokemonModifierType, - getPlayerModifierTypeOptions, - getPlayerShopModifierTypeOptionsForWave, - HeldItemReward, - PokemonModifierType, - PokemonMoveModifierType, - PokemonPpRestoreModifierType, - PokemonPpUpModifierType, - RememberMoveModifierType, - regenerateModifierPoolThresholds, - TmModifierType, - TrainerItemReward, -} from "#modifiers/modifier-type"; + type CustomRewardSettings, + generatePlayerRewardOptions, + generateRewardPoolWeights, + getRewardPoolForType, +} from "#items/reward-pool-utils"; +import { getPlayerShopRewardOptionsForWave, isMoveReward, isRememberMoveReward, isTmReward } from "#items/reward-utils"; +import { TrainerItemEffect } from "#items/trainer-item"; import { BattlePhase } from "#phases/battle-phase"; -import { PartyOption, PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler"; +import { PartyOption, PartyUiHandler, PartyUiMode, type PokemonMoveSelectFilter } from "#ui/party-ui-handler"; import { type RewardSelectUiHandler, SHOP_OPTIONS_ROW_LIMIT } from "#ui/reward-select-ui-handler"; import { isNullOrUndefined, NumberHolder } from "#utils/common"; import i18next from "i18next"; -export type ModifierSelectCallback = (rowCursor: number, cursor: number) => boolean; +export type RewardSelectCallback = (rowCursor: number, cursor: number) => boolean; export class SelectRewardPhase extends BattlePhase { public readonly phaseName = "SelectRewardPhase"; private rerollCount: number; - private modifierTiers?: RewardTier[]; - private customModifierSettings?: CustomModifierSettings; + private rarityTiers?: RarityTier[]; + private customRewardSettings?: CustomRewardSettings; private isCopy: boolean; - private typeOptions: ModifierTypeOption[]; + private typeOptions: RewardOption[]; constructor( rerollCount = 0, - modifierTiers?: RewardTier[], - customModifierSettings?: CustomModifierSettings, + rarityTiers?: RarityTier[], + customRewardSettings?: CustomRewardSettings, isCopy = false, ) { super(); this.rerollCount = rerollCount; - this.modifierTiers = modifierTiers; - this.customModifierSettings = customModifierSettings; + this.rarityTiers = rarityTiers; + this.customRewardSettings = customRewardSettings; this.isCopy = isCopy; } @@ -67,14 +66,15 @@ export class SelectRewardPhase extends BattlePhase { const party = globalScene.getPlayerParty(); if (!this.isCopy) { - regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); + generateRewardPoolWeights(getRewardPoolForType(this.getPoolType()), party, this.rerollCount); } - const modifierCount = this.getModifierCount(); + const rewardCount = this.getRewardCount(); - this.typeOptions = this.getModifierTypeOptions(modifierCount); + this.typeOptions = this.getRewardOptions(rewardCount); - const modifierSelectCallback = (rowCursor: number, cursor: number) => { + const rewardSelectCallback = (rowCursor: number, cursor: number) => { if (rowCursor < 0 || cursor < 0) { + // Attempt to skip the item pickup globalScene.ui.showText(i18next.t("battle:skipItemQuestion"), null, () => { globalScene.ui.setOverlayMode( UiMode.CONFIRM, @@ -83,7 +83,7 @@ export class SelectRewardPhase extends BattlePhase { globalScene.ui.setMode(UiMode.MESSAGE); super.end(); }, - () => this.resetModifierSelect(modifierSelectCallback), + () => this.resetRewardSelect(rewardSelectCallback), ); }); return false; @@ -94,13 +94,13 @@ export class SelectRewardPhase extends BattlePhase { case 0: switch (cursor) { case 0: - return this.rerollModifiers(); + return this.rerollRewards(); case 1: - return this.openModifierTransferScreen(modifierSelectCallback); - // Check the party, pass a callback to restore the modifier select screen. + return this.openItemTransferScreen(rewardSelectCallback); + // Check the party, pass a callback to restore the reward select screen. case 2: globalScene.ui.setModeWithoutClear(UiMode.PARTY, PartyUiMode.CHECK, -1, () => { - this.resetModifierSelect(modifierSelectCallback); + this.resetRewardSelect(rewardSelectCallback); }); return true; case 3: @@ -108,38 +108,34 @@ export class SelectRewardPhase extends BattlePhase { default: return false; } - // Pick an option from the rewards + // Pick an option from the allRewards case 1: - return this.selectRewardModifierOption(cursor, modifierSelectCallback); + return this.selectRewardOption(cursor, rewardSelectCallback); // Pick an option from the shop default: { - return this.selectShopModifierOption(rowCursor, cursor, modifierSelectCallback); + return this.selectShopOption(rowCursor, cursor, rewardSelectCallback); } } }; - this.resetModifierSelect(modifierSelectCallback); + this.resetRewardSelect(rewardSelectCallback); } - // Pick a modifier from among the rewards and apply it - private selectRewardModifierOption(cursor: number, modifierSelectCallback: ModifierSelectCallback): boolean { + // Pick a reward from among the allRewards and apply it + private selectRewardOption(cursor: number, rewardSelectCallback: RewardSelectCallback): boolean { if (this.typeOptions.length === 0) { globalScene.ui.clearText(); globalScene.ui.setMode(UiMode.MESSAGE); super.end(); return true; } - const modifierType = this.typeOptions[cursor].type; - return this.applyChosenModifier(modifierType, -1, modifierSelectCallback); + const reward = this.typeOptions[cursor].type; + return this.applyReward(reward, -1, rewardSelectCallback); } - // Pick a modifier from the shop and apply it - private selectShopModifierOption( - rowCursor: number, - cursor: number, - modifierSelectCallback: ModifierSelectCallback, - ): boolean { - const shopOptions = getPlayerShopModifierTypeOptionsForWave( + // Pick a reward from the shop and apply it + private selectShopOption(rowCursor: number, cursor: number, rewardSelectCallback: RewardSelectCallback): boolean { + const shopOptions = getPlayerShopRewardOptionsForWave( globalScene.currentBattle.waveIndex, globalScene.getWaveMoneyAmount(1), ); @@ -147,7 +143,7 @@ export class SelectRewardPhase extends BattlePhase { shopOptions[ rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT ]; - const modifierType = shopOption.type; + const reward = shopOption.type; // Apply Black Sludge to healing item cost const healingItemCost = new NumberHolder(shopOption.cost); globalScene.applyPlayerItems(TrainerItemEffect.HEAL_SHOP_COST, { numberHolder: healingItemCost }); @@ -158,39 +154,30 @@ export class SelectRewardPhase extends BattlePhase { return false; } - return this.applyChosenModifier(modifierType, cost, modifierSelectCallback); + return this.applyReward(reward, cost, rewardSelectCallback); } - // Apply a chosen modifier: do an effect or open the party menu - private applyChosenModifier( - modifierType: ModifierType, - cost: number, - modifierSelectCallback: ModifierSelectCallback, - ): boolean { - if (modifierType instanceof PokemonModifierType) { - if (modifierType instanceof HeldItemReward || modifierType instanceof FormChangeItemReward) { - this.openGiveHeldItemMenu(modifierType, modifierSelectCallback); - } else if (modifierType instanceof FusePokemonModifierType) { - this.openFusionMenu(modifierType, cost, modifierSelectCallback); + // Apply a chosen reward: do an effect or open the party menu + private applyReward(reward: Reward, cost: number, rewardSelectCallback: RewardSelectCallback): boolean { + if (reward instanceof PokemonReward) { + if (reward instanceof FusePokemonReward) { + this.openFusionMenu(reward, cost, rewardSelectCallback); } else { - this.openModifierMenu(modifierType, cost, modifierSelectCallback); + this.openPokemonRewardMenu(reward, cost, rewardSelectCallback); } - } else if (modifierType instanceof TrainerItemReward) { - console.log("WE GOT HERE"); - modifierType.apply(); + } else { + globalScene.applyReward(reward, {}, true); globalScene.updateItems(true); globalScene.ui.clearText(); globalScene.ui.setMode(UiMode.MESSAGE); super.end(); - } else { - this.applyModifier(modifierType.newModifier()!); } return cost === -1; } - // Reroll rewards - private rerollModifiers() { - const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); + // Reroll allRewards + private rerollRewards() { + const rerollCost = this.getRerollCost(globalScene.lockRarityTiers); if (rerollCost < 0 || globalScene.money < rerollCost) { globalScene.ui.playError(); return false; @@ -199,7 +186,7 @@ export class SelectRewardPhase extends BattlePhase { globalScene.phaseManager.unshiftNew( "SelectRewardPhase", this.rerollCount + 1, - this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as RewardTier[], + this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as RarityTier[], ); globalScene.ui.clearText(); globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end()); @@ -212,12 +199,12 @@ export class SelectRewardPhase extends BattlePhase { return true; } - // Transfer modifiers among party pokemon - private openModifierTransferScreen(modifierSelectCallback: ModifierSelectCallback) { + // Transfer rewards among party pokemon + private openItemTransferScreen(rewardSelectCallback: RewardSelectCallback) { const party = globalScene.getPlayerParty(); globalScene.ui.setModeWithoutClear( UiMode.PARTY, - PartyUiMode.MODIFIER_TRANSFER, + PartyUiMode.ITEM_TRANSFER, -1, (fromSlotIndex: number, itemIndex: number, itemQuantity: number, toSlotIndex: number) => { if ( @@ -239,7 +226,7 @@ export class SelectRewardPhase extends BattlePhase { false, ); } else { - this.resetModifierSelect(modifierSelectCallback); + this.resetRewardSelect(rewardSelectCallback); } }, PartyUiHandler.FilterItemMaxStacks, @@ -249,36 +236,115 @@ export class SelectRewardPhase extends BattlePhase { // Toggle reroll lock private toggleRerollLock() { - const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); + const rerollCost = this.getRerollCost(globalScene.lockRarityTiers); if (rerollCost < 0) { // Reroll lock button is also disabled when reroll is disabled globalScene.ui.playError(); return false; } - globalScene.lockModifierTiers = !globalScene.lockModifierTiers; + globalScene.lockRarityTiers = !globalScene.lockRarityTiers; const uiHandler = globalScene.ui.getHandler() as RewardSelectUiHandler; - uiHandler.setRerollCost(this.getRerollCost(globalScene.lockModifierTiers)); + uiHandler.setRerollCost(this.getRerollCost(globalScene.lockRarityTiers)); uiHandler.updateLockRaritiesText(); uiHandler.updateRerollCostText(); return false; } + // Opens the party menu specifically for fusions + private openFusionMenu(reward: FusePokemonReward, _cost: number, rewardSelectCallback: RewardSelectCallback): void { + const party = globalScene.getPlayerParty(); + globalScene.ui.setModeWithoutClear( + UiMode.PARTY, + PartyUiMode.SPLICE, + -1, + (fromSlotIndex: number, spliceSlotIndex: number) => { + if ( + spliceSlotIndex !== undefined && + fromSlotIndex < 6 && + spliceSlotIndex < 6 && + fromSlotIndex !== spliceSlotIndex + ) { + globalScene.ui.setMode(UiMode.REWARD_SELECT, this.isPlayer()).then(() => { + reward.apply({ pokemon: party[fromSlotIndex], pokemon2: party[spliceSlotIndex] }); + }); + } else { + this.resetRewardSelect(rewardSelectCallback); + } + }, + reward.selectFilter, + ); + } + + // Opens the party menu to apply one of various Pokemon rewards. We pass the reward's filter to decide which Pokemon can be selected. + // For MoveReward (e.g. PP UP or Ether) we also pass a filter to decide which moves can be selected. + private openPokemonRewardMenu(reward: PokemonReward, cost: number, rewardSelectCallback: RewardSelectCallback): void { + const party = globalScene.getPlayerParty(); + + let partyUiMode = PartyUiMode.REWARD; + let moveSelectFilter: PokemonMoveSelectFilter | undefined; + let tmMoveId: MoveId | undefined; + let isMove = false; + let getParams = (slotIndex: number, _option: PartyOption) => { + return { pokemon: party[slotIndex] } as PokemonRewardParams; + }; + + if (isMoveReward(reward)) { + partyUiMode = PartyUiMode.MOVE_REWARD; + moveSelectFilter = (reward as PokemonMoveReward).moveSelectFilter; + isMove = true; + getParams = (slotIndex: number, option: PartyOption) => { + return { pokemon: party[slotIndex], moveIndex: option - PartyOption.MOVE_1 } as PokemonMoveRewardParams; + }; + } + if (isRememberMoveReward(reward)) { + partyUiMode = PartyUiMode.REMEMBER_MOVE_REWARD; + getParams = (slotIndex: number, option: PartyOption) => { + return { pokemon: party[slotIndex], moveIndex: option, cost: cost } as PokemonMoveRecallRewardParams; + }; + } + if (isTmReward(reward)) { + partyUiMode = PartyUiMode.TM_REWARD; + tmMoveId = reward.moveId; + } + + globalScene.ui.setModeWithoutClear( + UiMode.PARTY, + partyUiMode, + -1, + (slotIndex: number, option: PartyOption) => { + if (slotIndex < 6) { + globalScene.ui.setMode(UiMode.REWARD_SELECT, this.isPlayer()).then(() => { + const params = getParams(slotIndex, option); + const result = globalScene.applyReward(reward, params, true); + this.postApplyPokemonReward(reward, result, cost); + }); + } else { + this.resetRewardSelect(rewardSelectCallback); + } + }, + reward.selectFilter, + moveSelectFilter, + tmMoveId, + isMove, + ); + } + + // TODO: Rework this to work properly with rewards /** - * Apply the effects of the chosen modifier - * @param modifier - The modifier to apply - * @param cost - The cost of the modifier if it was purchased, or -1 if selected as the modifier reward - * @param playSound - Whether the 'obtain modifier' sound should be played when adding the modifier. + * Apply the effects of the chosen reward + * @param reward - The reward to apply + * @param cost - The cost of the reward if it was purchased, or -1 if selected as the reward reward + * @param playSound - Whether the 'obtain reward' sound should be played when adding the reward. */ - private applyModifier(modifier: Modifier, cost = -1, playSound = false): void { - const result = globalScene.addModifier(modifier, playSound, undefined, cost); + private postApplyPokemonReward(reward: Reward, result = false, cost = -1): void { // Queue a copy of this phase when applying a TM or Memory Mushroom. // If the player selects either of these, then escapes out of consuming them, // they are returned to a shop in the same state. - if (modifier.type instanceof RememberMoveModifierType || modifier.type instanceof TmModifierType) { + if (reward instanceof RememberMoveReward || reward instanceof TmReward) { globalScene.phaseManager.unshiftPhase(this.copy()); } - if (cost !== -1 && !(modifier.type instanceof RememberMoveModifierType)) { + if (cost !== -1 && !(reward instanceof RememberMoveReward)) { if (result) { if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { globalScene.money -= cost; @@ -297,138 +363,37 @@ export class SelectRewardPhase extends BattlePhase { } } - // Opens the party menu specifically for fusions - private openFusionMenu( - modifierType: PokemonModifierType, - cost: number, - modifierSelectCallback: ModifierSelectCallback, - ): void { - const party = globalScene.getPlayerParty(); - globalScene.ui.setModeWithoutClear( - UiMode.PARTY, - PartyUiMode.SPLICE, - -1, - (fromSlotIndex: number, spliceSlotIndex: number) => { - if ( - spliceSlotIndex !== undefined && - fromSlotIndex < 6 && - spliceSlotIndex < 6 && - fromSlotIndex !== spliceSlotIndex - ) { - globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { - const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct? - this.applyModifier(modifier, cost, true); - }); - } else { - this.resetModifierSelect(modifierSelectCallback); - } - }, - modifierType.selectFilter, - ); - } - - // Opens the party menu to apply one of various modifiers - private openModifierMenu( - modifierType: PokemonModifierType, - cost: number, - modifierSelectCallback: ModifierSelectCallback, - ): void { - const party = globalScene.getPlayerParty(); - const pokemonModifierType = modifierType as PokemonModifierType; - const isMoveModifier = modifierType instanceof PokemonMoveModifierType; - const isTmModifier = modifierType instanceof TmModifierType; - const isRememberMoveModifier = modifierType instanceof RememberMoveModifierType; - const isPpRestoreModifier = - modifierType instanceof PokemonPpRestoreModifierType || modifierType instanceof PokemonPpUpModifierType; - const partyUiMode = isMoveModifier - ? PartyUiMode.MOVE_MODIFIER - : isTmModifier - ? PartyUiMode.TM_MODIFIER - : isRememberMoveModifier - ? PartyUiMode.REMEMBER_MOVE_MODIFIER - : PartyUiMode.MODIFIER; - const tmMoveId = isTmModifier ? (modifierType as TmModifierType).moveId : undefined; - globalScene.ui.setModeWithoutClear( - UiMode.PARTY, - partyUiMode, - -1, - (slotIndex: number, option: PartyOption) => { - if (slotIndex < 6) { - globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { - const modifier = !isMoveModifier - ? !isRememberMoveModifier - ? modifierType.newModifier(party[slotIndex]) - : modifierType.newModifier(party[slotIndex], option as number) - : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); - this.applyModifier(modifier!, cost, true); // TODO: is the bang correct? - }); - } else { - this.resetModifierSelect(modifierSelectCallback); - } - }, - pokemonModifierType.selectFilter, - modifierType instanceof PokemonMoveModifierType - ? (modifierType as PokemonMoveModifierType).moveSelectFilter - : undefined, - tmMoveId, - isPpRestoreModifier, - ); - } - - private openGiveHeldItemMenu(reward, modifierSelectCallback) { - const party = globalScene.getPlayerParty(); - const partyUiMode = PartyUiMode.MODIFIER; - globalScene.ui.setModeWithoutClear( - UiMode.PARTY, - partyUiMode, - -1, - (slotIndex: number, _option: PartyOption) => { - if (slotIndex < 6) { - globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => { - reward.apply(party[slotIndex]); - globalScene.ui.clearText(); - globalScene.ui.setMode(UiMode.MESSAGE); - super.end(); - }); - } else { - this.resetModifierSelect(modifierSelectCallback); - } - }, - reward.selectFilter, - ); - } - // Function that determines how many reward slots are available - private getModifierCount(): number { - const modifierCountHolder = new NumberHolder(3); - globalScene.applyPlayerItems(TrainerItemEffect.EXTRA_REWARD, { numberHolder: modifierCountHolder }); + private getRewardCount(): number { + const rewardCountHolder = new NumberHolder(3); + globalScene.applyPlayerItems(TrainerItemEffect.EXTRA_REWARD, { numberHolder: rewardCountHolder }); - // If custom modifiers are specified, overrides default item count - if (this.customModifierSettings) { + // If custom rewards are specified, overrides default item count + if (this.customRewardSettings) { const newItemCount = - (this.customModifierSettings.guaranteedModifierTiers?.length ?? 0) + - (this.customModifierSettings.guaranteedModifierTypeOptions?.length ?? 0) + - (this.customModifierSettings.guaranteedModifierTypeFuncs?.length ?? 0); - if (this.customModifierSettings.fillRemaining) { - const originalCount = modifierCountHolder.value; - modifierCountHolder.value = originalCount > newItemCount ? originalCount : newItemCount; + (this.customRewardSettings.guaranteedRarityTiers?.length ?? 0) + + (this.customRewardSettings.guaranteedRewardOptions?.length ?? 0) + + (this.customRewardSettings.guaranteedRewardFuncs?.length ?? 0); + if (this.customRewardSettings.fillRemaining) { + const originalCount = rewardCountHolder.value; + rewardCountHolder.value = originalCount > newItemCount ? originalCount : newItemCount; } else { - modifierCountHolder.value = newItemCount; + rewardCountHolder.value = newItemCount; } } - return modifierCountHolder.value; + return rewardCountHolder.value; } // Function that resets the reward selection screen, // e.g. after pressing cancel in the party ui or while learning a move - private resetModifierSelect(modifierSelectCallback: ModifierSelectCallback) { + private resetRewardSelect(rewardSelectCallback: RewardSelectCallback) { globalScene.ui.setMode( - UiMode.MODIFIER_SELECT, + UiMode.REWARD_SELECT, this.isPlayer(), this.typeOptions, - modifierSelectCallback, - this.getRerollCost(globalScene.lockModifierTiers), + rewardSelectCallback, + this.getRerollCost(globalScene.lockRarityTiers), ); } @@ -455,14 +420,14 @@ export class SelectRewardPhase extends BattlePhase { } let multiplier = 1; - if (!isNullOrUndefined(this.customModifierSettings?.rerollMultiplier)) { - if (this.customModifierSettings.rerollMultiplier < 0) { + if (!isNullOrUndefined(this.customRewardSettings?.rerollMultiplier)) { + if (this.customRewardSettings.rerollMultiplier < 0) { // Completely overrides reroll cost to -1 and early exits return -1; } // Otherwise, continue with custom multiplier - multiplier = this.customModifierSettings.rerollMultiplier; + multiplier = this.customRewardSettings.rerollMultiplier; } const baseMultiplier = Math.min( @@ -476,16 +441,16 @@ export class SelectRewardPhase extends BattlePhase { return modifiedRerollCost.value; } - getPoolType(): ModifierPoolType { - return ModifierPoolType.PLAYER; + getPoolType(): RewardPoolType { + return RewardPoolType.PLAYER; } - getModifierTypeOptions(modifierCount: number): ModifierTypeOption[] { - return getPlayerModifierTypeOptions( - modifierCount, + getRewardOptions(rewardCount: number): RewardOption[] { + return generatePlayerRewardOptions( + rewardCount, globalScene.getPlayerParty(), - globalScene.lockModifierTiers ? this.modifierTiers : undefined, - this.customModifierSettings, + globalScene.lockRarityTiers ? this.rarityTiers : undefined, + this.customRewardSettings, ); } @@ -493,17 +458,13 @@ export class SelectRewardPhase extends BattlePhase { return globalScene.phaseManager.create( "SelectRewardPhase", this.rerollCount, - this.modifierTiers, + this.rarityTiers, { - guaranteedModifierTypeOptions: this.typeOptions, - rerollMultiplier: this.customModifierSettings?.rerollMultiplier, + guaranteedRewardOptions: this.typeOptions, + rerollMultiplier: this.customRewardSettings?.rerollMultiplier, allowLuckUpgrades: false, }, true, ); } - - addModifier(modifier: Modifier): boolean { - return globalScene.addModifier(modifier, false, true); - } } diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index 731504d9eb9..ff97a9e33c0 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -7,7 +7,7 @@ import { Gender } from "#data/gender"; import { ChallengeType } from "#enums/challenge-type"; import type { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; -import { overrideHeldItems, overrideTrainerItems } from "#modifiers/modifier"; +import { overrideHeldItems, overrideTrainerItems } from "#items/item-overrides"; import { SaveSlotUiMode } from "#ui/save-slot-select-ui-handler"; import type { Starter } from "#ui/starter-select-ui-handler"; import { isNullOrUndefined } from "#utils/common"; diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index c47314e268e..8156149628a 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -8,10 +8,10 @@ import { OctolockTag } from "#data/battler-tags"; import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagType } from "#enums/arena-tag-type"; import type { BattlerIndex } from "#enums/battler-index"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { type BattleStat, getStatKey, getStatStageChangeDescriptionKey, Stat } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import { PokemonPhase } from "#phases/pokemon-phase"; import type { ConditionalUserFieldProtectStatAbAttrParams, PreStatStageChangeAbAttrParams } from "#types/ability-types"; import { BooleanHolder, isNullOrUndefined, NumberHolder } from "#utils/common"; diff --git a/src/phases/trainer-item-reward-phase.ts b/src/phases/trainer-item-reward-phase.ts index f950985cc99..033a022958f 100644 --- a/src/phases/trainer-item-reward-phase.ts +++ b/src/phases/trainer-item-reward-phase.ts @@ -1,20 +1,19 @@ import { globalScene } from "#app/global-scene"; -import type { ModifierType } from "#modifiers/modifier-type"; +import type { Reward } from "#items/reward"; import { BattlePhase } from "#phases/battle-phase"; -import type { ModifierTypeFunc } from "#types/modifier-types"; -import { getModifierType } from "#utils/modifier-utils"; +import type { RewardFunc } from "#types/rewards"; import i18next from "i18next"; export class RewardPhase extends BattlePhase { // RibbonRewardPhase extends RewardPhase and to make typescript happy // we need to use a union type here public readonly phaseName: "RewardPhase" | "RibbonRewardPhase" | "GameOverRewardPhase" = "RewardPhase"; - protected modifierType: ModifierType; + protected reward: Reward; - constructor(modifierTypeFunc: ModifierTypeFunc) { + constructor(rewardFunc: RewardFunc) { super(); - this.modifierType = getModifierType(modifierTypeFunc); + this.reward = rewardFunc(); } start() { @@ -25,12 +24,11 @@ export class RewardPhase extends BattlePhase { doReward(): Promise { return new Promise(resolve => { - const newModifier = this.modifierType.newModifier(); - globalScene.addModifier(newModifier); + globalScene.applyReward(this.reward); globalScene.playSound("item_fanfare"); globalScene.ui.showText( i18next.t("battle:rewardGain", { - modifierName: newModifier?.type.name, + modifierName: this.reward.name, }), null, () => resolve(), diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index 3aff1b4fb4e..df9026fc536 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -1,6 +1,6 @@ import { timedEventManager } from "#app/global-event-manager"; import { globalScene } from "#app/global-scene"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { getCharVariantFromDialogue } from "#data/dialogue"; import { BiomeId } from "#enums/biome-id"; import { TrainerSlot } from "#enums/trainer-slot"; @@ -20,9 +20,9 @@ export class TrainerVictoryPhase extends BattlePhase { globalScene.phaseManager.unshiftNew("MoneyRewardPhase", globalScene.currentBattle.trainer?.config.moneyMultiplier!); // TODO: is this bang correct? - const modifierRewardFuncs = globalScene.currentBattle.trainer?.config.modifierRewardFuncs!; // TODO: is this bang correct? - for (const modifierRewardFunc of modifierRewardFuncs) { - globalScene.phaseManager.unshiftNew("RewardPhase", modifierRewardFunc); + const rewardFuncs = globalScene.currentBattle.trainer?.config.rewardFuncs!; // TODO: is this bang correct? + for (const rewardFunc of rewardFuncs) { + globalScene.phaseManager.unshiftNew("RewardPhase", rewardFunc); } const trainerType = globalScene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? @@ -35,17 +35,14 @@ export class TrainerVictoryPhase extends BattlePhase { if (timedEventManager.getUpgradeUnlockedVouchers()) { globalScene.phaseManager.unshiftNew( "RewardPhase", - [ - modifierTypes.VOUCHER_PLUS, - modifierTypes.VOUCHER_PLUS, - modifierTypes.VOUCHER_PLUS, - modifierTypes.VOUCHER_PREMIUM, - ][vouchers[TrainerType[trainerType]].voucherType], + [allRewards.VOUCHER_PLUS, allRewards.VOUCHER_PLUS, allRewards.VOUCHER_PLUS, allRewards.VOUCHER_PREMIUM][ + vouchers[TrainerType[trainerType]].voucherType + ], ); } else { globalScene.phaseManager.unshiftNew( "RewardPhase", - [modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][ + [allRewards.VOUCHER, allRewards.VOUCHER, allRewards.VOUCHER_PLUS, allRewards.VOUCHER_PREMIUM][ vouchers[TrainerType[trainerType]].voucherType ], ); diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index af34011780b..a9394791a1e 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -3,11 +3,11 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { TerrainType } from "#data/terrain"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { WeatherType } from "#enums/weather-type"; import { TurnEndEvent } from "#events/battle-scene"; import type { Pokemon } from "#field/pokemon"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import { TrainerItemEffect } from "#items/trainer-item"; import { FieldPhase } from "#phases/field-phase"; import i18next from "i18next"; diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 044987808a5..bf674baa46d 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -4,11 +4,11 @@ import { TrickRoomTag } from "#data/arena-tag"; import { allMoves } from "#data/data-lists"; import { BattlerIndex } from "#enums/battler-index"; import { Command } from "#enums/command"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { Stat } from "#enums/stat"; import { SwitchType } from "#enums/switch-type"; import type { Pokemon } from "#field/pokemon"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import { PokemonMove } from "#moves/pokemon-move"; import { FieldPhase } from "#phases/field-phase"; import { BooleanHolder, randSeedShuffle } from "#utils/common"; diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index e951adfd6dd..8fdf8d52bc3 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -1,10 +1,10 @@ import { timedEventManager } from "#app/global-event-manager"; import { globalScene } from "#app/global-scene"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { BattleType } from "#enums/battle-type"; import type { BattlerIndex } from "#enums/battler-index"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; -import type { CustomModifierSettings } from "#modifiers/modifier-type"; +import type { CustomRewardSettings } from "#items/reward-pool-utils"; import { handleMysteryEncounterVictory } from "#mystery-encounters/encounter-phase-utils"; import { PokemonPhase } from "#phases/pokemon-phase"; @@ -55,11 +55,11 @@ export class VictoryPhase extends PokemonPhase { // Get event modifiers for this wave timedEventManager .getFixedBattleEventRewards(globalScene.currentBattle.waveIndex) - .map(r => globalScene.phaseManager.pushNew("RewardPhase", modifierTypes[r])); + .map(r => globalScene.phaseManager.pushNew("RewardPhase", allRewards[r])); break; case ClassicFixedBossWaves.EVIL_BOSS_2: - // Should get Lock Capsule on 165 before shop phase so it can be used in the rewards shop - globalScene.phaseManager.pushNew("RewardPhase", modifierTypes.LOCK_CAPSULE); + // Should get Lock Capsule on 165 before shop phase so it can be used in the allRewards shop + globalScene.phaseManager.pushNew("RewardPhase", allRewards.LOCK_CAPSULE); break; } } @@ -68,20 +68,20 @@ export class VictoryPhase extends PokemonPhase { "SelectRewardPhase", undefined, undefined, - this.getFixedBattleCustomModifiers(), + this.getFixedBattleCustomRewards(), ); } else if (globalScene.gameMode.isDaily) { - globalScene.phaseManager.pushNew("RewardPhase", modifierTypes.EXP_CHARM); + globalScene.phaseManager.pushNew("RewardPhase", allRewards.EXP_CHARM); if ( globalScene.currentBattle.waveIndex > 10 && !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex) ) { - globalScene.phaseManager.pushNew("RewardPhase", modifierTypes.GOLDEN_POKEBALL); + globalScene.phaseManager.pushNew("RewardPhase", allRewards.GOLDEN_POKEBALL); } } else { const superExpWave = !globalScene.gameMode.isEndless ? (globalScene.offsetGym ? 0 : 20) : 10; if (globalScene.gameMode.isEndless && globalScene.currentBattle.waveIndex === 10) { - globalScene.phaseManager.pushNew("RewardPhase", modifierTypes.EXP_SHARE); + globalScene.phaseManager.pushNew("RewardPhase", allRewards.EXP_SHARE); } if ( globalScene.currentBattle.waveIndex <= 750 && @@ -90,19 +90,19 @@ export class VictoryPhase extends PokemonPhase { globalScene.phaseManager.pushNew( "RewardPhase", globalScene.currentBattle.waveIndex % 30 !== superExpWave || globalScene.currentBattle.waveIndex > 250 - ? modifierTypes.EXP_CHARM - : modifierTypes.SUPER_EXP_CHARM, + ? allRewards.EXP_CHARM + : allRewards.SUPER_EXP_CHARM, ); } if (globalScene.currentBattle.waveIndex <= 150 && !(globalScene.currentBattle.waveIndex % 50)) { - globalScene.phaseManager.pushNew("RewardPhase", modifierTypes.GOLDEN_POKEBALL); + globalScene.phaseManager.pushNew("RewardPhase", allRewards.GOLDEN_POKEBALL); } if (globalScene.gameMode.isEndless && !(globalScene.currentBattle.waveIndex % 50)) { globalScene.phaseManager.pushNew( "RewardPhase", - !(globalScene.currentBattle.waveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS, + !(globalScene.currentBattle.waveIndex % 250) ? allRewards.VOUCHER_PREMIUM : allRewards.VOUCHER_PLUS, ); - globalScene.phaseManager.pushNew("AddEnemyBuffModifierPhase"); + globalScene.phaseManager.pushNew("AddEnemyTokenPhase"); } } @@ -123,14 +123,14 @@ export class VictoryPhase extends PokemonPhase { } /** - * If this wave is a fixed battle with special custom modifier rewards, + * If this wave is a fixed battle with special custom modifier allRewards, * will pass those settings to the upcoming {@linkcode SelectRewardPhase}`. */ - getFixedBattleCustomModifiers(): CustomModifierSettings | undefined { + getFixedBattleCustomRewards(): CustomRewardSettings | undefined { const gameMode = globalScene.gameMode; const waveIndex = globalScene.currentBattle.waveIndex; if (gameMode.isFixedBattle(waveIndex)) { - return gameMode.getFixedBattle(waveIndex).customModifierRewardSettings; + return gameMode.getFixedBattle(waveIndex).customRewardSettings; } return undefined; diff --git a/src/system/achv.ts b/src/system/achv.ts index bb981d39062..9158d827dc6 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -16,7 +16,6 @@ import type { Pokemon } from "#field/pokemon"; import type { ConditionFn } from "#types/common"; import { NumberHolder } from "#utils/common"; import i18next from "i18next"; -import type { Modifier } from "typescript"; export enum AchvTier { COMMON, @@ -177,19 +176,6 @@ export class LevelAchv extends Achv { } } -export class ModifierAchv extends Achv { - constructor( - localizationKey: string, - name: string, - description: string, - iconImage: string, - score: number, - modifierFunc: (modifier: Modifier) => boolean, - ) { - super(localizationKey, name, description, iconImage, score, (args: any[]) => modifierFunc(args[0] as Modifier)); - } -} - export class HeldItemAchv extends Achv { constructor( localizationKey: string, diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 19d10baedfd..106e7855fc5 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -66,7 +66,7 @@ const TOUCH_CONTROLS_OPTIONS: SettingOption[] = [ const SHOP_CURSOR_TARGET_OPTIONS: SettingOption[] = [ { value: "Rewards", - label: i18next.t("settings:rewards"), + label: i18next.t("settings:allRewards"), }, { value: "Shop", diff --git a/src/system/version-migration/versions/v1_0_4.ts b/src/system/version-migration/versions/v1_0_4.ts index 2c50e05d40f..643737d7645 100644 --- a/src/system/version-migration/versions/v1_0_4.ts +++ b/src/system/version-migration/versions/v1_0_4.ts @@ -137,7 +137,7 @@ const migrateModifiers: SessionSaveMigrator = { m.className = "ResetNegativeStatStageModifier"; } else if (m.className === "TempBattleStatBoosterModifier") { const maxBattles = 5; - // Dire Hit no longer a part of the TempBattleStatBoosterModifierTypeGenerator + // Dire Hit no longer a part of the TempBattleStatBoosterRewardGenerator if (m.typeId !== "DIRE_HIT") { m.className = "TempStatStageBoosterModifier"; m.typeId = "TEMP_STAT_STAGE_BOOSTER"; diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index 6f9705e4563..4c476292fa6 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -65,7 +65,7 @@ interface TimedEvent extends EventBanner { mysteryEncounterTierChanges?: EventMysteryEncounterTier[]; luckBoostedSpecies?: SpeciesId[]; boostFusions?: boolean; //MODIFIER REWORK PLEASE - classicWaveRewards?: EventWaveReward[]; // Rival battle rewards + classicWaveRewards?: EventWaveReward[]; // Rival battle allRewards trainerShinyChance?: number; // Odds over 65536 of trainer mon generating as shiny music?: EventMusicReplacement[]; dailyRunChallenges?: EventChallenge[]; @@ -466,7 +466,7 @@ export class TimedEventManager { /** * For events where Delibirdy gives extra items - * @returns list of ids of {@linkcode ModifierType}s that Delibirdy hands out as a bonus + * @returns list of ids of {@linkcode Reward}s that Delibirdy hands out as a bonus */ getDelibirdyBuff(): TrainerItemId[] { const ret: TrainerItemId[] = []; @@ -567,7 +567,7 @@ export class TimedEventManager { /** * Gets all the modifier types associated with a certain wave during an event * @see EventWaveReward - * @param wave the wave to check for associated rewards + * @param wave the wave to check for associated allRewards * @returns array of strings of the event modifier reward types */ getFixedBattleEventRewards(wave: number): string[] { diff --git a/src/tutorial.ts b/src/tutorial.ts index 018d0927da0..93e11335c65 100644 --- a/src/tutorial.ts +++ b/src/tutorial.ts @@ -98,7 +98,7 @@ const tutorialHandlers = { null, () => globalScene.ui.showText("", null, () => - globalScene.ui.setModeWithoutClear(UiMode.MODIFIER_SELECT).then(() => resolve()), + globalScene.ui.setModeWithoutClear(UiMode.REWARD_SELECT).then(() => resolve()), ), null, true, diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index 71c5ac1049e..cf340cf3ea7 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -185,7 +185,7 @@ export class UiInputs { } case UiMode.TITLE: case UiMode.COMMAND: - case UiMode.MODIFIER_SELECT: + case UiMode.REWARD_SELECT: case UiMode.MYSTERY_ENCOUNTER: globalScene.ui.setOverlayMode(UiMode.MENU); break; diff --git a/src/ui/egg-hatch-scene-handler.ts b/src/ui/egg-hatch-scene-handler.ts index 5b2c9d40cfa..dda259e899a 100644 --- a/src/ui/egg-hatch-scene-handler.ts +++ b/src/ui/egg-hatch-scene-handler.ts @@ -37,7 +37,7 @@ export class EggHatchSceneHandler extends UiHandler { this.getUi().showText("", 0); - globalScene.setModifiersVisible(false); + globalScene.setItemsVisible(false); return true; } diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 6e1ca89c9c7..8e975ffe7e6 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -61,27 +61,27 @@ export enum PartyUiMode { * Indicates that the party UI is open to select a mon to apply a modifier to. * This type of selection can be cancelled. */ - MODIFIER, + REWARD, /** * Indicates that the party UI is open to select a mon to apply a move * modifier to (such as an Ether or PP Up). This type of selection can be cancelled. */ - MOVE_MODIFIER, + MOVE_REWARD, /** * Indicates that the party UI is open to select a mon to teach a TM. This * type of selection can be cancelled. */ - TM_MODIFIER, + TM_REWARD, /** * Indicates that the party UI is open to select a mon to remember a move. * This type of selection can be cancelled. */ - REMEMBER_MOVE_MODIFIER, + REMEMBER_MOVE_REWARD, /** * Indicates that the party UI is open to transfer items between mons. This * type of selection can be cancelled. */ - MODIFIER_TRANSFER, + ITEM_TRANSFER, /** * Indicates that the party UI is open because of a DNA Splicer. This * type of selection can be cancelled. @@ -131,15 +131,15 @@ export enum PartyOption { } export type PartySelectCallback = (cursor: number, option: PartyOption) => void; -export type PartyModifierTransferSelectCallback = ( +export type PartyItemTransferSelectCallback = ( fromCursor: number, index: number, itemQuantity?: number, toCursor?: number, ) => void; -export type PartyModifierSpliceSelectCallback = (fromCursor: number, toCursor?: number) => void; +export type PartyRewardSpliceSelectCallback = (fromCursor: number, toCursor?: number) => void; export type PokemonSelectFilter = (pokemon: PlayerPokemon) => string | null; -export type PokemonModifierTransferSelectFilter = (pokemon: PlayerPokemon, item: HeldItemId) => string | null; +export type PokemonItemTransferSelectFilter = (pokemon: PlayerPokemon, item: HeldItemId) => string | null; export type PokemonMoveSelectFilter = (pokemonMove: PokemonMove) => string | null; export class PartyUiHandler extends MessageUiHandler { @@ -176,8 +176,8 @@ export class PartyUiHandler extends MessageUiHandler { private transferAll: boolean; private lastCursor = 0; - private selectCallback: PartySelectCallback | PartyModifierTransferSelectCallback | null; - private selectFilter: PokemonSelectFilter | PokemonModifierTransferSelectFilter; + private selectCallback: PartySelectCallback | PartyItemTransferSelectCallback | null; + private selectFilter: PokemonSelectFilter | PokemonItemTransferSelectFilter; private moveSelectFilter: PokemonMoveSelectFilter; private tmMoveId: MoveId; private showMovePp: boolean; @@ -503,7 +503,7 @@ export class PartyUiHandler extends MessageUiHandler { [this.transferCursor].heldItemManager.getTransferableHeldItems() .forEach((_, i, array) => { const invertedIndex = array.length - 1 - i; - (this.selectCallback as PartyModifierTransferSelectCallback)( + (this.selectCallback as PartyItemTransferSelectCallback)( this.transferCursor, invertedIndex, this.transferQuantitiesMax[invertedIndex], @@ -511,7 +511,7 @@ export class PartyUiHandler extends MessageUiHandler { ); }); } else { - (this.selectCallback as PartyModifierTransferSelectCallback)( + (this.selectCallback as PartyItemTransferSelectCallback)( this.transferCursor, this.transferOptionCursor, this.transferQuantities[this.transferOptionCursor], @@ -526,7 +526,7 @@ export class PartyUiHandler extends MessageUiHandler { } // TODO: This will be largely changed with the modifier rework - private processModifierTransferModeInput(pokemon: PlayerPokemon) { + private processItemTransferModeInput(pokemon: PlayerPokemon) { const ui = this.getUi(); const option = this.options[this.optionsCursor]; @@ -581,12 +581,12 @@ export class PartyUiHandler extends MessageUiHandler { } // TODO: Might need to check here for when this.transferMode is active. - private processModifierTransferModeLeftRightInput(button: Button) { + private processItemTransferModeLeftRightInput(button: Button) { let success = false; const option = this.options[this.optionsCursor]; if (button === Button.LEFT) { /** Decrease quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + if (this.partyUiMode === PartyUiMode.ITEM_TRANSFER) { this.transferQuantities[option] = this.transferQuantities[option] === 1 ? this.transferQuantitiesMax[option] @@ -600,7 +600,7 @@ export class PartyUiHandler extends MessageUiHandler { if (button === Button.RIGHT) { /** Increase quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + if (this.partyUiMode === PartyUiMode.ITEM_TRANSFER) { this.transferQuantities[option] = this.transferQuantities[option] === this.transferQuantitiesMax[option] ? 1 @@ -615,11 +615,11 @@ export class PartyUiHandler extends MessageUiHandler { } // TODO: Might need to check here for when this.transferMode is active. - private processModifierTransferModeUpDownInput(button: Button.UP | Button.DOWN) { + private processItemTransferModeUpDownInput(button: Button.UP | Button.DOWN) { let success = false; const option = this.options[this.optionsCursor]; - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + if (this.partyUiMode === PartyUiMode.ITEM_TRANSFER) { if (option !== PartyOption.ALL) { this.transferQuantities[option] = this.transferQuantitiesMax[option]; } @@ -681,11 +681,11 @@ export class PartyUiHandler extends MessageUiHandler { if (filterResult === null && (option === PartyOption.SEND_OUT || option === PartyOption.PASS_BATON)) { filterResult = this.FilterChallengeLegal(pokemon); } - if (filterResult === null && this.partyUiMode === PartyUiMode.MOVE_MODIFIER) { + if (filterResult === null && this.partyUiMode === PartyUiMode.MOVE_REWARD) { filterResult = this.moveSelectFilter(pokemon.moveset[this.optionsCursor]); } } else { - filterResult = (this.selectFilter as PokemonModifierTransferSelectFilter)( + filterResult = (this.selectFilter as PokemonItemTransferSelectFilter)( pokemon, globalScene.getPlayerParty()[this.transferCursor].heldItemManager.getTransferableHeldItems()[ this.transferOptionCursor @@ -706,12 +706,12 @@ export class PartyUiHandler extends MessageUiHandler { // TODO: Careful about using success for the return values here. Find a better way // PartyOption.ALL, and options specific to the mode (held items) - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - return this.processModifierTransferModeInput(pokemon); + if (this.partyUiMode === PartyUiMode.ITEM_TRANSFER) { + return this.processItemTransferModeInput(pokemon); } // options specific to the mode (moves) - if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { + if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_REWARD) { return this.processRememberMoveModeInput(pokemon); } @@ -757,13 +757,13 @@ export class PartyUiHandler extends MessageUiHandler { // PartyUiMode.RELEASE (RELEASE) // PartyUiMode.FAINT_SWITCH (SEND_OUT or PASS_BATON (?)) // PartyUiMode.REVIVAL_BLESSING (REVIVE) - // PartyUiMode.MODIFIER_TRANSFER (held items, and ALL) + // PartyUiMode.ITEM_TRANSFER (held items, and ALL) // PartyUiMode.CHECK --- no specific option, only relevant on cancel? // PartyUiMode.SPLICE (SPLICE) - // PartyUiMode.MOVE_MODIFIER (MOVE_1, MOVE_2, MOVE_3, MOVE_4) - // PartyUiMode.TM_MODIFIER (TEACH) - // PartyUiMode.REMEMBER_MOVE_MODIFIER (no specific option, callback is invoked when selecting a move) - // PartyUiMode.MODIFIER (APPLY option) + // PartyUiMode.MOVE_REWARD (MOVE_1, MOVE_2, MOVE_3, MOVE_4) + // PartyUiMode.TM_REWARD (TEACH) + // PartyUiMode.REMEMBER_MOVE_REWARD (no specific option, callback is invoked when selecting a move) + // PartyUiMode.REWARD (APPLY option) // PartyUiMode.POST_BATTLE_SWITCH (SEND_OUT) // These are the options that need a callback @@ -773,7 +773,7 @@ export class PartyUiHandler extends MessageUiHandler { if (this.partyUiMode === PartyUiMode.SPLICE) { if (option === PartyOption.SPLICE) { - (this.selectCallback as PartyModifierSpliceSelectCallback)(this.transferCursor, this.cursor); + (this.selectCallback as PartyRewardSpliceSelectCallback)(this.transferCursor, this.cursor); this.clearTransfer(); } else if (option === PartyOption.APPLY) { this.startTransfer(); @@ -837,11 +837,11 @@ export class PartyUiHandler extends MessageUiHandler { } if (button === Button.UP || button === Button.DOWN) { - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - return this.processModifierTransferModeUpDownInput(button); + if (this.partyUiMode === PartyUiMode.ITEM_TRANSFER) { + return this.processItemTransferModeUpDownInput(button); } - if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { + if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_REWARD) { return this.processRememberMoveModeUpDownInput(button); } @@ -849,8 +849,8 @@ export class PartyUiHandler extends MessageUiHandler { } if (button === Button.LEFT || button === Button.RIGHT) { - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - return this.processModifierTransferModeLeftRightInput(button); + if (this.partyUiMode === PartyUiMode.ITEM_TRANSFER) { + return this.processItemTransferModeLeftRightInput(button); } } @@ -907,7 +907,7 @@ export class PartyUiHandler extends MessageUiHandler { private processPartyActionInput(): boolean { const ui = this.getUi(); if (this.cursor < 6) { - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode) { + if (this.partyUiMode === PartyUiMode.ITEM_TRANSFER && !this.transferMode) { /** Initialize item quantities for the selected Pokemon */ const pokemon = globalScene.getPlayerParty()[this.cursor]; const items = pokemon.heldItemManager.getTransferableHeldItems(); @@ -931,7 +931,7 @@ export class PartyUiHandler extends MessageUiHandler { private processPartyCancelInput(): boolean { const ui = this.getUi(); if ( - (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER || this.partyUiMode === PartyUiMode.SPLICE) && + (this.partyUiMode === PartyUiMode.ITEM_TRANSFER || this.partyUiMode === PartyUiMode.SPLICE) && this.transferMode ) { this.clearTransfer(); @@ -1111,10 +1111,10 @@ export class PartyUiHandler extends MessageUiHandler { let optionsMessage = i18next.t("partyUiHandler:doWhatWithThisPokemon"); switch (this.partyUiMode) { - case PartyUiMode.MOVE_MODIFIER: + case PartyUiMode.MOVE_REWARD: optionsMessage = i18next.t("partyUiHandler:selectAMove"); break; - case PartyUiMode.MODIFIER_TRANSFER: + case PartyUiMode.ITEM_TRANSFER: if (!this.transferMode) { optionsMessage = i18next.t("partyUiHandler:changeQuantity"); } @@ -1131,7 +1131,7 @@ export class PartyUiHandler extends MessageUiHandler { this.updateOptions(); /** When an item is being selected for transfer, the message box is taller as the message occupies two lines */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + if (this.partyUiMode === PartyUiMode.ITEM_TRANSFER) { this.partyMessageBox.setSize(262 - Math.max(this.optionsBg.displayWidth - 56, 0), 42); } else { this.partyMessageBox.setSize(262 - Math.max(this.optionsBg.displayWidth - 56, 0), 30); @@ -1140,7 +1140,7 @@ export class PartyUiHandler extends MessageUiHandler { this.setCursor(0); } - private allowBatonModifierSwitch(): boolean { + private allowBatonSwitch(): boolean { return !!( this.partyUiMode !== PartyUiMode.FAINT_SWITCH && globalScene.getPlayerField()[this.fieldIndex].heldItemManager.hasItem(HeldItemId.BATON) @@ -1158,7 +1158,7 @@ export class PartyUiHandler extends MessageUiHandler { ); } - private updateOptionsWithRememberMoveModifierMode(pokemon): void { + private updateOptionsWithRememberMoveRewardMode(pokemon): void { const learnableMoves = pokemon.getLearnableLevelMoves(); for (let m = 0; m < learnableMoves.length; m++) { this.options.push(m); @@ -1169,14 +1169,14 @@ export class PartyUiHandler extends MessageUiHandler { } } - private updateOptionsWithMoveModifierMode(pokemon): void { + private updateOptionsWithMoveRewardMode(pokemon): void { // MOVE_1, MOVE_2, MOVE_3, MOVE_4 for (let m = 0; m < pokemon.moveset.length; m++) { this.options.push(PartyOption.MOVE_1 + m); } } - private updateOptionsWithModifierTransferMode(pokemon): void { + private updateOptionsWithItemTransferMode(pokemon): void { const items = pokemon.getHeldItems(); for (let im = 0; im < items.length; im++) { this.options.push(im); @@ -1233,15 +1233,15 @@ export class PartyUiHandler extends MessageUiHandler { } switch (this.partyUiMode) { - case PartyUiMode.MOVE_MODIFIER: - this.updateOptionsWithMoveModifierMode(pokemon); + case PartyUiMode.MOVE_REWARD: + this.updateOptionsWithMoveRewardMode(pokemon); break; - case PartyUiMode.REMEMBER_MOVE_MODIFIER: - this.updateOptionsWithRememberMoveModifierMode(pokemon); + case PartyUiMode.REMEMBER_MOVE_REWARD: + this.updateOptionsWithRememberMoveRewardMode(pokemon); break; - case PartyUiMode.MODIFIER_TRANSFER: + case PartyUiMode.ITEM_TRANSFER: if (!this.transferMode) { - this.updateOptionsWithModifierTransferMode(pokemon); + this.updateOptionsWithItemTransferMode(pokemon); } else { this.options.push(PartyOption.TRANSFER); this.addCommonOptions(pokemon); @@ -1254,21 +1254,19 @@ export class PartyUiHandler extends MessageUiHandler { case PartyUiMode.FAINT_SWITCH: case PartyUiMode.POST_BATTLE_SWITCH: if (this.cursor >= globalScene.currentBattle.getBattlerCount()) { - const allowBatonModifierSwitch = this.allowBatonModifierSwitch(); + const allowBatonSwitch = this.allowBatonSwitch(); const isBatonPassMove = this.isBatonPassMove(); - if (allowBatonModifierSwitch && !isBatonPassMove) { + if (allowBatonSwitch && !isBatonPassMove) { // the BATON modifier gives an extra switch option for // pokemon-command switches, allowing buffs to be optionally passed this.options.push(PartyOption.PASS_BATON); } - // isBatonPassMove and allowBatonModifierSwitch shouldn't ever be true + // isBatonPassMove and allowBatonItemSwitch shouldn't ever be true // at the same time, because they both explicitly check for a mutually // exclusive partyUiMode. But better safe than sorry. - this.options.push( - isBatonPassMove && !allowBatonModifierSwitch ? PartyOption.PASS_BATON : PartyOption.SEND_OUT, - ); + this.options.push(isBatonPassMove && !allowBatonSwitch ? PartyOption.PASS_BATON : PartyOption.SEND_OUT); } this.addCommonOptions(pokemon); if (this.partyUiMode === PartyUiMode.SWITCH) { @@ -1282,11 +1280,11 @@ export class PartyUiHandler extends MessageUiHandler { this.options.push(PartyOption.REVIVE); this.addCommonOptions(pokemon); break; - case PartyUiMode.MODIFIER: + case PartyUiMode.REWARD: this.options.push(PartyOption.APPLY); this.addCommonOptions(pokemon); break; - case PartyUiMode.TM_MODIFIER: + case PartyUiMode.TM_REWARD: this.options.push(PartyOption.TEACH); this.addCommonOptions(pokemon); break; @@ -1351,8 +1349,8 @@ export class PartyUiHandler extends MessageUiHandler { } else if (option === PartyOption.SCROLL_DOWN) { optionName = "↓"; } else if ( - (this.partyUiMode !== PartyUiMode.REMEMBER_MOVE_MODIFIER && - (this.partyUiMode !== PartyUiMode.MODIFIER_TRANSFER || this.transferMode)) || + (this.partyUiMode !== PartyUiMode.REMEMBER_MOVE_REWARD && + (this.partyUiMode !== PartyUiMode.ITEM_TRANSFER || this.transferMode)) || option === PartyOption.CANCEL ) { switch (option) { @@ -1387,7 +1385,7 @@ export class PartyUiHandler extends MessageUiHandler { break; } } - } else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { + } else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_REWARD) { const learnableLevelMoves = pokemon.getLearnableLevelMoves(); const move = learnableLevelMoves[option]; optionName = allMoves[move].name; @@ -1415,7 +1413,7 @@ export class PartyUiHandler extends MessageUiHandler { const items = pokemon.getHeldItems(); const item = items[option]; if ( - this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && + this.partyUiMode === PartyUiMode.ITEM_TRANSFER && this.transferQuantitiesMax[option] > 1 && !this.transferMode && item !== undefined && @@ -1816,7 +1814,7 @@ class PartySlot extends Phaser.GameObjects.Container { slotInfoContainer.add([this.slotHpBar, this.slotHpOverlay, this.slotHpText, this.slotDescriptionLabel]); - if (partyUiMode !== PartyUiMode.TM_MODIFIER) { + if (partyUiMode !== PartyUiMode.TM_REWARD) { this.slotDescriptionLabel.setVisible(false); this.slotHpBar.setVisible(true); this.slotHpOverlay.setVisible(true); diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index c95f412c834..707d476da5f 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -434,7 +434,7 @@ export class PokemonInfoContainer extends Phaser.GameObjects.Container { this.setVisible(true); this.shown = true; - globalScene.hideEnemyModifierBar(); + globalScene.hideEnemyItemBar(); }); } @@ -488,7 +488,7 @@ export class PokemonInfoContainer extends Phaser.GameObjects.Container { hide(speedMultiplier = 1): Promise { return new Promise(resolve => { if (!this.shown) { - globalScene.showEnemyModifierBar(); + globalScene.showEnemyItemBar(); return resolve(); } @@ -509,7 +509,7 @@ export class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonShinyIcon.off("pointerover"); this.pokemonShinyIcon.off("pointerout"); globalScene.ui.hideTooltip(); - globalScene.showEnemyModifierBar(); + globalScene.showEnemyItemBar(); resolve(); }, }); diff --git a/src/ui/reward-select-ui-handler.ts b/src/ui/reward-select-ui-handler.ts index 256e581e867..2e4776adc0b 100644 --- a/src/ui/reward-select-ui-handler.ts +++ b/src/ui/reward-select-ui-handler.ts @@ -8,12 +8,12 @@ import type { PokeballType } from "#enums/pokeball"; import { ShopCursorTarget } from "#enums/shop-cursor-target"; import { TrainerItemId } from "#enums/trainer-item-id"; import { UiMode } from "#enums/ui-mode"; +import type { RewardOption } from "#items/reward"; +import { getPlayerShopRewardOptionsForWave, isTmReward } from "#items/reward-utils"; import { TrainerItemEffect } from "#items/trainer-item"; -import type { ModifierTypeOption } from "#modifiers/modifier-type"; -import { getPlayerShopModifierTypeOptionsForWave, TmModifierType } from "#modifiers/modifier-type"; import { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; import { MoveInfoOverlay } from "#ui/move-info-overlay"; -import { addTextObject, getModifierTierTextTint, getTextColor, getTextStyleOptions, TextStyle } from "#ui/text"; +import { addTextObject, getRarityTierTextTint, getTextColor, getTextStyleOptions, TextStyle } from "#ui/text"; import { formatMoney, NumberHolder } from "#utils/common"; import i18next from "i18next"; import Phaser from "phaser"; @@ -69,8 +69,8 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { if (context) { context.font = styleOptions.fontSize + "px " + styleOptions.fontFamily; - this.transferButtonWidth = context.measureText(i18next.t("modifierSelectUiHandler:transfer")).width; - this.checkButtonWidth = context.measureText(i18next.t("modifierSelectUiHandler:checkTeam")).width; + this.transferButtonWidth = context.measureText(i18next.t("rewardSelectUiHandler:transfer")).width; + this.checkButtonWidth = context.measureText(i18next.t("rewardSelectUiHandler:checkTeam")).width; } this.transferButtonContainer = globalScene.add.container( @@ -81,7 +81,7 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { this.transferButtonContainer.setVisible(false); ui.add(this.transferButtonContainer); - const transferButtonText = addTextObject(-4, -2, i18next.t("modifierSelectUiHandler:transfer"), TextStyle.PARTY); + const transferButtonText = addTextObject(-4, -2, i18next.t("rewardSelectUiHandler:transfer"), TextStyle.PARTY); transferButtonText.setName("text-transfer-btn"); transferButtonText.setOrigin(1, 0); this.transferButtonContainer.add(transferButtonText); @@ -94,7 +94,7 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { this.checkButtonContainer.setVisible(false); ui.add(this.checkButtonContainer); - const checkButtonText = addTextObject(-4, -2, i18next.t("modifierSelectUiHandler:checkTeam"), TextStyle.PARTY); + const checkButtonText = addTextObject(-4, -2, i18next.t("rewardSelectUiHandler:checkTeam"), TextStyle.PARTY); checkButtonText.setName("text-use-btn"); checkButtonText.setOrigin(1, 0); this.checkButtonContainer.add(checkButtonText); @@ -104,7 +104,7 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { this.rerollButtonContainer.setVisible(false); ui.add(this.rerollButtonContainer); - const rerollButtonText = addTextObject(-4, -2, i18next.t("modifierSelectUiHandler:reroll"), TextStyle.PARTY); + const rerollButtonText = addTextObject(-4, -2, i18next.t("rewardSelectUiHandler:reroll"), TextStyle.PARTY); rerollButtonText.setName("text-reroll-btn"); rerollButtonText.setOrigin(0, 0); this.rerollButtonContainer.add(rerollButtonText); @@ -119,12 +119,7 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { this.lockRarityButtonContainer.setVisible(false); ui.add(this.lockRarityButtonContainer); - this.lockRarityButtonText = addTextObject( - -4, - -2, - i18next.t("modifierSelectUiHandler:lockRarities"), - TextStyle.PARTY, - ); + this.lockRarityButtonText = addTextObject(-4, -2, i18next.t("rewardSelectUiHandler:lockRarities"), TextStyle.PARTY); this.lockRarityButtonText.setOrigin(0, 0); this.lockRarityButtonContainer.add(this.lockRarityButtonText); @@ -139,7 +134,7 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { const continueButtonText = addTextObject( -24, 5, - i18next.t("modifierSelectUiHandler:continueNextWaveButton"), + i18next.t("rewardSelectUiHandler:continueNextWaveButton"), TextStyle.MESSAGE, ); continueButtonText.setName("text-continue-btn"); @@ -211,12 +206,12 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { this.updateRerollCostText(); - const typeOptions = args[1] as ModifierTypeOption[]; + const typeOptions = args[1] as RewardOption[]; const removeHealShop = globalScene.gameMode.hasNoShop; const baseShopCost = new NumberHolder(globalScene.getWaveMoneyAmount(1)); globalScene.applyPlayerItems(TrainerItemEffect.HEAL_SHOP_COST, { numberHolder: baseShopCost }); const shopTypeOptions = !removeHealShop - ? getPlayerShopModifierTypeOptionsForWave(globalScene.currentBattle.waveIndex, baseShopCost.value) + ? getPlayerShopRewardOptionsForWave(globalScene.currentBattle.waveIndex, baseShopCost.value) : []; const optionsYOffset = shopTypeOptions.length > SHOP_OPTIONS_ROW_LIMIT ? -SINGLE_SHOP_ROW_YOFFSET : -DOUBLE_SHOP_ROW_YOFFSET; @@ -543,7 +538,7 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { -globalScene.game.canvas.height / 12 - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2), ); - ui.showText(i18next.t("modifierSelectUiHandler:continueNextWaveDescription")); + ui.showText(i18next.t("rewardSelectUiHandler:continueNextWaveDescription")); return ret; } @@ -565,33 +560,33 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { ); } - const type = options[this.cursor].modifierTypeOption.type; - type && ui.showText(type.getDescription()); - if (type instanceof TmModifierType) { + const reward = options[this.cursor].rewardOption.type; + reward && ui.showText(reward.getDescription()); + if (isTmReward(reward)) { // prepare the move overlay to be shown with the toggle - this.moveInfoOverlay.show(allMoves[type.moveId]); + this.moveInfoOverlay.show(allMoves[reward.moveId]); } } else if (cursor === 0) { this.cursorObj.setPosition( 6, this.lockRarityButtonContainer.visible ? OPTION_BUTTON_YPOSITION - 8 : OPTION_BUTTON_YPOSITION + 4, ); - ui.showText(i18next.t("modifierSelectUiHandler:rerollDesc")); + ui.showText(i18next.t("rewardSelectUiHandler:rerollDesc")); } else if (cursor === 1) { this.cursorObj.setPosition( (globalScene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, OPTION_BUTTON_YPOSITION + 4, ); - ui.showText(i18next.t("modifierSelectUiHandler:transferDesc")); + ui.showText(i18next.t("rewardSelectUiHandler:transferDesc")); } else if (cursor === 2) { this.cursorObj.setPosition( (globalScene.game.canvas.width - this.checkButtonWidth) / 6 - 10, OPTION_BUTTON_YPOSITION + 4, ); - ui.showText(i18next.t("modifierSelectUiHandler:checkTeamDesc")); + ui.showText(i18next.t("rewardSelectUiHandler:checkTeamDesc")); } else { this.cursorObj.setPosition(6, OPTION_BUTTON_YPOSITION + 4); - ui.showText(i18next.t("modifierSelectUiHandler:lockRaritiesDesc")); + ui.showText(i18next.t("rewardSelectUiHandler:lockRaritiesDesc")); } return ret; @@ -671,13 +666,13 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { const formattedMoney = formatMoney(globalScene.moneyFormat, this.rerollCost); - this.rerollCostText.setText(i18next.t("modifierSelectUiHandler:rerollCost", { formattedMoney })); + this.rerollCostText.setText(i18next.t("rewardSelectUiHandler:rerollCost", { formattedMoney })); this.rerollCostText.setColor(this.getTextColor(canReroll ? TextStyle.MONEY : TextStyle.PARTY_RED)); this.rerollCostText.setShadowColor(this.getTextColor(canReroll ? TextStyle.MONEY : TextStyle.PARTY_RED, true)); } updateLockRaritiesText(): void { - const textStyle = globalScene.lockModifierTiers ? TextStyle.SUMMARY_BLUE : TextStyle.PARTY; + const textStyle = globalScene.lockRarityTiers ? TextStyle.SUMMARY_BLUE : TextStyle.PARTY; this.lockRarityButtonText.setColor(this.getTextColor(textStyle)); this.lockRarityButtonText.setShadowColor(this.getTextColor(textStyle, true)); } @@ -753,7 +748,7 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { } class ModifierOption extends Phaser.GameObjects.Container { - public modifierTypeOption: ModifierTypeOption; + public rewardOption: RewardOption; private pb: Phaser.GameObjects.Sprite; private pbTint: Phaser.GameObjects.Sprite; private itemContainer: Phaser.GameObjects.Container; @@ -762,18 +757,18 @@ class ModifierOption extends Phaser.GameObjects.Container { private itemText: Phaser.GameObjects.Text; private itemCostText: Phaser.GameObjects.Text; - constructor(x: number, y: number, modifierTypeOption: ModifierTypeOption) { + constructor(x: number, y: number, rewardOption: RewardOption) { super(globalScene, x, y); - this.modifierTypeOption = modifierTypeOption; + this.rewardOption = rewardOption; this.setup(); } setup() { - if (!this.modifierTypeOption.cost) { + if (!this.rewardOption.cost) { const getPb = (): Phaser.GameObjects.Sprite => { - const pb = globalScene.add.sprite(0, -182, "pb", this.getPbAtlasKey(-this.modifierTypeOption.upgradeCount)); + const pb = globalScene.add.sprite(0, -182, "pb", this.getPbAtlasKey(-this.rewardOption.upgradeCount)); pb.setScale(2); return pb; }; @@ -792,28 +787,28 @@ class ModifierOption extends Phaser.GameObjects.Container { this.add(this.itemContainer); const getItem = () => { - const item = globalScene.add.sprite(0, 0, "items", this.modifierTypeOption.type?.getIcon()); + const item = globalScene.add.sprite(0, 0, "items", this.rewardOption.type?.getIcon()); return item; }; this.item = getItem(); this.itemContainer.add(this.item); - if (!this.modifierTypeOption.cost) { + if (!this.rewardOption.cost) { this.itemTint = getItem(); this.itemTint.setTintFill(Phaser.Display.Color.GetColor(255, 192, 255)); this.itemContainer.add(this.itemTint); } - this.itemText = addTextObject(0, 35, this.modifierTypeOption.type?.name!, TextStyle.PARTY, { align: "center" }); // TODO: is this bang correct? + this.itemText = addTextObject(0, 35, this.rewardOption.type?.name!, TextStyle.PARTY, { align: "center" }); // TODO: is this bang correct? this.itemText.setOrigin(0.5, 0); this.itemText.setAlpha(0); this.itemText.setTint( - this.modifierTypeOption.type?.tier ? getModifierTierTextTint(this.modifierTypeOption.type?.tier) : undefined, + this.rewardOption.type?.tier ? getRarityTierTextTint(this.rewardOption.type?.tier) : undefined, ); this.add(this.itemText); - if (this.modifierTypeOption.cost) { + if (this.rewardOption.cost) { this.itemCostText = addTextObject(0, 45, "", TextStyle.MONEY, { align: "center", }); @@ -827,7 +822,7 @@ class ModifierOption extends Phaser.GameObjects.Container { } show(remainingDuration: number, upgradeCountOffset: number) { - if (!this.modifierTypeOption.cost) { + if (!this.rewardOption.cost) { globalScene.tweens.add({ targets: this.pb, y: 0, @@ -863,11 +858,11 @@ class ModifierOption extends Phaser.GameObjects.Container { // TODO: Figure out proper delay between chains and then convert this into a single tween chain // rather than starting multiple tween chains. - for (let u = 0; u < this.modifierTypeOption.upgradeCount; u++) { + for (let u = 0; u < this.rewardOption.upgradeCount; u++) { globalScene.tweens.chain({ tweens: [ { - delay: remainingDuration - 2000 * (this.modifierTypeOption.upgradeCount - (u + 1 + upgradeCountOffset)), + delay: remainingDuration - 2000 * (this.rewardOption.upgradeCount - (u + 1 + upgradeCountOffset)), onStart: () => { globalScene.playSound("se/upgrade", { rate: 1 + 0.25 * u, @@ -879,7 +874,7 @@ class ModifierOption extends Phaser.GameObjects.Container { duration: 1000, ease: "Sine.easeIn", onComplete: () => { - this.pb.setTexture("pb", this.getPbAtlasKey(-this.modifierTypeOption.upgradeCount + (u + 1))); + this.pb.setTexture("pb", this.getPbAtlasKey(-this.rewardOption.upgradeCount + (u + 1))); }, }, { @@ -901,7 +896,7 @@ class ModifierOption extends Phaser.GameObjects.Container { return; } - if (!this.modifierTypeOption.cost) { + if (!this.rewardOption.cost) { this.pb.setTexture("pb", `${this.getPbAtlasKey(0)}_open`); globalScene.playSound("se/pb_rel"); @@ -922,7 +917,7 @@ class ModifierOption extends Phaser.GameObjects.Container { scale: 2, alpha: 1, }); - if (!this.modifierTypeOption.cost) { + if (!this.rewardOption.cost) { globalScene.tweens.add({ targets: this.itemTint, alpha: 0, @@ -951,16 +946,16 @@ class ModifierOption extends Phaser.GameObjects.Container { } getPbAtlasKey(tierOffset = 0) { - return getPokeballAtlasKey((this.modifierTypeOption.type?.tier! + tierOffset) as number as PokeballType); // TODO: is this bang correct? + return getPokeballAtlasKey((this.rewardOption.type?.tier! + tierOffset) as number as PokeballType); // TODO: is this bang correct? } updateCostText(): void { - const cost = Overrides.WAIVE_ROLL_FEE_OVERRIDE ? 0 : this.modifierTypeOption.cost; + const cost = Overrides.WAIVE_ROLL_FEE_OVERRIDE ? 0 : this.rewardOption.cost; const textStyle = cost <= globalScene.money ? TextStyle.MONEY : TextStyle.PARTY_RED; const formattedMoney = formatMoney(globalScene.moneyFormat, cost); - this.itemCostText.setText(i18next.t("modifierSelectUiHandler:itemCost", { formattedMoney })); + this.itemCostText.setText(i18next.t("rewardSelectUiHandler:itemCost", { formattedMoney })); this.itemCostText.setColor(getTextColor(textStyle, false, globalScene.uiTheme)); this.itemCostText.setShadowColor(getTextColor(textStyle, true, globalScene.uiTheme)); } diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 96b1500f7dd..8bd0b276b6f 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -16,7 +16,6 @@ import type { SpeciesId } from "#enums/species-id"; import { TrainerVariant } from "#enums/trainer-variant"; import { UiMode } from "#enums/ui-mode"; import { heldItemSortFunc } from "#items/item-utility"; -import { getLuckString, getLuckTextTint } from "#modifiers/modifier-type"; import { getVariantTint } from "#sprites/variant"; import type { SessionSaveData } from "#system/game-data"; import type { PokemonData } from "#system/pokemon-data"; @@ -25,6 +24,7 @@ import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#ui import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import { formatFancyLargeNumber, formatLargeNumber, formatMoney, getPlayTimeString } from "#utils/common"; +import { getLuckString, getLuckTextTint } from "#utils/party"; import i18next from "i18next"; import RoundRectangle from "phaser3-rex-plugins/plugins/roundrectangle"; diff --git a/src/ui/text.ts b/src/ui/text.ts index 3c56d5a046b..73ddc235593 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; import { EggTier } from "#enums/egg-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { UiTheme } from "#enums/ui-theme"; import i18next from "#plugins/i18n"; import type Phaser from "phaser"; @@ -709,19 +709,19 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui } } -export function getModifierTierTextTint(tier: RewardTier): number { +export function getRarityTierTextTint(tier: RarityTier): number { switch (tier) { - case RewardTier.COMMON: + case RarityTier.COMMON: return 0xf8f8f8; - case RewardTier.GREAT: + case RarityTier.GREAT: return 0x4998f8; - case RewardTier.ULTRA: + case RarityTier.ULTRA: return 0xf8d038; - case RewardTier.ROGUE: + case RarityTier.ROGUE: return 0xdb4343; - case RewardTier.MASTER: + case RarityTier.MASTER: return 0xe331c5; - case RewardTier.LUXURY: + case RarityTier.LUXURY: return 0xe74c18; } } @@ -729,12 +729,12 @@ export function getModifierTierTextTint(tier: RewardTier): number { export function getEggTierTextTint(tier: EggTier): number { switch (tier) { case EggTier.COMMON: - return getModifierTierTextTint(RewardTier.COMMON); + return getRarityTierTextTint(RarityTier.COMMON); case EggTier.RARE: - return getModifierTierTextTint(RewardTier.GREAT); + return getRarityTierTextTint(RarityTier.GREAT); case EggTier.EPIC: - return getModifierTierTextTint(RewardTier.ULTRA); + return getRarityTierTextTint(RarityTier.ULTRA); case EggTier.LEGENDARY: - return getModifierTierTextTint(RewardTier.MASTER); + return getRarityTierTextTint(RarityTier.MASTER); } } diff --git a/src/utils/modifier-utils.ts b/src/utils/modifier-utils.ts deleted file mode 100644 index 2cc9c041457..00000000000 --- a/src/utils/modifier-utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { modifierTypes } from "#data/data-lists"; -import { ModifierPoolType } from "#enums/modifier-pool-type"; -import { modifierPool } from "#modifiers/modifier-pools"; -import type { ModifierType } from "#modifiers/modifier-type"; -import type { ModifierPool, ModifierTypeFunc } from "#types/modifier-types"; - -export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool { - switch (poolType) { - case ModifierPoolType.PLAYER: - return modifierPool; - } -} - -// TODO: document this -export function getModifierType(modifierTypeFunc: ModifierTypeFunc): ModifierType { - const modifierType = modifierTypeFunc(); - if (!modifierType.id) { - modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc)!; // TODO: is this bang correct? - } - return modifierType; -} diff --git a/src/utils/party.ts b/src/utils/party.ts new file mode 100644 index 00000000000..f1ed8579f29 --- /dev/null +++ b/src/utils/party.ts @@ -0,0 +1,56 @@ +import { timedEventManager } from "#app/global-event-manager"; +import { globalScene } from "#app/global-scene"; +import { RarityTier } from "#enums/reward-tier"; +import type { Pokemon } from "#field/pokemon"; +import { getRarityTierTextTint } from "#ui/text"; +import { NumberHolder, randSeedInt } from "./common"; + +/** + * Calculates the team's luck value. + * @param party The player's party. + * @returns A number between 0 and 14 based on the party's total luck value, or a random number between 0 and 14 if the player is in Daily Run mode. + */ +export function getPartyLuckValue(party: Pokemon[]): number { + if (globalScene.gameMode.isDaily) { + const DailyLuck = new NumberHolder(0); + globalScene.executeWithSeedOffset( + () => { + DailyLuck.value = randSeedInt(15); // Random number between 0 and 14 + }, + 0, + globalScene.seed, + ); + return DailyLuck.value; + } + const eventSpecies = timedEventManager.getEventLuckBoostedSpecies(); + const luck = Phaser.Math.Clamp( + party + .map(p => (p.isAllowedInBattle() ? p.getLuck() + (eventSpecies.includes(p.species.speciesId) ? 1 : 0) : 0)) + .reduce((total: number, value: number) => (total += value), 0), + 0, + 14, + ); + return Math.min(timedEventManager.getEventLuckBoost() + (luck ?? 0), 14); +} + +export function getLuckString(luckValue: number): string { + return ["D", "C", "C+", "B-", "B", "B+", "A-", "A", "A+", "A++", "S", "S+", "SS", "SS+", "SSS"][luckValue]; +} + +export function getLuckTextTint(luckValue: number): number { + let rarityTier: RarityTier; + if (luckValue > 11) { + rarityTier = RarityTier.LUXURY; + } else if (luckValue > 9) { + rarityTier = RarityTier.MASTER; + } else if (luckValue > 5) { + rarityTier = RarityTier.ROGUE; + } else if (luckValue > 2) { + rarityTier = RarityTier.ULTRA; + } else if (luckValue) { + rarityTier = RarityTier.GREAT; + } else { + rarityTier = RarityTier.COMMON; + } + return getRarityTierTextTint(rarityTier); +} diff --git a/test/achievements/achievement.test.ts b/test/achievements/achievement.test.ts index 3b59aceac97..7afadc5d2ad 100644 --- a/test/achievements/achievement.test.ts +++ b/test/achievements/achievement.test.ts @@ -11,7 +11,6 @@ import { HealAchv, HeldItemAchv, LevelAchv, - ModifierAchv, MoneyAchv, RibbonAchv, } from "#system/achv"; @@ -260,7 +259,7 @@ describe("achvs", () => { expect(achvs.TERASTALLIZE).toBeInstanceOf(Achv); expect(achvs.STELLAR_TERASTALLIZE).toBeInstanceOf(Achv); expect(achvs.SPLICE).toBeInstanceOf(Achv); - expect(achvs.MINI_BLACK_HOLE).toBeInstanceOf(ModifierAchv); + expect(achvs.MINI_BLACK_HOLE).toBeInstanceOf(HeldItemAchv); expect(achvs.CATCH_MYTHICAL).toBeInstanceOf(Achv); expect(achvs.CATCH_SUB_LEGENDARY).toBeInstanceOf(Achv); expect(achvs.CATCH_LEGENDARY).toBeInstanceOf(Achv); diff --git a/test/daily-mode.test.ts b/test/daily-mode.test.ts index 6973982c69d..425ebae2879 100644 --- a/test/daily-mode.test.ts +++ b/test/daily-mode.test.ts @@ -74,7 +74,7 @@ describe("Shop modifications", async () => { game.move.select(MoveId.SPLASH); await game.doKillOpponents(); await game.phaseInterceptor.to("BattleEndPhase"); - game.onNextPrompt("SelectRewardPhase", UiMode.MODIFIER_SELECT, () => { + game.onNextPrompt("SelectRewardPhase", UiMode.REWARD_SELECT, () => { expect(game.scene.ui.getHandler()).toBeInstanceOf(RewardSelectUiHandler); game.modifiers.testCheck("EVIOLITE", false).testCheck("MINI_BLACK_HOLE", false); }); @@ -85,7 +85,7 @@ describe("Shop modifications", async () => { game.move.select(MoveId.SPLASH); await game.doKillOpponents(); await game.phaseInterceptor.to("BattleEndPhase"); - game.onNextPrompt("SelectRewardPhase", UiMode.MODIFIER_SELECT, () => { + game.onNextPrompt("SelectRewardPhase", UiMode.REWARD_SELECT, () => { expect(game.scene.ui.getHandler()).toBeInstanceOf(RewardSelectUiHandler); game.modifiers.testCheck("EVIOLITE", true).testCheck("MINI_BLACK_HOLE", true); }); diff --git a/test/items/dire-hit.test.ts b/test/items/dire-hit.test.ts index d00faabe773..de20e378b4e 100644 --- a/test/items/dire-hit.test.ts +++ b/test/items/dire-hit.test.ts @@ -69,7 +69,7 @@ describe("Items - Dire Hit", () => { // Forced DIRE_HIT to spawn in the first slot with override game.onNextPrompt( "SelectRewardPhase", - UiMode.MODIFIER_SELECT, + UiMode.REWARD_SELECT, () => { const handler = game.scene.ui.getHandler() as RewardSelectUiHandler; // Traverse to first modifier slot diff --git a/test/items/double-battle-chance-booster.test.ts b/test/items/double-battle-chance-booster.test.ts index a7192b89ee4..9483095104d 100644 --- a/test/items/double-battle-chance-booster.test.ts +++ b/test/items/double-battle-chance-booster.test.ts @@ -75,7 +75,7 @@ describe("Items - Double Battle Chance Boosters", () => { // Forced LURE to spawn in the first slot with override game.onNextPrompt( "SelectRewardPhase", - UiMode.MODIFIER_SELECT, + UiMode.REWARD_SELECT, () => { const handler = game.scene.ui.getHandler() as RewardSelectUiHandler; // Traverse to first modifier slot diff --git a/test/items/eviolite.test.ts b/test/items/eviolite.test.ts index d56f2a99910..19c50ba90b4 100644 --- a/test/items/eviolite.test.ts +++ b/test/items/eviolite.test.ts @@ -1,8 +1,8 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import { GameManager } from "#test/test-utils/game-manager"; import { NumberHolder, randItem } from "#utils/common"; import Phaser from "phaser"; diff --git a/test/items/exp-booster.test.ts b/test/items/exp-booster.test.ts index 1206cadc89a..87d22350c00 100644 --- a/test/items/exp-booster.test.ts +++ b/test/items/exp-booster.test.ts @@ -1,7 +1,7 @@ import { AbilityId } from "#enums/ability-id"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import { GameManager } from "#test/test-utils/game-manager"; import { NumberHolder } from "#utils/common"; import Phaser from "phaser"; diff --git a/test/items/light-ball.test.ts b/test/items/light-ball.test.ts index 0f631002dce..74f88f695c1 100644 --- a/test/items/light-ball.test.ts +++ b/test/items/light-ball.test.ts @@ -1,8 +1,8 @@ +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import i18next from "#plugins/i18n"; import { GameManager } from "#test/test-utils/game-manager"; import { NumberHolder } from "#utils/common"; diff --git a/test/items/lock-capsule.test.ts b/test/items/lock-capsule.test.ts index 64216c6045a..435b6814bc4 100644 --- a/test/items/lock-capsule.test.ts +++ b/test/items/lock-capsule.test.ts @@ -1,6 +1,6 @@ import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { TrainerItemId } from "#enums/trainer-item-id"; import { UiMode } from "#enums/ui-mode"; import { SelectRewardPhase } from "#phases/select-reward-phase"; @@ -37,14 +37,14 @@ describe("Items - Lock Capsule", () => { await game.classicMode.startBattle(); game.scene.phaseManager.overridePhase( new SelectRewardPhase(0, undefined, { - guaranteedModifierTiers: [RewardTier.COMMON, RewardTier.COMMON, RewardTier.COMMON], + guaranteedRarityTiers: [RarityTier.COMMON, RarityTier.COMMON, RarityTier.COMMON], fillRemaining: false, }), ); - game.onNextPrompt("SelectRewardPhase", UiMode.MODIFIER_SELECT, () => { - const selectModifierPhase = game.scene.phaseManager.getCurrentPhase() as SelectRewardPhase; - const rerollCost = selectModifierPhase.getRerollCost(true); + game.onNextPrompt("SelectRewardPhase", UiMode.REWARD_SELECT, () => { + const selectRewardPhase = game.scene.phaseManager.getCurrentPhase() as SelectRewardPhase; + const rerollCost = selectRewardPhase.getRerollCost(true); expect(rerollCost).toBe(150); }); diff --git a/test/items/metal-powder.test.ts b/test/items/metal-powder.test.ts index b846a085075..9cb069c73ce 100644 --- a/test/items/metal-powder.test.ts +++ b/test/items/metal-powder.test.ts @@ -1,9 +1,8 @@ -import { modifierTypes } from "#data/data-lists"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import i18next from "#plugins/i18n"; import { GameManager } from "#test/test-utils/game-manager"; import { NumberHolder } from "#utils/common"; @@ -97,10 +96,7 @@ describe("Items - Metal Powder", () => { expect(defValue.value / defStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["METAL_POWDER"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.METAL_POWDER); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: defValue }); expect(defValue.value / defStat).toBe(2); @@ -130,10 +126,7 @@ describe("Items - Metal Powder", () => { expect(defValue.value / defStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["METAL_POWDER"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.METAL_POWDER); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: defValue }); expect(defValue.value / defStat).toBe(2); @@ -163,10 +156,7 @@ describe("Items - Metal Powder", () => { expect(defValue.value / defStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["METAL_POWDER"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.METAL_POWDER); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: defValue }); expect(defValue.value / defStat).toBe(2); @@ -186,10 +176,7 @@ describe("Items - Metal Powder", () => { expect(defValue.value / defStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["METAL_POWDER"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.METAL_POWDER); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.DEF, statValue: defValue }); expect(defValue.value / defStat).toBe(1); diff --git a/test/items/quick-powder.test.ts b/test/items/quick-powder.test.ts index ddb77587a66..fdab41514b6 100644 --- a/test/items/quick-powder.test.ts +++ b/test/items/quick-powder.test.ts @@ -1,9 +1,8 @@ -import { modifierTypes } from "#data/data-lists"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import i18next from "#plugins/i18n"; import { GameManager } from "#test/test-utils/game-manager"; import { NumberHolder } from "#utils/common"; @@ -97,10 +96,7 @@ describe("Items - Quick Powder", () => { expect(spdValue.value / spdStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["QUICK_POWDER"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.QUICK_POWDER); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPD, statValue: spdValue }); expect(spdValue.value / spdStat).toBe(2); @@ -130,10 +126,7 @@ describe("Items - Quick Powder", () => { expect(spdValue.value / spdStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["QUICK_POWDER"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.QUICK_POWDER); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPD, statValue: spdValue }); expect(spdValue.value / spdStat).toBe(2); @@ -163,10 +156,7 @@ describe("Items - Quick Powder", () => { expect(spdValue.value / spdStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["QUICK_POWDER"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.QUICK_POWDER); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPD, statValue: spdValue }); expect(spdValue.value / spdStat).toBe(2); @@ -186,10 +176,7 @@ describe("Items - Quick Powder", () => { expect(spdValue.value / spdStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["QUICK_POWDER"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.QUICK_POWDER); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.SPD, statValue: spdValue }); expect(spdValue.value / spdStat).toBe(1); diff --git a/test/items/temp-stat-stage-booster.test.ts b/test/items/temp-stat-stage-booster.test.ts index d1cedd92cd2..de51c1612b6 100644 --- a/test/items/temp-stat-stage-booster.test.ts +++ b/test/items/temp-stat-stage-booster.test.ts @@ -131,7 +131,7 @@ describe("Items - Temporary Stat Stage Boosters", () => { // Forced X_ATTACK to spawn in the first slot with override game.onNextPrompt( "SelectRewardPhase", - UiMode.MODIFIER_SELECT, + UiMode.REWARD_SELECT, () => { const handler = game.scene.ui.getHandler() as RewardSelectUiHandler; // Traverse to first modifier slot diff --git a/test/items/thick-club.test.ts b/test/items/thick-club.test.ts index e998466126e..1e7236d616d 100644 --- a/test/items/thick-club.test.ts +++ b/test/items/thick-club.test.ts @@ -1,9 +1,8 @@ -import { modifierTypes } from "#data/data-lists"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import i18next from "#plugins/i18n"; import { GameManager } from "#test/test-utils/game-manager"; import { NumberHolder, randInt } from "#utils/common"; @@ -97,10 +96,7 @@ describe("Items - Thick Club", () => { expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.THICK_CLUB); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(2); @@ -120,10 +116,7 @@ describe("Items - Thick Club", () => { expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.THICK_CLUB); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(2); @@ -143,10 +136,7 @@ describe("Items - Thick Club", () => { expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.THICK_CLUB); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(2); @@ -180,10 +170,7 @@ describe("Items - Thick Club", () => { expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.THICK_CLUB); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(2); @@ -217,10 +204,7 @@ describe("Items - Thick Club", () => { expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.THICK_CLUB); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(2); @@ -240,10 +224,7 @@ describe("Items - Thick Club", () => { expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - await game.scene.addModifier( - modifierTypes.RARE_SPECIES_STAT_BOOSTER().generateType([], ["THICK_CLUB"])!.newModifier(partyMember), - true, - ); + partyMember.heldItemManager.add(HeldItemId.THICK_CLUB); applyHeldItems(HeldItemEffect.STAT_BOOST, { pokemon: partyMember, stat: Stat.ATK, statValue: atkValue }); expect(atkValue.value / atkStat).toBe(1); diff --git a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts index 2d000c77a6c..a12508a04a0 100644 --- a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts +++ b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts @@ -149,13 +149,13 @@ describe("Berries Abound - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(5); - for (const option of modifierSelectHandler.options) { - expect(option.modifierTypeOption.type.id).toContain("BERRY"); + expect(rewardSelectHandler.options.length).toEqual(5); + for (const option of rewardSelectHandler.options) { + expect(option.rewardOption.type.id).toContain("BERRY"); } }); }); @@ -234,13 +234,13 @@ describe("Berries Abound - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(5); - for (const option of modifierSelectHandler.options) { - expect(option.modifierTypeOption.type.id).toContain("BERRY"); + expect(rewardSelectHandler.options.length).toEqual(5); + for (const option of rewardSelectHandler.options) { + expect(option.rewardOption.type.id).toContain("BERRY"); } expect(EncounterDialogueUtils.showEncounterText).toHaveBeenCalledWith(`${namespace}:option.2.selected`); diff --git a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index e3926e1f2ef..2a653f903b3 100644 --- a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -412,23 +412,23 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); }); - it("should proceed to rewards screen with 0-1 Bug Types reward options", async () => { + it("should proceed to allRewards screen with 0-1 Bug Types reward options", async () => { await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, defaultParty); await runMysteryEncounterToEnd(game, 2); expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(2); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("SUPER_LURE"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("GREAT_BALL"); + expect(rewardSelectHandler.options.length).toEqual(2); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toBe("SUPER_LURE"); + expect(rewardSelectHandler.options[1].rewardOption.type.id).toBe("GREAT_BALL"); }); - it("should proceed to rewards screen with 2-3 Bug Types reward options", async () => { + it("should proceed to allRewards screen with 2-3 Bug Types reward options", async () => { await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, [ SpeciesId.BUTTERFREE, SpeciesId.BEEDRILL, @@ -438,17 +438,17 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(3); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("QUICK_CLAW"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MAX_LURE"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toBe("ULTRA_BALL"); + expect(rewardSelectHandler.options.length).toEqual(3); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toBe("QUICK_CLAW"); + expect(rewardSelectHandler.options[1].rewardOption.type.id).toBe("MAX_LURE"); + expect(rewardSelectHandler.options[2].rewardOption.type.id).toBe("ULTRA_BALL"); }); - it("should proceed to rewards screen with 4-5 Bug Types reward options", async () => { + it("should proceed to allRewards screen with 4-5 Bug Types reward options", async () => { await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, [ SpeciesId.BUTTERFREE, SpeciesId.BEEDRILL, @@ -460,17 +460,17 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(3); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("GRIP_CLAW"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MAX_LURE"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toBe("ROGUE_BALL"); + expect(rewardSelectHandler.options.length).toEqual(3); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toBe("GRIP_CLAW"); + expect(rewardSelectHandler.options[1].rewardOption.type.id).toBe("MAX_LURE"); + expect(rewardSelectHandler.options[2].rewardOption.type.id).toBe("ROGUE_BALL"); }); - it("should proceed to rewards screen with 6 Bug Types reward options (including form change item)", async () => { + it("should proceed to allRewards screen with 6 Bug Types reward options (including form change item)", async () => { await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, [ SpeciesId.BUTTERFREE, SpeciesId.BEEDRILL, @@ -484,15 +484,15 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(4); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MASTER_BALL"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MEGA_BRACELET"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toBe("DYNAMAX_BAND"); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.id).toBe("FORM_CHANGE_ITEM"); + expect(rewardSelectHandler.options.length).toEqual(4); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toBe("MASTER_BALL"); + expect(rewardSelectHandler.options[1].rewardOption.type.id).toBe("MEGA_BRACELET"); + expect(rewardSelectHandler.options[2].rewardOption.type.id).toBe("DYNAMAX_BAND"); + expect(rewardSelectHandler.options[3].rewardOption.type.id).toBe("FORM_CHANGE_ITEM"); }); it("should leave encounter without battle", async () => { @@ -548,7 +548,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); }); - it("should remove the gifted item and proceed to rewards screen", async () => { + it("should remove the gifted item and proceed to allRewards screen", async () => { game.override.startingHeldItems([{ entry: HeldItemId.GRIP_CLAW, count: 1 }]); await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, [SpeciesId.BUTTERFREE]); @@ -559,13 +559,13 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(2); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MYSTERY_ENCOUNTER_GOLDEN_BUG_NET"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("REVIVER_SEED"); + expect(rewardSelectHandler.options.length).toEqual(2); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toBe("MYSTERY_ENCOUNTER_GOLDEN_BUG_NET"); + expect(rewardSelectHandler.options[1].rewardOption.type.id).toBe("REVIVER_SEED"); const gripClawCountAfter = scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.GRIP_CLAW); expect(gripClawCountBefore - 1).toBe(gripClawCountAfter); diff --git a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 3233582f892..72c69ac36e3 100644 --- a/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -9,11 +9,11 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PokemonType } from "#enums/pokemon-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import { UiMode } from "#enums/ui-mode"; -import { getHeldItemTier } from "#items/held-item-tiers"; +import { getHeldItemTier } from "#items/held-item-default-tiers"; import { PokemonMove } from "#moves/pokemon-move"; import { ClowningAroundEncounter } from "#mystery-encounters/clowning-around-encounter"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; @@ -277,10 +277,10 @@ describe("Clowning Around - Mystery Encounter", () => { const leadItemsAfter = scene.getPlayerParty()[0].getHeldItems(); const ultraCountAfter = leadItemsAfter - .filter(m => getHeldItemTier(m) === RewardTier.ULTRA) + .filter(m => getHeldItemTier(m) === RarityTier.ULTRA) .reduce((a, b) => a + scene.getPlayerParty()[0].heldItemManager.getStack(b), 0); const rogueCountAfter = leadItemsAfter - .filter(m => getHeldItemTier(m) === RewardTier.ROGUE) + .filter(m => getHeldItemTier(m) === RarityTier.ROGUE) .reduce((a, b) => a + scene.getPlayerParty()[0].heldItemManager.getStack(b), 0); expect(ultraCountAfter).toBe(13); expect(rogueCountAfter).toBe(7); diff --git a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index f8247e378b4..afbe2e30e59 100644 --- a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -118,7 +118,7 @@ describe("Dancing Lessons - Mystery Encounter", () => { expect(movePhases.filter(p => (p as MovePhase).move.moveId === MoveId.REVELATION_DANCE).length).toBe(1); // Revelation Dance used before battle }); - it("should have a Baton in the rewards after battle", async () => { + it("should have a Baton in the allRewards after battle", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty); // Make party lead's level arbitrarily high to not get KOed by move const partyLead = scene.getPlayerParty()[0]; @@ -130,12 +130,12 @@ describe("Dancing Lessons - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(3); // Should fill remaining - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toContain("BATON"); + expect(rewardSelectHandler.options.length).toEqual(3); // Should fill remaining + expect(rewardSelectHandler.options[0].rewardOption.type.id).toContain("BATON"); }); }); diff --git a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts index 6fb96ca6a39..11b1cadd7e9 100644 --- a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts +++ b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts @@ -96,13 +96,13 @@ describe("Department Store Sale - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(5); - for (const option of modifierSelectHandler.options) { - expect(option.modifierTypeOption.type.id).toContain("TM_"); + expect(rewardSelectHandler.options.length).toEqual(5); + for (const option of rewardSelectHandler.options) { + expect(option.rewardOption.type.id).toContain("TM_"); } }); @@ -133,15 +133,14 @@ describe("Department Store Sale - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(3); - for (const option of modifierSelectHandler.options) { + expect(rewardSelectHandler.options.length).toEqual(3); + for (const option of rewardSelectHandler.options) { expect( - option.modifierTypeOption.type.id.includes("PP_UP") || - option.modifierTypeOption.type.id.includes("BASE_STAT_BOOSTER"), + option.rewardOption.type.id.includes("PP_UP") || option.rewardOption.type.id.includes("BASE_STAT_BOOSTER"), ).toBeTruthy(); } }); @@ -173,15 +172,15 @@ describe("Department Store Sale - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(5); - for (const option of modifierSelectHandler.options) { + expect(rewardSelectHandler.options.length).toEqual(5); + for (const option of rewardSelectHandler.options) { expect( - option.modifierTypeOption.type.id.includes("DIRE_HIT") || - option.modifierTypeOption.type.id.includes("TEMP_STAT_STAGE_BOOSTER"), + option.rewardOption.type.id.includes("DIRE_HIT") || + option.rewardOption.type.id.includes("TEMP_STAT_STAGE_BOOSTER"), ).toBeTruthy(); } }); @@ -213,13 +212,13 @@ describe("Department Store Sale - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(4); - for (const option of modifierSelectHandler.options) { - expect(option.modifierTypeOption.type.id).toContain("BALL"); + expect(rewardSelectHandler.options.length).toEqual(4); + for (const option of rewardSelectHandler.options) { + expect(option.rewardOption.type.id).toContain("BALL"); } }); diff --git a/test/mystery-encounter/encounters/field-trip-encounter.test.ts b/test/mystery-encounter/encounters/field-trip-encounter.test.ts index 46a0791784b..bde7ec88e5e 100644 --- a/test/mystery-encounter/encounters/field-trip-encounter.test.ts +++ b/test/mystery-encounter/encounters/field-trip-encounter.test.ts @@ -87,36 +87,36 @@ describe("Field Trip - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1, optionNo: 2 }); await game.phaseInterceptor.to(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(0); + expect(rewardSelectHandler.options.length).toEqual(0); }); - it("Should give proper rewards on correct Physical move option", async () => { + it("Should give proper allRewards on correct Physical move option", async () => { await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1, optionNo: 1 }); await game.phaseInterceptor.to(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options.length).toEqual(5); + expect(rewardSelectHandler.options[0].rewardOption.type.name).toBe( i18next.t("modifierType:TempStatStageBoosterItem.x_attack"), ); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options[1].rewardOption.type.name).toBe( i18next.t("modifierType:TempStatStageBoosterItem.x_defense"), ); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options[2].rewardOption.type.name).toBe( i18next.t("modifierType:TempStatStageBoosterItem.x_speed"), ); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options[3].rewardOption.type.name).toBe( i18next.t("modifierType:ModifierType.DIRE_HIT.name"), ); - expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options[4].rewardOption.type.name).toBe( i18next.t("modifierType:ModifierType.RARER_CANDY.name"), ); }); @@ -148,36 +148,36 @@ describe("Field Trip - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); await game.phaseInterceptor.to(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(0); + expect(rewardSelectHandler.options.length).toEqual(0); }); - it("Should give proper rewards on correct Special move option", async () => { + it("Should give proper allRewards on correct Special move option", async () => { await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 2 }); await game.phaseInterceptor.to(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options.length).toEqual(5); + expect(rewardSelectHandler.options[0].rewardOption.type.name).toBe( i18next.t("modifierType:TempStatStageBoosterItem.x_sp_atk"), ); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options[1].rewardOption.type.name).toBe( i18next.t("modifierType:TempStatStageBoosterItem.x_sp_def"), ); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options[2].rewardOption.type.name).toBe( i18next.t("modifierType:TempStatStageBoosterItem.x_speed"), ); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options[3].rewardOption.type.name).toBe( i18next.t("modifierType:ModifierType.DIRE_HIT.name"), ); - expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options[4].rewardOption.type.name).toBe( i18next.t("modifierType:ModifierType.RARER_CANDY.name"), ); }); @@ -209,31 +209,31 @@ describe("Field Trip - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); await game.phaseInterceptor.to(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(0); + expect(rewardSelectHandler.options.length).toEqual(0); }); - it("Should give proper rewards on correct Special move option", async () => { + it("Should give proper allRewards on correct Special move option", async () => { vi.spyOn(i18next, "t"); await game.runToMysteryEncounter(MysteryEncounterType.FIELD_TRIP, defaultParty); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 3 }); await game.phaseInterceptor.to(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options.length).toEqual(5); + expect(rewardSelectHandler.options[0].rewardOption.type.name).toBe( i18next.t("modifierType:TempStatStageBoosterItem.x_accuracy"), ); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options[1].rewardOption.type.name).toBe( i18next.t("modifierType:TempStatStageBoosterItem.x_speed"), ); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options[2].rewardOption.type.name).toBe( i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", { modifierCount: 5, pokeballName: i18next.t("pokeball:greatBall"), @@ -243,10 +243,10 @@ describe("Field Trip - Mystery Encounter", () => { "modifierType:ModifierType.AddPokeballModifierType.name", expect.objectContaining({ modifierCount: 5 }), ); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options[3].rewardOption.type.name).toBe( i18next.t("modifierType:ModifierType.IV_SCANNER.name"), ); - expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe( + expect(rewardSelectHandler.options[4].rewardOption.type.name).toBe( i18next.t("modifierType:ModifierType.RARER_CANDY.name"), ); }); diff --git a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts index 34b88b3bf67..19d13e69a0d 100644 --- a/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts +++ b/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts @@ -125,13 +125,13 @@ describe("Fight or Flight - Mystery Encounter", () => { await game.phaseInterceptor.to(SelectRewardPhase, false); expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(1); - expect(item.type.name).toBe(modifierSelectHandler.options[0].modifierTypeOption.type.name); + expect(rewardSelectHandler.options.length).toEqual(1); + expect(item.type.name).toBe(rewardSelectHandler.options[0].rewardOption.type.name); }); }); @@ -185,13 +185,13 @@ describe("Fight or Flight - Mystery Encounter", () => { await game.phaseInterceptor.to(SelectRewardPhase, false); expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(1); - expect(item.type.name).toBe(modifierSelectHandler.options[0].modifierTypeOption.type.name); + expect(rewardSelectHandler.options.length).toEqual(1); + expect(item.type.name).toBe(rewardSelectHandler.options[0].rewardOption.type.name); expect(leaveEncounterWithoutBattleSpy).toBeCalled(); }); diff --git a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts index 412686cd658..2cae72c3c8d 100644 --- a/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts +++ b/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts @@ -168,7 +168,7 @@ describe("Fun And Games! - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); }); - it("should have no items in rewards if Wubboffet doesn't take enough damage", async () => { + it("should have no items in allRewards if Wubboffet doesn't take enough damage", async () => { scene.money = 20000; await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true); @@ -187,14 +187,14 @@ describe("Fun And Games! - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(0); + expect(rewardSelectHandler.options.length).toEqual(0); }); - it("should have Wide Lens item in rewards if Wubboffet is at 15-33% HP remaining", async () => { + it("should have Wide Lens item in allRewards if Wubboffet is at 15-33% HP remaining", async () => { scene.money = 20000; game.override.moveset([MoveId.SPLASH]); await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); @@ -216,15 +216,15 @@ describe("Fun And Games! - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(1); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("WIDE_LENS"); + expect(rewardSelectHandler.options.length).toEqual(1); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toEqual("WIDE_LENS"); }); - it("should have Scope Lens item in rewards if Wubboffet is at 3-15% HP remaining", async () => { + it("should have Scope Lens item in allRewards if Wubboffet is at 3-15% HP remaining", async () => { scene.money = 20000; game.override.moveset([MoveId.SPLASH]); await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); @@ -246,15 +246,15 @@ describe("Fun And Games! - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(1); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("SCOPE_LENS"); + expect(rewardSelectHandler.options.length).toEqual(1); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toEqual("SCOPE_LENS"); }); - it("should have Multi Lens item in rewards if Wubboffet is at <3% HP remaining", async () => { + it("should have Multi Lens item in allRewards if Wubboffet is at <3% HP remaining", async () => { scene.money = 20000; game.override.moveset([MoveId.SPLASH]); await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); @@ -276,12 +276,12 @@ describe("Fun And Games! - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(1); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MULTI_LENS"); + expect(rewardSelectHandler.options.length).toEqual(1); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toEqual("MULTI_LENS"); }); }); diff --git a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index 66f05e7b8ed..a58bb187ad2 100644 --- a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -4,7 +4,7 @@ import { HeldItemId } from "#enums/held-item-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; @@ -212,7 +212,7 @@ describe("Global Trade System - Mystery Encounter", () => { }); }); - it("should decrease item stacks of chosen item and have a tiered up item in rewards", async () => { + it("should decrease item stacks of chosen item and have a tiered up item in allRewards", async () => { await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty); // Set 2 Soul Dew on party lead @@ -223,12 +223,12 @@ describe("Global Trade System - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(1); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier).toBe(RewardTier.MASTER); + expect(rewardSelectHandler.options.length).toEqual(1); + expect(rewardSelectHandler.options[0].rewardOption.type.tier).toBe(RarityTier.MASTER); const soulDewAfter = scene.getPlayerParty()[0].heldItemManager.getStack(HeldItemId.SOUL_DEW); expect(soulDewAfter).toBe(1); }); diff --git a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index 092dd6092be..4df9d75e0d6 100644 --- a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -5,7 +5,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { MysteriousChallengersEncounter } from "#mystery-encounters/mysterious-challengers-encounter"; @@ -157,7 +157,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); }); - it("should have normal trainer rewards after battle", async () => { + it("should have normal trainer allRewards after battle", async () => { await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); @@ -165,14 +165,14 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(3); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toContain("TM_COMMON"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toContain("TM_GREAT"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toContain("MEMORY_MUSHROOM"); + expect(rewardSelectHandler.options.length).toEqual(3); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toContain("TM_COMMON"); + expect(rewardSelectHandler.options[1].rewardOption.type.id).toContain("TM_GREAT"); + expect(rewardSelectHandler.options[2].rewardOption.type.id).toContain("MEMORY_MUSHROOM"); }); }); @@ -201,7 +201,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); }); - it("should have hard trainer rewards after battle", async () => { + it("should have hard trainer allRewards after battle", async () => { await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty); await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); @@ -209,27 +209,27 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(4); + expect(rewardSelectHandler.options.length).toEqual(4); expect( - modifierSelectHandler.options[0].modifierTypeOption.type.tier - - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toBe(RewardTier.ULTRA); + rewardSelectHandler.options[0].rewardOption.type.tier - + rewardSelectHandler.options[0].rewardOption.upgradeCount, + ).toBe(RarityTier.ULTRA); expect( - modifierSelectHandler.options[1].modifierTypeOption.type.tier - - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toBe(RewardTier.ULTRA); + rewardSelectHandler.options[1].rewardOption.type.tier - + rewardSelectHandler.options[1].rewardOption.upgradeCount, + ).toBe(RarityTier.ULTRA); expect( - modifierSelectHandler.options[2].modifierTypeOption.type.tier - - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toBe(RewardTier.GREAT); + rewardSelectHandler.options[2].rewardOption.type.tier - + rewardSelectHandler.options[2].rewardOption.upgradeCount, + ).toBe(RarityTier.GREAT); expect( - modifierSelectHandler.options[3].modifierTypeOption.type.tier - - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toBe(RewardTier.GREAT); + rewardSelectHandler.options[3].rewardOption.type.tier - + rewardSelectHandler.options[3].rewardOption.upgradeCount, + ).toBe(RarityTier.GREAT); }); }); @@ -258,7 +258,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); }); - it("should have brutal trainer rewards after battle", async () => { + it("should have brutal trainer allRewards after battle", async () => { await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty); await runMysteryEncounterToEnd(game, 3, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); @@ -266,27 +266,27 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(4); + expect(rewardSelectHandler.options.length).toEqual(4); expect( - modifierSelectHandler.options[0].modifierTypeOption.type.tier - - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toBe(RewardTier.ROGUE); + rewardSelectHandler.options[0].rewardOption.type.tier - + rewardSelectHandler.options[0].rewardOption.upgradeCount, + ).toBe(RarityTier.ROGUE); expect( - modifierSelectHandler.options[1].modifierTypeOption.type.tier - - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toBe(RewardTier.ROGUE); + rewardSelectHandler.options[1].rewardOption.type.tier - + rewardSelectHandler.options[1].rewardOption.upgradeCount, + ).toBe(RarityTier.ROGUE); expect( - modifierSelectHandler.options[2].modifierTypeOption.type.tier - - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toBe(RewardTier.ULTRA); + rewardSelectHandler.options[2].rewardOption.type.tier - + rewardSelectHandler.options[2].rewardOption.upgradeCount, + ).toBe(RarityTier.ULTRA); expect( - modifierSelectHandler.options[3].modifierTypeOption.type.tier - - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toBe(RewardTier.GREAT); + rewardSelectHandler.options[3].rewardOption.type.tier - + rewardSelectHandler.options[3].rewardOption.upgradeCount, + ).toBe(RarityTier.GREAT); }); }); }); diff --git a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index 56526791102..3e9c8e2e5ad 100644 --- a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -295,7 +295,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { expect(enemyField[0].isBoss()).toBe(true); }); - it("should have Magnet and Metal Coat in rewards after battle", async () => { + it("should have Magnet and Metal Coat in allRewards after battle", async () => { await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, defaultParty); await runMysteryEncounterToEnd(game, 3, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); @@ -303,18 +303,18 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; expect( - modifierSelectHandler.options.some( - opt => opt.modifierTypeOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.metal_coat"), + rewardSelectHandler.options.some( + opt => opt.rewardOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.metal_coat"), ), ).toBe(true); expect( - modifierSelectHandler.options.some( - opt => opt.modifierTypeOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.magnet"), + rewardSelectHandler.options.some( + opt => opt.rewardOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.magnet"), ), ).toBe(true); }); diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index 328f3463ff0..872b2f9c076 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -4,6 +4,7 @@ import { CustomPokemonData } from "#data/pokemon-data"; import { AbilityId } from "#enums/ability-id"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BiomeId } from "#enums/biome-id"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -13,7 +14,6 @@ import { Nature } from "#enums/nature"; import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { applyHeldItems } from "#items/all-held-items"; -import { HeldItemEffect } from "#items/held-item"; import { PokemonMove } from "#moves/pokemon-move"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; @@ -218,7 +218,7 @@ describe("The Strong Stuff - Mystery Encounter", () => { expect(movePhases.filter(p => (p as MovePhase).move.moveId === MoveId.STEALTH_ROCK).length).toBe(1); }); - it("should have Soul Dew in rewards", async () => { + it("should have Soul Dew in allRewards", async () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty); await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); @@ -226,12 +226,12 @@ describe("The Strong Stuff - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(3); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("SOUL_DEW"); + expect(rewardSelectHandler.options.length).toEqual(3); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toEqual("SOUL_DEW"); }); }); }); diff --git a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts index 1847ba15fcf..8be8061fdfa 100644 --- a/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts @@ -293,18 +293,18 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { expect(scene.currentBattle.mysteryEncounter?.enemyPartyConfigs.length).toBe(0); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); - // Should have Macho Brace in the rewards + // Should have Macho Brace in the allRewards await skipBattleToNextBattle(game, true); await game.phaseInterceptor.to(SelectRewardPhase, false); expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(1); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MYSTERY_ENCOUNTER_MACHO_BRACE"); + expect(rewardSelectHandler.options.length).toEqual(1); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toBe("MYSTERY_ENCOUNTER_MACHO_BRACE"); }); }); @@ -335,18 +335,18 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { expect(partyHealPhases.length).toBe(1); }); - it("should have a Rarer Candy in the rewards", async () => { + it("should have a Rarer Candy in the allRewards", async () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty); await runMysteryEncounterToEnd(game, 2); expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(1); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("RARER_CANDY"); + expect(rewardSelectHandler.options.length).toEqual(1); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toBe("RARER_CANDY"); }); }); }); diff --git a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 5b10ecf4f0c..1e4d1c3deba 100644 --- a/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -6,7 +6,7 @@ import { MoveId } from "#enums/move-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerItemId } from "#enums/trainer-item-id"; import { UiMode } from "#enums/ui-mode"; @@ -200,7 +200,7 @@ describe("Trash to Treasure - Mystery Encounter", () => { expect(movePhases.filter(p => (p as MovePhase).move.moveId === MoveId.STOCKPILE).length).toBe(1); }); - it("should have 2 Rogue, 1 Ultra, 1 Great in rewards", async () => { + it("should have 2 Rogue, 1 Ultra, 1 Great in allRewards", async () => { await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty); await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); @@ -208,27 +208,27 @@ describe("Trash to Treasure - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(4); + expect(rewardSelectHandler.options.length).toEqual(4); expect( - modifierSelectHandler.options[0].modifierTypeOption.type.tier - - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.ROGUE); + rewardSelectHandler.options[0].rewardOption.type.tier - + rewardSelectHandler.options[0].rewardOption.upgradeCount, + ).toEqual(RarityTier.ROGUE); expect( - modifierSelectHandler.options[1].modifierTypeOption.type.tier - - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.ROGUE); + rewardSelectHandler.options[1].rewardOption.type.tier - + rewardSelectHandler.options[1].rewardOption.upgradeCount, + ).toEqual(RarityTier.ROGUE); expect( - modifierSelectHandler.options[2].modifierTypeOption.type.tier - - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.ULTRA); + rewardSelectHandler.options[2].rewardOption.type.tier - + rewardSelectHandler.options[2].rewardOption.upgradeCount, + ).toEqual(RarityTier.ULTRA); expect( - modifierSelectHandler.options[3].modifierTypeOption.type.tier - - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.GREAT); + rewardSelectHandler.options[3].rewardOption.type.tier - + rewardSelectHandler.options[3].rewardOption.upgradeCount, + ).toEqual(RarityTier.GREAT); }); }); }); diff --git a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index fd7c093a8ef..c32779e26aa 100644 --- a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -3,7 +3,7 @@ import { BiomeId } from "#enums/biome-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { RewardTier } from "#enums/reward-tier"; +import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; @@ -136,23 +136,23 @@ describe("Weird Dream - Mystery Encounter", () => { expect(plus40To50.length).toBe(1); }); - it("should have 1 Memory Mushroom, 5 Rogue Balls, and 3 Mints in rewards", async () => { + it("should have 1 Memory Mushroom, 5 Rogue Balls, and 3 Mints in allRewards", async () => { await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); await runMysteryEncounterToEnd(game, 1); await game.phaseInterceptor.to(SelectRewardPhase, false); expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("ROGUE_BALL"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toEqual("MINT"); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.id).toEqual("MINT"); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.id).toEqual("MINT"); + expect(rewardSelectHandler.options.length).toEqual(5); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toEqual("MEMORY_MUSHROOM"); + expect(rewardSelectHandler.options[1].rewardOption.type.id).toEqual("ROGUE_BALL"); + expect(rewardSelectHandler.options[2].rewardOption.type.id).toEqual("MINT"); + expect(rewardSelectHandler.options[3].rewardOption.type.id).toEqual("MINT"); + expect(rewardSelectHandler.options[3].rewardOption.type.id).toEqual("MINT"); }); it("should leave encounter without battle", async () => { @@ -191,7 +191,7 @@ describe("Weird Dream - Mystery Encounter", () => { expect(scene.getEnemyParty().length).toBe(scene.getPlayerParty().length); }); - it("should have 2 Rogue/2 Ultra/2 Great items in rewards", async () => { + it("should have 2 Rogue/2 Ultra/2 Great items in allRewards", async () => { await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); await runMysteryEncounterToEnd(game, 2, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); @@ -199,35 +199,35 @@ describe("Weird Dream - Mystery Encounter", () => { expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectRewardPhase.name); await game.phaseInterceptor.run(SelectRewardPhase); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( h => h instanceof RewardSelectUiHandler, ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(6); + expect(rewardSelectHandler.options.length).toEqual(6); expect( - modifierSelectHandler.options[0].modifierTypeOption.type.tier - - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.ROGUE); + rewardSelectHandler.options[0].rewardOption.type.tier - + rewardSelectHandler.options[0].rewardOption.upgradeCount, + ).toEqual(RarityTier.ROGUE); expect( - modifierSelectHandler.options[1].modifierTypeOption.type.tier - - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.ROGUE); + rewardSelectHandler.options[1].rewardOption.type.tier - + rewardSelectHandler.options[1].rewardOption.upgradeCount, + ).toEqual(RarityTier.ROGUE); expect( - modifierSelectHandler.options[2].modifierTypeOption.type.tier - - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.ULTRA); + rewardSelectHandler.options[2].rewardOption.type.tier - + rewardSelectHandler.options[2].rewardOption.upgradeCount, + ).toEqual(RarityTier.ULTRA); expect( - modifierSelectHandler.options[3].modifierTypeOption.type.tier - - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.ULTRA); + rewardSelectHandler.options[3].rewardOption.type.tier - + rewardSelectHandler.options[3].rewardOption.upgradeCount, + ).toEqual(RarityTier.ULTRA); expect( - modifierSelectHandler.options[4].modifierTypeOption.type.tier - - modifierSelectHandler.options[4].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.GREAT); + rewardSelectHandler.options[4].rewardOption.type.tier - + rewardSelectHandler.options[4].rewardOption.upgradeCount, + ).toEqual(RarityTier.GREAT); expect( - modifierSelectHandler.options[5].modifierTypeOption.type.tier - - modifierSelectHandler.options[5].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.GREAT); + rewardSelectHandler.options[5].rewardOption.type.tier - + rewardSelectHandler.options[5].rewardOption.upgradeCount, + ).toEqual(RarityTier.GREAT); }); }); diff --git a/test/phases/form-change-phase.test.ts b/test/phases/form-change-phase.test.ts index 3caf824b252..df0bd74f8ef 100644 --- a/test/phases/form-change-phase.test.ts +++ b/test/phases/form-change-phase.test.ts @@ -1,60 +1,59 @@ -import { modifierTypes } from "#data/data-lists"; -import { AbilityId } from "#enums/ability-id"; -import { MoveId } from "#enums/move-id"; -import { PokemonType } from "#enums/pokemon-type"; -import { SpeciesId } from "#enums/species-id"; -import { generateModifierType } from "#mystery-encounters/encounter-phase-utils"; -import { GameManager } from "#test/test-utils/game-manager"; -import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import type { RewardKeys } from "#items/reward"; +import { itemPoolChecks } from "#items/reward"; +import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper"; +import { expect } from "vitest"; -describe("Form Change Phase", () => { - let phaserGame: Phaser.Game; - let game: GameManager; +export class ModifierHelper extends GameManagerHelper { + /** + * Adds a Modifier to the list of modifiers to check for. + * + * Note that all modifiers are updated during the start of `SelectRewardPhase`. + * @param modifier The Modifier to add. + * @returns `this` + */ + addCheck(modifier: RewardKeys): this { + itemPoolChecks.set(modifier, undefined); + return this; + } - beforeAll(() => { - phaserGame = new Phaser.Game({ - type: Phaser.HEADLESS, - }); - }); + /** + * `get`s a value from the `itemPoolChecks` map. + * + * If the item is in the Modifier Pool, and the player can get it, will return `true`. + * + * If the item is *not* in the Modifier Pool, will return `false`. + * + * If a `SelectRewardPhase` has not occurred, and we do not know if the item is in the Modifier Pool or not, will return `undefined`. + * @param modifier + * @returns + */ + getCheck(modifier: RewardKeys): boolean | undefined { + return itemPoolChecks.get(modifier); + } - afterEach(() => { - game.phaseInterceptor.restoreOg(); - }); + /** + * `expect`s a Modifier `toBeTruthy` (in the Modifier Pool) or `Falsy` (unobtainable on this floor). Use during a test. + * + * Note that if a `SelectRewardPhase` has not been run yet, these values will be `undefined`, and the check will fail. + * @param modifier The modifier to check. + * @param expectToBePreset Whether the Modifier should be in the Modifier Pool. Set to `false` to expect it to be absent instead. + * @returns `this` + */ + testCheck(modifier: RewardKeys, expectToBePreset: boolean): this { + if (expectToBePreset) { + expect(itemPoolChecks.get(modifier)).toBeTruthy(); + } + expect(itemPoolChecks.get(modifier)).toBeFalsy(); + return this; + } - beforeEach(() => { - game = new GameManager(phaserGame); - game.override - .moveset([MoveId.SPLASH]) - .ability(AbilityId.BALL_FETCH) - .battleStyle("single") - .criticalHits(false) - .enemySpecies(SpeciesId.MAGIKARP) - .enemyAbility(AbilityId.BALL_FETCH) - .enemyMoveset(MoveId.SPLASH); - }); + /** Removes all modifier checks. @returns `this` */ + clearChecks() { + itemPoolChecks.clear(); + return this; + } - it("Zacian should successfully change into Crowned form", async () => { - await game.classicMode.startBattle([SpeciesId.ZACIAN]); - - // Before the form change: Should be Hero form - const zacian = game.scene.getPlayerParty()[0]; - expect(zacian.getFormKey()).toBe("hero-of-many-battles"); - expect(zacian.getTypes()).toStrictEqual([PokemonType.FAIRY]); - expect(zacian.calculateBaseStats()).toStrictEqual([92, 120, 115, 80, 115, 138]); - - // Give Zacian a Rusted Sword - const rustedSwordType = generateModifierType(modifierTypes.RARE_FORM_CHANGE_ITEM)!; - const rustedSword = rustedSwordType.newModifier(zacian); - await game.scene.addModifier(rustedSword); - - game.move.select(MoveId.SPLASH); - await game.toNextTurn(); - - // After the form change: Should be Crowned form - expect(game.phaseInterceptor.log.includes("FormChangePhase")).toBe(true); - expect(zacian.getFormKey()).toBe("crowned"); - expect(zacian.getTypes()).toStrictEqual([PokemonType.FAIRY, PokemonType.STEEL]); - expect(zacian.calculateBaseStats()).toStrictEqual([92, 150, 115, 80, 115, 148]); - }); -}); + private log(...params: any[]) { + console.log("Modifiers:", ...params); + } +} \ No newline at end of file diff --git a/test/phases/game-over-phase.test.ts b/test/phases/game-over-phase.test.ts index 81c00294d90..2baf1939b29 100644 --- a/test/phases/game-over-phase.test.ts +++ b/test/phases/game-over-phase.test.ts @@ -36,7 +36,7 @@ describe("Game Over Phase", () => { .startingLevel(10000); }); - it("winning a run should give rewards", async () => { + it("winning a run should give allRewards", async () => { await game.classicMode.startBattle([SpeciesId.BULBASAUR]); vi.spyOn(game.scene, "validateAchv"); @@ -59,7 +59,7 @@ describe("Game Over Phase", () => { expect(game.scene.gameData.achvUnlocks[achvs.CLASSIC_VICTORY.id]).toBeTruthy(); }); - it("losing a run should not give rewards", async () => { + it("losing a run should not give allRewards", async () => { await game.classicMode.startBattle([SpeciesId.BULBASAUR]); vi.spyOn(game.scene, "validateAchv"); diff --git a/test/phases/select-modifier-phase.test.ts b/test/phases/select-modifier-phase.test.ts deleted file mode 100644 index 3f3ff81472f..00000000000 --- a/test/phases/select-modifier-phase.test.ts +++ /dev/null @@ -1,279 +0,0 @@ -import type { BattleScene } from "#app/battle-scene"; -import { modifierTypes } from "#data/data-lists"; -import { AbilityId } from "#enums/ability-id"; -import { Button } from "#enums/buttons"; -import { MoveId } from "#enums/move-id"; -import { RewardTier } from "#enums/reward-tier"; -import { SpeciesId } from "#enums/species-id"; -import { TrainerItemId } from "#enums/trainer-item-id"; -import { UiMode } from "#enums/ui-mode"; -import { PlayerPokemon } from "#field/pokemon"; -import type { CustomModifierSettings } from "#modifiers/modifier-type"; -import { ModifierTypeOption } from "#modifiers/modifier-type"; -import { SelectRewardPhase } from "#phases/select-reward-phase"; -import { GameManager } from "#test/test-utils/game-manager"; -import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; -import { RewardSelectUiHandler } from "#ui/reward-select-ui-handler"; -import { shiftCharCodes } from "#utils/common"; -import { getPokemonSpecies } from "#utils/pokemon-utils"; -import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; - -describe("SelectRewardPhase", () => { - let phaserGame: Phaser.Game; - let game: GameManager; - let scene: BattleScene; - - beforeAll(() => { - phaserGame = new Phaser.Game({ - type: Phaser.HEADLESS, - }); - }); - - beforeEach(() => { - game = new GameManager(phaserGame); - scene = game.scene; - - game.override - .moveset([MoveId.FISSURE, MoveId.SPLASH]) - .ability(AbilityId.NO_GUARD) - .startingLevel(200) - .enemySpecies(SpeciesId.MAGIKARP); - }); - - afterEach(() => { - game.phaseInterceptor.restoreOg(); - }); - - it("should start a select modifier phase", async () => { - initSceneWithoutEncounterPhase(scene, [SpeciesId.ABRA, SpeciesId.VOLCARONA]); - const selectModifierPhase = new SelectRewardPhase(); - scene.phaseManager.unshiftPhase(selectModifierPhase); - await game.phaseInterceptor.to(SelectRewardPhase); - - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - }); - - it("should generate random modifiers", async () => { - await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); - game.move.select(MoveId.FISSURE); - await game.phaseInterceptor.to("SelectRewardPhase"); - - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof RewardSelectUiHandler, - ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(3); - }); - - it("should modify reroll cost", async () => { - initSceneWithoutEncounterPhase(scene, [SpeciesId.ABRA, SpeciesId.VOLCARONA]); - const options = [ - new ModifierTypeOption(modifierTypes.POTION(), 0, 100), - new ModifierTypeOption(modifierTypes.ETHER(), 0, 400), - new ModifierTypeOption(modifierTypes.REVIVE(), 0, 1000), - ]; - - const selectModifierPhase1 = new SelectRewardPhase(0, undefined, { - guaranteedModifierTypeOptions: options, - }); - const selectModifierPhase2 = new SelectRewardPhase(0, undefined, { - guaranteedModifierTypeOptions: options, - rerollMultiplier: 2, - }); - - const cost1 = selectModifierPhase1.getRerollCost(false); - const cost2 = selectModifierPhase2.getRerollCost(false); - expect(cost2).toEqual(cost1 * 2); - }); - - it.todo("should generate random modifiers from reroll", async () => { - await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); - scene.money = 1000000; - scene.shopCursorTarget = 0; - - game.move.select(MoveId.FISSURE); - await game.phaseInterceptor.to("SelectRewardPhase"); - - // TODO: nagivate the ui to reroll somehow - //const smphase = scene.phaseManager.getCurrentPhase() as SelectRewardPhase; - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof RewardSelectUiHandler, - ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(3); - - modifierSelectHandler.processInput(Button.ACTION); - - expect(scene.money).toBe(1000000 - 250); - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - expect(modifierSelectHandler.options.length).toEqual(3); - }); - - it.todo("should generate random modifiers of same tier for reroll with reroll lock", async () => { - game.override.startingTrainerItems([{ entry: TrainerItemId.LOCK_CAPSULE }]); - await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); - scene.money = 1000000; - // Just use fully random seed for this test - vi.spyOn(scene, "resetSeed").mockImplementation(() => { - scene.waveSeed = shiftCharCodes(scene.seed, 5); - Phaser.Math.RND.sow([scene.waveSeed]); - console.log("Wave Seed:", scene.waveSeed, 5); - scene.rngCounter = 0; - }); - - game.move.select(MoveId.FISSURE); - await game.phaseInterceptor.to("SelectRewardPhase"); - - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof RewardSelectUiHandler, - ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(3); - const firstRollTiers: RewardTier[] = modifierSelectHandler.options.map(o => o.modifierTypeOption.type.tier); - - // TODO: nagivate ui to reroll with lock capsule enabled - - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - expect(modifierSelectHandler.options.length).toEqual(3); - // Reroll with lock can still upgrade - expect( - modifierSelectHandler.options[0].modifierTypeOption.type.tier - - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toEqual(firstRollTiers[0]); - expect( - modifierSelectHandler.options[1].modifierTypeOption.type.tier - - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toEqual(firstRollTiers[1]); - expect( - modifierSelectHandler.options[2].modifierTypeOption.type.tier - - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toEqual(firstRollTiers[2]); - }); - - it("should generate custom modifiers", async () => { - await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); - scene.money = 1000000; - const customModifiers: CustomModifierSettings = { - guaranteedModifierTypeFuncs: [ - modifierTypes.MEMORY_MUSHROOM, - modifierTypes.TM_ULTRA, - modifierTypes.LEFTOVERS, - modifierTypes.AMULET_COIN, - modifierTypes.GOLDEN_PUNCH, - ], - }; - const selectModifierPhase = new SelectRewardPhase(0, undefined, customModifiers); - scene.phaseManager.unshiftPhase(selectModifierPhase); - game.move.select(MoveId.SPLASH); - await game.phaseInterceptor.to("SelectRewardPhase"); - - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof RewardSelectUiHandler, - ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("TM_ULTRA"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toEqual("LEFTOVERS"); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.id).toEqual("AMULET_COIN"); - expect(modifierSelectHandler.options[4].modifierTypeOption.type.id).toEqual("GOLDEN_PUNCH"); - }); - - it("should generate custom modifier tiers that can upgrade from luck", async () => { - await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); - scene.money = 1000000; - const customModifiers: CustomModifierSettings = { - guaranteedModifierTiers: [ - RewardTier.COMMON, - RewardTier.GREAT, - RewardTier.ULTRA, - RewardTier.ROGUE, - RewardTier.MASTER, - ], - }; - const pokemon = new PlayerPokemon(getPokemonSpecies(SpeciesId.BULBASAUR), 10, undefined, 0, undefined, true, 2); - - // Fill party with max shinies - while (scene.getPlayerParty().length > 0) { - scene.getPlayerParty().pop(); - } - scene.getPlayerParty().push(pokemon, pokemon, pokemon, pokemon, pokemon, pokemon); - - const selectModifierPhase = new SelectRewardPhase(0, undefined, customModifiers); - scene.phaseManager.unshiftPhase(selectModifierPhase); - game.move.select(MoveId.SPLASH); - await game.phaseInterceptor.to("SelectRewardPhase"); - - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof RewardSelectUiHandler, - ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(5); - expect( - modifierSelectHandler.options[0].modifierTypeOption.type.tier - - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.COMMON); - expect( - modifierSelectHandler.options[1].modifierTypeOption.type.tier - - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.GREAT); - expect( - modifierSelectHandler.options[2].modifierTypeOption.type.tier - - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.ULTRA); - expect( - modifierSelectHandler.options[3].modifierTypeOption.type.tier - - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.ROGUE); - expect( - modifierSelectHandler.options[4].modifierTypeOption.type.tier - - modifierSelectHandler.options[4].modifierTypeOption.upgradeCount, - ).toEqual(RewardTier.MASTER); - }); - - it("should generate custom modifiers and modifier tiers together", async () => { - await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); - scene.money = 1000000; - const customModifiers: CustomModifierSettings = { - guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM, modifierTypes.TM_COMMON], - guaranteedModifierTiers: [RewardTier.MASTER, RewardTier.MASTER], - }; - const selectModifierPhase = new SelectRewardPhase(0, undefined, customModifiers); - scene.phaseManager.unshiftPhase(selectModifierPhase); - game.move.select(MoveId.SPLASH); - await game.phaseInterceptor.run(SelectRewardPhase); - - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof RewardSelectUiHandler, - ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(4); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("TM_COMMON"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.tier).toEqual(RewardTier.MASTER); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.tier).toEqual(RewardTier.MASTER); - }); - - it("should fill remaining modifiers if fillRemaining is true with custom modifiers", async () => { - await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); - scene.money = 1000000; - const customModifiers: CustomModifierSettings = { - guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM], - guaranteedModifierTiers: [RewardTier.MASTER], - fillRemaining: true, - }; - const selectModifierPhase = new SelectRewardPhase(0, undefined, customModifiers); - scene.phaseManager.unshiftPhase(selectModifierPhase); - game.move.select(MoveId.SPLASH); - await game.phaseInterceptor.run(SelectRewardPhase); - - expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); - const modifierSelectHandler = scene.ui.handlers.find( - h => h instanceof RewardSelectUiHandler, - ) as RewardSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(3); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.tier).toEqual(RewardTier.MASTER); - }); -}); diff --git a/test/phases/select-reward-phase.test.ts b/test/phases/select-reward-phase.test.ts new file mode 100644 index 00000000000..ed664cf1aff --- /dev/null +++ b/test/phases/select-reward-phase.test.ts @@ -0,0 +1,281 @@ +import type { BattleScene } from "#app/battle-scene"; +import { allRewards } from "#data/data-lists"; +import { AbilityId } from "#enums/ability-id"; +import { Button } from "#enums/buttons"; +import { HeldItemId } from "#enums/held-item-id"; +import { MoveId } from "#enums/move-id"; +import { RewardId } from "#enums/reward-id"; +import { RarityTier } from "#enums/reward-tier"; +import { SpeciesId } from "#enums/species-id"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import { UiMode } from "#enums/ui-mode"; +import { PlayerPokemon } from "#field/pokemon"; +import type { HeldItemReward, TrainerItemReward } from "#items/reward"; +import { RewardOption } from "#items/reward"; +import type { CustomRewardSettings } from "#items/reward-pool-utils"; +import { SelectRewardPhase } from "#phases/select-reward-phase"; +import { GameManager } from "#test/test-utils/game-manager"; +import { initSceneWithoutEncounterPhase } from "#test/test-utils/game-manager-utils"; +import { RewardSelectUiHandler } from "#ui/reward-select-ui-handler"; +import { shiftCharCodes } from "#utils/common"; +import { getPokemonSpecies } from "#utils/pokemon-utils"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("SelectRewardPhase", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + let scene: BattleScene; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + scene = game.scene; + + game.override + .moveset([MoveId.FISSURE, MoveId.SPLASH]) + .ability(AbilityId.NO_GUARD) + .startingLevel(200) + .enemySpecies(SpeciesId.MAGIKARP); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + it("should start a select modifier phase", async () => { + initSceneWithoutEncounterPhase(scene, [SpeciesId.ABRA, SpeciesId.VOLCARONA]); + const selectRewardPhase = new SelectRewardPhase(); + scene.phaseManager.unshiftPhase(selectRewardPhase); + await game.phaseInterceptor.to(SelectRewardPhase); + + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + }); + + it("should generate random modifiers", async () => { + await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); + game.move.select(MoveId.FISSURE); + await game.phaseInterceptor.to("SelectRewardPhase"); + + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; + expect(rewardSelectHandler.options.length).toEqual(3); + }); + + it("should modify reroll cost", async () => { + initSceneWithoutEncounterPhase(scene, [SpeciesId.ABRA, SpeciesId.VOLCARONA]); + const options = [ + new RewardOption(allRewards.POTION(), 0, RarityTier.COMMON, 100), + new RewardOption(allRewards.ETHER(), 0, RarityTier.COMMON, 400), + new RewardOption(allRewards.REVIVE(), 0, RarityTier.COMMON, 1000), + ]; + + const selectRewardPhase1 = new SelectRewardPhase(0, undefined, { + guaranteedRewardOptions: options, + }); + const selectRewardPhase2 = new SelectRewardPhase(0, undefined, { + guaranteedRewardOptions: options, + rerollMultiplier: 2, + }); + + const cost1 = selectRewardPhase1.getRerollCost(false); + const cost2 = selectRewardPhase2.getRerollCost(false); + expect(cost2).toEqual(cost1 * 2); + }); + + it.todo("should generate random modifiers from reroll", async () => { + await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); + scene.money = 1000000; + scene.shopCursorTarget = 0; + + game.move.select(MoveId.FISSURE); + await game.phaseInterceptor.to("SelectRewardPhase"); + + // TODO: nagivate the ui to reroll somehow + //const smphase = scene.phaseManager.getCurrentPhase() as SelectRewardPhase; + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; + expect(rewardSelectHandler.options.length).toEqual(3); + + rewardSelectHandler.processInput(Button.ACTION); + + expect(scene.money).toBe(1000000 - 250); + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + expect(rewardSelectHandler.options.length).toEqual(3); + }); + + it.todo("should generate random modifiers of same tier for reroll with reroll lock", async () => { + game.override.startingTrainerItems([{ entry: TrainerItemId.LOCK_CAPSULE }]); + await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); + scene.money = 1000000; + // Just use fully random seed for this test + vi.spyOn(scene, "resetSeed").mockImplementation(() => { + scene.waveSeed = shiftCharCodes(scene.seed, 5); + Phaser.Math.RND.sow([scene.waveSeed]); + console.log("Wave Seed:", scene.waveSeed, 5); + scene.rngCounter = 0; + }); + + game.move.select(MoveId.FISSURE); + await game.phaseInterceptor.to("SelectRewardPhase"); + + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; + expect(rewardSelectHandler.options.length).toEqual(3); + const firstRollTiers: RarityTier[] = rewardSelectHandler.options.map(o => o.rewardOption.tier); + + // TODO: nagivate ui to reroll with lock capsule enabled + + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + expect(rewardSelectHandler.options.length).toEqual(3); + // Reroll with lock can still upgrade + expect( + rewardSelectHandler.options[0].rewardOption.tier - rewardSelectHandler.options[0].rewardOption.upgradeCount, + ).toEqual(firstRollTiers[0]); + expect( + rewardSelectHandler.options[1].rewardOption.tier - rewardSelectHandler.options[1].rewardOption.upgradeCount, + ).toEqual(firstRollTiers[1]); + expect( + rewardSelectHandler.options[2].rewardOption.tier - rewardSelectHandler.options[2].rewardOption.upgradeCount, + ).toEqual(firstRollTiers[2]); + }); + + it("should generate custom modifiers", async () => { + await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); + scene.money = 1000000; + const customRewards: CustomRewardSettings = { + guaranteedRewardFuncs: [ + allRewards.MEMORY_MUSHROOM, + allRewards.TM_ULTRA, + allRewards.LEFTOVERS, + allRewards.AMULET_COIN, + allRewards.GOLDEN_PUNCH, + ], + }; + const selectRewardPhase = new SelectRewardPhase(0, undefined, customRewards); + scene.phaseManager.unshiftPhase(selectRewardPhase); + game.move.select(MoveId.SPLASH); + await game.phaseInterceptor.to("SelectRewardPhase"); + + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; + expect(rewardSelectHandler.options.length).toEqual(5); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toEqual(RewardId.MEMORY_MUSHROOM); + expect(rewardSelectHandler.options[1].rewardOption.type.id).toEqual(RewardId.TM_ULTRA); + expect(rewardSelectHandler.options[2].rewardOption.type.id).toEqual(RewardId.HELD_ITEM); + expect((rewardSelectHandler.options[2].rewardOption.type as HeldItemReward).itemId).toEqual(HeldItemId.LEFTOVERS); + expect(rewardSelectHandler.options[3].rewardOption.type.id).toEqual(RewardId.TRAINER_ITEM); + expect((rewardSelectHandler.options[3].rewardOption.type as TrainerItemReward).itemId).toEqual( + TrainerItemId.AMULET_COIN, + ); + expect(rewardSelectHandler.options[4].rewardOption.type.id).toEqual(RewardId.HELD_ITEM); + expect((rewardSelectHandler.options[4].rewardOption.type as HeldItemReward).itemId).toEqual( + HeldItemId.GOLDEN_PUNCH, + ); + }); + + it("should generate custom modifier tiers that can upgrade from luck", async () => { + await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); + scene.money = 1000000; + const customRewards: CustomRewardSettings = { + guaranteedRarityTiers: [ + RarityTier.COMMON, + RarityTier.GREAT, + RarityTier.ULTRA, + RarityTier.ROGUE, + RarityTier.MASTER, + ], + }; + const pokemon = new PlayerPokemon(getPokemonSpecies(SpeciesId.BULBASAUR), 10, undefined, 0, undefined, true, 2); + + // Fill party with max shinies + while (scene.getPlayerParty().length > 0) { + scene.getPlayerParty().pop(); + } + scene.getPlayerParty().push(pokemon, pokemon, pokemon, pokemon, pokemon, pokemon); + + const selectRewardPhase = new SelectRewardPhase(0, undefined, customRewards); + scene.phaseManager.unshiftPhase(selectRewardPhase); + game.move.select(MoveId.SPLASH); + await game.phaseInterceptor.to("SelectRewardPhase"); + + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; + expect(rewardSelectHandler.options.length).toEqual(5); + expect( + rewardSelectHandler.options[0].rewardOption.tier - rewardSelectHandler.options[0].rewardOption.upgradeCount, + ).toEqual(RarityTier.COMMON); + expect( + rewardSelectHandler.options[1].rewardOption.tier - rewardSelectHandler.options[1].rewardOption.upgradeCount, + ).toEqual(RarityTier.GREAT); + expect( + rewardSelectHandler.options[2].rewardOption.tier - rewardSelectHandler.options[2].rewardOption.upgradeCount, + ).toEqual(RarityTier.ULTRA); + expect( + rewardSelectHandler.options[3].rewardOption.tier - rewardSelectHandler.options[3].rewardOption.upgradeCount, + ).toEqual(RarityTier.ROGUE); + expect( + rewardSelectHandler.options[4].rewardOption.tier - rewardSelectHandler.options[4].rewardOption.upgradeCount, + ).toEqual(RarityTier.MASTER); + }); + + it("should generate custom modifiers and modifier tiers together", async () => { + await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); + scene.money = 1000000; + const customRewards: CustomRewardSettings = { + guaranteedRewardFuncs: [allRewards.MEMORY_MUSHROOM, allRewards.TM_COMMON], + guaranteedRarityTiers: [RarityTier.MASTER, RarityTier.MASTER], + }; + const selectRewardPhase = new SelectRewardPhase(0, undefined, customRewards); + scene.phaseManager.unshiftPhase(selectRewardPhase); + game.move.select(MoveId.SPLASH); + await game.phaseInterceptor.run(SelectRewardPhase); + + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; + expect(rewardSelectHandler.options.length).toEqual(4); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toEqual(RewardId.MEMORY_MUSHROOM); + expect(rewardSelectHandler.options[1].rewardOption.type.id).toEqual(RewardId.TM_COMMON); + expect(rewardSelectHandler.options[2].rewardOption.tier).toEqual(RarityTier.MASTER); + expect(rewardSelectHandler.options[3].rewardOption.tier).toEqual(RarityTier.MASTER); + }); + + it("should fill remaining modifiers if fillRemaining is true with custom modifiers", async () => { + await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); + scene.money = 1000000; + const customRewards: CustomRewardSettings = { + guaranteedRewardFuncs: [allRewards.MEMORY_MUSHROOM], + guaranteedRarityTiers: [RarityTier.MASTER], + fillRemaining: true, + }; + const selectRewardPhase = new SelectRewardPhase(0, undefined, customRewards); + scene.phaseManager.unshiftPhase(selectRewardPhase); + game.move.select(MoveId.SPLASH); + await game.phaseInterceptor.run(SelectRewardPhase); + + expect(scene.ui.getMode()).to.equal(UiMode.REWARD_SELECT); + const rewardSelectHandler = scene.ui.handlers.find( + h => h instanceof RewardSelectUiHandler, + ) as RewardSelectUiHandler; + expect(rewardSelectHandler.options.length).toEqual(3); + expect(rewardSelectHandler.options[0].rewardOption.type.id).toEqual(RewardId.MEMORY_MUSHROOM); + expect(rewardSelectHandler.options[1].rewardOption.tier).toEqual(RarityTier.MASTER); + }); +}); diff --git a/test/test-utils/game-manager.ts b/test/test-utils/game-manager.ts index 40ced8495d2..01e11d1f69f 100644 --- a/test/test-utils/game-manager.ts +++ b/test/test-utils/game-manager.ts @@ -3,7 +3,7 @@ import { BattleScene } from "#app/battle-scene"; import { getGameMode } from "#app/game-mode"; import { globalScene } from "#app/global-scene"; import overrides from "#app/overrides"; -import { modifierTypes } from "#data/data-lists"; +import { allRewards } from "#data/data-lists"; import { BattlerIndex } from "#enums/battler-index"; import { Button } from "#enums/buttons"; import { ExpGainsSpeed } from "#enums/exp-gains-speed"; @@ -16,7 +16,6 @@ import type { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import type { EnemyPokemon, PlayerPokemon } from "#field/pokemon"; import { Trainer } from "#field/trainer"; -import { ModifierTypeOption } from "#modifiers/modifier-type"; import { CheckSwitchPhase } from "#phases/check-switch-phase"; import { CommandPhase } from "#phases/command-phase"; import { EncounterPhase } from "#phases/encounter-phase"; @@ -315,7 +314,7 @@ export class GameManager { doSelectModifier() { this.onNextPrompt( "SelectRewardPhase", - UiMode.MODIFIER_SELECT, + UiMode.REWARD_SELECT, () => { const handler = this.scene.ui.getHandler() as RewardSelectUiHandler; handler.processInput(Button.CANCEL); @@ -481,9 +480,8 @@ export class GameManager { */ doRevivePokemon(pokemonIndex: number) { const party = this.scene.getPlayerParty(); - const candidate = new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0); - const modifier = candidate.type!.newModifier(party[pokemonIndex]); - this.scene.addModifier(modifier, false); + const reward = allRewards.MAX_REVIVE(); + reward.apply({ pokemon: party[pokemonIndex] }); } /** diff --git a/test/test-utils/helpers/modifiers-helper.ts b/test/test-utils/helpers/modifiers-helper.ts index 14ae6bcb61c..df0bd74f8ef 100644 --- a/test/test-utils/helpers/modifiers-helper.ts +++ b/test/test-utils/helpers/modifiers-helper.ts @@ -1,5 +1,5 @@ -import type { ModifierTypeKeys } from "#modifiers/modifier-type"; -import { itemPoolChecks } from "#modifiers/modifier-type"; +import type { RewardKeys } from "#items/reward"; +import { itemPoolChecks } from "#items/reward"; import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper"; import { expect } from "vitest"; @@ -11,7 +11,7 @@ export class ModifierHelper extends GameManagerHelper { * @param modifier The Modifier to add. * @returns `this` */ - addCheck(modifier: ModifierTypeKeys): this { + addCheck(modifier: RewardKeys): this { itemPoolChecks.set(modifier, undefined); return this; } @@ -27,7 +27,7 @@ export class ModifierHelper extends GameManagerHelper { * @param modifier * @returns */ - getCheck(modifier: ModifierTypeKeys): boolean | undefined { + getCheck(modifier: RewardKeys): boolean | undefined { return itemPoolChecks.get(modifier); } @@ -39,7 +39,7 @@ export class ModifierHelper extends GameManagerHelper { * @param expectToBePreset Whether the Modifier should be in the Modifier Pool. Set to `false` to expect it to be absent instead. * @returns `this` */ - testCheck(modifier: ModifierTypeKeys, expectToBePreset: boolean): this { + testCheck(modifier: RewardKeys, expectToBePreset: boolean): this { if (expectToBePreset) { expect(itemPoolChecks.get(modifier)).toBeTruthy(); } @@ -56,4 +56,4 @@ export class ModifierHelper extends GameManagerHelper { private log(...params: any[]) { console.log("Modifiers:", ...params); } -} +} \ No newline at end of file diff --git a/test/test-utils/helpers/overrides-helper.ts b/test/test-utils/helpers/overrides-helper.ts index f3aa771d033..0d2d39a7b49 100644 --- a/test/test-utils/helpers/overrides-helper.ts +++ b/test/test-utils/helpers/overrides-helper.ts @@ -18,8 +18,8 @@ import { StatusEffect } from "#enums/status-effect"; import type { Unlockables } from "#enums/unlockables"; import { WeatherType } from "#enums/weather-type"; import type { HeldItemConfiguration } from "#items/held-item-data-types"; +import type { RewardOverride } from "#items/reward"; import type { TrainerItemConfiguration } from "#items/trainer-item-data-types"; -import type { ModifierOverride } from "#modifiers/modifier-type"; import type { Variant } from "#sprites/variant"; import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper"; import { coerceArray, shiftCharCodes } from "#utils/common"; @@ -557,9 +557,9 @@ export class OverridesHelper extends GameManagerHelper { * @param items - The items to be rolled * @returns `this` */ - public itemRewards(items: ModifierOverride[]): this { - vi.spyOn(Overrides, "ITEM_REWARD_OVERRIDE", "get").mockReturnValue(items); - this.log("Item rewards set to:", items); + public itemRewards(items: RewardOverride[]): this { + vi.spyOn(Overrides, "REWARD_OVERRIDE", "get").mockReturnValue(items); + this.log("Item allRewards set to:", items); return this; } diff --git a/test/test-utils/test-file-initialization.ts b/test/test-utils/test-file-initialization.ts index 7979cc7243b..3dc107c653a 100644 --- a/test/test-utils/test-file-initialization.ts +++ b/test/test-utils/test-file-initialization.ts @@ -9,9 +9,9 @@ import { initSpecies } from "#data/pokemon-species"; import { initHeldItems } from "#items/all-held-items"; import { initTrainerItems } from "#items/all-trainer-items"; import { initHeldItemPools } from "#items/init-held-item-pools"; +import { initRewardPools } from "#items/init-reward-pools"; import { initTrainerItemPools } from "#items/init-trainer-item-pools"; -import { initModifierPools } from "#modifiers/init-modifier-pools"; -import { initModifierTypes } from "#modifiers/modifier-type"; +import { initRewards } from "#items/reward"; import { initMoves } from "#moves/move"; import { initMysteryEncounters } from "#mystery-encounters/mystery-encounters"; import { initI18n } from "#plugins/i18n"; @@ -98,8 +98,8 @@ export function initTestFile() { initHeldItemPools(); initTrainerItems(); initTrainerItemPools(); - initModifierTypes(); - initModifierPools(); + initRewards(); + initRewardPools(); initVouchers(); initAchievements(); initStatsKeys(); diff --git a/test/ui/transfer-item.test.ts b/test/ui/transfer-item.test.ts index 368895ccf0b..5b67c38a609 100644 --- a/test/ui/transfer-item.test.ts +++ b/test/ui/transfer-item.test.ts @@ -43,14 +43,14 @@ describe("UI - Transfer Items", () => { game.move.select(MoveId.DRAGON_CLAW); - game.onNextPrompt("SelectRewardPhase", UiMode.MODIFIER_SELECT, () => { + game.onNextPrompt("SelectRewardPhase", UiMode.REWARD_SELECT, () => { expect(game.scene.ui.getHandler()).toBeInstanceOf(RewardSelectUiHandler); const handler = game.scene.ui.getHandler() as RewardSelectUiHandler; handler.setCursor(1); handler.processInput(Button.ACTION); - void game.scene.ui.setModeWithoutClear(UiMode.PARTY, PartyUiMode.MODIFIER_TRANSFER); + void game.scene.ui.setModeWithoutClear(UiMode.PARTY, PartyUiMode.ITEM_TRANSFER); }); await game.phaseInterceptor.to("BattleEndPhase"); diff --git a/tsconfig.json b/tsconfig.json index c179ab22c8b..6e714ceaa79 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,6 @@ "#field/*": ["./field/*.ts"], "#inputs/*": ["./configs/inputs/*.ts"], "#items/*": ["./items/held-items/*.ts", "./items/*.ts"], - "#modifiers/*": ["./modifier/*.ts"], "#moves/*": ["./data/moves/*.ts"], "#mystery-encounters/*": [ "./data/mystery-encounters/utils/*.ts", From e50296d14ce270a50d2cd21b90d23bbbe0cb4cf4 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sun, 3 Aug 2025 18:08:37 -0400 Subject: [PATCH 37/39] Merged `upstream/beta` into Modifier rework (#6206) * [Dev] test:silent now passes --silent='passed-only' to Vitest (#6131) * [Dev] test:silent now passes --silent='passed-only' to Vitest * Update test shard to actually use `test-silent` * Removed obselete `project` flag * [Bug] Unblock priority spread under Psychic Terrain (#6136) Unblock priority spread under Psychic Terrain Co-authored-by: Acelynn Zhang * [Dev] Remove `sanitizeOverrides`, consolidate initialization code into 1 file https://github.com/pagefaultgames/pokerogue/pull/6134 * Removed `sanitizeOverrides` * Moved initialization code to its own file * Hopefully fixed test contamination * Actually listened to people now * fixed the thingy * Run stub setup on init because * Update testFileInitialization.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * [Misc] Sinistea and Poltchageist line alt forms now available (#4989) * Sinistea and Poltchageist line alt forms now available * Unmark Poltchageist line as unobtainable, fix sprite key of alt forms * Correct forms not being marked as starter selectable * Reduce wild chance for Antique/Masterpiece forms Instead of being 1/2 chance to get the Antique or Masterpiece forms, it is now only a 1/16 chance to get them. --------- Co-authored-by: damocleas * [Bug] Fix when variable move power is called (#6126) * Apply variable power attribute before type boost * Update test/abilities/normal-move-type-change.test.ts Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Minor test improvements --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * [UI/UX] [Bug] Fix `ModifierSelectPhase` animation delay (#6121) * Rework promise handling to ensure no races * Add delay to ensure pokeball opening animation can be seen * Remove leftover debug statements. Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Add tween bouncing pokeball to tweens that must complete for promise to resolve * Fix typo in tsdoc Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * [Test] `MoveHelper#changeMoveset` disables moveset overrides (#5915) Also fix Assist tests and add `expect` for max moveset length * [Refactor] Minor cleanup of `initExpKeys` (#6127) * [Bug] Fix Thrash continuing on caught Pokemon (#6144) Fix Thrash continuing on caught Pokemon * [UI/UX] Replace 'Neutral' in the Arena Flyout with 'Field' (#6139) Update arena-flyout.ts for Field > Neutral * [Dev] Added typedoc deployments for Beta (#6147) * [Misc] Fix import in decrypt-save.js (#6149) * [Refactor][Bug] Illusion no longer overwrites data of original Pokemon https://github.com/pagefaultgames/pokerogue/pull/6140 * [UI/UX] Added "Hide Username" Setting (#6105) * [UI/UX] Added "Hide Username" Setting * Mask tid with asterisk instead of hiding completely --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * [Dev] Add `dist/` to `ls-lint` ignore list * [i18n] Update Locales * [Beta] [Bug] Fix shiny display issues (#6154) Fix shininess check * [Bug][Beta] Make bounce delay use fixed int (#6156) Make bounce delay use fixed int * [Refactor] Refactor UI text ts (#5946) * Add destroy method to pokemon-sprite-sparkle-handler * Move TextStyle to enums, convert into const object * Cleanup text.ts file * Add necessary explicit types for TextStyle let vars * Fix locales submodule commit * Fix merge issue --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * [Test] Added custom equality matchers (#6157) * Added rudimentary test matchers for `toEqualArrayUnsorted` and `toHaveTypes` Might port the rest at a later date * Actually run the effing setup matchers file + fixed ls lint to not be angy * added dev dep * Update .ls-lint.yml * Update .ls-lint.yml --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> * [Misc] Fix import in `vitest.d.ts` * [Dev] Turned on `checkJs` in TSConfig; fixed script type errors (#6115) * [Misc] Moved + cleaned up string manipulation functions (#6112) * Added string utility package to replace util functions * Changed string manipulation functions fully over to `change-case` * Fixed missing comma in package.json trailing commas when :( * fixed lockfile * Hopefully re-added all the string utils * fixed package json * Fixed remaining cases of regex + code dupliation * Fixed more bugs and errors * Moved around functions and hopefully fixed the regex issues * Minor renaming * Fixed incorrect casing on setting strings pascal snake case :skull: * ran biome * [Refactor] Prevent serialization of full species in pokemon summon data https://github.com/pagefaultgames/pokerogue/pull/6145 * Prevent serialization of entire species form in pokemon summon data * Apply suggestions from code review Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Apply Kev's suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * [Docs] Add `@module` modifier tag to `tsdoc.json` * [Bug] Fix camel case bug in `strings.ts` (#6161) * [Dev] Change `target` to `ES2023` in `tsconfig.json` (#6160) * breakup fight and ball commands into their own methods * Breakup run and pokemon commands * Breakup commandPhase#start Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Minor touchups * Add overload for handle command * Fix improperly named computeMoveId method * Improve `canUse` computation * Explicitly check against Moves.NONE Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Update with Bertie's comments Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Fix imports * Apply kev's suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Improve documentation Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * [Balance][Challenge] Added expanded Fresh Start options (#6162) * [Dev] Add `workflow-dispatch` trigger to tests github workflow (#6152) Add `workflow-dispatch` trigger to github workflow Co-authored-by: damocleas * [Test] Add support for custom boilerplates to `create-test.js` (#6158) * Added support for custom boilerplates to test:create script * Added support for custom boilerplates to create-test.js * Fixed syntax error * Update create-test.js Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> * Fix pluralization error in `create-test.js` --------- Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> * [Refactor] Mark nickname in pokemon as optional (#6168) Mark nickname in pokemon as optional * [Dev] Update Vite from 6.3.5 to 7.0.6 (#6163) * [ME] [Bug] Disable trades in GTS ME with only 1 valid party member https://github.com/pagefaultgames/pokerogue/pull/6167 * [Refactor] Minor refactor of battler tags (#6129) * Minor refactor battler tags * Improve documentation * Update type when loading in pokemon-data constructor for battler tags * Fix issues in tsdoc comments with Wlowscha's suggestions Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> * Apply bertie's suggestions from code review Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Remove unnecessary as const from tagType * Remove missed `as const` * Apply kev's suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/data/battler-tags.ts Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Update src/data/battler-tags.ts Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --------- Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * [Docs] Adjust `@module` doc comment in `battler-tags.ts` Note that currently Typedoc is not parsing `@module` docs, but this comment adjustment would be required if and when it gets fixed * [Balance] End of turn triggers won't occur when you end a biome (#6169) * [Balance] End of turn triggers won't occur when you end a biome * Add tests * Move phase manipulation logic into `PhaseManager` * Rename "biome end" to "interlude" * Rename `TurnEndPhase#endOfBiome` to `upcomingInterlude` --------- Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> * [Test] Add missing single battle override to `CheckInterludePhase` test * [UI/UX] Fix button and input field overlaps (#6013) * [Fix] Fix button overlap * [Fix] Fix input field overlaps * use getWidth to determine if label should be shortened --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Co-authored-by: damocleas * [Dev] Moved type helpers to separate directory; (#6123) * [Dev] Moved type helpers to separate directory; renamed `EnumValues` to `ObjectValues` and enforced usage * Update tsconfig.json Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Fixed import issue * Updated documentation slightly --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com> * [Misc] Improve type signatures of serialized arena/battler tags (#6180) * Improve type signatures of serialized arena/battler tags * Minor adjustments to tsdocs from code review Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --------- Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * [Balance] Updated SF/Triage interactions for moves (#6179) * Fixed move flags * Disabled order up interactionn with sheer force * Update src/data/moves/move.ts * Removed order up test that no longer applies shouldn't have been there in the first place * [Move Bug] Fully implemented Future Sight, Doom Desire; fixed Wish Double battle oversight (#5862) * Mostly implemented Future Sight/Doom Desire * Fixed a few docs * Fixed com * Update magic_guard.test.ts * Update documentation * Update documentation on arena-tag.ts * Update arena-tag.ts docs * Update arena-tag.ts * Update turn-end-phase.ts * Update move.ts documentation * Fixed tpyo * Update move.ts documentation * Add assorted TODO test cases * Refactored FS to use a positional tag manager * Added strong typing to the manager, finished save load stufff * Fixed locales + tests * Fixed tests and documentation * sh Fixed tests for good * Fixed MEP * Reverted overrides changse * Fixed issues with merging * Fixed locales update & heal block test * Fixed wish tests * Fixed test typo * Fixed wish test flaking out due to speed ties * Fixed tests fr fr * actually fixed tests bc i'm stupid * Fixed tests for real * Remove locales update * Update arena-tag.ts Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com> * Update move.ts Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com> * Update arena-tag.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Applied review suggestions and added a _wee_ bit more docs * fixed wish condition * Applied kev's reviews * Minor nits * Minor formatting change in `heal-block.test.ts` --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * [Refactor] Added `PhaseManager.toTitleScreen` (#6114) * Added `toTitlePhase`; documented phase manager * Documented phase methods * Fixed syntax errors + updated signature * Reverted all the goodies * Fixed missing shift phase call GHAG * Reverted change * [UI/UX] Implement Discard Button (#5985) * [feature]Implemented needed parts for discard function from issue #4780: -TryDiscardFunction in battlescene; -Created a party discard mode button; -Updated Transfer button in modifier-select-ui-handler to Manage items; -Created tests for the discard function in test/ui; -Added images for the new discard and transfer buttons to loading-scene; -Created placeholder messages for discard feature in party-ui-handler; Co-authored-by: Tiago Rodrigues * [Fix] Updated icon for dynamic messaging * [Fix] Corrected legacy mode icons and adjusted double-battle button location * [Fix]Adjusted button positioning and mapping after review. Mapping requires debugging. * [Fix] Fixed visible pokeball in legacy mode and key mapping * [Fix] Background fixes,manage menu is the only one affected by changes now * Implement i18n keys * [Fix] implemented most code optimizations and callbacks to the modified locales folder * [Fix] Implemented 3 suggestions * [Fix]improved/corrected test structure Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * [Fix] added functionality test for the discard button * [Fix] added necessary comment Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * [Fix] Implemented suggested changes in test/discard text prompt * [Fix] Implemented UI suggestions and removed discard text confirmation * [Fix] added missing imports * Fix imports in test file * [Fix] Implemented suggested cursor behavior and reworked test code * [Fix] Corrected failed test * [Fix] atempting to fix the test timeout issue * [Fix] Undoing latest attempt * [Fix] Implemented suggestions to fix broken tests * Reviews * [Fix] replaced icon images * [Fix] Updated jsons to match new icons and removed pokeball icon from legacy mode * Optimized new images * [Fix] Fixed referenced bug and added similar confirmation box to release * [Fix] Updated tests to handle the corfirmation box * [Fix] Added back the accidentally removed changes * [Fix]updated incorrect import path * [fix] add description for the manageItemMode function Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Update src/ui/party-ui-handler.ts Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * [Fix] corrected formating issue --------- Co-authored-by: Mikhail Shueb Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> Co-authored-by: damocleas Co-authored-by: Bertie690 Co-authored-by: Adri1 * [Bug] Fix Truant behavior (#6171) * Update locales * [i18n] Add Tagalog language support (#6170) * [Bug] [Beta] Fix serializable battler tags (#6183) Fix serializable battler tags * [Misc] [Beta] Fix serializable battler tags (#6184) Fix serializable battler tags * [Misc] Disallow using NonFunctionProperties in loadTag methods (#6185) Disallow using NonFunctionProperties in loadTag methods * [Dev] `pnpm biome` will now only display errors by default (#6187) * [Sprite] Move Clauncher, Clawitzer, Skiddo, Fomantis, Lurantis animations to consistent (#6177) * Move Clauncher, Clawitzer, Skiddo out of exp * Move Fomantis, Lurantis out of exp * [Misc] Fix missing types in battler tags and remove DragonCheerTag (#6188) * Fix missing types in battler tags * Fix issues with dragon cheer * [Misc] Set default for crit stage battler tag (#6192) Set default for crit stage * [Bug] [Beta] Remove setting currentPhase to null in clearAllPhases (#6193) Remove setting currentPhase to null in clearAllPhases * [Test] `game.move.use` overrides summon data moveset if present (#6174) * [Test] Update `game.move.use` to override summon data moveset if present * ran biome & fixed errors * [Beta] Remove non-Legend Fresh Start option, fix starting movesets https://github.com/pagefaultgames/pokerogue/pull/6196 * Remove non-Legend fresh start option, fix starting movesets * Move updateInfo call * Fix relearns counting for starter moves * Reduce array allocations and function calls * [Misc] Move asset initialization from `preload` to `launchBattle` https://github.com/pagefaultgames/pokerogue/pull/6109 * Move asset initialization into `preload` instead of `launchBattle` * Fix init problems; remove unused `waitUntil` util function * Fixed missing `clearAllPhases` call * [Balance] Adjust Cosmog / Cosmoem Evolutions, Lower Buneary Friendship requirements (#6198) * Lower Buneary Friendship Requirement and Change Cosmog method * Update biomes.ts - Cosmog/Cosmoem wild evo levels * fix ? * fixed now * Update Cosmog Evolution + Biome Levels --------- Co-authored-by: damocleas * [Test] Port over + augment remaining test matchers from pkty (#6159) * Partially ported over pkty matchers (WIP) * Cleaned up some more matchers * Fiexd up matchers * Fixed up remaining matchers * Removed the word "matcher" from the pkty matcher functions If we want them back we can always undo this commit and convert the other custom ones * Added wip spite test * Added `toHaveUsedPP` matcher * Fixed up docs and tests * Fixed spite test * Ran biome * Apply Biome * Reverted biome breaking i18next * Update src/typings/i18next.d.ts comment * Fixed log message to not be overly verbose * Added option to check for all PP used in pp matcher + cleaned up grudge tests * Fixed up tests * Fixed tests and such * Fix various TSDocs + missing TSDoc imports * [Beta] Further Cosmog Evolution Readjustments (#6203) Update pokemon-evolutions.ts * [Dev] Move various functions out of `pokemon-species.ts` (#6155) `initSpecies` moved to its own file, other functions moved to `pokemon-utils.ts` * [Balance] Prevent MEs on X1 Waves (#6204) * [Balance] Prevent MEs on X1 Waves * Fix Bug-Type Superfan Encounter Test * Fix Unit Tests * [Balance] Update and Change Breeder Trainer Class Teams (#6200) * Update Breeder Trainer Class * Update trainer-config.ts * Update trainer-config.ts * Update whirlwind.test.ts * Update src/data/trainers/trainer-config.ts Co-authored-by: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> * Adjust Party Templates and move Wynaut * Update trainer-party-template.ts --------- Co-authored-by: damocleas Co-authored-by: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> --------- Co-authored-by: Acelynn Zhang <102631387+acelynnzhang@users.noreply.github.com> Co-authored-by: Acelynn Zhang Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> Co-authored-by: damocleas Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Co-authored-by: SmhMyHead <191356399+SmhMyHead@users.noreply.github.com> Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> Co-authored-by: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Co-authored-by: fabske0 <192151969+fabske0@users.noreply.github.com> Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com> Co-authored-by: Tiago Rodrigues Co-authored-by: Mikhail Shueb Co-authored-by: Adri1 Co-authored-by: Blitzy <118096277+Blitz425@users.noreply.github.com> --- .github/workflows/github-pages.yml | 1 + .github/workflows/test-shard-template.yml | 2 +- .github/workflows/tests.yml | 1 + .ls-lint.yml | 5 +- biome.jsonc | 8 +- global.d.ts | 10 +- package.json | 13 +- pnpm-lock.yaml | 921 +++--- public/exp-sprites.json | 42 - public/images/pokemon/672.json | 518 +++- public/images/pokemon/672.png | Bin 645 -> 3337 bytes public/images/pokemon/692.json | 774 +++++ public/images/pokemon/693.json | 941 +++++- public/images/pokemon/693.png | Bin 1088 -> 25840 bytes public/images/pokemon/753.json | 2559 +++++++++++++++- public/images/pokemon/753.png | Bin 476 -> 2090 bytes public/images/pokemon/754.json | 1106 ++++++- public/images/pokemon/754.png | Bin 789 -> 3754 bytes public/images/pokemon/back/672.json | 518 +++- public/images/pokemon/back/672.png | Bin 599 -> 3350 bytes public/images/pokemon/back/692.json | 774 +++++ public/images/pokemon/back/693.json | 941 +++++- public/images/pokemon/back/693.png | Bin 982 -> 21703 bytes public/images/pokemon/back/753.json | 2561 +++++++++++++++- public/images/pokemon/back/753.png | Bin 441 -> 2062 bytes public/images/pokemon/back/754.json | 1085 ++++++- public/images/pokemon/back/754.png | Bin 689 -> 3640 bytes public/images/pokemon/back/shiny/672.json | 518 +++- public/images/pokemon/back/shiny/672.png | Bin 599 -> 3349 bytes public/images/pokemon/back/shiny/692.json | 833 +++++- public/images/pokemon/back/shiny/692.png | Bin 478 -> 2025 bytes public/images/pokemon/back/shiny/693.json | 941 +++++- public/images/pokemon/back/shiny/693.png | Bin 1029 -> 21707 bytes public/images/pokemon/back/shiny/753.json | 2561 +++++++++++++++- public/images/pokemon/back/shiny/753.png | Bin 441 -> 2061 bytes public/images/pokemon/back/shiny/754.json | 1106 ++++++- public/images/pokemon/back/shiny/754.png | Bin 690 -> 3640 bytes public/images/pokemon/exp/672.json | 479 --- public/images/pokemon/exp/672.png | Bin 3333 -> 0 bytes public/images/pokemon/exp/692.json | 794 ----- public/images/pokemon/exp/692.png | Bin 2580 -> 0 bytes public/images/pokemon/exp/693.json | 902 ------ public/images/pokemon/exp/693.png | Bin 25840 -> 0 bytes public/images/pokemon/exp/753.json | 2582 ----------------- public/images/pokemon/exp/753.png | Bin 2090 -> 0 bytes public/images/pokemon/exp/754.json | 1133 -------- public/images/pokemon/exp/754.png | Bin 3754 -> 0 bytes public/images/pokemon/exp/back/672.json | 479 --- public/images/pokemon/exp/back/672.png | Bin 3350 -> 0 bytes public/images/pokemon/exp/back/692.json | 794 ----- public/images/pokemon/exp/back/692.png | Bin 2025 -> 0 bytes public/images/pokemon/exp/back/693.json | 902 ------ public/images/pokemon/exp/back/693.png | Bin 21703 -> 0 bytes public/images/pokemon/exp/back/753.json | 2582 ----------------- public/images/pokemon/exp/back/753.png | Bin 2062 -> 0 bytes public/images/pokemon/exp/back/754.json | 1112 ------- public/images/pokemon/exp/back/754.png | Bin 3640 -> 0 bytes public/images/pokemon/exp/back/shiny/672.json | 479 --- public/images/pokemon/exp/back/shiny/672.png | Bin 3349 -> 0 bytes public/images/pokemon/exp/back/shiny/692.json | 794 ----- public/images/pokemon/exp/back/shiny/692.png | Bin 2025 -> 0 bytes public/images/pokemon/exp/back/shiny/693.json | 902 ------ public/images/pokemon/exp/back/shiny/693.png | Bin 21707 -> 0 bytes public/images/pokemon/exp/back/shiny/753.json | 2582 ----------------- public/images/pokemon/exp/back/shiny/753.png | Bin 2061 -> 0 bytes public/images/pokemon/exp/back/shiny/754.json | 1133 -------- public/images/pokemon/exp/back/shiny/754.png | Bin 3640 -> 0 bytes public/images/pokemon/exp/shiny/672.json | 479 --- public/images/pokemon/exp/shiny/672.png | Bin 3333 -> 0 bytes public/images/pokemon/exp/shiny/692.json | 794 ----- public/images/pokemon/exp/shiny/692.png | Bin 2580 -> 0 bytes public/images/pokemon/exp/shiny/693.json | 902 ------ public/images/pokemon/exp/shiny/693.png | Bin 25899 -> 0 bytes public/images/pokemon/exp/shiny/753.json | 2582 ----------------- public/images/pokemon/exp/shiny/753.png | Bin 2090 -> 0 bytes public/images/pokemon/exp/shiny/754.json | 1133 -------- public/images/pokemon/exp/shiny/754.png | Bin 3754 -> 0 bytes public/images/pokemon/shiny/672.json | 518 +++- public/images/pokemon/shiny/672.png | Bin 645 -> 3337 bytes public/images/pokemon/shiny/692.json | 833 +++++- public/images/pokemon/shiny/692.png | Bin 509 -> 2580 bytes public/images/pokemon/shiny/693.json | 941 +++++- public/images/pokemon/shiny/693.png | Bin 1144 -> 25899 bytes public/images/pokemon/shiny/753.json | 2559 +++++++++++++++- public/images/pokemon/shiny/753.png | Bin 471 -> 2090 bytes public/images/pokemon/shiny/754.json | 1106 ++++++- public/images/pokemon/shiny/754.png | Bin 812 -> 3754 bytes public/images/pokemon/variant/672.json | 15 + public/images/pokemon/variant/672_3.json | 41 - public/images/pokemon/variant/672_3.png | Bin 672 -> 0 bytes .../pokemon/variant/_exp_masterlist.json | 10 - .../images/pokemon/variant/_masterlist.json | 4 +- public/images/pokemon/variant/back/753.json | 28 +- .../pokemon/variant/{exp => back}/754.json | 0 public/images/pokemon/variant/back/754_2.json | 41 - public/images/pokemon/variant/back/754_2.png | Bin 703 -> 0 bytes public/images/pokemon/variant/back/754_3.json | 41 - public/images/pokemon/variant/back/754_3.png | Bin 700 -> 0 bytes public/images/pokemon/variant/exp/672.json | 32 - public/images/pokemon/variant/exp/692.json | 26 - public/images/pokemon/variant/exp/693.json | 30 - public/images/pokemon/variant/exp/753.json | 32 - public/images/pokemon/variant/exp/754_2.json | 1133 -------- public/images/pokemon/variant/exp/754_2.png | Bin 4040 -> 0 bytes public/images/pokemon/variant/exp/754_3.json | 1133 -------- public/images/pokemon/variant/exp/754_3.png | Bin 4040 -> 0 bytes .../images/pokemon/variant/exp/back/672.json | 32 - .../images/pokemon/variant/exp/back/692.json | 28 - .../images/pokemon/variant/exp/back/693.json | 28 - .../images/pokemon/variant/exp/back/753.json | 26 - .../images/pokemon/variant/exp/back/754.json | 14 - .../pokemon/variant/exp/back/754_2.json | 1112 ------- .../images/pokemon/variant/exp/back/754_2.png | Bin 3646 -> 0 bytes .../pokemon/variant/exp/back/754_3.json | 1112 ------- .../images/pokemon/variant/exp/back/754_3.png | Bin 3725 -> 0 bytes public/images/statuses_tl.json | 188 ++ public/images/statuses_tl.png | Bin 0 -> 419 bytes public/images/types_tl.json | 440 +++ public/images/types_tl.png | Bin 0 -> 1861 bytes .../ui/legacy/party_bg_double_manage.png | Bin 0 -> 431 bytes public/images/ui/legacy/party_discard.json | 62 + public/images/ui/legacy/party_discard.png | Bin 0 -> 346 bytes public/images/ui/legacy/party_transfer.json | 62 + public/images/ui/legacy/party_transfer.png | Bin 0 -> 366 bytes public/images/ui/party_bg_double_manage.png | Bin 0 -> 837 bytes public/images/ui/party_discard.json | 62 + public/images/ui/party_discard.png | Bin 0 -> 386 bytes public/images/ui/party_transfer.json | 62 + public/images/ui/party_transfer.png | Bin 0 -> 403 bytes public/locales | 2 +- .../default.ts} | 0 scripts/create-test/create-test.js | 121 +- scripts/decrypt-save.js | 6 +- src/@types/arena-tags.ts | 12 +- src/@types/battler-tags.ts | 137 + src/@types/{ => helpers}/enum-types.ts | 10 +- src/@types/{ => helpers}/type-helpers.ts | 38 +- src/@types/illusion-data.ts | 26 +- src/@types/phase-types.ts | 3 +- src/@types/ui.ts | 10 + src/battle-scene.ts | 140 +- src/data/abilities/ability.ts | 10 +- src/data/arena-tag.ts | 210 +- src/data/balance/biomes.ts | 11 +- src/data/balance/egg-moves.ts | 4 +- src/data/balance/pokemon-evolutions.ts | 8 +- src/data/balance/pokemon-species.ts | 1784 ++++++++++++ src/data/battle-anims.ts | 15 +- src/data/battler-tags.ts | 979 ++++--- src/data/challenge.ts | 150 +- src/data/daily-run.ts | 4 +- src/data/dialogue.ts | 4 +- src/data/moves/move-utils.ts | 22 + src/data/moves/move.ts | 187 +- .../global-trade-system-encounter.ts | 7 +- .../teleporting-hijinks-encounter.ts | 2 +- .../mystery-encounter-dialogue.ts | 2 +- .../mystery-encounters/mystery-encounter.ts | 3 +- .../utils/encounter-dialogue-utils.ts | 2 +- src/data/nature.ts | 7 +- src/data/pokemon-species.ts | 1905 +----------- src/data/pokemon/pokemon-data.ts | 142 +- .../positional-tags/load-positional-tag.ts | 70 + .../positional-tags/positional-tag-manager.ts | 55 + src/data/positional-tags/positional-tag.ts | 174 ++ src/data/terrain.ts | 21 +- src/data/trainer-names.ts | 4 +- src/data/trainers/trainer-config.ts | 68 +- src/data/trainers/trainer-party-template.ts | 1 + src/enums/ability-attr.ts | 4 +- src/enums/arena-tag-type.ts | 3 - src/enums/battler-tag-type.ts | 1 - src/enums/dex-attr.ts | 4 +- src/enums/dynamic-phase-type.ts | 3 +- src/enums/gacha-types.ts | 4 +- src/enums/hit-check-result.ts | 4 +- src/enums/move-flags.ts | 14 +- src/enums/move-use-mode.ts | 79 +- src/enums/positional-tag-type.ts | 10 + src/enums/text-style.ts | 59 + src/field/arena.ts | 51 +- src/field/damage-number-handler.ts | 3 +- src/field/pokemon-sprite-sparkle-handler.ts | 13 +- src/field/pokemon.ts | 271 +- src/field/trainer.ts | 8 +- src/init/init.ts | 50 + src/loading-scene.ts | 49 +- src/phase-manager.ts | 31 +- src/phases/attempt-capture-phase.ts | 1 + src/phases/check-interlude-phase.ts | 18 + src/phases/command-phase.ts | 925 +++--- src/phases/end-card-phase.ts | 3 +- src/phases/evolution-phase.ts | 2 +- src/phases/move-effect-phase.ts | 33 +- src/phases/move-phase.ts | 17 - src/phases/positional-tag-phase.ts | 21 + src/phases/scan-ivs-phase.ts | 3 +- src/phases/select-starter-phase.ts | 13 +- src/phases/title-phase.ts | 11 +- src/phases/turn-end-phase.ts | 8 +- src/phases/turn-start-phase.ts | 13 +- src/plugins/i18n.ts | 13 +- src/system/achv.ts | 2 +- src/system/arena-data.ts | 7 + src/system/game-data.ts | 9 +- src/system/pokemon-data.ts | 13 +- src/system/settings/settings.ts | 15 + .../version-migration/versions/v1_7_0.ts | 3 +- src/timed-event-manager.ts | 3 +- src/typings/i18next.d.ts | 1 + src/typings/phaser/index.d.ts | 42 +- src/ui/ability-bar.ts | 3 +- src/ui/abstact-option-select-ui-handler.ts | 3 +- src/ui/achv-bar.ts | 3 +- src/ui/achvs-ui-handler.ts | 3 +- src/ui/admin-ui-handler.ts | 8 +- src/ui/arena-flyout.ts | 16 +- src/ui/ball-ui-handler.ts | 3 +- src/ui/base-stats-overlay.ts | 3 +- src/ui/battle-flyout.ts | 3 +- src/ui/battle-info/battle-info.ts | 3 +- src/ui/battle-info/enemy-battle-info.ts | 3 +- src/ui/battle-message-ui-handler.ts | 3 +- src/ui/bgm-bar.ts | 7 +- src/ui/candy-bar.ts | 3 +- src/ui/challenges-select-ui-handler.ts | 6 +- src/ui/command-ui-handler.ts | 3 +- src/ui/daily-run-scoreboard.ts | 3 +- src/ui/dropdown.ts | 3 +- src/ui/egg-counter-container.ts | 3 +- src/ui/egg-gacha-ui-handler.ts | 5 +- src/ui/egg-list-ui-handler.ts | 3 +- src/ui/evolution-scene-handler.ts | 3 +- src/ui/fight-ui-handler.ts | 5 +- src/ui/filter-bar.ts | 3 +- src/ui/filter-text.ts | 3 +- src/ui/form-modal-ui-handler.ts | 23 +- src/ui/game-stats-ui-handler.ts | 12 +- src/ui/loading-modal-ui-handler.ts | 3 +- src/ui/login-form-ui-handler.ts | 3 +- src/ui/menu-ui-handler.ts | 3 +- src/ui/modal-ui-handler.ts | 22 +- src/ui/move-info-overlay.ts | 3 +- src/ui/mystery-encounter-ui-handler.ts | 3 +- src/ui/party-exp-bar.ts | 3 +- src/ui/party-ui-handler.ts | 328 ++- src/ui/pokedex-info-overlay.ts | 3 +- src/ui/pokedex-mon-container.ts | 3 +- src/ui/pokedex-page-ui-handler.ts | 19 +- src/ui/pokedex-scan-ui-handler.ts | 7 +- src/ui/pokedex-ui-handler.ts | 6 +- src/ui/pokemon-hatch-info-container.ts | 5 +- src/ui/pokemon-info-container.ts | 3 +- src/ui/registration-form-ui-handler.ts | 35 +- src/ui/reward-select-ui-handler.ts | 203 +- src/ui/run-history-ui-handler.ts | 3 +- src/ui/run-info-ui-handler.ts | 3 +- src/ui/save-slot-select-ui-handler.ts | 3 +- src/ui/session-reload-modal-ui-handler.ts | 3 +- .../settings/abstract-binding-ui-handler.ts | 3 +- .../abstract-control-settings-ui-handler.ts | 17 +- .../settings/abstract-settings-ui-handler.ts | 3 +- src/ui/settings/gamepad-binding-ui-handler.ts | 3 +- .../settings/keyboard-binding-ui-handler.ts | 3 +- src/ui/settings/navigation-menu.ts | 3 +- .../settings/settings-display-ui-handler.ts | 6 + .../settings/settings-gamepad-ui-handler.ts | 3 +- .../settings/settings-keyboard-ui-handler.ts | 8 +- src/ui/starter-container.ts | 3 +- src/ui/starter-select-ui-handler.ts | 31 +- src/ui/stats-container.ts | 3 +- src/ui/summary-ui-handler.ts | 54 +- src/ui/text.ts | 82 +- src/ui/title-ui-handler.ts | 3 +- src/ui/ui-handler.ts | 2 +- src/ui/ui.ts | 3 +- src/ui/unavailable-modal-ui-handler.ts | 3 +- src/utils/common.ts | 106 +- src/utils/enums.ts | 6 +- src/utils/pokemon-utils.ts | 106 +- src/utils/strings.ts | 181 ++ src/vite.env.d.ts | 18 +- test/@types/vitest.d.ts | 139 + test/abilities/illusion.test.ts | 4 +- .../abilities/normal-move-type-change.test.ts | 23 +- test/abilities/normalize.test.ts | 12 + test/abilities/truant.test.ts | 72 + test/arena/psychic-terrain.test.ts | 59 + test/matchers.setup.ts | 39 + test/misc.test.ts | 29 - test/moves/assist.test.ts | 5 +- test/moves/delayed-attack.test.ts | 389 +++ test/moves/future-sight.test.ts | 45 - test/moves/grudge.test.ts | 71 +- test/moves/heal-block.test.ts | 26 +- test/moves/order-up.test.ts | 19 - test/moves/spite.test.ts | 126 + test/moves/whirlwind.test.ts | 2 +- test/moves/wish.test.ts | 183 ++ .../berries-abound-encounter.test.ts | 2 +- .../bug-type-superfan-encounter.test.ts | 12 +- .../global-trade-system-encounter.test.ts | 4 +- .../teleporting-hijinks-encounter.test.ts | 20 +- .../mystery-encounter.test.ts | 12 +- test/phases/capture-phase.test.ts | 37 + test/phases/check-interlude-phase.test.ts | 63 + test/phases/mystery-encounter-phase.test.ts | 2 +- test/setting-menu/helpers/in-game-manip.ts | 14 +- test/setting-menu/helpers/menu-manip.ts | 1 + test/test-utils/game-manager-utils.ts | 14 +- test/test-utils/game-manager.ts | 77 +- test/test-utils/helpers/field-helper.ts | 12 +- test/test-utils/helpers/move-helper.ts | 72 +- test/test-utils/helpers/overrides-helper.ts | 14 +- .../matchers/to-equal-array-unsorted.ts | 47 + .../matchers/to-have-ability-applied.ts | 43 + .../matchers/to-have-battler-tag.ts | 43 + .../matchers/to-have-effective-stat.ts | 66 + test/test-utils/matchers/to-have-fainted.ts | 35 + test/test-utils/matchers/to-have-full-hp.ts | 35 + test/test-utils/matchers/to-have-hp.ts | 35 + .../test-utils/matchers/to-have-stat-stage.ts | 53 + .../matchers/to-have-status-effect.ts | 83 + .../matchers/to-have-taken-damage.ts | 46 + test/test-utils/matchers/to-have-terrain.ts | 62 + test/test-utils/matchers/to-have-types.ts | 61 + test/test-utils/matchers/to-have-used-move.ts | 70 + test/test-utils/matchers/to-have-used-pp.ts | 77 + test/test-utils/matchers/to-have-weather.ts | 62 + test/test-utils/phase-interceptor.ts | 2 + test/test-utils/string-utils.ts | 183 ++ test/test-utils/test-file-initialization.ts | 86 +- test/test-utils/test-utils.ts | 53 + test/types/enum-types.test-d.ts | 53 +- test/types/positional-tags.test-d.ts | 29 + test/types/type-helpers.test-d.ts | 38 + test/ui/item-manage-button.test.ts | 172 ++ test/utils/strings.test.ts | 47 + test/vitest.setup.ts | 4 +- tsconfig.json | 11 +- tsdoc.json | 4 + vitest.config.ts | 5 +- 342 files changed, 33022 insertions(+), 33757 deletions(-) delete mode 100644 public/images/pokemon/exp/672.json delete mode 100644 public/images/pokemon/exp/672.png delete mode 100644 public/images/pokemon/exp/692.json delete mode 100644 public/images/pokemon/exp/692.png delete mode 100644 public/images/pokemon/exp/693.json delete mode 100644 public/images/pokemon/exp/693.png delete mode 100644 public/images/pokemon/exp/753.json delete mode 100644 public/images/pokemon/exp/753.png delete mode 100644 public/images/pokemon/exp/754.json delete mode 100644 public/images/pokemon/exp/754.png delete mode 100644 public/images/pokemon/exp/back/672.json delete mode 100644 public/images/pokemon/exp/back/672.png delete mode 100644 public/images/pokemon/exp/back/692.json delete mode 100644 public/images/pokemon/exp/back/692.png delete mode 100644 public/images/pokemon/exp/back/693.json delete mode 100644 public/images/pokemon/exp/back/693.png delete mode 100644 public/images/pokemon/exp/back/753.json delete mode 100644 public/images/pokemon/exp/back/753.png delete mode 100644 public/images/pokemon/exp/back/754.json delete mode 100644 public/images/pokemon/exp/back/754.png delete mode 100644 public/images/pokemon/exp/back/shiny/672.json delete mode 100644 public/images/pokemon/exp/back/shiny/672.png delete mode 100644 public/images/pokemon/exp/back/shiny/692.json delete mode 100644 public/images/pokemon/exp/back/shiny/692.png delete mode 100644 public/images/pokemon/exp/back/shiny/693.json delete mode 100644 public/images/pokemon/exp/back/shiny/693.png delete mode 100644 public/images/pokemon/exp/back/shiny/753.json delete mode 100644 public/images/pokemon/exp/back/shiny/753.png delete mode 100644 public/images/pokemon/exp/back/shiny/754.json delete mode 100644 public/images/pokemon/exp/back/shiny/754.png delete mode 100644 public/images/pokemon/exp/shiny/672.json delete mode 100644 public/images/pokemon/exp/shiny/672.png delete mode 100644 public/images/pokemon/exp/shiny/692.json delete mode 100644 public/images/pokemon/exp/shiny/692.png delete mode 100644 public/images/pokemon/exp/shiny/693.json delete mode 100644 public/images/pokemon/exp/shiny/693.png delete mode 100644 public/images/pokemon/exp/shiny/753.json delete mode 100644 public/images/pokemon/exp/shiny/753.png delete mode 100644 public/images/pokemon/exp/shiny/754.json delete mode 100644 public/images/pokemon/exp/shiny/754.png delete mode 100644 public/images/pokemon/variant/672_3.json delete mode 100644 public/images/pokemon/variant/672_3.png rename public/images/pokemon/variant/{exp => back}/754.json (100%) delete mode 100644 public/images/pokemon/variant/back/754_2.json delete mode 100644 public/images/pokemon/variant/back/754_2.png delete mode 100644 public/images/pokemon/variant/back/754_3.json delete mode 100644 public/images/pokemon/variant/back/754_3.png delete mode 100644 public/images/pokemon/variant/exp/672.json delete mode 100644 public/images/pokemon/variant/exp/692.json delete mode 100644 public/images/pokemon/variant/exp/693.json delete mode 100644 public/images/pokemon/variant/exp/753.json delete mode 100644 public/images/pokemon/variant/exp/754_2.json delete mode 100644 public/images/pokemon/variant/exp/754_2.png delete mode 100644 public/images/pokemon/variant/exp/754_3.json delete mode 100644 public/images/pokemon/variant/exp/754_3.png delete mode 100644 public/images/pokemon/variant/exp/back/672.json delete mode 100644 public/images/pokemon/variant/exp/back/692.json delete mode 100644 public/images/pokemon/variant/exp/back/693.json delete mode 100644 public/images/pokemon/variant/exp/back/753.json delete mode 100644 public/images/pokemon/variant/exp/back/754.json delete mode 100644 public/images/pokemon/variant/exp/back/754_2.json delete mode 100644 public/images/pokemon/variant/exp/back/754_2.png delete mode 100644 public/images/pokemon/variant/exp/back/754_3.json delete mode 100644 public/images/pokemon/variant/exp/back/754_3.png create mode 100644 public/images/statuses_tl.json create mode 100644 public/images/statuses_tl.png create mode 100644 public/images/types_tl.json create mode 100644 public/images/types_tl.png create mode 100644 public/images/ui/legacy/party_bg_double_manage.png create mode 100644 public/images/ui/legacy/party_discard.json create mode 100644 public/images/ui/legacy/party_discard.png create mode 100644 public/images/ui/legacy/party_transfer.json create mode 100644 public/images/ui/legacy/party_transfer.png create mode 100644 public/images/ui/party_bg_double_manage.png create mode 100644 public/images/ui/party_discard.json create mode 100644 public/images/ui/party_discard.png create mode 100644 public/images/ui/party_transfer.json create mode 100644 public/images/ui/party_transfer.png rename scripts/create-test/{test-boilerplate.ts => boilerplates/default.ts} (100%) create mode 100644 src/@types/battler-tags.ts rename src/@types/{ => helpers}/enum-types.ts (68%) rename src/@types/{ => helpers}/type-helpers.ts (61%) create mode 100644 src/@types/ui.ts create mode 100644 src/data/balance/pokemon-species.ts create mode 100644 src/data/positional-tags/load-positional-tag.ts create mode 100644 src/data/positional-tags/positional-tag-manager.ts create mode 100644 src/data/positional-tags/positional-tag.ts create mode 100644 src/enums/positional-tag-type.ts create mode 100644 src/enums/text-style.ts create mode 100644 src/init/init.ts create mode 100644 src/phases/check-interlude-phase.ts create mode 100644 src/phases/positional-tag-phase.ts create mode 100644 src/utils/strings.ts create mode 100644 test/@types/vitest.d.ts create mode 100644 test/abilities/truant.test.ts create mode 100644 test/arena/psychic-terrain.test.ts create mode 100644 test/matchers.setup.ts create mode 100644 test/moves/delayed-attack.test.ts delete mode 100644 test/moves/future-sight.test.ts create mode 100644 test/moves/spite.test.ts create mode 100644 test/moves/wish.test.ts create mode 100644 test/phases/capture-phase.test.ts create mode 100644 test/phases/check-interlude-phase.test.ts create mode 100644 test/test-utils/matchers/to-equal-array-unsorted.ts create mode 100644 test/test-utils/matchers/to-have-ability-applied.ts create mode 100644 test/test-utils/matchers/to-have-battler-tag.ts create mode 100644 test/test-utils/matchers/to-have-effective-stat.ts create mode 100644 test/test-utils/matchers/to-have-fainted.ts create mode 100644 test/test-utils/matchers/to-have-full-hp.ts create mode 100644 test/test-utils/matchers/to-have-hp.ts create mode 100644 test/test-utils/matchers/to-have-stat-stage.ts create mode 100644 test/test-utils/matchers/to-have-status-effect.ts create mode 100644 test/test-utils/matchers/to-have-taken-damage.ts create mode 100644 test/test-utils/matchers/to-have-terrain.ts create mode 100644 test/test-utils/matchers/to-have-types.ts create mode 100644 test/test-utils/matchers/to-have-used-move.ts create mode 100644 test/test-utils/matchers/to-have-used-pp.ts create mode 100644 test/test-utils/matchers/to-have-weather.ts create mode 100644 test/test-utils/string-utils.ts create mode 100644 test/types/positional-tags.test-d.ts create mode 100644 test/types/type-helpers.test-d.ts create mode 100644 test/ui/item-manage-button.test.ts create mode 100644 test/utils/strings.test.ts diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index fff90047df2..1588a15afeb 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - beta pull_request: branches: - main diff --git a/.github/workflows/test-shard-template.yml b/.github/workflows/test-shard-template.yml index b154166f81b..124004f380f 100644 --- a/.github/workflows/test-shard-template.yml +++ b/.github/workflows/test-shard-template.yml @@ -44,4 +44,4 @@ jobs: run: pnpm i - name: Run tests - run: pnpm exec vitest --project ${{ inputs.project }} --no-isolate --shard=${{ inputs.shard }}/${{ inputs.totalShards }} ${{ !runner.debug && '--silent' || '' }} + run: pnpm test:silent --shard=${{ inputs.shard }}/${{ inputs.totalShards }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d3dd23eb379..764a35ace60 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,6 +11,7 @@ on: - beta merge_group: types: [checks_requested] + workflow_dispatch: jobs: check-path-change-filter: diff --git a/.ls-lint.yml b/.ls-lint.yml index 09d626af624..22f08f72938 100644 --- a/.ls-lint.yml +++ b/.ls-lint.yml @@ -11,17 +11,18 @@ _cfg: &cfg ls: <<: *cfg - src: + src: &src <<: *cfg .dir: kebab-case | regex:@types .js: exists:0 src/system/version-migration/versions: .ts: snake_case <<: *cfg - + test: *src ignore: - node_modules - .vscode - .github - .git - public + - dist diff --git a/biome.jsonc b/biome.jsonc index d4cb67d33a6..d2f7c711dc9 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -19,7 +19,6 @@ // and having to verify whether each individual file is ignored "includes": [ "**", - "!**/*.d.ts", "!**/dist/**/*", "!**/build/**/*", "!**/coverage/**/*", @@ -177,9 +176,10 @@ } }, - // Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes) + // Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes), + // as well as in all TS files in `scripts/` (which are assumed to be boilerplate templates). { - "includes": ["**/src/overrides.ts", "**/src/enums/**/*"], + "includes": ["**/src/overrides.ts", "**/src/enums/**/*", "**/scripts/**/*.ts", "**/*.d.ts"], "linter": { "rules": { "correctness": { @@ -189,7 +189,7 @@ } }, { - "includes": ["**/src/overrides.ts"], + "includes": ["**/src/overrides.ts", "**/scripts/**/*.ts"], "linter": { "rules": { "style": { diff --git a/global.d.ts b/global.d.ts index 27e96a4d8b5..8b79d966e3c 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,7 +1,6 @@ +import type { AnyFn } from "#types/type-helpers"; import type { SetupServerApi } from "msw/node"; -export {}; - declare global { /** * Only used in testing. @@ -11,4 +10,11 @@ declare global { * To set up your own server in a test see `game-data.test.ts` */ var server: SetupServerApi; + + // Overloads for `Function.apply` and `Function.call` to add type safety on matching argument types + interface Function { + apply(this: T, thisArg: ThisParameterType, argArray: Parameters): ReturnType; + + call(this: T, thisArg: ThisParameterType, ...argArray: Parameters): ReturnType; + } } diff --git a/package.json b/package.json index 85c95bcae3f..d3494da677c 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,12 @@ "test": "vitest run --no-isolate", "test:cov": "vitest run --coverage --no-isolate", "test:watch": "vitest watch --coverage --no-isolate", - "test:silent": "vitest run --silent --no-isolate", + "test:silent": "vitest run --silent='passed-only' --no-isolate", "test:create": "node scripts/create-test/create-test.js", "typecheck": "tsc --noEmit", "eslint": "eslint --fix .", "eslint-ci": "eslint .", - "biome": "biome check --write --changed --no-errors-on-unmatched", + "biome": "biome check --write --changed --no-errors-on-unmatched --diagnostic-level=error", "biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched", "docs": "typedoc", "depcruise": "depcruise src test", @@ -30,18 +30,19 @@ "@biomejs/biome": "2.0.0", "@ls-lint/ls-lint": "2.3.1", "@types/jsdom": "^21.1.7", - "@types/node": "^22.16.3", + "@types/node": "^22.16.5", "@vitest/coverage-istanbul": "^3.2.4", + "@vitest/expect": "^3.2.4", "chalk": "^5.4.1", "dependency-cruiser": "^16.10.4", - "inquirer": "^12.7.0", + "inquirer": "^12.8.2", "jsdom": "^26.1.0", "lefthook": "^1.12.2", "msw": "^2.10.4", "phaser3spectorjs": "^0.0.8", - "typedoc": "^0.28.7", + "typedoc": "^0.28.8", "typescript": "^5.8.3", - "vite": "^6.3.5", + "vite": "^7.0.6", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.2.4", "vitest-canvas-mock": "^0.3.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e77bf065fd5..900be6fd76e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,11 +52,14 @@ importers: specifier: ^21.1.7 version: 21.1.7 '@types/node': - specifier: ^22.16.3 - version: 22.16.3 + specifier: ^22.16.5 + version: 22.16.5 '@vitest/coverage-istanbul': specifier: ^3.2.4 - version: 3.2.4(vitest@3.2.4(@types/node@22.16.3)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.3)(typescript@5.8.3))(yaml@2.8.0)) + version: 3.2.4(vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.0)) + '@vitest/expect': + specifier: ^3.2.4 + version: 3.2.4 chalk: specifier: ^5.4.1 version: 5.4.1 @@ -64,8 +67,8 @@ importers: specifier: ^16.10.4 version: 16.10.4 inquirer: - specifier: ^12.7.0 - version: 12.7.0(@types/node@22.16.3) + specifier: ^12.8.2 + version: 12.8.2(@types/node@22.16.5) jsdom: specifier: ^26.1.0 version: 26.1.0 @@ -74,28 +77,28 @@ importers: version: 1.12.2 msw: specifier: ^2.10.4 - version: 2.10.4(@types/node@22.16.3)(typescript@5.8.3) + version: 2.10.4(@types/node@22.16.5)(typescript@5.8.3) phaser3spectorjs: specifier: ^0.0.8 version: 0.0.8 typedoc: - specifier: ^0.28.7 - version: 0.28.7(typescript@5.8.3) + specifier: ^0.28.8 + version: 0.28.8(typescript@5.8.3) typescript: specifier: ^5.8.3 version: 5.8.3 vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.16.3)(yaml@2.8.0) + specifier: ^7.0.6 + version: 7.0.6(@types/node@22.16.5)(yaml@2.8.0) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.3)(yaml@2.8.0)) + version: 5.1.4(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(yaml@2.8.0)) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.16.3)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.3)(typescript@5.8.3))(yaml@2.8.0) + version: 3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.0) vitest-canvas-mock: specifier: ^0.3.3 - version: 0.3.3(vitest@3.2.4(@types/node@22.16.3)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.3)(typescript@5.8.3))(yaml@2.8.0)) + version: 0.3.3(vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.0)) packages: @@ -152,8 +155,8 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.27.6': - resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} + '@babel/helpers@7.28.2': + resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} engines: {node: '>=6.9.0'} '@babel/parser@7.28.0': @@ -161,8 +164,8 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/runtime@7.27.6': - resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} + '@babel/runtime@7.28.2': + resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} engines: {node: '>=6.9.0'} '@babel/template@7.27.2': @@ -173,8 +176,8 @@ packages: resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.1': - resolution: {integrity: sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==} + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} '@biomejs/biome@2.0.0': @@ -267,167 +270,167 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@esbuild/aix-ppc64@0.25.6': - resolution: {integrity: sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==} + '@esbuild/aix-ppc64@0.25.8': + resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.6': - resolution: {integrity: sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==} + '@esbuild/android-arm64@0.25.8': + resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.6': - resolution: {integrity: sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==} + '@esbuild/android-arm@0.25.8': + resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.6': - resolution: {integrity: sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==} + '@esbuild/android-x64@0.25.8': + resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.6': - resolution: {integrity: sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==} + '@esbuild/darwin-arm64@0.25.8': + resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.6': - resolution: {integrity: sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==} + '@esbuild/darwin-x64@0.25.8': + resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.6': - resolution: {integrity: sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==} + '@esbuild/freebsd-arm64@0.25.8': + resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.6': - resolution: {integrity: sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==} + '@esbuild/freebsd-x64@0.25.8': + resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.6': - resolution: {integrity: sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==} + '@esbuild/linux-arm64@0.25.8': + resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.6': - resolution: {integrity: sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==} + '@esbuild/linux-arm@0.25.8': + resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.6': - resolution: {integrity: sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==} + '@esbuild/linux-ia32@0.25.8': + resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.6': - resolution: {integrity: sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==} + '@esbuild/linux-loong64@0.25.8': + resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.6': - resolution: {integrity: sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==} + '@esbuild/linux-mips64el@0.25.8': + resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.6': - resolution: {integrity: sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==} + '@esbuild/linux-ppc64@0.25.8': + resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.6': - resolution: {integrity: sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==} + '@esbuild/linux-riscv64@0.25.8': + resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.6': - resolution: {integrity: sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==} + '@esbuild/linux-s390x@0.25.8': + resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.6': - resolution: {integrity: sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==} + '@esbuild/linux-x64@0.25.8': + resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.6': - resolution: {integrity: sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==} + '@esbuild/netbsd-arm64@0.25.8': + resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.6': - resolution: {integrity: sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==} + '@esbuild/netbsd-x64@0.25.8': + resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.6': - resolution: {integrity: sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==} + '@esbuild/openbsd-arm64@0.25.8': + resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.6': - resolution: {integrity: sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==} + '@esbuild/openbsd-x64@0.25.8': + resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.6': - resolution: {integrity: sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==} + '@esbuild/openharmony-arm64@0.25.8': + resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.6': - resolution: {integrity: sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==} + '@esbuild/sunos-x64@0.25.8': + resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.6': - resolution: {integrity: sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==} + '@esbuild/win32-arm64@0.25.8': + resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.6': - resolution: {integrity: sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==} + '@esbuild/win32-ia32@0.25.8': + resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.6': - resolution: {integrity: sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==} + '@esbuild/win32-x64@0.25.8': + resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@gerrit0/mini-shiki@3.7.0': - resolution: {integrity: sha512-7iY9wg4FWXmeoFJpUL2u+tsmh0d0jcEJHAIzVxl3TG4KL493JNnisdLAILZ77zcD+z3J0keEXZ+lFzUgzQzPDg==} + '@gerrit0/mini-shiki@3.8.1': + resolution: {integrity: sha512-HVZW+8pxoOExr5ZMPK15U79jQAZTO/S6i5byQyyZGjtNj+qaYd82cizTncwFzTQgiLo8uUBym6vh+/1tfJklTw==} - '@inquirer/checkbox@4.1.9': - resolution: {integrity: sha512-DBJBkzI5Wx4jFaYm221LHvAhpKYkhVS0k9plqHwaHhofGNxvYB7J3Bz8w+bFJ05zaMb0sZNHo4KdmENQFlNTuQ==} + '@inquirer/checkbox@4.2.0': + resolution: {integrity: sha512-fdSw07FLJEU5vbpOPzXo5c6xmMGDzbZE2+niuDHX5N6mc6V0Ebso/q3xiHra4D73+PMsC8MJmcaZKuAAoaQsSA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -435,8 +438,8 @@ packages: '@types/node': optional: true - '@inquirer/confirm@5.1.13': - resolution: {integrity: sha512-EkCtvp67ICIVVzjsquUiVSd+V5HRGOGQfsqA4E4vMWhYnB7InUL0pa0TIWt1i+OfP16Gkds8CdIu6yGZwOM1Yw==} + '@inquirer/confirm@5.1.14': + resolution: {integrity: sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -444,8 +447,8 @@ packages: '@types/node': optional: true - '@inquirer/core@10.1.14': - resolution: {integrity: sha512-Ma+ZpOJPewtIYl6HZHZckeX1STvDnHTCB2GVINNUlSEn2Am6LddWwfPkIGY0IUFVjUUrr/93XlBwTK6mfLjf0A==} + '@inquirer/core@10.1.15': + resolution: {integrity: sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -453,8 +456,8 @@ packages: '@types/node': optional: true - '@inquirer/editor@4.2.14': - resolution: {integrity: sha512-yd2qtLl4QIIax9DTMZ1ZN2pFrrj+yL3kgIWxm34SS6uwCr0sIhsNyudUjAo5q3TqI03xx4SEBkUJqZuAInp9uA==} + '@inquirer/editor@4.2.15': + resolution: {integrity: sha512-wst31XT8DnGOSS4nNJDIklGKnf+8shuauVrWzgKegWUe28zfCftcWZ2vktGdzJgcylWSS2SrDnYUb6alZcwnCQ==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -462,8 +465,8 @@ packages: '@types/node': optional: true - '@inquirer/expand@4.0.16': - resolution: {integrity: sha512-oiDqafWzMtofeJyyGkb1CTPaxUkjIcSxePHHQCfif8t3HV9pHcw1Kgdw3/uGpDvaFfeTluwQtWiqzPVjAqS3zA==} + '@inquirer/expand@4.0.17': + resolution: {integrity: sha512-PSqy9VmJx/VbE3CT453yOfNa+PykpKg/0SYP7odez1/NWBGuDXgPhp4AeGYYKjhLn5lUUavVS/JbeYMPdH50Mw==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -471,12 +474,12 @@ packages: '@types/node': optional: true - '@inquirer/figures@1.0.12': - resolution: {integrity: sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==} + '@inquirer/figures@1.0.13': + resolution: {integrity: sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==} engines: {node: '>=18'} - '@inquirer/input@4.2.0': - resolution: {integrity: sha512-opqpHPB1NjAmDISi3uvZOTrjEEU5CWVu/HBkDby8t93+6UxYX0Z7Ps0Ltjm5sZiEbWenjubwUkivAEYQmy9xHw==} + '@inquirer/input@4.2.1': + resolution: {integrity: sha512-tVC+O1rBl0lJpoUZv4xY+WGWY8V5b0zxU1XDsMsIHYregdh7bN5X5QnIONNBAl0K765FYlAfNHS2Bhn7SSOVow==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -484,8 +487,8 @@ packages: '@types/node': optional: true - '@inquirer/number@3.0.16': - resolution: {integrity: sha512-kMrXAaKGavBEoBYUCgualbwA9jWUx2TjMA46ek+pEKy38+LFpL9QHlTd8PO2kWPUgI/KB+qi02o4y2rwXbzr3Q==} + '@inquirer/number@3.0.17': + resolution: {integrity: sha512-GcvGHkyIgfZgVnnimURdOueMk0CztycfC8NZTiIY9arIAkeOgt6zG57G+7vC59Jns3UX27LMkPKnKWAOF5xEYg==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -493,8 +496,8 @@ packages: '@types/node': optional: true - '@inquirer/password@4.0.16': - resolution: {integrity: sha512-g8BVNBj5Zeb5/Y3cSN+hDUL7CsIFDIuVxb9EPty3lkxBaYpjL5BNRKSYOF9yOLe+JOcKFd+TSVeADQ4iSY7rbg==} + '@inquirer/password@4.0.17': + resolution: {integrity: sha512-DJolTnNeZ00E1+1TW+8614F7rOJJCM4y4BAGQ3Gq6kQIG+OJ4zr3GLjIjVVJCbKsk2jmkmv6v2kQuN/vriHdZA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -502,8 +505,8 @@ packages: '@types/node': optional: true - '@inquirer/prompts@7.6.0': - resolution: {integrity: sha512-jAhL7tyMxB3Gfwn4HIJ0yuJ5pvcB5maYUcouGcgd/ub79f9MqZ+aVnBtuFf+VC2GTkCBF+R+eo7Vi63w5VZlzw==} + '@inquirer/prompts@7.7.1': + resolution: {integrity: sha512-XDxPrEWeWUBy8scAXzXuFY45r/q49R0g72bUzgQXZ1DY/xEFX+ESDMkTQolcb5jRBzaNJX2W8XQl6krMNDTjaA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -511,8 +514,8 @@ packages: '@types/node': optional: true - '@inquirer/rawlist@4.1.4': - resolution: {integrity: sha512-5GGvxVpXXMmfZNtvWw4IsHpR7RzqAR624xtkPd1NxxlV5M+pShMqzL4oRddRkg8rVEOK9fKdJp1jjVML2Lr7TQ==} + '@inquirer/rawlist@4.1.5': + resolution: {integrity: sha512-R5qMyGJqtDdi4Ht521iAkNqyB6p2UPuZUbMifakg1sWtu24gc2Z8CJuw8rP081OckNDMgtDCuLe42Q2Kr3BolA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -520,8 +523,8 @@ packages: '@types/node': optional: true - '@inquirer/search@3.0.16': - resolution: {integrity: sha512-POCmXo+j97kTGU6aeRjsPyuCpQQfKcMXdeTMw708ZMtWrj5aykZvlUxH4Qgz3+Y1L/cAVZsSpA+UgZCu2GMOMg==} + '@inquirer/search@3.0.17': + resolution: {integrity: sha512-CuBU4BAGFqRYors4TNCYzy9X3DpKtgIW4Boi0WNkm4Ei1hvY9acxKdBdyqzqBCEe4YxSdaQQsasJlFlUJNgojw==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -529,8 +532,8 @@ packages: '@types/node': optional: true - '@inquirer/select@4.2.4': - resolution: {integrity: sha512-unTppUcTjmnbl/q+h8XeQDhAqIOmwWYWNyiiP2e3orXrg6tOaa5DHXja9PChCSbChOsktyKgOieRZFnajzxoBg==} + '@inquirer/select@4.3.1': + resolution: {integrity: sha512-Gfl/5sqOF5vS/LIrSndFgOh7jgoe0UXEizDqahFRkq5aJBLegZ6WjuMh/hVEJwlFQjyLq1z9fRtvUMkb7jM1LA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -538,8 +541,8 @@ packages: '@types/node': optional: true - '@inquirer/type@3.0.7': - resolution: {integrity: sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==} + '@inquirer/type@3.0.8': + resolution: {integrity: sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -577,8 +580,8 @@ packages: '@material/material-color-utilities@0.2.7': resolution: {integrity: sha512-0FCeqG6WvK4/Cc06F/xXMd/pv4FeisI0c1tUpBbfhA2n9Y8eZEv4Karjbmf2ZqQCPUWMrGp8A571tCjizxoTiQ==} - '@mswjs/interceptors@0.39.2': - resolution: {integrity: sha512-RuzCup9Ct91Y7V79xwCb146RaBRHZ7NBbrIUySumd1rpKqHL5OonaqrGIbug5hNwP/fRyxFMA6ISgw4FTtYFYg==} + '@mswjs/interceptors@0.39.4': + resolution: {integrity: sha512-B82DbrGVCIBrNEfRJbqUFB0eNz0wVzqbenEpmbE71XLVU4yKZbDnRBuxz+7udc/uM7LDWDD4sRJ5tISzHf2QkQ==} engines: {node: '>=18'} '@open-draft/deferred-promise@2.2.0': @@ -590,161 +593,121 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@oxlint/darwin-arm64@1.6.0': - resolution: {integrity: sha512-m3wyqBh1TOHjpr/dXeIZY7OoX+MQazb+bMHQdDtwUvefrafUx+5YHRvulYh1sZSQ449nQ3nk3qj5qj535vZRjg==} - cpu: [arm64] - os: [darwin] - - '@oxlint/darwin-x64@1.6.0': - resolution: {integrity: sha512-75fJfF/9xNypr7cnOYoZBhfmG1yP7ex3pUOeYGakmtZRffO9z1i1quLYhjZsmaDXsAIZ3drMhenYHMmFKS3SRg==} - cpu: [x64] - os: [darwin] - - '@oxlint/linux-arm64-gnu@1.6.0': - resolution: {integrity: sha512-YhXGf0FXa72bEt4F7eTVKx5X3zWpbAOPnaA/dZ6/g8tGhw1m9IFjrabVHFjzcx3dQny4MgA59EhyElkDvpUe8A==} - cpu: [arm64] - os: [linux] - - '@oxlint/linux-arm64-musl@1.6.0': - resolution: {integrity: sha512-T3JDhx8mjGjvh5INsPZJrlKHmZsecgDYvtvussKRdkc1Nnn7WC+jH9sh5qlmYvwzvmetlPVNezAoNvmGO9vtMg==} - cpu: [arm64] - os: [linux] - - '@oxlint/linux-x64-gnu@1.6.0': - resolution: {integrity: sha512-Dx7ghtAl8aXBdqofJpi338At6lkeCtTfoinTYQXd9/TEJx+f+zCGNlQO6nJz3ydJBX48FDuOFKkNC+lUlWrd8w==} - cpu: [x64] - os: [linux] - - '@oxlint/linux-x64-musl@1.6.0': - resolution: {integrity: sha512-7KvMGdWmAZtAtg6IjoEJHKxTXdAcrHnUnqfgs0JpXst7trquV2mxBeRZusQXwxpu4HCSomKMvJfsp1qKaqSFDg==} - cpu: [x64] - os: [linux] - - '@oxlint/win32-arm64@1.6.0': - resolution: {integrity: sha512-iSGC9RwX+dl7o5KFr5aH7Gq3nFbkq/3Gda6mxNPMvNkWrgXdIyiINxpyD8hJu566M+QSv1wEAu934BZotFDyoQ==} - cpu: [arm64] - os: [win32] - - '@oxlint/win32-x64@1.6.0': - resolution: {integrity: sha512-jOj3L/gfLc0IwgOTkZMiZ5c673i/hbAmidlaylT0gE6H18hln9HxPgp5GCf4E4y6mwEJlW8QC5hQi221+9otdA==} - cpu: [x64] - os: [win32] - '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@rollup/rollup-android-arm-eabi@4.45.0': - resolution: {integrity: sha512-2o/FgACbji4tW1dzXOqAV15Eu7DdgbKsF2QKcxfG4xbh5iwU7yr5RRP5/U+0asQliSYv5M4o7BevlGIoSL0LXg==} + '@rollup/rollup-android-arm-eabi@4.46.1': + resolution: {integrity: sha512-oENme6QxtLCqjChRUUo3S6X8hjCXnWmJWnedD7VbGML5GUtaOtAyx+fEEXnBXVf0CBZApMQU0Idwi0FmyxzQhw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.45.0': - resolution: {integrity: sha512-PSZ0SvMOjEAxwZeTx32eI/j5xSYtDCRxGu5k9zvzoY77xUNssZM+WV6HYBLROpY5CkXsbQjvz40fBb7WPwDqtQ==} + '@rollup/rollup-android-arm64@4.46.1': + resolution: {integrity: sha512-OikvNT3qYTl9+4qQ9Bpn6+XHM+ogtFadRLuT2EXiFQMiNkXFLQfNVppi5o28wvYdHL2s3fM0D/MZJ8UkNFZWsw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.45.0': - resolution: {integrity: sha512-BA4yPIPssPB2aRAWzmqzQ3y2/KotkLyZukVB7j3psK/U3nVJdceo6qr9pLM2xN6iRP/wKfxEbOb1yrlZH6sYZg==} + '@rollup/rollup-darwin-arm64@4.46.1': + resolution: {integrity: sha512-EFYNNGij2WllnzljQDQnlFTXzSJw87cpAs4TVBAWLdkvic5Uh5tISrIL6NRcxoh/b2EFBG/TK8hgRrGx94zD4A==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.45.0': - resolution: {integrity: sha512-Pr2o0lvTwsiG4HCr43Zy9xXrHspyMvsvEw4FwKYqhli4FuLE5FjcZzuQ4cfPe0iUFCvSQG6lACI0xj74FDZKRA==} + '@rollup/rollup-darwin-x64@4.46.1': + resolution: {integrity: sha512-ZaNH06O1KeTug9WI2+GRBE5Ujt9kZw4a1+OIwnBHal92I8PxSsl5KpsrPvthRynkhMck4XPdvY0z26Cym/b7oA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.45.0': - resolution: {integrity: sha512-lYE8LkE5h4a/+6VnnLiL14zWMPnx6wNbDG23GcYFpRW1V9hYWHAw9lBZ6ZUIrOaoK7NliF1sdwYGiVmziUF4vA==} + '@rollup/rollup-freebsd-arm64@4.46.1': + resolution: {integrity: sha512-n4SLVebZP8uUlJ2r04+g2U/xFeiQlw09Me5UFqny8HGbARl503LNH5CqFTb5U5jNxTouhRjai6qPT0CR5c/Iig==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.45.0': - resolution: {integrity: sha512-PVQWZK9sbzpvqC9Q0GlehNNSVHR+4m7+wET+7FgSnKG3ci5nAMgGmr9mGBXzAuE5SvguCKJ6mHL6vq1JaJ/gvw==} + '@rollup/rollup-freebsd-x64@4.46.1': + resolution: {integrity: sha512-8vu9c02F16heTqpvo3yeiu7Vi1REDEC/yES/dIfq3tSXe6mLndiwvYr3AAvd1tMNUqE9yeGYa5w7PRbI5QUV+w==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.45.0': - resolution: {integrity: sha512-hLrmRl53prCcD+YXTfNvXd776HTxNh8wPAMllusQ+amcQmtgo3V5i/nkhPN6FakW+QVLoUUr2AsbtIRPFU3xIA==} + '@rollup/rollup-linux-arm-gnueabihf@4.46.1': + resolution: {integrity: sha512-K4ncpWl7sQuyp6rWiGUvb6Q18ba8mzM0rjWJ5JgYKlIXAau1db7hZnR0ldJvqKWWJDxqzSLwGUhA4jp+KqgDtQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.45.0': - resolution: {integrity: sha512-XBKGSYcrkdiRRjl+8XvrUR3AosXU0NvF7VuqMsm7s5nRy+nt58ZMB19Jdp1RdqewLcaYnpk8zeVs/4MlLZEJxw==} + '@rollup/rollup-linux-arm-musleabihf@4.46.1': + resolution: {integrity: sha512-YykPnXsjUjmXE6j6k2QBBGAn1YsJUix7pYaPLK3RVE0bQL2jfdbfykPxfF8AgBlqtYbfEnYHmLXNa6QETjdOjQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.45.0': - resolution: {integrity: sha512-fRvZZPUiBz7NztBE/2QnCS5AtqLVhXmUOPj9IHlfGEXkapgImf4W9+FSkL8cWqoAjozyUzqFmSc4zh2ooaeF6g==} + '@rollup/rollup-linux-arm64-gnu@4.46.1': + resolution: {integrity: sha512-kKvqBGbZ8i9pCGW3a1FH3HNIVg49dXXTsChGFsHGXQaVJPLA4f/O+XmTxfklhccxdF5FefUn2hvkoGJH0ScWOA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.45.0': - resolution: {integrity: sha512-Btv2WRZOcUGi8XU80XwIvzTg4U6+l6D0V6sZTrZx214nrwxw5nAi8hysaXj/mctyClWgesyuxbeLylCBNauimg==} + '@rollup/rollup-linux-arm64-musl@4.46.1': + resolution: {integrity: sha512-zzX5nTw1N1plmqC9RGC9vZHFuiM7ZP7oSWQGqpbmfjK7p947D518cVK1/MQudsBdcD84t6k70WNczJOct6+hdg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.45.0': - resolution: {integrity: sha512-Li0emNnwtUZdLwHjQPBxn4VWztcrw/h7mgLyHiEI5Z0MhpeFGlzaiBHpSNVOMB/xucjXTTcO+dhv469Djr16KA==} + '@rollup/rollup-linux-loongarch64-gnu@4.46.1': + resolution: {integrity: sha512-O8CwgSBo6ewPpktFfSDgB6SJN9XDcPSvuwxfejiddbIC/hn9Tg6Ai0f0eYDf3XvB/+PIWzOQL+7+TZoB8p9Yuw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.45.0': - resolution: {integrity: sha512-sB8+pfkYx2kvpDCfd63d5ScYT0Fz1LO6jIb2zLZvmK9ob2D8DeVqrmBDE0iDK8KlBVmsTNzrjr3G1xV4eUZhSw==} + '@rollup/rollup-linux-ppc64-gnu@4.46.1': + resolution: {integrity: sha512-JnCfFVEKeq6G3h3z8e60kAp8Rd7QVnWCtPm7cxx+5OtP80g/3nmPtfdCXbVl063e3KsRnGSKDHUQMydmzc/wBA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.45.0': - resolution: {integrity: sha512-5GQ6PFhh7E6jQm70p1aW05G2cap5zMOvO0se5JMecHeAdj5ZhWEHbJ4hiKpfi1nnnEdTauDXxPgXae/mqjow9w==} + '@rollup/rollup-linux-riscv64-gnu@4.46.1': + resolution: {integrity: sha512-dVxuDqS237eQXkbYzQQfdf/njgeNw6LZuVyEdUaWwRpKHhsLI+y4H/NJV8xJGU19vnOJCVwaBFgr936FHOnJsQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.45.0': - resolution: {integrity: sha512-N/euLsBd1rekWcuduakTo/dJw6U6sBP3eUq+RXM9RNfPuWTvG2w/WObDkIvJ2KChy6oxZmOSC08Ak2OJA0UiAA==} + '@rollup/rollup-linux-riscv64-musl@4.46.1': + resolution: {integrity: sha512-CvvgNl2hrZrTR9jXK1ye0Go0HQRT6ohQdDfWR47/KFKiLd5oN5T14jRdUVGF4tnsN8y9oSfMOqH6RuHh+ck8+w==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.45.0': - resolution: {integrity: sha512-2l9sA7d7QdikL0xQwNMO3xURBUNEWyHVHfAsHsUdq+E/pgLTUcCE+gih5PCdmyHmfTDeXUWVhqL0WZzg0nua3g==} + '@rollup/rollup-linux-s390x-gnu@4.46.1': + resolution: {integrity: sha512-x7ANt2VOg2565oGHJ6rIuuAon+A8sfe1IeUx25IKqi49OjSr/K3awoNqr9gCwGEJo9OuXlOn+H2p1VJKx1psxA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.45.0': - resolution: {integrity: sha512-XZdD3fEEQcwG2KrJDdEQu7NrHonPxxaV0/w2HpvINBdcqebz1aL+0vM2WFJq4DeiAVT6F5SUQas65HY5JDqoPw==} + '@rollup/rollup-linux-x64-gnu@4.46.1': + resolution: {integrity: sha512-9OADZYryz/7E8/qt0vnaHQgmia2Y0wrjSSn1V/uL+zw/i7NUhxbX4cHXdEQ7dnJgzYDS81d8+tf6nbIdRFZQoQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.45.0': - resolution: {integrity: sha512-7ayfgvtmmWgKWBkCGg5+xTQ0r5V1owVm67zTrsEY1008L5ro7mCyGYORomARt/OquB9KY7LpxVBZes+oSniAAQ==} + '@rollup/rollup-linux-x64-musl@4.46.1': + resolution: {integrity: sha512-NuvSCbXEKY+NGWHyivzbjSVJi68Xfq1VnIvGmsuXs6TCtveeoDRKutI5vf2ntmNnVq64Q4zInet0UDQ+yMB6tA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.45.0': - resolution: {integrity: sha512-B+IJgcBnE2bm93jEW5kHisqvPITs4ddLOROAcOc/diBgrEiQJJ6Qcjby75rFSmH5eMGrqJryUgJDhrfj942apQ==} + '@rollup/rollup-win32-arm64-msvc@4.46.1': + resolution: {integrity: sha512-mWz+6FSRb82xuUMMV1X3NGiaPFqbLN9aIueHleTZCc46cJvwTlvIh7reQLk4p97dv0nddyewBhwzryBHH7wtPw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.45.0': - resolution: {integrity: sha512-+CXwwG66g0/FpWOnP/v1HnrGVSOygK/osUbu3wPRy8ECXjoYKjRAyfxYpDQOfghC5qPJYLPH0oN4MCOjwgdMug==} + '@rollup/rollup-win32-ia32-msvc@4.46.1': + resolution: {integrity: sha512-7Thzy9TMXDw9AU4f4vsLNBxh7/VOKuXi73VH3d/kHGr0tZ3x/ewgL9uC7ojUKmH1/zvmZe2tLapYcZllk3SO8Q==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.45.0': - resolution: {integrity: sha512-SRf1cytG7wqcHVLrBc9VtPK4pU5wxiB/lNIkNmW2ApKXIg+RpqwHfsaEK+e7eH4A1BpI6BX/aBWXxZCIrJg3uA==} + '@rollup/rollup-win32-x64-msvc@4.46.1': + resolution: {integrity: sha512-7GVB4luhFmGUNXXJhH2jJwZCFB3pIOixv2E3s17GQHBFUOQaISlt7aGcQgqvCaDSxTZJUzlK/QJ1FN8S94MrzQ==} cpu: [x64] os: [win32] - '@shikijs/engine-oniguruma@3.7.0': - resolution: {integrity: sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw==} + '@shikijs/engine-oniguruma@3.8.1': + resolution: {integrity: sha512-KGQJZHlNY7c656qPFEQpIoqOuC4LrxjyNndRdzk5WKB/Ie87+NJCF1xo9KkOUxwxylk7rT6nhlZyTGTC4fCe1g==} - '@shikijs/langs@3.7.0': - resolution: {integrity: sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ==} + '@shikijs/langs@3.8.1': + resolution: {integrity: sha512-TjOFg2Wp1w07oKnXjs0AUMb4kJvujML+fJ1C5cmEj45lhjbUXtziT1x2bPQb9Db6kmPhkG5NI2tgYW1/DzhUuQ==} - '@shikijs/themes@3.7.0': - resolution: {integrity: sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ==} + '@shikijs/themes@3.8.1': + resolution: {integrity: sha512-Vu3t3BBLifc0GB0UPg2Pox1naTemrrvyZv2lkiSw3QayVV60me1ujFQwPZGgUTmwXl1yhCPW8Lieesm0CYruLQ==} - '@shikijs/types@3.7.0': - resolution: {integrity: sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg==} + '@shikijs/types@3.8.1': + resolution: {integrity: sha512-5C39Q8/8r1I26suLh+5TPk1DTrbY/kn3IdWA5HdizR0FhlhD05zx5nKCqhzSfDHH3p4S0ZefxWd77DLV+8FhGg==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -767,8 +730,8 @@ packages: '@types/jsdom@21.1.7': resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==} - '@types/node@22.16.3': - resolution: {integrity: sha512-sr4Xz74KOUeYadexo1r8imhRtlVXcs+j3XK3TcoiYk7B1t3YRVJgtaD3cwX73NYb71pmVuMLNRhJ9XKdoDB74g==} + '@types/node@22.16.5': + resolution: {integrity: sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==} '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} @@ -1005,8 +968,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.182: - resolution: {integrity: sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA==} + electron-to-chromium@1.5.191: + resolution: {integrity: sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1041,8 +1004,8 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - esbuild@0.25.6: - resolution: {integrity: sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==} + esbuild@0.25.8: + resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} engines: {node: '>=18'} hasBin: true @@ -1226,8 +1189,8 @@ packages: resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - inquirer@12.7.0: - resolution: {integrity: sha512-KKFRc++IONSyE2UYw9CJ1V0IWx5yQKomwB+pp3cWomWs+v2+ZsG11G2OVfAjFS6WWCppKw+RfKmpqGfSzD5QBQ==} + inquirer@12.8.2: + resolution: {integrity: sha512-oBDL9f4+cDambZVJdfJu2M5JQfvaug9lbo6fKDlFV40i8t3FGA1Db67ov5Hp5DInG4zmXhHWTSnlXBntnJ7GMA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -1405,8 +1368,8 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - loupe@3.1.4: - resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} + loupe@3.2.0: + resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -1498,8 +1461,8 @@ packages: node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - nwsapi@2.2.20: - resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} + nwsapi@2.2.21: + resolution: {integrity: sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==} object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} @@ -1515,11 +1478,6 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} - oxlint@1.6.0: - resolution: {integrity: sha512-jtaD65PqzIa1udvSxxscTKBxYKuZoFXyKGLiU1Qjo1ulq3uv/fQDtoV1yey1FrQZrQjACGPi1Widsy1TucC7Jg==} - engines: {node: '>=8.*'} - hasBin: true - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -1565,19 +1523,14 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} - engines: {node: '>=14'} - hasBin: true - process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -1626,16 +1579,16 @@ packages: engines: {node: '>= 0.4'} hasBin: true - rollup@4.45.0: - resolution: {integrity: sha512-WLjEcJRIo7i3WDDgOIJqVI2d+lAC3EwvOGy+Xfq6hs+GQuAA4Di/H72xmXkOhrIWFg2PFYSKZYfH0f4vfKXN4A==} + rollup@4.46.1: + resolution: {integrity: sha512-33xGNBsDJAkzt0PvninskHlWnTIPgDtTwhg0U38CUoNP/7H6wI2Cz6dUeoNPbjdTdsYTGuiFFASuUOWovH0SyQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} - run-async@4.0.4: - resolution: {integrity: sha512-2cgeRHnV11lSXBEhq7sN7a5UVjTKm9JTb9x8ApIT//16D7QL96AgnNeWSGoB4gIHc0iYw/Ha0Z+waBaCYZVNhg==} + run-async@4.0.5: + resolution: {integrity: sha512-oN9GTgxUNDBumHTTDmQ8dep6VIJbgj9S3dPP+9XylVLIK4xB9XTXtKWROd5pnhdXR9k0EgO1JRcNh0T+Ny2FsA==} engines: {node: '>=0.12.0'} rxjs@7.8.2: @@ -1830,8 +1783,8 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} - typedoc@0.28.7: - resolution: {integrity: sha512-lpz0Oxl6aidFkmS90VQDQjk/Qf2iw0IUvFqirdONBdj7jPSN9mGXhy66BcGNDxx5ZMyKKiBVAREvPEzT6Uxipw==} + typedoc@0.28.8: + resolution: {integrity: sha512-16GfLopc8icHfdvqZDqdGBoS2AieIRP2rpf9mU+MgN+gGLyEQvAO0QgOa6NJ5QNmQi0LFrDY9in4F2fUNKgJKA==} engines: {node: '>= 18', pnpm: '>= 10'} hasBin: true peerDependencies: @@ -1877,19 +1830,19 @@ packages: vite: optional: true - vite@6.3.5: - resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vite@7.0.6: + resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@types/node': ^20.19.0 || >=22.12.0 jiti: '>=1.21.0' - less: '*' + less: ^4.0.0 lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 @@ -2079,11 +2032,11 @@ snapshots: '@babel/generator': 7.28.0 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) - '@babel/helpers': 7.27.6 + '@babel/helpers': 7.28.2 '@babel/parser': 7.28.0 '@babel/template': 7.27.2 '@babel/traverse': 7.28.0 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 convert-source-map: 2.0.0 debug: 4.4.1 gensync: 1.0.0-beta.2 @@ -2095,7 +2048,7 @@ snapshots: '@babel/generator@7.28.0': dependencies: '@babel/parser': 7.28.0 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.1.0 @@ -2113,7 +2066,7 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: '@babel/traverse': 7.28.0 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color @@ -2132,22 +2085,22 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.27.6': + '@babel/helpers@7.28.2': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 '@babel/parser@7.28.0': dependencies: - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 - '@babel/runtime@7.27.6': {} + '@babel/runtime@7.28.2': {} '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 '@babel/parser': 7.28.0 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 '@babel/traverse@7.28.0': dependencies: @@ -2156,12 +2109,12 @@ snapshots: '@babel/helper-globals': 7.28.0 '@babel/parser': 7.28.0 '@babel/template': 7.27.2 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 debug: 4.4.1 transitivePeerDependencies: - supports-color - '@babel/types@7.28.1': + '@babel/types@7.28.2': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 @@ -2234,113 +2187,113 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} - '@esbuild/aix-ppc64@0.25.6': + '@esbuild/aix-ppc64@0.25.8': optional: true - '@esbuild/android-arm64@0.25.6': + '@esbuild/android-arm64@0.25.8': optional: true - '@esbuild/android-arm@0.25.6': + '@esbuild/android-arm@0.25.8': optional: true - '@esbuild/android-x64@0.25.6': + '@esbuild/android-x64@0.25.8': optional: true - '@esbuild/darwin-arm64@0.25.6': + '@esbuild/darwin-arm64@0.25.8': optional: true - '@esbuild/darwin-x64@0.25.6': + '@esbuild/darwin-x64@0.25.8': optional: true - '@esbuild/freebsd-arm64@0.25.6': + '@esbuild/freebsd-arm64@0.25.8': optional: true - '@esbuild/freebsd-x64@0.25.6': + '@esbuild/freebsd-x64@0.25.8': optional: true - '@esbuild/linux-arm64@0.25.6': + '@esbuild/linux-arm64@0.25.8': optional: true - '@esbuild/linux-arm@0.25.6': + '@esbuild/linux-arm@0.25.8': optional: true - '@esbuild/linux-ia32@0.25.6': + '@esbuild/linux-ia32@0.25.8': optional: true - '@esbuild/linux-loong64@0.25.6': + '@esbuild/linux-loong64@0.25.8': optional: true - '@esbuild/linux-mips64el@0.25.6': + '@esbuild/linux-mips64el@0.25.8': optional: true - '@esbuild/linux-ppc64@0.25.6': + '@esbuild/linux-ppc64@0.25.8': optional: true - '@esbuild/linux-riscv64@0.25.6': + '@esbuild/linux-riscv64@0.25.8': optional: true - '@esbuild/linux-s390x@0.25.6': + '@esbuild/linux-s390x@0.25.8': optional: true - '@esbuild/linux-x64@0.25.6': + '@esbuild/linux-x64@0.25.8': optional: true - '@esbuild/netbsd-arm64@0.25.6': + '@esbuild/netbsd-arm64@0.25.8': optional: true - '@esbuild/netbsd-x64@0.25.6': + '@esbuild/netbsd-x64@0.25.8': optional: true - '@esbuild/openbsd-arm64@0.25.6': + '@esbuild/openbsd-arm64@0.25.8': optional: true - '@esbuild/openbsd-x64@0.25.6': + '@esbuild/openbsd-x64@0.25.8': optional: true - '@esbuild/openharmony-arm64@0.25.6': + '@esbuild/openharmony-arm64@0.25.8': optional: true - '@esbuild/sunos-x64@0.25.6': + '@esbuild/sunos-x64@0.25.8': optional: true - '@esbuild/win32-arm64@0.25.6': + '@esbuild/win32-arm64@0.25.8': optional: true - '@esbuild/win32-ia32@0.25.6': + '@esbuild/win32-ia32@0.25.8': optional: true - '@esbuild/win32-x64@0.25.6': + '@esbuild/win32-x64@0.25.8': optional: true - '@gerrit0/mini-shiki@3.7.0': + '@gerrit0/mini-shiki@3.8.1': dependencies: - '@shikijs/engine-oniguruma': 3.7.0 - '@shikijs/langs': 3.7.0 - '@shikijs/themes': 3.7.0 - '@shikijs/types': 3.7.0 + '@shikijs/engine-oniguruma': 3.8.1 + '@shikijs/langs': 3.8.1 + '@shikijs/themes': 3.8.1 + '@shikijs/types': 3.8.1 '@shikijs/vscode-textmate': 10.0.2 - '@inquirer/checkbox@4.1.9(@types/node@22.16.3)': + '@inquirer/checkbox@4.2.0(@types/node@22.16.5)': dependencies: - '@inquirer/core': 10.1.14(@types/node@22.16.3) - '@inquirer/figures': 1.0.12 - '@inquirer/type': 3.0.7(@types/node@22.16.3) + '@inquirer/core': 10.1.15(@types/node@22.16.5) + '@inquirer/figures': 1.0.13 + '@inquirer/type': 3.0.8(@types/node@22.16.5) ansi-escapes: 4.3.2 yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 - '@inquirer/confirm@5.1.13(@types/node@22.16.3)': + '@inquirer/confirm@5.1.14(@types/node@22.16.5)': dependencies: - '@inquirer/core': 10.1.14(@types/node@22.16.3) - '@inquirer/type': 3.0.7(@types/node@22.16.3) + '@inquirer/core': 10.1.15(@types/node@22.16.5) + '@inquirer/type': 3.0.8(@types/node@22.16.5) optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 - '@inquirer/core@10.1.14(@types/node@22.16.3)': + '@inquirer/core@10.1.15(@types/node@22.16.5)': dependencies: - '@inquirer/figures': 1.0.12 - '@inquirer/type': 3.0.7(@types/node@22.16.3) + '@inquirer/figures': 1.0.13 + '@inquirer/type': 3.0.8(@types/node@22.16.5) ansi-escapes: 4.3.2 cli-width: 4.1.0 mute-stream: 2.0.0 @@ -2348,93 +2301,93 @@ snapshots: wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 - '@inquirer/editor@4.2.14(@types/node@22.16.3)': + '@inquirer/editor@4.2.15(@types/node@22.16.5)': dependencies: - '@inquirer/core': 10.1.14(@types/node@22.16.3) - '@inquirer/type': 3.0.7(@types/node@22.16.3) + '@inquirer/core': 10.1.15(@types/node@22.16.5) + '@inquirer/type': 3.0.8(@types/node@22.16.5) external-editor: 3.1.0 optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 - '@inquirer/expand@4.0.16(@types/node@22.16.3)': + '@inquirer/expand@4.0.17(@types/node@22.16.5)': dependencies: - '@inquirer/core': 10.1.14(@types/node@22.16.3) - '@inquirer/type': 3.0.7(@types/node@22.16.3) + '@inquirer/core': 10.1.15(@types/node@22.16.5) + '@inquirer/type': 3.0.8(@types/node@22.16.5) yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 - '@inquirer/figures@1.0.12': {} + '@inquirer/figures@1.0.13': {} - '@inquirer/input@4.2.0(@types/node@22.16.3)': + '@inquirer/input@4.2.1(@types/node@22.16.5)': dependencies: - '@inquirer/core': 10.1.14(@types/node@22.16.3) - '@inquirer/type': 3.0.7(@types/node@22.16.3) + '@inquirer/core': 10.1.15(@types/node@22.16.5) + '@inquirer/type': 3.0.8(@types/node@22.16.5) optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 - '@inquirer/number@3.0.16(@types/node@22.16.3)': + '@inquirer/number@3.0.17(@types/node@22.16.5)': dependencies: - '@inquirer/core': 10.1.14(@types/node@22.16.3) - '@inquirer/type': 3.0.7(@types/node@22.16.3) + '@inquirer/core': 10.1.15(@types/node@22.16.5) + '@inquirer/type': 3.0.8(@types/node@22.16.5) optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 - '@inquirer/password@4.0.16(@types/node@22.16.3)': + '@inquirer/password@4.0.17(@types/node@22.16.5)': dependencies: - '@inquirer/core': 10.1.14(@types/node@22.16.3) - '@inquirer/type': 3.0.7(@types/node@22.16.3) + '@inquirer/core': 10.1.15(@types/node@22.16.5) + '@inquirer/type': 3.0.8(@types/node@22.16.5) ansi-escapes: 4.3.2 optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 - '@inquirer/prompts@7.6.0(@types/node@22.16.3)': + '@inquirer/prompts@7.7.1(@types/node@22.16.5)': dependencies: - '@inquirer/checkbox': 4.1.9(@types/node@22.16.3) - '@inquirer/confirm': 5.1.13(@types/node@22.16.3) - '@inquirer/editor': 4.2.14(@types/node@22.16.3) - '@inquirer/expand': 4.0.16(@types/node@22.16.3) - '@inquirer/input': 4.2.0(@types/node@22.16.3) - '@inquirer/number': 3.0.16(@types/node@22.16.3) - '@inquirer/password': 4.0.16(@types/node@22.16.3) - '@inquirer/rawlist': 4.1.4(@types/node@22.16.3) - '@inquirer/search': 3.0.16(@types/node@22.16.3) - '@inquirer/select': 4.2.4(@types/node@22.16.3) + '@inquirer/checkbox': 4.2.0(@types/node@22.16.5) + '@inquirer/confirm': 5.1.14(@types/node@22.16.5) + '@inquirer/editor': 4.2.15(@types/node@22.16.5) + '@inquirer/expand': 4.0.17(@types/node@22.16.5) + '@inquirer/input': 4.2.1(@types/node@22.16.5) + '@inquirer/number': 3.0.17(@types/node@22.16.5) + '@inquirer/password': 4.0.17(@types/node@22.16.5) + '@inquirer/rawlist': 4.1.5(@types/node@22.16.5) + '@inquirer/search': 3.0.17(@types/node@22.16.5) + '@inquirer/select': 4.3.1(@types/node@22.16.5) optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 - '@inquirer/rawlist@4.1.4(@types/node@22.16.3)': + '@inquirer/rawlist@4.1.5(@types/node@22.16.5)': dependencies: - '@inquirer/core': 10.1.14(@types/node@22.16.3) - '@inquirer/type': 3.0.7(@types/node@22.16.3) + '@inquirer/core': 10.1.15(@types/node@22.16.5) + '@inquirer/type': 3.0.8(@types/node@22.16.5) yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 - '@inquirer/search@3.0.16(@types/node@22.16.3)': + '@inquirer/search@3.0.17(@types/node@22.16.5)': dependencies: - '@inquirer/core': 10.1.14(@types/node@22.16.3) - '@inquirer/figures': 1.0.12 - '@inquirer/type': 3.0.7(@types/node@22.16.3) + '@inquirer/core': 10.1.15(@types/node@22.16.5) + '@inquirer/figures': 1.0.13 + '@inquirer/type': 3.0.8(@types/node@22.16.5) yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 - '@inquirer/select@4.2.4(@types/node@22.16.3)': + '@inquirer/select@4.3.1(@types/node@22.16.5)': dependencies: - '@inquirer/core': 10.1.14(@types/node@22.16.3) - '@inquirer/figures': 1.0.12 - '@inquirer/type': 3.0.7(@types/node@22.16.3) + '@inquirer/core': 10.1.15(@types/node@22.16.5) + '@inquirer/figures': 1.0.13 + '@inquirer/type': 3.0.8(@types/node@22.16.5) ansi-escapes: 4.3.2 yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 - '@inquirer/type@3.0.7(@types/node@22.16.3)': + '@inquirer/type@3.0.8(@types/node@22.16.5)': optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 '@isaacs/cliui@8.0.2': dependencies: @@ -2465,7 +2418,7 @@ snapshots: '@material/material-color-utilities@0.2.7': {} - '@mswjs/interceptors@0.39.2': + '@mswjs/interceptors@0.39.4': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -2483,107 +2436,83 @@ snapshots: '@open-draft/until@2.1.0': {} - '@oxlint/darwin-arm64@1.6.0': - optional: true - - '@oxlint/darwin-x64@1.6.0': - optional: true - - '@oxlint/linux-arm64-gnu@1.6.0': - optional: true - - '@oxlint/linux-arm64-musl@1.6.0': - optional: true - - '@oxlint/linux-x64-gnu@1.6.0': - optional: true - - '@oxlint/linux-x64-musl@1.6.0': - optional: true - - '@oxlint/win32-arm64@1.6.0': - optional: true - - '@oxlint/win32-x64@1.6.0': - optional: true - '@pkgjs/parseargs@0.11.0': optional: true - '@rollup/rollup-android-arm-eabi@4.45.0': + '@rollup/rollup-android-arm-eabi@4.46.1': optional: true - '@rollup/rollup-android-arm64@4.45.0': + '@rollup/rollup-android-arm64@4.46.1': optional: true - '@rollup/rollup-darwin-arm64@4.45.0': + '@rollup/rollup-darwin-arm64@4.46.1': optional: true - '@rollup/rollup-darwin-x64@4.45.0': + '@rollup/rollup-darwin-x64@4.46.1': optional: true - '@rollup/rollup-freebsd-arm64@4.45.0': + '@rollup/rollup-freebsd-arm64@4.46.1': optional: true - '@rollup/rollup-freebsd-x64@4.45.0': + '@rollup/rollup-freebsd-x64@4.46.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.45.0': + '@rollup/rollup-linux-arm-gnueabihf@4.46.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.45.0': + '@rollup/rollup-linux-arm-musleabihf@4.46.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.45.0': + '@rollup/rollup-linux-arm64-gnu@4.46.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.45.0': + '@rollup/rollup-linux-arm64-musl@4.46.1': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.45.0': + '@rollup/rollup-linux-loongarch64-gnu@4.46.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.45.0': + '@rollup/rollup-linux-ppc64-gnu@4.46.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.45.0': + '@rollup/rollup-linux-riscv64-gnu@4.46.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.45.0': + '@rollup/rollup-linux-riscv64-musl@4.46.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.45.0': + '@rollup/rollup-linux-s390x-gnu@4.46.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.45.0': + '@rollup/rollup-linux-x64-gnu@4.46.1': optional: true - '@rollup/rollup-linux-x64-musl@4.45.0': + '@rollup/rollup-linux-x64-musl@4.46.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.45.0': + '@rollup/rollup-win32-arm64-msvc@4.46.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.45.0': + '@rollup/rollup-win32-ia32-msvc@4.46.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.45.0': + '@rollup/rollup-win32-x64-msvc@4.46.1': optional: true - '@shikijs/engine-oniguruma@3.7.0': + '@shikijs/engine-oniguruma@3.8.1': dependencies: - '@shikijs/types': 3.7.0 + '@shikijs/types': 3.8.1 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.7.0': + '@shikijs/langs@3.8.1': dependencies: - '@shikijs/types': 3.7.0 + '@shikijs/types': 3.8.1 - '@shikijs/themes@3.7.0': + '@shikijs/themes@3.8.1': dependencies: - '@shikijs/types': 3.7.0 + '@shikijs/types': 3.8.1 - '@shikijs/types@3.7.0': + '@shikijs/types@3.8.1': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -2606,11 +2535,11 @@ snapshots: '@types/jsdom@21.1.7': dependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 - '@types/node@22.16.3': + '@types/node@22.16.5': dependencies: undici-types: 6.21.0 @@ -2620,7 +2549,7 @@ snapshots: '@types/unist@3.0.3': {} - '@vitest/coverage-istanbul@3.2.4(vitest@3.2.4(@types/node@22.16.3)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.3)(typescript@5.8.3))(yaml@2.8.0))': + '@vitest/coverage-istanbul@3.2.4(vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.0))': dependencies: '@istanbuljs/schema': 0.1.3 debug: 4.4.1 @@ -2632,7 +2561,7 @@ snapshots: magicast: 0.3.5 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@22.16.3)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.3)(typescript@5.8.3))(yaml@2.8.0) + vitest: 3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.0) transitivePeerDependencies: - supports-color @@ -2644,14 +2573,14 @@ snapshots: chai: 5.2.1 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(msw@2.10.4(@types/node@22.16.3)(typescript@5.8.3))(vite@6.3.5(@types/node@22.16.3)(yaml@2.8.0))': + '@vitest/mocker@3.2.4(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(vite@7.0.6(@types/node@22.16.5)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - msw: 2.10.4(@types/node@22.16.3)(typescript@5.8.3) - vite: 6.3.5(@types/node@22.16.3)(yaml@2.8.0) + msw: 2.10.4(@types/node@22.16.5)(typescript@5.8.3) + vite: 7.0.6(@types/node@22.16.5)(yaml@2.8.0) '@vitest/pretty-format@3.2.4': dependencies: @@ -2676,7 +2605,7 @@ snapshots: '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 - loupe: 3.1.4 + loupe: 3.2.0 tinyrainbow: 2.0.0 acorn-jsx-walk@2.0.0: {} @@ -2731,7 +2660,7 @@ snapshots: browserslist@4.25.1: dependencies: caniuse-lite: 1.0.30001727 - electron-to-chromium: 1.5.182 + electron-to-chromium: 1.5.191 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.1) @@ -2761,7 +2690,7 @@ snapshots: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.4 + loupe: 3.2.0 pathval: 2.0.1 chalk@4.1.2: @@ -2860,7 +2789,7 @@ snapshots: json5: 2.2.3 memoize: 10.1.0 picocolors: 1.1.1 - picomatch: 4.0.2 + picomatch: 4.0.3 prompts: 2.4.2 rechoir: 0.8.0 safe-regex: 2.1.1 @@ -2877,7 +2806,7 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.182: {} + electron-to-chromium@1.5.191: {} emoji-regex@8.0.0: {} @@ -2902,34 +2831,34 @@ snapshots: dependencies: es-errors: 1.3.0 - esbuild@0.25.6: + esbuild@0.25.8: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.6 - '@esbuild/android-arm': 0.25.6 - '@esbuild/android-arm64': 0.25.6 - '@esbuild/android-x64': 0.25.6 - '@esbuild/darwin-arm64': 0.25.6 - '@esbuild/darwin-x64': 0.25.6 - '@esbuild/freebsd-arm64': 0.25.6 - '@esbuild/freebsd-x64': 0.25.6 - '@esbuild/linux-arm': 0.25.6 - '@esbuild/linux-arm64': 0.25.6 - '@esbuild/linux-ia32': 0.25.6 - '@esbuild/linux-loong64': 0.25.6 - '@esbuild/linux-mips64el': 0.25.6 - '@esbuild/linux-ppc64': 0.25.6 - '@esbuild/linux-riscv64': 0.25.6 - '@esbuild/linux-s390x': 0.25.6 - '@esbuild/linux-x64': 0.25.6 - '@esbuild/netbsd-arm64': 0.25.6 - '@esbuild/netbsd-x64': 0.25.6 - '@esbuild/openbsd-arm64': 0.25.6 - '@esbuild/openbsd-x64': 0.25.6 - '@esbuild/openharmony-arm64': 0.25.6 - '@esbuild/sunos-x64': 0.25.6 - '@esbuild/win32-arm64': 0.25.6 - '@esbuild/win32-ia32': 0.25.6 - '@esbuild/win32-x64': 0.25.6 + '@esbuild/aix-ppc64': 0.25.8 + '@esbuild/android-arm': 0.25.8 + '@esbuild/android-arm64': 0.25.8 + '@esbuild/android-x64': 0.25.8 + '@esbuild/darwin-arm64': 0.25.8 + '@esbuild/darwin-x64': 0.25.8 + '@esbuild/freebsd-arm64': 0.25.8 + '@esbuild/freebsd-x64': 0.25.8 + '@esbuild/linux-arm': 0.25.8 + '@esbuild/linux-arm64': 0.25.8 + '@esbuild/linux-ia32': 0.25.8 + '@esbuild/linux-loong64': 0.25.8 + '@esbuild/linux-mips64el': 0.25.8 + '@esbuild/linux-ppc64': 0.25.8 + '@esbuild/linux-riscv64': 0.25.8 + '@esbuild/linux-s390x': 0.25.8 + '@esbuild/linux-x64': 0.25.8 + '@esbuild/netbsd-arm64': 0.25.8 + '@esbuild/netbsd-x64': 0.25.8 + '@esbuild/openbsd-arm64': 0.25.8 + '@esbuild/openbsd-x64': 0.25.8 + '@esbuild/openharmony-arm64': 0.25.8 + '@esbuild/sunos-x64': 0.25.8 + '@esbuild/win32-arm64': 0.25.8 + '@esbuild/win32-ia32': 0.25.8 + '@esbuild/win32-x64': 0.25.8 escalade@3.2.0: {} @@ -2955,9 +2884,9 @@ snapshots: fast-uri@3.0.6: {} - fdir@6.4.6(picomatch@4.0.2): + fdir@6.4.6(picomatch@4.0.3): optionalDependencies: - picomatch: 4.0.2 + picomatch: 4.0.3 foreground-child@3.3.1: dependencies: @@ -3060,7 +2989,7 @@ snapshots: i18next-browser-languagedetector@8.2.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.2 i18next-http-backend@2.7.3: dependencies: @@ -3080,11 +3009,11 @@ snapshots: i18next@22.5.1: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.2 i18next@24.2.3(typescript@5.8.3): dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.2 optionalDependencies: typescript: 5.8.3 @@ -3104,17 +3033,17 @@ snapshots: ini@4.1.1: {} - inquirer@12.7.0(@types/node@22.16.3): + inquirer@12.8.2(@types/node@22.16.5): dependencies: - '@inquirer/core': 10.1.14(@types/node@22.16.3) - '@inquirer/prompts': 7.6.0(@types/node@22.16.3) - '@inquirer/type': 3.0.7(@types/node@22.16.3) + '@inquirer/core': 10.1.15(@types/node@22.16.5) + '@inquirer/prompts': 7.7.1(@types/node@22.16.5) + '@inquirer/type': 3.0.8(@types/node@22.16.5) ansi-escapes: 4.3.2 mute-stream: 2.0.0 - run-async: 4.0.4 + run-async: 4.0.5 rxjs: 7.8.2 optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 interpret@3.1.1: {} @@ -3200,7 +3129,7 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.20 + nwsapi: 2.2.21 parse5: 7.3.0 rrweb-cssom: 0.8.0 saxes: 6.0.0 @@ -3296,7 +3225,7 @@ snapshots: lodash@4.17.21: {} - loupe@3.1.4: {} + loupe@3.2.0: {} lru-cache@10.4.3: {} @@ -3313,7 +3242,7 @@ snapshots: magicast@0.3.5: dependencies: '@babel/parser': 7.28.0 - '@babel/types': 7.28.1 + '@babel/types': 7.28.2 source-map-js: 1.2.1 make-dir@4.0.0: @@ -3353,13 +3282,13 @@ snapshots: ms@2.1.3: {} - msw@2.10.4(@types/node@22.16.3)(typescript@5.8.3): + msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3): dependencies: '@bundled-es-modules/cookie': 2.0.1 '@bundled-es-modules/statuses': 1.0.1 '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 5.1.13(@types/node@22.16.3) - '@mswjs/interceptors': 0.39.2 + '@inquirer/confirm': 5.1.14(@types/node@22.16.5) + '@mswjs/interceptors': 0.39.4 '@open-draft/deferred-promise': 2.2.0 '@open-draft/until': 2.1.0 '@types/cookie': 0.6.0 @@ -3390,7 +3319,7 @@ snapshots: node-releases@2.0.19: {} - nwsapi@2.2.20: {} + nwsapi@2.2.21: {} object-keys@1.1.1: {} @@ -3400,17 +3329,6 @@ snapshots: outvariant@1.4.3: {} - oxlint@1.6.0: - optionalDependencies: - '@oxlint/darwin-arm64': 1.6.0 - '@oxlint/darwin-x64': 1.6.0 - '@oxlint/linux-arm64-gnu': 1.6.0 - '@oxlint/linux-arm64-musl': 1.6.0 - '@oxlint/linux-x64-gnu': 1.6.0 - '@oxlint/linux-x64-musl': 1.6.0 - '@oxlint/win32-arm64': 1.6.0 - '@oxlint/win32-x64': 1.6.0 - package-json-from-dist@1.0.1: {} pako@1.0.11: {} @@ -3459,7 +3377,7 @@ snapshots: picocolors@1.1.1: {} - picomatch@4.0.2: {} + picomatch@4.0.3: {} postcss@8.5.6: dependencies: @@ -3467,8 +3385,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - prettier@3.6.2: {} - process-nextick-args@2.0.1: {} prompts@2.4.2: @@ -3514,38 +3430,35 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - rollup@4.45.0: + rollup@4.46.1: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.45.0 - '@rollup/rollup-android-arm64': 4.45.0 - '@rollup/rollup-darwin-arm64': 4.45.0 - '@rollup/rollup-darwin-x64': 4.45.0 - '@rollup/rollup-freebsd-arm64': 4.45.0 - '@rollup/rollup-freebsd-x64': 4.45.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.45.0 - '@rollup/rollup-linux-arm-musleabihf': 4.45.0 - '@rollup/rollup-linux-arm64-gnu': 4.45.0 - '@rollup/rollup-linux-arm64-musl': 4.45.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.45.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.45.0 - '@rollup/rollup-linux-riscv64-gnu': 4.45.0 - '@rollup/rollup-linux-riscv64-musl': 4.45.0 - '@rollup/rollup-linux-s390x-gnu': 4.45.0 - '@rollup/rollup-linux-x64-gnu': 4.45.0 - '@rollup/rollup-linux-x64-musl': 4.45.0 - '@rollup/rollup-win32-arm64-msvc': 4.45.0 - '@rollup/rollup-win32-ia32-msvc': 4.45.0 - '@rollup/rollup-win32-x64-msvc': 4.45.0 + '@rollup/rollup-android-arm-eabi': 4.46.1 + '@rollup/rollup-android-arm64': 4.46.1 + '@rollup/rollup-darwin-arm64': 4.46.1 + '@rollup/rollup-darwin-x64': 4.46.1 + '@rollup/rollup-freebsd-arm64': 4.46.1 + '@rollup/rollup-freebsd-x64': 4.46.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.46.1 + '@rollup/rollup-linux-arm-musleabihf': 4.46.1 + '@rollup/rollup-linux-arm64-gnu': 4.46.1 + '@rollup/rollup-linux-arm64-musl': 4.46.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.46.1 + '@rollup/rollup-linux-ppc64-gnu': 4.46.1 + '@rollup/rollup-linux-riscv64-gnu': 4.46.1 + '@rollup/rollup-linux-riscv64-musl': 4.46.1 + '@rollup/rollup-linux-s390x-gnu': 4.46.1 + '@rollup/rollup-linux-x64-gnu': 4.46.1 + '@rollup/rollup-linux-x64-musl': 4.46.1 + '@rollup/rollup-win32-arm64-msvc': 4.46.1 + '@rollup/rollup-win32-ia32-msvc': 4.46.1 + '@rollup/rollup-win32-x64-msvc': 4.46.1 fsevents: 2.3.3 rrweb-cssom@0.8.0: {} - run-async@4.0.4: - dependencies: - oxlint: 1.6.0 - prettier: 3.6.2 + run-async@4.0.5: {} rxjs@7.8.2: dependencies: @@ -3654,8 +3567,8 @@ snapshots: tinyglobby@0.2.14: dependencies: - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 tinypool@1.1.1: {} @@ -3713,9 +3626,9 @@ snapshots: type-fest@4.41.0: {} - typedoc@0.28.7(typescript@5.8.3): + typedoc@0.28.8(typescript@5.8.3): dependencies: - '@gerrit0/mini-shiki': 3.7.0 + '@gerrit0/mini-shiki': 3.8.1 lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 @@ -3743,13 +3656,13 @@ snapshots: util-deprecate@1.0.2: {} - vite-node@3.2.4(@types/node@22.16.3)(yaml@2.8.0): + vite-node@3.2.4(@types/node@22.16.5)(yaml@2.8.0): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.5(@types/node@22.16.3)(yaml@2.8.0) + vite: 7.0.6(@types/node@22.16.5)(yaml@2.8.0) transitivePeerDependencies: - '@types/node' - jiti @@ -3764,40 +3677,40 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.3)(yaml@2.8.0)): + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(yaml@2.8.0)): dependencies: debug: 4.4.1 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.8.3) optionalDependencies: - vite: 6.3.5(@types/node@22.16.3)(yaml@2.8.0) + vite: 7.0.6(@types/node@22.16.5)(yaml@2.8.0) transitivePeerDependencies: - supports-color - typescript - vite@6.3.5(@types/node@22.16.3)(yaml@2.8.0): + vite@7.0.6(@types/node@22.16.5)(yaml@2.8.0): dependencies: - esbuild: 0.25.6 - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 + esbuild: 0.25.8 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.45.0 + rollup: 4.46.1 tinyglobby: 0.2.14 optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 fsevents: 2.3.3 yaml: 2.8.0 - vitest-canvas-mock@0.3.3(vitest@3.2.4(@types/node@22.16.3)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.3)(typescript@5.8.3))(yaml@2.8.0)): + vitest-canvas-mock@0.3.3(vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.0)): dependencies: jest-canvas-mock: 2.5.2 - vitest: 3.2.4(@types/node@22.16.3)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.3)(typescript@5.8.3))(yaml@2.8.0) + vitest: 3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.0) - vitest@3.2.4(@types/node@22.16.3)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.3)(typescript@5.8.3))(yaml@2.8.0): + vitest@3.2.4(@types/node@22.16.5)(jsdom@26.1.0)(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.10.4(@types/node@22.16.3)(typescript@5.8.3))(vite@6.3.5(@types/node@22.16.3)(yaml@2.8.0)) + '@vitest/mocker': 3.2.4(msw@2.10.4(@types/node@22.16.5)(typescript@5.8.3))(vite@7.0.6(@types/node@22.16.5)(yaml@2.8.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -3808,18 +3721,18 @@ snapshots: expect-type: 1.2.2 magic-string: 0.30.17 pathe: 2.0.3 - picomatch: 4.0.2 + picomatch: 4.0.3 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@22.16.3)(yaml@2.8.0) - vite-node: 3.2.4(@types/node@22.16.3)(yaml@2.8.0) + vite: 7.0.6(@types/node@22.16.5)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@22.16.5)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.16.3 + '@types/node': 22.16.5 jsdom: 26.1.0 transitivePeerDependencies: - jiti diff --git a/public/exp-sprites.json b/public/exp-sprites.json index 7430bcf4dba..1903a6c7804 100644 --- a/public/exp-sprites.json +++ b/public/exp-sprites.json @@ -333,8 +333,6 @@ "671-yellow", "6713", "6713", - "672", - "672", "6724", "6724", "673", @@ -377,10 +375,6 @@ "690", "691", "691", - "692", - "692", - "693", - "693", "695", "695", "696", @@ -503,10 +497,6 @@ "751", "752", "752", - "753", - "753", - "754", - "754", "755", "755", "756", @@ -1459,8 +1449,6 @@ "671b-yellow", "6713b", "6713b", - "672b", - "672b", "6724b", "6724b", "673b", @@ -1503,10 +1491,6 @@ "690b", "691b", "691b", - "692b", - "692b", - "693b", - "693b", "695b", "695b", "696b", @@ -1629,10 +1613,6 @@ "751b", "752b", "752b", - "753b", - "753b", - "754b", - "754b", "755b", "755b", "756b", @@ -2585,8 +2565,6 @@ "671sb-yellow", "6713sb", "6713sb", - "672sb", - "672sb", "6724sb", "6724sb", "673sb", @@ -2629,10 +2607,6 @@ "690sb", "691sb", "691sb", - "692sb", - "692sb", - "693sb", - "693sb", "695sb", "695sb", "696sb", @@ -2755,10 +2729,6 @@ "751sb", "752sb", "752sb", - "753sb", - "753sb", - "754sb", - "754sb", "755sb", "755sb", "756sb", @@ -3716,8 +3686,6 @@ "671s-yellow", "6713s", "6713s", - "672s", - "672s", "6724s", "6724s", "673s", @@ -3760,10 +3728,6 @@ "690s", "691s", "691s", - "692s", - "692s", - "693s", - "693s", "695s", "695s", "696s", @@ -3886,10 +3850,6 @@ "751s", "752s", "752s", - "753s", - "753s", - "754s", - "754s", "755s", "755s", "756s", @@ -4625,8 +4585,6 @@ "730", "747", "748", - "753", - "754", "755", "756", "761", diff --git a/public/images/pokemon/672.json b/public/images/pokemon/672.json index eabec185e7a..f337bef7d29 100644 --- a/public/images/pokemon/672.json +++ b/public/images/pokemon/672.json @@ -1,41 +1,479 @@ -{ - "textures": [ - { - "image": "672.png", - "format": "RGBA8888", - "size": { - "w": 50, - "h": 50 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 42, - "h": 50 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 42, - "h": 50 - }, - "frame": { - "x": 0, - "y": 0, - "w": 42, - "h": 50 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:a5389856891adb93e4e47b311ec032fc:ff2b44df2ba78f8e713e7ecfbd8a40e8:2e4767b7cd134fc0ab1bb6e9eee82bc7$" - } +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0002.png", + "frame": { "x": 127, "y": 50, "w": 42, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 42, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0003.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0004.png", + "frame": { "x": 43, "y": 53, "w": 43, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 43, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0005.png", + "frame": { "x": 124, "y": 148, "w": 43, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 13, "w": 43, "h": 45 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0006.png", + "frame": { "x": 0, "y": 102, "w": 42, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 42, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0007.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0008.png", + "frame": { "x": 127, "y": 99, "w": 41, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 41, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0009.png", + "frame": { "x": 128, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0010.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0011.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0012.png", + "frame": { "x": 127, "y": 50, "w": 42, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 42, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0013.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0014.png", + "frame": { "x": 43, "y": 53, "w": 43, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 43, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0015.png", + "frame": { "x": 124, "y": 148, "w": 43, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 13, "w": 43, "h": 45 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0016.png", + "frame": { "x": 0, "y": 102, "w": 42, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 42, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0017.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0018.png", + "frame": { "x": 127, "y": 99, "w": 41, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 41, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0019.png", + "frame": { "x": 128, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0020.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0021.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0022.png", + "frame": { "x": 127, "y": 50, "w": 42, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 42, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0023.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0024.png", + "frame": { "x": 43, "y": 53, "w": 43, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 43, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0025.png", + "frame": { "x": 124, "y": 148, "w": 43, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 13, "w": 43, "h": 45 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0026.png", + "frame": { "x": 0, "y": 102, "w": 42, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 42, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0027.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0028.png", + "frame": { "x": 127, "y": 99, "w": 41, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 41, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0029.png", + "frame": { "x": 128, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0030.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0031.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0032.png", + "frame": { "x": 127, "y": 50, "w": 42, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 42, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0033.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0034.png", + "frame": { "x": 43, "y": 53, "w": 43, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 43, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0035.png", + "frame": { "x": 124, "y": 148, "w": 43, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 13, "w": 43, "h": 45 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0036.png", + "frame": { "x": 0, "y": 102, "w": 42, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 42, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0037.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0038.png", + "frame": { "x": 127, "y": 99, "w": 41, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 41, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0039.png", + "frame": { "x": 128, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0040.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0041.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0042.png", + "frame": { "x": 43, "y": 0, "w": 43, "h": 53 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 4, "w": 43, "h": 53 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0043.png", + "frame": { "x": 0, "y": 0, "w": 43, "h": 54 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 0, "w": 43, "h": 54 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0044.png", + "frame": { "x": 42, "y": 100, "w": 41, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 9, "w": 41, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0045.png", + "frame": { "x": 86, "y": 50, "w": 41, "h": 51 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 7, "w": 41, "h": 51 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0046.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0047.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0048.png", + "frame": { "x": 43, "y": 0, "w": 43, "h": 53 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 4, "w": 43, "h": 53 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0049.png", + "frame": { "x": 0, "y": 0, "w": 43, "h": 54 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 0, "w": 43, "h": 54 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0050.png", + "frame": { "x": 83, "y": 101, "w": 41, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 9, "w": 41, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0051.png", + "frame": { "x": 86, "y": 50, "w": 41, "h": 51 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 7, "w": 41, "h": 51 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0052.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.11-x64", + "image": "672.png", + "format": "I8", + "size": { "w": 170, "h": 193 }, + "scale": "1" + } } diff --git a/public/images/pokemon/672.png b/public/images/pokemon/672.png index 2fdd68acd32510bbb57ccc654cc8ae07082560ef..ff319db5822189e38382ff2966380a614293dc6f 100644 GIT binary patch literal 3337 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 diff --git a/public/images/pokemon/692.json b/public/images/pokemon/692.json index 125642a01f1..86b535260ae 100644 --- a/public/images/pokemon/692.json +++ b/public/images/pokemon/692.json @@ -7,6 +7,780 @@ "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, "sourceSize": { "w": 63, "h": 35 }, "duration": 50 + }, + { + "filename": "0002.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0003.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0004.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0005.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0006.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0007.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0008.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0009.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0010.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0011.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0012.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0013.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0014.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0015.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0016.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0017.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0018.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0019.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0020.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0021.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0022.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0023.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0024.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0025.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0026.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0027.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0028.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0029.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0030.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0031.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0032.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0033.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0034.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0035.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0036.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0037.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0038.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0039.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0040.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0041.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0042.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0043.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0044.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0045.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0046.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0047.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0048.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0049.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0050.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0051.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0052.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0053.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0054.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0055.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0056.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0057.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0058.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0059.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0060.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0061.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0062.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0063.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0064.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0065.png", + "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0066.png", + "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0067.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0068.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0069.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0070.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0071.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0072.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0073.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0074.png", + "frame": { "x": 1, "y": 71, "w": 57, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0075.png", + "frame": { "x": 1, "y": 71, "w": 57, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0076.png", + "frame": { "x": 117, "y": 72, "w": 59, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 59, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0077.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0078.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0079.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0080.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0081.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0082.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0083.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0084.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0085.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0086.png", + "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0087.png", + "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 } ], "meta": { diff --git a/public/images/pokemon/693.json b/public/images/pokemon/693.json index 5e35ec822d2..c8f7763de1d 100644 --- a/public/images/pokemon/693.json +++ b/public/images/pokemon/693.json @@ -1,41 +1,902 @@ -{ - "textures": [ - { - "image": "693.png", - "format": "RGBA8888", - "size": { - "w": 87, - "h": 87 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 87, - "h": 78 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 87, - "h": 78 - }, - "frame": { - "x": 0, - "y": 0, - "w": 87, - "h": 78 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:56529e9f35f7fe73552976d184900c50:cf9846d335c4b48dea98b7f53c4caa05:9c1f9147e693c05eb4655590e9099679$" - } +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 208, "y": 78, "w": 95, "h": 76 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 2, "w": 95, "h": 76 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0002.png", + "frame": { "x": 416, "y": 214, "w": 91, "h": 76 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 1, "w": 91, "h": 76 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0003.png", + "frame": { "x": 405, "y": 145, "w": 103, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 9, "w": 103, "h": 69 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0004.png", + "frame": { "x": 207, "y": 344, "w": 105, "h": 62 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 17, "w": 105, "h": 62 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0005.png", + "frame": { "x": 507, "y": 285, "w": 105, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 18, "w": 105, "h": 63 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0006.png", + "frame": { "x": 412, "y": 1, "w": 104, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 13, "w": 104, "h": 71 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0007.png", + "frame": { "x": 1, "y": 145, "w": 99, "h": 72 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 14, "w": 99, "h": 72 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0008.png", + "frame": { "x": 1, "y": 76, "w": 105, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 21, "w": 105, "h": 69 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0009.png", + "frame": { "x": 507, "y": 219, "w": 104, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 24, "w": 104, "h": 66 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0010.png", + "frame": { "x": 1, "y": 283, "w": 102, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 23, "w": 102, "h": 66 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0011.png", + "frame": { "x": 207, "y": 1, "w": 99, "h": 77 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 10, "w": 99, "h": 77 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0012.png", + "frame": { "x": 517, "y": 74, "w": 93, "h": 78 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 7, "w": 93, "h": 78 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0013.png", + "frame": { "x": 106, "y": 78, "w": 102, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 12, "w": 102, "h": 71 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0014.png", + "frame": { "x": 421, "y": 348, "w": 102, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 19, "w": 102, "h": 63 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0015.png", + "frame": { "x": 312, "y": 354, "w": 100, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 18, "w": 100, "h": 61 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0016.png", + "frame": { "x": 208, "y": 78, "w": 95, "h": 76 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 2, "w": 95, "h": 76 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0017.png", + "frame": { "x": 416, "y": 214, "w": 91, "h": 76 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 1, "w": 91, "h": 76 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0018.png", + "frame": { "x": 405, "y": 145, "w": 103, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 9, "w": 103, "h": 69 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0019.png", + "frame": { "x": 207, "y": 344, "w": 105, "h": 62 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 17, "w": 105, "h": 62 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0020.png", + "frame": { "x": 507, "y": 285, "w": 105, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 18, "w": 105, "h": 63 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0021.png", + "frame": { "x": 412, "y": 1, "w": 104, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 13, "w": 104, "h": 71 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0022.png", + "frame": { "x": 1, "y": 145, "w": 99, "h": 72 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 14, "w": 99, "h": 72 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0023.png", + "frame": { "x": 1, "y": 76, "w": 105, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 21, "w": 105, "h": 69 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0024.png", + "frame": { "x": 507, "y": 219, "w": 104, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 24, "w": 104, "h": 66 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0025.png", + "frame": { "x": 1, "y": 283, "w": 102, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 23, "w": 102, "h": 66 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0026.png", + "frame": { "x": 207, "y": 1, "w": 99, "h": 77 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 10, "w": 99, "h": 77 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0027.png", + "frame": { "x": 517, "y": 74, "w": 93, "h": 78 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 7, "w": 93, "h": 78 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0028.png", + "frame": { "x": 106, "y": 78, "w": 102, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 12, "w": 102, "h": 71 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0029.png", + "frame": { "x": 421, "y": 348, "w": 102, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 19, "w": 102, "h": 63 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0030.png", + "frame": { "x": 312, "y": 354, "w": 100, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 18, "w": 100, "h": 61 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0031.png", + "frame": { "x": 208, "y": 78, "w": 95, "h": 76 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 2, "w": 95, "h": 76 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0032.png", + "frame": { "x": 416, "y": 214, "w": 91, "h": 76 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 1, "w": 91, "h": 76 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0033.png", + "frame": { "x": 405, "y": 145, "w": 103, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 9, "w": 103, "h": 69 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0034.png", + "frame": { "x": 207, "y": 344, "w": 105, "h": 62 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 17, "w": 105, "h": 62 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0035.png", + "frame": { "x": 507, "y": 285, "w": 105, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 18, "w": 105, "h": 63 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0036.png", + "frame": { "x": 412, "y": 1, "w": 104, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 13, "w": 104, "h": 71 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0037.png", + "frame": { "x": 1, "y": 145, "w": 99, "h": 72 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 14, "w": 99, "h": 72 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0038.png", + "frame": { "x": 1, "y": 76, "w": 105, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 21, "w": 105, "h": 69 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0039.png", + "frame": { "x": 507, "y": 219, "w": 104, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 24, "w": 104, "h": 66 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0040.png", + "frame": { "x": 1, "y": 283, "w": 102, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 23, "w": 102, "h": 66 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0041.png", + "frame": { "x": 207, "y": 1, "w": 99, "h": 77 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 10, "w": 99, "h": 77 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0042.png", + "frame": { "x": 517, "y": 74, "w": 93, "h": 78 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 7, "w": 93, "h": 78 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0043.png", + "frame": { "x": 106, "y": 78, "w": 102, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 12, "w": 102, "h": 71 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0044.png", + "frame": { "x": 421, "y": 348, "w": 102, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 19, "w": 102, "h": 63 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0045.png", + "frame": { "x": 312, "y": 354, "w": 100, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 18, "w": 100, "h": 61 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0046.png", + "frame": { "x": 208, "y": 78, "w": 95, "h": 76 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 2, "w": 95, "h": 76 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0047.png", + "frame": { "x": 416, "y": 214, "w": 91, "h": 76 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 1, "w": 91, "h": 76 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0048.png", + "frame": { "x": 405, "y": 145, "w": 103, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 9, "w": 103, "h": 69 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0049.png", + "frame": { "x": 207, "y": 344, "w": 105, "h": 62 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 17, "w": 105, "h": 62 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0050.png", + "frame": { "x": 507, "y": 285, "w": 105, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 18, "w": 105, "h": 63 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0051.png", + "frame": { "x": 412, "y": 1, "w": 104, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 13, "w": 104, "h": 71 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0052.png", + "frame": { "x": 1, "y": 145, "w": 99, "h": 72 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 14, "w": 99, "h": 72 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0053.png", + "frame": { "x": 1, "y": 76, "w": 105, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 21, "w": 105, "h": 69 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0054.png", + "frame": { "x": 507, "y": 219, "w": 104, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 24, "w": 104, "h": 66 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0055.png", + "frame": { "x": 1, "y": 283, "w": 102, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 23, "w": 102, "h": 66 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0056.png", + "frame": { "x": 207, "y": 1, "w": 99, "h": 77 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 10, "w": 99, "h": 77 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0057.png", + "frame": { "x": 517, "y": 74, "w": 93, "h": 78 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 7, "w": 93, "h": 78 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0058.png", + "frame": { "x": 106, "y": 78, "w": 102, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 12, "w": 102, "h": 71 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0059.png", + "frame": { "x": 421, "y": 348, "w": 102, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 19, "w": 102, "h": 63 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0060.png", + "frame": { "x": 312, "y": 354, "w": 100, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 18, "w": 100, "h": 61 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0061.png", + "frame": { "x": 208, "y": 78, "w": 95, "h": 76 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 2, "w": 95, "h": 76 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0062.png", + "frame": { "x": 416, "y": 214, "w": 91, "h": 76 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 1, "w": 91, "h": 76 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0063.png", + "frame": { "x": 405, "y": 145, "w": 103, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 9, "w": 103, "h": 69 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0064.png", + "frame": { "x": 207, "y": 344, "w": 105, "h": 62 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 17, "w": 105, "h": 62 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0065.png", + "frame": { "x": 507, "y": 285, "w": 105, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 18, "w": 105, "h": 63 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0066.png", + "frame": { "x": 412, "y": 1, "w": 104, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 13, "w": 104, "h": 71 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0067.png", + "frame": { "x": 1, "y": 145, "w": 99, "h": 72 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 14, "w": 99, "h": 72 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0068.png", + "frame": { "x": 1, "y": 76, "w": 105, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 21, "w": 105, "h": 69 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0069.png", + "frame": { "x": 507, "y": 219, "w": 104, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 24, "w": 104, "h": 66 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0070.png", + "frame": { "x": 1, "y": 283, "w": 102, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 23, "w": 102, "h": 66 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0071.png", + "frame": { "x": 207, "y": 1, "w": 99, "h": 77 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 10, "w": 99, "h": 77 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0072.png", + "frame": { "x": 517, "y": 74, "w": 93, "h": 78 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 7, "w": 93, "h": 78 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0073.png", + "frame": { "x": 106, "y": 78, "w": 102, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 12, "w": 102, "h": 71 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0074.png", + "frame": { "x": 421, "y": 348, "w": 102, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 19, "w": 102, "h": 63 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0075.png", + "frame": { "x": 312, "y": 354, "w": 100, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 18, "w": 100, "h": 61 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0076.png", + "frame": { "x": 208, "y": 78, "w": 95, "h": 76 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 2, "w": 95, "h": 76 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0077.png", + "frame": { "x": 303, "y": 140, "w": 102, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 11, "y": 5, "w": 102, "h": 70 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0078.png", + "frame": { "x": 523, "y": 348, "w": 102, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 13, "y": 11, "w": 102, "h": 63 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0079.png", + "frame": { "x": 318, "y": 290, "w": 103, "h": 64 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 15, "y": 7, "w": 103, "h": 64 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0080.png", + "frame": { "x": 105, "y": 1, "w": 102, "h": 75 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 11, "y": 0, "w": 102, "h": 75 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0081.png", + "frame": { "x": 306, "y": 72, "w": 108, "h": 68 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 14, "w": 108, "h": 68 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0082.png", + "frame": { "x": 108, "y": 276, "w": 105, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 14, "w": 105, "h": 65 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0083.png", + "frame": { "x": 1, "y": 1, "w": 104, "h": 74 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 8, "w": 104, "h": 74 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0084.png", + "frame": { "x": 203, "y": 210, "w": 106, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 13, "w": 106, "h": 66 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0085.png", + "frame": { "x": 508, "y": 152, "w": 106, "h": 67 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 15, "w": 106, "h": 67 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0086.png", + "frame": { "x": 414, "y": 74, "w": 103, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 8, "w": 103, "h": 71 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0087.png", + "frame": { "x": 306, "y": 72, "w": 108, "h": 68 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 14, "w": 108, "h": 68 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0088.png", + "frame": { "x": 108, "y": 276, "w": 105, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 14, "w": 105, "h": 65 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0089.png", + "frame": { "x": 1, "y": 1, "w": 104, "h": 74 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 8, "w": 104, "h": 74 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0090.png", + "frame": { "x": 203, "y": 210, "w": 106, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 13, "w": 106, "h": 66 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0091.png", + "frame": { "x": 309, "y": 214, "w": 107, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 14, "w": 107, "h": 65 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0092.png", + "frame": { "x": 306, "y": 1, "w": 106, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 9, "w": 106, "h": 70 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0093.png", + "frame": { "x": 1, "y": 218, "w": 107, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 14, "w": 107, "h": 65 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0094.png", + "frame": { "x": 213, "y": 279, "w": 105, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 14, "w": 105, "h": 65 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0095.png", + "frame": { "x": 103, "y": 341, "w": 104, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 15, "w": 104, "h": 63 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0096.png", + "frame": { "x": 516, "y": 1, "w": 101, "h": 73 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 5, "w": 101, "h": 73 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0097.png", + "frame": { "x": 100, "y": 149, "w": 103, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 9, "w": 103, "h": 69 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0098.png", + "frame": { "x": 1, "y": 349, "w": 102, "h": 62 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 16, "w": 102, "h": 62 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + }, + { + "filename": "0099.png", + "frame": { "x": 103, "y": 404, "w": 100, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 16, "w": 100, "h": 61 }, + "sourceSize": { "w": 118, "h": 90 }, + "duration": 100 + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.12-x64", + "image": "693.png", + "format": "I8", + "size": { "w": 626, "h": 466 }, + "scale": "1" + } } diff --git a/public/images/pokemon/693.png b/public/images/pokemon/693.png index 848c881311acdc85f6eb12840a8e3f60be8256a4..28f3bd88282bc4d4972b347c0308bf6d9e16d016 100644 GIT binary patch literal 25840 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 U6<`(rIRF3v07*qoM6N<$f?TKLMgRZ+ literal 476 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 literal 789 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 literal 982 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 diff --git a/public/images/pokemon/back/754.json b/public/images/pokemon/back/754.json index 125ebed161c..86abaac1814 100644 --- a/public/images/pokemon/back/754.json +++ b/public/images/pokemon/back/754.json @@ -4,29 +4,1100 @@ "image": "754.png", "format": "RGBA8888", "size": { - "w": 68, - "h": 68 + "w": 222, + "h": 222 }, "scale": 1, "frames": [ { - "filename": "0001.png", + "filename": "0036.png", "rotated": false, "trimmed": false, "sourceSize": { - "w": 36, + "w": 92, "h": 68 }, "spriteSourceSize": { "x": 0, "y": 0, - "w": 36, + "w": 92, "h": 68 }, "frame": { "x": 0, "y": 0, - "w": 36, + "w": 92, + "h": 68 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 92, + "y": 0, + "w": 92, + "h": 68 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 92, + "y": 0, + "w": 92, + "h": 68 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 92, + "y": 0, + "w": 92, + "h": 68 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 92, + "y": 0, + "w": 92, + "h": 68 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 92, + "y": 0, + "w": 92, + "h": 68 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 92, + "y": 0, + "w": 92, + "h": 68 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 0, + "y": 68, + "w": 92, + "h": 68 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 0, + "y": 68, + "w": 92, + "h": 68 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 0, + "y": 136, + "w": 92, + "h": 68 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 0, + "y": 136, + "w": 92, + "h": 68 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 88, + "h": 68 + }, + "frame": { + "x": 92, + "y": 68, + "w": 88, + "h": 68 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 88, + "h": 68 + }, + "frame": { + "x": 92, + "y": 68, + "w": 88, + "h": 68 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 40, + "h": 68 + }, + "frame": { + "x": 180, + "y": 68, + "w": 40, + "h": 68 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 40, + "h": 68 + }, + "frame": { + "x": 180, + "y": 68, + "w": 40, + "h": 68 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 39, + "h": 68 + }, + "frame": { + "x": 92, + "y": 136, + "w": 39, + "h": 68 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 39, + "h": 68 + }, + "frame": { + "x": 92, + "y": 136, + "w": 39, + "h": 68 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 39, + "h": 68 + }, + "frame": { + "x": 92, + "y": 136, + "w": 39, + "h": 68 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 39, + "h": 68 + }, + "frame": { + "x": 92, + "y": 136, + "w": 39, + "h": 68 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 39, + "h": 68 + }, + "frame": { + "x": 92, + "y": 136, + "w": 39, + "h": 68 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 39, + "h": 68 + }, + "frame": { + "x": 92, + "y": 136, + "w": 39, + "h": 68 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, "h": 68 } } @@ -36,6 +1107,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:2774f1d6c293a696177c371795e6ba38:d35951afed6391313aa94000563ae143:f7cb0e9bb3adbe899317e6e2e306035d$" + "smartupdate": "$TexturePacker:SmartUpdate:3adad944aac48ad53efa41f8c9916d1c:ea15b954875ad08814f50cbbf849c1b3:f7cb0e9bb3adbe899317e6e2e306035d$" } } diff --git a/public/images/pokemon/back/754.png b/public/images/pokemon/back/754.png index ec44dc6326687a0e77c233c354284913bd32a6ac..66bd6a1b9758f405bbc4754bcad9fa3c6e1db256 100644 GIT binary patch delta 3613 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 delta 639 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 diff --git a/public/images/pokemon/back/shiny/692.json b/public/images/pokemon/back/shiny/692.json index 2dec26a2616..801710c4861 100644 --- a/public/images/pokemon/back/shiny/692.json +++ b/public/images/pokemon/back/shiny/692.json @@ -1,41 +1,794 @@ -{ - "textures": [ - { - "image": "692.png", - "format": "RGBA8888", - "size": { - "w": 56, - "h": 56 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 56, - "h": 35 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 56, - "h": 35 - }, - "frame": { - "x": 0, - "y": 0, - "w": 56, - "h": 35 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:8b2c775abfa9b635f2149e201570e6ff:f327a0cd8d92fa087869ded83baa8e41:2880def858c84cd859bedf13b0b49a33$" - } +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0002.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0003.png", + "frame": { "x": 1, "y": 36, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0004.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0005.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0006.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0007.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0008.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0009.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0010.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0011.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0012.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0013.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0014.png", + "frame": { "x": 1, "y": 36, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0015.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0016.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0017.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0018.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0019.png", + "frame": { "x": 1, "y": 36, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0020.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0021.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0022.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0023.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0024.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0025.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0026.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0027.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0028.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0029.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0030.png", + "frame": { "x": 1, "y": 36, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0031.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0032.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0033.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0034.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0035.png", + "frame": { "x": 1, "y": 36, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0036.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0037.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0038.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0039.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0040.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0041.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0042.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0043.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0044.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0045.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0046.png", + "frame": { "x": 1, "y": 36, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0047.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0048.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0049.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0050.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0051.png", + "frame": { "x": 1, "y": 36, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0052.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0053.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0054.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0055.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0056.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0057.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0058.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0059.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0060.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0061.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0062.png", + "frame": { "x": 1, "y": 36, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0063.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0064.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0065.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0066.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0067.png", + "frame": { "x": 59, "y": 37, "w": 57, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 1, "w": 57, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0068.png", + "frame": { "x": 59, "y": 37, "w": 57, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 1, "w": 57, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0069.png", + "frame": { "x": 1, "y": 72, "w": 58, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 58, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0070.png", + "frame": { "x": 1, "y": 72, "w": 58, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 58, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0071.png", + "frame": { "x": 1, "y": 72, "w": 58, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 58, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0072.png", + "frame": { "x": 1, "y": 72, "w": 58, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 58, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0073.png", + "frame": { "x": 1, "y": 72, "w": 58, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 58, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0074.png", + "frame": { "x": 60, "y": 72, "w": 58, "h": 31 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 4, "w": 58, "h": 31 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0075.png", + "frame": { "x": 119, "y": 72, "w": 56, "h": 31 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 4, "w": 56, "h": 31 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0076.png", + "frame": { "x": 60, "y": 72, "w": 58, "h": 31 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 4, "w": 58, "h": 31 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0077.png", + "frame": { "x": 1, "y": 72, "w": 58, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 58, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0078.png", + "frame": { "x": 1, "y": 72, "w": 58, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 58, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0079.png", + "frame": { "x": 1, "y": 72, "w": 58, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 58, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0080.png", + "frame": { "x": 1, "y": 72, "w": 58, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 58, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0081.png", + "frame": { "x": 1, "y": 72, "w": 58, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 58, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0082.png", + "frame": { "x": 1, "y": 72, "w": 58, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 58, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0083.png", + "frame": { "x": 1, "y": 72, "w": 58, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 58, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0084.png", + "frame": { "x": 59, "y": 37, "w": 57, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 1, "w": 57, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0085.png", + "frame": { "x": 59, "y": 37, "w": 57, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 1, "w": 57, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0086.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0087.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.12-x64", + "image": "692.png", + "format": "I8", + "size": { "w": 181, "h": 106 }, + "scale": "1" + } } diff --git a/public/images/pokemon/back/shiny/692.png b/public/images/pokemon/back/shiny/692.png index 7459aabd207d6e05e7d533fe0daf144a7e619cd7..dfad01dd44659d0f5ce9c3a42fdc114c166f3250 100644 GIT binary patch literal 2025 zcmV0000aP)t-s0000G z5D+3*bHBg8`1treoXkwa|BOg6zhXoGgIP^YP5-Hku?;U90000CbW%=J0RR90|NsC0 z|NsC03B*t)000MQNklJ;g3Ofgf_qtjKfw!4963&$U${)0YkQtk(->(R_h>q3jr7KXAYVdy7!~76; zhMIV>YTKmWc5Z|k#hySz=FSUE54DNC%%I`*T8-!n8aK0k!kIbnTF?oM{Wd_2D?82^ z3h}mW$8kuoM>tH>2z?u7jOes20ghwe_wziT3kNvEah<5GnuE&4YS3`F>lcV+pI^6Q zfIn?d@Z4@`%@pdZ)`?Z4;Rqjd)#umOc!U5&xPw@=NUsj65vxPParmFk3Cf0TgE;AM z=7j;NUOiMH7K47h4(AWpw(Yzf04}IMF*VD7lq1Sb-s*Z*8RmaisT!mg8R3TQ6?%}C!1`l~Jc(mV9 z4cbo*nyT}5fPH(ay3q?*=q414C-zIkRlflwvSwvvG?DlMds`aq%Sue@00??ZH&R@_|Gr6*ZH^{NU#keJcy(kKi$hAuZ^ zfd?0~W6`uk`fp)b5Nc22;-bonccfQ@rk-v=q!v0B3(=&F&WjvZ^MeBv1dC-Kem((% zOCzraRxf}(URwej0s85oBeCMY`7r}=m~)sbgJ#RV>Q3CyEscl86_EbJ;&*2rA@=9d zFH7h=qu3%Xx}01zreIdDeZ%FujG=>LM|2(^Wg2`rSKQ6_sDip19dx`SwB^uHe<$x^ zv|;C|r;46Cev7(%vrZ-sKT1m+d#!0T70TFY6Y6Ee&xA4)v>Ej_mz|>#1F@a9!K;m6 zn8*nxXlKbwHe!h3rsnKnXnw2L2v$8|*Q;qeyjJY8vZu(Ra4FNPA@AXgO=+?PH<8_=!YAFe$m zk`>};wZGt`F~MCxF+&^Rt?TZbFj>jOx$HXJIKS?56*FywcdolISxH|2ST?jV!F_gS zrH$y$vlk{SkX^!9^;W_Sj0wg8g`GB|8@peatl*!Kwu=eI0fn75qIPSX?R< zOot$^Pg`_wp1hE11ar6Aa78W|l=n`O?^Ns`S>Y2euHb7JsnH6_(zYnRleTPpheMx6 zm6Sp)**nmcXY>m6*4t#Ll{*9AOgV*GvJ8dFY>Q^d$hP9kj+vIY&BS-tN`_E4y1PbE z*+s8#l_PF5P~TlA8G-Wmx=&S8y;N;Z_fnZ$BN>7ECJSw=kzT4c=Ubst%`BgwO)!gm-gg)YpG<=LR*XQ z@#dz{YZJE_PJ!>vN(N^Oy^wUal4l_+y_~eozlf(n?N#R1XoaS z;Ii9xTQ{(J3EEs)-_+H0kCIhJa0PYIdIH{fqDAw1J+!&8zLPr}EbLS`CmAGzuZKo( z_M`lXH&s*5zMk~B&7AcOk(Ti?-yP6OWZl85BOuNQX7V8N>atY@FhcUQjQ z*quSl++}gu;TKg+9+LNm5xd3h5I{wL`|N7$(OhcTMne(n~00000NkvXX Hu0mjfmxbn~ literal 478 zcmV<40U`d0P)WS^uew*M9dZ00001bW%=J06^y0W&i*I zXh}ptRCr$P&aqCzFc1b{`we^wZIVt=2Oc1icmUu9GIXblk-B;5N!`4Vh>fl8#?nY; z@50M*=QNNahmol_IEp^~zkcz3vgn`a$@_wzIEtH+pQ-5MpHTw&Fdd!l6-gQ56Va-e z-mA5qMDX5gN=MB((sdN2bF5vWBA8i<9(-paC~b;djHpEWqJQDScbK~d5i}L(rwgPp zXxawnJD=0C(^&HZA!e=F_<$d$i>-%_alWAf>h+&LQOKPmG;MOn@9Q`!{d*W=Y!$FU z(jmI=-s}S|Z+pa@Cmg1BS&N7;BuS5>sf=cXcpRpBE<-b`m(Sn09i}#n04t~nse{ji zVBce)-h$NFqX7t*(l1x8QdIoI2#N}Y1ZWu{oz;MbkbB@f)Y+`r0@S#sm=TiE7H53R zEC4D_F$$oXq+AA+`Q8fI1Aj>~TUF&bx>rJ~5%LUBJxbtrNQw9{PM;G0de^(&7l6;I UI}(g#6#xJL07*qoM6N<$f|E1W4gdfE diff --git a/public/images/pokemon/back/shiny/693.json b/public/images/pokemon/back/shiny/693.json index 6c1d41485e9..6358a8908f6 100644 --- a/public/images/pokemon/back/shiny/693.json +++ b/public/images/pokemon/back/shiny/693.json @@ -1,41 +1,902 @@ -{ - "textures": [ - { - "image": "693.png", - "format": "RGBA8888", - "size": { - "w": 90, - "h": 90 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 90, - "h": 70 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 90, - "h": 70 - }, - "frame": { - "x": 0, - "y": 0, - "w": 90, - "h": 70 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:e4bbb1dc7d71678d99aa6c088ee2dda6:9e2c014adc4489792adb3203513e62b4:9c1f9147e693c05eb4655590e9099679$" - } +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0002.png", + "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0003.png", + "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0004.png", + "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0005.png", + "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0006.png", + "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0007.png", + "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0008.png", + "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0009.png", + "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0010.png", + "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0011.png", + "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0012.png", + "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0013.png", + "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0014.png", + "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0015.png", + "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0016.png", + "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0017.png", + "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0018.png", + "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0019.png", + "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0020.png", + "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0021.png", + "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0022.png", + "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0023.png", + "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0024.png", + "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0025.png", + "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0026.png", + "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0027.png", + "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0028.png", + "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0029.png", + "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0030.png", + "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0031.png", + "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0032.png", + "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0033.png", + "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0034.png", + "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0035.png", + "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0036.png", + "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0037.png", + "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0038.png", + "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0039.png", + "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0040.png", + "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0041.png", + "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0042.png", + "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0043.png", + "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0044.png", + "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0045.png", + "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0046.png", + "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0047.png", + "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0048.png", + "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0049.png", + "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0050.png", + "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0051.png", + "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0052.png", + "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0053.png", + "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0054.png", + "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0055.png", + "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0056.png", + "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0057.png", + "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0058.png", + "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0059.png", + "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0060.png", + "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0061.png", + "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0062.png", + "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0063.png", + "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0064.png", + "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0065.png", + "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0066.png", + "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0067.png", + "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0068.png", + "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0069.png", + "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0070.png", + "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0071.png", + "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0072.png", + "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0073.png", + "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0074.png", + "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0075.png", + "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0076.png", + "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0077.png", + "frame": { "x": 565, "y": 196, "w": 90, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 16, "y": 6, "w": 90, "h": 65 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0078.png", + "frame": { "x": 278, "y": 266, "w": 90, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 10, "w": 90, "h": 59 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0079.png", + "frame": { "x": 189, "y": 199, "w": 95, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 8, "w": 95, "h": 61 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0080.png", + "frame": { "x": 193, "y": 1, "w": 98, "h": 68 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 3, "w": 98, "h": 68 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0081.png", + "frame": { "x": 1, "y": 71, "w": 94, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 65 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0082.png", + "frame": { "x": 469, "y": 196, "w": 96, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 12, "y": 12, "w": 96, "h": 61 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0083.png", + "frame": { "x": 1, "y": 1, "w": 98, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 13, "y": 5, "w": 98, "h": 70 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0084.png", + "frame": { "x": 1, "y": 136, "w": 94, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 14, "y": 10, "w": 94, "h": 63 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0085.png", + "frame": { "x": 95, "y": 72, "w": 96, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 15, "y": 12, "w": 96, "h": 63 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0086.png", + "frame": { "x": 381, "y": 1, "w": 97, "h": 67 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 11, "y": 6, "w": 97, "h": 67 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0087.png", + "frame": { "x": 1, "y": 71, "w": 94, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 65 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0088.png", + "frame": { "x": 469, "y": 196, "w": 96, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 12, "y": 12, "w": 96, "h": 61 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0089.png", + "frame": { "x": 1, "y": 1, "w": 98, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 13, "y": 5, "w": 98, "h": 70 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0090.png", + "frame": { "x": 191, "y": 136, "w": 94, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 14, "y": 10, "w": 94, "h": 63 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0091.png", + "frame": { "x": 95, "y": 135, "w": 96, "h": 62 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 12, "y": 11, "w": 96, "h": 62 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0092.png", + "frame": { "x": 572, "y": 67, "w": 99, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 7, "w": 99, "h": 65 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0093.png", + "frame": { "x": 284, "y": 205, "w": 95, "h": 61 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 14, "y": 11, "w": 95, "h": 61 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0094.png", + "frame": { "x": 1, "y": 199, "w": 91, "h": 60 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 18, "y": 12, "w": 91, "h": 60 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0095.png", + "frame": { "x": 1, "y": 259, "w": 95, "h": 60 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 15, "y": 12, "w": 95, "h": 60 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0096.png", + "frame": { "x": 193, "y": 69, "w": 95, "h": 67 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 15, "y": 5, "w": 95, "h": 67 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0097.png", + "frame": { "x": 285, "y": 141, "w": 92, "h": 64 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 18, "y": 8, "w": 92, "h": 64 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0098.png", + "frame": { "x": 96, "y": 318, "w": 89, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 22, "y": 14, "w": 89, "h": 58 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + }, + { + "filename": "0099.png", + "frame": { "x": 564, "y": 261, "w": 92, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 19, "y": 14, "w": 92, "h": 58 }, + "sourceSize": { "w": 111, "h": 83 }, + "duration": 100 + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.12-x64", + "image": "693.png", + "format": "I8", + "size": { "w": 672, "h": 377 }, + "scale": "1" + } } diff --git a/public/images/pokemon/back/shiny/693.png b/public/images/pokemon/back/shiny/693.png index 47715b534bdd4894f6270acef17d166dd5950a85..6884c2e28c7cc0a973f46300b758328f9b60c0f1 100644 GIT binary patch literal 21707 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 diff --git a/public/images/pokemon/back/shiny/753.json b/public/images/pokemon/back/shiny/753.json index 70c1091b725..f1d1bc11bb0 100644 --- a/public/images/pokemon/back/shiny/753.json +++ b/public/images/pokemon/back/shiny/753.json @@ -4,29 +4,2570 @@ "image": "753.png", "format": "RGBA8888", "size": { - "w": 45, - "h": 45 + "w": 140, + "h": 140 }, "scale": 1, "frames": [ { - "filename": "0001.png", + "filename": "0019.png", "rotated": false, - "trimmed": false, + "trimmed": true, "sourceSize": { - "w": 28, - "h": 45 + "w": 31, + "h": 53 }, "spriteSourceSize": { "x": 0, - "y": 0, - "w": 28, - "h": 45 + "y": 5, + "w": 31, + "h": 47 }, "frame": { "x": 0, "y": 0, + "w": 31, + "h": 47 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 31, + "h": 47 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 31, + "h": 47 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 31, + "h": 47 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 31, + "h": 47 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 31, + "h": 47 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 31, + "h": 47 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 31, + "h": 47 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 31, + "h": 47 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 31, + "h": 47 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 31, + "h": 47 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 31, + "h": 47 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 31, + "h": 47 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 31, + "h": 47 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 31, + "h": 47 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 31, + "h": 47 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 31, + "h": 47 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 31, + "h": 47 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 31, + "h": 47 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 31, + "h": 47 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 31, + "h": 47 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 31, + "h": 47 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 31, + "h": 47 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 31, + "h": 47 + }, + "frame": { + "x": 0, + "y": 47, + "w": 31, + "h": 47 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 31, + "h": 46 + }, + "frame": { + "x": 0, + "y": 94, + "w": 31, + "h": 46 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 31, + "h": 46 + }, + "frame": { + "x": 0, + "y": 94, + "w": 31, + "h": 46 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 31, + "h": 46 + }, + "frame": { + "x": 0, + "y": 94, + "w": 31, + "h": 46 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 31, + "h": 46 + }, + "frame": { + "x": 0, + "y": 94, + "w": 31, + "h": 46 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 31, + "h": 46 + }, + "frame": { + "x": 0, + "y": 94, + "w": 31, + "h": 46 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 31, + "h": 46 + }, + "frame": { + "x": 0, + "y": 94, + "w": 31, + "h": 46 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0111.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0112.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0120.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0121.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0122.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 47, + "w": 30, + "h": 47 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 47, + "w": 30, + "h": 47 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 47, + "w": 30, + "h": 47 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 47, + "w": 30, + "h": 47 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 47, + "w": 30, + "h": 47 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 47, + "w": 30, + "h": 47 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 47, + "w": 30, + "h": 47 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 47, + "w": 30, + "h": 47 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 47, + "w": 30, + "h": 47 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 47, + "w": 30, + "h": 47 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 47, + "w": 30, + "h": 47 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 31, + "y": 47, + "w": 30, + "h": 47 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 30, + "h": 46 + }, + "frame": { + "x": 31, + "y": 94, + "w": 30, + "h": 46 + } + }, + { + "filename": "0119.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 7, + "w": 30, + "h": 46 + }, + "frame": { + "x": 31, + "y": 94, + "w": 30, + "h": 46 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 30, + "h": 47 + }, + "frame": { + "x": 61, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 30, + "h": 47 + }, + "frame": { + "x": 61, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 30, + "h": 47 + }, + "frame": { + "x": 61, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 30, + "h": 47 + }, + "frame": { + "x": 61, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 30, + "h": 47 + }, + "frame": { + "x": 61, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 30, + "h": 47 + }, + "frame": { + "x": 61, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 30, + "h": 47 + }, + "frame": { + "x": 61, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 30, + "h": 47 + }, + "frame": { + "x": 61, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 30, + "h": 47 + }, + "frame": { + "x": 61, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 30, + "h": 47 + }, + "frame": { + "x": 61, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 30, + "h": 47 + }, + "frame": { + "x": 61, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 30, + "h": 47 + }, + "frame": { + "x": 61, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 29, + "h": 47 + }, + "frame": { + "x": 91, + "y": 0, + "w": 29, + "h": 47 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 29, + "h": 47 + }, + "frame": { + "x": 91, + "y": 0, + "w": 29, + "h": 47 + } + }, + { + "filename": "0113.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 29, + "h": 47 + }, + "frame": { + "x": 91, + "y": 0, + "w": 29, + "h": 47 + } + }, + { + "filename": "0114.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 29, + "h": 47 + }, + "frame": { + "x": 91, + "y": 0, + "w": 29, + "h": 47 + } + }, + { + "filename": "0107.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 29, + "h": 47 + }, + "frame": { + "x": 61, + "y": 47, + "w": 29, + "h": 47 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 29, + "h": 47 + }, + "frame": { + "x": 61, + "y": 47, + "w": 29, + "h": 47 + } + }, + { + "filename": "0117.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 29, + "h": 47 + }, + "frame": { + "x": 61, + "y": 47, + "w": 29, + "h": 47 + } + }, + { + "filename": "0118.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 29, + "h": 47 + }, + "frame": { + "x": 61, + "y": 47, + "w": 29, + "h": 47 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, "w": 28, + "h": 46 + }, + "frame": { + "x": 61, + "y": 94, + "w": 28, + "h": 46 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 28, + "h": 46 + }, + "frame": { + "x": 61, + "y": 94, + "w": 28, + "h": 46 + } + }, + { + "filename": "0115.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 28, + "h": 46 + }, + "frame": { + "x": 61, + "y": 94, + "w": 28, + "h": 46 + } + }, + { + "filename": "0116.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 28, + "h": 46 + }, + "frame": { + "x": 61, + "y": 94, + "w": 28, + "h": 46 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 8, + "w": 29, + "h": 45 + }, + "frame": { + "x": 89, + "y": 94, + "w": 29, + "h": 45 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 8, + "w": 29, + "h": 45 + }, + "frame": { + "x": 89, + "y": 94, + "w": 29, + "h": 45 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 8, + "w": 29, + "h": 45 + }, + "frame": { + "x": 89, + "y": 94, + "w": 29, + "h": 45 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 8, + "w": 29, + "h": 45 + }, + "frame": { + "x": 89, + "y": 94, + "w": 29, + "h": 45 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 8, + "w": 29, + "h": 45 + }, + "frame": { + "x": 89, + "y": 94, + "w": 29, + "h": 45 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 31, + "h": 53 + }, + "spriteSourceSize": { + "x": 0, + "y": 8, + "w": 29, + "h": 45 + }, + "frame": { + "x": 89, + "y": 94, + "w": 29, "h": 45 } } @@ -36,6 +2577,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:f2829e1ebd212cc5203393968a2efd5f:dd79bfe2b6a61007ace9092be7975ffe:16c1874bc814253ca78e52a99a340ff7$" + "smartupdate": "$TexturePacker:SmartUpdate:b6d27dc4e44833805071498f628d15c3:7ab61edae9d3eecb963334bb47dd5aa7:16c1874bc814253ca78e52a99a340ff7$" } } diff --git a/public/images/pokemon/back/shiny/753.png b/public/images/pokemon/back/shiny/753.png index aadcbe3fa04508f4a2949cc97e86a058b1027008..14f23fc6bb90dfa52bcfab1a181053d58bc48518 100644 GIT binary patch delta 2054 zcmV+h2>JK91C020 ze|niczrVlCy>R&W`2X;xcn<2A00001bW%=J06^y0W&i*Okx4{BRCr#^nL%sZRuqNx zYFtWJQpZutcw-cU3#KrxQZe-qAhuw-v?=W-t7PCILN_r3vkeHs>re193?mxqQB!jluD2a{An{4;tLsHP}PP zNWD1oNmGzq{xP~{TCc&cV?plnnrZ3mjm_etvc+oOah(yVsYz{3I>uOPYtpp5f5KcE z+Dp^GM-`+n*!n0yH`oI?M$KJ2w1;l;1Y|f#%Q=@26!HNIbA`DyuxE`}*KBm0=o%Y@ zE3Qjp_u}+?s800P#3&!6vC4H#48e1A#dT@yo_a7;YJfFErI*f?%F&*v94?qEu4{+( zRQ@v4HA3knHI!!Y8-*9l700Eaf4y8vZ3vmcQl=7_4N!*?FPJOLr7?RlgPIr}A!U|G z35%21*b*##g}XFn4{PApmD(&0RarZc^$d5wURt`CGk4JJRO*b16p&N>3~X`Zw)J^<%NR4DsX0e+_SHH757$?MmlB zE=ma&$kW$Q13&8SLckHxvo7SSEoE+Db7H=j7q>x@EZp{>ZL9fZ2chff(J zM6pCOZX+K)9E7^bhtDQ4>AhJ#Qy`S9T&)ImONv%4+%@c9_>*@9sHubS=2=Yv70 zc__C%`EU^WF!JFb^hce1j$6ow59JQ)jB?gZK5ZbxbH2kxT{ThU#e*98Xeo({I{r~$ z?hF_uQ@JIde?!QD1SL0I1hTaTbKj1DQKYiu^AuX^Tog#zRzr(xiEv}d2Z1IVRJcfj zY^{lrb<_yh2H*yO#?%xc-VYi1G=B}&WDnO~Aa8WdNdh?}pBfkSZ(3^>@Y%QW25KOr zf#w8rD?k>kA@Yg6wi-mH+h;cqkzjPqd>>E;$!A_8e;)y=WrEYi50|HN4JCjBub^fL zHEXD-kx$q37o%k`w@~8ueep~G1Zo%;wU2lOL=yNFpegz%Hb>;sISgw8;Rl}>`D{-x zUc_>YV3^(ZAjET=U?Q-)9)u{z35Hl;N|HSYu^b~9VuQ-Mv*)uMBNze`RzrJCGlJom z#0e&Fe{{pkPD+@>YRn$XF@h06Q1T7cP|WNFEXN4OkVuJ1G!&bbh{AJ3FqTA!zAA&U zDefgc%Q*xCu9Yd_6@4|%5D>-5F@gz)`R0g6WBbox4^hL(@s}M1@&ULmUWF24!-f|j zmtX`?zNgd&z>1S1d%j*GkRE}B7Tc0f5!Ft+9l?W-mb zLi%Aj#y7eK{4s+x@~DQ$hu&_KmeCFg2DSv}L&q5aAOHMaLxw znVcmU&!R^tu~m!juhD$rd`k!+paKIze>I@Va;jM(i0l|Fxi0>c>~p}8SaYjEDBRST z7a>b9AeGnC;&*u10ZQUE9E9?fdD+3FA(+oW^%inq-iI#TUnS&g4MHOWLY92WmmP1% z63mytGx1_R0C5dmB3&%%GZ50b3y@f22}aDphvMD>YS!Mutr5xV$r7G~3<%+iSkS$n zA&0U`FrtTafv19?;15Fj;AL+CJY^nSGzh^@WCUY;{Hg%{-dE!bY6lSV`zLz>%07*qoM6N<$f_ZDV-~a#s delta 421 zcmV;W0b2fz5V->-iBL{Q4GJ0x0000DNk~Le0000j0000j2m=5B01dv=-?684@Ki~zoO@4cqpo(` zOBv~6!@K7q8N)!!xFySoIOcbagI1jTEhKbSe>ttNG(g93;IQ)mq$QCgraIF-ln?r* zhdu`Hq^Ssp{;9m z&GpFF9cue2MOS^idefozO$OJXX||=fJOs#@r>$tYD-iJ%Sis)Ub?wC;fJzj3#7xe0 P00000NkvXXu0mjfr3Jw4 diff --git a/public/images/pokemon/back/shiny/754.json b/public/images/pokemon/back/shiny/754.json index 10165ba4b4a..8b1a3d44a4d 100644 --- a/public/images/pokemon/back/shiny/754.json +++ b/public/images/pokemon/back/shiny/754.json @@ -4,29 +4,1121 @@ "image": "754.png", "format": "RGBA8888", "size": { - "w": 68, - "h": 68 + "w": 222, + "h": 222 }, "scale": 1, "frames": [ { - "filename": "0001.png", + "filename": "0036.png", "rotated": false, "trimmed": false, "sourceSize": { - "w": 36, + "w": 92, "h": 68 }, "spriteSourceSize": { "x": 0, "y": 0, - "w": 36, + "w": 92, "h": 68 }, "frame": { "x": 0, "y": 0, - "w": 36, + "w": 92, + "h": 68 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 92, + "y": 0, + "w": 92, + "h": 68 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 92, + "y": 0, + "w": 92, + "h": 68 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 92, + "y": 0, + "w": 92, + "h": 68 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 92, + "y": 0, + "w": 92, + "h": 68 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 92, + "y": 0, + "w": 92, + "h": 68 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 92, + "y": 0, + "w": 92, + "h": 68 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 184, + "y": 0, + "w": 38, + "h": 68 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 0, + "y": 68, + "w": 92, + "h": 68 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 0, + "y": 68, + "w": 92, + "h": 68 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 0, + "y": 136, + "w": 92, + "h": 68 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 92, + "h": 68 + }, + "frame": { + "x": 0, + "y": 136, + "w": 92, + "h": 68 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 88, + "h": 68 + }, + "frame": { + "x": 92, + "y": 68, + "w": 88, + "h": 68 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 88, + "h": 68 + }, + "frame": { + "x": 92, + "y": 68, + "w": 88, + "h": 68 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 40, + "h": 68 + }, + "frame": { + "x": 180, + "y": 68, + "w": 40, + "h": 68 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 40, + "h": 68 + }, + "frame": { + "x": 180, + "y": 68, + "w": 40, + "h": 68 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 39, + "h": 68 + }, + "frame": { + "x": 92, + "y": 136, + "w": 39, + "h": 68 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 39, + "h": 68 + }, + "frame": { + "x": 92, + "y": 136, + "w": 39, + "h": 68 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 39, + "h": 68 + }, + "frame": { + "x": 92, + "y": 136, + "w": 39, + "h": 68 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 39, + "h": 68 + }, + "frame": { + "x": 92, + "y": 136, + "w": 39, + "h": 68 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 39, + "h": 68 + }, + "frame": { + "x": 92, + "y": 136, + "w": 39, + "h": 68 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 39, + "h": 68 + }, + "frame": { + "x": 92, + "y": 136, + "w": 39, + "h": 68 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 131, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, + "h": 68 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 92, + "h": 68 + }, + "spriteSourceSize": { + "x": 25, + "y": 0, + "w": 38, + "h": 68 + }, + "frame": { + "x": 169, + "y": 136, + "w": 38, "h": 68 } } @@ -36,6 +1128,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:fb4a59b5a68751679b02829509901f6d:d3641a5857a0273c94152df891d4cf5c:f7cb0e9bb3adbe899317e6e2e306035d$" + "smartupdate": "$TexturePacker:SmartUpdate:7651b73927071f2814265b66582a8d13:a2d1ef3cf0c2458640f77c2fbcc821a0:f7cb0e9bb3adbe899317e6e2e306035d$" } } diff --git a/public/images/pokemon/back/shiny/754.png b/public/images/pokemon/back/shiny/754.png index 47a582ff7883f33d7326488abffb40e3af06501a..1f7346ed822ae7a5f5d53874880134a108d990c8 100644 GIT binary patch delta 3613 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 delta 640 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 P00000NkvXXu0mjfca=Sz diff --git a/public/images/pokemon/exp/692.json b/public/images/pokemon/exp/692.json deleted file mode 100644 index 86b535260ae..00000000000 --- a/public/images/pokemon/exp/692.json +++ /dev/null @@ -1,794 +0,0 @@ -{ "frames": [ - { - "filename": "0001.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0002.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0003.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0004.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0005.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0006.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0007.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0008.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0009.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0010.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0011.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0012.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0013.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0014.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0015.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0016.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0017.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0018.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0019.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0020.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0021.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0022.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0023.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0024.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0025.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0026.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0027.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0028.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0029.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0030.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0031.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0032.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0033.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0034.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0035.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0036.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0037.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0038.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0039.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0040.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0041.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0042.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0043.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0044.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0045.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0046.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0047.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0048.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0049.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0050.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0051.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0052.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0053.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0054.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0055.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0056.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0057.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0058.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0059.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0060.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0061.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0062.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0063.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0064.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0065.png", - "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0066.png", - "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0067.png", - "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0068.png", - "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0069.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0070.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0071.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0072.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0073.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0074.png", - "frame": { "x": 1, "y": 71, "w": 57, "h": 33 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 33 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0075.png", - "frame": { "x": 1, "y": 71, "w": 57, "h": 33 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 33 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0076.png", - "frame": { "x": 117, "y": 72, "w": 59, "h": 33 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 59, "h": 33 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0077.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0078.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0079.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0080.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0081.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0082.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0083.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0084.png", - "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0085.png", - "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0086.png", - "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0087.png", - "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - } - ], - "meta": { - "app": "https://www.aseprite.org/", - "version": "1.3.12-x64", - "image": "692.png", - "format": "I8", - "size": { "w": 239, "h": 106 }, - "scale": "1" - } -} diff --git a/public/images/pokemon/exp/692.png b/public/images/pokemon/exp/692.png deleted file mode 100644 index daa9db0a203be7bfe9b81f0f8ac9199b1f7ef65b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2580 zcmV+v3hVWWP)*$hx9Oqoww5il7w+*^*C`(BgI1u|M3Z$ z=!X9=0Se(4E&m*vZZcWxIJzNv=II=o>1n+!h~w$acqy0FgeFkaJ~{l*h>sTPWHw#v z``RY}n7&<6TUF^q-^bBcp2ITX+Vu$MNNcTA^|S6llkj*xN@m`kPG36|(I>adWQ{t? zW*K#F__CySwdYWc5KXj>s@Xdf#(M7U{N&35X{jxTn%;!!b2!&~RE{bvqhA($nf2Nb zGkOxf|Dlr-75GO%&+ne*sJqwoLq%UhbqGC5QdV*-9DTvdTvTd1G6+QP!gDOV{;9C$ zAo^g0b!4T@p~5K%3oSh}I<3}rPL&J-QBLmV$fC<9KHz26Ya8M7rv(cWElKYA>ZjVE zPGg^0DQpXa5@CJie|gVmVqr|d$N}q6D2y=D+*Y;e0EM3xr7$>we;O%k5a&`@(f_OB z$Py0xBA-#|E7x}dY+GR(VG_PL2n|GoD%o#=OKtry*@-`az_)tN&TVw!JA57M0?q?J z^Dv+3?RuRDZU6zqS6tiNCec~Z)w5zbI}t9QB1 zGwqQuej)%)o7?m#i)~?c;v*gVhRsnpvueA(?^gx^+Mv|4?-$qMAn4^;#xN%JKd-fm zl{S-!4>E{vNFz#Zxmi%qsu0eJO|9ClpZ-UQu!U030Txy)oPz^U3g`QmBU7Q$a=lzo8M zP2IG-tueC7aje>zn-VI6fUQAYzd$8I?!ryhdUlz?*=Zbk`}K~5chvrPf`i!Z@jkAz zv&Yle&0R`5^v7(;hp*R`LBKvK&lye(ZIrr`l7f4 zK_^<7?@Lfj?76nd+Jm4uFe=G1E>tP3|I5?ap(M=$QMye4tV00`3*IYl#~n#H&$Ug~ z9)w$>P(vMtw+QbTL19N#R|uw|n4NNVoR)a5JvCW-5MB$)VRT7Y)wV67fd@iasDM&i zvN8x~--hy>SdgcqDhZR^8I4^XmD=^J2YTIzG6QCE4#Ra?89E)c)UId2;kts>KcE~2 z96ETN1|x#mmfF8K6V?^9b?1hLT|-JY)^>hfTF~L)sQ(W-9#U8Ag8m_fL0d=TcDrEN z7nL>jMY|y|Q~m7%LHn-!a-^_1%WN-HU_hY#NGsK7CUCYlDt1ESk>0f%Go0;@ir4UA zfCIvraM6E1tB(skH!m*R0X+XcN*fD%qx=w$*8d^k6DK_66n3_SEw|C%0? zO@YZ&HCR!`E`ZLctj}C~+CZ~SF*O^1J)Z4?San5ZeZIx(TAVl1Y*Wa_UmX)~?Xm!O zLuGwF;Z;*;$NU92Bx6%ZS5=qeyk-|1N5#juBVpT4Fn`5SGG?2?bX6ad*aaed6cr!i zE)-$16Rvn6So0igH=#bJ3#wg9x1zE>|28KZL+k{IBToOQxe4_#r8VKBsI1Swfkg*^ zo2)XfpSTG^gR;kzet(+h*_Q1licPVBsx>YO;Bt^*keKzv)Oj1H@?0G0zzL&jqleLb@oi1GU z78bXmU)&w&2ju8x_7Wv8S1|OzeLFrO-Fw_bR!lDk=!W9G^cRk9n0~1SxOFo%K=)>? zzw-|BFT^jGJ#^(+!-|A^k@BT1j{ZHLCgI1lJ1@@~mgMSz8YFxlhbsAf=IIE!lJcx! zrcT0-I#mO7vGH)4pr3#pbmZk(!)T#!>DSov0O9*rp?e6I+(Jjxe%6r8yk0$z9Bty; ztNDQ!+4X&AVR_c@`RSd1*h#_p-T* z6LDOZPt2<|_uN^SOq=5OV_u-E4;xxTo;8pKQhWB$P5$#Hm>!-&FXz47HxC=S2?u`2 zLo&kV*+a*;2m<(inCDyC%W+FJ4;xxTo;9%dVtSTm4_#t0=O2i@E$!tjjLNE;aE9pj zC+XQdd*~G71z^x#zUTFrg#C85`mCYaGfIy-3a`uxv;D-vv|qRDR-ZMbR?itfd*~_* zMmYI|dwDv63?7fN_Ok}NXLba-hBCsuK=W=5?LhTe1C%{yz6%>bwnvzs`L8os%a4RS zYk2Niyo5f&UvYE4ex1s0-6cY>dKN>Vm$1fo14?%5E;0LH^$gNB??Bpy$3#ixshx-FiUJeVZ050$7;jhx9z8X@+n#ieWXh qXKs2x7(p1^GdKOmhBz*N`uHD_t?dpKsbp&a0000gP)0000gP)t-s0000G z5D+FNCUaOKB1>~XKtNw#U(B36|HMrH$XPaYvm>I&MU~}Y+VZ7$Ul9NR00DGTPE!Ct z=GbNc0AzGYL_t(|UhI}JZrm^sMMYZ&;0FTGaJMSmx`^w5)k3*L)TlO%095NrI*E#f z&XHoFLuE%Km#RpP0H+EoH4pRo=g&~pzdHX9z(xccI8p?Vd;|DMD8uKh<+4!VAwp3R zrm!1h_z)KnpfU%%6Lvc+8^A>ffPE)`#aLwxAF^QfG+u@s240)kh@h@kWq^o`1q3A1 zI0L{Oz6=XAlAluZMMVJkp%#weE0b&lEFi2Y95N0}B2z>+odXazNBNkF3OMez;*B zcIFP)zlj*ejB)Ly4>>h}he~G^$}NSm+9xRkKekvYk7Ujv`4q(wlfZ);J}FiKwL?3>V<{^VsG5rU`i)xefJU5TG%epNCU0&QV1%s8=@>uC-BM8rov&ObBLAg>RAH zin++Gi+ApMS^wOfn=%dc475IB7!wm?kf`#BzXD zPkPo!RWPaP)V;oQ8_>)uVQmk%XoH&N0yJ|$;;;Y)yx@1M8L5FhG8{FU;Xti`rh9x% zp#}FGwZW9@f4R>S5m`@VMn*+SIWjlgXbkWW5#M}%MOM`(8ofVj<$lM?q1I1bn_;zM z!A6eMNNym|4ep4E0Bm3n3O!l-5(}JUAdVyFgIym|e8cYl0+aBbVbLYwv~mf~a{IQl z=!-I&LDkUrv~u$<_i1#~Ld5A51#r8Z`Do`|3mhIRi)hX6#mRMq@11x9O+8C0S7pX?OGu#=iMQ@eRnd^F~MRx);Hx;F`96#EZtL~VXHW`+SBd&Rr?e92pa3D3{ zS(0blU+i=8b<2&DNaZ^F`%t1OW948NvKy2zW95dOCn!Ssn4tAh9D!>d_R=(pgnfoX z5}Id$B=I}@Aj)aL%C&uJ=-sB>0l)Va5qmsC=)-tvLtziiN|?_QbmPJ9Z&T_|@$-baR8yzC-jd(hf(gylG^TE zVt6Uc5>D0OE{gZk{|qY@3KzH4!W@pu^Y~9txE9;EcQf3^O5{H&AzW=8vm6J-OMh2B z+VD~_A^pMWrhlH+S}xB%L9nhQkn;V*J`G&emQ>EYnIOPJ=u6xTdBJuPi89J@qDMDq zm2kQBLh(27am{fD7cw zaDnzmOEs_S@6(`7Ih3R2@$m5>ZKF{^1|4%cR14F}g~{Pj__?>a;CWsg?--NeV?^Ot zxjq?fPbqiX^DO09d1`<$F4GmXt;+FSnNswMAMNL!cgOQqR^meGmHQmaCyP$1+%!=Q z1MaK%!A{Nek}?VApR~o8vS|DV3#U}j$|Yga3z|_Bg*Q;QolxXM{5%<&j=G%$KWgJ< z=1}gB<6~ zU6s43=J~k5r&FP~vU7V%x$~@Yw8Uen+~N4NHy)vMaTGybxFMxRV>8DjcRPwUQEj;y zdm5qc=O3^-?olPue(HHeu7keKbMa)uqju7}2!1qr6NNibD2hBaVQ)BJQ!#NEwJDH3 zM%{|nx6TiiIx{YmbEq{shsV4blsg!IiE5MF#c3|} z15Q@~1>J3w%~YEv8=vPV8>Y&QdgC!p*D4UM5wPH#f;f<(5Q;n0Ny4HB3paRqs5KN9 zU>+m_DR+wNdJ}OoV}v+MH=HP^_97Q%KdKya4M&^ca5?hOPm5efC1M`h*|l<~Bwc}7 zgEJ#5Hyub(B&_S>2dkHtEMpic;oQna?O;4Q8{>$;+)U1Z!bvID8xPc8!r7haF{dl( zr;pH2Tf7^ip*M0dCE_DJ+n0uNl!lMlO<6e(B$(BqN{j#ZW@dRRh}_CO3^vAtvvJTH zM=h|vvdE>MfQ*!*isBl{j*y9PBj(&I#O!l;HXe*GdgC|YiP35u=Odg(!hvKHF3COE z>E-1Cot6C6mQxFb1sFONT{faCLFetieHeHkF za<}C3#HHcNTEb{9a_x`qo7hjuq`Y#sF2-uM(NeTD5E{oxvAO6=+=O}K-;i#wxBy>KZPQn(~}+C98P=05h+CQE3tBN&VZINin4SMk!|xhEXT z4bf&gkOrbeQ8@}{FRvbu6bblk4DVxFR~wa+1_M-XW3X^%T52*-Ax@h~cr$1-BDG1m zfpB*+T#DFF3!bZ7n@q=3leLeQaJm8h8+;iz2VTU=>6R>e`Wof~X&_341cwn>7-Oy0 z^xKQRe?LivL#dq9dNiVC-8y}+oG(PpHAv7=@1vu((-0^3(R=x-ta8bb#ZS%2$jjH- zlp)l!jh3W0;1aiXmL9~EzFE1OfF6nwj{2Q*@`mX^$|`5+V=~L**yABkIU4(k8d3Wp zsj#&%7)0@{Z&r3f!xicf1n=D+CIDz~oKUnjRsN;DC_l9;$9}M6^HUx$nQReot|9qc zn5fYE#{tcD_Dvj#soV%a*#{$Bzn@~|IFO!ucZ$DJ!-G;cFOQYWN}-fnm%<%|tw+@S z#SdwY!Ai`^@n}fp4t&3j5iS?jRBDr-Zl6BzLXmGSaxFI*g=#aVpJvlTGic&upe9pk zE~KLT$9QA#70R#@A93M(A*f-viGIovPDbIubRc=lq5HvxXBq&HeSDGkU$t`4 zJrZH-;l@_H9mHBWqbB>iu;{NoO|;QHsdj*~#7pCq)C>5SCrc!>sl{OB*iSit%+V0_ zML#oH)b6Pz4&u1^JU$MKVjgWs?o_#DQf_Bwl!PDRkeiJxw5pAJ0F z{tKOsnv9i`4|nGjU2briUt)btP{ZI;*@GnY)89@7k{4%`7sH6IjQTxTGPLOfPUK6HjoV@)|$`n2Y`RM zf+zQQc_72c@Hij;_jr|+Yi`BSZ+`t~0NYFQ#TFDO_qg-8vr9&8W%O_>e)OwIO6!Nx=9_qH=g|m4xGR+t#qs?a z`DrS54{d()>oF<^>;%~eYBwqOB#YGkB(kb3tIcdkKji>2ACv#s-+i1;c7)2cc6PRY zgG+=~beEQRP#EF~(uNS;c zTvf4hf2GtWxmB>DjZ_<8sn`hsBZzX(fj^0$hg*-vzxvmm(J8vzY5eoAt;*43@>eI5 zMId@_c5u3rRun~DR?diUGD?&U2a>Ts5ER{T5Rh^Qi%<3P4!5>2jCRs6IzJK?<@l$C z8<<1Jub0N7cX~8Ok#h4|?cG#4^d5Xf;4Meqzo*K%BIw~8wHeB%OX zZZ0|-pZ>X6xyKz9;d@#+9Z0RsXj4x1Z^lnp7uP!IJ^pGM;Px8AvNUT-uSpNJkdKg#IetlpC@jXp_ugN}2uupB)+=SpOC@Li@A zG&V*koTzzYn{pje+h3&|L<2wtQY*Um{I0ezOy2EiH_a?>88gEFRixZJHA*A`e;f7U zV%&qdv(d6;4)h$19dd$+o4)P^7b7tjM}q-x#h!cCrEKhvh{>ExF(CelEf^UlUSn{xiH-$ASF@)n}VTkaMpH!qzh^Hs(*l1^Z- z4!XbJKTisj+gIZ$&)yvOf{}P<{QRSVj&PfD7(v?SdOea(cxP!=5EkL+(PqC)-NPmJ zaQ3-Ru_^iec``#eI1n62_}}rU$1<2bhOep~FK;f|96#?Cne3;+@#r;4x%l4b$2j+Y zEtmsXICi?;>ly~@bFaq{UX**%2m%=QE=FVYhBqT}Q@t{CyzTRB*@s)JeQ!Me_ur#F z!lqmwGJ#a9eP?IcsvJGk)k)QRO`k%b1$yZPdud(;$V~nE7Am)lTJP)pXW|$3;c3R^ z2k3N|8b2PB5Bv+yV%A7s)<}R_9b|-KK3z8Qsr@nW!yQqj_~X5ThHbu+S7lR!ej`bnNKf;FM;!D<>mJ zPe@KD=FY}ncJ0ARPRGLW!|kbPjW&NQl`|uJm6JCIQf&@jBa=lzG4?0%-AoQd8b&xr zyvbhb8g&o6m7Pset}FmzZUGiN9IqGI$fDdXOd`Dby4Ltq?nSzs9W7xvVng(Bg8#JX zDS>h$P2M6GNVVGg-i7oBQ@F>t>vNAd-r0<|Lll8=8;-FO z$I(h0dP4RO79;9^IMvq$DK{6X!&FVBlxh04__ z!oi^TU}$(=v2yi&Go6Zts8LOM$X;4PyBn=YpmMaI(k3W&O)3YrgL|%wdupuO0^9{D zh*ox5J}%N7;VgCZAn`JE6r* zUBa!z(G3W{%P!}3(VeYW<{(k5wJEmYrOvT20Ng7(Pbm`=8Ke4dc}py~$Md%7=n_)5uX+Mz zOpO*NaO*BEX+Tof;gV2AATO;pj9Dv>4PbY@f^sLd{))!f~Mf|5>^IWwVd4!&IJ0tPYECDdhkNfNw_~S69vxGpv7codFIu=za>R zU`AKHUwRsmN9NZrN5L1YoRia9LH8l5CsN=Pw&UATrkwYURe{Eaa&$EV5+;D<499@-kRB=YI^C1=~k}697mi_nwC>; zuBvjJn(Nnr>zyjNq-lDD+hm*jvKo z?i6XoTG-aegxiG!Y3f#Por($y=-G;j`z|Ous=v|A(IQRdfLabG9a+b!J9l_1!i-gN zKqqFBIjtnKTe(2;vYSCy-^kH7lykBA8P)H`7bZF7=HG?=Jr!1V+-Q=5ugkFGae{*I ztnZ0(oYr$9V1lFea{)^j4(uo@#(4`E)jtDY_$n3Q?~-Ttzwmij{BJy)o&-x2HgPXc}YU~ z{%4(i98dQ3K$n9Ou=VqZfxXU#7QqN{)&)2wED4o)+1tzk<&-1}wx+YNJW@oPdJeYjaKktc>)aSf% zMESJ190&L_z;lr+z`2isT~Exsf}ydFaloiHQVmcxNa$Ciphh{^Is3`;q}j70jwP^Rh+mejs}vBX6&;yIdp40ms&M;VsxcAjqBPc?$&L+YvV5 z@)g;4l~vviS7S<7u6rA}ZDQrNzmSso9i5Fa4?9c$Fv?W&w@GblAJatxb~%%qrG#VU zK%&}`VEvF-Z885c*iA?F(OnrP6MIv}Dcyxv^#wY@70A*q8pL7U}>jt%&7Y}IR*kA2!Q(FA@ zKbx*zgxM1=AaydF?osDugcrJ8H|4;qYFy-U-7Kg>>TsMYcRH33tOy(^X5y#eZqEJ3 zPxjur(Y};&z-<}%t8fx-@yQ|pb59BOR93G2zC95K$Au@#cWc^V_rM+#LtTx-BA5G* zvB}vyPn8=4c2*k~d=d^DXrF$(Vga?fz~o7&g##D7_>iVsRkFR^hjxe-bHMCS3*MDa zl-L7)Fz@4HpD5kHo`%UFy@P>W&W_DWQEkb?@kluxICe8UPr1@hTy$hkJ*-^v3{W|r zg|kS&#s7)gGql=C)5T>|RT4b_86fE?*Xi3cO zND@YdE9@*Q=kx=8KK_tgVCGc<8N#hB{z(uJr}YkjO+nb~@=X)wHr5ShRer9w0ok#v6_bdzbWG)(BoGLziKX6Ck4@SJjAbolG^7WQ{y6n}!^Dx7c> zmSeR0IJ9KsvkT5NJXEbcFRct`vv;b&io|LwY64#+$*_hiXQbS4Tn)!s(F5?&y=zT!-5xwrQXO;YY(;Z%7x?J6szqdZ%62F-pe%4GYFL* zFhtgNivVAC@uWCYbK{hqj5)269U4Z$aTy1eegd@X5T%gtJmyqbaIzfVMR$cXtoXt< zjHXy^1Cj4#rX^3Nd7c_8%_L?A(mkf;U=M_q{!N5ivu$GK#M`8hPI}bdM^Wf1DtwEH zx@Cn!g4wQt!X*KNrlfKRFdPgftTykC3lON4vtp%lsP+KG^VPAb4{OQiDuP&V*IX~g z7m^{?yI@YMtq0Wg7?IQ=Dc9rN=v3}yN8WM}2_qyx6H4*VBoKxQ?}oCC&NKzXR{JdnTvcd&J;%9dm=W%2n_2xCgpUS} zF=z8w0mlgah|)-)fHyKQhZV);`C#&48soDoNP++{DhN8r7GFa&oVJWNzm|S%i+|QQcbB3z0ju?1soI12{+|`;!^dzun3ahHoIvMldK;E;we7n=oMy(%eW@1!gre-Rb)wM)FFD;`gHSvVhP4GlsjlMikW%62h&(-QQ!v#n4I|jqY0S2plvc?gpA-ty9*48 zkZV6jDjHb0$|JO*+~_P|>HQF+Q)cxS)8v@58MWG*L|!AjnF8Y_8RY<)L`Mb~k_K8h zr5t0WMZx_lym_sm9Zb&3sfH6sKW>|r6oPj^=*J+B?d@_rn95O1kaCU!#v!2(d4{KSv>^0R+SS=F#Bf2D)5Mx$gskU2iHBbNX6vi>pAX%*aTgYjp zPRo7n7#>i!NiHXIqwAjBS2`qAG8xg`KE)&_-zVsyRt_5DttgI05Jl0$B799#5Filc zNT5C0O)`y@4pySjeapd+De%Sg@7e@L_+Kbw6~<8`c>jn@+^-mT>^OG}`m47N}? z*WJ29vQZ&%fdWPVP^=u0oFvGpoe**i-E(XF8u^9Z-6F6~ooL6Bp6=gIQOw~{4l5lx zcH;n(^HZt=!U+2~m%=z|<=!9@_kFBjfusq$f!VY?eJ-M^BJ8Dqc$^Q33(KqVlwbJE z^#g!9NL(ZhvHX!8IRM}${7S}1CKly1zfh#JOUhks?w(z_x~RYBqi{ZR zrKhmcU&TK^a&^xHg-`kZgJLi_C*s}RMLG?o%EdcB`5}U2nI`O*i$YxHMi~-uGw?iN z+~!iy+z)KYgqc4~YNK&MeU?zLHam=vbeke_hm4h$Js?;=@L(|c*a0SI@?(cFj!D$p zLMCoB0;UO@$_-_|$#Z=^mdo3qMrQmx|9KhpOr~;aNEG+>l7W>4ax^d}1_X0j84;|_ z3L})tF;|+g(t(uQ4JLo6dQ<5K8-wSnp|zGU92o@fNh@q^{%1ousg#DaYt*;|WM)HD&vjJb?mY z9G@GFdMzN7H+Aaj>JMluR{!Cft>{<3QPk(b`+T6ZS1>fztbWTS40TgDVn51gU5@VV z&s1)Am6g+2>4oLZfElH%+`&zGV$0mwpds)m9CFiRi*g-*LVX6D3jh>?7kj5MgJcc7 z{egOSmR7KUT2w+GAL*>?zMEk)OgUPMj^hKY*-wG$(z~nVS@M8Unq;mX_7;g{l zCMm~6YJ5{IoBI%2r{{)pg#}E9giI#kMrqVZUGbclHf7<|@4*4o$v|VJ>3v(z$UPvM z=_yLz%0e!8_{aDszxwqQ-`_^rq|xuEXoxx?c*mmVA%pjBAx*fxMrV)3-TvYP``kmW zGA=K|5y%JYYM!wgKhhyVXFR-S<<2F@cq`i ziFx&FldT#v9=4m0RwMj_##ZG@XISU-B=3)Q<1NH-BKo(oa?9lf= zMZ&E;o8Sr=Ip4cQs}qQ@W_Yoo9KCX+GNv1u-WK-TvdxN{cql5Z9DUe>V7ys9$pmr9 zSZVTuF4p713mae!jbNJoP0a;FIFuhaIXlb_{R7o6$%@|FWdrlR5RIHCc5x4CR8;~Q z5{7cz0+N83O%Abdjt1w7X+;3232wjpcEcmOxJ z8kZUXP5J`Zsx4SMKD!)arM+l#q~Nj^H&AzrNPvEi@Y)zb(b;}csTNS*nbK=wO&P%)-al}cYf5z%BN)O_O=%%?R*|n~-jG3O0TA-V0!^_py30ws zEKDhC#Y8XW(r7=SjmUP6^QT8!CF!~XbJmokbT+78L|@g*%A^U8z)?1TKhGkK8{Yum(9X( zNn9Zi4;i?bloLbjr=U0Z6bzq&)GG{7b2d-xuyY%*^YRxWzTaerL*_%)?Yu zC|J83tQ7!dPKp*N^QjSf{+h)=`Q}kn*leZ!mXQJwe zI}>B=C@NrJ$(J#RID)R1V+sI^>~gLIscsGQjYx^qvJL6og33;ad?wi%JTEM&fv za6uBox2v4HlYvSZL~@g=FOXYx$aYyJFJ@^RE6}LsSJ(d%%2g7MI4$5r*SRw>pF6

@r)_VP8Gb^t(^NRh4Ld{CMB>|*REW7%+wvj#fs5SsR!I~Dp&q0 z1vh_9XsVuYi$Iy7x^m?)Q{F*-(@@w*2QoEIz{quHYII+v;0_FQ*aq6NaGd5WnZco4 zb>W!8d^0kHvmh)fH6rpS7LKA){wjr<6DUl{7@2=i1~J{B1#**r4a#LH9p_)?m$los z6Zi-wW5fHpuTn6F3kWu3=ASFg1>H0%wy#OK-W0zGmCJs*SO=owIZ~qq?eD%yq5Nnt zhL3_H%P!pO7iujR$hdH*)@oUJnY^lPlPB1Brbe@ zyx9G=eJ;pwQ!^w+O&b#~0^iLe7K)W~U!`Ch=L#- zF7S*wat18ib(KPS<+LrAvGWOT!pEqvmOaLkMtxB&SlyVZFZ>Sc2A3f4@yo6!%&>G^ zrQlZ1v{lf|Na)EhEM~7zD$T7Hheh>ZolK;x?htjRBP>grt$j+w929NK5kzrU0 z*HsEHQMA1RuDue@ZX2Y^=_0oGmOUs6K`x#7l4?e_Qkns4%k_$K8r<}cCN zR+VJ%_EUh{&Z`t~^;+jXI!&tElihwFtXWmXMORUum2OY=pwtQ(7f#89!=n6fQaS)Y z@l68ao3wU7o6wbn!YY4Nh#j>b~IRey&<*rib1GK_SZLPC`VapJ)bnkB&q;czy zeP5=y@V&Ry#Fcd8ATnS{Dc{77{!X$4K3dC#E-Hi@b6=%E-bUPnMDO17W{8m5D&?L? zPC8DhZReOqXwvYBOg?co0H_1N!U`jMt8^sHFe-p0MK%l%r$#B<1ez5`QJJe00O;Nv z_f-mHyPk!+25+HgIN^`NxJsdW?z&2Wb<5+ax-ZDK9bAMDmXTp?v0c}ob;gBju&c*WQDqQo!k}~* zup|q@k_mjnBZg){t;$sjCetdJ#(-r=%$i}n6M-m7-kchUSiGGaUaZTYh@p zX`?6L&fVvb0lQXkIx~#;8Ew)Wv4SN915pNHnJk?d(5#AADX{G^{xb=@Q=&sAcNag} z=+>0f6;!ifrWlq)e3P%3Z{pyX9~jWAYF8<+a@zJdXPaSIy9i6H+?a9U)s!8DKH*pa79+}Xr3Rfxc5Yo0SnGQ4$D<_{6nZ~4?aA7Hr80;#g zt~4kVG-e7nmMq?Hr^p{khB>(rGvCBWjmP=a$k41{uz)YWDqN*t+5#yzJX~uZD*~@B z5N8u~@f}CMkO8|^SMK{Dk;>={?;&PNqeiJGQ6xqHMa2t?IOYd8RrF|R*1c(HmbZ)> zsc@A-ZqO>FEf9fam`K%oT>p__9ff)2G9@KR!LDZ`33jcZ9COl|Yc&Xh_xv{E#uO~6 zhe04ojLQ*&Ycd2rzyNQqrK$1!Sf$1{8E96Os}#)9L?D{vh_p#MQk@yr+mc~zLN+Cv zYFar4yKXRFyMlVOC&2yqDR3Tnf{MiWjiQZZluJ40O-_wNl^Xvfp;=s1DqN*tj{Bk` zQF45lK!gq zZ!_|bbN5=xrn;Z#-0-7MN$O9f)C6|@7L}uQ{ZevMF9=*Sckj)zg)-F?wI(SCiz!QZ z;|z<6gk~+Bs^+QkRSG7Upo6BVZPc4MInAh*TzD(dDG4&z^ zOH@2zCnWZsFxa*FVALq#O(Zf4m21RfBp3b*uPHB{uTmq0-fz)M z&k$jW93GGTw8~Wqrq5}faH^cVN?;6|VWddR|7JW|(e#0`>blM+5lwXhpnSBNawez^ zG~R@KE<*YW2_t5tM(sd=UfO#th{MB0rTSG0%wg_4eyn-I%|!8p&!}^H9o<7qO+z)S z9_GeLR>htqLb_thL+>`Gs;nGiJW#mhO-Y8?wM_9Yr$!tE*gUCuaWv>L4Xwge3MSWH zkwdTpN>b|J6F9*!6aeriTqUpb1@c6i{|sS&g0IcU+6Un!iNTr43u0Y;c}?a zIz%A?_!OqinHoh3Vw~IZMyFg9OWUCI3wI3kM)TVrS7{ z(?H2p6fTEM0Eht?GG!0FbUSl*0O7uy_kPSprTSG0Y;1tte}FX!Fjz5Ky9`UT1O=J) zZjo~L#)E*n`^w|(&rG)22DCoFX~AApvH_0q?cun$%0;EZRSNF0COfC)kM9jm$+BR^ zzsdxKLgFIxdep6ilCz^m8JPuQmA7I^T6==y8xgfTp;1&SUZr3k)Hxv;&Kor?mh;(u zL7>*yFhm#o6cwy=MW@3gQb4ez2(be0^4L^>8w7RbqEhWD1^4h4JR0CzEaw#xSMX#| z^EPp!DkwQKjfhKPYou=3`~BDAuqEdCDOb8m!9Bhs$#BtvShcuZ=P^Iq350KDlP?#z zp5YKqw+YkpH!w4Xwdn}1a+QL6+_^yjUa(Ht@h5C5hVUUuj-#R?A+SPf%sOg7nGu!a zKBnqb3T~Qk46T|4{_PuVDu4*$Z6@NDnW6$S$vOZaPeKzEBSx%NxJtn_0P8<0R~W2C zl^ky1JmqKN6gD%7D6_p~2sK;xC~bR3P^`W~@kq7%GTQSv*XTq@b=> z6&%lvguA*&5Vt7}tU&I&9_4yje$B$)3gqax z&k)FiU`CY#apr>b+L3U|>RH-&}ZaBv>tCfuN|KQl&yJhe)U+LfEc z+nuX^EgV^a?0A-4RSQ-|(R}^C_uX?aZfZ!%ZQq`|l5o6up2CPXkc7!AQIN+HnCdfy3VB-L~UtJ7>1(YR(dFquhqpeHlO7&}(1oWtqR;~drd zJVThgOh*=E$bG+CsNJrAyCgoucbg!d&_X}5FlAyH<411Xl<9IaMQC)ndP{7vLpcYz z@2x6WG&jU2N#ZOxs+fMndaH&m*)EK6;dVK@Tk|Bp)7aumC>Q*8dQ+&J(J)k0u0fb3 zBU&TzF5YKV^9f2M*>HV38HH&)F6Cys44vz> z+uOASQXt>jH@oso#iO%t79o=e_o0L)Va<{*j#F66!?8;_625;`R1n5o4u1qUCH_d# zk5-UKv{PsptTTlrcp_uF570iMrPV==K7bu1Hhc#8L&2&UtT`gw-+h9}Lw0ZeLS6}S z-`^qguEGF^O*nQ=0q zAMHrtOhj3>tiD1&z4^N-<g8MiLbJ>&Ggj`kT;RC1vpkk6aS^qXsL0SWYeq@DI$Yigq z5S>Mo(hzk6amgDwd=@TGgnOl{HwEc|$_GT>i%CJ-Jx#})O*GmIZh+(^-a+F#-U?Je z7&d0$k0$h^maz2n2B=Fa*Hf%57Ow1#9E33kORCa+<`YSTL*+KTIms;+7TqXz3dns_ z_QKD-%>^>H@Dd%viIXwtM`f&%u-zM<_Ux2WK8PAOa?ESt%<9N}CIBETvKO>9w$RF- zW8{rN?h7=zFD6FSH7NHgLq7tuE}Da(V-m$DE_WjbHMV_Gcz9x#o`OY6-ZRyF7S6%0 zo1*?nIwfY4i0a;TD0h&dAAuXqr1@?H9UET|0E(n@v{`&80RM?axWvNJqs>F@%H%#Y zt|S~|9ggz_B11nCC6x<;9+8%awG}Nv`FkavDR92oYny;l-aHFu!Xm9tmW&yfSW&ql zq^zLHpmuheesq#gBbEW^4H83hfmpN?QC7Jx=m?~Jz?*i=VG(ANv4z6s(e|8samBKg!ULMBe8BfD~r0ATxr{*jbx44ALi3IQde>i{sEr zgd;~HsOd;~89g=KG$lKZMT9V3iLxkE-6g~>GiD94TZ{tF?MJ^-K;$uOSq zV4@r&svJ$EFNH(fTBbqkj!R$*BTFDzmAlYoFJN+Cg;8Itx0?aIGC6>RI&TT_Ihdv& zJ=lpvo}!`oBTNgD$_;dK9K;_DgdIDtKk&eFsH_bNo*ok&*-{=kA&hwQ1DIX*5~FDv z84W|F0i**dA#oE!sX;r;(vR}ZdcC;@S~>MXU0MB>ni67XeZ|B`3t>CxYV7w_(##5UYR^O?hZ$_4C6%V^x&v$31Wqi7^RS73&a+AzuMlS` z-wlxexP{5F1q*kJSZaJ9kOQ91M7ZF+-98fGp0<-?nW{D^A1G|$QV^r;u5D$?-m)}C z)psZ+hTocU=|_)DKl;uNgLsNLORF7Uv3Q^j0EEWf8J~E|9$Pu@GFcj1;OPxfTMRm= z+@hai3y&fKK5J}YKvTGsNk7l=Bcm6O0{g)JO(9m>t;llQ|v##_IkvbW8& z#7cv~Km9EIXb!9Y-E%unuA|X-AheY{P(weQ1HFa|Kdlopr6;7UhEx0DN?M z?o#d-2$P%UC1OZy?VxekV#s1;pX)HMBeEjHa{08$ui{P9EX~dLWu0A2s zj!+>%$z;UIyLwOJGfaf(N8YB!O$j$=iN@<*fbc8~*5^dqOb*=F`sCp08v~JD*94Wh ztD+SXfdn2ROjjjf;baRk-KNo##sT9gE06jsW24&2 z$75n`?K8;8dWI)>`Xh#?=WgWiJ2-_#WdbTk@z#^T=ec*(?%m31Mhk}o5V=p;ejCcY zErrW}`zVsynmEhX^~SHX{d5XHuK~$`?RwJprK$ z7b@{S?VjvsSH0|qkv$@N3MBU~8<52+(t*gsRH886ju^yK!ciN4=rvy>1xiu6OyxAy z9fkpsByQX30U{YDL`P2H=?}7pf;yuzH5I0wV*6I!ETeLc0^#S92@w8KG_V1}WP*xt7rY2#GgkDXe+c9Aiqua6TKX%)?Kq?sVgWJc(}1 zf~VI3-p!8n}>{>XM9Yccnlq#8AqjJf?`4z6Oh1|UP+ zR6xjg+(1-!BG1b+QLias$8*>{Uj`G6esjmE91Sy>5TEk9*Xwc(xO#}s+p3Dd@+hFQ zOX-`!6?zhOEHTlgm7@#6BW{;2F`yO2oJ`6MV~%SJad3dqK(Wd7`%u{^5LbFo7O*|% z5y25?`~55Q5WvZ#%y{5!|D}|J(l74!P`N{e?8RU$jfqBo3uBPm7gu_+oJ`y!VAj-i z4-ih>YV+#d4;@7Ts8OI`Esco=zz!z*p#1htK&4+&^G09+>$t|9>yQ?8>pcF<*KBeP zPmjGzVWMFPs9m~3A`S*3pG+Lz$yHA0UFz}ttwZgqFYbSNAau`$ToTY=v($-h!?sAz_ z7GqW}vA|{eR!9AvksPZF=db33Yar|x%3-2|tCf4^ry^}ZT$*8cPRP-1g3Gvx z6W!wsGO_|K))?P4*yMq1PFKqacYIauZlL(7&8{P1SwSBJ)+wP}BZrLiL zG(VIOPSInp=5ovtD_dCe${jhC(~inew1r_<0TJkAlfxxZnT=rm&SjHak`_rLgrEih zmR>H4Bsy{_YMP8SS$ z_b%n^X`7UTuRkCOItt;lD@VT&SHWgq{BNI-&}byTq4_RoqLDQ>lG?)}*d`RV`;8P7 zG*?}z8Rh=irkopj2s(tcxAj4dydMOI8Fqp%nJ_#EXe5O*^9d^#D|{adz>Zp6!4#rO zeCt_mY%TY0t_Y`X9YP(&pa3z+ScQPIS3y9C6dR@s%&;&CP>a4EsG1cg7szO`G))yp z*Fa@*YyB?J5bj_lTM9j23MZ`O*~U5p&is@K*WXi^u0H)k&jB$Twi!#!R{bXg=0Wk% z%_Zv%tS4kg(^Q?U&E=k!_E_@nh&Sef@#s|l1z*lts2t<-&lzw=bhB2ddMfqLoG1(1 zr1E3Hj4bFZl-$xZRqs~11}ZZ|XM2+R!|s#Jn(PIS91d;zd71x8m>o%yB8iBq(~e}e zcf&Sc5p45ihh|CfQ5@fW5bol~MFRPzl!Ku!Bwz~Jp&nLqmO!@zkgt@>a; z68(M9%>d5G+kPiuQxeN_8+lW=XW=lq-Sfa-& z5lC+Qr>{qJHBAfvr$p3A5vP5qKxC{na*)wfH&KEw{?{z}OR{?kz_?=*b27{zi5PH3 zRGyK>bQS8ho}rEb06LInXL&f70Ocxv)HJDp>Qf~Wl_;RRd)og?U&d)4+)R6J6<%T5 zR&r+?A)B!RLI>oeJ2o&~1-fNmn+!n;f;O_V?tDz>tk8L}1gCI+k3Zk4LZ0QmRQgRv z=Xg#aTpz6kFX+nTGaW)i65Fx=UV{-SnrEajT~O$6xIhEt!}usWPmW*8FfB+oO`_UW zGfeK&m^yYi!AC_< zviPMboQi2><+$9-qQwy>O4%MyAp>qwl|1aN9eR;R5H%1+EwRCz&LmKdFkNPDG#Q4+ zd3F{Xlxmv#rc+cvL?@}=^l#{hD=+$S+FwZ6!f8Oj+8y>d7pChl2nf>^5L;#m9MrL4 zo;o&W;oMCVm%i#FAmP|=0BiS@a^8w}+iiQ!Rg#obxjqzSl?z5aiA~60x?EV5qhP|$ zY6-h=LBg_Gnx-rEBSFTS1KF2Sca?ZFR_wq>It^fkHP7EG6m*LG{Ua+~yC0lVjN%DIrmj2x3aLXQd2 zE?lGu54}LAiVY&1AaFA%!P;?_I!1m<5zg!^30V04yZDl9B8SOZj!0B2cDS3JRVeNY73qztJ;oUB6Wr|5TsV&wvL+_g7N z;@ej)x}}x7pmHxRK`2E}Sdy0GwHeCQg#w)UPgyy}bbS^7{79I*Hqg|ub~02UuIlmD zKq4cVoy8dr$F%P-iTa_~jr7~qn$tS#8+v4V> zaw*^pm19ho1s%{_Yx;12Rv7}+8}w6VXSqxNEi8MZv}tNxRXOVD4an$FF%KE|DD1Ky zkaAq{q;d~s2&r5@dJso8<+6aYRy6+84}U!x%pmK4sQdn+BtWrpZ=|0RJ4-6(F8v1- z4|W2$d+_gwRY#u%?=Dfm$)dHz<=#;4h3plY!F^OtSyT4e)XEXKZ;ZKq2H=d9gVv75 z^0e+UIKhI9)C|LD;SsU3B1tTJpK)@I(tm5ZX}WTS0O}8D^W4!7b*j>Y=-8^9{`EE~ zhr|F$i##TO^)z+$O9Y8Mk7p^jyUNNbCeg{l@@62Fvm+x3yrmsACa%L6BOC%|XYp$S zSJPBMIoZ*#-8|48J(K%-s<|S+`57uFEJ$d{i`Ic2;m|!Y2JSLCXRk1 z@pH-(4lK&$m_!>R6pk=mb}G;OV_#9n6g>*C6h)ZnTExz>Dwhq~M7Nugil$yIR2oeH z|Gvg0s5qe1yYsV+0s76^3Lk}vx3jBqz0TXx5h#RU>7mM4cI6mE@Pi(nluzcy-)x?; z2sRSmdLmhyO@)(tXFOm6l=%uZ!M~ZNNuuh}?W%$gvt~S z;GvR1+8)Y;MIfP)tkYe>vU3@6LWb`1uxnhNuHZ1LE>uwI_g+_4>`1-6sKbx$j9B(Fj7I>ge=2pW$@}Z3$7s?BTsLzOYi% ze2H^<3R%EeZ(-T<6qCr}r#WQgYXWcK`STPRNXA40wN=QQ|21uzWUETbMY?HPi7P5c zoXyYVss}8eEQp&XYB$F}L7FZ#O+T-|?B#U5O1K1ao1{rF zBH}%M<1u&F4$zmv0n{1^Ng-qiNkH6PD}fCr!twCp7Gb&;rOVk#d>~jDq1joAR%t=r zKn}6J6=M$nlpz1jE`ydTC`Xe^(1bvyB6SD?Yu4bjJ6pMZJ^FGZI)sAPG{M>E^nCwc zEVh85nr9NxWqz#>=55M-m#Jf{Ont$_Av?og*Z?K-m>wB-Ozq}BQ4VLwe|6<{HJl14 z;3V&H-~c`pCD3b%j=q@{6XqBIK3q{F!NE(@c9BH<(f{KM-2Y%@z|byr+9b(h7XSP*JX zFsF)eLq7Mbg_=CFwHA#t^))DXg;5umVpTl56y+mOCKTZ|6Zfly)v_*ky8?vo+IXuB zZ6UJE_U$_&i<5?#V9vzmr%zo@)7@(=rCW)pNOX%Cpkn>gfsAP z+q>h$u9=736Hy)vM=~@5rg^$%3KUB%=ni9HGpT08-otd5B=i*y;_W_wqB8ZZD1#wK zxMiN|uNKyo4EUe2GBna8On+P^{D8Dk$$1({hVntb*j=Tci0=W^wFPoSER;l!@UqIy zxx;go3`NQnkTkmXB+MdIG2%&>tK<93TwohJ${=p52+vQ$`^nvMC9Z}cNo30ebHb^d zHsK_E$0>qoblYL1Ct>BjP|O8`RUs_&432O^xWa%~PZU5+l*jjX6MjSzQe_~=`i0)Z zq*`|>F^v{n*vCeYQO?)oFvCd9;`rp?a~R~3XH}T7h6_ zHj^Qz5@$eg&10iH6i(QnUZfid*Afng&~BieTf*nUnm_NWaUY~`7J^!W0JU5SCMpe8 zaj@bE*cZ;f7KU(E>MW-ccR&Qz%*_)OK)qfuRp%{8BH=u)8F)!dkDX}{Sl=eX@EK%_ z*L-Kv{S?jH%i_H<*De7l;q2uL3OJTz1QvV`8y!tY5l@tddUb>D%RN~c+UP$n<$`=} z#IZj;EFiG1nd*shrVM}`!CuABw-tiKmxD}6ID7fxB&_j$Bm%>q${6{QQTjChT!Vxu zA54cymAh?gOj$YRB&KwwJ(R!>0%K|)rFtEw0GcfH3o8Rl+a(g0G&N(@`p2AL5LvxlGXg`h;=v>Iv$0GiFxBTrsPHb0Y(w7~@Gp)M0jvbE;Rm zG01ZEj!*(pj&(x~z=bcA9Lo<;f+rKB!hk3+A``}CiXy0&4A|ZNO`@hBMv#1tXet5{ z?bxt=(u_Dhv<@?upvbYzWD-%hsToH7{6vim z)N2ieL*;rw`@!<&9Z9AF8_IzBPr4WzE7o_1Q9fqKQi& zT&~T@WKWsA1yX^!8&JwdGVwqGeUDHbCgo#-rm{i_)-@J|BAm!s&)SCdv}oJlQ3seG;wze6C=fYoYO|kB1#Gyc_u!ml7}57+i3{h(7xp2A@ICr63R@A_GfB>f=ZVVU(1u_~ zQxVD_$w3yFsX{aw4A)RNQ#}>--nk>HeMAV9h?59P;tK<*+$<&-GUy!h*p*}Y z+~{n?tcr3p*rRD0&FobFiVke5d8V@pr+W8}hRlT<2k1c%eP9-T17E0cj0(bM3D=;T z3{xYXXEaKUB4P4T6ks$@`8Xpq6_{wMU}%f*r85_ehI<8K&%t?`>PPlePY%R8(xpG; z!Vysqz-VTN$@FHl$r(fVSZp51oiMsunuk20XBZJ5*=T;-Q3k z9RzCz<%293P31-zke`k;Ld~vRws;MLAke2gTdGI33JVw$^$dKW8^ULY)Z6C~c|Hmd zTgjk&{FW?Yg{BG!O=T*V2O!BbH=0eOsZI9^s^i|!DYzJ+hC3QscSHq@W#BCAhVYr8 z%2w`qn2!P)hXzdfU^LbCt>+TkXQB)+zF9a@j{Vf8T#zeX6hmpMZxxh?hGj=77tVyk z1eV_Z2u4<(49jiKV;b>T6Lpy8_D!H(TU!C6sj|881)x}xX*xybl#_g_{IpoPAd^ct zk#MRnhjAnxBO^}AF^rIka>gdkX|&OpjZEe066*CHQ9f2R;^b)a9s!niPTyB(syr&P zDQU1l&`xtUIqK&O&e~}P63l5bd())I;b5eCw=WE3=O_^;U*=?JShFxg_zILOo+de< zv|Th0&!@S4t10Cp+QDCl6GDy9R6p0;0H(p#IXA8$nNtBKKiBvtDk`G~E0M4hHkIJ^ z6wJz5b|gb6CF;2uMng?SIb#!d(IX>s9@Fg>GxWJ$glMX0gvyz$in=7v0kNa*50k|S znREFOsvRpDO#yPOLG{N(HxFznm~}8^koD`2BZW9Ap#zuuN28(J1`HSh$bye!DqHq&%R*Fa8FkL4T(4j z2HqV*1Sa@pA$(-AwS7A9yh7#j;G@mVJTvHX6R6iiY!bGj@kta$3|Pve3<7x1hsBAX z9;)BtOBxMDcqB>`$ropw>MyArAwaQ!-8y}+6I$?vjGfIw_(l&t4n-j?@>u@O%=0Fl zhk7$}ysM#JTDc9Rd^{SzCN!0uxUg1R>^#N@S2jrx_p%d>kz@b^j&QDCWmCOnM~VEa ziqYp0)vx$eR{n2R^2#aHu>$s@je@5#gpYYLS-Is=3ec39hY_i5%ws^kIt2BSa2SxM zH6jNvV96XJjc{3z4+rR{pN@$}Gms33Blo-KVRERm52s)gJIXtv4w8_1KZ-vcwkl&t#s8nzBP(Yon257%2$fNb_Xagylvf&<6ry)shj`$tW|A73#GoDIawtF5o)? zKs42$$E*r#gv)|_xG|;yM5AFpW#wk2`VCI?hH@`}8lbd{t73#%_$R-K#p&L;G<)sC-G28XZFO~>S>BW9{NQ~l?YRKJBO*fUaa_l3|nd@AE9>=?6fw1sc0 zThJ=T&O)tDz+TzgPau5J){blzmZV8&z%`nn+l92~Q7R!LoCgNV$f}$YPCFfOE9KvQ#dvPN>|HmcQBXI*(>!*HNfFOMo=VG3r1Scd_azip0sL^$!z#`elj-k>{F7D14S zJ#*9GdFTnrI@z;!7=-;cey{7@@n}e=0t>G#1LmyPP`SBi6B(NY42Z5Qk~bOKC*gC4 z601kq#{ndKmy}DnItNX~=xTgAKGVuMJ5)?1l!Y5T7Q1j{=eeLdS&Ji`e@vq~BY7B* z5=YoBTG3SGr-~glv6@@B`;CWS#St+e^0RamC7P7OA_=;95mt{?Yrvv2}HOt6takX#hZYP0-W2km5chE}&UN#(h{!vF*7Rlh+9 zHTaNm4kmog&ob*);_b-$7-=-C1%BGVBH1M1A!GYg!cy61Ssn81%5@a%n94Yap5$~; z=~tAhnb=wEP)S)?zsQ6$Rw0W}^4<1j(Ib*dPsm_B=XsnItU5ppEoRTzXl-l(Ek&bQ z+%Mv0394K*2-dK8rO;aPoil`4d1v3;&2&H3c2iX&Avqnmr3>Wgox8EKX%?0yzh{o6 zjo(9#3KJg4%Ex1*+g^r-1t6(?yTU?1GT2dd*g~vw0cb1gFrr7RY*do-QJd$mGj-Sp$V<}- zL$u+tVGK~%KApG;S$skm%aKZ04&|oSA~Y3_)39lh2g>|`O#X&VEXrkAI+EasHr!#9 zlt;1M6F9fYk{3~%l zmtY<{27r94{*C}a?zHg|+ABM5;$a11Se=1~0%GIW`Yy&U!JwmyDzxkGC_$!`zl2(o zkkWm!AErzieU1^9m{_)NhH! zSo&1M?Z(tz`;Pz#m1ZYll} zUJ>FOE}f25gvOneHkLt5TXgeMC$Iy^fJNL=#3gajlU!rKXD)rcn480eq9^xa^X7$1 zaH^hk==M3vz&Sx~ZnzFvT}UypY~QBM>n)_}X*ZmBn_*f25*0msNzv=Z&(6$gOW~HZHDRi z$P7-gNod1((_un92PuSDD$sKX9Go;*ot$~%$jLH^XDtkAwLq|1EPgC6t7k2~!tR~} zt-8gzk{H&)v3-^$z{%PFX97y%B$X_i{kk!Oo4m)#8AzqUGmtvBQ;SpGzeCS#S93QH z1B8OS(;+pm7WYHT&+V^3LD%@oN{pmpajFp_Y90y#)sU3kd43MRc_`>i(IiUoIGx}& zvqpudR3x6O!^v$wdMLVRlarETC(0~clv9Kj=Aem+ zu18yFQes3It&dn21)-qq!4VbnhRI1@lyNSK268e>R8(*ei4|qG4HTq)&JUJXlcKcfHOdt*|^M7X4#FJ`1tt$W`F;`%2}32S^xk50d!JM zQvg8b*k%9#2dha$K~#9!-J5}u+B^(|QGB@o0p9;{o5Xe^%hFo*U1$FOnQ7s=U3n8Y zF#6E#_Fp}w?HCdpT7QnO@2_veheT|&B{Rro`QHCbog&n>kXB6`qMiT}fp&)MxhR)OIRPYM&8}%2A%yLJhY)Xt3yIK}LTZv3WLcK*z5l)6 z#=)te)oKOArWdpD6A%amK4W)J*Y?oS{-tBJW&FFEU$x_BGBrPuBU8B z^&UlGYKyieLe70j9uICKhX}MXq^Jp_gwwRtuWY7hYa-<0erXHrfj#b%PXwAs8C*f` zpU+h=te+6bCqg4nDK(Jfsu;E(KI9V{8cQOIf*_~s@Gut=n?_3mN#5`1n!|SYEee`q zL(56(qIQz^>$z2&=Mav&?;?pUV(m;>7X?Y4_vdy7`$gREiX^7k&~nPUs2wF1JdS80 zp9nN1rRR5NeeaQD!R_vQC7%d2o>CS?QLcjTwe%nnXk|)S)Q)mrlj*Zr$S(qorBsUA zpLU4W;~=8?6OAcCV@|1QAj$U^9mkjU*~%0fT1=@GHQ)ax_Hk{Rrt*WADF&#WDV3r| zj^l-Jno_}O-%C*WA>$JFpUTZ%Y2+G9DT?Ap^t87F(0+K{4m1$|OrVhgYFR--A?*6$Me~cp3UU-|zSPncmAE$3O2m ziHpa6FqW&dLyXn37GGXPQ9B9c?L;=Md!bY^locn8U3{jZg;hW3+)tFIv>b6U zR?FqWt273nm1=9_#!ccaB#A_uK;=nXM&c_`HWCr-H&sy-C7YM#7+p#SZ5{Fj%UmVi_lSs~AbwOOwa*lBo-32)ZT<&m=aTU!4IR{+sf*Au` zrFKEi0hi;>F|I;>&Hk8y~qCokjpbs6afqr^=M6c>X{=AXFb+`_X@Z>NG%AZ-#v!UwnOwLc z`&45GRk;>ZOfFoZEp{3^s0#J~&T#|Ai*vZCN`RjI&7?pX^g$KjNgri2 zl%W|(Zb&CaRq%VUfGUaLh>EjGKwU^xM}+ZBZH8%Q3l-J{Q;2C;1#H^tw$Ld%5d2y__|@KF${EZn&7r@XSIqd4;RRF85?|D#JB_FxKnGQwCvVD&zNN zC}uRwF_ZV<>Fo6jdQf-t#r2wATloXq1LaL^S9CF72b9~Yi*yfuF?S9!04@>xINpG{iHDwUw_4-g(Nq@7Swfps! z{*Oxbj8L$yz24IApUPmYNk7cyxGMZbxqcuO(-|e3|$1)mo8Eqqp@Al_y zTj(6iXv}3ef?NXw#h5|bmc+4))l>7L4t$ir5hV>g6axd->_SK|CXQvSt7}3eQ3o-& zfJZ29eBj~ynm*-bdPX3|^=c!8r+b7VJ`?mLdBo-T>i#uM(5_ii2hRvabjtR19y2&D zv~j(B2!RbQc>4V$+t;~=l^!`>^5!1PC{E5lLeaR;EM-s4%SJ!%&nqP19~n{j1N^D? U6<`(rIRF3v07*qoM6N<$f?TKLMgRZ+ diff --git a/public/images/pokemon/exp/754.json b/public/images/pokemon/exp/754.json deleted file mode 100644 index 64490baa49f..00000000000 --- a/public/images/pokemon/exp/754.json +++ /dev/null @@ -1,1133 +0,0 @@ -{ - "textures": [ - { - "image": "754.png", - "format": "RGBA8888", - "size": { - "w": 234, - "h": 234 - }, - "scale": 1, - "frames": [ - { - "filename": "0036.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 0, - "y": 68, - "w": 93, - "h": 68 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 0, - "y": 68, - "w": 93, - "h": 68 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 68, - "w": 93, - "h": 68 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 85, - "h": 68 - }, - "frame": { - "x": 0, - "y": 136, - "w": 85, - "h": 68 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 85, - "h": 68 - }, - "frame": { - "x": 0, - "y": 136, - "w": 85, - "h": 68 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 133, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 133, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 133, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 133, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 133, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 133, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 181, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 181, - "y": 136, - "w": 48, - "h": 68 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:f79fcee4451cea6aad915f561b31bf78:95fdb55190edb6ce0d5847a4e46b4d5c:f7cb0e9bb3adbe899317e6e2e306035d$" - } -} diff --git a/public/images/pokemon/exp/754.png b/public/images/pokemon/exp/754.png deleted file mode 100644 index f6410e02a1137c8c8ec3cc4103ddd52c1cbacafa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3754 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 diff --git a/public/images/pokemon/exp/back/672.json b/public/images/pokemon/exp/back/672.json deleted file mode 100644 index f877b9abc2e..00000000000 --- a/public/images/pokemon/exp/back/672.json +++ /dev/null @@ -1,479 +0,0 @@ -{ "frames": [ - { - "filename": "0001.png", - "frame": { "x": 120, "y": 0, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0002.png", - "frame": { "x": 118, "y": 97, "w": 40, "h": 49 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 9, "w": 40, "h": 49 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0003.png", - "frame": { "x": 41, "y": 98, "w": 40, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 10, "w": 40, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0004.png", - "frame": { "x": 78, "y": 0, "w": 42, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 10, "w": 42, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0005.png", - "frame": { "x": 118, "y": 50, "w": 42, "h": 47 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 11, "w": 42, "h": 47 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0006.png", - "frame": { "x": 0, "y": 56, "w": 41, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 10, "w": 41, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0007.png", - "frame": { "x": 0, "y": 104, "w": 40, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 10, "w": 40, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0008.png", - "frame": { "x": 40, "y": 146, "w": 39, "h": 49 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 9, "w": 39, "h": 49 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0009.png", - "frame": { "x": 78, "y": 48, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0010.png", - "frame": { "x": 120, "y": 0, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0011.png", - "frame": { "x": 120, "y": 0, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0012.png", - "frame": { "x": 118, "y": 97, "w": 40, "h": 49 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 9, "w": 40, "h": 49 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0013.png", - "frame": { "x": 41, "y": 98, "w": 40, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 10, "w": 40, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0014.png", - "frame": { "x": 78, "y": 0, "w": 42, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 10, "w": 42, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0015.png", - "frame": { "x": 118, "y": 50, "w": 42, "h": 47 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 11, "w": 42, "h": 47 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0016.png", - "frame": { "x": 0, "y": 56, "w": 41, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 10, "w": 41, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0017.png", - "frame": { "x": 0, "y": 104, "w": 40, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 10, "w": 40, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0018.png", - "frame": { "x": 40, "y": 146, "w": 39, "h": 49 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 9, "w": 39, "h": 49 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0019.png", - "frame": { "x": 78, "y": 48, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0020.png", - "frame": { "x": 120, "y": 0, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0021.png", - "frame": { "x": 120, "y": 0, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0022.png", - "frame": { "x": 118, "y": 97, "w": 40, "h": 49 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 9, "w": 40, "h": 49 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0023.png", - "frame": { "x": 41, "y": 98, "w": 40, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 10, "w": 40, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0024.png", - "frame": { "x": 78, "y": 0, "w": 42, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 10, "w": 42, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0025.png", - "frame": { "x": 118, "y": 50, "w": 42, "h": 47 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 11, "w": 42, "h": 47 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0026.png", - "frame": { "x": 0, "y": 56, "w": 41, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 10, "w": 41, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0027.png", - "frame": { "x": 0, "y": 104, "w": 40, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 10, "w": 40, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0028.png", - "frame": { "x": 40, "y": 146, "w": 39, "h": 49 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 9, "w": 39, "h": 49 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0029.png", - "frame": { "x": 78, "y": 48, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0030.png", - "frame": { "x": 120, "y": 0, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0031.png", - "frame": { "x": 120, "y": 0, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0032.png", - "frame": { "x": 118, "y": 97, "w": 40, "h": 49 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 9, "w": 40, "h": 49 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0033.png", - "frame": { "x": 41, "y": 98, "w": 40, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 10, "w": 40, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0034.png", - "frame": { "x": 78, "y": 0, "w": 42, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 10, "w": 42, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0035.png", - "frame": { "x": 118, "y": 50, "w": 42, "h": 47 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 11, "w": 42, "h": 47 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0036.png", - "frame": { "x": 0, "y": 56, "w": 41, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 10, "w": 41, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0037.png", - "frame": { "x": 0, "y": 104, "w": 40, "h": 48 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 10, "w": 40, "h": 48 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0038.png", - "frame": { "x": 40, "y": 146, "w": 39, "h": 49 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 9, "w": 39, "h": 49 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0039.png", - "frame": { "x": 78, "y": 48, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0040.png", - "frame": { "x": 120, "y": 0, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0041.png", - "frame": { "x": 120, "y": 0, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0042.png", - "frame": { "x": 39, "y": 0, "w": 39, "h": 53 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 5, "w": 39, "h": 53 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0043.png", - "frame": { "x": 0, "y": 0, "w": 39, "h": 56 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 0, "w": 39, "h": 56 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0044.png", - "frame": { "x": 79, "y": 146, "w": 40, "h": 47 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 9, "w": 40, "h": 47 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0045.png", - "frame": { "x": 119, "y": 146, "w": 38, "h": 49 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 8, "w": 38, "h": 49 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0046.png", - "frame": { "x": 120, "y": 0, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0047.png", - "frame": { "x": 120, "y": 0, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0048.png", - "frame": { "x": 39, "y": 0, "w": 39, "h": 53 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 5, "w": 39, "h": 53 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0049.png", - "frame": { "x": 0, "y": 0, "w": 39, "h": 56 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 0, "w": 39, "h": 56 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0050.png", - "frame": { "x": 79, "y": 146, "w": 40, "h": 47 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 9, "w": 40, "h": 47 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0051.png", - "frame": { "x": 119, "y": 146, "w": 38, "h": 49 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 8, "w": 38, "h": 49 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - }, - { - "filename": "0052.png", - "frame": { "x": 120, "y": 0, "w": 40, "h": 50 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 8, "w": 40, "h": 50 }, - "sourceSize": { "w": 44, "h": 58 }, - "duration": 100 - } - ], - "meta": { - "app": "https://www.aseprite.org/", - "version": "1.3.11-x64", - "image": "672.png", - "format": "I8", - "size": { "w": 160, "h": 195 }, - "scale": "1" - } -} diff --git a/public/images/pokemon/exp/back/672.png b/public/images/pokemon/exp/back/672.png deleted file mode 100644 index 2201da3b627868ded81f15570ebc12d6a7921c9f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3350 zcmV+x4e9cUP)006@T0{{R3_kjoM0000jP)t-s0000G z5D-8(Fc(cbKtMoEO-*K5OmlN{nNlt$fMS`3bN`4;HnWMpzrXnS_`0Og)&Kwi0d!JM zQvg8b*k%9#43|knK~#8Nm6}0oTgw&4?<9q`Kw(BsuB~7hTePl_hNLldv-m#CYQI}v(Y-4jbdD2Yh_yZ`bv>&bp3r0H@Z?I zB{^C6Ra`afqrmpcyVs*L<0NI-itGHgFSD)Q{K6%3HDtmB`dr?haRw%ee%1GmFnQNh z!-j!^0x|npRlhz2pNSJHSiC5zyNr#U&%*S6U0vSw+si1`hed?y6m>D}AA(>peSOz& zC)dWN1xJd@yUH%oPRyr;iMzZD6loRXoPIoPCD+z9<-oiy+#;=9QCH44t=eV3a*>xl zl#?$%wgjtn)LmQ9#nr}%-uf_1CaYRu{Tdd@It2uB_NV?|Ts<6INuRrK za?K*rMI1Z9X4d>+#i`c0GyBWi`kU34-v4AKj=f+r?md1v_>c6t`zF^c@^Y>J{5TWc z`9wG41Dnd#44Tu~x68-Xin7HlD{03QJ#U)(m+ui|WPbKF5UGhIfASgcKGC5QTx!zf!U|2V1%V@5sRWYn<5->a32v)_{1cx<+ zR#q^YD^#5y6;)o4zaS@8NTELas5mmJ^tH|{FqZ-=nHR(1Fwgt7gr7JH3Z2UJ$`qLA zYFPE1VAlIoWt4jqbLb79=GSU0A`Je`e9L z!~NA^!zfVPu%P`?iUpzU=%{#QVUk(!#+@pa#mnYVEwb$iWP2p&S60divPH{&u=_Kz zy>J2s=adulq9BN6XUhj8`rQYaZBM{MY6Oj;Y_2BjJ&2nhGWB6=0&EF1#R?0nce(&6WcT$w(L zh;ABpR;8xIZ}? z?V4`@H3Gpj8s}Bvs@_&q{XZVju|G>qoY5F4pROxlQUx6c710yNQ8dnLzOD!kSa4!N z@L!0=Ea>boJYd0zMZsB!#z6VSr#>DqnpIS=IW7^6xefMi0uPwG^g&bQ$z*ma12hgP z-(4Hv0h?ACnx#=dj3m^ynbx(&hiTpfH-3)G6CF;o5p3+~TMX9>gSw5~a^R}n!#V{SumZ6Gvt z_FfE#<*5zh)#@E+#n3oxgJ)>bwI_C<%Q-c-^k)eS(U{x7*9QK4X=(3h<`a8j(GZP& z8{pc&4u5k;)3_6hhG>j!a9g$qesf1dZA8Hc8nchD4ZNH0X#DuV5O0z4$HA3@6LcpQ z4e=H!eaguK9Ls8ZCj;0t!)F3$p zs>jLbM?v}$gtvUa&@dUHJP^!`;HI%mR}mOW zK)F@&f*x;~t~?BZXdKE($*L(7B)W1i1nd%raxeHETI3uKhJamCP%e}cWJ4HI>S<7( z$aq!}3_S_TarXEG{!}Q(*>exw6ZjLM9B0oj)_|EwD38uQe6dE%OhY-&oUQpvlq?GzgSP2g9yCfdv$E~_JP^ISU2~l;iA? zhGyd@KsnAHX=o;&0OdG)q#-Ua3FV2|;}bX<(r9e<2^va6d1Cfy8cIVs&OSjyX(&(4 zUUiZ*#0(8kj$&pwJ*X@-u^9`Nc(42`o-<5ilWaa0MsN;7nNREW!I zg!V2aVN@wxMxR%6E=3Rwqe|g2`urNsMG%amisLf+cn$x=2&0PPa?+>NJL{4#sst{l zeI0Ot0WO0NAuMf(%jn}ZEQH02L|jh$RPG?GN~s|(qc5bo=`^)##AWm`)y3Z!Q@Fh2 ze8e;CW2#G1HR3YkhcZ0ld`xu!;9!W${18e!<9*Bm0O0W4jkxR{Lfy+M`8Z4rDGmS} zU#CM{-YMNfsC!u@ABPDW7XmnfaEQwe|LjAkEl+lDEubp$F$McLf-p3GJPdJ}6fI9WKrIi*#}sVi4lIPB z@#EnZE<2udfNB7VDcHwF3gpMb0GCDKd6EFt01{I$Vm0m=ak=9@W>}CZnB$^`xSYM3 zwgA-;7gKN=s}Yx(KD=*s3=|y4YQ*I&7E+$LI#Y;4i{NrV9|J>NzG{ZBh#=xJ;NO6@ zY{L-3&=8lAc>k=ms~R8-Er!dg?fa=2NE3z-h8Dx+yb1(Qn3({;!2p*XEYubIv;<~~ z6&S%~p>jtb4rf%1nb4xRoICnpoB?J^3l<<)4NZ}ZGt5j$!6+_w>Y*T~#Tn(939ClK z$3suNl0>WC!r}}+Vsq6ABQ<_J42hShCLhZ15t|_lEvQBjE>}Y&UY`sQh6dFTF0){d zh}Zc5!bmz|z~AQqpo*3c{}(x0g1`5Oc9-Yu3jjPt)yUr-6b0000aP)t-s0000G z5D;@%B0xYuSy@@koIU@J;g3Ofgf_qtjKfw!4963&$U${)0YkQtk(->(R_h>q3jr7KXAYVdy7!~76; zhMIV>YTKmWc5Z|k#hySz=FSUE54DNC%%I`*T8-!n8aK0k!kIbnTF?oM{Wd_2D?82^ z3h}mW$8kuoM>tH>2z?u7jOes20ghwe_wziT3kNvEah<5GnuE&4YS3`F>lcV+pI^6Q zfIn?d@Z4@`%@pdZ)`?Z4;Rqjd)#umOc!U5&xPw@=NUsj65vxPParmFk3Cf0TgE;AM z=7j;NUOiMH7K47h4(AWpw(Yzf04}IMF*VD7lq1Sb-s*Z*8RmaisT!mg8R3TQ6?%}C!1`l~Jc(mV9 z4cbo*nyT}5fPH(ay3q?*=q414C-zIkRlflwvSwvvG?DlMds`aq%Sue@00??ZH&R@_|Gr6*ZH^{NU#keJcy(kKi$hAuZ^ zfd?0~W6`uk`fp)b5Nc22;-bonccfQ@rk-v=q!v0B3(=&F&WjvZ^MeBv1dC-Kem((% zOCzraRxf}(URwej0s85oBeCMY`7r}=m~)sbgJ#RV>Q3CyEscl86_EbJ;&*2rA@=9d zFH7h=qu3%Xx}01zreIdDeZ%FujG=>LM|2(^Wg2`rSKQ6_sDip19dx`SwB^uHe<$x^ zv|;C|r;46Cev7(%vrZ-sKT1m+d#!0T70TFY6Y6Ee&xA4)v>Ej_mz|>#1F@a9!K;m6 zn8*nxXlKbwHe!h3rsnKnXnw2L2v$8|*Q;qeyjJY8vZu(Ra4FNPA@AXgO=+?PH<8_=!YAFe$m zk`>};wZGt`F~MCxF+&^Rt?TZbFj>jOx$HXJIKS?56*FywcdolISxH|2ST?jV!F_gS zrH$y$vlk{SkX^!9^;W_Sj0wg8g`GB|8@peatl*!Kwu=eI0fn75qIPSX?R< zOot$^Pg`_wp1hE11ar6Aa78W|l=n`O?^Ns`S>Y2euHb7JsnH6_(zYnRleTPpheMx6 zm6Sp)**nmcXY>m6*4t#Ll{*9AOgV*GvJ8dFY>Q^d$hP9kj+vIY&BS-tN`_E4y1PbE z*+s8#l_PF5P~TlA8G-Wmx=&S8y;N;Z_fnZ$BN>7ECJSw=kzT4c=Ubst%`BgwO)!gm-gg)YpG<=LR*XQ z@#dz{YZJE_PJ!>vN(N^Oy^wUal4l_+y_~eozlf(n?N#R1XoaS z;Ii9xTQ{(J3EEs)-_+H0kCIhJa0PYIdIH{fqDAw1J+!&8zLPr}EbLS`CmAGzuZKo( z_M`lXH&s*5zMk~B&7AcOk(Ti?-yP6OWZl85BOuNQX7V8N>atY@FhcUQjQ z*quSl++}gu;TKg+9+LNm5xd3h5I{wL`|N7$(OhcTMne(n~00000NkvXX Hu0mjfP_*US diff --git a/public/images/pokemon/exp/back/693.json b/public/images/pokemon/exp/back/693.json deleted file mode 100644 index 6358a8908f6..00000000000 --- a/public/images/pokemon/exp/back/693.json +++ /dev/null @@ -1,902 +0,0 @@ -{ "frames": [ - { - "filename": "0001.png", - "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0002.png", - "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0003.png", - "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0004.png", - "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0005.png", - "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0006.png", - "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0007.png", - "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0008.png", - "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0009.png", - "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0010.png", - "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0011.png", - "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0012.png", - "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0013.png", - "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0014.png", - "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0015.png", - "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0016.png", - "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0017.png", - "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0018.png", - "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0019.png", - "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0020.png", - "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0021.png", - "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0022.png", - "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0023.png", - "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0024.png", - "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0025.png", - "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0026.png", - "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0027.png", - "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0028.png", - "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0029.png", - "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0030.png", - "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0031.png", - "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0032.png", - "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0033.png", - "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0034.png", - "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0035.png", - "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0036.png", - "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0037.png", - "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0038.png", - "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0039.png", - "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0040.png", - "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0041.png", - "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0042.png", - "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0043.png", - "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0044.png", - "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0045.png", - "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0046.png", - "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0047.png", - "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0048.png", - "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0049.png", - "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0050.png", - "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0051.png", - "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0052.png", - "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0053.png", - "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0054.png", - "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0055.png", - "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0056.png", - "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0057.png", - "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0058.png", - "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0059.png", - "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0060.png", - "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0061.png", - "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0062.png", - "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0063.png", - "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0064.png", - "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0065.png", - "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0066.png", - "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0067.png", - "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0068.png", - "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0069.png", - "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0070.png", - "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0071.png", - "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0072.png", - "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0073.png", - "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0074.png", - "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0075.png", - "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0076.png", - "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0077.png", - "frame": { "x": 565, "y": 196, "w": 90, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 16, "y": 6, "w": 90, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0078.png", - "frame": { "x": 278, "y": 266, "w": 90, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 10, "y": 10, "w": 90, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0079.png", - "frame": { "x": 189, "y": 199, "w": 95, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 8, "w": 95, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0080.png", - "frame": { "x": 193, "y": 1, "w": 98, "h": 68 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 3, "w": 98, "h": 68 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0081.png", - "frame": { "x": 1, "y": 71, "w": 94, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0082.png", - "frame": { "x": 469, "y": 196, "w": 96, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 12, "y": 12, "w": 96, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0083.png", - "frame": { "x": 1, "y": 1, "w": 98, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 5, "w": 98, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0084.png", - "frame": { "x": 1, "y": 136, "w": 94, "h": 63 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 14, "y": 10, "w": 94, "h": 63 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0085.png", - "frame": { "x": 95, "y": 72, "w": 96, "h": 63 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 15, "y": 12, "w": 96, "h": 63 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0086.png", - "frame": { "x": 381, "y": 1, "w": 97, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 11, "y": 6, "w": 97, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0087.png", - "frame": { "x": 1, "y": 71, "w": 94, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0088.png", - "frame": { "x": 469, "y": 196, "w": 96, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 12, "y": 12, "w": 96, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0089.png", - "frame": { "x": 1, "y": 1, "w": 98, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 5, "w": 98, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0090.png", - "frame": { "x": 191, "y": 136, "w": 94, "h": 63 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 14, "y": 10, "w": 94, "h": 63 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0091.png", - "frame": { "x": 95, "y": 135, "w": 96, "h": 62 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 12, "y": 11, "w": 96, "h": 62 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0092.png", - "frame": { "x": 572, "y": 67, "w": 99, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 9, "y": 7, "w": 99, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0093.png", - "frame": { "x": 284, "y": 205, "w": 95, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 14, "y": 11, "w": 95, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0094.png", - "frame": { "x": 1, "y": 199, "w": 91, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 12, "w": 91, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0095.png", - "frame": { "x": 1, "y": 259, "w": 95, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 15, "y": 12, "w": 95, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0096.png", - "frame": { "x": 193, "y": 69, "w": 95, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 15, "y": 5, "w": 95, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0097.png", - "frame": { "x": 285, "y": 141, "w": 92, "h": 64 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 8, "w": 92, "h": 64 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0098.png", - "frame": { "x": 96, "y": 318, "w": 89, "h": 58 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 22, "y": 14, "w": 89, "h": 58 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0099.png", - "frame": { "x": 564, "y": 261, "w": 92, "h": 58 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 19, "y": 14, "w": 92, "h": 58 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - } - ], - "meta": { - "app": "https://www.aseprite.org/", - "version": "1.3.12-x64", - "image": "693.png", - "format": "I8", - "size": { "w": 672, "h": 377 }, - "scale": "1" - } -} diff --git a/public/images/pokemon/exp/back/693.png b/public/images/pokemon/exp/back/693.png deleted file mode 100644 index 1ff5db69b60d4ceee2ee140b113c2591b6d2292f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21703 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 diff --git a/public/images/pokemon/exp/back/753.json b/public/images/pokemon/exp/back/753.json deleted file mode 100644 index dbd9fd7d635..00000000000 --- a/public/images/pokemon/exp/back/753.json +++ /dev/null @@ -1,2582 +0,0 @@ -{ - "textures": [ - { - "image": "753.png", - "format": "RGBA8888", - "size": { - "w": 140, - "h": 140 - }, - "scale": 1, - "frames": [ - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 0, - "w": 31, - "h": 47 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 0, - "w": 31, - "h": 47 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 0, - "w": 31, - "h": 47 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 0, - "w": 31, - "h": 47 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 0, - "w": 31, - "h": 47 - } - }, - { - "filename": "0054.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 0, - "w": 31, - "h": 47 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 0, - "w": 31, - "h": 47 - } - }, - { - "filename": "0062.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 0, - "w": 31, - "h": 47 - } - }, - { - "filename": "0087.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 0, - "w": 31, - "h": 47 - } - }, - { - "filename": "0088.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 0, - "w": 31, - "h": 47 - } - }, - { - "filename": "0095.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 0, - "w": 31, - "h": 47 - } - }, - { - "filename": "0096.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 0, - "w": 31, - "h": 47 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 47, - "w": 31, - "h": 47 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 47, - "w": 31, - "h": 47 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 47, - "w": 31, - "h": 47 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 47, - "w": 31, - "h": 47 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 47, - "w": 31, - "h": 47 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 47, - "w": 31, - "h": 47 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 47, - "w": 31, - "h": 47 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 47, - "w": 31, - "h": 47 - } - }, - { - "filename": "0089.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 47, - "w": 31, - "h": 47 - } - }, - { - "filename": "0090.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 47, - "w": 31, - "h": 47 - } - }, - { - "filename": "0093.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 47, - "w": 31, - "h": 47 - } - }, - { - "filename": "0094.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 31, - "h": 47 - }, - "frame": { - "x": 0, - "y": 47, - "w": 31, - "h": 47 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 7, - "w": 31, - "h": 46 - }, - "frame": { - "x": 0, - "y": 94, - "w": 31, - "h": 46 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 7, - "w": 31, - "h": 46 - }, - "frame": { - "x": 0, - "y": 94, - "w": 31, - "h": 46 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 7, - "w": 31, - "h": 46 - }, - "frame": { - "x": 0, - "y": 94, - "w": 31, - "h": 46 - } - }, - { - "filename": "0058.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 7, - "w": 31, - "h": 46 - }, - "frame": { - "x": 0, - "y": 94, - "w": 31, - "h": 46 - } - }, - { - "filename": "0091.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 7, - "w": 31, - "h": 46 - }, - "frame": { - "x": 0, - "y": 94, - "w": 31, - "h": 46 - } - }, - { - "filename": "0092.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 7, - "w": 31, - "h": 46 - }, - "frame": { - "x": 0, - "y": 94, - "w": 31, - "h": 46 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0066.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0067.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0068.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0069.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0070.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0081.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0082.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0083.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0084.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0085.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0086.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0097.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0098.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0099.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0100.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0101.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0102.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0110.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0111.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0112.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0120.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0121.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0122.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 47, - "w": 30, - "h": 47 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 47, - "w": 30, - "h": 47 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 47, - "w": 30, - "h": 47 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 47, - "w": 30, - "h": 47 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 47, - "w": 30, - "h": 47 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 47, - "w": 30, - "h": 47 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 47, - "w": 30, - "h": 47 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 47, - "w": 30, - "h": 47 - } - }, - { - "filename": "0071.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 47, - "w": 30, - "h": 47 - } - }, - { - "filename": "0072.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 47, - "w": 30, - "h": 47 - } - }, - { - "filename": "0079.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 47, - "w": 30, - "h": 47 - } - }, - { - "filename": "0080.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 30, - "h": 47 - }, - "frame": { - "x": 31, - "y": 47, - "w": 30, - "h": 47 - } - }, - { - "filename": "0109.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 7, - "w": 30, - "h": 46 - }, - "frame": { - "x": 31, - "y": 94, - "w": 30, - "h": 46 - } - }, - { - "filename": "0119.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 7, - "w": 30, - "h": 46 - }, - "frame": { - "x": 31, - "y": 94, - "w": 30, - "h": 46 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 30, - "h": 47 - }, - "frame": { - "x": 61, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 30, - "h": 47 - }, - "frame": { - "x": 61, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 30, - "h": 47 - }, - "frame": { - "x": 61, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 30, - "h": 47 - }, - "frame": { - "x": 61, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 30, - "h": 47 - }, - "frame": { - "x": 61, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 30, - "h": 47 - }, - "frame": { - "x": 61, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 30, - "h": 47 - }, - "frame": { - "x": 61, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 30, - "h": 47 - }, - "frame": { - "x": 61, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0073.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 30, - "h": 47 - }, - "frame": { - "x": 61, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0074.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 30, - "h": 47 - }, - "frame": { - "x": 61, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0077.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 30, - "h": 47 - }, - "frame": { - "x": 61, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0078.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 6, - "w": 30, - "h": 47 - }, - "frame": { - "x": 61, - "y": 0, - "w": 30, - "h": 47 - } - }, - { - "filename": "0103.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 29, - "h": 47 - }, - "frame": { - "x": 91, - "y": 0, - "w": 29, - "h": 47 - } - }, - { - "filename": "0104.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 29, - "h": 47 - }, - "frame": { - "x": 91, - "y": 0, - "w": 29, - "h": 47 - } - }, - { - "filename": "0113.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 29, - "h": 47 - }, - "frame": { - "x": 91, - "y": 0, - "w": 29, - "h": 47 - } - }, - { - "filename": "0114.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 29, - "h": 47 - }, - "frame": { - "x": 91, - "y": 0, - "w": 29, - "h": 47 - } - }, - { - "filename": "0107.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 4, - "w": 29, - "h": 47 - }, - "frame": { - "x": 61, - "y": 47, - "w": 29, - "h": 47 - } - }, - { - "filename": "0108.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 4, - "w": 29, - "h": 47 - }, - "frame": { - "x": 61, - "y": 47, - "w": 29, - "h": 47 - } - }, - { - "filename": "0117.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 4, - "w": 29, - "h": 47 - }, - "frame": { - "x": 61, - "y": 47, - "w": 29, - "h": 47 - } - }, - { - "filename": "0118.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 4, - "w": 29, - "h": 47 - }, - "frame": { - "x": 61, - "y": 47, - "w": 29, - "h": 47 - } - }, - { - "filename": "0105.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 28, - "h": 46 - }, - "frame": { - "x": 61, - "y": 94, - "w": 28, - "h": 46 - } - }, - { - "filename": "0106.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 28, - "h": 46 - }, - "frame": { - "x": 61, - "y": 94, - "w": 28, - "h": 46 - } - }, - { - "filename": "0115.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 28, - "h": 46 - }, - "frame": { - "x": 61, - "y": 94, - "w": 28, - "h": 46 - } - }, - { - "filename": "0116.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 28, - "h": 46 - }, - "frame": { - "x": 61, - "y": 94, - "w": 28, - "h": 46 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 8, - "w": 29, - "h": 45 - }, - "frame": { - "x": 89, - "y": 94, - "w": 29, - "h": 45 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 8, - "w": 29, - "h": 45 - }, - "frame": { - "x": 89, - "y": 94, - "w": 29, - "h": 45 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 8, - "w": 29, - "h": 45 - }, - "frame": { - "x": 89, - "y": 94, - "w": 29, - "h": 45 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 8, - "w": 29, - "h": 45 - }, - "frame": { - "x": 89, - "y": 94, - "w": 29, - "h": 45 - } - }, - { - "filename": "0075.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 8, - "w": 29, - "h": 45 - }, - "frame": { - "x": 89, - "y": 94, - "w": 29, - "h": 45 - } - }, - { - "filename": "0076.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 31, - "h": 53 - }, - "spriteSourceSize": { - "x": 0, - "y": 8, - "w": 29, - "h": 45 - }, - "frame": { - "x": 89, - "y": 94, - "w": 29, - "h": 45 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:4209223453e7dabb3758c23bb26a3f91:234fdcf4efd83f52e8b51f13ec19a55c:16c1874bc814253ca78e52a99a340ff7$" - } -} diff --git a/public/images/pokemon/exp/back/753.png b/public/images/pokemon/exp/back/753.png deleted file mode 100644 index aa1fb7067451bf45a6cfae43f0a9e2f6a945ab38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2062 zcmV+p2=VucP)JXlcKcfHOe9zrV~9o5Q*1+|kDH z@w|U`qKf(~+OKMq+~qCfXgzf}Jln5m404yZOonUD;dnB>07069+@=1>aNQA;@dRs0 zQ;=LfUr&bM_!T*R?%4Y^Zsi*6p<|?8ocN?ENG|^vT{EuM;McJrcX`dYbasYj@p0K= zwePsjh}6`iwk927EVVUhT3%r;4eh0A;G+ss7;JqUpd0Lf9HQo~9oj=Tc>*#VrRAJU z2nu7nYT()Hu1&%6TZ|TkAfAnoPZ* z<+-f>`?I;c%!MOqrSN{Bq%*G>*?RLCDp@X zaUJ5vksIDrYE16g+l9`7oRtzRkf*Pq27c82m4G9nV_nFtx0Jb=&57x3THF~az&)My z_D*lG29J0h>*8)@j5AnV>6{mUoNI|Ax=_=Fnk5`SZC%`@G0yIF7acQQe_aNzJTadN zlse|B8yQ3<)9@(}5?b5VQCVHItn3(iFiLwiUl zq$lY(VqDZZhZb!JQb4zp4<{H)9;7cA7c~s+P5df~4E;9pq2XvlHP378mYUR_Pl>M@ z$fq8JHX@&95NanMK4pv$#S+c1iG28Q5NanMKC9Syi71w6(kAlZV`D#-h+>H*ZXh2% z9E94*hYts#cJkrFL1+{5@fty>oqYIk5NanMJ{*KLAs-qJLha^0ROGTlA9d4vR`Yo>dEI!Hd#3i$|74HKNs zez-oJYA695cm*|cs98cqg?!qkI~&Y{sf7~1>x=DBSE0mMzve~AB^Ut|aGtB2;Z->5TM&YnlVb!EqRf_vzZjCiB*CIL!fJXQN5#0h5G z;LSS=Uo=7eix49iQ4rqLfe^5UlXFOf)PmK}VjIC!OjfhK0eTQ@#UDj5#3hXcgR+TW zcK=CDJJ19#Yin!{94E&KMjVk@144g#EXN3D=ggQ?!$2rvIYux7q2Q>v|L&?DgeLox z;{;=C&d|Q710kdzmScRQYrr2eNF$GGheA)4~EWvyUJQL5RJrK9RCDO&BE(0N*y8?+dmSDsbd?@bBpl0dK+!~R*9?juN z$bb;O2<`p?Ih5-JBRWVIcq#}A{vf0eUUz1|Q|7@%gAn{gMli<5uL|JreKo$Ib^sy2 sd+gV~-2}CKhey_Oq&6ZL;eG7<4Vg+-K{zz)2LJ#707*qoM6N<$f^$j7lK=n! diff --git a/public/images/pokemon/exp/back/754.json b/public/images/pokemon/exp/back/754.json deleted file mode 100644 index 86abaac1814..00000000000 --- a/public/images/pokemon/exp/back/754.json +++ /dev/null @@ -1,1112 +0,0 @@ -{ - "textures": [ - { - "image": "754.png", - "format": "RGBA8888", - "size": { - "w": 222, - "h": 222 - }, - "scale": 1, - "frames": [ - { - "filename": "0036.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 68, - "w": 92, - "h": 68 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 68, - "w": 92, - "h": 68 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 136, - "w": 92, - "h": 68 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 136, - "w": 92, - "h": 68 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 88, - "h": 68 - }, - "frame": { - "x": 92, - "y": 68, - "w": 88, - "h": 68 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 88, - "h": 68 - }, - "frame": { - "x": 92, - "y": 68, - "w": 88, - "h": 68 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 40, - "h": 68 - }, - "frame": { - "x": 180, - "y": 68, - "w": 40, - "h": 68 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 40, - "h": 68 - }, - "frame": { - "x": 180, - "y": 68, - "w": 40, - "h": 68 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:3adad944aac48ad53efa41f8c9916d1c:ea15b954875ad08814f50cbbf849c1b3:f7cb0e9bb3adbe899317e6e2e306035d$" - } -} diff --git a/public/images/pokemon/exp/back/754.png b/public/images/pokemon/exp/back/754.png deleted file mode 100644 index 66bd6a1b9758f405bbc4754bcad9fa3c6e1db256..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3640 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 Hu0mjfmxbn~ diff --git a/public/images/pokemon/exp/back/shiny/693.json b/public/images/pokemon/exp/back/shiny/693.json deleted file mode 100644 index 6358a8908f6..00000000000 --- a/public/images/pokemon/exp/back/shiny/693.json +++ /dev/null @@ -1,902 +0,0 @@ -{ "frames": [ - { - "filename": "0001.png", - "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0002.png", - "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0003.png", - "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0004.png", - "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0005.png", - "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0006.png", - "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0007.png", - "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0008.png", - "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0009.png", - "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0010.png", - "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0011.png", - "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0012.png", - "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0013.png", - "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0014.png", - "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0015.png", - "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0016.png", - "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0017.png", - "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0018.png", - "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0019.png", - "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0020.png", - "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0021.png", - "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0022.png", - "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0023.png", - "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0024.png", - "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0025.png", - "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0026.png", - "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0027.png", - "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0028.png", - "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0029.png", - "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0030.png", - "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0031.png", - "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0032.png", - "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0033.png", - "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0034.png", - "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0035.png", - "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0036.png", - "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0037.png", - "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0038.png", - "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0039.png", - "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0040.png", - "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0041.png", - "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0042.png", - "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0043.png", - "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0044.png", - "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0045.png", - "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0046.png", - "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0047.png", - "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0048.png", - "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0049.png", - "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0050.png", - "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0051.png", - "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0052.png", - "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0053.png", - "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0054.png", - "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0055.png", - "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0056.png", - "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0057.png", - "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0058.png", - "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0059.png", - "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0060.png", - "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0061.png", - "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0062.png", - "frame": { "x": 472, "y": 70, "w": 88, "h": 72 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 0, "w": 88, "h": 72 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0063.png", - "frame": { "x": 378, "y": 138, "w": 91, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 8, "w": 91, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0064.png", - "frame": { "x": 187, "y": 260, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0065.png", - "frame": { "x": 379, "y": 257, "w": 95, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 16, "y": 16, "w": 95, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0066.png", - "frame": { "x": 572, "y": 1, "w": 98, "h": 66 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 12, "w": 98, "h": 66 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0067.png", - "frame": { "x": 478, "y": 1, "w": 94, "h": 69 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 11, "w": 94, "h": 69 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0068.png", - "frame": { "x": 560, "y": 132, "w": 93, "h": 64 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 19, "w": 93, "h": 64 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0069.png", - "frame": { "x": 474, "y": 257, "w": 90, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 22, "w": 90, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0070.png", - "frame": { "x": 95, "y": 197, "w": 94, "h": 62 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 21, "w": 94, "h": 62 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0071.png", - "frame": { "x": 99, "y": 1, "w": 94, "h": 71 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 71 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0072.png", - "frame": { "x": 291, "y": 1, "w": 90, "h": 73 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 6, "w": 90, "h": 73 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0073.png", - "frame": { "x": 288, "y": 74, "w": 90, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 21, "y": 11, "w": 90, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0074.png", - "frame": { "x": 368, "y": 317, "w": 88, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 23, "y": 17, "w": 88, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0075.png", - "frame": { "x": 96, "y": 259, "w": 91, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 15, "w": 91, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0076.png", - "frame": { "x": 381, "y": 68, "w": 91, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 20, "y": 3, "w": 91, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0077.png", - "frame": { "x": 565, "y": 196, "w": 90, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 16, "y": 6, "w": 90, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0078.png", - "frame": { "x": 278, "y": 266, "w": 90, "h": 59 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 10, "y": 10, "w": 90, "h": 59 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0079.png", - "frame": { "x": 189, "y": 199, "w": 95, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 8, "w": 95, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0080.png", - "frame": { "x": 193, "y": 1, "w": 98, "h": 68 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 3, "w": 98, "h": 68 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0081.png", - "frame": { "x": 1, "y": 71, "w": 94, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0082.png", - "frame": { "x": 469, "y": 196, "w": 96, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 12, "y": 12, "w": 96, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0083.png", - "frame": { "x": 1, "y": 1, "w": 98, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 5, "w": 98, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0084.png", - "frame": { "x": 1, "y": 136, "w": 94, "h": 63 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 14, "y": 10, "w": 94, "h": 63 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0085.png", - "frame": { "x": 95, "y": 72, "w": 96, "h": 63 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 15, "y": 12, "w": 96, "h": 63 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0086.png", - "frame": { "x": 381, "y": 1, "w": 97, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 11, "y": 6, "w": 97, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0087.png", - "frame": { "x": 1, "y": 71, "w": 94, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 17, "y": 10, "w": 94, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0088.png", - "frame": { "x": 469, "y": 196, "w": 96, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 12, "y": 12, "w": 96, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0089.png", - "frame": { "x": 1, "y": 1, "w": 98, "h": 70 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 13, "y": 5, "w": 98, "h": 70 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0090.png", - "frame": { "x": 191, "y": 136, "w": 94, "h": 63 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 14, "y": 10, "w": 94, "h": 63 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0091.png", - "frame": { "x": 95, "y": 135, "w": 96, "h": 62 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 12, "y": 11, "w": 96, "h": 62 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0092.png", - "frame": { "x": 572, "y": 67, "w": 99, "h": 65 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 9, "y": 7, "w": 99, "h": 65 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0093.png", - "frame": { "x": 284, "y": 205, "w": 95, "h": 61 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 14, "y": 11, "w": 95, "h": 61 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0094.png", - "frame": { "x": 1, "y": 199, "w": 91, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 12, "w": 91, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0095.png", - "frame": { "x": 1, "y": 259, "w": 95, "h": 60 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 15, "y": 12, "w": 95, "h": 60 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0096.png", - "frame": { "x": 193, "y": 69, "w": 95, "h": 67 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 15, "y": 5, "w": 95, "h": 67 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0097.png", - "frame": { "x": 285, "y": 141, "w": 92, "h": 64 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 18, "y": 8, "w": 92, "h": 64 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0098.png", - "frame": { "x": 96, "y": 318, "w": 89, "h": 58 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 22, "y": 14, "w": 89, "h": 58 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - }, - { - "filename": "0099.png", - "frame": { "x": 564, "y": 261, "w": 92, "h": 58 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 19, "y": 14, "w": 92, "h": 58 }, - "sourceSize": { "w": 111, "h": 83 }, - "duration": 100 - } - ], - "meta": { - "app": "https://www.aseprite.org/", - "version": "1.3.12-x64", - "image": "693.png", - "format": "I8", - "size": { "w": 672, "h": 377 }, - "scale": "1" - } -} diff --git a/public/images/pokemon/exp/back/shiny/693.png b/public/images/pokemon/exp/back/shiny/693.png deleted file mode 100644 index 6884c2e28c7cc0a973f46300b758328f9b60c0f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21707 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 diff --git a/public/images/pokemon/exp/back/shiny/754.json b/public/images/pokemon/exp/back/shiny/754.json deleted file mode 100644 index 8b1a3d44a4d..00000000000 --- a/public/images/pokemon/exp/back/shiny/754.json +++ /dev/null @@ -1,1133 +0,0 @@ -{ - "textures": [ - { - "image": "754.png", - "format": "RGBA8888", - "size": { - "w": 222, - "h": 222 - }, - "scale": 1, - "frames": [ - { - "filename": "0036.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 68, - "w": 92, - "h": 68 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 68, - "w": 92, - "h": 68 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 136, - "w": 92, - "h": 68 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 136, - "w": 92, - "h": 68 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 88, - "h": 68 - }, - "frame": { - "x": 92, - "y": 68, - "w": 88, - "h": 68 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 88, - "h": 68 - }, - "frame": { - "x": 92, - "y": 68, - "w": 88, - "h": 68 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 40, - "h": 68 - }, - "frame": { - "x": 180, - "y": 68, - "w": 40, - "h": 68 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 40, - "h": 68 - }, - "frame": { - "x": 180, - "y": 68, - "w": 40, - "h": 68 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:7651b73927071f2814265b66582a8d13:a2d1ef3cf0c2458640f77c2fbcc821a0:f7cb0e9bb3adbe899317e6e2e306035d$" - } -} diff --git a/public/images/pokemon/exp/back/shiny/754.png b/public/images/pokemon/exp/back/shiny/754.png deleted file mode 100644 index 1f7346ed822ae7a5f5d53874880134a108d990c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3640 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 P00000NkvXXu0mjf3{pa$ diff --git a/public/images/pokemon/exp/shiny/692.json b/public/images/pokemon/exp/shiny/692.json deleted file mode 100644 index 86b535260ae..00000000000 --- a/public/images/pokemon/exp/shiny/692.json +++ /dev/null @@ -1,794 +0,0 @@ -{ "frames": [ - { - "filename": "0001.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0002.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0003.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0004.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0005.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0006.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0007.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0008.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0009.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0010.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0011.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0012.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0013.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0014.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0015.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0016.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0017.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0018.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0019.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0020.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0021.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0022.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0023.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0024.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0025.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0026.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0027.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0028.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0029.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0030.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0031.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0032.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0033.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0034.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0035.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0036.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0037.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0038.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0039.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0040.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0041.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0042.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0043.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0044.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0045.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0046.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0047.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0048.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0049.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0050.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0051.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0052.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0053.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0054.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0055.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0056.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0057.png", - "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0058.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0059.png", - "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0060.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0061.png", - "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0062.png", - "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0063.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0064.png", - "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0065.png", - "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0066.png", - "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0067.png", - "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0068.png", - "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0069.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0070.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0071.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0072.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0073.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0074.png", - "frame": { "x": 1, "y": 71, "w": 57, "h": 33 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 33 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0075.png", - "frame": { "x": 1, "y": 71, "w": 57, "h": 33 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 33 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0076.png", - "frame": { "x": 117, "y": 72, "w": 59, "h": 33 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 59, "h": 33 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0077.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0078.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0079.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0080.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0081.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0082.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0083.png", - "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0084.png", - "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0085.png", - "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0086.png", - "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - }, - { - "filename": "0087.png", - "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, - "sourceSize": { "w": 63, "h": 35 }, - "duration": 50 - } - ], - "meta": { - "app": "https://www.aseprite.org/", - "version": "1.3.12-x64", - "image": "692.png", - "format": "I8", - "size": { "w": 239, "h": 106 }, - "scale": "1" - } -} diff --git a/public/images/pokemon/exp/shiny/692.png b/public/images/pokemon/exp/shiny/692.png deleted file mode 100644 index 86f8cf51e927f901f82eebd19c51b559b8bb1977..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2580 zcmV+v3hVWWP)q=zam(3`1treoXkwa|BOg6zhXoGgIP^YP5-Hk3+_jB00001bW%=J06^y0W&i*Q zo=HSORCr#^nlW$NN)pFK2!V0kj-C;bc15PS@(hDQQqtfT5CWx16Tvtg(m612jNs;| z;1KGnQ=S2Fk;<)GSAo9V%?y{GWf)Q*$hx9Oqoww5il7w+*^*C`(BgI1u|M3Z$ z=!X9=0Se(4E&m*vZZcWxIJzNv=II=o>1n+!h~w$acqy0FgeFkaJ~{l*h>sTPWHw#v z``RY}n7&<6TUF^q-^bBcp2ITX+Vu$MNNcTA^|S6llkj*xN@m`kPG36|(I>adWQ{t? zW*K#F__CySwdYWc5KXj>s@Xdf#(M7U{N&35X{jxTn%;!!b2!&~RE{bvqhA($nf2Nb zGkOxf|Dlr-75GO%&+ne*sJqwoLq%UhbqGC5QdV*-9DTvdTvTd1G6+QP!gDOV{;9C$ zAo^g0b!4T@p~5K%3oSh}I<3}rPL&J-QBLmV$fC<9KHz26Ya8M7rv(cWElKYA>ZjVE zPGg^0DQpXa5@CJie|gVmVqr|d$N}q6D2y=D+*Y;e0EM3xr7$>we;O%k5a&`@(f_OB z$Py0xBA-#|E7x}dY+GR(VG_PL2n|GoD%o#=OKtry*@-`az_)tN&TVw!JA57M0?q?J z^Dv+3?RuRDZU6zqS6tiNCec~Z)w5zbI}t9QB1 zGwqQuej)%)o7?m#i)~?c;v*gVhRsnpvueA(?^gx^+Mv|4?-$qMAn4^;#xN%JKd-fm zl{S-!4>E{vNFz#Zxmi%qsu0eJO|9ClpZ-UQu!U030Txy)oPz^U3g`QmBU7Q$a=lzo8M zP2IG-tueC7aje>zn-VI6fUQAYzd$8I?!ryhdUlz?*=Zbk`}K~5chvrPf`i!Z@jkAz zv&Yle&0R`5^v7(;hp*R`LBKvK&lye(ZIrr`l7f4 zK_^<7?@Lfj?76nd+Jm4uFe=G1E>tP3|I5?ap(M=$QMye4tV00`3*IYl#~n#H&$Ug~ z9)w$>P(vMtw+QbTL19N#R|uw|n4NNVoR)a5JvCW-5MB$)VRT7Y)wV67fd@iasDM&i zvN8x~--hy>SdgcqDhZR^8I4^XmD=^J2YTIzG6QCE4#Ra?89E)c)UId2;kts>KcE~2 z96ETN1|x#mmfF8K6V?^9b?1hLT|-JY)^>hfTF~L)sQ(W-9#U8Ag8m_fL0d=TcDrEN z7nL>jMY|y|Q~m7%LHn-!a-^_1%WN-HU_hY#NGsK7CUCYlDt1ESk>0f%Go0;@ir4UA zfCIvraM6E1tB(skH!m*R0X+XcN*fD%qx=w$*8d^k6DK_66n3_SEw|C%0? zO@YZ&HCR!`E`ZLctj}C~+CZ~SF*O^1J)Z4?San5ZeZIx(TAVl1Y*Wa_UmX)~?Xm!O zLuGwF;Z;*;$NU92Bx6%ZS5=qeyk-|1N5#juBVpT4Fn`5SGG?2?bX6ad*aaed6cr!i zE)-$16Rvn6So0igH=#bJ3#wg9x1zE>|28KZL+k{IBToOQxe4_#r8VKBsI1Swfkg*^ zo2)XfpSTG^gR;kzet(+h*_Q1licPVBsx>YO;Bt^*keKzv)Oj1H@?0G0zzL&jqleLb@oi1GU z78bXmU)&w&2ju8x_7Wv8S1|OzeLFrO-Fw_bR!lDk=!W9G^cRk9n0~1SxOFo%K=)>? zzw-|BFT^jGJ#^(+!-|A^k@BT1j{ZHLCgI1lJ1@@~mgMSz8YFxlhbsAf=IIE!lJcx! zrcT0-I#mO7vGH)4pr3#pbmZk(!)T#!>DSov0O9*rp?e6I+(Jjxe%6r8yk0$z9Bty; ztNDQ!+4X&AVR_c@`RSd1*h#_p-T* z6LDOZPt2<|_uN^SOq=5OV_u-E4;xxTo;8pKQhWB$P5$#Hm>!-&FXz47HxC=S2?u`2 zLo&kV*+a*;2m<(inCDyC%W+FJ4;xxTo;9%dVtSTm4_#t0=O2i@E$!tjjLNE;aE9pj zC+XQdd*~G71z^x#zUTFrg#C85`mCYaGfIy-3a`uxv;D-vv|qRDR-ZMbR?itfd*~_* zMmYI|dwDv63?7fN_Ok}NXLba-hBCsuK=W=5?LhTe1C%{yz6%>bwnvzs`L8os%a4RS zYk2Niyo5f&UvYE4ex1s0-6cY>dKN>Vm$1fo14?%5E;0LH^$gNB??Bpy$3#ixshx-FiUJeVZ050$7;jhx9z8X@+n#ieWXh qXKs2x7(p1^GdKOmhBz*N`uHD_t?dpKsbp&a00000000gP)t-s0000G z5D<7eGbc${b8~aEHa7oRS^sib|HxS#k+e~=%)h_C|DBBZ`1sRCJ@^0s00DGTPE!Ct z=GbNc0A#O8L_t(|UhI}JZrm^sMMc>M;1A%6aKm6#rCA8MK{~_bs!+cwaw@A;WyU3; zL%0@n7w?GVQWeP&kW^u%=3zen{28kHSLgo$*oa^QM~VQFZvY<&W%!)6TowvEL?|l4 z6n0|_AL1ecROWzp!fuCU1GoqQu=q7z-tp55!BVH3=omAfPiEg zX8@SPmtlcM@>6R5QV{@tsD)$r$|M^B3kYiphl~T0$Q04-=m5mcQ9h=k0*)Poxe*V-sB4Q(-XCIqwB!neq8 z#a!fu&O7(i4ZrWst(k^;23nuaq2SBCS9q9Lp-JaxXA2{vt3=93>@@n>l5MZ${ zx<*F6Xwfq-1pk*k=R{;hW=3X4R+Tu|u%PzKK}3Ax{K{12Cu;3)m2yAR%AwY8Y@5+) zkAya|q(*WBfo`xRrUIA-W>Bcf8Z#_#nt}AmS`T)kNAV38{}W8ZyTGDF!YSo^oaNp< zwit^tn?cpk_mpzWHutHuqC%w8DGFeBIrC_jZ42ymrE-SY*7(NdeJrBYT@|@pe>z4D zoWR?gibyK7rAF)|R!V2|(K!!ul7EQst= zkkuI~H@fvxVO>|Pom6hc6_#nFAIQ!8qYm)h5*>*XCKh;hYp;a+^b&C-sw&yrYo3pD zpx%*V+rv)#T>qz@U4t-Y)9sB`4g?7&>=lyt&jDW6KI`&y zeA}~}wS-|(b&NT?D1}Wtl9yRpV-Dnd$*}m0H)hQ*E^ZgMWt*tH7CJ*^!Ke5MAq(m?B z(!z7y*pCM*DS*3q^Lgp`;g$LRLrgr@M<`iTbcyVLq13h~20fWNTAPudK3V(vYO$IZ zMUg0&K~2$8Hk~Ux&!(_#Mk~k8?dy_ab2+`4vrjtgI@VL_CMNrYj&@i+CRpr|eo(P? z3))Msgw?AbJC9Mi|I+s`=B}V|U~9Bn$<26bGxptRv2jC2EiRKzR?!p22SbZd>SRf6 zdoD4&6pDnCb-0D%J@LN7iiN_B8ggM4N7eIqFHyLrwz2P~cZQY7dubDnPs#7bg5rsH z!ACosDJIw-tZw>kTx(ul{shj3lz`>?%WE3As#8|Zz8S}HTKy-u8S;WNKMt(2WR4#uph0HjEUdi$%1cn(|AY}EwAP{8+{Z`D>r@z02=s9V z;O%-S_ZzrvSFY7Hf=-#(i)8OH+PsGX;9LzqupaG3d@elMm&VE34%XAlGu*A~>uS%+ z-6FM1DCcNLeKOt5kd!N*=tn4=E$G-vYT)w%UtW)|$KYF)`;%BqpqpZGfQL5aY8%Pr z?c(yXENcC4eZ3Q1T@%W=OYXf4Qzb!<{q6$B0K6I7$`Xyt6Yr%x=-9iv@t}ZPfD6=< z;R5rImTF$h;}pc33PkBXtMB0M;ce9Id??Z(-uHW^qn`QBj~cj{ zC6xQCQMMX>bhC3gT9S(8I$!68h-oI`O)NcFgy>2BFj^K?G62Pd9wRKg97O* z)Gd2`8~R|WGt-4~3AG-IRHg450n5rwoDiiOHiuxmyo!5jud6qMay#Lts5Z%+o#rAx z(CNydpu6Ypm}>K6VLv@tFH&yO4nv%-X$r@u+fBzrERcdBWOu03ghdAnxApQ+YcLA1 zF?t82+!3zpbzn3zMu>OOV5*$l3ofkaQKhSEINCV-+kuOIn&mpFMBG=-u9Q0>=`ze} zIx}kJ;(-(dB6ofIVAb;cZ3-h6&aPb0aKg!Ph$8@VGbsZ;T~@9g4&+{j#hqzarz`f; z=jf+Ry&I#UH*zs0;sZ6?r-pKrhP(P;wQ@R;pr}J-i~sdjVtF!%?8-fJHp9Vj=(NM2 z3Fa${%< zXZ&+orU>n$$pn{$(&4&9xy?KZk0!X6(8*buW0Xkkr{o7Bb1QkuJ#(C6j0)!xSGWd+ zL7`(p2a?F{NV+ALSEC7_+wY~JE zgLS|(pm!BOD92z8l@m$j`gxlHwU-dR0EVg5lf9iswDp1VQymc2$$CR|PT}NmfXW?~ z$it5c0*OPyJlIF^FbYyH$m`B}Z_jboDH|v&>a_1joAOzJ5MUt%TcbE2qa;=$jd%hy}Wb=s;)z$&dTje zEqqcACdU)Gtj|(er1HH!#W*V$n~jX_473*<7I!i_=#FE4n&oq9vb|fqxJMk2aw;kY zqc2f8dj(?)6-b6V{9VrSVqv5#v~r?SW##;Rpp)(Ky7rF4W0HG)FghEglzRk#A>Q-r z(3sX1Hd>=%$9p+9l+MU^2kzF4sOAtqq>K!lGOc zZQ_A65G9Jr!LWFFRfnYT!E0c6cWGS>RE`Y>sNCjYWh*W<8ORW)&G>pVXfq+TNx6Zr zcQU#ZX+O<4S6&;Zh>^-A4q zta{!9-|zKUIo5hIp=I4Uy1Sh&MD=y>(NQ0uqc)-tC-+f%`K+vR{z0~%nv=1YuQw<| z$g>UB*c)((r{`;TN0h#`a<>2v6(bz=>+9qV@jyx{m(#~2mN&>)pFrhkT>Ekajc267 z>E>V%jP881b3Qa&p$d-k-h-YGfCgQkqO~sbFYSf>)TSK!!J6Tx#(?Q$tAKO$=$MAS z484CC&}_%w#NQRP`T&%DFv8XQDXp9iq^Isy@)tGcpwt~-UgpVGrE(j-&vDm$PR-xw z8O<@+85zpaY^>a#=QS|Gd0};_Hu>q<(Oq{a(#=JxGg`$@EZn>Npw5$z+-v zQc?cnXmjux%CIv!(1q`jpoZa=UynsN84dU1f#hxvtq04?Q(=^+-15bRhcEK}O(_@L zClOAcZJv(KoRLz_sL9?1ta`g|d}VZ>)pl@}(OS5JUXXgs;}QuCYB98O+D~-=nWHAE zt6pNVpwX5~9E?Wwr=xyP>mWFDH};$;!c`6$?k5AurW~0h!U(<89G#1c7v8l}E*O#u zum65DaYpBBPM?-#4zY|jBzL6THYs<0zWM2BwDwfJIn=r55s(LPp^Qb)v#Fn+o*fNb zH~$NrPI9uK!H0W(gf2HYN-wdxE~sJfsO*6s`RPw$f#i-1<+!MD+?vbdFv@rtx=hMl zpmH0LazPlr`t|wX2o3t#$}NAiOg2-agJ(gcHu~w#=pTdRV;KU(1dDi{3v`QUx*Nm{7fUP|h!vtIWe?4h4Flno>UsW&vA-}eBf zsgZ2%$Ck%oWO$s1|9i2km8+kQf?xjX`2fz=_{A1vDEFfCqH{q;4Qcf3boBfezYa+4 zwWHtmdkGbQA677eICTS>^wpO6bFfwCxmwpj(!OHCgP=7r-J%2udA$-8fiR}3P zj{G!|yNx!#{8fm`f#C!yf*N(oJ^m`SKR#ELZMm6E_EQ}|<}rCMyo(pnWCy5R^Zfku zm$*b|1|QH8_c9~giz}xCNiR@0+_I>6Dm8M|+t*>Fz0^<+CAj*%4>Uxj{W=KF!&fWr z39c$zxj&U^v)ml4Xv1m)tYtd^U<6U_+4H6m^z8I`_={hkPmb_Rk4C@!X|8f~Oy2Hv zvH(Q;^*K&=*bIW8rIj-xoQ!;FqXWrUAP9N~3^ko=5*IsH)!YSkmqSb0R3hDTDl4B;@xvD&nmJpiav$4F{FRa?nYuBsZxsQrogDcck%=kYN)K7gQg){c3) zrQkR``c^4tCSs28E2W$Yq~=L*!jt{2@d@kddI!D7+l>y^5=N|oM$2nZt4xaUmN@P0tg{BqM zHYX^YsJLOCavf6J+oc>t13(5+Gr0fs17%^DeAH2Hnpoa8W`zGEOSxrglt={q`Pb!T z*oLL!$#(V}Fu#IQPG0 z%mG?B?R4!|6%5v=Zd*rqR_=)+2q0`lw4D!>v@lABO+^=ctd! zQ*I22K&n)JaK4?Z93ATJwCcU0e}zB`JlV_0OHCCZG4-oEsN6PceXa7JiC?&eHwl{` zqSIk&{3;|L_;)>vStC8JkpPt{aD-z%-Hzo`+cD|GolvFt$tS6l>gXbK{!B|05Cc5r`iM6>5B#}T9^ zB&QQg$Kj`~++by=Gli?XYRhO1PJYbFnGwFLleZ3}%2F6!l0`-__NMV&_xD5;MmWdl zM0=@iR9$d)&QD0WvH*ys6{q6k-I0T4T* zQLr-_x`O+M93$EPM6y-|DYq2xUjEG&b5O?Iogpb_n5qhv4kWY)0`>>P;?KBSP)pa# z<1ZX9e{Ohb?jbh=IEUe~J{;>nli}#R2^HE&j&dG#BrL1m%@XEQnHA>Xf4T0cnW!*N z6a+ixO%E4ok8mw@@P(4WS}7H}XPdmVybIOf5Gu4e6+`TsI^fO0f5RL!gVar|d%-HX+|7^b zY&+r9XgEPx-NlRsBy|;Lgen4g>0+bwtmTyh>8^7scX-oMbNa3CU!9GDFSK%2POC-tn{rbfM5fE7A$;$YRqn2Prh&aU4AimpG^fgs ztsKWQF}re$x@ln$IR=DJ?Z>X1(^J4+t_ddN)-D`qs=J-zNK?3PO}X*>z%UNf+?a!fZH3(G8;!qQ`8xC>&+ zLY*alj3MsTb`Y}@u@bA|=9ROYDP_47gs(porDk!|cIv<1(7;}Unr=T#yK*(nam4x9 zw48EF3oFN2&WhDh%K1Z!>>ALi3$g{jYdK7da;;txC#-a~S}*Ou0?JiEq54HGGAaqd z`o}EgL^cyvJAwtjYfHTBe|C1UBw*gFT57)rQjg{@$MB9q^^dJs9Yr@Y5k~ONSY-OD z3G6N5a$8wi8Cn=?)P&oWJvOx~x4}iFQgic)iv2FAd{lp}m7+zO$^o?;RywkZReSDm zcZ3nE?0{Cxq~^4e%y#7*=4IENmb#H+Y$#`A^=nkW9bf3Dlw1A?uCFkx3UQ-;1imiA zj>icy!n1lM%5hrzM8E_~?bij&mvCTvQ8AylK%@E#@P&^f5&p)1_}~*ykIr{5@#j&Z z$PfAikv+2kdo?VFa!>8lE~Pa%EF9w zUlJuBD3^q~J}N4Cq7V*4q$%lN=C~R|0c>3>X9MI6<&)_b9@b_id4IL7vKs2y$=8H6#$4*u=P)_Oz^cGOg`N@V|)d+ zdQ9pah(v^E-Vjyms$7D9iR@>|S{gH@X^{wCbDkd~j4=oxH-M6`_1|;|jB5wAnimf! z;Y@!u+oq`aUw;>`UWC~bHXya%i}$F@9O0QR*NQmsvKnW(Tq_CcU>&-Vaz`OUumZ54 zn2Dc;yXku`US7R(yD^h;pxZL?S79aG;>jWa^Gbcm!jyys5=;x%N1j@B2=6IESyNE1IuoD{fI05(nd$t zsYfg4KLk|Hvj|tw7)NfK$k&<;KX8|bi5oyUAT`xvq5-I3Ug~VV!g$DcFbGrJTw0{R zNfa#fx?oKdcOhD+?MO}UM3`uX?^a)H9D1QLW>F8;pb5T|vEz@{Mb?9xpW<~FSx z6jgp*jXT$4Gum>ML-HfD7@aVH!z}6k!fE+&t~5;P$ec;;VKZ|tSMZc_pL8f^i6Zv5 zZxnxq;xbM+7?xwSdpMM2P&AS{=0VCg47y9!YZ35GtKW+tSG z<2&oFkXkOjumO`8t8F0Cy-ZwkKhE=1SZPIKwjM?>m~)%2O4fyj4BE!Wu^HJ>khYBJAauZ!rPgJSU+)3>*XAy2cl> za*EY=i6v#Q7rHC7smF1z3WawuanegYyexhC72bD6YJ=CmR^l(5Pct}@qSxOL{Y zsl5+^p|z;!TTJ*a4jmGj?dnKe5@^sAD~ABX!C=a2bGPh(KvpgnE1g2MJ1CwfADhOo z?td&JNb7Bz>rr^g46*hlb6PDuAlKttT#u24p>v~ExhEaI?1{(Fsr_|Q&fPI`Nfjy}fn$3Qum+=aOtA!-;E&7? zTML=hW>+qWl@>On`(wDJ!Q|)$!-CK;mAezG{a{6T^Ne>89x*tkn8;6bwdu_oPZ)kf zw@~;WXIEoJ5)m$Gb;{Wc8WLbQLVg&7@&0irO6(?ql|B?Xh1CR;b9WKU0-Q%LkcD*; z0gugQW-8otN?aeEY@4e}Q1FkZe6vP}a>+wfZbFYLTiMlky#n3?KyFEdYHJSn6+etw zjK3w8lZj$B3Pc<$eP|Jml~a6wGSl>LyK;o#8?e~~2ARq^UF~sGChT668>y;5Q6tle z-w!e2D}!Q>fj zlL;f#2wt?i#Gn{b?Ke=~-8`=H1g$7HS__!EAA&xTQ zl(Q5t9TMsxFH<^C(IOf8LlR&bKrq}dpAQaiqsyg~`yLS3^_E02M~BO1dvUDvq0z`C zwxf{l5&ioz%%OijiU_v}60tAQ0v7p)uW!AIC~NJHgO?%Rx_5;Irx9l_`wyzfj0B#xX+h{t=nDpDEz6 z9fIED<#9+1PEk4A-MT%pks)yzQZfO^R*pzc403Atgd9WnJPltVztH_43#?Nm+A-79 zy$2DBd3cb*N)N5Ov4hF^l&XL*!ZFSzF^)>P*T}?uA1j!{G+{e1o0rGWMO;-xe(CRZ z(;;!0^J+Ze7yf=@51zCWv9B@9q%F+>Uf#3pJQLpJB}~ z!bga4j7ZGEb7JBKgZI(poYeXpKc|yv^TBd%{HF}%91>2yk};A=j&h1$D5BY!a#vZp zpRZh1RNwPZI8Sq>V_50WM!!9WmJcH&-C{)Up~gyc4+zH9o+g+) zw1CMq`LUxhj(*TSMJ8@A0ZkJ&mFsc8sptAQr4kmvldsFs<&B0UI(B_sf90>&PVRP6#`Ok)O ztdxSZE7Ujxv!4@I`JXm1B-V~GnGB6vWsUKOLL&DPhU#~_2K2C24<&jH^JzaH!@`_; zO-F8OtaK|!Iod`(n0#BeOlCTvWyTS_s6+F_-8Mn8%(4RwY4^K52B)7sC#>?9gAq-Y zxZhu7pv8!?ZlZRF=hLz@e?2DYIs2FiR!S{Fb z=-=KHMyM#;cbFXth;e+HJ|zCG1?t?X^VJ`W8Ydw2>(k&Dzm(MH!TXF=wp1`QR;>P< zOBkw#aLj(>Xx$DzxGq%gVpl7tu+l5rCyr*6YUTEBG15J8XM=*kqj1PgCqKxp4^!$h z;9LM85xm$tg&HJl;O+P1yR)c*In<&O!utI)P9_J!)->_*I3)5eVE_Qb5yg7#4}`fZ zz+ib^$D?4y_PGH#6jpjI9DJCr$2EG2s;^T3y@$%J`@ZsnQw1h3;-;^94E|=Ea1E2! z6v2!2D`4gHA!)olH2AHfCQ{>@YHsd_&^+oJ%4HTX6%w3GhMT2PCsoOF(zK}@PW=_^ z0b_9#R+`?owT;{Z0yh#N-B+VK8}|P=`ukt}D#rKEp=|OPk0Ufhl@Pq4sJO`B{UDPj zTwNy+`s&4ab&7p%BUc%h7vOOCU~SDaR^#U~B={K*uUXlefsD6v_JyIGL0cCItn?_z zh@f@-6h&9SL@xp(zJFcu{k3vtB7OrY1TOSu%`W6%WaJNrJ!!L+AR;1imB(Ty;;WsBonWWY_A>CIyP zO20q4KOkQ3zd+vIB5c-R<-&;s?w`0Ps<|jTL{$(J)_q0FS$JsjP|XTf_hAYLUTTMzCY`*7 z!s!-pl`EL4DIp-KU*-~4ZgE;$#*+usR@z8UU6Czd{$^AF?LqX6S3CbwcYrg%Mn_FS z%H{UyoaDx$3Tk;YmqJ+YC6#+5VlAM$Go@X}nlgbUyno;WT~iikF@hl+)s!YeXC?W% z=!Oh_768F77HEo{$p=Q#w!)MUHY(}GR|VAPgsfHOtOvUpAt9m`ur6<#dBd4eF0V~* z+GbqdW|4aWhN?lSB|rGwAYU6-zyLIW6Jw~}D-hq`2v(bR&=mRKAbZ@&8L|@gIVU85 z8z)?1RQna8eiR&qvu0tK5myMrxXmYDFTap(z~mv`JSHDv;(z^hiz8@O7$InqtmO@m zE-E9&KId_J<9DX)%bJ40`iz6s7dMG^TZbm-ueZ#%dDm$-00uh*(i3OoU$QFRSlIKh zW?t`%nr2Ng?@YPkJd8DkgSBN3Ry+C0jgtUvXYb4GfKITY>+G3WKX*V*7iDMu_#;tsdWzB|ZbCWxRSNdknmRsFti_bONYt7+ zGe=8;MmOvf=JH7C@$6SAl;y9nR$!Ehn`#)W+*F?^9S=2y*{Xs&vMXo5N}>F4G?Nlo ztD9GDHq6wPaT?j^r^o}gtjd+YO2N)w6N;)QtRiq`xH;ua%#^#A-ZW%3(hjG_DHyqG zO^x=e6zqY44qHQ87S5XHI5XU&ax9#tFy9Ic;j$5yoEn`LrbbIqDSwqh#R?RrWQ@!| zD1(@;(E_>2zbWOCl#c#S)62RT=M(rO57&jEXiUYy`er2Pl-Goc$_=@^AHO zVFHQ&)snW&;r%)q>t%@w`>ezmjf+&C0g|yY^$G;xN zT@JEoWtyV9vLs+^M1@$ivZhFk;0Nq%9fg)DycU5MhRO{qCd1Nxl|uOk4|X~2<#F52 zb>`XndM;M_9WduwY_v?wl;=%}f*>p|@KAB&Ojx-6DuwdODO+8}`cvG5hf$GR_B5U} z7>mV%)sC6^#Or9?UF)Zy@DcF@WZ6!1_5Na|Ei{cfs(z?~sVX=6y#*~}F zYjBJK?pe@Jnj<&Vx^a~GDf(G)%TI|gTBU7hhNb-~1r}^q67>*s>vi79yEm;oVKpHW-uaB|pmhzs*3Fh~ zQtiV^>5#fLB#Los6y&q1lELG7pClNT!giIy6BO+#gKJO171=tGa;k`3y^|jl8K8BQ zOujRkRLihqrY^?UW5qXlO?;Db81pCSY`ao2c=rv!J?m8pxO%1Y7@a24?d8RI421;V zR=*{w&r-LSS5UY@6Bka`X6uu?hzK=MtTkbnsymyE1aV}xQ5z0pI%83`(jf2R5B}IIbeDt@UAn@^8 zE@e?c;e`Dv1@<=LCirUiu3I2NWGj_>$((eYQrRwP8lg$UCo=ie*#Mvl04qBZ*}G^* zq8UaRu%sXl!=qCp3pa&kF@ev1l>z|Wn`6C7fo(UEa98IY6b&c*(a`^Ri@zvA+af%v zs^0HPu&ZRry#JXPxv**2WZ z;oWUwSZiCeU)Yb~!WG!nl~J+aVAg~|=_rOJX@sRF@bwNfGz%(=T%}+#t^7C!EJ0#c z4D0hu`nS{7sey>aXa4@>1_#9`SX?-hHDTeKXpDJ^z~?wl1kL(`3mh+kUAqN*uq{ulv z&d!?XtqqFUg*C~V*91NTnk5#wO2J~Q+~~kySQ|aJi3_)YU2VgT8}s9^q{EEvA~!W& zhO($Ec$I>k?b>Igo}!H!rj8bFTF4v%&obs*AnJz(*mcpLXvQ#8C}NMOicE$Hh%!~*WWI@=!1v6AW-V})f*y}V zl|UFiIO_?9SOCY8HYyp`_KO7kp$5Ayt{gUq0EATLjjcBnIJF%R873r#rQ(~|34G5@ zXx0K(Dd-{Cw!usXnnx?gCqglm{B@%2F>fI206S3ODAjK4?V9A11>b-X3Yb ziIEz+@nt2USzKi=cIE7xwEB7l9Opf+LAWu7CABdKn8dgp7~mR7-~$Zs`g)Wa`(2qD zUmMV@d9PA1M;(D^%n@mjbfmgwSf4S&+JtN}n<}oH2D@%DUwcvYXitFq@gm?n@&qM` z@f%4SODGp{%Eve``KNRDdc>xB;H%v5qpwNouUTpW zyS_!`XkEW&Zt5k0YnJZ6-p-*+)kURF%E4;H65c$EX=EF; zr%p~YD&=(UrmE-Q(XM*n%h4X4|MJg+FVO%fDi)7cjT(I+0bL;4=yV91Ks2CyrV zuDTAAUwyrigl3)SqB8$g3g#f?zPr|!2|OdFULawKk|%5&?Ycu%&*P(&`Q)FG*n6nK zt_u&1xMREtBePJs+9*VF;lD>~GR5;aHFD_v1ikba5thi|akZZ=c9nwZb4YQE$_t8?(P|d1`xpA6Ru_pT29R zrhI}?flAMKSA$%Gow|KCctO>@}oMYF+n2xbEHSU3^3c`)mz78k4cV6@=1(Sqz zCDK<3ERC>O=4ifL>AdJn`-OI$Abe=>+(5}KBwPwLT7_VU06vLnv!+H7ff$!g-N}(I zDhpqwpa-OHW3JX;QRjpe>BQBV8<;KV@5Eht zpX0QUUsRF-j`8i{xWB85$^us@*vC3|PTODI9~_Zo#*BZO2nvbBMdtOSH9tzujv8fT z7KmNFm7Jt?C3JiTCrG?DipqRfDcA>hPDqAxC-of5U13-5i(eyhxrcL-=-1^yLCKGc3ZXHX%NLZIkXdkDu}) zS1H)X)@=gtLhh6uFUh822p^*4WK`TFA<&*0laA_8W<=$5AG7FH3U-?C2%41~__t@U zsQ>~Pohc%2$ti3UmN{{iX%d>C7_r5w{VIj>1F-&A_^?VLaVu~e=PA$gthqa)ELLyv zRJm+8&LUSSSdp0wVwOsnO^{LcYKI)$7f=>bF}gwJOaTppg|1St6Ra!V)i@`Zg<(MR zlq?TUi>L_}Jd6d$v*3UQu2QfQtjSQ!V(6Y%z0wk<$Q(57Uz;XOp@Gy=;WRx3J&ZKzX{IF|gvS=PECW>7-EqL$Yf1q-6xSV^V$-CBz zV9^rK9VC^*7 z#c4rOITp&Q9DAyc@rC*2kfYoZx0&;k`)*RXc9LJS^49`6I(96`MmNT&8qH1N{#`K= z&e^c|vz^>W)W5>H?ZZxCAvhfLhqwtlsOwve(I8KqJ4OqYo5tJLygv&^R$!jv@vasR zR*jHj&l&!KTsJyP!M-qKvc;o^-cjCcbkOwL0=VsrejMY+Wat5&Wz6y_WSp2~Eu zl>;$LEi{Qe?-Xzn9b@nI=GO7!$@|RAYj)-Ay_~^JGGhwz>3x00MHBW6Y_fqwVQFo3 zi;0KTu@%fns;LTA9SaMZXr63fGLuZ8r^y`C*s*NoEKY|WXQ|%(7-4d@j?77r`+l}k zxmSI=BtFDzIp-(wO`&o|!?2)oHNq?z(He-4@II@GM^GYW z!;SOFD4ND&Q?A%0-@On8EWMme3b`|EnB>0k+?3lQgpuQL74zg^uFiLzYp=ruNE7C? zuj;YMF|zQOX)HmOm%W=;z4hKnIkPm|;bWTIXZZZW*XXdU+XZA!Dep3I)ro61vZe1iYowH;{5!Ve8Z_|E4AupO)A%Dr z%#!1J3|B;s%7rsAXz7XcQ*-39;RsDGYR<#6gjD9eaV_6u=`jwjLgYT+vjLHf&_Y|A za#ff+SZmmj_;LKvg`{OR8;meoLL$*h5jLHfGVdw9ub7=QhIeJCUtL|~1d_vV?dvUm zrqZKxZ5APu2=@g;ldxv7i{liz<)Ld+j)Wi2iweS+OW}{;7UPeYezb!`qVr5cXG2q1 zoR=KiV}Qm1Ev*W2i~;gdV!elu#-Q(-!I~n%{mpL>d8pl6{~)gfx$kd~dD!rTM->eJ zQVqj=F6TIXzh^>xdPCp&QJv{W*v=tgZ1Cu-=J7&D&E;iq3CWKm)lmu5oQ8 zl>3cDa_T&cFj$eFgml@XG>o<+*6uY{Fzy*ToK3`>+?IIp>1xKwlzw#1!kLJ&WLbTT zetPR?G3C%-@7YSZg1~Da_p*5pveQZs;dZCULjaYCZH&Agmp#;p6*ozfFdq#q1@Y<9 zM68^qADO~AEx)HdRbw!JN}17j-7N&1AyaN>t4R7r4iO3WM&uCTSht7(+XP|1ITE8H zgWPxh24U$jpB>wUD~~W@h>xZp3DZ%hdh3d;tehdSwt!+h5CwgA>P8NsgflxvjoDPc zBO`cUAS~cMO2WGAaW)}WnUmGGiwa^54Ej+nq=J*Zv_fN8(55e}6*ahI4|EOKM{aU;jF63(oSy3YgvghjTUhQb!+^5-=2#vu1OirhCM zM%4`{_c%d60<$ifLqo?H#V2OFk%JoBvB+fIX_lUZMKbT1YQ7fE!mf)^|CmmR*(9L4 zcN5C(CFn8qD8$rjyO9DU@NmtuQ-^k&;%n{DdVd>H4p)xnQ4~-WRPGcRq=>n0U zABmF6IZhi%OU&8|)}Z{o5)UOf-|V$bK&jrm7S4o4=AJAYD-hqp%Jm>(1x*LFvvK;- zVLFZE3_x#S49z8C(GEpfv|glOmOY1a5F0P5+#>)2Isgz# zB#u9d(~nY|t|_DaDSbq}*_>vWE>r8ykj2 zo$isSFfTztd}8>c1pP>)eGULfVbo9S#t@o()@B|C={GDKzm#$HV~0tE>-Htobi$iw zmLANe+@m{K_68BTZ!xFS2qRK%-}g2CC{900aihNXNU+j-0F)v%jAytvRZbzQ>`kP{ z!l7*?(V(@$64=5ZCy;WLyHsV*VRGN1qq<&g)E#1}ZrYMqds@0d}6_u7%H`TpJ`jeMEF*T^_TQQxrn8Tgsvt8BNo~Xc!h6Kst~T zjGO4O2905oew1$3tMzqI%BdIXZ1odrN{F5HnI=Z&5VoC`!p>hoL&4KgI6Dz8jBQ@z zDhF^&mpx7WGsu05j%pq14k3&l_z@K4S&V+-U>!YOP^ z*ly$?-9W?BhgPs{FM%!0Nu?3HZZEg&>4sr(x2qFm$=hvcRgg z8XUEv=|@L&)EOew0UW>(_t2=zY3|_8l$~`J6355_?0*b=uf%55*bgl%J(}Y?%2il7jXzqM(vOZ>IfRC4rTU(S!tIg~D;KmWIU+j? zJ@xKR4vl9c1y4t!!4#f8%;TWy@~v2_%+edUBDd^Sxa{3hWshS*mi_@X46NL}x5OVI zaFmAlX!_BSg$m;BvJop6Vagc5(d3g;p*`KRS^JqdY67J47M=zuzYo-`51&I?4f@Ier{FpN6Lc zq=;~egW8RhQ{27*V+)@m?XS4(d0h5LIxY|j?8%VDi8fwbxfdE7Raf*QH~2szjB+^| zq}+xIaR$5*rp$nCnpAf`1D?Jn;xtcgwk2egt+;#}*uwlFsO+uAllOBrb_SolJ)GRK z=P{4yYQx{#0|24uN5_+=M-s$Sz**ADHP4Y^NC>Di3USlQVUnc=_UFUXzXz(>Cdepb z5`CGfWIo`{Gq!N1vL}s?HcuDA$(h_NdMVogAu3b)5r)RV`8F4F;w>}cY|3{7&;S5JJeNn9XN?pP{kzsscO@2BAD zCMb!7Q(2y|g+X{ocPIU4n{u5O7akci^$+#{FeHMnLvUT!d0WaYzj&dENt%9yV4eH_ zn+EaNI7^i_a*RTOuq}-UwBKcN-+-r^Ud?na8eblVuQ4Aq3C@|3Moigx^*)hygbWEv zCKH{!7w<_(0uU+sk$a+WQ^Lww;_<5OT!sRJ^(oOd{XP4&J}G$m=0GIZHAQ8T-!Q*% z!ob5I9LNMLtZYF|x2d(+*wJ{(g-7*TbAL4b=!0+EouR8hJlSx#6yF07J2t88d=(OF z>-t}W?G_k@r$5*5^wf$|eW+X>C=SR>d?mSZ6iB0Zzy9_%XCE)2MxKG3jS4h~t zX}eUQoM*;Fn?m9TU){e{$>gR+4PZ#Tshq-^X3c3#>2SDg1gqlVM^tyZ@qyXon?>;S zD!{wRIojxSf1vg!oU|If+=ITq zs&3?HQ=7r?^yX2Yl`G_qRcoRxC<(jaj}20yEqPZ3GErSlCVa9tHe`$N0A|IMafq7> zt34txInbM0Pu_Qtgr^5iIlwlp=RYCW)F}FPL}|r!S59`$fs@Hix*W`7whq7{ZYm)7 z9XAlwoyhY{J8st*cD#h$^DLNX^qVc4ay-mrLVU_UyIGg3!Tce=??bs!Ag=VFEMR-dC4wW+ z_Q!Md5TKJunQ_-W`;Sr%O24?jLgn@)vKNE3G$tDTZ8!qnzL@J|buzIJN3*7GdVsL% zMtIzdUHJ+F02>7k*3y`00PJ9b&&zM$1XOw@HLp1mtTXf2x(R7fx6X_2JjEtg@br;8 z6DAtgfZC-w60tK7W-=-K^hr9MJJa#}wMFekU)=wGPpF;^xzc}C8WYWqgLd800_iMvdo84N9$XiKBHF!9hrPEd%H%8anh$31Ox zBX^lps#q#PVWMxYH?7^BF@kCu^pUHa$a7SN@$}E$c4>{+=SbIjNmeM;zX;P zLPnZ$&#T;@8EkTgo73Gg!gc56?mCj6T5nkrRvzes!8#?BtEG^Uu9zR#Z)cNla(4nH zgyY?dxm?cRh}jEkTDb$Oa>`LP6m6w9oF5TrXOqK>sLYzPu{CRw^P?hZgb-8!AhVZC zBZ&^INFtpZc`jyW*_E@`y~RPt8hSW0H%X`Zkz#0!1`34mYlLFAKH_neBcp0tGEC zu2ey}@8&6IM;?L-A?0mjkR$B}&VGWO;4u?M4+0voaQS>!%nB0UCj~Ga)vRC=Q6=7b zmJ5w?U!E(X)3yqsj$}}Pm}aa@z}aKRAw)_ZrVGrlFbPnro*JlHE>6zjXtFd-bB>mQ z%G9m(M?gbZgOzP5^gI?$89!Jb}KCdl{rLbdl~t|#YXdQiRk6DYl63fKvEIMQB;J=Gz*?w%veKv4c%J3Zr22yO)*`!^;h49sT58+Dsdly z9hIhHfRwFi(g1KuM4bq6I)(*^jFnmnG8*Y7O7O-1M2r4}?A`!q+_8x{=_Qav8gNEb zo{7SAW$Lz(ppF3m9M&$jbRPox;5JBKa4Mz?E2qmn7cCueqLghnh77nxRdT<*zV8MuLDWDPwImPbbj(0G!gQIr(PS8Q z)9frZDBm=VO{Z7@5$#8QGrp}NuDs~osJ~#?!jVJ3+H>u3HcZ#P;}E9HA-2pKSg2#Y zG<7UAgtIkGe4>jFhlJC916aFb%DFr4J-cmRS4mP%=K7&1tDG}wGd3ZP>9S%~4xA}F zt0@ZF24Bl&X_{v2N1TK=JKUF2cjdb@c8Y+Hf8k(;H~c>L$GIvHxyEr@mR~NB=|>Kc`yfNP zy@mleGcaA{cx`tkR(6&K{`QTgse;viZhmTuPv0c>v2txLT0_W9yzC5VWWb}4DQcHk zCIe`S^n3ty2{`K+fHMQrWkt1yCOfN$zWSF}?@-gk%AL@A1?-;7DQ80#YvdUB2yIP> zw&5a8c<3cMU9gts{(TS7Q{|T~iu0iXXOqEkgy}lWppMa>#6Y7m>?|kNG>v7`b6}MFQcdlF%vktW2F$XGe;g+ZaQ0u1^DtekTsIBd>=7Dea;(`| zkXu;Irpe)^X*4T&)?V)WcP8y)^Z*LD;c!MUH=>D(VU~3P1UPFcz!{TuhW;8GO(IO! zFdgDk>R4A($6$b^wUC`vYx!o=G|W=wdb3$a zmy>`q;(Rb6MFNc^rtA2dB<180R0&AqgR|aEQpdy1FjxmvjsRy#OxI|N>H0hiwn-UMg*aIa*-z2$2HDCv^tfBp zG>NybTyRGzcS+@5%s?nbPgrBiaJ@jes*r#)?~PVYW4b;Y{q{hZyf#qOv4-DMAs)wRVf0TO_h^Cqw^!; z84;!n@?e{wVVepcrP*1gv?rN|8u%#k zW#1#^bj4%ko^c4tTtB%x3i6ao0?wL2_^*HdtI42%tOKIzd8-2n4fQ(cO!(in(v9kgumc6fVa`w`HC*Cy8T_J$_1KPaq=zA(vDMIupS2^|d zHYtb10H#HD$zNSXUHue6Voyg!%3bVgR$9N|Ob zHG!pRnqN8Y=+|%U#XEW~T5WEwxM=+oDkrj$=BihC&%=s0&D+5?u|t`3E(5oHYPPMKX!jkS$X{)?s9%mYeZW!9=sON>o22VJeO_FadlKwl;V4?;$i{H9_uE z^w?3|akB}fLiP%E$GbqzrY85DB8%{sL|tWGtya!NK@l-y530OTPCl;))754^j2#(y z5GX%w4t0cE8m=BSO;lQ=c2g|O*7hDm9X+i{rmFKz>M+Bnn0gD0z4guofVUgv?Psq? z!7mv=Te&ooXmf(X5vD7T%EKRdNa~oRN5Qj@aB^Xj*jc&CC4<&i?Iu&v8gK(Q>Yj@-{mHg%GSglNl>tISnHCVH;5chfCo% zCr5?|28nOOXVzw2;^aOI2Z{h?zCumC2;_KTR6Y9L;>t0yxO1_pi`K#Eb52z#+}RPK zGKB?rC}m*VeNI>e5^Dym++GpQDa9oEP-42S{k>vjBnW9UnS_Ux_rpn!at-Pj0D#&q z-Ec9-`ky2fjWdyb*ny94qJTRMexXv8QSP(0BZ*iWtlR|G#UQbhZHWOb^0Zbdgul!*edkZ#kWC0))TSTCk3 zc;7(km_s4ht`vgpCk#=83PSY?_;a03M03(y9#p{%PA1HA6D3G|H9$uV#rHA7+bY7% z(yw4a`{4VW!q;h3j-RHGk*^56<%Ho8GLVdk1ZpdhH~(|gG;ym+$_1)v+8HgT z9CJ3m<5el;jw#>}5fr$p+~@NzJ1@|XlTY3EK~&d~E7&Hbo!8HDtE7TxC8~67rR}=s zD-1XjdDO9l)ejl$uycO@l8(+7Sy=a~!>}OTG*P=5{yoxkscHJ{qRd`S*SmyEAh+@B z2qR+N<2SC!-8BLFEF3_k=93hHLx=%!ALJ6)ASoOVchn?I*DASO0f`TU7Dgy`mZVkY zAa5Xr*ghR$4*!}U|MeDwmKIcwCuh)vK&B#f2m))?;HXus+_f5G-iQhz=M_zGJUQxL z|9g%tV5p{8D)-8nx&5TB$~+WQ7pPNC`p z2wDrnEIq6b3GOZj+=t;eh}Jfsm5Ic*M zgQ6^cp&E5n{V)|F7VU$DmGiJeNFcvz3?q6!44+!koN^TdU&p%&0@~0l%573dzdQ>E z3ZOX}HVffV-bbAmofs$@s~-xt@?mI2RM`jB1|0}G^L}-IaMb_#zt4FrODC>&g&EK? z%f><2v?KQ81r&m!Uno>E09C)^i6GKaxUh02XR`tyymEuUoiKNIj$`2z6d1e(J6dMe zQIqVf!dT_L;p)O+%+e1pCv5`Cje=+4!Ucj;Z(`~nAU@8;@#@`qfsgLCI~vYNU|XN# zguc&yx;wTawvm+#JPA4-w$J$BVRW1JVNbn4tlrTOL|(bK!|Tow1iD)?SY1m|jnA;f8$nR|^$>WNWT+*|@O*FmHtsGqEZj&P4feqzOg1VN&|l!fsiYyLa;tJ|0{1 zeTz(FnX`LaB8iiRLNKSF=ciDn_Ai%&*1Bgi`v)fBIn>^nMQ%saVKi5V#z$Ba3G)Kk z-{)C!<2$0ntA*uqvFa4HOx53If9f!Ya%5Egglg@ia<_=9!#pB}E-=lYD8h4<11q(* zEVH-PmAu}&Tbas@v#}_ocJ1z#C?60GViUBH6<+9s8k+Er-99SEpNK~sCmtd+6%5brXPh(E5+MH%!| zgjXx1WQ3=uG54cxxf1hXNPKRYU`be& zQzkkIZ&^h!jqX{EcoMDLCz81UmQTdn-x?8a2$vZU8@>dniSqdVmamV9Ayo!)Y;1Z@ zf>diyC8p7e4f|Mg63Tgs9A+4aB92cAK8HchJgdx%)nkBKI{5(L{=ou?22$=;AdDj# z=b7}(RHBTEYF=byLg7Rn)C+ZE;hMtY5Xuddb2EGnId{vR91nnn%OR-w2vAFtN>?aBF$j#yKBsyWrvQp9^t%EE7TfuZONz~yHB~E7zWj$k zA}q52-=V1)5N6Gj`G+~9JeLW&-9(eQZV?AKnEg$EL9NW$9G!sm9SxwbvPSnit5s0W3+F382WaOk@&~xv2u9aeAU! z0_wGXi+S^Hr*U`tWQ)mEP>3>M{*x-k201H#M>^FrkufpHxNt|DQD5?ea+E1wlnu24 z?@~;ZHv&8!)538Oag$9RXd@c5=aI_mm{3WSq~fgmxwZOS-10i zrb-xB&i@SyS31I3ggcOQ$7qdRnRPAKxk|q z=f;ReQv{>oRIl^MCmlmL%Z^mL2{JD*7p?%{_n6tKXrN8d22CvBBb!mJpQ3#5Y<4u2 z&W#$|*V8nbfUWWj6A5d#lyEgIgL*Ekb6!|N_s*GX!P0%HByNow>C=5BdmNBO#)MBs=#E9@C>O>;G9iI z(P(%Ud#dNa8$=ccKCSYk(y;g;Hw!-x}65?>gH@DMQmfLNHaTAL&$Y+Y#43A_OwxB!H6mLWh+rVuCq-*D{Yq zIi}A|jwc4I!a{>Rh|_3_Q~hHG_Hy%#9k)*PC3e))T)5Cd4}#!xv+$evLdG#l2wzUP zIxW8!8R>Z@lgKC#CLcutM$?p!V?tAbiKcRTc@e&LY@yNUUV+(j(2r96BsbNw1L+;9 z(jRf*h^Pl(G_yTFz8Ph5tRZ|TwkcfSH@aG!hdrTf7!e=k5!_OU6P!;&y_EN8R)v*- z|^6!Y8GKnap6$AU`?2?vE&N7F|k7l;WHyV8Q;B(c|fFy zhYa=FbJh*Y2U=(}l^tckemYSIwS480#jEEyjsg(prFv8=vw&%$o`Em4L-;I^`pkJm znvVj+Rx&6bKOu{eL{mA0rZSaF1CTh)O^RtWm3XfpJMK-Dg0m57xT9X)j<|rK3`Q2V zL-@>4MJww(CLaY94h=NrL!+tA?mT5|pNTT)a@1*#Ps~sAlyj2Bi(@EC_05bDQOns; z#Dz2AFiuWye*hDMCu5PD=P82FtcxndxqVZp*XgNaps7@Dd;};KGfiV;PM+jz>8IJs zIf+~%6N#DX?JOtaF&c;y3&UuQh}_!5J`Ed%+Q?L{%22QOi1M+k5GMyG_X)6ce)PUX zQ>9Unb*8~OP9w_Mt-sK1|OXy<|&}hO`%@Tut_)#!oy$~M1Um|W#GVj z9;{CNbYFgt&oml}@IaI(l8=scs-I0cMu1`gyK{8+e3*?d)Y#b+gl}^9tD(rGMRxh` z#5}L3^WX`Y<0A$2Qp#;2<>PtyiqKT~#D(?BYUc$-INSJb+{<|oBFO*-CXJ>Q&XnrS z9r^sXEJhy(RKG??T=~D*Nh>E&#}e2}mJCnT5I%z^qvYmCDYzA99tNbgF^>WD>JZe6 z;V{79OCkqoz>+yc6yaQu_Xp^wUx!4a888Fl!2a&J=kKfRqf@Yn9gXb>MI@x&k49gI zO-3t@>YF>%Gnr3Cb?#8t8)(D~BMIS~D4vWq(Q=~^DD0o#^VtaNWMIsb3H7>S%10H6 z3;2!z5KT2`8>|X5!nq*tZ-z9$bpyjsNx7m_zo}Ebq1+?j21u20S&T3X|NSq6kSqVu z=(j%|nomU$!S4ata29?Z{^HjP!WWrnqennWE4cv__=xM|ujM@0p9Oi!>0chzurqaD zNi)>;Nj*#WMD_pfpAAyjQU*0`H(Q|M#1yR z;q9|E;C_*}*n>=?6f za2ke(O=xCgXQ5IhU@!OfQwU#hdd|(l8k-CaxJDgRyATyUN+o23^T0qFg2H8{;Hse3ksZiutVYGi z!IDDtD+q#TdMcxB@?NY?;i>z_l!a03$ON)_T3DxcVOsPkiU{F&vK@SoHxI^L65EG5 z2*P|YRY&6`O|{wOV9Eh&6C+VFAWY?YhOkw`#K-BvV@Sz43WzG_vp1)^ajV2R?cAi@C@ zji&yJg7v99pQI7b+(5PJ@>!Rk*f1O@)$^muFp+{ygjh!dFn>~?w25%y9*4P=pS?kK zs9FR;Cha1anx2PGi0foKuft&MxADEMyXDegr-B?_TLR2kt)OyC!3i=pGZ+vpE|Rx2 zwvXX+`;672>|+5EexzW>tlVB*GP-JChsR1eYlljyB!o(>oi8?z>7X5VXJ~atkyM`McNkzm zy{fkfp$1=QoP!CU^ODTEozYp~euXre-3&jiVUese@K9s>IAO``vn?O;@|EjI*fEuH z5N+mkQ0bSHszU56cBrH**Do?@6$NX@W4_xp7d;}Wv;_xiU(aKuU{wJ|&}8;pA+1eY zKvU9aR+@QDMph+4(9&y6r49EC8nVU5Fe4lEIFu!YPU^mbs(OGNCq0tYXDe@HxOU zZDS&-h4$M?Nf=Hkj00Dv7E?+Wl=7+7NMwcIt`m9X`sv>$mFlsq(HeOONR-L zXk&C3HqOU#5gi3glfx$3aM`e7`xw3Yijho9$FH}GTZQHHhETUDloE3B$mxZ!iH-=% z1A#_3N(N1NmjeUzV8_)ljE1Hz#P+?ImOM_|OGXN|w41yoEd4`GA~a2s^^2u8Ow2}0 zQhrki$1e)iHiL0a3>ZbYs1#!Rc2&t!9Lw4W0icwE^^``tnL=RF5dYui8+!10sqrIdnRY6p@nqIM``DV`F#6ugBD3Y7 zkB3Oy6V7d3J9)bJ1GTN9Ti_9H8~2xl|1;I^{M0Z4DD5c?j^&78wJAV(844b@Ph#=B z5VSV&kHj%uf_dy10LpIt6#;_WDSrwzqkgBv!wST(T73@%#Ky1nLyTR5K}Q!==+s|P zf=nxa3biI7rTgSrm@;YgIYwAw;@G~QehEg*ieG}0Evg*o1)aMhGKEf)V~O+LR73Fq zKpI9YA*G^)rSCP|ZcLqxZv;r7)C*SV5=<{CjW{LJ0iy(s)#s=-;oc>V9NZtfdHDb; zV3uI+lHyO{6(PRC(&<=5Xxy99)-s5wOE)id2z!7GSi~hooDvs(lWPKa&!uk`b91;* z^yFS_-@I@MPSukR-9ASdI48)>4c8&73n?a+?c26_y~Be~xhG_*zRGi{n0=kqJ zmiQ7fqC-&sL;ZE!V|J8vADC8njS5exNIX}EliPXlP>|gqBpyT?Z$Lq1-4-FWmLS@4fVqc)YPlQ9OL7L> zSm$n#0#h!DGMiygkVGjtQvSd|LDJyERx%(bP>>YHS|CwDp&*HuR45cAg%Br2aYUdX zDGX{qEC!0`E_~c}Aj;Q!?h{DN3Hgv?2R3plui{hY;8w&+x1_~N_+(RPHW)3j6 z#EE%h_t#Cign~ZTXtgrAT|`tM(<+DqLDr!l{j>G+beS9+WH7bnVLpPwK|$pL3Uasy zNhSwMQz|_p$`qQl=s758?x3J=gW1-}NuX5XUJ^$aWdQ{lqXMpm<=6VjNy&i|WtJ|= z5kd=d&_u=1qb)QkF`|stN34s2P*8U7h>Cf`h9{~?wWTxq|-G@C4sVhS=~0^9+DSj*4YGr>UOV72pWkM zWz>N-maGKUkVHk9mWCG64(Ad`k3>Yv&{*m)gOjW%Lye^bn88WJG6|WPB>e*!&%*zN Sp>*^B0000kXB6`qMiT}fp&)MxhR)OIRPYM&8}%2A%yLJhY)Xt3yIK}LTZv3WLcK*z5l)6 z#=)te)oKOArWdpD6A%amK4W)J*Y?oS{-tBJW&FFEU$x_BGBrPuBU8B z^`1pxYKyieLe70jo=|D==Mav&uOf*pV(m;>7X?Wk_s4by`$gREiX^7k&~nPUs2wF1JdS80 zp9nN1rRR5NeeIEB!R_vAC7%d2o>CS?QLciowe%nnXk|)S)Q)mrlj*Zr$S(qorBsUA zpLU4W;~=8?6OAcCV@|1QAj$U^9mkjU*~%0fT1=@GHQ)ax_Hk{Rrt*WADF&#WDV3r| zj^l-Jno_}O-%C*WA>$JFpUTZ%Y2+G9DT?A1On$3O2m ziHpa6FqW&dLyXn37GGXPQ9B9c?L;=Md!bY^locn8U3{dXg;hW3+)tFIv>b6U zR?FqWyEF!%m1=9_#!ccaB#A_uK;=nXM&c_`HWCr-H&sy-C7YM#7+p#SZ5{Fj%UmVi_lSs~AbwOOwa*lBo-32)ZT<&m=aTU!4IR{+sf*Au` zrFKEi0hi;>F|I;>&Hk8y~qCokjpbs6afqr^=M6c>X{=AXFb+`_X@Z>NG%AZ-#v!UwnOwLc z`&45GRk;>ZOfFoZEp{3^s0#J~&T#|Ai*vZCN`RjI&7?pX^g$KjNgri2 zl%W|(Zb&CaRq%VUfGUaLh>Ej-J{Q;2C;1#H^tw$Ld%5d2y__|@KF${EZn&7r@XSIqd4;RRF85?|D#JB_FxKnGQwCvVD&zNN zC}uRwF_ZV<>Fo6jdQf-t#r2wATloXq1LaL^S9CF72b9~Yi*yfuF?S9!04@>xINpG{iHDwUw_4-g(Nq@7Swfps! z{*Oxbj8L$yz24IApUPmYNk7cyxGMZbxqcuO(-|e3|$1)mo8Eqqp@Al_y zTj(6iXv}3ef?NXw#h5|bmc+4))l>7L4t$ir5hV>g6axd->_SK|CXQvSt7}3eQ3o-& zfJZ29eBj~ynm*-bdPX3|^=c!8hkJw~J`?mLdBo-T>i#uM(5_ii2hRvabjtR19y2&D zv~j(B2!RbQc>4V$+t;~=l^!`>^5!1PC{E5lLeaR;EM-s4%SJ!%&nqP19~n{j18D*Q U@SQEz%m4rY07*qoM6N<$f@D_Uga7~l diff --git a/public/images/pokemon/exp/shiny/754.json b/public/images/pokemon/exp/shiny/754.json deleted file mode 100644 index 18bb597aa75..00000000000 --- a/public/images/pokemon/exp/shiny/754.json +++ /dev/null @@ -1,1133 +0,0 @@ -{ - "textures": [ - { - "image": "754.png", - "format": "RGBA8888", - "size": { - "w": 234, - "h": 234 - }, - "scale": 1, - "frames": [ - { - "filename": "0036.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 0, - "w": 93, - "h": 68 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 0, - "w": 48, - "h": 68 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 0, - "y": 68, - "w": 93, - "h": 68 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 0, - "y": 68, - "w": 93, - "h": 68 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 93, - "h": 68 - }, - "frame": { - "x": 93, - "y": 68, - "w": 93, - "h": 68 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 186, - "y": 68, - "w": 48, - "h": 68 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 85, - "h": 68 - }, - "frame": { - "x": 0, - "y": 136, - "w": 85, - "h": 68 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 85, - "h": 68 - }, - "frame": { - "x": 0, - "y": 136, - "w": 85, - "h": 68 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 85, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 133, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 133, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 133, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 133, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 133, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 133, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 181, - "y": 136, - "w": 48, - "h": 68 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 93, - "h": 68 - }, - "spriteSourceSize": { - "x": 23, - "y": 0, - "w": 48, - "h": 68 - }, - "frame": { - "x": 181, - "y": 136, - "w": 48, - "h": 68 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:05bdd50d4b0041ca84c3293ee7fdf36e:adfe2b6fb11cad37f71416b628cb08c7:f7cb0e9bb3adbe899317e6e2e306035d$" - } -} diff --git a/public/images/pokemon/exp/shiny/754.png b/public/images/pokemon/exp/shiny/754.png deleted file mode 100644 index b1d4806163aa503f2e6f9d32ee7174ecb103bebe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3754 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 diff --git a/public/images/pokemon/shiny/672.json b/public/images/pokemon/shiny/672.json index dfd98acaf6b..f337bef7d29 100644 --- a/public/images/pokemon/shiny/672.json +++ b/public/images/pokemon/shiny/672.json @@ -1,41 +1,479 @@ -{ - "textures": [ - { - "image": "672.png", - "format": "RGBA8888", - "size": { - "w": 50, - "h": 50 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 42, - "h": 50 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 42, - "h": 50 - }, - "frame": { - "x": 0, - "y": 0, - "w": 42, - "h": 50 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:b346b616fb3566e3bb64cdd6b14c5d59:fb0b72a9ea01a28cfcf7731d5cf359af:2e4767b7cd134fc0ab1bb6e9eee82bc7$" - } +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0002.png", + "frame": { "x": 127, "y": 50, "w": 42, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 42, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0003.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0004.png", + "frame": { "x": 43, "y": 53, "w": 43, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 43, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0005.png", + "frame": { "x": 124, "y": 148, "w": 43, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 13, "w": 43, "h": 45 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0006.png", + "frame": { "x": 0, "y": 102, "w": 42, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 42, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0007.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0008.png", + "frame": { "x": 127, "y": 99, "w": 41, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 41, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0009.png", + "frame": { "x": 128, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0010.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0011.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0012.png", + "frame": { "x": 127, "y": 50, "w": 42, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 42, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0013.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0014.png", + "frame": { "x": 43, "y": 53, "w": 43, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 43, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0015.png", + "frame": { "x": 124, "y": 148, "w": 43, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 13, "w": 43, "h": 45 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0016.png", + "frame": { "x": 0, "y": 102, "w": 42, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 42, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0017.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0018.png", + "frame": { "x": 127, "y": 99, "w": 41, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 41, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0019.png", + "frame": { "x": 128, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0020.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0021.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0022.png", + "frame": { "x": 127, "y": 50, "w": 42, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 42, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0023.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0024.png", + "frame": { "x": 43, "y": 53, "w": 43, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 43, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0025.png", + "frame": { "x": 124, "y": 148, "w": 43, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 13, "w": 43, "h": 45 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0026.png", + "frame": { "x": 0, "y": 102, "w": 42, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 42, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0027.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0028.png", + "frame": { "x": 127, "y": 99, "w": 41, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 41, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0029.png", + "frame": { "x": 128, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0030.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0031.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0032.png", + "frame": { "x": 127, "y": 50, "w": 42, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 42, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0033.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0034.png", + "frame": { "x": 43, "y": 53, "w": 43, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 43, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0035.png", + "frame": { "x": 124, "y": 148, "w": 43, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 13, "w": 43, "h": 45 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0036.png", + "frame": { "x": 0, "y": 102, "w": 42, "h": 47 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 11, "w": 42, "h": 47 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0037.png", + "frame": { "x": 0, "y": 54, "w": 42, "h": 48 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 10, "w": 42, "h": 48 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0038.png", + "frame": { "x": 127, "y": 99, "w": 41, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 9, "w": 41, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0039.png", + "frame": { "x": 128, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0040.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0041.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0042.png", + "frame": { "x": 43, "y": 0, "w": 43, "h": 53 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 4, "w": 43, "h": 53 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0043.png", + "frame": { "x": 0, "y": 0, "w": 43, "h": 54 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 0, "w": 43, "h": 54 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0044.png", + "frame": { "x": 42, "y": 100, "w": 41, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 9, "w": 41, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0045.png", + "frame": { "x": 86, "y": 50, "w": 41, "h": 51 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 7, "w": 41, "h": 51 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0046.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0047.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0048.png", + "frame": { "x": 43, "y": 0, "w": 43, "h": 53 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 4, "w": 43, "h": 53 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0049.png", + "frame": { "x": 0, "y": 0, "w": 43, "h": 54 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 0, "w": 43, "h": 54 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0050.png", + "frame": { "x": 83, "y": 101, "w": 41, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 9, "w": 41, "h": 49 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0051.png", + "frame": { "x": 86, "y": 50, "w": 41, "h": 51 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 7, "w": 41, "h": 51 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + }, + { + "filename": "0052.png", + "frame": { "x": 86, "y": 0, "w": 42, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 8, "w": 42, "h": 50 }, + "sourceSize": { "w": 44, "h": 58 }, + "duration": 100 + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.11-x64", + "image": "672.png", + "format": "I8", + "size": { "w": 170, "h": 193 }, + "scale": "1" + } } diff --git a/public/images/pokemon/shiny/672.png b/public/images/pokemon/shiny/672.png index b25ec4f08d3793635a101a729d9ba37669dde734..8602e7a026b66294dd20c1094f6e47cef215331a 100644 GIT binary patch literal 3337 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 diff --git a/public/images/pokemon/shiny/692.json b/public/images/pokemon/shiny/692.json index fc36cdb3337..86b535260ae 100644 --- a/public/images/pokemon/shiny/692.json +++ b/public/images/pokemon/shiny/692.json @@ -1,41 +1,794 @@ -{ - "textures": [ - { - "image": "692.png", - "format": "RGBA8888", - "size": { - "w": 56, - "h": 56 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 56, - "h": 35 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 56, - "h": 35 - }, - "frame": { - "x": 0, - "y": 0, - "w": 56, - "h": 35 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:19c1aa023d08bc22ccccf8bfeaa199e7:b92ea755c18e1ed1e6d509334c6f592e:2880def858c84cd859bedf13b0b49a33$" - } +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0002.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0003.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0004.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0005.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0006.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0007.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0008.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0009.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0010.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0011.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0012.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0013.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0014.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0015.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0016.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0017.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0018.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0019.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0020.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0021.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0022.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0023.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0024.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0025.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0026.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0027.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0028.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0029.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0030.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0031.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0032.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0033.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0034.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0035.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0036.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0037.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0038.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0039.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0040.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0041.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0042.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0043.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0044.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0045.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0046.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0047.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0048.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0049.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0050.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0051.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0052.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0053.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0054.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0055.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0056.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0057.png", + "frame": { "x": 121, "y": 1, "w": 59, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 1, "w": 59, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0058.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0059.png", + "frame": { "x": 1, "y": 36, "w": 58, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 58, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0060.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0061.png", + "frame": { "x": 181, "y": 1, "w": 57, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 0, "w": 57, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0062.png", + "frame": { "x": 60, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0063.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0064.png", + "frame": { "x": 121, "y": 36, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0065.png", + "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0066.png", + "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0067.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0068.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0069.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0070.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0071.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0072.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0073.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0074.png", + "frame": { "x": 1, "y": 71, "w": 57, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0075.png", + "frame": { "x": 1, "y": 71, "w": 57, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0076.png", + "frame": { "x": 117, "y": 72, "w": 59, "h": 33 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 59, "h": 33 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0077.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0078.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0079.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0080.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0081.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0082.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0083.png", + "frame": { "x": 1, "y": 1, "w": 60, "h": 34 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 1, "w": 60, "h": 34 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0084.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0085.png", + "frame": { "x": 62, "y": 1, "w": 58, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 0, "w": 58, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0086.png", + "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + }, + { + "filename": "0087.png", + "frame": { "x": 178, "y": 37, "w": 56, "h": 35 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 56, "h": 35 }, + "sourceSize": { "w": 63, "h": 35 }, + "duration": 50 + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.12-x64", + "image": "692.png", + "format": "I8", + "size": { "w": 239, "h": 106 }, + "scale": "1" + } } diff --git a/public/images/pokemon/shiny/692.png b/public/images/pokemon/shiny/692.png index 8dc8ee90534866c392bd1d25bf699b5ce1f1c4b9..86f8cf51e927f901f82eebd19c51b559b8bb1977 100644 GIT binary patch literal 2580 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 KMNUMnLSTZ}Z5|Z> diff --git a/public/images/pokemon/shiny/753.json b/public/images/pokemon/shiny/753.json index 964519cb949..5b20ef749a0 100644 --- a/public/images/pokemon/shiny/753.json +++ b/public/images/pokemon/shiny/753.json @@ -4,30 +4,2571 @@ "image": "753.png", "format": "RGBA8888", "size": { - "w": 45, - "h": 45 + "w": 137, + "h": 137 }, "scale": 1, "frames": [ { "filename": "0001.png", "rotated": false, - "trimmed": false, + "trimmed": true, "sourceSize": { - "w": 28, - "h": 45 + "w": 30, + "h": 52 }, "spriteSourceSize": { "x": 0, - "y": 0, - "w": 28, - "h": 45 + "y": 5, + "w": 30, + "h": 47 }, "frame": { "x": 0, "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0111.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0112.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0120.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0121.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0122.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 5, + "w": 30, + "h": 47 + }, + "frame": { + "x": 0, + "y": 0, + "w": 30, + "h": 47 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, "w": 28, + "h": 47 + }, + "frame": { + "x": 30, + "y": 0, + "w": 28, + "h": 47 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 28, + "h": 47 + }, + "frame": { + "x": 30, + "y": 0, + "w": 28, + "h": 47 + } + }, + { + "filename": "0113.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 28, + "h": 47 + }, + "frame": { + "x": 30, + "y": 0, + "w": 28, + "h": 47 + } + }, + { + "filename": "0114.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 28, + "h": 47 + }, + "frame": { + "x": 30, + "y": 0, + "w": 28, + "h": 47 + } + }, + { + "filename": "0107.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 28, + "h": 47 + }, + "frame": { + "x": 58, + "y": 0, + "w": 28, + "h": 47 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 28, + "h": 47 + }, + "frame": { + "x": 58, + "y": 0, + "w": 28, + "h": 47 + } + }, + { + "filename": "0117.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 28, + "h": 47 + }, + "frame": { + "x": 58, + "y": 0, + "w": 28, + "h": 47 + } + }, + { + "filename": "0118.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 4, + "w": 28, + "h": 47 + }, + "frame": { + "x": 58, + "y": 0, + "w": 28, + "h": 47 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 0, + "w": 29, + "h": 46 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 0, + "w": 29, + "h": 46 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 0, + "w": 29, + "h": 46 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 0, + "w": 29, + "h": 46 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 0, + "w": 29, + "h": 46 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 0, + "w": 29, + "h": 46 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 0, + "w": 29, + "h": 46 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 0, + "w": 29, + "h": 46 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 0, + "w": 29, + "h": 46 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 0, + "w": 29, + "h": 46 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 0, + "w": 29, + "h": 46 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 0, + "w": 29, + "h": 46 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 46, + "w": 29, + "h": 46 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 46, + "w": 29, + "h": 46 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 46, + "w": 29, + "h": 46 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 46, + "w": 29, + "h": 46 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 46, + "w": 29, + "h": 46 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 46, + "w": 29, + "h": 46 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 46, + "w": 29, + "h": 46 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 46, + "w": 29, + "h": 46 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 46, + "w": 29, + "h": 46 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 46, + "w": 29, + "h": 46 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 46, + "w": 29, + "h": 46 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 6, + "w": 29, + "h": 46 + }, + "frame": { + "x": 86, + "y": 46, + "w": 29, + "h": 46 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, "h": 45 + }, + "frame": { + "x": 0, + "y": 47, + "w": 30, + "h": 45 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, + "h": 45 + }, + "frame": { + "x": 0, + "y": 47, + "w": 30, + "h": 45 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, + "h": 45 + }, + "frame": { + "x": 0, + "y": 47, + "w": 30, + "h": 45 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, + "h": 45 + }, + "frame": { + "x": 0, + "y": 47, + "w": 30, + "h": 45 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, + "h": 45 + }, + "frame": { + "x": 0, + "y": 47, + "w": 30, + "h": 45 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, + "h": 45 + }, + "frame": { + "x": 0, + "y": 47, + "w": 30, + "h": 45 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, + "h": 45 + }, + "frame": { + "x": 0, + "y": 47, + "w": 30, + "h": 45 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, + "h": 45 + }, + "frame": { + "x": 0, + "y": 47, + "w": 30, + "h": 45 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, + "h": 45 + }, + "frame": { + "x": 0, + "y": 47, + "w": 30, + "h": 45 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, + "h": 45 + }, + "frame": { + "x": 0, + "y": 47, + "w": 30, + "h": 45 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, + "h": 45 + }, + "frame": { + "x": 0, + "y": 47, + "w": 30, + "h": 45 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, + "h": 45 + }, + "frame": { + "x": 0, + "y": 47, + "w": 30, + "h": 45 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 27, + "h": 46 + }, + "frame": { + "x": 30, + "y": 47, + "w": 27, + "h": 46 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 27, + "h": 46 + }, + "frame": { + "x": 30, + "y": 47, + "w": 27, + "h": 46 + } + }, + { + "filename": "0115.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 27, + "h": 46 + }, + "frame": { + "x": 30, + "y": 47, + "w": 27, + "h": 46 + } + }, + { + "filename": "0116.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 27, + "h": 46 + }, + "frame": { + "x": 30, + "y": 47, + "w": 27, + "h": 46 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, + "h": 45 + }, + "frame": { + "x": 0, + "y": 92, + "w": 30, + "h": 45 + } + }, + { + "filename": "0119.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 7, + "w": 30, + "h": 45 + }, + "frame": { + "x": 0, + "y": 92, + "w": 30, + "h": 45 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 8, + "w": 29, + "h": 44 + }, + "frame": { + "x": 57, + "y": 47, + "w": 29, + "h": 44 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 8, + "w": 29, + "h": 44 + }, + "frame": { + "x": 57, + "y": 47, + "w": 29, + "h": 44 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 8, + "w": 29, + "h": 44 + }, + "frame": { + "x": 57, + "y": 47, + "w": 29, + "h": 44 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 8, + "w": 29, + "h": 44 + }, + "frame": { + "x": 57, + "y": 47, + "w": 29, + "h": 44 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 8, + "w": 29, + "h": 44 + }, + "frame": { + "x": 57, + "y": 47, + "w": 29, + "h": 44 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 0, + "y": 8, + "w": 29, + "h": 44 + }, + "frame": { + "x": 57, + "y": 47, + "w": 29, + "h": 44 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 57, + "y": 91, + "w": 28, + "h": 44 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 57, + "y": 91, + "w": 28, + "h": 44 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 57, + "y": 91, + "w": 28, + "h": 44 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 57, + "y": 91, + "w": 28, + "h": 44 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 57, + "y": 91, + "w": 28, + "h": 44 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 57, + "y": 91, + "w": 28, + "h": 44 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 57, + "y": 91, + "w": 28, + "h": 44 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 57, + "y": 91, + "w": 28, + "h": 44 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 57, + "y": 91, + "w": 28, + "h": 44 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 57, + "y": 91, + "w": 28, + "h": 44 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 57, + "y": 91, + "w": 28, + "h": 44 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 57, + "y": 91, + "w": 28, + "h": 44 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 85, + "y": 92, + "w": 28, + "h": 44 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 85, + "y": 92, + "w": 28, + "h": 44 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 85, + "y": 92, + "w": 28, + "h": 44 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 85, + "y": 92, + "w": 28, + "h": 44 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 85, + "y": 92, + "w": 28, + "h": 44 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 30, + "h": 52 + }, + "spriteSourceSize": { + "x": 2, + "y": 8, + "w": 28, + "h": 44 + }, + "frame": { + "x": 85, + "y": 92, + "w": 28, + "h": 44 } } ] @@ -36,6 +2577,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:0dfeaaa8dc9e984b8345da123854713a:ca6cbb0693924b86228f2fe9ab294ec9:16c1874bc814253ca78e52a99a340ff7$" + "smartupdate": "$TexturePacker:SmartUpdate:0a066207f4eaa63438f1b44ba24dbced:1c3824600c00c692fd9dab4dbe03a2ed:16c1874bc814253ca78e52a99a340ff7$" } } diff --git a/public/images/pokemon/shiny/753.png b/public/images/pokemon/shiny/753.png index e13412f2a4f3bceaace7423bcdf472f49eb8ab1d..12f0f7a090f25a4f94d78f930a5487b7512ad024 100644 GIT binary patch delta 2042 zcmVOSH8jNmbK+zRH4ZEg)i}ErAO% zbK8FRe0m`fYC$NhCVrL_$9z4gNCa9Pa&|mX0)H&8gPJ1H>X5FdY)JKyBIM$JX$$OuJ?@iF1e!=0TtV)i&s8w2pAg6= zLL*NpHIU@07`7iiCS?QLciowe%nnXk|)S)Q)mrlj*Zr$S(qorBsUApLU4W;~=8? z6OAcCV@|1QAj$U^9mkjU*~%0fT1=@GHQ)ax_Hk{Rrt*WADF&#We<_usMvmi!aGFxV zY2Qmw`61&H_n*qmUTNeSODT%tUF>{RoTjhTDC6PPwQv_>(6dW(B%i>B>SL{Tfr`QFilSzc z_xnk7yB1rM>q#-^f2PVLOFD;lp+(=5RE-q{QRsLX`aE8**Xxnq%b&+T?>UK!$9^!D ztF%Lm)v^{}UPVzm3FYlXHm!T1R5FwmFQwMF7`0t|q@smYKj_>~l%}*CaWPiQ<-)r( z2B4K{YvaaE;w>bJM4Le6NnA$aD^WHQ5$!isQ4}ScsIr`*e^QKTq-ocGarXR}sZWH) zv^@FSu`Lr4hgSPv9Mb@kNX}n%L0r*tj&T*;1vv*??r@HA70m@X2VCxg83SCUc0tYo zm*dVcu0np!0hgN?%oyM*1_>|=+RK$r>bxS-1#!hD+smEKo{M6eAm`X+=~N&{2Uh_m z$T_65SdBZ~e_REa8cc%8(MUJvIrIY_$T`wQVMf8I=3cI1njo%7A4Bxj6lWV}*e1w1 z4z=!ylLk6`Tm_gQ=QtmkSvhZoA+)&)FhS0t8fUTyiG!;k6XYDpSr|E^;tat@Gk^(l z4&A7Nj=3wz(_lC@=SaVErI9p(;N&XM1Ubk4YA+y;e_i_gRObyj$7!rjyD$z>6={N;<1{LZUB)4*Qkh)1BKuTh z2UWQie^X2@T%j#?8at>8_5jXt1ICMUxT#8jp8d_FKpEsRhLV9Cr?B)v72!!AWi*tb z8A)zPCq`B9d$E8jiQtHevw|xOV?{_d-*yV^k2}n=CQq@;IH8I}8H$l_^a?^Um7t>m zRqn#WKPsHyBrjhZcQKrpLn9=ifikkC#1&`7e@HF_%1FFL8oBQzBn&UKQHEq044fo7 zqN0p!y!!UVK3s}zl#wl$rvNAK?I(sZiX_6gY5N5ugOIdQ2HmQH+>;qjL>W7dc$aHs zJdje#XTpUSd`N|X00Bi~Ue;Y6S8@OT|E&OiL ze+F6@OWEcX04>-J{Q;2C;1#H^tw$Ld%5d2y__|@KF${EZn&7r@XSIqd4;RRF85?|D#JB_FxKnGQwCvVD&zNN zC}uRwF_ZV<>Fo6jdQf-t#r2w=%t~IUq7G9ZBZC8Hxa=3_@M4Ue?|0M^gs*<%C}^R}joATYRj?y!DpsX!fVJz1q{izlNqV8oUBG{PmvxeI1#~Xz+?w!=Lr2i*w5- z*HshFWq2C?%9z2ue=MUhm(kc^e@)Y*j~R52Wwhopnp?f2H9ytg?VmcwG8%IkZ6k^A z_UCO|=p4&v%w;%&Tmu8em_gc>#IcOkQ}dz@e3ZZuB@H|j0|VFWLP#(sj%BQ?YeFPZ z2Qj#SM<{N5;Nkq5KILY5Mj*!ZY9oY)dxRoB6Z9l`#O3(v{xwX{u31wDP|pZObjtR1 z9y2&Dv~j(B2!RbQc>4V$+t;~=l^!`>^5!1PC{E5lLeaR;EM-s4%SJ!%&nqP19~n{j Y18D*Q@SQEz%m4rY07*qoM6N<$g8!J$?EnA( delta 410 zcmV;L0cHNG5Z41CiBL{Q4GJ0x0000DNk~Le0000j0000j2m=5B01d|OwlvFq7Ol;de0wuP^Kz!}5ebJ-Py*_Xm zHx3JUXrJGumx{kjFBB&tQA9dAnvUb3y*}4bzh&z~v5cC)qCOnmcrHp*d%?lnCu71bH*hFrXv&4i+Et(^b07*qoM6N<$ Eg8inv3jhEB diff --git a/public/images/pokemon/shiny/754.json b/public/images/pokemon/shiny/754.json index ecb5b8a6779..18bb597aa75 100644 --- a/public/images/pokemon/shiny/754.json +++ b/public/images/pokemon/shiny/754.json @@ -4,29 +4,1121 @@ "image": "754.png", "format": "RGBA8888", "size": { - "w": 68, - "h": 68 + "w": 234, + "h": 234 }, "scale": 1, "frames": [ { - "filename": "0001.png", + "filename": "0036.png", "rotated": false, "trimmed": false, "sourceSize": { - "w": 46, + "w": 93, "h": 68 }, "spriteSourceSize": { "x": 0, "y": 0, - "w": 46, + "w": 93, "h": 68 }, "frame": { "x": 0, "y": 0, - "w": 46, + "w": 93, + "h": 68 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 93, + "h": 68 + }, + "frame": { + "x": 0, + "y": 0, + "w": 93, + "h": 68 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 93, + "h": 68 + }, + "frame": { + "x": 0, + "y": 0, + "w": 93, + "h": 68 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 93, + "h": 68 + }, + "frame": { + "x": 93, + "y": 0, + "w": 93, + "h": 68 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 93, + "h": 68 + }, + "frame": { + "x": 93, + "y": 0, + "w": 93, + "h": 68 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 93, + "h": 68 + }, + "frame": { + "x": 93, + "y": 0, + "w": 93, + "h": 68 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 93, + "h": 68 + }, + "frame": { + "x": 93, + "y": 0, + "w": 93, + "h": 68 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 93, + "h": 68 + }, + "frame": { + "x": 93, + "y": 0, + "w": 93, + "h": 68 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 93, + "h": 68 + }, + "frame": { + "x": 93, + "y": 0, + "w": 93, + "h": 68 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 0, + "w": 48, + "h": 68 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 0, + "w": 48, + "h": 68 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 0, + "w": 48, + "h": 68 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 0, + "w": 48, + "h": 68 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 0, + "w": 48, + "h": 68 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 0, + "w": 48, + "h": 68 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 0, + "w": 48, + "h": 68 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 0, + "w": 48, + "h": 68 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 0, + "w": 48, + "h": 68 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 0, + "w": 48, + "h": 68 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 93, + "h": 68 + }, + "frame": { + "x": 0, + "y": 68, + "w": 93, + "h": 68 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 93, + "h": 68 + }, + "frame": { + "x": 0, + "y": 68, + "w": 93, + "h": 68 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 93, + "h": 68 + }, + "frame": { + "x": 93, + "y": 68, + "w": 93, + "h": 68 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 68, + "w": 48, + "h": 68 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 68, + "w": 48, + "h": 68 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 68, + "w": 48, + "h": 68 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 68, + "w": 48, + "h": 68 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 68, + "w": 48, + "h": 68 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 68, + "w": 48, + "h": 68 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 68, + "w": 48, + "h": 68 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 68, + "w": 48, + "h": 68 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 68, + "w": 48, + "h": 68 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 68, + "w": 48, + "h": 68 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 186, + "y": 68, + "w": 48, + "h": 68 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 85, + "h": 68 + }, + "frame": { + "x": 0, + "y": 136, + "w": 85, + "h": 68 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 85, + "h": 68 + }, + "frame": { + "x": 0, + "y": 136, + "w": 85, + "h": 68 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 85, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 85, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 85, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 85, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 85, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 85, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 85, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 85, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 85, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 85, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 133, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 133, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 133, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 133, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 133, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 133, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 181, + "y": 136, + "w": 48, + "h": 68 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 93, + "h": 68 + }, + "spriteSourceSize": { + "x": 23, + "y": 0, + "w": 48, + "h": 68 + }, + "frame": { + "x": 181, + "y": 136, + "w": 48, "h": 68 } } @@ -36,6 +1128,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:f4866096a7f384feda7689540b5499f2:1c5d416a85ea018909e59a1eb9c84c3a:f7cb0e9bb3adbe899317e6e2e306035d$" + "smartupdate": "$TexturePacker:SmartUpdate:05bdd50d4b0041ca84c3293ee7fdf36e:adfe2b6fb11cad37f71416b628cb08c7:f7cb0e9bb3adbe899317e6e2e306035d$" } } diff --git a/public/images/pokemon/shiny/754.png b/public/images/pokemon/shiny/754.png index f50262ab10ed86de88ba36b22827ed93d058280d..b1d4806163aa503f2e6f9d32ee7174ecb103bebe 100644 GIT binary patch delta 3725 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 diff --git a/public/images/pokemon/variant/exp/back/672.json b/public/images/pokemon/variant/exp/back/672.json deleted file mode 100644 index c118d603d57..00000000000 --- a/public/images/pokemon/variant/exp/back/672.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "1": { - "3d3128": "69112a", - "67615b": "9e2c3d", - "615140": "89431b", - "7e6d5a": "b3743e", - "554538": "642509", - "efeded": "f8e2b7", - "beb8b6": "e3a378", - "0e5d58": "8c6859", - "09a77c": "f8f0e2", - "0d8374": "d2af94", - "c16a3f": "321512", - "c6b379": "552d30", - "a8905c": "4b2525" - }, - "2": { - "3d3128": "161526", - "67615b": "2d2b40", - "615140": "4c7a68", - "7e6d5a": "72b692", - "554538": "305a4f", - "efeded": "ffeffe", - "beb8b6": "d4b3d7", - "0e5d58": "363e6c", - "09a77c": "96d5e3", - "0d8374": "6885b6", - "c16a3f": "612c6b", - "c6b379": "9f5f9b", - "a8905c": "854d87" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/692.json b/public/images/pokemon/variant/exp/back/692.json deleted file mode 100644 index d4c85f37c9d..00000000000 --- a/public/images/pokemon/variant/exp/back/692.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "1": { - "337380": "783a1d", - "b3b3b3": "c8ba6d", - "595959": "c85b5b", - "61daf2": "e1ac53", - "cc9c3d": "53be53", - "404040": "7d182d", - "ffc44c": "a9f076", - "b2f2ff": "fada7f", - "47a1b3": "af6a37", - "101010": "070707", - "735822": "20734c" - }, - "2": { - "337380": "5f3c23", - "b3b3b3": "68a7aa", - "595959": "88cd56", - "61daf2": "e1d6b6", - "cc9c3d": "7743be", - "404040": "1c873e", - "ffc44c": "a36feb", - "b2f2ff": "faf8d7", - "47a1b3": "968144", - "101010": "070707", - "735822": "371c72" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/693.json b/public/images/pokemon/variant/exp/back/693.json deleted file mode 100644 index 3187a81e0c0..00000000000 --- a/public/images/pokemon/variant/exp/back/693.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "1": { - "224b73": "552813", - "4595e5": "aa6839", - "23a2c8": "c87a23", - "262626": "230808", - "cc9c3d": "1b3c17", - "404040": "3c171b", - "5f5f5f": "6e2e3b", - "61daf2": "f2bd61", - "3674b3": "7d3e21", - "ffc44c": "426e2e", - "735822": "08230e" - }, - "2": { - "224b73": "5f463a", - "4595e5": "c8b493", - "23a2c8": "beb099", - "262626": "295a1c", - "cc9c3d": "6259af", - "404040": "2a8c53", - "5f5f5f": "51c85d", - "61daf2": "f0eadb", - "3674b3": "9b8265", - "ffc44c": "a39afa", - "735822": "36235f" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/753.json b/public/images/pokemon/variant/exp/back/753.json deleted file mode 100644 index 26e48f43509..00000000000 --- a/public/images/pokemon/variant/exp/back/753.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "1": { - "234028": "2e1643", - "5ba668": "4e2c62", - "468050": "3e2253", - "315945": "0e2616", - "549977": "1b3822", - "69bf94": "27452c", - "d98d9a": "a55c36", - "ffbfca": "b47145", - "803340": "682c16", - "cc5266": "a55c36" - }, - "2": { - "234028": "531034", - "5ba668": "ce54b0", - "468050": "9b2d76", - "315945": "441342", - "549977": "5a215a", - "69bf94": "6e3472", - "d98d9a": "263b83", - "ffbfca": "3454a5", - "803340": "0b1d4e", - "cc5266": "263b83" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/754.json b/public/images/pokemon/variant/exp/back/754.json deleted file mode 100644 index 5fb99ea57c9..00000000000 --- a/public/images/pokemon/variant/exp/back/754.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "1": { - "803340": "82180e", - "ff667f": "c95623", - "cc5266": "ac351f", - "ffbfca": "f48b49", - "d98d9a": "c95623", - "315945": "122a1a", - "69bf94": "314e36", - "bfbfbf": "c9d6b7", - "737373": "859970", - "f8f8f8": "eff7e2" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/754_2.json b/public/images/pokemon/variant/exp/back/754_2.json deleted file mode 100644 index f32f0133f99..00000000000 --- a/public/images/pokemon/variant/exp/back/754_2.json +++ /dev/null @@ -1,1112 +0,0 @@ -{ - "textures": [ - { - "image": "754_2.png", - "format": "RGBA8888", - "size": { - "w": 222, - "h": 222 - }, - "scale": 1, - "frames": [ - { - "filename": "0036.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 92, - "y": 0, - "w": 92, - "h": 68 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 184, - "y": 0, - "w": 38, - "h": 68 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 68, - "w": 92, - "h": 68 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 68, - "w": 92, - "h": 68 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 136, - "w": 92, - "h": 68 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 92, - "h": 68 - }, - "frame": { - "x": 0, - "y": 136, - "w": 92, - "h": 68 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 88, - "h": 68 - }, - "frame": { - "x": 92, - "y": 68, - "w": 88, - "h": 68 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 88, - "h": 68 - }, - "frame": { - "x": 92, - "y": 68, - "w": 88, - "h": 68 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 40, - "h": 68 - }, - "frame": { - "x": 180, - "y": 68, - "w": 40, - "h": 68 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 40, - "h": 68 - }, - "frame": { - "x": 180, - "y": 68, - "w": 40, - "h": 68 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 39, - "h": 68 - }, - "frame": { - "x": 92, - "y": 136, - "w": 39, - "h": 68 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 131, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 92, - "h": 68 - }, - "spriteSourceSize": { - "x": 25, - "y": 0, - "w": 38, - "h": 68 - }, - "frame": { - "x": 169, - "y": 136, - "w": 38, - "h": 68 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:3adad944aac48ad53efa41f8c9916d1c:ea15b954875ad08814f50cbbf849c1b3:f7cb0e9bb3adbe899317e6e2e306035d$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/754_2.png b/public/images/pokemon/variant/exp/back/754_2.png deleted file mode 100644 index 85eadd7428f1f47714ab694948233ca6260f4d25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3646 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 aqf9^bo)+t5(&1;3?Uu2nQLTYX)c*kQPkuK5 diff --git a/public/images/statuses_tl.json b/public/images/statuses_tl.json new file mode 100644 index 00000000000..094b0188d69 --- /dev/null +++ b/public/images/statuses_tl.json @@ -0,0 +1,188 @@ +{ + "textures": [ + { + "image": "statuses_tl.png", + "format": "RGBA8888", + "size": { + "w": 22, + "h": 64 + }, + "scale": 1, + "frames": [ + { + "filename": "pokerus", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 22, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 22, + "h": 8 + }, + "frame": { + "x": 0, + "y": 0, + "w": 22, + "h": 8 + } + }, + { + "filename": "burn", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 8, + "w": 20, + "h": 8 + } + }, + { + "filename": "faint", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 16, + "w": 20, + "h": 8 + } + }, + { + "filename": "freeze", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 24, + "w": 20, + "h": 8 + } + }, + { + "filename": "paralysis", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 32, + "w": 20, + "h": 8 + } + }, + { + "filename": "poison", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 40, + "w": 20, + "h": 8 + } + }, + { + "filename": "sleep", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 48, + "w": 20, + "h": 8 + } + }, + { + "filename": "toxic", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 56, + "w": 20, + "h": 8 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:37686e85605d17b806f22d43081c1139:70535ffee63ba61b3397d8470c2c8982:e6649238c018d3630e55681417c698ca$" + } +} diff --git a/public/images/statuses_tl.png b/public/images/statuses_tl.png new file mode 100644 index 0000000000000000000000000000000000000000..9f24c6a0810ebd6ca4ecbc752e77f3ec43243352 GIT binary patch literal 419 zcmV;U0bKrxP)==ukM|a8Nin zUPJ%@00eYWPE!B?006U-W|{y10T@X{K~#8Nt<&3%!!QU0P|{BB?Oy)>XLlHZ&|-O? z77^o|xPexx+5R!aE(Jiy3b+%Xj0#nYQ;qgTfCQL zafK2f0dh0#$wV#g)>xQK^Mo53x0k48NVYV23+eOq)#CLeyMieJv6KQ@%*&1 zc+zq{psja{H5E#zKyFk5IS_=#cjI^f_s%l$&{Dk42ejj}#SsdC0B9HPz^sMvH^=9p zT`{kI`WX3`4>ofVQ12wp0KoSh%Enl@*`= z#hX}t{ql925BM0r&Vx+71Q&}ke{ZqmViJnwBiH%j#A3B%w3d?re*h(mIE1ye$|wK; N002ovPDHLkV1kAS!6N_w literal 0 HcmV?d00001 diff --git a/public/images/types_tl.json b/public/images/types_tl.json new file mode 100644 index 00000000000..2706c6f49f3 --- /dev/null +++ b/public/images/types_tl.json @@ -0,0 +1,440 @@ +{ + "textures": [ + { + "image": "types_tl.png", + "format": "RGBA8888", + "size": { + "w": 32, + "h": 280 + }, + "scale": 1, + "frames": [ + { + "filename": "unknown", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + } + }, + { + "filename": "bug", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 14, + "w": 32, + "h": 14 + } + }, + { + "filename": "dark", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 28, + "w": 32, + "h": 14 + } + }, + { + "filename": "dragon", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 42, + "w": 32, + "h": 14 + } + }, + { + "filename": "electric", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 56, + "w": 32, + "h": 14 + } + }, + { + "filename": "fairy", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 70, + "w": 32, + "h": 14 + } + }, + { + "filename": "fighting", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 84, + "w": 32, + "h": 14 + } + }, + { + "filename": "fire", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 98, + "w": 32, + "h": 14 + } + }, + { + "filename": "flying", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 112, + "w": 32, + "h": 14 + } + }, + { + "filename": "ghost", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 126, + "w": 32, + "h": 14 + } + }, + { + "filename": "grass", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 140, + "w": 32, + "h": 14 + } + }, + { + "filename": "ground", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 154, + "w": 32, + "h": 14 + } + }, + { + "filename": "ice", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 168, + "w": 32, + "h": 14 + } + }, + { + "filename": "normal", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 182, + "w": 32, + "h": 14 + } + }, + { + "filename": "poison", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 196, + "w": 32, + "h": 14 + } + }, + { + "filename": "psychic", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 210, + "w": 32, + "h": 14 + } + }, + { + "filename": "rock", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 224, + "w": 32, + "h": 14 + } + }, + { + "filename": "steel", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 238, + "w": 32, + "h": 14 + } + }, + { + "filename": "water", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 252, + "w": 32, + "h": 14 + } + }, + { + "filename": "stellar", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 266, + "w": 32, + "h": 14 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:f14cf47d9a8f1d40c8e03aa6ba00fff3:6fc4227b57a95d429a1faad4280f7ec8:5961efbfbf4c56b8745347e7a663a32f$" + } +} diff --git a/public/images/types_tl.png b/public/images/types_tl.png new file mode 100644 index 0000000000000000000000000000000000000000..b9fdceba7da236067f326c6ae0411d21ecc3aca6 GIT binary patch literal 1861 zcmV-L2fFx)P)w(#bTLPPF?g0TJzF&)W;aGAIh_AGV0S+vJ3(AuL^4fDElo&gWl5En=CyV`H^s7$9UM5@tAf zXfp?BNC0zCdw3yLdq{hG&Qg3B7=fm~gQZ%LLCcg-i=3_ZrBXSqrIfCEJG%e2y`@^c z!T-L{sLQ$k%;5jcUA@h+O4#N9<<_9+;Q#5mjP5u}?o}!8L3!_R@9&lW^z+{JL&|9_&Ef3f1>&ZhtW|NsBSF=(KWb6QHX@AnA+000DZQchC<00029lV+L#00qrS zL_t(|Uc8T4Zp1JQLy4Ia%3Ee_d;e3EQDO*g1@^BG6l22=F$_EJ z?G(5z?pX06tFX(bu$AHmzFvhwg+tYglL*_qBK?D(%@g1LeuZC?y~5c%oVg7_fue?r*X5*;aT1UVdHqv4afmw?wAuLy9LSD@IiV(Quc+FP~!iq1) z%R}K0Ftjj$Ap!7~@ccva220|CRpuz0Yc5iuGUmgCz|CTvix75sy~0TThsYw(h$|u? zxU)|#)av0Z2|nI_&{UEwux5ir$@|wkLwHc4G?+ zig8MPS%4RY2L>>#0Nky(f3v!SVMQQ%nw5_1LXCi&I?Y#IIf2~iXLV%(+G)E2!6=~j zKR}(JuP`SncqZck^iD6aJ_&fCd!Pf|3c$A&_n%hZU|2nXh{84-=emXXsh zphc4_7C0=&>X(12zI;N6P*7pr$LtP+TW zzTzB%4{HIa1z^era9RMq@_OQ5fFrU(tDGJH+jN$OoG!o%hX)Seu=?S9jFB4(VIaC` zD5z@eEv=gpbe^Cg$Oo3MW}c^8JsC;)*M0uKfdG65l+Cfvp})o>5o{#beGmyIBJyU!{C0bI;6y~PT@xEa?SuKz01D1$&&`XtCQ6$q*ZWr+%P z+wQPjCc>41l0%K>w#APfXQ#qdMB4=gGDF=v$|zLTVlr7ws;Vl>*=*Lf7zMRd!Np%{~g5QHxY$a-t%-rTob~bzRE`&F;ShLE)A900000NkvXXu0mjf9426$ literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/party_bg_double_manage.png b/public/images/ui/legacy/party_bg_double_manage.png new file mode 100644 index 0000000000000000000000000000000000000000..2bf2d63c3154c6f6f28101d351899c3170753130 GIT binary patch literal 431 zcmeAS@N?(olHy`uVBq!ia0y~yU~~Yox3Dk+N%2!Jlz@~_fKQ04hetv}1VhIRjSU+j zPF$#X@S?*b=ny|pd4#8nV@O3@Qh?Nxge#3I4T0=^RsoU+_T9bh!o!*nOL7kyWVeXD&{?48nhv#qhU|MW}uTI4_ tb@7<(Q=06hADPGYS`}PrT+GM95V7FIR?(B*mcSTb@O1TaS?83{1OS1sq`m+E literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/party_discard.json b/public/images/ui/legacy/party_discard.json new file mode 100644 index 00000000000..4aa563fcd77 --- /dev/null +++ b/public/images/ui/legacy/party_discard.json @@ -0,0 +1,62 @@ +{ + "textures": [ + { + "image": "party_discard.png", + "format": "RGBA8888", + "size": { + "w": 75, + "h": 50 + }, + "scale": 1, + "frames": [ + { + "filename": "normal", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 75, + "h": 25 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 75, + "h": 25 + }, + "frame": { + "x": 0, + "y": 0, + "w": 75, + "h": 25 + } + }, + { + "filename": "selected", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 75, + "h": 25 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 75, + "h": 25 + }, + "frame": { + "x": 0, + "y": 25, + "w": 75, + "h": 25 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:17219773dfffd6b1204d988fea3f9462:1127ad21d64bc7ebb9df4fc28f3d2d39:7ad46e8fb4648c3d3d84a746ecb371ea$" + } +} diff --git a/public/images/ui/legacy/party_discard.png b/public/images/ui/legacy/party_discard.png new file mode 100644 index 0000000000000000000000000000000000000000..d95ba6960152815beb50ca60c39624525d694622 GIT binary patch literal 346 zcmeAS@N?(olHy`uVBq!ia0vp^-au@`!VDyz`C4B9QqloFA+A8$!y};}V#8FQiUkE9 zHYIM^X>WfdsNzR~LDCoZOrSJlNswPKgTu2MX+X|fPZ!6KinzU#4f&c4cwAo|li0!N z`ifD)!ZFa2L8w~$Da+bTX$cQ*94)kYcANRpdH?^X|CPCBO^^|EO7zp1yW4ctrpRjc zQ)NDt{1(%VI3<25T?>5Gd&aH)Mew%AXYQ%$2`_uoHU+ohq^|Dx4vhH4J>gKncQ{K+tVk|IB z724qAnzQW6tTWdNUW5aU)>P328Jz|*T0HT7@CILy(FTnWqsw001%s0{{R3=1n`$0000RP)t-s0000A z@3s)BmOz1{NJwB+u+wl@u%K}8_;4_OM|0i)0004WQchCyU5JW2wSOhr_$$dZtx}vE^;i17J2$gm1XP1}TRL<_xFEf|S-UaT88-{D)nHBM> zyNiWAR)NJ~NzmwA*Ch+>Noz&eHS4(2xpckPO6RpZ8ANN*I>_^&HA|a&N8@eCgVHea zLSXM`OP)KB8hB~8m3kX%Rex>n#R_a{+8{O!T}braXmtty1&CM>u;8nBhb%m^B3}J< zv9Qk}usAFU8lA_nC!xJ)tqB{Gjw_ve(|WCRj?Kv=T8q{|+9s`8+SWQ6Z$qAxhLLsx zTSwc|)_~N&OS7%i+gPjqYg;Q;U{lj3v1#Z+qW4CtOZYE9!~);?0S@8G#sK zXvnGQD=I1~X&Gy3Y8qHL8X6j!7`R%Pcv@OoIypIcx<-0=c?AUpMMXs=CnrzLKDC#D zf$5;9i(^Q|t+#jY_FXm*V7uV(LZIoi*n_|Gr*=1mX#HZIY_hF*-|VcX-S6aX-aR4b z-~MsV;q}*lzIxvL@%Lk9g$4#jCKi?g?E{DB&*cA}{6{Nofhv!Hf`bD$quZInb0v>W z&F@(!OexZOE|b6K*|EjW2YxCoziM1Q{j{V+>RZVOuj}4FL>rb@{NY~rU2@NZA88G$ za*W4zz2ASKB8M$v+3$wHSqy({1VAqKXrj*2Iu>gZoA447&556?_@df#&x#TwlC3ZC(0c! zoO5{LrTteu@(cBDAKm$(Hq1;XRZF7W@O3re@0pMlI)k#!bfcYRAmHajDjDvbhR^|6J zf1UJ7n)$=Q3Wk`kJ}e6!DK)%ezRDhT2o&5PW8E9xaqrg8;q3qA@qUNfE-Q!HCFM*j z`oDa7eu&{K{}uZSLTo@~4zNJroIdyK#C1#aeKyxFSq-%JZQM`(+9krKm$rWC&D{Ob zkKxb%Uk>+;=B(ylvpC=9wRgqmeTK3OWnbBWwq39|aQzCqS>*ii;MxU}X`IYJ2Lya> z$o)R?p4^77c9(v!r~UTGw_&z^q0Afb-3*)}BEC$#=6(z6hHaarm~z&*?K-t+cQE(P zNco6^Go4SHUgGPWa|#qj7cMtUI>Yao`a^Fe&wZ7t4_7ZS+T*EpSR5##vyx{%P-O8E vqnZgJZQmzx0ps1;^JrdI2NayB`y{-Z-FxlHe76_C{J`Mp>gTe~DWM4f{^n*n literal 0 HcmV?d00001 diff --git a/public/images/ui/party_discard.json b/public/images/ui/party_discard.json new file mode 100644 index 00000000000..4aa563fcd77 --- /dev/null +++ b/public/images/ui/party_discard.json @@ -0,0 +1,62 @@ +{ + "textures": [ + { + "image": "party_discard.png", + "format": "RGBA8888", + "size": { + "w": 75, + "h": 50 + }, + "scale": 1, + "frames": [ + { + "filename": "normal", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 75, + "h": 25 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 75, + "h": 25 + }, + "frame": { + "x": 0, + "y": 0, + "w": 75, + "h": 25 + } + }, + { + "filename": "selected", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 75, + "h": 25 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 75, + "h": 25 + }, + "frame": { + "x": 0, + "y": 25, + "w": 75, + "h": 25 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:17219773dfffd6b1204d988fea3f9462:1127ad21d64bc7ebb9df4fc28f3d2d39:7ad46e8fb4648c3d3d84a746ecb371ea$" + } +} diff --git a/public/images/ui/party_discard.png b/public/images/ui/party_discard.png new file mode 100644 index 0000000000000000000000000000000000000000..e56c845eadccfb9462418fa7c67996915e793d40 GIT binary patch literal 386 zcmV-|0e$|7P)001%s0{{R3=1n`$0000dP)t-s00008 zT8s#cwILxXIXO~hR)TYLgn2wkoSdz#&epjR*P_;X9!G-PkFWl(aN~a zFF_a-4bh4KT17)NUIEbpP&7oFSmRBRZJD%h&YE%!Bjp;x;s3ULrb(k4f~td}+4jx4 gy~bOxUY7Ar|BwSDE^sg}CjbBd07*qoM6N<$f{o9q?EnA( literal 0 HcmV?d00001 diff --git a/public/images/ui/party_transfer.json b/public/images/ui/party_transfer.json new file mode 100644 index 00000000000..7cfcf5ccc30 --- /dev/null +++ b/public/images/ui/party_transfer.json @@ -0,0 +1,62 @@ +{ + "textures": [ + { + "image": "party_transfer.png", + "format": "RGBA8888", + "size": { + "w": 75, + "h": 50 + }, + "scale": 1, + "frames": [ + { + "filename": "normal", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 75, + "h": 25 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 75, + "h": 25 + }, + "frame": { + "x": 0, + "y": 0, + "w": 75, + "h": 25 + } + }, + { + "filename": "selected", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 75, + "h": 25 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 75, + "h": 25 + }, + "frame": { + "x": 0, + "y": 25, + "w": 75, + "h": 25 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:17219773dfffd6b1204d988fea3f9462:1127ad21d64bc7ebb9df4fc28f3d2d39:7ad46e8fb4648c3d3d84a746ecb371ea$" + } +} diff --git a/public/images/ui/party_transfer.png b/public/images/ui/party_transfer.png new file mode 100644 index 0000000000000000000000000000000000000000..45815a156b5a97589eee9d390fcf242f16862a39 GIT binary patch literal 403 zcmV;E0c`$>P)001%s0{{R3=1n`$0000dP)t-s00008 zT8s#cwF#x63h%ZMsg@xjDJ5}qIXO~ubA+6nt*y@1|NsB4BSDG)0004WQchC>zWWkSeKMRky!inK$sX415(GDDK{9D=5T9(R{oRhE$v-rtT}O`=&f~C?S{shz zNohPBcxJJq1GG^W)PyS_6trt!4C5gOYdceaK}hZ;0?8}6-VpFJrMr+t2@YGWKR zCuJPzyDvAyyKnyOMV!R@vb^sUtRxFqzmF(*QHzpE%cJ;YPHp{KCMI)eNP8w8E#8xK zE44f>la11tmR6} the selected type + * @returns {Promise} the selected type */ async function promptTestType() { - const typeAnswer = await inquirer.prompt([ - { - type: "list", - name: "selectedOption", - message: "What type of test would you like to create?", - choices: [...choices.map(choice => ({ name: choice.label, value: choice })), "EXIT"], - }, - ]); + /** @type {choiceType | "EXIT"} */ + const choice = await inquirer + .prompt([ + { + type: "list", + name: "selectedOption", + message: "What type of test would you like to create?", + choices: [...choices, "EXIT"], + }, + ]) + .then(ta => ta.selectedOption); - if (typeAnswer.selectedOption === "EXIT") { + if (choice === "EXIT") { console.log("Exiting..."); - return process.exit(); - } - if (!choices.some(choice => choice.dir === typeAnswer.selectedOption.dir)) { - console.error(`Please provide a valid type: (${choices.map(choice => choice.label).join(", ")})!`); - return await promptTestType(); + return process.exit(0); } - return typeAnswer; + return choice; } /** * Prompts the user to provide a file name. - * @param {string} selectedType - * @returns {Promise<{userInput: string}>} the selected file name + * @param {choiceType} selectedType The chosen string (used to display console logs) + * @returns {Promise} the selected file name */ async function promptFileName(selectedType) { - /** @type {{userInput: string}} */ - const fileNameAnswer = await inquirer.prompt([ - { - type: "input", - name: "userInput", - message: `Please provide the name of the ${selectedType}:`, - }, - ]); + /** @type {string} */ + const fileNameAnswer = await inquirer + .prompt([ + { + type: "input", + name: "userInput", + message: `Please provide the name of the ${selectedType}.`, + }, + ]) + .then(fa => fa.userInput); - if (!fileNameAnswer.userInput || fileNameAnswer.userInput.trim().length === 0) { + if (fileNameAnswer.trim().length === 0) { console.error("Please provide a valid file name!"); return await promptFileName(selectedType); } @@ -88,51 +94,66 @@ async function promptFileName(selectedType) { return fileNameAnswer; } +/** + * Obtain the path to the boilerplate file based on the current option. + * @param {choiceType} choiceType The choice selected + * @returns {string} The path to the boilerplate file + */ +function getBoilerplatePath(choiceType) { + switch (choiceType) { + // case "Reward": + // return path.join(__dirname, "boilerplates/reward.ts"); + default: + return path.join(__dirname, "boilerplates/default.ts"); + } +} + /** * Runs the interactive test:create "CLI" * @returns {Promise} */ async function runInteractive() { - console.group(chalk.grey(`Create Test - v${version}\n`)); + console.group(chalk.grey(`🧪 Create Test - v${version}\n`)); try { - const typeAnswer = await promptTestType(); - const fileNameAnswer = await promptFileName(typeAnswer.selectedOption.label); + const choice = await promptTestType(); + const fileNameAnswer = await promptFileName(choice); - const type = typeAnswer.selectedOption; // Convert fileName from snake_case or camelCase to kebab-case - const fileName = fileNameAnswer.userInput + const fileName = fileNameAnswer .replace(/_+/g, "-") // Convert snake_case (underscore) to kebab-case (dashes) .replace(/([a-z])([A-Z])/g, "$1-$2") // Convert camelCase to kebab-case .replace(/\s+/g, "-") // Replace spaces with dashes .toLowerCase(); // Ensure all lowercase - // Format the description for the test case + // Format the description for the test case in Title Case const formattedName = fileName.replace(/-/g, " ").replace(/\b\w/g, char => char.toUpperCase()); + const description = `${choice} - ${formattedName}`; + // Determine the directory based on the type - const dir = getTestFolderPath(type.dir); - const description = `${type.label} - ${formattedName}`; + const localDir = choicesToDirs[choice]; + const absoluteDir = getTestFolderPath(localDir); // Define the content template - const content = fs.readFileSync(boilerplateFilePath, "utf8").replace("{{description}}", description); + const content = fs.readFileSync(getBoilerplatePath(choice), "utf8").replace("{{description}}", description); // Ensure the directory exists - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); + if (!fs.existsSync(absoluteDir)) { + fs.mkdirSync(absoluteDir, { recursive: true }); } // Create the file with the given name - const filePath = path.join(dir, `${fileName}.test.ts`); + const filePath = path.join(absoluteDir, `${fileName}.test.ts`); if (fs.existsSync(filePath)) { - console.error(chalk.red.bold(`\n✗ File "${fileName}.test.ts" already exists!\n`)); + console.error(chalk.red.bold(`✗ File "${fileName}.test.ts" already exists!\n`)); process.exit(1); } // Write the template content to the file fs.writeFileSync(filePath, content, "utf8"); - console.log(chalk.green.bold(`\n✔ File created at: test/${type.dir}/${fileName}.test.ts\n`)); + console.log(chalk.green.bold(`✔ File created at: test/${localDir}/${fileName}.test.ts\n`)); console.groupEnd(); } catch (err) { console.error(chalk.red("✗ Error: ", err.message)); diff --git a/scripts/decrypt-save.js b/scripts/decrypt-save.js index 219cdb47bed..e50f152f159 100644 --- a/scripts/decrypt-save.js +++ b/scripts/decrypt-save.js @@ -2,7 +2,9 @@ // biome-ignore lint/performance/noNamespaceImport: This is how you import fs from node import * as fs from "node:fs"; -import { AES, enc } from "crypto-js"; +import crypto_js from "crypto-js"; + +const { AES, enc } = crypto_js; const SAVE_KEY = "x0i2O7WRiANTqPmZ"; @@ -144,7 +146,7 @@ function main() { process.exit(0); } - writeToFile(destPath, decrypt); + writeToFile(args[1], decrypt); } main(); diff --git a/src/@types/arena-tags.ts b/src/@types/arena-tags.ts index ab4339b2fef..afcc8a0f924 100644 --- a/src/@types/arena-tags.ts +++ b/src/@types/arena-tags.ts @@ -1,6 +1,5 @@ import type { ArenaTagTypeMap } from "#data/arena-tag"; import type { ArenaTagType } from "#enums/arena-tag-type"; -import type { NonFunctionProperties } from "./type-helpers"; /** Subset of {@linkcode ArenaTagType}s that apply some negative effect to pokemon that switch in ({@link https://bulbapedia.bulbagarden.net/wiki/List_of_moves_that_cause_entry_hazards#List_of_traps | entry hazards} and Imprison. */ export type ArenaTrapTagType = @@ -10,9 +9,6 @@ export type ArenaTrapTagType = | ArenaTagType.STEALTH_ROCK | ArenaTagType.IMPRISON; -/** Subset of {@linkcode ArenaTagType}s that are considered delayed attacks */ -export type ArenaDelayedAttackTagType = ArenaTagType.FUTURE_SIGHT | ArenaTagType.DOOM_DESIRE; - /** Subset of {@linkcode ArenaTagType}s that create {@link https://bulbapedia.bulbagarden.net/wiki/Category:Screen-creating_moves | screens}. */ export type ArenaScreenTagType = ArenaTagType.REFLECT | ArenaTagType.LIGHT_SCREEN | ArenaTagType.AURORA_VEIL; @@ -30,13 +26,13 @@ export type NonSerializableArenaTagType = ArenaTagType.NONE | TurnProtectArenaTa export type SerializableArenaTagType = Exclude; /** - * Type-safe representation of the serializable data of an ArenaTag + * Type-safe representation of an arbitrary, serialized Arena Tag */ -export type ArenaTagTypeData = NonFunctionProperties< +export type ArenaTagTypeData = Parameters< ArenaTagTypeMap[keyof { [K in keyof ArenaTagTypeMap as K extends SerializableArenaTagType ? K : never]: ArenaTagTypeMap[K]; - }] ->; + }]["loadTag"] +>[0]; /** Dummy, typescript-only declaration to ensure that * {@linkcode ArenaTagTypeMap} has a map for all ArenaTagTypes. diff --git a/src/@types/battler-tags.ts b/src/@types/battler-tags.ts new file mode 100644 index 00000000000..211eb25113d --- /dev/null +++ b/src/@types/battler-tags.ts @@ -0,0 +1,137 @@ +// biome-ignore-start lint/correctness/noUnusedImports: Used in a TSDoc comment +import type { AbilityBattlerTag, BattlerTagTypeMap, SerializableBattlerTag, TypeBoostTag } from "#data/battler-tags"; +import type { AbilityId } from "#enums/ability-id"; +// biome-ignore-end lint/correctness/noUnusedImports: end +import type { BattlerTagType } from "#enums/battler-tag-type"; + +/** + * Subset of {@linkcode BattlerTagType}s that restrict the use of moves. + */ +export type MoveRestrictionBattlerTagType = + | BattlerTagType.THROAT_CHOPPED + | BattlerTagType.TORMENT + | BattlerTagType.TAUNT + | BattlerTagType.IMPRISON + | BattlerTagType.HEAL_BLOCK + | BattlerTagType.ENCORE + | BattlerTagType.DISABLED + | BattlerTagType.GORILLA_TACTICS; + +/** + * Subset of {@linkcode BattlerTagType}s that block damage from moves. + */ +export type FormBlockDamageBattlerTagType = BattlerTagType.ICE_FACE | BattlerTagType.DISGUISE; + +/** + * Subset of {@linkcode BattlerTagType}s that are related to trapping effects. + */ +export type TrappingBattlerTagType = + | BattlerTagType.BIND + | BattlerTagType.WRAP + | BattlerTagType.FIRE_SPIN + | BattlerTagType.WHIRLPOOL + | BattlerTagType.CLAMP + | BattlerTagType.SAND_TOMB + | BattlerTagType.MAGMA_STORM + | BattlerTagType.SNAP_TRAP + | BattlerTagType.THUNDER_CAGE + | BattlerTagType.INFESTATION + | BattlerTagType.INGRAIN + | BattlerTagType.OCTOLOCK + | BattlerTagType.NO_RETREAT; + +/** + * Subset of {@linkcode BattlerTagType}s that are related to protection effects. + */ +export type ProtectionBattlerTagType = BattlerTagType.PROTECTED | BattlerTagType.SPIKY_SHIELD | DamageProtectedTagType; +/** + * Subset of {@linkcode BattlerTagType}s related to protection effects that block damage but not status moves. + */ +export type DamageProtectedTagType = ContactSetStatusProtectedTagType | ContactStatStageChangeProtectedTagType; + +/** + * Subset of {@linkcode BattlerTagType}s related to protection effects that set a status effect on the attacker. + */ +export type ContactSetStatusProtectedTagType = BattlerTagType.BANEFUL_BUNKER | BattlerTagType.BURNING_BULWARK; + +/** + * Subset of {@linkcode BattlerTagType}s related to protection effects that change stat stages of the attacker. + */ +export type ContactStatStageChangeProtectedTagType = + | BattlerTagType.KINGS_SHIELD + | BattlerTagType.SILK_TRAP + | BattlerTagType.OBSTRUCT; + +/** Subset of {@linkcode BattlerTagType}s that provide the Endure effect */ +export type EndureTagType = BattlerTagType.ENDURE_TOKEN | BattlerTagType.ENDURING; + +/** + * Subset of {@linkcode BattlerTagType}s that are related to semi-invulnerable states. + */ +export type SemiInvulnerableTagType = + | BattlerTagType.FLYING + | BattlerTagType.UNDERGROUND + | BattlerTagType.UNDERWATER + | BattlerTagType.HIDDEN; + +/** + * Subset of {@linkcode BattlerTagType}s corresponding to {@linkcode AbilityBattlerTag}s + * + * @remarks + * ⚠️ {@linkcode AbilityId.FLASH_FIRE | Flash Fire}'s {@linkcode BattlerTagType.FIRE_BOOST} is not included as it + * subclasses {@linkcode TypeBoostTag} and not `AbilityBattlerTag`. + */ +export type AbilityBattlerTagType = + | BattlerTagType.PROTOSYNTHESIS + | BattlerTagType.QUARK_DRIVE + | BattlerTagType.UNBURDEN + | BattlerTagType.SLOW_START + | BattlerTagType.TRUANT; + +/** Subset of {@linkcode BattlerTagType}s that provide type boosts */ +export type TypeBoostTagType = BattlerTagType.FIRE_BOOST | BattlerTagType.CHARGED; + +/** Subset of {@linkcode BattlerTagType}s that boost the user's critical stage */ +export type CritStageBoostTagType = BattlerTagType.CRIT_BOOST | BattlerTagType.DRAGON_CHEER; + +/** Subset of {@linkcode BattlerTagType}s that remove one of the users' types */ +export type RemovedTypeTagType = BattlerTagType.DOUBLE_SHOCKED | BattlerTagType.BURNED_UP; + +/** + * Subset of {@linkcode BattlerTagType}s related to abilities that boost the highest stat. + */ +export type HighestStatBoostTagType = + | BattlerTagType.QUARK_DRIVE // formatting + | BattlerTagType.PROTOSYNTHESIS; +/** + * Subset of {@linkcode BattlerTagType}s that are able to persist between turns and should therefore be serialized + */ +export type SerializableBattlerTagType = keyof { + [K in keyof BattlerTagTypeMap as BattlerTagTypeMap[K] extends SerializableBattlerTag + ? K + : never]: BattlerTagTypeMap[K]; +}; + +/** + * Subset of {@linkcode BattlerTagType}s that are not able to persist across waves and should therefore not be serialized + */ +export type NonSerializableBattlerTagType = Exclude; + +/** + * Type-safe representation of an arbitrary, serialized Battler Tag + */ +export type BattlerTagTypeData = Parameters< + BattlerTagTypeMap[keyof { + [K in keyof BattlerTagTypeMap as K extends SerializableBattlerTagType ? K : never]: BattlerTagTypeMap[K]; + }]["loadTag"] +>[0]; + +/** + * Dummy, typescript-only declaration to ensure that + * {@linkcode BattlerTagTypeMap} has an entry for all `BattlerTagType`s. + * + * If a battler tag is missing from the map, Typescript will throw an error on this statement. + * + * ⚠️ Does not actually exist at runtime, so it must not be used! + */ +declare const EnsureAllBattlerTagTypesAreMapped: BattlerTagTypeMap[BattlerTagType] & never; diff --git a/src/@types/enum-types.ts b/src/@types/helpers/enum-types.ts similarity index 68% rename from src/@types/enum-types.ts rename to src/@types/helpers/enum-types.ts index 84df0a96505..2461f900c6b 100644 --- a/src/@types/enum-types.ts +++ b/src/@types/helpers/enum-types.ts @@ -1,18 +1,14 @@ +import type { ObjectValues } from "#types/type-helpers"; + /** Union type accepting any TS Enum or `const object`, with or without reverse mapping. */ export type EnumOrObject = Record; -/** - * Utility type to extract the enum values from a `const object`, - * or convert an `enum` interface produced by `typeof Enum` into the union type representing its values. - */ -export type EnumValues = E[keyof E]; - /** * Generic type constraint representing a TS numeric enum with reverse mappings. * @example * TSNumericEnum */ -export type TSNumericEnum = number extends EnumValues ? T : never; +export type TSNumericEnum = number extends ObjectValues ? T : never; /** Generic type constraint representing a non reverse-mapped TS enum or `const object`. */ export type NormalEnum = Exclude>; diff --git a/src/@types/type-helpers.ts b/src/@types/helpers/type-helpers.ts similarity index 61% rename from src/@types/type-helpers.ts rename to src/@types/helpers/type-helpers.ts index 3a5c88e3f15..7ad20b88956 100644 --- a/src/@types/type-helpers.ts +++ b/src/@types/helpers/type-helpers.ts @@ -6,8 +6,6 @@ import type { AbAttr } from "#abilities/ability"; // biome-ignore-end lint/correctness/noUnusedImports: Used in a tsdoc comment -import type { EnumValues } from "#types/enum-types"; - /** * Exactly matches the type of the argument, preventing adding additional properties. * @@ -37,16 +35,25 @@ export type Mutable = { }; /** - * Type helper to obtain the keys associated with a given value inside a `const object`. + * Type helper to obtain the keys associated with a given value inside an object. * @typeParam O - The type of the object * @typeParam V - The type of one of O's values */ -export type InferKeys, V extends EnumValues> = { +export type InferKeys> = { [K in keyof O]: O[K] extends V ? K : never; }[keyof O]; /** - * Type helper that matches any `Function` type. Equivalent to `Function`, but will not raise a warning from Biome. + * Utility type to obtain the values of a given object. \ + * Functions similar to `keyof E`, except producing the values instead of the keys. + * @remarks + * This can be used to convert an `enum` interface produced by `typeof Enum` into the union type representing its members. + */ +export type ObjectValues = E[keyof E]; + +/** + * Type helper that matches any `Function` type. + * Equivalent to `Function`, but will not raise a warning from Biome. */ export type AnyFn = (...args: any[]) => any; @@ -65,6 +72,7 @@ export type NonFunctionProperties = { /** * Type helper to extract out non-function properties from a type, recursively applying to nested properties. + * This can be used to mimic the effects of JSON serialization and de-serialization on a given type. */ export type NonFunctionPropertiesRecursive = { [K in keyof Class as Class[K] extends AnyFn ? never : K]: Class[K] extends Array @@ -75,3 +83,23 @@ export type NonFunctionPropertiesRecursive = { }; export type AbstractConstructor = abstract new (...args: any[]) => T; + +/** + * Type helper that iterates through the fields of the type and coerces any `null` properties to `undefined` (including in union types). + * + * @remarks + * This is primarily useful when an object with nullable properties wants to be serialized and have its `null` + * properties coerced to `undefined`. + */ +export type CoerceNullPropertiesToUndefined = { + [K in keyof T]: null extends T[K] ? Exclude | undefined : T[K]; +}; + +/** + * Type helper to mark all properties in `T` as optional, while still mandating that at least 1 + * of its properties be present. + * + * Distinct from {@linkcode Partial} as this requires at least 1 property to _not_ be undefined. + * @typeParam T - The type to render partial + */ +export type AtLeastOne = Partial & ObjectValues<{ [K in keyof T]: Pick, K> }>; diff --git a/src/@types/illusion-data.ts b/src/@types/illusion-data.ts index 854c98c8cc9..5bf86d23ac2 100644 --- a/src/@types/illusion-data.ts +++ b/src/@types/illusion-data.ts @@ -8,20 +8,14 @@ import type { Variant } from "#sprites/variant"; * Data pertaining to a Pokemon's Illusion. */ export interface IllusionData { - basePokemon: { - /** The actual name of the Pokemon */ - name: string; - /** The actual nickname of the Pokemon */ - nickname: string; - /** Whether the base pokemon is shiny or not */ - shiny: boolean; - /** The shiny variant of the base pokemon */ - variant: Variant; - /** Whether the fusion species of the base pokemon is shiny or not */ - fusionShiny: boolean; - /** The variant of the fusion species of the base pokemon */ - fusionVariant: Variant; - }; + /** The name of pokemon featured in the illusion */ + name: string; + /** The nickname of the pokemon featured in the illusion */ + nickname?: string; + /** Whether the pokemon featured in the illusion is shiny or not */ + shiny: boolean; + /** The variant of the pokemon featured in the illusion */ + variant: Variant; /** The species of the illusion */ species: SpeciesId; /** The formIndex of the illusion */ @@ -34,6 +28,10 @@ export interface IllusionData { fusionSpecies?: PokemonSpecies; /** The fusionFormIndex of the illusion */ fusionFormIndex?: number; + /** Whether the fusion species of the pokemon featured in the illusion is shiny or not */ + fusionShiny?: boolean; + /** The variant of the fusion species of the pokemon featured in the illusion */ + fusionVariant?: Variant; /** The fusionGender of the illusion if it's a fusion */ fusionGender?: Gender; /** The level of the illusion (not used currently) */ diff --git a/src/@types/phase-types.ts b/src/@types/phase-types.ts index 1d68c7921dd..91673053747 100644 --- a/src/@types/phase-types.ts +++ b/src/@types/phase-types.ts @@ -1,4 +1,5 @@ import type { PhaseConstructorMap } from "#app/phase-manager"; +import type { ObjectValues } from "#types/type-helpers"; // Intentionally export the types of everything in phase-manager, as this file is meant to be // the centralized place for type definitions for the phase system. @@ -17,7 +18,7 @@ export type PhaseMap = { /** * Union type of all phase constructors. */ -export type PhaseClass = PhaseConstructorMap[keyof PhaseConstructorMap]; +export type PhaseClass = ObjectValues; /** * Union type of all phase names as strings. diff --git a/src/@types/ui.ts b/src/@types/ui.ts new file mode 100644 index 00000000000..10dab01c616 --- /dev/null +++ b/src/@types/ui.ts @@ -0,0 +1,10 @@ +import type Phaser from "phaser"; +import type InputText from "phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputText"; + +export interface TextStyleOptions { + scale: number; + styleOptions: Phaser.Types.GameObjects.Text.TextStyle | InputText.IConfig; + shadowColor: string; + shadowXpos: number; + shadowYpos: number; +} diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 74906adfd72..ee740b70965 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -70,6 +70,7 @@ import { ShopCursorTarget } from "#enums/shop-cursor-target"; import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; import { TrainerItemId } from "#enums/trainer-item-id"; +import { TextStyle } from "#enums/text-style"; import type { TrainerSlot } from "#enums/trainer-slot"; import { TrainerType } from "#enums/trainer-type"; import { TrainerVariant } from "#enums/trainer-variant"; @@ -123,7 +124,7 @@ import { ItemBar } from "#ui/item-bar-ui"; import { PartyExpBar } from "#ui/party-exp-bar"; import { PokeballTray } from "#ui/pokeball-tray"; import { PokemonInfoContainer } from "#ui/pokemon-info-container"; -import { addTextObject, getTextColor, TextStyle } from "#ui/text"; +import { addTextObject, getTextColor } from "#ui/text"; import { UI } from "#ui/ui"; import { addUiThemeOverrides } from "#ui/ui-theme"; import { @@ -227,6 +228,7 @@ export class BattleScene extends SceneBase { public enableTouchControls = false; public enableVibration = false; public showBgmBar = true; + public hideUsername = false; /** Determines the selected battle style. */ public battleStyle: BattleStyle = BattleStyle.SWITCH; /** @@ -369,9 +371,21 @@ export class BattleScene extends SceneBase { }; } - populateAnims(); + /** + * These moves serve as fallback animations for other moves without loaded animations, and + * must be loaded prior to game start. + */ + const defaultMoves = [MoveId.TACKLE, MoveId.TAIL_WHIP, MoveId.FOCUS_ENERGY, MoveId.STRUGGLE]; - await this.initVariantData(); + await Promise.all([ + populateAnims(), + this.initVariantData(), + initCommonAnims().then(() => loadCommonAnimAssets(true)), + Promise.all(defaultMoves.map(m => initMoveAnim(m))).then(() => loadMoveAnimAssets(defaultMoves, true)), + this.initStarterColors(), + ]).catch(reason => { + throw new Error(`Unexpected error during BattleScene preLoad!\nReason: ${reason}`); + }); } create() { @@ -573,8 +587,6 @@ export class BattleScene extends SceneBase { this.party = []; - const loadPokemonAssets = []; - this.arenaPlayer = new ArenaBase(true); this.arenaPlayer.setName("arena-player"); this.arenaPlayerTransition = new ArenaBase(true); @@ -629,28 +641,14 @@ export class BattleScene extends SceneBase { this.reset(false, false, true); + // Initialize UI-related aspects and then start the login phase. const ui = new UI(); this.uiContainer.add(ui); - this.ui = ui; - ui.setup(); - const defaultMoves = [MoveId.TACKLE, MoveId.TAIL_WHIP, MoveId.FOCUS_ENERGY, MoveId.STRUGGLE]; - - Promise.all([ - Promise.all(loadPokemonAssets), - initCommonAnims().then(() => loadCommonAnimAssets(true)), - Promise.all( - [MoveId.TACKLE, MoveId.TAIL_WHIP, MoveId.FOCUS_ENERGY, MoveId.STRUGGLE].map(m => initMoveAnim(m)), - ).then(() => loadMoveAnimAssets(defaultMoves, true)), - this.initStarterColors(), - ]).then(() => { - this.phaseManager.pushNew("LoginPhase"); - this.phaseManager.pushNew("TitlePhase"); - - this.phaseManager.shiftPhase(); - }); + this.phaseManager.toTitleScreen(true); + this.phaseManager.shiftPhase(); } initSession(): void { @@ -690,16 +688,16 @@ export class BattleScene extends SceneBase { if (expSpriteKeys.size > 0) { return; } - this.cachedFetch("./exp-sprites.json") - .then(res => res.json()) - .then(keys => { - if (Array.isArray(keys)) { - for (const key of keys) { - expSpriteKeys.add(key); - } - } - Promise.resolve(); - }); + const res = await this.cachedFetch("./exp-sprites.json"); + const keys = await res.json(); + if (!Array.isArray(keys)) { + throw new Error("EXP Sprites were not array when fetched!"); + } + + // TODO: Optimize this + for (const k of keys) { + expSpriteKeys.add(k); + } } /** @@ -1262,13 +1260,12 @@ export class BattleScene extends SceneBase { duration: 250, ease: "Sine.easeInOut", onComplete: () => { - this.phaseManager.clearPhaseQueue(); - this.ui.freeUIData(); this.uiContainer.remove(this.ui, true); this.uiContainer.destroy(); this.children.removeAll(true); this.game.domContainer.innerHTML = ""; + // TODO: `launchBattle` calls `reset(false, false, true)` this.launchBattle(); }, }); @@ -1664,6 +1661,11 @@ export class BattleScene extends SceneBase { case SpeciesId.MAUSHOLD: case SpeciesId.DUDUNSPARCE: return !randSeedInt(4) ? 1 : 0; + case SpeciesId.SINISTEA: + case SpeciesId.POLTEAGEIST: + case SpeciesId.POLTCHAGEIST: + case SpeciesId.SINISTCHA: + return !randSeedInt(16) ? 1 : 0; case SpeciesId.PIKACHU: if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) { return 0; // Ban Cosplay and Partner Pika from Trainers before wave 30 @@ -2778,6 +2780,75 @@ export class BattleScene extends SceneBase { }); } + + // TODO @Wlowscha: Fix this + /** + * Attempt to discard one or more copies of a held item. + * @param itemModifier - The {@linkcode PokemonHeldItemModifier} being discarded + * @param discardQuantity - The number of copies to remove (up to the amount currently held); default `1` + * @returns Whether the item was successfully discarded. + * Removing fewer items than requested is still considered a success. + */ + tryDiscardHeldItemModifier(itemModifier: PokemonHeldItemModifier, discardQuantity = 1): boolean { + const countTaken = Math.min(discardQuantity, itemModifier.stackCount); + itemModifier.stackCount -= countTaken; + + if (itemModifier.stackCount > 0) { + return true; + } + + return this.removeModifier(itemModifier); + } + + canTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, transferQuantity = 1): boolean { + const mod = itemModifier.clone() as PokemonHeldItemModifier; + const source = mod.pokemonId ? mod.getPokemon() : null; + const cancelled = new BooleanHolder(false); + + if (source && source.isPlayer() !== target.isPlayer()) { + applyAbAttrs("BlockItemTheftAbAttr", { pokemon: source, cancelled }); + } + + if (cancelled.value) { + return false; + } + + const matchingModifier = this.findModifier( + m => m instanceof PokemonHeldItemModifier && m.matchType(mod) && m.pokemonId === target.id, + target.isPlayer(), + ) as PokemonHeldItemModifier; + + if (matchingModifier) { + const maxStackCount = matchingModifier.getMaxStackCount(); + if (matchingModifier.stackCount >= maxStackCount) { + return false; + } + const countTaken = Math.min(transferQuantity, mod.stackCount, maxStackCount - matchingModifier.stackCount); + mod.stackCount -= countTaken; + } else { + const countTaken = Math.min(transferQuantity, mod.stackCount); + mod.stackCount -= countTaken; + } + + const removeOld = mod.stackCount === 0; + + return !removeOld || !source || this.hasModifier(itemModifier, !source.isPlayer()); + } + + removePartyMemberModifiers(partyMemberIndex: number): Promise { + return new Promise(resolve => { + const pokemonId = this.getPlayerParty()[partyMemberIndex].id; + const modifiersToRemove = this.modifiers.filter( + m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === pokemonId, + ); + for (const m of modifiersToRemove) { + this.modifiers.splice(this.modifiers.indexOf(m), 1); + } + this.updateModifiers(); + resolve(); + }); + } + assignTrainerItemsFromSaveData(saveData: TrainerItemSaveData, isPlayer: boolean) { const manager = isPlayer ? this.trainerItems : this.enemyTrainerItems; for (const item of saveData) { @@ -3236,6 +3307,7 @@ export class BattleScene extends SceneBase { this.gameMode.hasMysteryEncounters && battleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && + waveIndex % 10 !== 1 && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave ); diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index cea5d885a1d..42a63a65727 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -15,6 +15,7 @@ import { SpeciesFormChangeAbilityTrigger, SpeciesFormChangeWeatherTrigger } from import { Gender } from "#data/gender"; import { getPokeballName } from "#data/pokeball"; import { pokemonFormChanges } from "#data/pokemon-forms"; +import type { PokemonSpecies } from "#data/pokemon-species"; import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#data/status-effect"; import { TerrainType } from "#data/terrain"; import type { Weather } from "#data/weather"; @@ -5975,8 +5976,13 @@ export class IllusionPreSummonAbAttr extends PreSummonAbAttr { const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter( p => p.isAllowedInBattle(), ); - const lastPokemon: Pokemon = party.filter(p => p !== pokemon).at(-1) || pokemon; - pokemon.setIllusion(lastPokemon); + let illusionPokemon: Pokemon | PokemonSpecies; + if (pokemon.hasTrainer()) { + illusionPokemon = party.filter(p => p !== pokemon).at(-1) || pokemon; + } else { + illusionPokemon = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, pokemon.level); + } + pokemon.setIllusion(illusionPokemon); } /** @returns Whether the illusion can be applied. */ diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 2c4c8a04282..15c2cde1d58 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -1,3 +1,7 @@ +/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ +import type { BattlerTag } from "#app/data/battler-tags"; +/** biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports */ + import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -6,58 +10,72 @@ import { allMoves } from "#data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagType } from "#enums/arena-tag-type"; -import type { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; import { HitResult } from "#enums/hit-result"; import { CommonAnim } from "#enums/move-anims-common"; import { MoveCategory } from "#enums/move-category"; import { MoveId } from "#enums/move-id"; import { MoveTarget } from "#enums/move-target"; -import { MoveUseMode } from "#enums/move-use-mode"; import { PokemonType } from "#enums/pokemon-type"; import { Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import type { Arena } from "#field/arena"; import type { Pokemon } from "#field/pokemon"; import type { - ArenaDelayedAttackTagType, ArenaScreenTagType, ArenaTagTypeData, ArenaTrapTagType, SerializableArenaTagType, } from "#types/arena-tags"; -import type { Mutable, NonFunctionProperties } from "#types/type-helpers"; -import { BooleanHolder, isNullOrUndefined, NumberHolder, toDmgValue } from "#utils/common"; +import type { Mutable } from "#types/type-helpers"; +import { BooleanHolder, NumberHolder, toDmgValue } from "#utils/common"; import i18next from "i18next"; -/* -ArenaTags are are meant for effects that are tied to the arena (as opposed to a specific pokemon). -Examples include (but are not limited to) -- Cross-turn effects that persist even if the user/target switches out, such as Wish, Future Sight, and Happy Hour -- Effects that are applied to a specific side of the field, such as Crafty Shield, Reflect, and Spikes -- Field-Effects, like Gravity and Trick Room - -Any arena tag that persists across turns *must* extend from `SerializableArenaTag` in the class definition signature. - -Serializable ArenaTags have strict rules for their fields. -These rules ensure that only the data necessary to reconstruct the tag is serialized, and that the -session loader is able to deserialize saved tags correctly. - -If the data is static (i.e. it is always the same for all instances of the class, such as the -type that is weakened by Mud Sport/Water Sport), then it must not be defined as a field, and must -instead be defined as a getter. -A static property is also acceptable, though static properties are less ergonomic with inheritance. - -If the data is mutable (i.e. it can change over the course of the tag's lifetime), then it *must* -be defined as a field, and it must be set in the `loadTag` method. -Such fields cannot be marked as `private/protected`, as if they were, typescript would omit them from -types that are based off of the class, namely, `ArenaTagTypeData`. It is preferrable to trade the -type-safety of private/protected fields for the type safety when deserializing arena tags from save data. - -For data that is mutable only within a turn (e.g. SuppressAbilitiesTag's beingRemoved field), -where it does not make sense to be serialized, the field should use ES2020's private field syntax (a `#` prepended to the field name). -If the field should be accessible outside of the class, then a public getter should be used. -*/ +/** + * @module + * ArenaTags are are meant for effects that are tied to the arena (as opposed to a specific pokemon). + * Examples include (but are not limited to) + * - Cross-turn effects that persist even if the user/target switches out, such as Happy Hour + * - Effects that are applied to a specific side of the field, such as Crafty Shield, Reflect, and Spikes + * - Field-Effects, like Gravity and Trick Room + * + * Any arena tag that persists across turns *must* extend from `SerializableArenaTag` in the class definition signature. + * + * Serializable ArenaTags have strict rules for their fields. + * These rules ensure that only the data necessary to reconstruct the tag is serialized, and that the + * session loader is able to deserialize saved tags correctly. + * + * If the data is static (i.e. it is always the same for all instances of the class, such as the + * type that is weakened by Mud Sport/Water Sport), then it must not be defined as a field, and must + * instead be defined as a getter. + * A static property is also acceptable, though static properties are less ergonomic with inheritance. + * + * If the data is mutable (i.e. it can change over the course of the tag's lifetime), then it *must* + * be defined as a field, and it must be set in the `loadTag` method. + * Such fields cannot be marked as `private`/`protected`; if they were, Typescript would omit them from + * types that are based off of the class, namely, `ArenaTagTypeData`. It is preferrable to trade the + * type-safety of private/protected fields for the type safety when deserializing arena tags from save data. + * + * For data that is mutable only within a turn (e.g. SuppressAbilitiesTag's beingRemoved field), + * where it does not make sense to be serialized, the field should use ES2020's + * [private field syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_elements#private_fields). + * If the field should be accessible outside of the class, then a public getter should be used. + * + * If any new serializable fields *are* added, then the class *must* override the + * `loadTag` method to set the new fields. Its signature *must* match the example below, + * ``` + * class ExampleTag extends SerializableArenaTag { + * // Example, if we add 2 new fields that should be serialized: + * public a: string; + * public b: number; + * // Then we must also define a loadTag method with one of the following signatures + * public override loadTag(source: BaseArenaTag & Pick(source: BaseArenaTag & Pick): void; + * } + * ``` + * Notes + * - If the class has any subclasses, then the second form of `loadTag` *must* be used. + */ /** Interface containing the serializable fields of ArenaTagData. */ interface BaseArenaTag { @@ -141,9 +159,9 @@ export abstract class ArenaTag implements BaseArenaTag { /** * When given a arena tag or json representing one, load the data for it. * This is meant to be inherited from by any arena tag with custom attributes - * @param source - The {@linkcode BaseArenaTag} being loaded + * @param source - The arena tag being loaded */ - loadTag(source: BaseArenaTag): void { + loadTag(source: BaseArenaTag & Pick): void { this.turnCount = source.turnCount; this.sourceMove = source.sourceMove; this.sourceId = source.sourceId; @@ -604,56 +622,6 @@ export class NoCritTag extends SerializableArenaTag { } } -/** - * Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Wish_(move) | Wish}. - * Heals the Pokémon in the user's position the turn after Wish is used. - */ -class WishTag extends SerializableArenaTag { - // The following fields are meant to be inwardly mutable, but outwardly immutable. - readonly battlerIndex: BattlerIndex; - readonly healHp: number; - readonly sourceName: string; - // End inwardly mutable fields - - public readonly tagType = ArenaTagType.WISH; - - constructor(turnCount: number, sourceId: number | undefined, side: ArenaTagSide) { - super(turnCount, MoveId.WISH, sourceId, side); - } - - onAdd(_arena: Arena): void { - const source = this.getSourcePokemon(); - if (!source) { - console.warn(`Failed to get source Pokemon for WishTag on add message; id: ${this.sourceId}`); - return; - } - - (this as Mutable).sourceName = getPokemonNameWithAffix(source); - (this as Mutable).healHp = toDmgValue(source.getMaxHp() / 2); - (this as Mutable).battlerIndex = source.getBattlerIndex(); - } - - onRemove(_arena: Arena): void { - const target = globalScene.getField()[this.battlerIndex]; - if (target?.isActive(true)) { - globalScene.phaseManager.queueMessage( - // TODO: Rename key as it triggers on activation - i18next.t("arenaTag:wishTagOnAdd", { - pokemonNameWithAffix: this.sourceName, - }), - ); - globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(), this.healHp, null, true, false); - } - } - - override loadTag(source: NonFunctionProperties): void { - super.loadTag(source); - (this as Mutable).battlerIndex = source.battlerIndex; - (this as Mutable).healHp = source.healHp; - (this as Mutable).sourceName = source.sourceName; - } -} - /** * Abstract class to implement weakened moves of a specific type. */ @@ -813,7 +781,7 @@ export abstract class ArenaTrapTag extends SerializableArenaTag { : Phaser.Math.Linear(0, 1 / Math.pow(2, this.layers), Math.min(pokemon.getHpRatio(), 0.5) * 2); } - loadTag(source: NonFunctionProperties): void { + public loadTag(source: BaseArenaTag & Pick): void { super.loadTag(source); this.layers = source.layers; this.maxLayers = source.maxLayers; @@ -1126,48 +1094,6 @@ class StickyWebTag extends ArenaTrapTag { } } -/** - * Arena Tag class for delayed attacks, such as {@linkcode MoveId.FUTURE_SIGHT} or {@linkcode MoveId.DOOM_DESIRE}. - * Delays the attack's effect by a set amount of turns, usually 3 (including the turn the move is used), - * and deals damage after the turn count is reached. - */ -export class DelayedAttackTag extends SerializableArenaTag { - public targetIndex: BattlerIndex; - public readonly tagType: ArenaDelayedAttackTagType; - - constructor( - tagType: ArenaTagType.DOOM_DESIRE | ArenaTagType.FUTURE_SIGHT, - sourceMove: MoveId | undefined, - sourceId: number | undefined, - targetIndex: BattlerIndex, - side: ArenaTagSide = ArenaTagSide.BOTH, - ) { - super(3, sourceMove, sourceId, side); - this.tagType = tagType; - this.targetIndex = targetIndex; - this.side = side; - } - - lapse(arena: Arena): boolean { - const ret = super.lapse(arena); - - if (!ret) { - // TODO: This should not add to move history (for Spite) - globalScene.phaseManager.unshiftNew( - "MoveEffectPhase", - this.sourceId!, - [this.targetIndex], - allMoves[this.sourceMove!], - MoveUseMode.FOLLOW_UP, - ); // TODO: are those bangs correct? - } - - return ret; - } - - onRemove(_arena: Arena): void {} -} - /** * Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Trick_Room_(move) Trick Room}. * Reverses the Speed stats for all Pokémon on the field as long as this arena tag is up, @@ -1581,7 +1507,7 @@ export class SuppressAbilitiesTag extends SerializableArenaTag { this.#beingRemoved = false; } - public override loadTag(source: NonFunctionProperties): void { + public override loadTag(source: BaseArenaTag & Pick): void { super.loadTag(source); (this as Mutable).sourceCount = source.sourceCount; } @@ -1663,7 +1589,6 @@ export function getArenaTag( turnCount: number, sourceMove: MoveId | undefined, sourceId: number | undefined, - targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH, ): ArenaTag | null { switch (tagType) { @@ -1689,14 +1614,6 @@ export function getArenaTag( return new SpikesTag(sourceId, side); case ArenaTagType.TOXIC_SPIKES: return new ToxicSpikesTag(sourceId, side); - case ArenaTagType.FUTURE_SIGHT: - case ArenaTagType.DOOM_DESIRE: - if (isNullOrUndefined(targetIndex)) { - return null; // If missing target index, no tag is created - } - return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex, side); - case ArenaTagType.WISH: - return new WishTag(turnCount, sourceId, side); case ArenaTagType.STEALTH_ROCK: return new StealthRockTag(sourceId, side); case ArenaTagType.STICKY_WEB: @@ -1739,16 +1656,12 @@ export function getArenaTag( * @param source - An arena tag * @returns The valid arena tag */ -export function loadArenaTag(source: (ArenaTag | ArenaTagTypeData) & { targetIndex?: BattlerIndex }): ArenaTag { +export function loadArenaTag(source: ArenaTag | ArenaTagTypeData | { tagType: ArenaTagType.NONE }): ArenaTag { + if (source.tagType === ArenaTagType.NONE) { + return new NoneTag(); + } const tag = - getArenaTag( - source.tagType, - source.turnCount, - source.sourceMove, - source.sourceId, - source.targetIndex, - source.side, - ) ?? new NoneTag(); + getArenaTag(source.tagType, source.turnCount, source.sourceMove, source.sourceId, source.side) ?? new NoneTag(); tag.loadTag(source); return tag; } @@ -1765,9 +1678,6 @@ export type ArenaTagTypeMap = { [ArenaTagType.CRAFTY_SHIELD]: CraftyShieldTag; [ArenaTagType.NO_CRIT]: NoCritTag; [ArenaTagType.TOXIC_SPIKES]: ToxicSpikesTag; - [ArenaTagType.FUTURE_SIGHT]: DelayedAttackTag; - [ArenaTagType.DOOM_DESIRE]: DelayedAttackTag; - [ArenaTagType.WISH]: WishTag; [ArenaTagType.STEALTH_ROCK]: StealthRockTag; [ArenaTagType.STICKY_WEB]: StickyWebTag; [ArenaTagType.TRICK_ROOM]: TrickRoomTag; diff --git a/src/data/balance/biomes.ts b/src/data/balance/biomes.ts index 32195b90e43..1298e80c362 100644 --- a/src/data/balance/biomes.ts +++ b/src/data/balance/biomes.ts @@ -86,7 +86,7 @@ export enum BiomePoolTier { export const uncatchableSpecies: SpeciesId[] = []; -export interface SpeciesTree { +interface SpeciesTree { [key: number]: SpeciesId[] } @@ -94,11 +94,11 @@ export interface PokemonPools { [key: number]: (SpeciesId | SpeciesTree)[] } -export interface BiomeTierPokemonPools { +interface BiomeTierPokemonPools { [key: number]: PokemonPools } -export interface BiomePokemonPools { +interface BiomePokemonPools { [key: number]: BiomeTierPokemonPools } @@ -1291,11 +1291,11 @@ export const biomePokemonPools: BiomePokemonPools = { [TimeOfDay.ALL]: [ { 1: [ SpeciesId.BELDUM ], 20: [ SpeciesId.METANG ], 45: [ SpeciesId.METAGROSS ] }, SpeciesId.SIGILYPH, { 1: [ SpeciesId.SOLOSIS ], 32: [ SpeciesId.DUOSION ], 41: [ SpeciesId.REUNICLUS ] } ] }, [BiomePoolTier.SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ { 1: [ SpeciesId.PORYGON ], 30: [ SpeciesId.PORYGON2 ] } ] }, - [BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ { 1: [ SpeciesId.COSMOG ], 23: [ SpeciesId.COSMOEM ] }, SpeciesId.CELESTEELA ] }, + [BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ { 1: [ SpeciesId.COSMOG ], 60: [ SpeciesId.COSMOEM ] }, SpeciesId.CELESTEELA ] }, [BiomePoolTier.BOSS]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [ SpeciesId.SOLROCK ], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [ SpeciesId.LUNATONE ], [TimeOfDay.ALL]: [ SpeciesId.CLEFABLE, SpeciesId.BRONZONG, SpeciesId.MUSHARNA, SpeciesId.REUNICLUS, SpeciesId.MINIOR ] }, [BiomePoolTier.BOSS_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.METAGROSS, SpeciesId.PORYGON_Z ] }, [BiomePoolTier.BOSS_SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.CELESTEELA ] }, - [BiomePoolTier.BOSS_ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [ SpeciesId.SOLGALEO ], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [ SpeciesId.LUNALA ], [TimeOfDay.ALL]: [ SpeciesId.RAYQUAZA, SpeciesId.NECROZMA ] } + [BiomePoolTier.BOSS_ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [ { 1: [ SpeciesId.COSMOG ], 60: [ SpeciesId.COSMOEM ], 80: [ SpeciesId.SOLGALEO ] } ], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [ { 1: [ SpeciesId.COSMOG ], 60: [ SpeciesId.COSMOEM ], 80: [ SpeciesId.LUNALA ] } ], [TimeOfDay.ALL]: [ SpeciesId.RAYQUAZA, SpeciesId.NECROZMA ] } }, [BiomeId.CONSTRUCTION_SITE]: { [BiomePoolTier.COMMON]: { @@ -2022,7 +2022,6 @@ export const biomeTrainerPools: BiomeTrainerPools = { } }; -// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: init methods are expected to have many lines. export function initBiomes() { const pokemonBiomes = [ [ SpeciesId.BULBASAUR, PokemonType.GRASS, PokemonType.POISON, [ diff --git a/src/data/balance/egg-moves.ts b/src/data/balance/egg-moves.ts index f5026abe2ef..3475fe4fdea 100644 --- a/src/data/balance/egg-moves.ts +++ b/src/data/balance/egg-moves.ts @@ -1,8 +1,8 @@ import { allMoves } from "#data/data-lists"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import { toReadableString } from "#utils/common"; import { getEnumKeys, getEnumValues } from "#utils/enums"; +import { toTitleCase } from "#utils/strings"; export const speciesEggMoves = { [SpeciesId.BULBASAUR]: [ MoveId.SAPPY_SEED, MoveId.MALIGNANT_CHAIN, MoveId.EARTH_POWER, MoveId.MATCHA_GOTCHA ], @@ -617,7 +617,7 @@ function parseEggMoves(content: string): void { } if (eggMoves.every(m => m === MoveId.NONE)) { - console.warn(`Species ${toReadableString(SpeciesId[species])} could not be parsed, excluding from output...`) + console.warn(`Species ${toTitleCase(SpeciesId[species])} could not be parsed, excluding from output...`) } else { output += `[SpeciesId.${SpeciesId[species]}]: [ ${eggMoves.map(m => `MoveId.${MoveId[m]}`).join(", ")} ],\n`; } diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index 33c836afc6f..aae3fd99b05 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -1188,11 +1188,11 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.KOMMO_O, 45, null, null) ], [SpeciesId.COSMOG]: [ - new SpeciesEvolution(SpeciesId.COSMOEM, 23, null, null) + new SpeciesEvolution(SpeciesId.COSMOEM, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 43}, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.COSMOEM]: [ - new SpeciesEvolution(SpeciesId.SOLGALEO, 1, EvolutionItem.SUN_FLUTE, null, SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesEvolution(SpeciesId.LUNALA, 1, EvolutionItem.MOON_FLUTE, null, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(SpeciesId.SOLGALEO, 13, EvolutionItem.SUN_FLUTE, null, SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesEvolution(SpeciesId.LUNALA, 13, EvolutionItem.MOON_FLUTE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [SpeciesId.MELTAN]: [ new SpeciesEvolution(SpeciesId.MELMETAL, 48, null, null) @@ -1821,7 +1821,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.ROSELIA, 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 70}, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]}], SpeciesWildEvolutionDelay.SHORT) ], [SpeciesId.BUNEARY]: [ - new SpeciesEvolution(SpeciesId.LOPUNNY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 70}, SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(SpeciesId.LOPUNNY, 1, null, {key: EvoCondKey.FRIENDSHIP, value: 50}, SpeciesWildEvolutionDelay.MEDIUM) ], [SpeciesId.CHINGLING]: [ new SpeciesEvolution(SpeciesId.CHIMECHO, 1, null, [{key: EvoCondKey.FRIENDSHIP, value: 90}, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}], SpeciesWildEvolutionDelay.MEDIUM) diff --git a/src/data/balance/pokemon-species.ts b/src/data/balance/pokemon-species.ts new file mode 100644 index 00000000000..5e9d352f437 --- /dev/null +++ b/src/data/balance/pokemon-species.ts @@ -0,0 +1,1784 @@ +import { allSpecies } from "#data/data-lists"; +import { GrowthRate } from "#data/exp"; +import { PokemonForm, PokemonSpecies } from "#data/pokemon-species"; +import { AbilityId } from "#enums/ability-id"; +import { PokemonType } from "#enums/pokemon-type"; +import { SpeciesFormKey } from "#enums/species-form-key"; +import { SpeciesId } from "#enums/species-id"; + +// biome-ignore format: manually formatted +export function initSpecies() { + allSpecies.push( + new PokemonSpecies(SpeciesId.BULBASAUR, 1, false, false, false, "Seed Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.7, 6.9, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CHLOROPHYLL, 318, 45, 49, 49, 65, 65, 45, 45, 50, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.IVYSAUR, 1, false, false, false, "Seed Pokémon", PokemonType.GRASS, PokemonType.POISON, 1, 13, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CHLOROPHYLL, 405, 60, 62, 63, 80, 80, 60, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.VENUSAUR, 1, false, false, false, "Seed Pokémon", PokemonType.GRASS, PokemonType.POISON, 2, 100, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CHLOROPHYLL, 525, 80, 82, 83, 100, 100, 80, 45, 50, 263, GrowthRate.MEDIUM_SLOW, 87.5, true, true, + new PokemonForm("Normal", "", PokemonType.GRASS, PokemonType.POISON, 2, 100, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CHLOROPHYLL, 525, 80, 82, 83, 100, 100, 80, 45, 50, 263, true, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.GRASS, PokemonType.POISON, 2.4, 155.5, AbilityId.THICK_FAT, AbilityId.THICK_FAT, AbilityId.THICK_FAT, 625, 80, 100, 123, 122, 120, 80, 45, 50, 263, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.GRASS, PokemonType.POISON, 24, 999.9, AbilityId.EFFECT_SPORE, AbilityId.NONE, AbilityId.EFFECT_SPORE, 625, 120, 122, 90, 108, 105, 80, 45, 50, 263, true) + ), + new PokemonSpecies(SpeciesId.CHARMANDER, 1, false, false, false, "Lizard Pokémon", PokemonType.FIRE, null, 0.6, 8.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SOLAR_POWER, 309, 39, 52, 43, 60, 50, 65, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.CHARMELEON, 1, false, false, false, "Flame Pokémon", PokemonType.FIRE, null, 1.1, 19, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SOLAR_POWER, 405, 58, 64, 58, 80, 65, 80, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.CHARIZARD, 1, false, false, false, "Flame Pokémon", PokemonType.FIRE, PokemonType.FLYING, 1.7, 90.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SOLAR_POWER, 534, 78, 84, 78, 109, 85, 100, 45, 50, 267, GrowthRate.MEDIUM_SLOW, 87.5, false, true, + new PokemonForm("Normal", "", PokemonType.FIRE, PokemonType.FLYING, 1.7, 90.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SOLAR_POWER, 534, 78, 84, 78, 109, 85, 100, 45, 50, 267, false, null, true), + new PokemonForm("Mega X", SpeciesFormKey.MEGA_X, PokemonType.FIRE, PokemonType.DRAGON, 1.7, 110.5, AbilityId.TOUGH_CLAWS, AbilityId.NONE, AbilityId.TOUGH_CLAWS, 634, 78, 130, 111, 130, 85, 100, 45, 50, 267), + new PokemonForm("Mega Y", SpeciesFormKey.MEGA_Y, PokemonType.FIRE, PokemonType.FLYING, 1.7, 100.5, AbilityId.DROUGHT, AbilityId.NONE, AbilityId.DROUGHT, 634, 78, 104, 78, 159, 115, 100, 45, 50, 267), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.FIRE, PokemonType.FLYING, 28, 999.9, AbilityId.BERSERK, AbilityId.NONE, AbilityId.BERSERK, 634, 118, 99, 88, 134, 95, 100, 45, 50, 267) + ), + new PokemonSpecies(SpeciesId.SQUIRTLE, 1, false, false, false, "Tiny Turtle Pokémon", PokemonType.WATER, null, 0.5, 9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.RAIN_DISH, 314, 44, 48, 65, 50, 64, 43, 45, 50, 63, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.WARTORTLE, 1, false, false, false, "Turtle Pokémon", PokemonType.WATER, null, 1, 22.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.RAIN_DISH, 405, 59, 63, 80, 65, 80, 58, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.BLASTOISE, 1, false, false, false, "Shellfish Pokémon", PokemonType.WATER, null, 1.6, 85.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.RAIN_DISH, 530, 79, 83, 100, 85, 105, 78, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, + new PokemonForm("Normal", "", PokemonType.WATER, null, 1.6, 85.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.RAIN_DISH, 530, 79, 83, 100, 85, 105, 78, 45, 50, 265, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.WATER, null, 1.6, 101.1, AbilityId.MEGA_LAUNCHER, AbilityId.NONE, AbilityId.MEGA_LAUNCHER, 630, 79, 103, 120, 135, 115, 78, 45, 50, 265), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.WATER, PokemonType.STEEL, 25, 999.9, AbilityId.SHELL_ARMOR, AbilityId.NONE, AbilityId.SHELL_ARMOR, 630, 119, 108, 125, 105, 110, 63, 45, 50, 265) + ), + new PokemonSpecies(SpeciesId.CATERPIE, 1, false, false, false, "Worm Pokémon", PokemonType.BUG, null, 0.3, 2.9, AbilityId.SHIELD_DUST, AbilityId.NONE, AbilityId.RUN_AWAY, 195, 45, 30, 35, 20, 20, 45, 255, 50, 39, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.METAPOD, 1, false, false, false, "Cocoon Pokémon", PokemonType.BUG, null, 0.7, 9.9, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.SHED_SKIN, 205, 50, 20, 55, 25, 25, 30, 120, 50, 72, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BUTTERFREE, 1, false, false, false, "Butterfly Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.1, 32, AbilityId.COMPOUND_EYES, AbilityId.NONE, AbilityId.TINTED_LENS, 395, 60, 45, 50, 90, 80, 70, 45, 50, 198, GrowthRate.MEDIUM_FAST, 50, true, true, + new PokemonForm("Normal", "", PokemonType.BUG, PokemonType.FLYING, 1.1, 32, AbilityId.COMPOUND_EYES, AbilityId.NONE, AbilityId.TINTED_LENS, 395, 60, 45, 50, 90, 80, 70, 45, 50, 198, true, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.BUG, PokemonType.FLYING, 17, 999.9, AbilityId.COMPOUND_EYES, AbilityId.NONE, AbilityId.COMPOUND_EYES, 495, 80, 40, 75, 120, 95, 85, 45, 50, 198, true) + ), + new PokemonSpecies(SpeciesId.WEEDLE, 1, false, false, false, "Hairy Bug Pokémon", PokemonType.BUG, PokemonType.POISON, 0.3, 3.2, AbilityId.SHIELD_DUST, AbilityId.NONE, AbilityId.RUN_AWAY, 195, 40, 35, 30, 20, 20, 50, 255, 70, 39, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.KAKUNA, 1, false, false, false, "Cocoon Pokémon", PokemonType.BUG, PokemonType.POISON, 0.6, 10, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.SHED_SKIN, 205, 45, 25, 50, 25, 25, 35, 120, 70, 72, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BEEDRILL, 1, false, false, false, "Poison Bee Pokémon", PokemonType.BUG, PokemonType.POISON, 1, 29.5, AbilityId.SWARM, AbilityId.NONE, AbilityId.SNIPER, 395, 65, 90, 40, 45, 80, 75, 45, 70, 198, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.BUG, PokemonType.POISON, 1, 29.5, AbilityId.SWARM, AbilityId.NONE, AbilityId.SNIPER, 395, 65, 90, 40, 45, 80, 75, 45, 70, 198, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.BUG, PokemonType.POISON, 1.4, 40.5, AbilityId.ADAPTABILITY, AbilityId.NONE, AbilityId.ADAPTABILITY, 495, 65, 150, 40, 15, 80, 145, 45, 70, 198) + ), + new PokemonSpecies(SpeciesId.PIDGEY, 1, false, false, false, "Tiny Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 1.8, AbilityId.KEEN_EYE, AbilityId.TANGLED_FEET, AbilityId.BIG_PECKS, 251, 40, 45, 40, 35, 35, 56, 255, 70, 50, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.PIDGEOTTO, 1, false, false, false, "Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.1, 30, AbilityId.KEEN_EYE, AbilityId.TANGLED_FEET, AbilityId.BIG_PECKS, 349, 63, 60, 55, 50, 50, 71, 120, 70, 122, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.PIDGEOT, 1, false, false, false, "Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.5, 39.5, AbilityId.KEEN_EYE, AbilityId.TANGLED_FEET, AbilityId.BIG_PECKS, 479, 83, 80, 75, 70, 70, 101, 45, 70, 240, GrowthRate.MEDIUM_SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.NORMAL, PokemonType.FLYING, 1.5, 39.5, AbilityId.KEEN_EYE, AbilityId.TANGLED_FEET, AbilityId.BIG_PECKS, 479, 83, 80, 75, 70, 70, 101, 45, 70, 240, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.NORMAL, PokemonType.FLYING, 2.2, 50.5, AbilityId.NO_GUARD, AbilityId.NO_GUARD, AbilityId.NO_GUARD, 579, 83, 80, 80, 135, 80, 121, 45, 70, 240) + ), + new PokemonSpecies(SpeciesId.RATTATA, 1, false, false, false, "Mouse Pokémon", PokemonType.NORMAL, null, 0.3, 3.5, AbilityId.RUN_AWAY, AbilityId.GUTS, AbilityId.HUSTLE, 253, 30, 56, 35, 25, 35, 72, 255, 70, 51, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.RATICATE, 1, false, false, false, "Mouse Pokémon", PokemonType.NORMAL, null, 0.7, 18.5, AbilityId.RUN_AWAY, AbilityId.GUTS, AbilityId.HUSTLE, 413, 55, 81, 60, 50, 70, 97, 127, 70, 145, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.SPEAROW, 1, false, false, false, "Tiny Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 2, AbilityId.KEEN_EYE, AbilityId.NONE, AbilityId.SNIPER, 262, 40, 60, 30, 31, 31, 70, 255, 70, 52, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.FEAROW, 1, false, false, false, "Beak Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.2, 38, AbilityId.KEEN_EYE, AbilityId.NONE, AbilityId.SNIPER, 442, 65, 90, 65, 61, 61, 100, 90, 70, 155, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.EKANS, 1, false, false, false, "Snake Pokémon", PokemonType.POISON, null, 2, 6.9, AbilityId.INTIMIDATE, AbilityId.SHED_SKIN, AbilityId.UNNERVE, 288, 35, 60, 44, 40, 54, 55, 255, 70, 58, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ARBOK, 1, false, false, false, "Cobra Pokémon", PokemonType.POISON, null, 3.5, 65, AbilityId.INTIMIDATE, AbilityId.SHED_SKIN, AbilityId.UNNERVE, 448, 60, 95, 69, 65, 79, 80, 90, 70, 157, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PIKACHU, 1, false, false, false, "Mouse Pokémon", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, GrowthRate.MEDIUM_FAST, 50, true, true, + new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, true, null, true), + new PokemonForm("Partner", "partner", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), + new PokemonForm("Cosplay", "cosplay", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom + new PokemonForm("Cool Cosplay", "cool-cosplay", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom + new PokemonForm("Beauty Cosplay", "beauty-cosplay", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom + new PokemonForm("Cute Cosplay", "cute-cosplay", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom + new PokemonForm("Smart Cosplay", "smart-cosplay", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom + new PokemonForm("Tough Cosplay", "tough-cosplay", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.ELECTRIC, null, 21, 999.9, AbilityId.LIGHTNING_ROD, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 530, 125, 95, 60, 90, 70, 90, 190, 50, 112) + ), + new PokemonSpecies(SpeciesId.RAICHU, 1, false, false, false, "Mouse Pokémon", PokemonType.ELECTRIC, null, 0.8, 30, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 485, 60, 90, 55, 90, 80, 110, 75, 50, 243, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.SANDSHREW, 1, false, false, false, "Mouse Pokémon", PokemonType.GROUND, null, 0.6, 12, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.SAND_RUSH, 300, 50, 75, 85, 20, 30, 40, 255, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SANDSLASH, 1, false, false, false, "Mouse Pokémon", PokemonType.GROUND, null, 1, 29.5, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.SAND_RUSH, 450, 75, 100, 110, 45, 55, 65, 90, 50, 158, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.NIDORAN_F, 1, false, false, false, "Poison Pin Pokémon", PokemonType.POISON, null, 0.4, 7, AbilityId.POISON_POINT, AbilityId.RIVALRY, AbilityId.HUSTLE, 275, 55, 47, 52, 40, 40, 41, 235, 50, 55, GrowthRate.MEDIUM_SLOW, 0, false), + new PokemonSpecies(SpeciesId.NIDORINA, 1, false, false, false, "Poison Pin Pokémon", PokemonType.POISON, null, 0.8, 20, AbilityId.POISON_POINT, AbilityId.RIVALRY, AbilityId.HUSTLE, 365, 70, 62, 67, 55, 55, 56, 120, 50, 128, GrowthRate.MEDIUM_SLOW, 0, false), + new PokemonSpecies(SpeciesId.NIDOQUEEN, 1, false, false, false, "Drill Pokémon", PokemonType.POISON, PokemonType.GROUND, 1.3, 60, AbilityId.POISON_POINT, AbilityId.RIVALRY, AbilityId.SHEER_FORCE, 505, 90, 92, 87, 75, 85, 76, 45, 50, 253, GrowthRate.MEDIUM_SLOW, 0, false), + new PokemonSpecies(SpeciesId.NIDORAN_M, 1, false, false, false, "Poison Pin Pokémon", PokemonType.POISON, null, 0.5, 9, AbilityId.POISON_POINT, AbilityId.RIVALRY, AbilityId.HUSTLE, 273, 46, 57, 40, 40, 40, 50, 235, 50, 55, GrowthRate.MEDIUM_SLOW, 100, false), + new PokemonSpecies(SpeciesId.NIDORINO, 1, false, false, false, "Poison Pin Pokémon", PokemonType.POISON, null, 0.9, 19.5, AbilityId.POISON_POINT, AbilityId.RIVALRY, AbilityId.HUSTLE, 365, 61, 72, 57, 55, 55, 65, 120, 50, 128, GrowthRate.MEDIUM_SLOW, 100, false), + new PokemonSpecies(SpeciesId.NIDOKING, 1, false, false, false, "Drill Pokémon", PokemonType.POISON, PokemonType.GROUND, 1.4, 62, AbilityId.POISON_POINT, AbilityId.RIVALRY, AbilityId.SHEER_FORCE, 505, 81, 102, 77, 85, 75, 85, 45, 50, 253, GrowthRate.MEDIUM_SLOW, 100, false), + new PokemonSpecies(SpeciesId.CLEFAIRY, 1, false, false, false, "Fairy Pokémon", PokemonType.FAIRY, null, 0.6, 7.5, AbilityId.CUTE_CHARM, AbilityId.MAGIC_GUARD, AbilityId.FRIEND_GUARD, 323, 70, 45, 48, 60, 65, 35, 150, 140, 113, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.CLEFABLE, 1, false, false, false, "Fairy Pokémon", PokemonType.FAIRY, null, 1.3, 40, AbilityId.CUTE_CHARM, AbilityId.MAGIC_GUARD, AbilityId.UNAWARE, 483, 95, 70, 73, 95, 90, 60, 25, 140, 242, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.VULPIX, 1, false, false, false, "Fox Pokémon", PokemonType.FIRE, null, 0.6, 9.9, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.DROUGHT, 299, 38, 41, 40, 50, 65, 65, 190, 50, 60, GrowthRate.MEDIUM_FAST, 25, false), + new PokemonSpecies(SpeciesId.NINETALES, 1, false, false, false, "Fox Pokémon", PokemonType.FIRE, null, 1.1, 19.9, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.DROUGHT, 505, 73, 76, 75, 81, 100, 100, 75, 50, 177, GrowthRate.MEDIUM_FAST, 25, false), + new PokemonSpecies(SpeciesId.JIGGLYPUFF, 1, false, false, false, "Balloon Pokémon", PokemonType.NORMAL, PokemonType.FAIRY, 0.5, 5.5, AbilityId.CUTE_CHARM, AbilityId.COMPETITIVE, AbilityId.FRIEND_GUARD, 270, 115, 45, 20, 45, 25, 20, 170, 50, 95, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.WIGGLYTUFF, 1, false, false, false, "Balloon Pokémon", PokemonType.NORMAL, PokemonType.FAIRY, 1, 12, AbilityId.CUTE_CHARM, AbilityId.COMPETITIVE, AbilityId.FRISK, 435, 140, 70, 45, 85, 50, 45, 50, 50, 218, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.ZUBAT, 1, false, false, false, "Bat Pokémon", PokemonType.POISON, PokemonType.FLYING, 0.8, 7.5, AbilityId.INNER_FOCUS, AbilityId.NONE, AbilityId.INFILTRATOR, 245, 40, 45, 35, 30, 40, 55, 255, 50, 49, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.GOLBAT, 1, false, false, false, "Bat Pokémon", PokemonType.POISON, PokemonType.FLYING, 1.6, 55, AbilityId.INNER_FOCUS, AbilityId.NONE, AbilityId.INFILTRATOR, 455, 75, 80, 70, 65, 75, 90, 90, 50, 159, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.ODDISH, 1, false, false, false, "Weed Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.5, 5.4, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.RUN_AWAY, 320, 45, 50, 55, 75, 65, 30, 255, 50, 64, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.GLOOM, 1, false, false, false, "Weed Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.8, 8.6, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.STENCH, 395, 60, 65, 70, 85, 75, 40, 120, 50, 138, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.VILEPLUME, 1, false, false, false, "Flower Pokémon", PokemonType.GRASS, PokemonType.POISON, 1.2, 18.6, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.EFFECT_SPORE, 490, 75, 80, 85, 110, 90, 50, 45, 50, 245, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.PARAS, 1, false, false, false, "Mushroom Pokémon", PokemonType.BUG, PokemonType.GRASS, 0.3, 5.4, AbilityId.EFFECT_SPORE, AbilityId.DRY_SKIN, AbilityId.DAMP, 285, 35, 70, 55, 45, 55, 25, 190, 70, 57, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PARASECT, 1, false, false, false, "Mushroom Pokémon", PokemonType.BUG, PokemonType.GRASS, 1, 29.5, AbilityId.EFFECT_SPORE, AbilityId.DRY_SKIN, AbilityId.DAMP, 405, 60, 95, 80, 60, 80, 30, 75, 70, 142, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.VENONAT, 1, false, false, false, "Insect Pokémon", PokemonType.BUG, PokemonType.POISON, 1, 30, AbilityId.COMPOUND_EYES, AbilityId.TINTED_LENS, AbilityId.RUN_AWAY, 305, 60, 55, 50, 40, 55, 45, 190, 70, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.VENOMOTH, 1, false, false, false, "Poison Moth Pokémon", PokemonType.BUG, PokemonType.POISON, 1.5, 12.5, AbilityId.SHIELD_DUST, AbilityId.TINTED_LENS, AbilityId.WONDER_SKIN, 450, 70, 65, 60, 90, 75, 90, 75, 70, 158, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DIGLETT, 1, false, false, false, "Mole Pokémon", PokemonType.GROUND, null, 0.2, 0.8, AbilityId.SAND_VEIL, AbilityId.ARENA_TRAP, AbilityId.SAND_FORCE, 265, 10, 55, 25, 35, 45, 95, 255, 50, 53, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DUGTRIO, 1, false, false, false, "Mole Pokémon", PokemonType.GROUND, null, 0.7, 33.3, AbilityId.SAND_VEIL, AbilityId.ARENA_TRAP, AbilityId.SAND_FORCE, 425, 35, 100, 50, 50, 70, 120, 50, 50, 149, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MEOWTH, 1, false, false, false, "Scratch Cat Pokémon", PokemonType.NORMAL, null, 0.4, 4.2, AbilityId.PICKUP, AbilityId.TECHNICIAN, AbilityId.UNNERVE, 290, 40, 45, 35, 40, 40, 90, 255, 50, 58, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.NORMAL, null, 0.4, 4.2, AbilityId.PICKUP, AbilityId.TECHNICIAN, AbilityId.UNNERVE, 290, 40, 45, 35, 40, 40, 90, 255, 50, 58, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.NORMAL, null, 33, 999.9, AbilityId.TECHNICIAN, AbilityId.TECHNICIAN, AbilityId.TECHNICIAN, 540, 115, 110, 65, 65, 70, 115, 255, 50, 58) + ), + new PokemonSpecies(SpeciesId.PERSIAN, 1, false, false, false, "Classy Cat Pokémon", PokemonType.NORMAL, null, 1, 32, AbilityId.LIMBER, AbilityId.TECHNICIAN, AbilityId.UNNERVE, 440, 65, 70, 60, 65, 65, 115, 90, 50, 154, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PSYDUCK, 1, false, false, false, "Duck Pokémon", PokemonType.WATER, null, 0.8, 19.6, AbilityId.DAMP, AbilityId.CLOUD_NINE, AbilityId.SWIFT_SWIM, 320, 50, 52, 48, 65, 50, 55, 190, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GOLDUCK, 1, false, false, false, "Duck Pokémon", PokemonType.WATER, null, 1.7, 76.6, AbilityId.DAMP, AbilityId.CLOUD_NINE, AbilityId.SWIFT_SWIM, 500, 80, 82, 78, 95, 80, 85, 75, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MANKEY, 1, false, false, false, "Pig Monkey Pokémon", PokemonType.FIGHTING, null, 0.5, 28, AbilityId.VITAL_SPIRIT, AbilityId.ANGER_POINT, AbilityId.DEFIANT, 305, 40, 80, 35, 35, 45, 70, 190, 70, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PRIMEAPE, 1, false, false, false, "Pig Monkey Pokémon", PokemonType.FIGHTING, null, 1, 32, AbilityId.VITAL_SPIRIT, AbilityId.ANGER_POINT, AbilityId.DEFIANT, 455, 65, 105, 60, 60, 70, 95, 75, 70, 159, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GROWLITHE, 1, false, false, false, "Puppy Pokémon", PokemonType.FIRE, null, 0.7, 19, AbilityId.INTIMIDATE, AbilityId.FLASH_FIRE, AbilityId.JUSTIFIED, 350, 55, 70, 45, 70, 50, 60, 190, 50, 70, GrowthRate.SLOW, 75, false), + new PokemonSpecies(SpeciesId.ARCANINE, 1, false, false, false, "Legendary Pokémon", PokemonType.FIRE, null, 1.9, 155, AbilityId.INTIMIDATE, AbilityId.FLASH_FIRE, AbilityId.JUSTIFIED, 555, 90, 110, 80, 100, 80, 95, 75, 50, 194, GrowthRate.SLOW, 75, false), + new PokemonSpecies(SpeciesId.POLIWAG, 1, false, false, false, "Tadpole Pokémon", PokemonType.WATER, null, 0.6, 12.4, AbilityId.WATER_ABSORB, AbilityId.DAMP, AbilityId.SWIFT_SWIM, 300, 40, 50, 40, 40, 40, 90, 255, 50, 60, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.POLIWHIRL, 1, false, false, false, "Tadpole Pokémon", PokemonType.WATER, null, 1, 20, AbilityId.WATER_ABSORB, AbilityId.DAMP, AbilityId.SWIFT_SWIM, 385, 65, 65, 65, 50, 50, 90, 120, 50, 135, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.POLIWRATH, 1, false, false, false, "Tadpole Pokémon", PokemonType.WATER, PokemonType.FIGHTING, 1.3, 54, AbilityId.WATER_ABSORB, AbilityId.DAMP, AbilityId.SWIFT_SWIM, 510, 90, 95, 95, 70, 90, 70, 45, 50, 255, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.ABRA, 1, false, false, false, "Psi Pokémon", PokemonType.PSYCHIC, null, 0.9, 19.5, AbilityId.SYNCHRONIZE, AbilityId.INNER_FOCUS, AbilityId.MAGIC_GUARD, 310, 25, 20, 15, 105, 55, 90, 200, 50, 62, GrowthRate.MEDIUM_SLOW, 75, false), + new PokemonSpecies(SpeciesId.KADABRA, 1, false, false, false, "Psi Pokémon", PokemonType.PSYCHIC, null, 1.3, 56.5, AbilityId.SYNCHRONIZE, AbilityId.INNER_FOCUS, AbilityId.MAGIC_GUARD, 400, 40, 35, 30, 120, 70, 105, 100, 50, 140, GrowthRate.MEDIUM_SLOW, 75, true), + new PokemonSpecies(SpeciesId.ALAKAZAM, 1, false, false, false, "Psi Pokémon", PokemonType.PSYCHIC, null, 1.5, 48, AbilityId.SYNCHRONIZE, AbilityId.INNER_FOCUS, AbilityId.MAGIC_GUARD, 500, 55, 50, 45, 135, 95, 120, 50, 50, 250, GrowthRate.MEDIUM_SLOW, 75, true, true, + new PokemonForm("Normal", "", PokemonType.PSYCHIC, null, 1.5, 48, AbilityId.SYNCHRONIZE, AbilityId.INNER_FOCUS, AbilityId.MAGIC_GUARD, 500, 55, 50, 45, 135, 95, 120, 50, 50, 250, true, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.PSYCHIC, null, 1.2, 48, AbilityId.TRACE, AbilityId.TRACE, AbilityId.TRACE, 600, 55, 50, 65, 175, 105, 150, 50, 50, 250, true) + ), + new PokemonSpecies(SpeciesId.MACHOP, 1, false, false, false, "Superpower Pokémon", PokemonType.FIGHTING, null, 0.8, 19.5, AbilityId.GUTS, AbilityId.NO_GUARD, AbilityId.STEADFAST, 305, 70, 80, 50, 35, 35, 35, 180, 50, 61, GrowthRate.MEDIUM_SLOW, 75, false), + new PokemonSpecies(SpeciesId.MACHOKE, 1, false, false, false, "Superpower Pokémon", PokemonType.FIGHTING, null, 1.5, 70.5, AbilityId.GUTS, AbilityId.NO_GUARD, AbilityId.STEADFAST, 405, 80, 100, 70, 50, 60, 45, 90, 50, 142, GrowthRate.MEDIUM_SLOW, 75, false), + new PokemonSpecies(SpeciesId.MACHAMP, 1, false, false, false, "Superpower Pokémon", PokemonType.FIGHTING, null, 1.6, 130, AbilityId.GUTS, AbilityId.NO_GUARD, AbilityId.STEADFAST, 505, 90, 130, 80, 65, 85, 55, 45, 50, 253, GrowthRate.MEDIUM_SLOW, 75, false, true, + new PokemonForm("Normal", "", PokemonType.FIGHTING, null, 1.6, 130, AbilityId.GUTS, AbilityId.NO_GUARD, AbilityId.STEADFAST, 505, 90, 130, 80, 65, 85, 55, 45, 50, 253, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.FIGHTING, null, 25, 999.9, AbilityId.GUTS, AbilityId.GUTS, AbilityId.GUTS, 605, 120, 170, 85, 75, 90, 65, 45, 50, 253) + ), + new PokemonSpecies(SpeciesId.BELLSPROUT, 1, false, false, false, "Flower Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.7, 4, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.GLUTTONY, 300, 50, 75, 35, 70, 30, 40, 255, 70, 60, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.WEEPINBELL, 1, false, false, false, "Flycatcher Pokémon", PokemonType.GRASS, PokemonType.POISON, 1, 6.4, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.GLUTTONY, 390, 65, 90, 50, 85, 45, 55, 120, 70, 137, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.VICTREEBEL, 1, false, false, false, "Flycatcher Pokémon", PokemonType.GRASS, PokemonType.POISON, 1.7, 15.5, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.GLUTTONY, 490, 80, 105, 65, 100, 70, 70, 45, 70, 245, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.TENTACOOL, 1, false, false, false, "Jellyfish Pokémon", PokemonType.WATER, PokemonType.POISON, 0.9, 45.5, AbilityId.CLEAR_BODY, AbilityId.LIQUID_OOZE, AbilityId.RAIN_DISH, 335, 40, 40, 35, 50, 100, 70, 190, 50, 67, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.TENTACRUEL, 1, false, false, false, "Jellyfish Pokémon", PokemonType.WATER, PokemonType.POISON, 1.6, 55, AbilityId.CLEAR_BODY, AbilityId.LIQUID_OOZE, AbilityId.RAIN_DISH, 515, 80, 70, 65, 80, 120, 100, 60, 50, 180, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.GEODUDE, 1, false, false, false, "Rock Pokémon", PokemonType.ROCK, PokemonType.GROUND, 0.4, 20, AbilityId.ROCK_HEAD, AbilityId.STURDY, AbilityId.SAND_VEIL, 300, 40, 80, 100, 30, 30, 20, 255, 70, 60, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.GRAVELER, 1, false, false, false, "Rock Pokémon", PokemonType.ROCK, PokemonType.GROUND, 1, 105, AbilityId.ROCK_HEAD, AbilityId.STURDY, AbilityId.SAND_VEIL, 390, 55, 95, 115, 45, 45, 35, 120, 70, 137, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.GOLEM, 1, false, false, false, "Megaton Pokémon", PokemonType.ROCK, PokemonType.GROUND, 1.4, 300, AbilityId.ROCK_HEAD, AbilityId.STURDY, AbilityId.SAND_VEIL, 495, 80, 120, 130, 55, 65, 45, 45, 70, 248, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.PONYTA, 1, false, false, false, "Fire Horse Pokémon", PokemonType.FIRE, null, 1, 30, AbilityId.RUN_AWAY, AbilityId.FLASH_FIRE, AbilityId.FLAME_BODY, 410, 50, 85, 55, 65, 65, 90, 190, 50, 82, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.RAPIDASH, 1, false, false, false, "Fire Horse Pokémon", PokemonType.FIRE, null, 1.7, 95, AbilityId.RUN_AWAY, AbilityId.FLASH_FIRE, AbilityId.FLAME_BODY, 500, 65, 100, 70, 80, 80, 105, 60, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SLOWPOKE, 1, false, false, false, "Dopey Pokémon", PokemonType.WATER, PokemonType.PSYCHIC, 1.2, 36, AbilityId.OBLIVIOUS, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 315, 90, 65, 65, 40, 40, 15, 190, 50, 63, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SLOWBRO, 1, false, false, false, "Hermit Crab Pokémon", PokemonType.WATER, PokemonType.PSYCHIC, 1.6, 78.5, AbilityId.OBLIVIOUS, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 490, 95, 75, 110, 100, 80, 30, 75, 50, 172, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.PSYCHIC, 1.6, 78.5, AbilityId.OBLIVIOUS, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 490, 95, 75, 110, 100, 80, 30, 75, 50, 172, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.WATER, PokemonType.PSYCHIC, 2, 120, AbilityId.SHELL_ARMOR, AbilityId.SHELL_ARMOR, AbilityId.SHELL_ARMOR, 590, 95, 75, 180, 130, 80, 30, 75, 50, 172) + ), + new PokemonSpecies(SpeciesId.MAGNEMITE, 1, false, false, false, "Magnet Pokémon", PokemonType.ELECTRIC, PokemonType.STEEL, 0.3, 6, AbilityId.MAGNET_PULL, AbilityId.STURDY, AbilityId.ANALYTIC, 325, 25, 35, 70, 95, 55, 45, 190, 50, 65, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.MAGNETON, 1, false, false, false, "Magnet Pokémon", PokemonType.ELECTRIC, PokemonType.STEEL, 1, 60, AbilityId.MAGNET_PULL, AbilityId.STURDY, AbilityId.ANALYTIC, 465, 50, 60, 95, 120, 70, 70, 60, 50, 163, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.FARFETCHD, 1, false, false, false, "Wild Duck Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.8, 15, AbilityId.KEEN_EYE, AbilityId.INNER_FOCUS, AbilityId.DEFIANT, 377, 52, 90, 55, 58, 62, 60, 45, 50, 132, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DODUO, 1, false, false, false, "Twin Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.4, 39.2, AbilityId.RUN_AWAY, AbilityId.EARLY_BIRD, AbilityId.TANGLED_FEET, 310, 35, 85, 45, 35, 35, 75, 190, 70, 62, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.DODRIO, 1, false, false, false, "Triple Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.8, 85.2, AbilityId.RUN_AWAY, AbilityId.EARLY_BIRD, AbilityId.TANGLED_FEET, 470, 60, 110, 70, 60, 60, 110, 45, 70, 165, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.SEEL, 1, false, false, false, "Sea Lion Pokémon", PokemonType.WATER, null, 1.1, 90, AbilityId.THICK_FAT, AbilityId.HYDRATION, AbilityId.ICE_BODY, 325, 65, 45, 55, 45, 70, 45, 190, 70, 65, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DEWGONG, 1, false, false, false, "Sea Lion Pokémon", PokemonType.WATER, PokemonType.ICE, 1.7, 120, AbilityId.THICK_FAT, AbilityId.HYDRATION, AbilityId.ICE_BODY, 475, 90, 70, 80, 70, 95, 70, 75, 70, 166, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GRIMER, 1, false, false, false, "Sludge Pokémon", PokemonType.POISON, null, 0.9, 30, AbilityId.STENCH, AbilityId.STICKY_HOLD, AbilityId.POISON_TOUCH, 325, 80, 80, 50, 40, 50, 25, 190, 70, 65, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MUK, 1, false, false, false, "Sludge Pokémon", PokemonType.POISON, null, 1.2, 30, AbilityId.STENCH, AbilityId.STICKY_HOLD, AbilityId.POISON_TOUCH, 500, 105, 105, 75, 65, 100, 50, 75, 70, 175, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SHELLDER, 1, false, false, false, "Bivalve Pokémon", PokemonType.WATER, null, 0.3, 4, AbilityId.SHELL_ARMOR, AbilityId.SKILL_LINK, AbilityId.OVERCOAT, 305, 30, 65, 100, 45, 25, 40, 190, 50, 61, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.CLOYSTER, 1, false, false, false, "Bivalve Pokémon", PokemonType.WATER, PokemonType.ICE, 1.5, 132.5, AbilityId.SHELL_ARMOR, AbilityId.SKILL_LINK, AbilityId.OVERCOAT, 525, 50, 95, 180, 85, 45, 70, 60, 50, 184, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.GASTLY, 1, false, false, false, "Gas Pokémon", PokemonType.GHOST, PokemonType.POISON, 1.3, 0.1, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 310, 30, 35, 30, 100, 35, 80, 190, 50, 62, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.HAUNTER, 1, false, false, false, "Gas Pokémon", PokemonType.GHOST, PokemonType.POISON, 1.6, 0.1, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 405, 45, 50, 45, 115, 55, 95, 90, 50, 142, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.GENGAR, 1, false, false, false, "Shadow Pokémon", PokemonType.GHOST, PokemonType.POISON, 1.5, 40.5, AbilityId.CURSED_BODY, AbilityId.NONE, AbilityId.NONE, 500, 60, 65, 60, 130, 75, 110, 45, 50, 250, GrowthRate.MEDIUM_SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.GHOST, PokemonType.POISON, 1.5, 40.5, AbilityId.CURSED_BODY, AbilityId.NONE, AbilityId.NONE, 500, 60, 65, 60, 130, 75, 110, 45, 50, 250, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.GHOST, PokemonType.POISON, 1.4, 40.5, AbilityId.SHADOW_TAG, AbilityId.NONE, AbilityId.NONE, 600, 60, 65, 80, 170, 95, 130, 45, 50, 250), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.GHOST, PokemonType.POISON, 20, 999.9, AbilityId.CURSED_BODY, AbilityId.NONE, AbilityId.NONE, 600, 140, 65, 70, 140, 85, 100, 45, 50, 250) + ), + new PokemonSpecies(SpeciesId.ONIX, 1, false, false, false, "Rock Snake Pokémon", PokemonType.ROCK, PokemonType.GROUND, 8.8, 210, AbilityId.ROCK_HEAD, AbilityId.STURDY, AbilityId.WEAK_ARMOR, 385, 35, 45, 160, 30, 45, 70, 45, 50, 77, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DROWZEE, 1, false, false, false, "Hypnosis Pokémon", PokemonType.PSYCHIC, null, 1, 32.4, AbilityId.INSOMNIA, AbilityId.FOREWARN, AbilityId.INNER_FOCUS, 328, 60, 48, 45, 43, 90, 42, 190, 70, 66, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.HYPNO, 1, false, false, false, "Hypnosis Pokémon", PokemonType.PSYCHIC, null, 1.6, 75.6, AbilityId.INSOMNIA, AbilityId.FOREWARN, AbilityId.INNER_FOCUS, 483, 85, 73, 70, 73, 115, 67, 75, 70, 169, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.KRABBY, 1, false, false, false, "River Crab Pokémon", PokemonType.WATER, null, 0.4, 6.5, AbilityId.HYPER_CUTTER, AbilityId.SHELL_ARMOR, AbilityId.SHEER_FORCE, 325, 30, 105, 90, 25, 25, 50, 225, 50, 65, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.KINGLER, 1, false, false, false, "Pincer Pokémon", PokemonType.WATER, null, 1.3, 60, AbilityId.HYPER_CUTTER, AbilityId.SHELL_ARMOR, AbilityId.SHEER_FORCE, 475, 55, 130, 115, 50, 50, 75, 60, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.WATER, null, 1.3, 60, AbilityId.HYPER_CUTTER, AbilityId.SHELL_ARMOR, AbilityId.SHEER_FORCE, 475, 55, 130, 115, 50, 50, 75, 60, 50, 166, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.WATER, null, 19, 999.9, AbilityId.TOUGH_CLAWS, AbilityId.TOUGH_CLAWS, AbilityId.TOUGH_CLAWS, 575, 92, 145, 140, 60, 65, 73, 60, 50, 166) + ), + new PokemonSpecies(SpeciesId.VOLTORB, 1, false, false, false, "Ball Pokémon", PokemonType.ELECTRIC, null, 0.5, 10.4, AbilityId.SOUNDPROOF, AbilityId.STATIC, AbilityId.AFTERMATH, 330, 40, 30, 50, 55, 55, 100, 190, 70, 66, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.ELECTRODE, 1, false, false, false, "Ball Pokémon", PokemonType.ELECTRIC, null, 1.2, 66.6, AbilityId.SOUNDPROOF, AbilityId.STATIC, AbilityId.AFTERMATH, 490, 60, 50, 70, 80, 80, 150, 60, 70, 172, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.EXEGGCUTE, 1, false, false, false, "Egg Pokémon", PokemonType.GRASS, PokemonType.PSYCHIC, 0.4, 2.5, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.HARVEST, 325, 60, 40, 80, 60, 45, 40, 90, 50, 65, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.EXEGGUTOR, 1, false, false, false, "Coconut Pokémon", PokemonType.GRASS, PokemonType.PSYCHIC, 2, 120, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.HARVEST, 530, 95, 95, 85, 125, 75, 55, 45, 50, 186, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.CUBONE, 1, false, false, false, "Lonely Pokémon", PokemonType.GROUND, null, 0.4, 6.5, AbilityId.ROCK_HEAD, AbilityId.LIGHTNING_ROD, AbilityId.BATTLE_ARMOR, 320, 50, 50, 95, 40, 50, 35, 190, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MAROWAK, 1, false, false, false, "Bone Keeper Pokémon", PokemonType.GROUND, null, 1, 45, AbilityId.ROCK_HEAD, AbilityId.LIGHTNING_ROD, AbilityId.BATTLE_ARMOR, 425, 60, 80, 110, 50, 80, 45, 75, 50, 149, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.HITMONLEE, 1, false, false, false, "Kicking Pokémon", PokemonType.FIGHTING, null, 1.5, 49.8, AbilityId.LIMBER, AbilityId.RECKLESS, AbilityId.UNBURDEN, 455, 50, 120, 53, 35, 110, 87, 45, 50, 159, GrowthRate.MEDIUM_FAST, 100, false), + new PokemonSpecies(SpeciesId.HITMONCHAN, 1, false, false, false, "Punching Pokémon", PokemonType.FIGHTING, null, 1.4, 50.2, AbilityId.KEEN_EYE, AbilityId.IRON_FIST, AbilityId.INNER_FOCUS, 455, 50, 105, 79, 35, 110, 76, 45, 50, 159, GrowthRate.MEDIUM_FAST, 100, false), + new PokemonSpecies(SpeciesId.LICKITUNG, 1, false, false, false, "Licking Pokémon", PokemonType.NORMAL, null, 1.2, 65.5, AbilityId.OWN_TEMPO, AbilityId.OBLIVIOUS, AbilityId.CLOUD_NINE, 385, 90, 55, 75, 60, 75, 30, 45, 50, 77, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.KOFFING, 1, false, false, false, "Poison Gas Pokémon", PokemonType.POISON, null, 0.6, 1, AbilityId.LEVITATE, AbilityId.NEUTRALIZING_GAS, AbilityId.STENCH, 340, 40, 65, 95, 60, 45, 35, 190, 50, 68, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.WEEZING, 1, false, false, false, "Poison Gas Pokémon", PokemonType.POISON, null, 1.2, 9.5, AbilityId.LEVITATE, AbilityId.NEUTRALIZING_GAS, AbilityId.STENCH, 490, 65, 90, 120, 85, 70, 60, 60, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.RHYHORN, 1, false, false, false, "Spikes Pokémon", PokemonType.GROUND, PokemonType.ROCK, 1, 115, AbilityId.LIGHTNING_ROD, AbilityId.ROCK_HEAD, AbilityId.RECKLESS, 345, 80, 85, 95, 30, 30, 25, 120, 50, 69, GrowthRate.SLOW, 50, true), + new PokemonSpecies(SpeciesId.RHYDON, 1, false, false, false, "Drill Pokémon", PokemonType.GROUND, PokemonType.ROCK, 1.9, 120, AbilityId.LIGHTNING_ROD, AbilityId.ROCK_HEAD, AbilityId.RECKLESS, 485, 105, 130, 120, 45, 45, 40, 60, 50, 170, GrowthRate.SLOW, 50, true), + new PokemonSpecies(SpeciesId.CHANSEY, 1, false, false, false, "Egg Pokémon", PokemonType.NORMAL, null, 1.1, 34.6, AbilityId.NATURAL_CURE, AbilityId.SERENE_GRACE, AbilityId.HEALER, 450, 250, 5, 5, 35, 105, 50, 30, 140, 395, GrowthRate.FAST, 0, false), + new PokemonSpecies(SpeciesId.TANGELA, 1, false, false, false, "Vine Pokémon", PokemonType.GRASS, null, 1, 35, AbilityId.CHLOROPHYLL, AbilityId.LEAF_GUARD, AbilityId.REGENERATOR, 435, 65, 55, 115, 100, 40, 60, 45, 50, 87, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.KANGASKHAN, 1, false, false, false, "Parent Pokémon", PokemonType.NORMAL, null, 2.2, 80, AbilityId.EARLY_BIRD, AbilityId.SCRAPPY, AbilityId.INNER_FOCUS, 490, 105, 95, 80, 40, 80, 90, 45, 50, 172, GrowthRate.MEDIUM_FAST, 0, false, true, + new PokemonForm("Normal", "", PokemonType.NORMAL, null, 2.2, 80, AbilityId.EARLY_BIRD, AbilityId.SCRAPPY, AbilityId.INNER_FOCUS, 490, 105, 95, 80, 40, 80, 90, 45, 50, 172, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.NORMAL, null, 2.2, 100, AbilityId.PARENTAL_BOND, AbilityId.PARENTAL_BOND, AbilityId.PARENTAL_BOND, 590, 105, 125, 100, 60, 100, 100, 45, 50, 172) + ), + new PokemonSpecies(SpeciesId.HORSEA, 1, false, false, false, "Dragon Pokémon", PokemonType.WATER, null, 0.4, 8, AbilityId.SWIFT_SWIM, AbilityId.SNIPER, AbilityId.DAMP, 295, 30, 40, 70, 70, 25, 60, 225, 50, 59, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SEADRA, 1, false, false, false, "Dragon Pokémon", PokemonType.WATER, null, 1.2, 25, AbilityId.POISON_POINT, AbilityId.SNIPER, AbilityId.DAMP, 440, 55, 65, 95, 95, 45, 85, 75, 50, 154, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GOLDEEN, 1, false, false, false, "Goldfish Pokémon", PokemonType.WATER, null, 0.6, 15, AbilityId.SWIFT_SWIM, AbilityId.WATER_VEIL, AbilityId.LIGHTNING_ROD, 320, 45, 67, 60, 35, 50, 63, 225, 50, 64, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.SEAKING, 1, false, false, false, "Goldfish Pokémon", PokemonType.WATER, null, 1.3, 39, AbilityId.SWIFT_SWIM, AbilityId.WATER_VEIL, AbilityId.LIGHTNING_ROD, 450, 80, 92, 65, 65, 80, 68, 60, 50, 158, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.STARYU, 1, false, false, false, "Star Shape Pokémon", PokemonType.WATER, null, 0.8, 34.5, AbilityId.ILLUMINATE, AbilityId.NATURAL_CURE, AbilityId.ANALYTIC, 340, 30, 45, 55, 70, 55, 85, 225, 50, 68, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.STARMIE, 1, false, false, false, "Mysterious Pokémon", PokemonType.WATER, PokemonType.PSYCHIC, 1.1, 80, AbilityId.ILLUMINATE, AbilityId.NATURAL_CURE, AbilityId.ANALYTIC, 520, 60, 75, 85, 100, 85, 115, 60, 50, 182, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.MR_MIME, 1, false, false, false, "Barrier Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.3, 54.5, AbilityId.SOUNDPROOF, AbilityId.FILTER, AbilityId.TECHNICIAN, 460, 40, 45, 65, 100, 120, 90, 45, 50, 161, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SCYTHER, 1, false, false, false, "Mantis Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.5, 56, AbilityId.SWARM, AbilityId.TECHNICIAN, AbilityId.STEADFAST, 500, 70, 110, 80, 55, 80, 105, 45, 50, 100, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.JYNX, 1, false, false, false, "Human Shape Pokémon", PokemonType.ICE, PokemonType.PSYCHIC, 1.4, 40.6, AbilityId.OBLIVIOUS, AbilityId.FOREWARN, AbilityId.DRY_SKIN, 455, 65, 50, 35, 115, 95, 95, 45, 50, 159, GrowthRate.MEDIUM_FAST, 0, false), + new PokemonSpecies(SpeciesId.ELECTABUZZ, 1, false, false, false, "Electric Pokémon", PokemonType.ELECTRIC, null, 1.1, 30, AbilityId.STATIC, AbilityId.NONE, AbilityId.VITAL_SPIRIT, 490, 65, 83, 57, 95, 85, 105, 45, 50, 172, GrowthRate.MEDIUM_FAST, 75, false), + new PokemonSpecies(SpeciesId.MAGMAR, 1, false, false, false, "Spitfire Pokémon", PokemonType.FIRE, null, 1.3, 44.5, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.VITAL_SPIRIT, 495, 65, 95, 57, 100, 85, 93, 45, 50, 173, GrowthRate.MEDIUM_FAST, 75, false), + new PokemonSpecies(SpeciesId.PINSIR, 1, false, false, false, "Stag Beetle Pokémon", PokemonType.BUG, null, 1.5, 55, AbilityId.HYPER_CUTTER, AbilityId.MOLD_BREAKER, AbilityId.MOXIE, 500, 65, 125, 100, 55, 70, 85, 45, 50, 175, GrowthRate.SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.BUG, null, 1.5, 55, AbilityId.HYPER_CUTTER, AbilityId.MOLD_BREAKER, AbilityId.MOXIE, 500, 65, 125, 100, 55, 70, 85, 45, 50, 175, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.BUG, PokemonType.FLYING, 1.7, 59, AbilityId.AERILATE, AbilityId.AERILATE, AbilityId.AERILATE, 600, 65, 155, 120, 65, 90, 105, 45, 50, 175) + ), + new PokemonSpecies(SpeciesId.TAUROS, 1, false, false, false, "Wild Bull Pokémon", PokemonType.NORMAL, null, 1.4, 88.4, AbilityId.INTIMIDATE, AbilityId.ANGER_POINT, AbilityId.SHEER_FORCE, 490, 75, 100, 95, 40, 70, 110, 45, 50, 172, GrowthRate.SLOW, 100, false), + new PokemonSpecies(SpeciesId.MAGIKARP, 1, false, false, false, "Fish Pokémon", PokemonType.WATER, null, 0.9, 10, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.RATTLED, 200, 20, 10, 55, 15, 20, 80, 255, 50, 40, GrowthRate.SLOW, 50, true), + new PokemonSpecies(SpeciesId.GYARADOS, 1, false, false, false, "Atrocious Pokémon", PokemonType.WATER, PokemonType.FLYING, 6.5, 235, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.MOXIE, 540, 95, 125, 79, 60, 100, 81, 45, 50, 189, GrowthRate.SLOW, 50, true, true, + new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.FLYING, 6.5, 235, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.MOXIE, 540, 95, 125, 79, 60, 100, 81, 45, 50, 189, true, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.WATER, PokemonType.DARK, 6.5, 305, AbilityId.MOLD_BREAKER, AbilityId.MOLD_BREAKER, AbilityId.MOLD_BREAKER, 640, 95, 155, 109, 70, 130, 81, 45, 50, 189, true) + ), + new PokemonSpecies(SpeciesId.LAPRAS, 1, false, false, false, "Transport Pokémon", PokemonType.WATER, PokemonType.ICE, 2.5, 220, AbilityId.WATER_ABSORB, AbilityId.SHELL_ARMOR, AbilityId.HYDRATION, 535, 130, 85, 80, 85, 95, 60, 45, 50, 187, GrowthRate.SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.ICE, 2.5, 220, AbilityId.WATER_ABSORB, AbilityId.SHELL_ARMOR, AbilityId.HYDRATION, 535, 130, 85, 80, 85, 95, 60, 45, 50, 187, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.WATER, PokemonType.ICE, 24, 999.9, AbilityId.SHIELD_DUST, AbilityId.SHIELD_DUST, AbilityId.SHIELD_DUST, 635, 170, 97, 85, 107, 111, 65, 45, 50, 187) + ), + new PokemonSpecies(SpeciesId.DITTO, 1, false, false, false, "Transform Pokémon", PokemonType.NORMAL, null, 0.3, 4, AbilityId.LIMBER, AbilityId.NONE, AbilityId.IMPOSTER, 288, 48, 48, 48, 48, 48, 48, 35, 50, 101, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.EEVEE, 1, false, false, false, "Evolution Pokémon", PokemonType.NORMAL, null, 0.3, 6.5, AbilityId.RUN_AWAY, AbilityId.ADAPTABILITY, AbilityId.ANTICIPATION, 325, 55, 55, 50, 45, 65, 55, 45, 50, 65, GrowthRate.MEDIUM_FAST, 87.5, false, true, + new PokemonForm("Normal", "", PokemonType.NORMAL, null, 0.3, 6.5, AbilityId.RUN_AWAY, AbilityId.ADAPTABILITY, AbilityId.ANTICIPATION, 325, 55, 55, 50, 45, 65, 55, 45, 50, 65, false, null, true), + new PokemonForm("Partner", "partner", PokemonType.NORMAL, null, 0.3, 6.5, AbilityId.RUN_AWAY, AbilityId.ADAPTABILITY, AbilityId.ANTICIPATION, 435, 65, 75, 70, 65, 85, 75, 45, 50, 65, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.NORMAL, null, 18, 999.9, AbilityId.PROTEAN, AbilityId.PROTEAN, AbilityId.PROTEAN, 535, 110, 95, 70, 90, 85, 85, 45, 50, 65) + ), + new PokemonSpecies(SpeciesId.VAPOREON, 1, false, false, false, "Bubble Jet Pokémon", PokemonType.WATER, null, 1, 29, AbilityId.WATER_ABSORB, AbilityId.NONE, AbilityId.HYDRATION, 525, 130, 65, 60, 110, 95, 65, 45, 50, 184, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.JOLTEON, 1, false, false, false, "Lightning Pokémon", PokemonType.ELECTRIC, null, 0.8, 24.5, AbilityId.VOLT_ABSORB, AbilityId.NONE, AbilityId.QUICK_FEET, 525, 65, 65, 60, 110, 95, 130, 45, 50, 184, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.FLAREON, 1, false, false, false, "Flame Pokémon", PokemonType.FIRE, null, 0.9, 25, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.GUTS, 525, 65, 130, 60, 95, 110, 65, 45, 50, 184, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.PORYGON, 1, false, false, false, "Virtual Pokémon", PokemonType.NORMAL, null, 0.8, 36.5, AbilityId.TRACE, AbilityId.DOWNLOAD, AbilityId.ANALYTIC, 395, 65, 60, 70, 85, 75, 40, 45, 50, 79, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.OMANYTE, 1, false, false, false, "Spiral Pokémon", PokemonType.ROCK, PokemonType.WATER, 0.4, 7.5, AbilityId.SWIFT_SWIM, AbilityId.SHELL_ARMOR, AbilityId.WEAK_ARMOR, 355, 35, 40, 100, 90, 55, 35, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.OMASTAR, 1, false, false, false, "Spiral Pokémon", PokemonType.ROCK, PokemonType.WATER, 1, 35, AbilityId.SWIFT_SWIM, AbilityId.SHELL_ARMOR, AbilityId.WEAK_ARMOR, 495, 70, 60, 125, 115, 70, 55, 45, 50, 173, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.KABUTO, 1, false, false, false, "Shellfish Pokémon", PokemonType.ROCK, PokemonType.WATER, 0.5, 11.5, AbilityId.SWIFT_SWIM, AbilityId.BATTLE_ARMOR, AbilityId.WEAK_ARMOR, 355, 30, 80, 90, 55, 45, 55, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.KABUTOPS, 1, false, false, false, "Shellfish Pokémon", PokemonType.ROCK, PokemonType.WATER, 1.3, 40.5, AbilityId.SWIFT_SWIM, AbilityId.BATTLE_ARMOR, AbilityId.WEAK_ARMOR, 495, 60, 115, 105, 65, 70, 80, 45, 50, 173, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.AERODACTYL, 1, false, false, false, "Fossil Pokémon", PokemonType.ROCK, PokemonType.FLYING, 1.8, 59, AbilityId.ROCK_HEAD, AbilityId.PRESSURE, AbilityId.UNNERVE, 515, 80, 105, 65, 60, 75, 130, 45, 50, 180, GrowthRate.SLOW, 87.5, false, true, + new PokemonForm("Normal", "", PokemonType.ROCK, PokemonType.FLYING, 1.8, 59, AbilityId.ROCK_HEAD, AbilityId.PRESSURE, AbilityId.UNNERVE, 515, 80, 105, 65, 60, 75, 130, 45, 50, 180, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.ROCK, PokemonType.FLYING, 2.1, 79, AbilityId.TOUGH_CLAWS, AbilityId.TOUGH_CLAWS, AbilityId.TOUGH_CLAWS, 615, 80, 135, 85, 70, 95, 150, 45, 50, 180) + ), + new PokemonSpecies(SpeciesId.SNORLAX, 1, false, false, false, "Sleeping Pokémon", PokemonType.NORMAL, null, 2.1, 460, AbilityId.IMMUNITY, AbilityId.THICK_FAT, AbilityId.GLUTTONY, 540, 160, 110, 65, 65, 110, 30, 25, 50, 189, GrowthRate.SLOW, 87.5, false, true, + new PokemonForm("Normal", "", PokemonType.NORMAL, null, 2.1, 460, AbilityId.IMMUNITY, AbilityId.THICK_FAT, AbilityId.GLUTTONY, 540, 160, 110, 65, 65, 110, 30, 25, 50, 189, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.NORMAL, null, 35, 999.9, AbilityId.HARVEST, AbilityId.HARVEST, AbilityId.HARVEST, 640, 210, 135, 70, 90, 115, 20, 25, 50, 189) + ), + new PokemonSpecies(SpeciesId.ARTICUNO, 1, true, false, false, "Freeze Pokémon", PokemonType.ICE, PokemonType.FLYING, 1.7, 55.4, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.SNOW_CLOAK, 580, 90, 85, 100, 95, 125, 85, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.ZAPDOS, 1, true, false, false, "Electric Pokémon", PokemonType.ELECTRIC, PokemonType.FLYING, 1.6, 52.6, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.STATIC, 580, 90, 90, 85, 125, 90, 100, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.MOLTRES, 1, true, false, false, "Flame Pokémon", PokemonType.FIRE, PokemonType.FLYING, 2, 60, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.FLAME_BODY, 580, 90, 100, 90, 125, 85, 90, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.DRATINI, 1, false, false, false, "Dragon Pokémon", PokemonType.DRAGON, null, 1.8, 3.3, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.MARVEL_SCALE, 300, 41, 64, 45, 50, 50, 50, 45, 35, 60, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.DRAGONAIR, 1, false, false, false, "Dragon Pokémon", PokemonType.DRAGON, null, 4, 16.5, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.MARVEL_SCALE, 420, 61, 84, 65, 70, 70, 70, 45, 35, 147, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.DRAGONITE, 1, false, false, false, "Dragon Pokémon", PokemonType.DRAGON, PokemonType.FLYING, 2.2, 210, AbilityId.INNER_FOCUS, AbilityId.NONE, AbilityId.MULTISCALE, 600, 91, 134, 95, 100, 100, 80, 45, 35, 300, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.MEWTWO, 1, false, true, false, "Genetic Pokémon", PokemonType.PSYCHIC, null, 2, 122, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.UNNERVE, 680, 106, 110, 90, 154, 90, 130, 3, 0, 340, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal", "", PokemonType.PSYCHIC, null, 2, 122, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.UNNERVE, 680, 106, 110, 90, 154, 90, 130, 3, 0, 340, false, null, true), + new PokemonForm("Mega X", SpeciesFormKey.MEGA_X, PokemonType.PSYCHIC, PokemonType.FIGHTING, 2.3, 127, AbilityId.STEADFAST, AbilityId.NONE, AbilityId.STEADFAST, 780, 106, 190, 100, 154, 100, 130, 3, 0, 340), + new PokemonForm("Mega Y", SpeciesFormKey.MEGA_Y, PokemonType.PSYCHIC, null, 1.5, 33, AbilityId.INSOMNIA, AbilityId.NONE, AbilityId.INSOMNIA, 780, 106, 150, 70, 194, 120, 140, 3, 0, 340) + ), + new PokemonSpecies(SpeciesId.MEW, 1, false, false, true, "New Species Pokémon", PokemonType.PSYCHIC, null, 0.4, 4, AbilityId.SYNCHRONIZE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 45, 100, 300, GrowthRate.MEDIUM_SLOW, null, false), + new PokemonSpecies(SpeciesId.CHIKORITA, 2, false, false, false, "Leaf Pokémon", PokemonType.GRASS, null, 0.9, 6.4, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.LEAF_GUARD, 318, 45, 49, 65, 49, 65, 45, 45, 70, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.BAYLEEF, 2, false, false, false, "Leaf Pokémon", PokemonType.GRASS, null, 1.2, 15.8, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.LEAF_GUARD, 405, 60, 62, 80, 63, 80, 60, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.MEGANIUM, 2, false, false, false, "Herb Pokémon", PokemonType.GRASS, null, 1.8, 100.5, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.LEAF_GUARD, 525, 80, 82, 100, 83, 100, 80, 45, 70, 263, GrowthRate.MEDIUM_SLOW, 87.5, true), + new PokemonSpecies(SpeciesId.CYNDAQUIL, 2, false, false, false, "Fire Mouse Pokémon", PokemonType.FIRE, null, 0.5, 7.9, AbilityId.BLAZE, AbilityId.NONE, AbilityId.FLASH_FIRE, 309, 39, 52, 43, 60, 50, 65, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.QUILAVA, 2, false, false, false, "Volcano Pokémon", PokemonType.FIRE, null, 0.9, 19, AbilityId.BLAZE, AbilityId.NONE, AbilityId.FLASH_FIRE, 405, 58, 64, 58, 80, 65, 80, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.TYPHLOSION, 2, false, false, false, "Volcano Pokémon", PokemonType.FIRE, null, 1.7, 79.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.FLASH_FIRE, 534, 78, 84, 78, 109, 85, 100, 45, 70, 267, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.TOTODILE, 2, false, false, false, "Big Jaw Pokémon", PokemonType.WATER, null, 0.6, 9.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHEER_FORCE, 314, 50, 65, 64, 44, 48, 43, 45, 70, 63, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.CROCONAW, 2, false, false, false, "Big Jaw Pokémon", PokemonType.WATER, null, 1.1, 25, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHEER_FORCE, 405, 65, 80, 80, 59, 63, 58, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.FERALIGATR, 2, false, false, false, "Big Jaw Pokémon", PokemonType.WATER, null, 2.3, 88.8, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHEER_FORCE, 530, 85, 105, 100, 79, 83, 78, 45, 70, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.SENTRET, 2, false, false, false, "Scout Pokémon", PokemonType.NORMAL, null, 0.8, 6, AbilityId.RUN_AWAY, AbilityId.KEEN_EYE, AbilityId.FRISK, 215, 35, 46, 34, 35, 45, 20, 255, 70, 43, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.FURRET, 2, false, false, false, "Long Body Pokémon", PokemonType.NORMAL, null, 1.8, 32.5, AbilityId.RUN_AWAY, AbilityId.KEEN_EYE, AbilityId.FRISK, 415, 85, 76, 64, 45, 55, 90, 90, 70, 145, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.HOOTHOOT, 2, false, false, false, "Owl Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.7, 21.2, AbilityId.INSOMNIA, AbilityId.KEEN_EYE, AbilityId.TINTED_LENS, 262, 60, 30, 30, 36, 56, 50, 255, 50, 52, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.NOCTOWL, 2, false, false, false, "Owl Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.6, 40.8, AbilityId.INSOMNIA, AbilityId.KEEN_EYE, AbilityId.TINTED_LENS, 452, 100, 50, 50, 86, 96, 70, 90, 50, 158, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.LEDYBA, 2, false, false, false, "Five Star Pokémon", PokemonType.BUG, PokemonType.FLYING, 1, 10.8, AbilityId.SWARM, AbilityId.EARLY_BIRD, AbilityId.RATTLED, 265, 40, 20, 30, 40, 80, 55, 255, 70, 53, GrowthRate.FAST, 50, true), + new PokemonSpecies(SpeciesId.LEDIAN, 2, false, false, false, "Five Star Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.4, 35.6, AbilityId.SWARM, AbilityId.EARLY_BIRD, AbilityId.IRON_FIST, 390, 55, 35, 50, 55, 110, 85, 90, 70, 137, GrowthRate.FAST, 50, true), + new PokemonSpecies(SpeciesId.SPINARAK, 2, false, false, false, "String Spit Pokémon", PokemonType.BUG, PokemonType.POISON, 0.5, 8.5, AbilityId.SWARM, AbilityId.INSOMNIA, AbilityId.SNIPER, 250, 40, 60, 40, 40, 40, 30, 255, 70, 50, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.ARIADOS, 2, false, false, false, "Long Leg Pokémon", PokemonType.BUG, PokemonType.POISON, 1.1, 33.5, AbilityId.SWARM, AbilityId.INSOMNIA, AbilityId.SNIPER, 400, 70, 90, 70, 60, 70, 40, 90, 70, 140, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.CROBAT, 2, false, false, false, "Bat Pokémon", PokemonType.POISON, PokemonType.FLYING, 1.8, 75, AbilityId.INNER_FOCUS, AbilityId.NONE, AbilityId.INFILTRATOR, 535, 85, 90, 80, 70, 80, 130, 90, 50, 268, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CHINCHOU, 2, false, false, false, "Angler Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 0.5, 12, AbilityId.VOLT_ABSORB, AbilityId.ILLUMINATE, AbilityId.WATER_ABSORB, 330, 75, 38, 38, 56, 56, 67, 190, 50, 66, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.LANTURN, 2, false, false, false, "Light Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 1.2, 22.5, AbilityId.VOLT_ABSORB, AbilityId.ILLUMINATE, AbilityId.WATER_ABSORB, 460, 125, 58, 58, 76, 76, 67, 75, 50, 161, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.PICHU, 2, false, false, false, "Tiny Mouse Pokémon", PokemonType.ELECTRIC, null, 0.3, 2, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.4, 2, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), + new PokemonForm("Spiky-Eared", "spiky", PokemonType.ELECTRIC, null, 1.4, 2, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true) + ), + new PokemonSpecies(SpeciesId.CLEFFA, 2, false, false, false, "Star Shape Pokémon", PokemonType.FAIRY, null, 0.3, 3, AbilityId.CUTE_CHARM, AbilityId.MAGIC_GUARD, AbilityId.FRIEND_GUARD, 218, 50, 25, 28, 45, 55, 15, 150, 140, 44, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.IGGLYBUFF, 2, false, false, false, "Balloon Pokémon", PokemonType.NORMAL, PokemonType.FAIRY, 0.3, 1, AbilityId.CUTE_CHARM, AbilityId.COMPETITIVE, AbilityId.FRIEND_GUARD, 210, 90, 30, 15, 40, 20, 15, 170, 50, 42, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.TOGEPI, 2, false, false, false, "Spike Ball Pokémon", PokemonType.FAIRY, null, 0.3, 1.5, AbilityId.HUSTLE, AbilityId.SERENE_GRACE, AbilityId.SUPER_LUCK, 245, 35, 20, 65, 40, 65, 20, 190, 50, 49, GrowthRate.FAST, 87.5, false), + new PokemonSpecies(SpeciesId.TOGETIC, 2, false, false, false, "Happiness Pokémon", PokemonType.FAIRY, PokemonType.FLYING, 0.6, 3.2, AbilityId.HUSTLE, AbilityId.SERENE_GRACE, AbilityId.SUPER_LUCK, 405, 55, 40, 85, 80, 105, 40, 75, 50, 142, GrowthRate.FAST, 87.5, false), + new PokemonSpecies(SpeciesId.NATU, 2, false, false, false, "Tiny Bird Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 0.2, 2, AbilityId.SYNCHRONIZE, AbilityId.EARLY_BIRD, AbilityId.MAGIC_BOUNCE, 320, 40, 50, 45, 70, 45, 70, 190, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.XATU, 2, false, false, false, "Mystic Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 1.5, 15, AbilityId.SYNCHRONIZE, AbilityId.EARLY_BIRD, AbilityId.MAGIC_BOUNCE, 470, 65, 75, 70, 95, 70, 95, 75, 50, 165, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.MAREEP, 2, false, false, false, "Wool Pokémon", PokemonType.ELECTRIC, null, 0.6, 7.8, AbilityId.STATIC, AbilityId.NONE, AbilityId.PLUS, 280, 55, 40, 40, 65, 45, 35, 235, 70, 56, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.FLAAFFY, 2, false, false, false, "Wool Pokémon", PokemonType.ELECTRIC, null, 0.8, 13.3, AbilityId.STATIC, AbilityId.NONE, AbilityId.PLUS, 365, 70, 55, 55, 80, 60, 45, 120, 70, 128, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.AMPHAROS, 2, false, false, false, "Light Pokémon", PokemonType.ELECTRIC, null, 1.4, 61.5, AbilityId.STATIC, AbilityId.NONE, AbilityId.PLUS, 510, 90, 75, 85, 115, 90, 55, 45, 70, 255, GrowthRate.MEDIUM_SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.4, 61.5, AbilityId.STATIC, AbilityId.NONE, AbilityId.PLUS, 510, 90, 75, 85, 115, 90, 55, 45, 70, 255, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.ELECTRIC, PokemonType.DRAGON, 1.4, 61.5, AbilityId.MOLD_BREAKER, AbilityId.NONE, AbilityId.MOLD_BREAKER, 610, 90, 95, 105, 165, 110, 45, 45, 70, 255) + ), + new PokemonSpecies(SpeciesId.BELLOSSOM, 2, false, false, false, "Flower Pokémon", PokemonType.GRASS, null, 0.4, 5.8, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.HEALER, 490, 75, 80, 95, 90, 100, 50, 45, 50, 245, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.MARILL, 2, false, false, false, "Aqua Mouse Pokémon", PokemonType.WATER, PokemonType.FAIRY, 0.4, 8.5, AbilityId.THICK_FAT, AbilityId.HUGE_POWER, AbilityId.SAP_SIPPER, 250, 70, 20, 50, 20, 50, 40, 190, 50, 88, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.AZUMARILL, 2, false, false, false, "Aqua Rabbit Pokémon", PokemonType.WATER, PokemonType.FAIRY, 0.8, 28.5, AbilityId.THICK_FAT, AbilityId.HUGE_POWER, AbilityId.SAP_SIPPER, 420, 100, 50, 80, 60, 80, 50, 75, 50, 210, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.SUDOWOODO, 2, false, false, false, "Imitation Pokémon", PokemonType.ROCK, null, 1.2, 38, AbilityId.STURDY, AbilityId.ROCK_HEAD, AbilityId.RATTLED, 410, 70, 100, 115, 30, 65, 30, 65, 50, 144, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.POLITOED, 2, false, false, false, "Frog Pokémon", PokemonType.WATER, null, 1.1, 33.9, AbilityId.WATER_ABSORB, AbilityId.DAMP, AbilityId.DRIZZLE, 500, 90, 75, 75, 90, 100, 70, 45, 50, 250, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.HOPPIP, 2, false, false, false, "Cottonweed Pokémon", PokemonType.GRASS, PokemonType.FLYING, 0.4, 0.5, AbilityId.CHLOROPHYLL, AbilityId.LEAF_GUARD, AbilityId.INFILTRATOR, 250, 35, 35, 40, 35, 55, 50, 255, 70, 50, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SKIPLOOM, 2, false, false, false, "Cottonweed Pokémon", PokemonType.GRASS, PokemonType.FLYING, 0.6, 1, AbilityId.CHLOROPHYLL, AbilityId.LEAF_GUARD, AbilityId.INFILTRATOR, 340, 55, 45, 50, 45, 65, 80, 120, 70, 119, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.JUMPLUFF, 2, false, false, false, "Cottonweed Pokémon", PokemonType.GRASS, PokemonType.FLYING, 0.8, 3, AbilityId.CHLOROPHYLL, AbilityId.LEAF_GUARD, AbilityId.INFILTRATOR, 460, 75, 55, 70, 55, 95, 110, 45, 70, 230, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.AIPOM, 2, false, false, false, "Long Tail Pokémon", PokemonType.NORMAL, null, 0.8, 11.5, AbilityId.RUN_AWAY, AbilityId.PICKUP, AbilityId.SKILL_LINK, 360, 55, 70, 55, 40, 55, 85, 45, 70, 72, GrowthRate.FAST, 50, true), + new PokemonSpecies(SpeciesId.SUNKERN, 2, false, false, false, "Seed Pokémon", PokemonType.GRASS, null, 0.3, 1.8, AbilityId.CHLOROPHYLL, AbilityId.SOLAR_POWER, AbilityId.EARLY_BIRD, 180, 30, 30, 30, 30, 30, 30, 235, 70, 36, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SUNFLORA, 2, false, false, false, "Sun Pokémon", PokemonType.GRASS, null, 0.8, 8.5, AbilityId.CHLOROPHYLL, AbilityId.SOLAR_POWER, AbilityId.EARLY_BIRD, 425, 75, 75, 55, 105, 85, 30, 120, 70, 149, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.YANMA, 2, false, false, false, "Clear Wing Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.2, 38, AbilityId.SPEED_BOOST, AbilityId.COMPOUND_EYES, AbilityId.FRISK, 390, 65, 65, 45, 75, 45, 95, 75, 70, 78, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.WOOPER, 2, false, false, false, "Water Fish Pokémon", PokemonType.WATER, PokemonType.GROUND, 0.4, 8.5, AbilityId.DAMP, AbilityId.WATER_ABSORB, AbilityId.UNAWARE, 210, 55, 45, 45, 25, 25, 15, 255, 50, 42, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.QUAGSIRE, 2, false, false, false, "Water Fish Pokémon", PokemonType.WATER, PokemonType.GROUND, 1.4, 75, AbilityId.DAMP, AbilityId.WATER_ABSORB, AbilityId.UNAWARE, 430, 95, 85, 85, 65, 65, 35, 90, 50, 151, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.ESPEON, 2, false, false, false, "Sun Pokémon", PokemonType.PSYCHIC, null, 0.9, 26.5, AbilityId.SYNCHRONIZE, AbilityId.NONE, AbilityId.MAGIC_BOUNCE, 525, 65, 65, 60, 130, 95, 110, 45, 50, 184, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.UMBREON, 2, false, false, false, "Moonlight Pokémon", PokemonType.DARK, null, 1, 27, AbilityId.SYNCHRONIZE, AbilityId.NONE, AbilityId.INNER_FOCUS, 525, 95, 65, 110, 60, 130, 65, 45, 35, 184, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.MURKROW, 2, false, false, false, "Darkness Pokémon", PokemonType.DARK, PokemonType.FLYING, 0.5, 2.1, AbilityId.INSOMNIA, AbilityId.SUPER_LUCK, AbilityId.PRANKSTER, 405, 60, 85, 42, 85, 42, 91, 30, 35, 81, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.SLOWKING, 2, false, false, false, "Royal Pokémon", PokemonType.WATER, PokemonType.PSYCHIC, 2, 79.5, AbilityId.OBLIVIOUS, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 490, 95, 75, 80, 100, 110, 30, 70, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MISDREAVUS, 2, false, false, false, "Screech Pokémon", PokemonType.GHOST, null, 0.7, 1, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 435, 60, 60, 60, 85, 85, 85, 45, 35, 87, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.UNOWN, 2, false, false, false, "Symbol Pokémon", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, GrowthRate.MEDIUM_FAST, null, false, false, + new PokemonForm("A", "a", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("B", "b", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("C", "c", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("D", "d", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("E", "e", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("F", "f", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("G", "g", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("H", "h", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("I", "i", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("J", "j", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("K", "k", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("L", "l", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("M", "m", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("N", "n", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("O", "o", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("P", "p", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("Q", "q", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("R", "r", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("S", "s", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("T", "t", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("U", "u", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("V", "v", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("W", "w", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("X", "x", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("Y", "y", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("Z", "z", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("!", "exclamation", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), + new PokemonForm("?", "question", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true) + ), + new PokemonSpecies(SpeciesId.WOBBUFFET, 2, false, false, false, "Patient Pokémon", PokemonType.PSYCHIC, null, 1.3, 28.5, AbilityId.SHADOW_TAG, AbilityId.NONE, AbilityId.TELEPATHY, 405, 190, 33, 58, 33, 58, 33, 45, 50, 142, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.GIRAFARIG, 2, false, false, false, "Long Neck Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 1.5, 41.5, AbilityId.INNER_FOCUS, AbilityId.EARLY_BIRD, AbilityId.SAP_SIPPER, 455, 70, 80, 65, 90, 65, 85, 60, 70, 159, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.PINECO, 2, false, false, false, "Bagworm Pokémon", PokemonType.BUG, null, 0.6, 7.2, AbilityId.STURDY, AbilityId.NONE, AbilityId.OVERCOAT, 290, 50, 65, 90, 35, 35, 15, 190, 70, 58, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.FORRETRESS, 2, false, false, false, "Bagworm Pokémon", PokemonType.BUG, PokemonType.STEEL, 1.2, 125.8, AbilityId.STURDY, AbilityId.NONE, AbilityId.OVERCOAT, 465, 75, 90, 140, 60, 60, 40, 75, 70, 163, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DUNSPARCE, 2, false, false, false, "Land Snake Pokémon", PokemonType.NORMAL, null, 1.5, 14, AbilityId.SERENE_GRACE, AbilityId.RUN_AWAY, AbilityId.RATTLED, 415, 100, 70, 70, 65, 65, 45, 190, 50, 145, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GLIGAR, 2, false, false, false, "Fly Scorpion Pokémon", PokemonType.GROUND, PokemonType.FLYING, 1.1, 64.8, AbilityId.HYPER_CUTTER, AbilityId.SAND_VEIL, AbilityId.IMMUNITY, 430, 65, 75, 105, 35, 65, 85, 60, 70, 86, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.STEELIX, 2, false, false, false, "Iron Snake Pokémon", PokemonType.STEEL, PokemonType.GROUND, 9.2, 400, AbilityId.ROCK_HEAD, AbilityId.STURDY, AbilityId.SHEER_FORCE, 510, 75, 85, 200, 55, 65, 30, 25, 50, 179, GrowthRate.MEDIUM_FAST, 50, true, true, + new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.GROUND, 9.2, 400, AbilityId.ROCK_HEAD, AbilityId.STURDY, AbilityId.SHEER_FORCE, 510, 75, 85, 200, 55, 65, 30, 25, 50, 179, true, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.STEEL, PokemonType.GROUND, 10.5, 740, AbilityId.SAND_FORCE, AbilityId.SAND_FORCE, AbilityId.SAND_FORCE, 610, 75, 125, 230, 55, 95, 30, 25, 50, 179, true) + ), + new PokemonSpecies(SpeciesId.SNUBBULL, 2, false, false, false, "Fairy Pokémon", PokemonType.FAIRY, null, 0.6, 7.8, AbilityId.INTIMIDATE, AbilityId.RUN_AWAY, AbilityId.RATTLED, 300, 60, 80, 50, 40, 40, 30, 190, 70, 60, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.GRANBULL, 2, false, false, false, "Fairy Pokémon", PokemonType.FAIRY, null, 1.4, 48.7, AbilityId.INTIMIDATE, AbilityId.QUICK_FEET, AbilityId.RATTLED, 450, 90, 120, 75, 60, 60, 45, 75, 70, 158, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.QWILFISH, 2, false, false, false, "Balloon Pokémon", PokemonType.WATER, PokemonType.POISON, 0.5, 3.9, AbilityId.POISON_POINT, AbilityId.SWIFT_SWIM, AbilityId.INTIMIDATE, 440, 65, 95, 85, 55, 55, 85, 45, 50, 88, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SCIZOR, 2, false, false, false, "Pincer Pokémon", PokemonType.BUG, PokemonType.STEEL, 1.8, 118, AbilityId.SWARM, AbilityId.TECHNICIAN, AbilityId.LIGHT_METAL, 500, 70, 130, 100, 55, 80, 65, 25, 50, 175, GrowthRate.MEDIUM_FAST, 50, true, true, + new PokemonForm("Normal", "", PokemonType.BUG, PokemonType.STEEL, 1.8, 118, AbilityId.SWARM, AbilityId.TECHNICIAN, AbilityId.LIGHT_METAL, 500, 70, 130, 100, 55, 80, 65, 25, 50, 175, true, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.BUG, PokemonType.STEEL, 2, 125, AbilityId.TECHNICIAN, AbilityId.TECHNICIAN, AbilityId.TECHNICIAN, 600, 70, 150, 140, 65, 100, 75, 25, 50, 175, true) + ), + new PokemonSpecies(SpeciesId.SHUCKLE, 2, false, false, false, "Mold Pokémon", PokemonType.BUG, PokemonType.ROCK, 0.6, 20.5, AbilityId.STURDY, AbilityId.GLUTTONY, AbilityId.CONTRARY, 505, 20, 10, 230, 10, 230, 5, 190, 50, 177, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.HERACROSS, 2, false, false, false, "Single Horn Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 1.5, 54, AbilityId.SWARM, AbilityId.GUTS, AbilityId.MOXIE, 500, 80, 125, 75, 40, 95, 85, 45, 50, 175, GrowthRate.SLOW, 50, true, true, + new PokemonForm("Normal", "", PokemonType.BUG, PokemonType.FIGHTING, 1.5, 54, AbilityId.SWARM, AbilityId.GUTS, AbilityId.MOXIE, 500, 80, 125, 75, 40, 95, 85, 45, 50, 175, true, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.BUG, PokemonType.FIGHTING, 1.7, 62.5, AbilityId.SKILL_LINK, AbilityId.SKILL_LINK, AbilityId.SKILL_LINK, 600, 80, 185, 115, 40, 105, 75, 45, 50, 175, true) + ), + new PokemonSpecies(SpeciesId.SNEASEL, 2, false, false, false, "Sharp Claw Pokémon", PokemonType.DARK, PokemonType.ICE, 0.9, 28, AbilityId.INNER_FOCUS, AbilityId.KEEN_EYE, AbilityId.PICKPOCKET, 430, 55, 95, 55, 35, 75, 115, 60, 35, 86, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.TEDDIURSA, 2, false, false, false, "Little Bear Pokémon", PokemonType.NORMAL, null, 0.6, 8.8, AbilityId.PICKUP, AbilityId.QUICK_FEET, AbilityId.HONEY_GATHER, 330, 60, 80, 50, 50, 50, 40, 120, 70, 66, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.URSARING, 2, false, false, false, "Hibernator Pokémon", PokemonType.NORMAL, null, 1.8, 125.8, AbilityId.GUTS, AbilityId.QUICK_FEET, AbilityId.UNNERVE, 500, 90, 130, 75, 75, 75, 55, 60, 70, 175, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.SLUGMA, 2, false, false, false, "Lava Pokémon", PokemonType.FIRE, null, 0.7, 35, AbilityId.MAGMA_ARMOR, AbilityId.FLAME_BODY, AbilityId.WEAK_ARMOR, 250, 40, 40, 40, 70, 40, 20, 190, 70, 50, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MAGCARGO, 2, false, false, false, "Lava Pokémon", PokemonType.FIRE, PokemonType.ROCK, 0.8, 55, AbilityId.MAGMA_ARMOR, AbilityId.FLAME_BODY, AbilityId.WEAK_ARMOR, 430, 60, 50, 120, 90, 80, 30, 75, 70, 151, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SWINUB, 2, false, false, false, "Pig Pokémon", PokemonType.ICE, PokemonType.GROUND, 0.4, 6.5, AbilityId.OBLIVIOUS, AbilityId.SNOW_CLOAK, AbilityId.THICK_FAT, 250, 50, 50, 40, 30, 30, 50, 225, 50, 50, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.PILOSWINE, 2, false, false, false, "Swine Pokémon", PokemonType.ICE, PokemonType.GROUND, 1.1, 55.8, AbilityId.OBLIVIOUS, AbilityId.SNOW_CLOAK, AbilityId.THICK_FAT, 450, 100, 100, 80, 60, 60, 50, 75, 50, 158, GrowthRate.SLOW, 50, true), + new PokemonSpecies(SpeciesId.CORSOLA, 2, false, false, false, "Coral Pokémon", PokemonType.WATER, PokemonType.ROCK, 0.6, 5, AbilityId.HUSTLE, AbilityId.NATURAL_CURE, AbilityId.REGENERATOR, 410, 65, 55, 95, 65, 95, 35, 60, 50, 144, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.REMORAID, 2, false, false, false, "Jet Pokémon", PokemonType.WATER, null, 0.6, 12, AbilityId.HUSTLE, AbilityId.SNIPER, AbilityId.MOODY, 300, 35, 65, 35, 65, 35, 65, 190, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.OCTILLERY, 2, false, false, false, "Jet Pokémon", PokemonType.WATER, null, 0.9, 28.5, AbilityId.SUCTION_CUPS, AbilityId.SNIPER, AbilityId.MOODY, 480, 75, 105, 75, 105, 75, 45, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.DELIBIRD, 2, false, false, false, "Delivery Pokémon", PokemonType.ICE, PokemonType.FLYING, 0.9, 16, AbilityId.VITAL_SPIRIT, AbilityId.HUSTLE, AbilityId.INSOMNIA, 330, 45, 55, 45, 65, 45, 75, 45, 50, 116, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.MANTINE, 2, false, false, false, "Kite Pokémon", PokemonType.WATER, PokemonType.FLYING, 2.1, 220, AbilityId.SWIFT_SWIM, AbilityId.WATER_ABSORB, AbilityId.WATER_VEIL, 485, 85, 40, 70, 80, 140, 70, 25, 50, 170, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.SKARMORY, 2, false, false, false, "Armor Bird Pokémon", PokemonType.STEEL, PokemonType.FLYING, 1.7, 50.5, AbilityId.KEEN_EYE, AbilityId.STURDY, AbilityId.WEAK_ARMOR, 465, 65, 80, 140, 40, 70, 70, 25, 50, 163, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.HOUNDOUR, 2, false, false, false, "Dark Pokémon", PokemonType.DARK, PokemonType.FIRE, 0.6, 10.8, AbilityId.EARLY_BIRD, AbilityId.FLASH_FIRE, AbilityId.UNNERVE, 330, 45, 60, 30, 80, 50, 65, 120, 35, 66, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.HOUNDOOM, 2, false, false, false, "Dark Pokémon", PokemonType.DARK, PokemonType.FIRE, 1.4, 35, AbilityId.EARLY_BIRD, AbilityId.FLASH_FIRE, AbilityId.UNNERVE, 500, 75, 90, 50, 110, 80, 95, 45, 35, 175, GrowthRate.SLOW, 50, true, true, + new PokemonForm("Normal", "", PokemonType.DARK, PokemonType.FIRE, 1.4, 35, AbilityId.EARLY_BIRD, AbilityId.FLASH_FIRE, AbilityId.UNNERVE, 500, 75, 90, 50, 110, 80, 95, 45, 35, 175, true, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DARK, PokemonType.FIRE, 1.9, 49.5, AbilityId.SOLAR_POWER, AbilityId.SOLAR_POWER, AbilityId.SOLAR_POWER, 600, 75, 90, 90, 140, 90, 115, 45, 35, 175, true) + ), + new PokemonSpecies(SpeciesId.KINGDRA, 2, false, false, false, "Dragon Pokémon", PokemonType.WATER, PokemonType.DRAGON, 1.8, 152, AbilityId.SWIFT_SWIM, AbilityId.SNIPER, AbilityId.DAMP, 540, 75, 95, 95, 95, 95, 85, 45, 50, 270, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PHANPY, 2, false, false, false, "Long Nose Pokémon", PokemonType.GROUND, null, 0.5, 33.5, AbilityId.PICKUP, AbilityId.NONE, AbilityId.SAND_VEIL, 330, 90, 60, 60, 40, 40, 40, 120, 70, 66, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DONPHAN, 2, false, false, false, "Armor Pokémon", PokemonType.GROUND, null, 1.1, 120, AbilityId.STURDY, AbilityId.NONE, AbilityId.SAND_VEIL, 500, 90, 120, 120, 60, 60, 50, 60, 70, 175, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.PORYGON2, 2, false, false, false, "Virtual Pokémon", PokemonType.NORMAL, null, 0.6, 32.5, AbilityId.TRACE, AbilityId.DOWNLOAD, AbilityId.ANALYTIC, 515, 85, 80, 90, 105, 95, 60, 45, 50, 180, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.STANTLER, 2, false, false, false, "Big Horn Pokémon", PokemonType.NORMAL, null, 1.4, 71.2, AbilityId.INTIMIDATE, AbilityId.FRISK, AbilityId.SAP_SIPPER, 465, 73, 95, 62, 85, 65, 85, 45, 70, 163, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.SMEARGLE, 2, false, false, false, "Painter Pokémon", PokemonType.NORMAL, null, 1.2, 58, AbilityId.OWN_TEMPO, AbilityId.TECHNICIAN, AbilityId.MOODY, 250, 55, 20, 35, 20, 45, 75, 45, 70, 88, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.TYROGUE, 2, false, false, false, "Scuffle Pokémon", PokemonType.FIGHTING, null, 0.7, 21, AbilityId.GUTS, AbilityId.STEADFAST, AbilityId.VITAL_SPIRIT, 210, 35, 35, 35, 35, 35, 35, 75, 50, 42, GrowthRate.MEDIUM_FAST, 100, false), + new PokemonSpecies(SpeciesId.HITMONTOP, 2, false, false, false, "Handstand Pokémon", PokemonType.FIGHTING, null, 1.4, 48, AbilityId.INTIMIDATE, AbilityId.TECHNICIAN, AbilityId.STEADFAST, 455, 50, 95, 95, 35, 110, 70, 45, 50, 159, GrowthRate.MEDIUM_FAST, 100, false), + new PokemonSpecies(SpeciesId.SMOOCHUM, 2, false, false, false, "Kiss Pokémon", PokemonType.ICE, PokemonType.PSYCHIC, 0.4, 6, AbilityId.OBLIVIOUS, AbilityId.FOREWARN, AbilityId.HYDRATION, 305, 45, 30, 15, 85, 65, 65, 45, 50, 61, GrowthRate.MEDIUM_FAST, 0, false), + new PokemonSpecies(SpeciesId.ELEKID, 2, false, false, false, "Electric Pokémon", PokemonType.ELECTRIC, null, 0.6, 23.5, AbilityId.STATIC, AbilityId.NONE, AbilityId.VITAL_SPIRIT, 360, 45, 63, 37, 65, 55, 95, 45, 50, 72, GrowthRate.MEDIUM_FAST, 75, false), + new PokemonSpecies(SpeciesId.MAGBY, 2, false, false, false, "Live Coal Pokémon", PokemonType.FIRE, null, 0.7, 21.4, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.VITAL_SPIRIT, 365, 45, 75, 37, 70, 55, 83, 45, 50, 73, GrowthRate.MEDIUM_FAST, 75, false), + new PokemonSpecies(SpeciesId.MILTANK, 2, false, false, false, "Milk Cow Pokémon", PokemonType.NORMAL, null, 1.2, 75.5, AbilityId.THICK_FAT, AbilityId.SCRAPPY, AbilityId.SAP_SIPPER, 490, 95, 80, 105, 40, 70, 100, 45, 50, 172, GrowthRate.SLOW, 0, false), + new PokemonSpecies(SpeciesId.BLISSEY, 2, false, false, false, "Happiness Pokémon", PokemonType.NORMAL, null, 1.5, 46.8, AbilityId.NATURAL_CURE, AbilityId.SERENE_GRACE, AbilityId.HEALER, 540, 255, 10, 10, 75, 135, 55, 30, 140, 608, GrowthRate.FAST, 0, false), + new PokemonSpecies(SpeciesId.RAIKOU, 2, true, false, false, "Thunder Pokémon", PokemonType.ELECTRIC, null, 1.9, 178, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.INNER_FOCUS, 580, 90, 85, 75, 115, 100, 115, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.ENTEI, 2, true, false, false, "Volcano Pokémon", PokemonType.FIRE, null, 2.1, 198, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.INNER_FOCUS, 580, 115, 115, 85, 90, 75, 100, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.SUICUNE, 2, true, false, false, "Aurora Pokémon", PokemonType.WATER, null, 2, 187, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.INNER_FOCUS, 580, 100, 75, 115, 90, 115, 85, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.LARVITAR, 2, false, false, false, "Rock Skin Pokémon", PokemonType.ROCK, PokemonType.GROUND, 0.6, 72, AbilityId.GUTS, AbilityId.NONE, AbilityId.SAND_VEIL, 300, 50, 64, 50, 45, 50, 41, 45, 35, 60, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.PUPITAR, 2, false, false, false, "Hard Shell Pokémon", PokemonType.ROCK, PokemonType.GROUND, 1.2, 152, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.SHED_SKIN, 410, 70, 84, 70, 65, 70, 51, 45, 35, 144, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.TYRANITAR, 2, false, false, false, "Armor Pokémon", PokemonType.ROCK, PokemonType.DARK, 2, 202, AbilityId.SAND_STREAM, AbilityId.NONE, AbilityId.UNNERVE, 600, 100, 134, 110, 95, 100, 61, 45, 35, 300, GrowthRate.SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.ROCK, PokemonType.DARK, 2, 202, AbilityId.SAND_STREAM, AbilityId.NONE, AbilityId.UNNERVE, 600, 100, 134, 110, 95, 100, 61, 45, 35, 300, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.ROCK, PokemonType.DARK, 2.5, 255, AbilityId.SAND_STREAM, AbilityId.NONE, AbilityId.SAND_STREAM, 700, 100, 164, 150, 95, 120, 71, 45, 35, 300) + ), + new PokemonSpecies(SpeciesId.LUGIA, 2, false, true, false, "Diving Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 5.2, 216, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.MULTISCALE, 680, 106, 90, 130, 90, 154, 110, 3, 0, 340, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.HO_OH, 2, false, true, false, "Rainbow Pokémon", PokemonType.FIRE, PokemonType.FLYING, 3.8, 199, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.REGENERATOR, 680, 106, 130, 90, 110, 154, 90, 3, 0, 340, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.CELEBI, 2, false, false, true, "Time Travel Pokémon", PokemonType.PSYCHIC, PokemonType.GRASS, 0.6, 5, AbilityId.NATURAL_CURE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 45, 100, 300, GrowthRate.MEDIUM_SLOW, null, false), + new PokemonSpecies(SpeciesId.TREECKO, 3, false, false, false, "Wood Gecko Pokémon", PokemonType.GRASS, null, 0.5, 5, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.UNBURDEN, 310, 40, 45, 35, 65, 55, 70, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.GROVYLE, 3, false, false, false, "Wood Gecko Pokémon", PokemonType.GRASS, null, 0.9, 21.6, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.UNBURDEN, 405, 50, 65, 45, 85, 65, 95, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.SCEPTILE, 3, false, false, false, "Forest Pokémon", PokemonType.GRASS, null, 1.7, 52.2, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.UNBURDEN, 530, 70, 85, 65, 105, 85, 120, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, + new PokemonForm("Normal", "", PokemonType.GRASS, null, 1.7, 52.2, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.UNBURDEN, 530, 70, 85, 65, 105, 85, 120, 45, 50, 265, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.GRASS, PokemonType.DRAGON, 1.9, 55.2, AbilityId.LIGHTNING_ROD, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 630, 70, 110, 75, 145, 85, 145, 45, 50, 265) + ), + new PokemonSpecies(SpeciesId.TORCHIC, 3, false, false, false, "Chick Pokémon", PokemonType.FIRE, null, 0.4, 2.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SPEED_BOOST, 310, 45, 60, 40, 70, 50, 45, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, true), + new PokemonSpecies(SpeciesId.COMBUSKEN, 3, false, false, false, "Young Fowl Pokémon", PokemonType.FIRE, PokemonType.FIGHTING, 0.9, 19.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SPEED_BOOST, 405, 60, 85, 60, 85, 60, 55, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, true), + new PokemonSpecies(SpeciesId.BLAZIKEN, 3, false, false, false, "Blaze Pokémon", PokemonType.FIRE, PokemonType.FIGHTING, 1.9, 52, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SPEED_BOOST, 530, 80, 120, 70, 110, 70, 80, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, true, true, + new PokemonForm("Normal", "", PokemonType.FIRE, PokemonType.FIGHTING, 1.9, 52, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SPEED_BOOST, 530, 80, 120, 70, 110, 70, 80, 45, 50, 265, true, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.FIRE, PokemonType.FIGHTING, 1.9, 52, AbilityId.SPEED_BOOST, AbilityId.NONE, AbilityId.SPEED_BOOST, 630, 80, 160, 80, 130, 80, 100, 45, 50, 265, true) + ), + new PokemonSpecies(SpeciesId.MUDKIP, 3, false, false, false, "Mud Fish Pokémon", PokemonType.WATER, null, 0.4, 7.6, AbilityId.TORRENT, AbilityId.NONE, AbilityId.DAMP, 310, 50, 70, 50, 50, 50, 40, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.MARSHTOMP, 3, false, false, false, "Mud Fish Pokémon", PokemonType.WATER, PokemonType.GROUND, 0.7, 28, AbilityId.TORRENT, AbilityId.NONE, AbilityId.DAMP, 405, 70, 85, 70, 60, 70, 50, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.SWAMPERT, 3, false, false, false, "Mud Fish Pokémon", PokemonType.WATER, PokemonType.GROUND, 1.5, 81.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.DAMP, 535, 100, 110, 90, 85, 90, 60, 45, 50, 268, GrowthRate.MEDIUM_SLOW, 87.5, false, true, + new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.GROUND, 1.5, 81.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.DAMP, 535, 100, 110, 90, 85, 90, 60, 45, 50, 268, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.WATER, PokemonType.GROUND, 1.9, 102, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.SWIFT_SWIM, 635, 100, 150, 110, 95, 110, 70, 45, 50, 268) + ), + new PokemonSpecies(SpeciesId.POOCHYENA, 3, false, false, false, "Bite Pokémon", PokemonType.DARK, null, 0.5, 13.6, AbilityId.RUN_AWAY, AbilityId.QUICK_FEET, AbilityId.RATTLED, 220, 35, 55, 35, 30, 30, 35, 255, 70, 56, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MIGHTYENA, 3, false, false, false, "Bite Pokémon", PokemonType.DARK, null, 1, 37, AbilityId.INTIMIDATE, AbilityId.QUICK_FEET, AbilityId.MOXIE, 420, 70, 90, 70, 60, 60, 70, 127, 70, 147, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ZIGZAGOON, 3, false, false, false, "Tiny Raccoon Pokémon", PokemonType.NORMAL, null, 0.4, 17.5, AbilityId.PICKUP, AbilityId.GLUTTONY, AbilityId.QUICK_FEET, 240, 38, 30, 41, 30, 41, 60, 255, 50, 56, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.LINOONE, 3, false, false, false, "Rushing Pokémon", PokemonType.NORMAL, null, 0.5, 32.5, AbilityId.PICKUP, AbilityId.GLUTTONY, AbilityId.QUICK_FEET, 420, 78, 70, 61, 50, 61, 100, 90, 50, 147, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.WURMPLE, 3, false, false, false, "Worm Pokémon", PokemonType.BUG, null, 0.3, 3.6, AbilityId.SHIELD_DUST, AbilityId.NONE, AbilityId.RUN_AWAY, 195, 45, 45, 35, 20, 30, 20, 255, 70, 56, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SILCOON, 3, false, false, false, "Cocoon Pokémon", PokemonType.BUG, null, 0.6, 10, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.SHED_SKIN, 205, 50, 35, 55, 25, 25, 15, 120, 70, 72, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BEAUTIFLY, 3, false, false, false, "Butterfly Pokémon", PokemonType.BUG, PokemonType.FLYING, 1, 28.4, AbilityId.SWARM, AbilityId.NONE, AbilityId.RIVALRY, 395, 60, 70, 50, 100, 50, 65, 45, 70, 198, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.CASCOON, 3, false, false, false, "Cocoon Pokémon", PokemonType.BUG, null, 0.7, 11.5, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.SHED_SKIN, 205, 50, 35, 55, 25, 25, 15, 120, 70, 72, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DUSTOX, 3, false, false, false, "Poison Moth Pokémon", PokemonType.BUG, PokemonType.POISON, 1.2, 31.6, AbilityId.SHIELD_DUST, AbilityId.NONE, AbilityId.COMPOUND_EYES, 385, 60, 50, 70, 50, 90, 65, 45, 70, 193, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.LOTAD, 3, false, false, false, "Water Weed Pokémon", PokemonType.WATER, PokemonType.GRASS, 0.5, 2.6, AbilityId.SWIFT_SWIM, AbilityId.RAIN_DISH, AbilityId.OWN_TEMPO, 220, 40, 30, 30, 40, 50, 30, 255, 50, 44, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.LOMBRE, 3, false, false, false, "Jolly Pokémon", PokemonType.WATER, PokemonType.GRASS, 1.2, 32.5, AbilityId.SWIFT_SWIM, AbilityId.RAIN_DISH, AbilityId.OWN_TEMPO, 340, 60, 50, 50, 60, 70, 50, 120, 50, 119, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.LUDICOLO, 3, false, false, false, "Carefree Pokémon", PokemonType.WATER, PokemonType.GRASS, 1.5, 55, AbilityId.SWIFT_SWIM, AbilityId.RAIN_DISH, AbilityId.OWN_TEMPO, 480, 80, 70, 70, 90, 100, 70, 45, 50, 240, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.SEEDOT, 3, false, false, false, "Acorn Pokémon", PokemonType.GRASS, null, 0.5, 4, AbilityId.CHLOROPHYLL, AbilityId.EARLY_BIRD, AbilityId.PICKPOCKET, 220, 40, 40, 50, 30, 30, 30, 255, 50, 44, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.NUZLEAF, 3, false, false, false, "Wily Pokémon", PokemonType.GRASS, PokemonType.DARK, 1, 28, AbilityId.CHLOROPHYLL, AbilityId.EARLY_BIRD, AbilityId.PICKPOCKET, 340, 70, 70, 40, 60, 40, 60, 120, 50, 119, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.SHIFTRY, 3, false, false, false, "Wicked Pokémon", PokemonType.GRASS, PokemonType.DARK, 1.3, 59.6, AbilityId.CHLOROPHYLL, AbilityId.WIND_RIDER, AbilityId.PICKPOCKET, 480, 90, 100, 60, 90, 60, 80, 45, 50, 240, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.TAILLOW, 3, false, false, false, "Tiny Swallow Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 2.3, AbilityId.GUTS, AbilityId.NONE, AbilityId.SCRAPPY, 270, 40, 55, 30, 30, 30, 85, 200, 70, 54, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SWELLOW, 3, false, false, false, "Swallow Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.7, 19.8, AbilityId.GUTS, AbilityId.NONE, AbilityId.SCRAPPY, 455, 60, 85, 60, 75, 50, 125, 45, 70, 159, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.WINGULL, 3, false, false, false, "Seagull Pokémon", PokemonType.WATER, PokemonType.FLYING, 0.6, 9.5, AbilityId.KEEN_EYE, AbilityId.HYDRATION, AbilityId.RAIN_DISH, 270, 40, 30, 30, 55, 30, 85, 190, 50, 54, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PELIPPER, 3, false, false, false, "Water Bird Pokémon", PokemonType.WATER, PokemonType.FLYING, 1.2, 28, AbilityId.KEEN_EYE, AbilityId.DRIZZLE, AbilityId.RAIN_DISH, 440, 60, 50, 100, 95, 70, 65, 45, 50, 154, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.RALTS, 3, false, false, false, "Feeling Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 0.4, 6.6, AbilityId.SYNCHRONIZE, AbilityId.TRACE, AbilityId.TELEPATHY, 198, 28, 25, 25, 45, 35, 40, 235, 35, 40, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.KIRLIA, 3, false, false, false, "Emotion Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 0.8, 20.2, AbilityId.SYNCHRONIZE, AbilityId.TRACE, AbilityId.TELEPATHY, 278, 38, 35, 35, 65, 55, 50, 120, 35, 97, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.GARDEVOIR, 3, false, false, false, "Embrace Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.6, 48.4, AbilityId.SYNCHRONIZE, AbilityId.TRACE, AbilityId.TELEPATHY, 518, 68, 65, 65, 125, 115, 80, 45, 35, 259, GrowthRate.SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.6, 48.4, AbilityId.SYNCHRONIZE, AbilityId.TRACE, AbilityId.TELEPATHY, 518, 68, 65, 65, 125, 115, 80, 45, 35, 259, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.PSYCHIC, PokemonType.FAIRY, 1.6, 48.4, AbilityId.PIXILATE, AbilityId.PIXILATE, AbilityId.PIXILATE, 618, 68, 85, 65, 165, 135, 100, 45, 35, 259) + ), + new PokemonSpecies(SpeciesId.SURSKIT, 3, false, false, false, "Pond Skater Pokémon", PokemonType.BUG, PokemonType.WATER, 0.5, 1.7, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.RAIN_DISH, 269, 40, 30, 32, 50, 52, 65, 200, 70, 54, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MASQUERAIN, 3, false, false, false, "Eyeball Pokémon", PokemonType.BUG, PokemonType.FLYING, 0.8, 3.6, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.UNNERVE, 454, 70, 60, 62, 100, 82, 80, 75, 70, 159, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SHROOMISH, 3, false, false, false, "Mushroom Pokémon", PokemonType.GRASS, null, 0.4, 4.5, AbilityId.EFFECT_SPORE, AbilityId.POISON_HEAL, AbilityId.QUICK_FEET, 295, 60, 40, 60, 40, 60, 35, 255, 70, 59, GrowthRate.FLUCTUATING, 50, false), + new PokemonSpecies(SpeciesId.BRELOOM, 3, false, false, false, "Mushroom Pokémon", PokemonType.GRASS, PokemonType.FIGHTING, 1.2, 39.2, AbilityId.EFFECT_SPORE, AbilityId.POISON_HEAL, AbilityId.TECHNICIAN, 460, 60, 130, 80, 60, 60, 70, 90, 70, 161, GrowthRate.FLUCTUATING, 50, false), + new PokemonSpecies(SpeciesId.SLAKOTH, 3, false, false, false, "Slacker Pokémon", PokemonType.NORMAL, null, 0.8, 24, AbilityId.TRUANT, AbilityId.NONE, AbilityId.STALL, 280, 60, 60, 60, 35, 35, 30, 255, 70, 56, GrowthRate.SLOW, 50, false), //Custom Hidden + new PokemonSpecies(SpeciesId.VIGOROTH, 3, false, false, false, "Wild Monkey Pokémon", PokemonType.NORMAL, null, 1.4, 46.5, AbilityId.VITAL_SPIRIT, AbilityId.NONE, AbilityId.INSOMNIA, 440, 80, 80, 80, 55, 55, 90, 120, 70, 154, GrowthRate.SLOW, 50, false), //Custom Hidden + new PokemonSpecies(SpeciesId.SLAKING, 3, false, false, false, "Lazy Pokémon", PokemonType.NORMAL, null, 2, 130.5, AbilityId.TRUANT, AbilityId.NONE, AbilityId.STALL, 670, 150, 160, 100, 95, 65, 100, 45, 70, 285, GrowthRate.SLOW, 50, false), //Custom Hidden + new PokemonSpecies(SpeciesId.NINCADA, 3, false, false, false, "Trainee Pokémon", PokemonType.BUG, PokemonType.GROUND, 0.5, 5.5, AbilityId.COMPOUND_EYES, AbilityId.NONE, AbilityId.RUN_AWAY, 266, 31, 45, 90, 30, 30, 40, 255, 50, 53, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(SpeciesId.NINJASK, 3, false, false, false, "Ninja Pokémon", PokemonType.BUG, PokemonType.FLYING, 0.8, 12, AbilityId.SPEED_BOOST, AbilityId.NONE, AbilityId.INFILTRATOR, 456, 61, 90, 45, 50, 50, 160, 120, 50, 160, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(SpeciesId.SHEDINJA, 3, false, false, false, "Shed Pokémon", PokemonType.BUG, PokemonType.GHOST, 0.8, 1.2, AbilityId.WONDER_GUARD, AbilityId.NONE, AbilityId.NONE, 236, 1, 90, 45, 30, 30, 40, 45, 50, 83, GrowthRate.ERRATIC, null, false), + new PokemonSpecies(SpeciesId.WHISMUR, 3, false, false, false, "Whisper Pokémon", PokemonType.NORMAL, null, 0.6, 16.3, AbilityId.SOUNDPROOF, AbilityId.NONE, AbilityId.RATTLED, 240, 64, 51, 23, 51, 23, 28, 190, 50, 48, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.LOUDRED, 3, false, false, false, "Big Voice Pokémon", PokemonType.NORMAL, null, 1, 40.5, AbilityId.SOUNDPROOF, AbilityId.NONE, AbilityId.SCRAPPY, 360, 84, 71, 43, 71, 43, 48, 120, 50, 126, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.EXPLOUD, 3, false, false, false, "Loud Noise Pokémon", PokemonType.NORMAL, null, 1.5, 84, AbilityId.SOUNDPROOF, AbilityId.NONE, AbilityId.SCRAPPY, 490, 104, 91, 63, 91, 73, 68, 45, 50, 245, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.MAKUHITA, 3, false, false, false, "Guts Pokémon", PokemonType.FIGHTING, null, 1, 86.4, AbilityId.THICK_FAT, AbilityId.GUTS, AbilityId.SHEER_FORCE, 237, 72, 60, 30, 20, 30, 25, 180, 70, 47, GrowthRate.FLUCTUATING, 75, false), + new PokemonSpecies(SpeciesId.HARIYAMA, 3, false, false, false, "Arm Thrust Pokémon", PokemonType.FIGHTING, null, 2.3, 253.8, AbilityId.THICK_FAT, AbilityId.GUTS, AbilityId.SHEER_FORCE, 474, 144, 120, 60, 40, 60, 50, 200, 70, 166, GrowthRate.FLUCTUATING, 75, false), + new PokemonSpecies(SpeciesId.AZURILL, 3, false, false, false, "Polka Dot Pokémon", PokemonType.NORMAL, PokemonType.FAIRY, 0.2, 2, AbilityId.THICK_FAT, AbilityId.HUGE_POWER, AbilityId.SAP_SIPPER, 190, 50, 20, 40, 20, 40, 20, 150, 50, 38, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.NOSEPASS, 3, false, false, false, "Compass Pokémon", PokemonType.ROCK, null, 1, 97, AbilityId.STURDY, AbilityId.MAGNET_PULL, AbilityId.SAND_FORCE, 375, 30, 45, 135, 45, 90, 30, 255, 70, 75, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SKITTY, 3, false, false, false, "Kitten Pokémon", PokemonType.NORMAL, null, 0.6, 11, AbilityId.CUTE_CHARM, AbilityId.NORMALIZE, AbilityId.WONDER_SKIN, 260, 50, 45, 45, 35, 35, 50, 255, 70, 52, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.DELCATTY, 3, false, false, false, "Prim Pokémon", PokemonType.NORMAL, null, 1.1, 32.6, AbilityId.CUTE_CHARM, AbilityId.NORMALIZE, AbilityId.WONDER_SKIN, 400, 70, 65, 65, 55, 55, 90, 60, 70, 140, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.SABLEYE, 3, false, false, false, "Darkness Pokémon", PokemonType.DARK, PokemonType.GHOST, 0.5, 11, AbilityId.KEEN_EYE, AbilityId.STALL, AbilityId.PRANKSTER, 380, 50, 75, 75, 65, 65, 50, 45, 35, 133, GrowthRate.MEDIUM_SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.DARK, PokemonType.GHOST, 0.5, 11, AbilityId.KEEN_EYE, AbilityId.STALL, AbilityId.PRANKSTER, 380, 50, 75, 75, 65, 65, 50, 45, 35, 133, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DARK, PokemonType.GHOST, 0.5, 161, AbilityId.MAGIC_BOUNCE, AbilityId.MAGIC_BOUNCE, AbilityId.MAGIC_BOUNCE, 480, 50, 85, 125, 85, 115, 20, 45, 35, 133) + ), + new PokemonSpecies(SpeciesId.MAWILE, 3, false, false, false, "Deceiver Pokémon", PokemonType.STEEL, PokemonType.FAIRY, 0.6, 11.5, AbilityId.HYPER_CUTTER, AbilityId.INTIMIDATE, AbilityId.SHEER_FORCE, 380, 50, 85, 85, 55, 55, 50, 45, 50, 133, GrowthRate.FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.FAIRY, 0.6, 11.5, AbilityId.HYPER_CUTTER, AbilityId.INTIMIDATE, AbilityId.SHEER_FORCE, 380, 50, 85, 85, 55, 55, 50, 45, 50, 133, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.STEEL, PokemonType.FAIRY, 1, 23.5, AbilityId.HUGE_POWER, AbilityId.HUGE_POWER, AbilityId.HUGE_POWER, 480, 50, 105, 125, 55, 95, 50, 45, 50, 133) + ), + new PokemonSpecies(SpeciesId.ARON, 3, false, false, false, "Iron Armor Pokémon", PokemonType.STEEL, PokemonType.ROCK, 0.4, 60, AbilityId.STURDY, AbilityId.ROCK_HEAD, AbilityId.HEAVY_METAL, 330, 50, 70, 100, 40, 40, 30, 180, 35, 66, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.LAIRON, 3, false, false, false, "Iron Armor Pokémon", PokemonType.STEEL, PokemonType.ROCK, 0.9, 120, AbilityId.STURDY, AbilityId.ROCK_HEAD, AbilityId.HEAVY_METAL, 430, 60, 90, 140, 50, 50, 40, 90, 35, 151, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.AGGRON, 3, false, false, false, "Iron Armor Pokémon", PokemonType.STEEL, PokemonType.ROCK, 2.1, 360, AbilityId.STURDY, AbilityId.ROCK_HEAD, AbilityId.HEAVY_METAL, 530, 70, 110, 180, 60, 60, 50, 45, 35, 265, GrowthRate.SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.ROCK, 2.1, 360, AbilityId.STURDY, AbilityId.ROCK_HEAD, AbilityId.HEAVY_METAL, 530, 70, 110, 180, 60, 60, 50, 45, 35, 265, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.STEEL, null, 2.2, 395, AbilityId.FILTER, AbilityId.FILTER, AbilityId.FILTER, 630, 70, 140, 230, 60, 80, 50, 45, 35, 265) + ), + new PokemonSpecies(SpeciesId.MEDITITE, 3, false, false, false, "Meditate Pokémon", PokemonType.FIGHTING, PokemonType.PSYCHIC, 0.6, 11.2, AbilityId.PURE_POWER, AbilityId.NONE, AbilityId.TELEPATHY, 280, 30, 40, 55, 40, 55, 60, 180, 70, 56, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.MEDICHAM, 3, false, false, false, "Meditate Pokémon", PokemonType.FIGHTING, PokemonType.PSYCHIC, 1.3, 31.5, AbilityId.PURE_POWER, AbilityId.NONE, AbilityId.TELEPATHY, 410, 60, 60, 75, 60, 75, 80, 90, 70, 144, GrowthRate.MEDIUM_FAST, 50, true, true, + new PokemonForm("Normal", "", PokemonType.FIGHTING, PokemonType.PSYCHIC, 1.3, 31.5, AbilityId.PURE_POWER, AbilityId.NONE, AbilityId.TELEPATHY, 410, 60, 60, 75, 60, 75, 80, 90, 70, 144, true, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.FIGHTING, PokemonType.PSYCHIC, 1.3, 31.5, AbilityId.PURE_POWER, AbilityId.NONE, AbilityId.PURE_POWER, 510, 60, 100, 85, 80, 85, 100, 90, 70, 144, true) + ), + new PokemonSpecies(SpeciesId.ELECTRIKE, 3, false, false, false, "Lightning Pokémon", PokemonType.ELECTRIC, null, 0.6, 15.2, AbilityId.STATIC, AbilityId.LIGHTNING_ROD, AbilityId.MINUS, 295, 40, 45, 40, 65, 40, 65, 120, 50, 59, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.MANECTRIC, 3, false, false, false, "Discharge Pokémon", PokemonType.ELECTRIC, null, 1.5, 40.2, AbilityId.STATIC, AbilityId.LIGHTNING_ROD, AbilityId.MINUS, 475, 70, 75, 60, 105, 60, 105, 45, 50, 166, GrowthRate.SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.5, 40.2, AbilityId.STATIC, AbilityId.LIGHTNING_ROD, AbilityId.MINUS, 475, 70, 75, 60, 105, 60, 105, 45, 50, 166, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.ELECTRIC, null, 1.8, 44, AbilityId.INTIMIDATE, AbilityId.INTIMIDATE, AbilityId.INTIMIDATE, 575, 70, 75, 80, 135, 80, 135, 45, 50, 166) + ), + new PokemonSpecies(SpeciesId.PLUSLE, 3, false, false, false, "Cheering Pokémon", PokemonType.ELECTRIC, null, 0.4, 4.2, AbilityId.PLUS, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 405, 60, 50, 40, 85, 75, 95, 200, 70, 142, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MINUN, 3, false, false, false, "Cheering Pokémon", PokemonType.ELECTRIC, null, 0.4, 4.2, AbilityId.MINUS, AbilityId.NONE, AbilityId.VOLT_ABSORB, 405, 60, 40, 50, 75, 85, 95, 200, 70, 142, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.VOLBEAT, 3, false, false, false, "Firefly Pokémon", PokemonType.BUG, null, 0.7, 17.7, AbilityId.ILLUMINATE, AbilityId.SWARM, AbilityId.PRANKSTER, 430, 65, 73, 75, 47, 85, 85, 150, 70, 151, GrowthRate.ERRATIC, 100, false), + new PokemonSpecies(SpeciesId.ILLUMISE, 3, false, false, false, "Firefly Pokémon", PokemonType.BUG, null, 0.6, 17.7, AbilityId.OBLIVIOUS, AbilityId.TINTED_LENS, AbilityId.PRANKSTER, 430, 65, 47, 75, 73, 85, 85, 150, 70, 151, GrowthRate.FLUCTUATING, 0, false), + new PokemonSpecies(SpeciesId.ROSELIA, 3, false, false, false, "Thorn Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.3, 2, AbilityId.NATURAL_CURE, AbilityId.POISON_POINT, AbilityId.LEAF_GUARD, 400, 50, 60, 45, 100, 80, 65, 150, 50, 140, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.GULPIN, 3, false, false, false, "Stomach Pokémon", PokemonType.POISON, null, 0.4, 10.3, AbilityId.LIQUID_OOZE, AbilityId.STICKY_HOLD, AbilityId.GLUTTONY, 302, 70, 43, 53, 43, 53, 40, 225, 70, 60, GrowthRate.FLUCTUATING, 50, true), + new PokemonSpecies(SpeciesId.SWALOT, 3, false, false, false, "Poison Bag Pokémon", PokemonType.POISON, null, 1.7, 80, AbilityId.LIQUID_OOZE, AbilityId.STICKY_HOLD, AbilityId.GLUTTONY, 467, 100, 73, 83, 73, 83, 55, 75, 70, 163, GrowthRate.FLUCTUATING, 50, true), + new PokemonSpecies(SpeciesId.CARVANHA, 3, false, false, false, "Savage Pokémon", PokemonType.WATER, PokemonType.DARK, 0.8, 20.8, AbilityId.ROUGH_SKIN, AbilityId.NONE, AbilityId.SPEED_BOOST, 305, 45, 90, 20, 65, 20, 65, 225, 35, 61, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.SHARPEDO, 3, false, false, false, "Brutal Pokémon", PokemonType.WATER, PokemonType.DARK, 1.8, 88.8, AbilityId.ROUGH_SKIN, AbilityId.NONE, AbilityId.SPEED_BOOST, 460, 70, 120, 40, 95, 40, 95, 60, 35, 161, GrowthRate.SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.DARK, 1.8, 88.8, AbilityId.ROUGH_SKIN, AbilityId.NONE, AbilityId.SPEED_BOOST, 460, 70, 120, 40, 95, 40, 95, 60, 35, 161, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.WATER, PokemonType.DARK, 2.5, 130.3, AbilityId.STRONG_JAW, AbilityId.NONE, AbilityId.STRONG_JAW, 560, 70, 140, 70, 110, 65, 105, 60, 35, 161) + ), + new PokemonSpecies(SpeciesId.WAILMER, 3, false, false, false, "Ball Whale Pokémon", PokemonType.WATER, null, 2, 130, AbilityId.WATER_VEIL, AbilityId.OBLIVIOUS, AbilityId.PRESSURE, 400, 130, 70, 35, 70, 35, 60, 125, 50, 80, GrowthRate.FLUCTUATING, 50, false), + new PokemonSpecies(SpeciesId.WAILORD, 3, false, false, false, "Float Whale Pokémon", PokemonType.WATER, null, 14.5, 398, AbilityId.WATER_VEIL, AbilityId.OBLIVIOUS, AbilityId.PRESSURE, 500, 170, 90, 45, 90, 45, 60, 60, 50, 175, GrowthRate.FLUCTUATING, 50, false), + new PokemonSpecies(SpeciesId.NUMEL, 3, false, false, false, "Numb Pokémon", PokemonType.FIRE, PokemonType.GROUND, 0.7, 24, AbilityId.OBLIVIOUS, AbilityId.SIMPLE, AbilityId.OWN_TEMPO, 305, 60, 60, 40, 65, 45, 35, 255, 70, 61, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.CAMERUPT, 3, false, false, false, "Eruption Pokémon", PokemonType.FIRE, PokemonType.GROUND, 1.9, 220, AbilityId.MAGMA_ARMOR, AbilityId.SOLID_ROCK, AbilityId.ANGER_POINT, 460, 70, 100, 70, 105, 75, 40, 150, 70, 161, GrowthRate.MEDIUM_FAST, 50, true, true, + new PokemonForm("Normal", "", PokemonType.FIRE, PokemonType.GROUND, 1.9, 220, AbilityId.MAGMA_ARMOR, AbilityId.SOLID_ROCK, AbilityId.ANGER_POINT, 460, 70, 100, 70, 105, 75, 40, 150, 70, 161, true, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.FIRE, PokemonType.GROUND, 2.5, 320.5, AbilityId.SHEER_FORCE, AbilityId.SHEER_FORCE, AbilityId.SHEER_FORCE, 560, 70, 120, 100, 145, 105, 20, 150, 70, 161) + ), + new PokemonSpecies(SpeciesId.TORKOAL, 3, false, false, false, "Coal Pokémon", PokemonType.FIRE, null, 0.5, 80.4, AbilityId.WHITE_SMOKE, AbilityId.DROUGHT, AbilityId.SHELL_ARMOR, 470, 70, 85, 140, 85, 70, 20, 90, 50, 165, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SPOINK, 3, false, false, false, "Bounce Pokémon", PokemonType.PSYCHIC, null, 0.7, 30.6, AbilityId.THICK_FAT, AbilityId.OWN_TEMPO, AbilityId.GLUTTONY, 330, 60, 25, 35, 70, 80, 60, 255, 70, 66, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.GRUMPIG, 3, false, false, false, "Manipulate Pokémon", PokemonType.PSYCHIC, null, 0.9, 71.5, AbilityId.THICK_FAT, AbilityId.OWN_TEMPO, AbilityId.GLUTTONY, 470, 80, 45, 65, 90, 110, 80, 60, 70, 165, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.SPINDA, 3, false, false, false, "Spot Panda Pokémon", PokemonType.NORMAL, null, 1.1, 5, AbilityId.OWN_TEMPO, AbilityId.TANGLED_FEET, AbilityId.CONTRARY, 360, 60, 60, 60, 60, 60, 60, 255, 70, 126, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.TRAPINCH, 3, false, false, false, "Ant Pit Pokémon", PokemonType.GROUND, null, 0.7, 15, AbilityId.HYPER_CUTTER, AbilityId.ARENA_TRAP, AbilityId.SHEER_FORCE, 290, 45, 100, 45, 45, 45, 10, 255, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.VIBRAVA, 3, false, false, false, "Vibration Pokémon", PokemonType.GROUND, PokemonType.DRAGON, 1.1, 15.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 340, 50, 70, 50, 50, 50, 70, 120, 50, 119, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.FLYGON, 3, false, false, false, "Mystic Pokémon", PokemonType.GROUND, PokemonType.DRAGON, 2, 82, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 520, 80, 100, 80, 80, 80, 100, 45, 50, 260, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.CACNEA, 3, false, false, false, "Cactus Pokémon", PokemonType.GRASS, null, 0.4, 51.3, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.WATER_ABSORB, 335, 50, 85, 40, 85, 40, 35, 190, 35, 67, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.CACTURNE, 3, false, false, false, "Scarecrow Pokémon", PokemonType.GRASS, PokemonType.DARK, 1.3, 77.4, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.WATER_ABSORB, 475, 70, 115, 60, 115, 60, 55, 60, 35, 166, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.SWABLU, 3, false, false, false, "Cotton Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.4, 1.2, AbilityId.NATURAL_CURE, AbilityId.NONE, AbilityId.CLOUD_NINE, 310, 45, 40, 60, 40, 75, 50, 255, 50, 62, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(SpeciesId.ALTARIA, 3, false, false, false, "Humming Pokémon", PokemonType.DRAGON, PokemonType.FLYING, 1.1, 20.6, AbilityId.NATURAL_CURE, AbilityId.NONE, AbilityId.CLOUD_NINE, 490, 75, 70, 90, 70, 105, 80, 45, 50, 172, GrowthRate.ERRATIC, 50, false, true, + new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.FLYING, 1.1, 20.6, AbilityId.NATURAL_CURE, AbilityId.NONE, AbilityId.CLOUD_NINE, 490, 75, 70, 90, 70, 105, 80, 45, 50, 172, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.FAIRY, 1.5, 20.6, AbilityId.PIXILATE, AbilityId.NONE, AbilityId.PIXILATE, 590, 75, 110, 110, 110, 105, 80, 45, 50, 172) + ), + new PokemonSpecies(SpeciesId.ZANGOOSE, 3, false, false, false, "Cat Ferret Pokémon", PokemonType.NORMAL, null, 1.3, 40.3, AbilityId.IMMUNITY, AbilityId.NONE, AbilityId.TOXIC_BOOST, 458, 73, 115, 60, 60, 60, 90, 90, 70, 160, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(SpeciesId.SEVIPER, 3, false, false, false, "Fang Snake Pokémon", PokemonType.POISON, null, 2.7, 52.5, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.INFILTRATOR, 458, 73, 100, 60, 100, 60, 65, 90, 70, 160, GrowthRate.FLUCTUATING, 50, false), + new PokemonSpecies(SpeciesId.LUNATONE, 3, false, false, false, "Meteorite Pokémon", PokemonType.ROCK, PokemonType.PSYCHIC, 1, 168, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 460, 90, 55, 65, 95, 85, 70, 45, 50, 161, GrowthRate.FAST, null, false), + new PokemonSpecies(SpeciesId.SOLROCK, 3, false, false, false, "Meteorite Pokémon", PokemonType.ROCK, PokemonType.PSYCHIC, 1.2, 154, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 460, 90, 95, 85, 55, 65, 70, 45, 50, 161, GrowthRate.FAST, null, false), + new PokemonSpecies(SpeciesId.BARBOACH, 3, false, false, false, "Whiskers Pokémon", PokemonType.WATER, PokemonType.GROUND, 0.4, 1.9, AbilityId.OBLIVIOUS, AbilityId.ANTICIPATION, AbilityId.HYDRATION, 288, 50, 48, 43, 46, 41, 60, 190, 50, 58, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.WHISCASH, 3, false, false, false, "Whiskers Pokémon", PokemonType.WATER, PokemonType.GROUND, 0.9, 23.6, AbilityId.OBLIVIOUS, AbilityId.ANTICIPATION, AbilityId.HYDRATION, 468, 110, 78, 73, 76, 71, 60, 75, 50, 164, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CORPHISH, 3, false, false, false, "Ruffian Pokémon", PokemonType.WATER, null, 0.6, 11.5, AbilityId.HYPER_CUTTER, AbilityId.SHELL_ARMOR, AbilityId.ADAPTABILITY, 308, 43, 80, 65, 50, 35, 35, 205, 50, 62, GrowthRate.FLUCTUATING, 50, false), + new PokemonSpecies(SpeciesId.CRAWDAUNT, 3, false, false, false, "Rogue Pokémon", PokemonType.WATER, PokemonType.DARK, 1.1, 32.8, AbilityId.HYPER_CUTTER, AbilityId.SHELL_ARMOR, AbilityId.ADAPTABILITY, 468, 63, 120, 85, 90, 55, 55, 155, 50, 164, GrowthRate.FLUCTUATING, 50, false), + new PokemonSpecies(SpeciesId.BALTOY, 3, false, false, false, "Clay Doll Pokémon", PokemonType.GROUND, PokemonType.PSYCHIC, 0.5, 21.5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 300, 40, 40, 55, 40, 70, 55, 255, 50, 60, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.CLAYDOL, 3, false, false, false, "Clay Doll Pokémon", PokemonType.GROUND, PokemonType.PSYCHIC, 1.5, 108, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 500, 60, 70, 105, 70, 120, 75, 90, 50, 175, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.LILEEP, 3, false, false, false, "Sea Lily Pokémon", PokemonType.ROCK, PokemonType.GRASS, 1, 23.8, AbilityId.SUCTION_CUPS, AbilityId.NONE, AbilityId.STORM_DRAIN, 355, 66, 41, 77, 61, 87, 23, 45, 50, 71, GrowthRate.ERRATIC, 87.5, false), + new PokemonSpecies(SpeciesId.CRADILY, 3, false, false, false, "Barnacle Pokémon", PokemonType.ROCK, PokemonType.GRASS, 1.5, 60.4, AbilityId.SUCTION_CUPS, AbilityId.NONE, AbilityId.STORM_DRAIN, 495, 86, 81, 97, 81, 107, 43, 45, 50, 173, GrowthRate.ERRATIC, 87.5, false), + new PokemonSpecies(SpeciesId.ANORITH, 3, false, false, false, "Old Shrimp Pokémon", PokemonType.ROCK, PokemonType.BUG, 0.7, 12.5, AbilityId.BATTLE_ARMOR, AbilityId.NONE, AbilityId.SWIFT_SWIM, 355, 45, 95, 50, 40, 50, 75, 45, 50, 71, GrowthRate.ERRATIC, 87.5, false), + new PokemonSpecies(SpeciesId.ARMALDO, 3, false, false, false, "Plate Pokémon", PokemonType.ROCK, PokemonType.BUG, 1.5, 68.2, AbilityId.BATTLE_ARMOR, AbilityId.NONE, AbilityId.SWIFT_SWIM, 495, 75, 125, 100, 70, 80, 45, 45, 50, 173, GrowthRate.ERRATIC, 87.5, false), + new PokemonSpecies(SpeciesId.FEEBAS, 3, false, false, false, "Fish Pokémon", PokemonType.WATER, null, 0.6, 7.4, AbilityId.SWIFT_SWIM, AbilityId.OBLIVIOUS, AbilityId.ADAPTABILITY, 200, 20, 15, 20, 10, 55, 80, 255, 50, 40, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(SpeciesId.MILOTIC, 3, false, false, false, "Tender Pokémon", PokemonType.WATER, null, 6.2, 162, AbilityId.MARVEL_SCALE, AbilityId.COMPETITIVE, AbilityId.CUTE_CHARM, 540, 95, 60, 79, 100, 125, 81, 60, 50, 189, GrowthRate.ERRATIC, 50, true), + new PokemonSpecies(SpeciesId.CASTFORM, 3, false, false, false, "Weather Pokémon", PokemonType.NORMAL, null, 0.3, 0.8, AbilityId.FORECAST, AbilityId.NONE, AbilityId.NONE, 420, 70, 70, 70, 70, 70, 70, 45, 70, 147, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal Form", "", PokemonType.NORMAL, null, 0.3, 0.8, AbilityId.FORECAST, AbilityId.NONE, AbilityId.NONE, 420, 70, 70, 70, 70, 70, 70, 45, 70, 147, false, null, true), + new PokemonForm("Sunny Form", "sunny", PokemonType.FIRE, null, 0.3, 0.8, AbilityId.FORECAST, AbilityId.NONE, AbilityId.NONE, 420, 70, 70, 70, 70, 70, 70, 45, 70, 147), + new PokemonForm("Rainy Form", "rainy", PokemonType.WATER, null, 0.3, 0.8, AbilityId.FORECAST, AbilityId.NONE, AbilityId.NONE, 420, 70, 70, 70, 70, 70, 70, 45, 70, 147), + new PokemonForm("Snowy Form", "snowy", PokemonType.ICE, null, 0.3, 0.8, AbilityId.FORECAST, AbilityId.NONE, AbilityId.NONE, 420, 70, 70, 70, 70, 70, 70, 45, 70, 147) + ), + new PokemonSpecies(SpeciesId.KECLEON, 3, false, false, false, "Color Swap Pokémon", PokemonType.NORMAL, null, 1, 22, AbilityId.COLOR_CHANGE, AbilityId.NONE, AbilityId.PROTEAN, 440, 60, 90, 70, 60, 120, 40, 200, 70, 154, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SHUPPET, 3, false, false, false, "Puppet Pokémon", PokemonType.GHOST, null, 0.6, 2.3, AbilityId.INSOMNIA, AbilityId.FRISK, AbilityId.CURSED_BODY, 295, 44, 75, 35, 63, 33, 45, 225, 35, 59, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.BANETTE, 3, false, false, false, "Marionette Pokémon", PokemonType.GHOST, null, 1.1, 12.5, AbilityId.INSOMNIA, AbilityId.FRISK, AbilityId.CURSED_BODY, 455, 64, 115, 65, 83, 63, 65, 45, 35, 159, GrowthRate.FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.GHOST, null, 1.1, 12.5, AbilityId.INSOMNIA, AbilityId.FRISK, AbilityId.CURSED_BODY, 455, 64, 115, 65, 83, 63, 65, 45, 35, 159, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.GHOST, null, 1.2, 13, AbilityId.PRANKSTER, AbilityId.PRANKSTER, AbilityId.PRANKSTER, 555, 64, 165, 75, 93, 83, 75, 45, 35, 159) + ), + new PokemonSpecies(SpeciesId.DUSKULL, 3, false, false, false, "Requiem Pokémon", PokemonType.GHOST, null, 0.8, 15, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.FRISK, 295, 20, 40, 90, 30, 90, 25, 190, 35, 59, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.DUSCLOPS, 3, false, false, false, "Beckon Pokémon", PokemonType.GHOST, null, 1.6, 30.6, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.FRISK, 455, 40, 70, 130, 60, 130, 25, 90, 35, 159, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.TROPIUS, 3, false, false, false, "Fruit Pokémon", PokemonType.GRASS, PokemonType.FLYING, 2, 100, AbilityId.CHLOROPHYLL, AbilityId.SOLAR_POWER, AbilityId.HARVEST, 460, 99, 68, 83, 72, 87, 51, 200, 70, 161, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.CHIMECHO, 3, false, false, false, "Wind Chime Pokémon", PokemonType.PSYCHIC, null, 0.6, 1, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 455, 75, 50, 80, 95, 90, 65, 45, 70, 159, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.ABSOL, 3, false, false, false, "Disaster Pokémon", PokemonType.DARK, null, 1.2, 47, AbilityId.PRESSURE, AbilityId.SUPER_LUCK, AbilityId.JUSTIFIED, 465, 65, 130, 60, 75, 60, 75, 30, 35, 163, GrowthRate.MEDIUM_SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.DARK, null, 1.2, 47, AbilityId.PRESSURE, AbilityId.SUPER_LUCK, AbilityId.JUSTIFIED, 465, 65, 130, 60, 75, 60, 75, 30, 35, 163, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DARK, null, 1.2, 49, AbilityId.MAGIC_BOUNCE, AbilityId.MAGIC_BOUNCE, AbilityId.MAGIC_BOUNCE, 565, 65, 150, 60, 115, 60, 115, 30, 35, 163) + ), + new PokemonSpecies(SpeciesId.WYNAUT, 3, false, false, false, "Bright Pokémon", PokemonType.PSYCHIC, null, 0.6, 14, AbilityId.SHADOW_TAG, AbilityId.NONE, AbilityId.TELEPATHY, 260, 95, 23, 48, 23, 48, 23, 125, 50, 52, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SNORUNT, 3, false, false, false, "Snow Hat Pokémon", PokemonType.ICE, null, 0.7, 16.8, AbilityId.INNER_FOCUS, AbilityId.ICE_BODY, AbilityId.MOODY, 300, 50, 50, 50, 50, 50, 50, 190, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GLALIE, 3, false, false, false, "Face Pokémon", PokemonType.ICE, null, 1.5, 256.5, AbilityId.INNER_FOCUS, AbilityId.ICE_BODY, AbilityId.MOODY, 480, 80, 80, 80, 80, 80, 80, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.ICE, null, 1.5, 256.5, AbilityId.INNER_FOCUS, AbilityId.ICE_BODY, AbilityId.MOODY, 480, 80, 80, 80, 80, 80, 80, 75, 50, 168, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.ICE, null, 2.1, 350.2, AbilityId.REFRIGERATE, AbilityId.REFRIGERATE, AbilityId.REFRIGERATE, 580, 80, 120, 80, 120, 80, 100, 75, 50, 168) + ), + new PokemonSpecies(SpeciesId.SPHEAL, 3, false, false, false, "Clap Pokémon", PokemonType.ICE, PokemonType.WATER, 0.8, 39.5, AbilityId.THICK_FAT, AbilityId.ICE_BODY, AbilityId.OBLIVIOUS, 290, 70, 40, 50, 55, 50, 25, 255, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SEALEO, 3, false, false, false, "Ball Roll Pokémon", PokemonType.ICE, PokemonType.WATER, 1.1, 87.6, AbilityId.THICK_FAT, AbilityId.ICE_BODY, AbilityId.OBLIVIOUS, 410, 90, 60, 70, 75, 70, 45, 120, 50, 144, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.WALREIN, 3, false, false, false, "Ice Break Pokémon", PokemonType.ICE, PokemonType.WATER, 1.4, 150.6, AbilityId.THICK_FAT, AbilityId.ICE_BODY, AbilityId.OBLIVIOUS, 530, 110, 80, 90, 95, 90, 65, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.CLAMPERL, 3, false, false, false, "Bivalve Pokémon", PokemonType.WATER, null, 0.4, 52.5, AbilityId.SHELL_ARMOR, AbilityId.NONE, AbilityId.RATTLED, 345, 35, 64, 85, 74, 55, 32, 255, 70, 69, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(SpeciesId.HUNTAIL, 3, false, false, false, "Deep Sea Pokémon", PokemonType.WATER, null, 1.7, 27, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.WATER_VEIL, 485, 55, 104, 105, 94, 75, 52, 60, 70, 170, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(SpeciesId.GOREBYSS, 3, false, false, false, "South Sea Pokémon", PokemonType.WATER, null, 1.8, 22.6, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.HYDRATION, 485, 55, 84, 105, 114, 75, 52, 60, 70, 170, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(SpeciesId.RELICANTH, 3, false, false, false, "Longevity Pokémon", PokemonType.WATER, PokemonType.ROCK, 1, 23.4, AbilityId.SWIFT_SWIM, AbilityId.ROCK_HEAD, AbilityId.STURDY, 485, 100, 90, 130, 45, 65, 55, 25, 50, 170, GrowthRate.SLOW, 87.5, true), + new PokemonSpecies(SpeciesId.LUVDISC, 3, false, false, false, "Rendezvous Pokémon", PokemonType.WATER, null, 0.6, 8.7, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.HYDRATION, 330, 43, 30, 55, 40, 65, 97, 225, 70, 116, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.BAGON, 3, false, false, false, "Rock Head Pokémon", PokemonType.DRAGON, null, 0.6, 42.1, AbilityId.ROCK_HEAD, AbilityId.NONE, AbilityId.SHEER_FORCE, 300, 45, 75, 60, 40, 30, 50, 45, 35, 60, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.SHELGON, 3, false, false, false, "Endurance Pokémon", PokemonType.DRAGON, null, 1.1, 110.5, AbilityId.ROCK_HEAD, AbilityId.NONE, AbilityId.OVERCOAT, 420, 65, 95, 100, 60, 50, 50, 45, 35, 147, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.SALAMENCE, 3, false, false, false, "Dragon Pokémon", PokemonType.DRAGON, PokemonType.FLYING, 1.5, 102.6, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.MOXIE, 600, 95, 135, 80, 110, 80, 100, 45, 35, 300, GrowthRate.SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.FLYING, 1.5, 102.6, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.MOXIE, 600, 95, 135, 80, 110, 80, 100, 45, 35, 300, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.FLYING, 1.8, 112.6, AbilityId.AERILATE, AbilityId.NONE, AbilityId.AERILATE, 700, 95, 145, 130, 120, 90, 120, 45, 35, 300) + ), + new PokemonSpecies(SpeciesId.BELDUM, 3, false, false, false, "Iron Ball Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 0.6, 95.2, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.LIGHT_METAL, 300, 40, 55, 80, 35, 60, 30, 45, 35, 60, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Frigibax + new PokemonSpecies(SpeciesId.METANG, 3, false, false, false, "Iron Claw Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 1.2, 202.5, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.LIGHT_METAL, 420, 60, 75, 100, 55, 80, 50, 25, 35, 147, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Arctibax + new PokemonSpecies(SpeciesId.METAGROSS, 3, false, false, false, "Iron Leg Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 1.6, 550, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.LIGHT_METAL, 600, 80, 135, 130, 95, 90, 70, 10, 35, 300, GrowthRate.SLOW, null, false, true, //Custom Catchrate, matching Baxcalibur + new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.PSYCHIC, 1.6, 550, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.LIGHT_METAL, 600, 80, 135, 130, 95, 90, 70, 3, 35, 300, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.STEEL, PokemonType.PSYCHIC, 2.5, 942.9, AbilityId.TOUGH_CLAWS, AbilityId.NONE, AbilityId.TOUGH_CLAWS, 700, 80, 145, 150, 105, 110, 110, 3, 35, 300) + ), + new PokemonSpecies(SpeciesId.REGIROCK, 3, true, false, false, "Rock Peak Pokémon", PokemonType.ROCK, null, 1.7, 230, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.STURDY, 580, 80, 100, 200, 50, 100, 50, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.REGICE, 3, true, false, false, "Iceberg Pokémon", PokemonType.ICE, null, 1.8, 175, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.ICE_BODY, 580, 80, 50, 100, 100, 200, 50, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.REGISTEEL, 3, true, false, false, "Iron Pokémon", PokemonType.STEEL, null, 1.9, 205, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.LIGHT_METAL, 580, 80, 75, 150, 75, 150, 50, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.LATIAS, 3, true, false, false, "Eon Pokémon", PokemonType.DRAGON, PokemonType.PSYCHIC, 1.4, 40, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 600, 80, 80, 90, 110, 130, 110, 3, 90, 300, GrowthRate.SLOW, 0, false, true, + new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.PSYCHIC, 1.4, 40, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 600, 80, 80, 90, 110, 130, 110, 3, 90, 300, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.PSYCHIC, 1.8, 52, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 700, 80, 100, 120, 140, 150, 110, 3, 90, 300) + ), + new PokemonSpecies(SpeciesId.LATIOS, 3, true, false, false, "Eon Pokémon", PokemonType.DRAGON, PokemonType.PSYCHIC, 2, 60, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 600, 80, 90, 80, 130, 110, 110, 3, 90, 300, GrowthRate.SLOW, 100, false, true, + new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.PSYCHIC, 2, 60, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 600, 80, 90, 80, 130, 110, 110, 3, 90, 300, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.PSYCHIC, 2.3, 70, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 700, 80, 130, 100, 160, 120, 110, 3, 90, 300) + ), + new PokemonSpecies(SpeciesId.KYOGRE, 3, false, true, false, "Sea Basin Pokémon", PokemonType.WATER, null, 4.5, 352, AbilityId.DRIZZLE, AbilityId.NONE, AbilityId.NONE, 670, 100, 100, 90, 150, 140, 90, 3, 0, 335, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal", "", PokemonType.WATER, null, 4.5, 352, AbilityId.DRIZZLE, AbilityId.NONE, AbilityId.NONE, 670, 100, 100, 90, 150, 140, 90, 3, 0, 335, false, null, true), + new PokemonForm("Primal", "primal", PokemonType.WATER, null, 9.8, 430, AbilityId.PRIMORDIAL_SEA, AbilityId.NONE, AbilityId.NONE, 770, 100, 150, 90, 180, 160, 90, 3, 0, 335) + ), + new PokemonSpecies(SpeciesId.GROUDON, 3, false, true, false, "Continent Pokémon", PokemonType.GROUND, null, 3.5, 950, AbilityId.DROUGHT, AbilityId.NONE, AbilityId.NONE, 670, 100, 150, 140, 100, 90, 90, 3, 0, 335, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal", "", PokemonType.GROUND, null, 3.5, 950, AbilityId.DROUGHT, AbilityId.NONE, AbilityId.NONE, 670, 100, 150, 140, 100, 90, 90, 3, 0, 335, false, null, true), + new PokemonForm("Primal", "primal", PokemonType.GROUND, PokemonType.FIRE, 5, 999.7, AbilityId.DESOLATE_LAND, AbilityId.NONE, AbilityId.NONE, 770, 100, 180, 160, 150, 90, 90, 3, 0, 335) + ), + new PokemonSpecies(SpeciesId.RAYQUAZA, 3, false, true, false, "Sky High Pokémon", PokemonType.DRAGON, PokemonType.FLYING, 7, 206.5, AbilityId.AIR_LOCK, AbilityId.NONE, AbilityId.NONE, 680, 105, 150, 90, 150, 90, 95, 3, 0, 340, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.FLYING, 7, 206.5, AbilityId.AIR_LOCK, AbilityId.NONE, AbilityId.NONE, 680, 105, 150, 90, 150, 90, 95, 3, 0, 340, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.FLYING, 10.8, 392, AbilityId.DELTA_STREAM, AbilityId.NONE, AbilityId.NONE, 780, 105, 180, 100, 180, 100, 115, 3, 0, 340) + ), + new PokemonSpecies(SpeciesId.JIRACHI, 3, false, false, true, "Wish Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 0.3, 1.1, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 100, 300, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.DEOXYS, 3, false, false, true, "DNA Pokémon", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 150, 50, 150, 50, 150, 3, 0, 300, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal Forme", "normal", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 150, 50, 150, 50, 150, 3, 0, 300, false, "", true), + new PokemonForm("Attack Forme", "attack", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 180, 20, 180, 20, 150, 3, 0, 300), + new PokemonForm("Defense Forme", "defense", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 70, 160, 70, 160, 90, 3, 0, 300), + new PokemonForm("Speed Forme", "speed", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 95, 90, 95, 90, 180, 3, 0, 300) + ), + new PokemonSpecies(SpeciesId.TURTWIG, 4, false, false, false, "Tiny Leaf Pokémon", PokemonType.GRASS, null, 0.4, 10.2, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.SHELL_ARMOR, 318, 55, 68, 64, 45, 55, 31, 45, 70, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.GROTLE, 4, false, false, false, "Grove Pokémon", PokemonType.GRASS, null, 1.1, 97, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.SHELL_ARMOR, 405, 75, 89, 85, 55, 65, 36, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.TORTERRA, 4, false, false, false, "Continent Pokémon", PokemonType.GRASS, PokemonType.GROUND, 2.2, 310, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.SHELL_ARMOR, 525, 95, 109, 105, 75, 85, 56, 45, 70, 263, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.CHIMCHAR, 4, false, false, false, "Chimp Pokémon", PokemonType.FIRE, null, 0.5, 6.2, AbilityId.BLAZE, AbilityId.NONE, AbilityId.IRON_FIST, 309, 44, 58, 44, 58, 44, 61, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.MONFERNO, 4, false, false, false, "Playful Pokémon", PokemonType.FIRE, PokemonType.FIGHTING, 0.9, 22, AbilityId.BLAZE, AbilityId.NONE, AbilityId.IRON_FIST, 405, 64, 78, 52, 78, 52, 81, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.INFERNAPE, 4, false, false, false, "Flame Pokémon", PokemonType.FIRE, PokemonType.FIGHTING, 1.2, 55, AbilityId.BLAZE, AbilityId.NONE, AbilityId.IRON_FIST, 534, 76, 104, 71, 104, 71, 108, 45, 70, 267, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.PIPLUP, 4, false, false, false, "Penguin Pokémon", PokemonType.WATER, null, 0.4, 5.2, AbilityId.TORRENT, AbilityId.NONE, AbilityId.COMPETITIVE, 314, 53, 51, 53, 61, 56, 40, 45, 70, 63, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.PRINPLUP, 4, false, false, false, "Penguin Pokémon", PokemonType.WATER, null, 0.8, 23, AbilityId.TORRENT, AbilityId.NONE, AbilityId.COMPETITIVE, 405, 64, 66, 68, 81, 76, 50, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.EMPOLEON, 4, false, false, false, "Emperor Pokémon", PokemonType.WATER, PokemonType.STEEL, 1.7, 84.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.COMPETITIVE, 530, 84, 86, 88, 111, 101, 60, 45, 70, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.STARLY, 4, false, false, false, "Starling Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 2, AbilityId.KEEN_EYE, AbilityId.NONE, AbilityId.RECKLESS, 245, 40, 55, 30, 30, 30, 60, 255, 70, 49, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.STARAVIA, 4, false, false, false, "Starling Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 15.5, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.RECKLESS, 340, 55, 75, 50, 40, 40, 80, 120, 70, 119, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.STARAPTOR, 4, false, false, false, "Predator Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.2, 24.9, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.RECKLESS, 485, 85, 120, 70, 50, 60, 100, 45, 70, 243, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.BIDOOF, 4, false, false, false, "Plump Mouse Pokémon", PokemonType.NORMAL, null, 0.5, 20, AbilityId.SIMPLE, AbilityId.UNAWARE, AbilityId.MOODY, 250, 59, 45, 40, 35, 40, 31, 255, 70, 50, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.BIBAREL, 4, false, false, false, "Beaver Pokémon", PokemonType.NORMAL, PokemonType.WATER, 1, 31.5, AbilityId.SIMPLE, AbilityId.UNAWARE, AbilityId.MOODY, 410, 79, 85, 60, 55, 60, 71, 127, 70, 144, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.KRICKETOT, 4, false, false, false, "Cricket Pokémon", PokemonType.BUG, null, 0.3, 2.2, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.RUN_AWAY, 194, 37, 25, 41, 25, 41, 25, 255, 70, 39, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.KRICKETUNE, 4, false, false, false, "Cricket Pokémon", PokemonType.BUG, null, 1, 25.5, AbilityId.SWARM, AbilityId.NONE, AbilityId.TECHNICIAN, 384, 77, 85, 51, 55, 51, 65, 45, 70, 134, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.SHINX, 4, false, false, false, "Flash Pokémon", PokemonType.ELECTRIC, null, 0.5, 9.5, AbilityId.RIVALRY, AbilityId.INTIMIDATE, AbilityId.GUTS, 263, 45, 65, 34, 40, 34, 45, 235, 50, 53, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.LUXIO, 4, false, false, false, "Spark Pokémon", PokemonType.ELECTRIC, null, 0.9, 30.5, AbilityId.RIVALRY, AbilityId.INTIMIDATE, AbilityId.GUTS, 363, 60, 85, 49, 60, 49, 60, 120, 100, 127, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.LUXRAY, 4, false, false, false, "Gleam Eyes Pokémon", PokemonType.ELECTRIC, null, 1.4, 42, AbilityId.RIVALRY, AbilityId.INTIMIDATE, AbilityId.GUTS, 523, 80, 120, 79, 95, 79, 70, 45, 50, 262, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.BUDEW, 4, false, false, false, "Bud Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.2, 1.2, AbilityId.NATURAL_CURE, AbilityId.POISON_POINT, AbilityId.LEAF_GUARD, 280, 40, 30, 35, 50, 70, 55, 255, 50, 56, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.ROSERADE, 4, false, false, false, "Bouquet Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.9, 14.5, AbilityId.NATURAL_CURE, AbilityId.POISON_POINT, AbilityId.TECHNICIAN, 515, 60, 70, 65, 125, 105, 90, 75, 50, 258, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.CRANIDOS, 4, false, false, false, "Head Butt Pokémon", PokemonType.ROCK, null, 0.9, 31.5, AbilityId.MOLD_BREAKER, AbilityId.NONE, AbilityId.SHEER_FORCE, 350, 67, 125, 40, 30, 30, 58, 45, 70, 70, GrowthRate.ERRATIC, 87.5, false), + new PokemonSpecies(SpeciesId.RAMPARDOS, 4, false, false, false, "Head Butt Pokémon", PokemonType.ROCK, null, 1.6, 102.5, AbilityId.MOLD_BREAKER, AbilityId.NONE, AbilityId.SHEER_FORCE, 495, 97, 165, 60, 65, 50, 58, 45, 70, 173, GrowthRate.ERRATIC, 87.5, false), + new PokemonSpecies(SpeciesId.SHIELDON, 4, false, false, false, "Shield Pokémon", PokemonType.ROCK, PokemonType.STEEL, 0.5, 57, AbilityId.STURDY, AbilityId.NONE, AbilityId.SOUNDPROOF, 350, 30, 42, 118, 42, 88, 30, 45, 70, 70, GrowthRate.ERRATIC, 87.5, false), + new PokemonSpecies(SpeciesId.BASTIODON, 4, false, false, false, "Shield Pokémon", PokemonType.ROCK, PokemonType.STEEL, 1.3, 149.5, AbilityId.STURDY, AbilityId.NONE, AbilityId.SOUNDPROOF, 495, 60, 52, 168, 47, 138, 30, 45, 70, 173, GrowthRate.ERRATIC, 87.5, false), + new PokemonSpecies(SpeciesId.BURMY, 4, false, false, false, "Bagworm Pokémon", PokemonType.BUG, null, 0.2, 3.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.OVERCOAT, 224, 40, 29, 45, 29, 45, 36, 120, 70, 45, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Plant Cloak", "plant", PokemonType.BUG, null, 0.2, 3.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.OVERCOAT, 224, 40, 29, 45, 29, 45, 36, 120, 70, 45, false, null, true), + new PokemonForm("Sandy Cloak", "sandy", PokemonType.BUG, null, 0.2, 3.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.OVERCOAT, 224, 40, 29, 45, 29, 45, 36, 120, 70, 45, false, null, true), + new PokemonForm("Trash Cloak", "trash", PokemonType.BUG, null, 0.2, 3.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.OVERCOAT, 224, 40, 29, 45, 29, 45, 36, 120, 70, 45, false, null, true) + ), + new PokemonSpecies(SpeciesId.WORMADAM, 4, false, false, false, "Bagworm Pokémon", PokemonType.BUG, PokemonType.GRASS, 0.5, 6.5, AbilityId.ANTICIPATION, AbilityId.NONE, AbilityId.OVERCOAT, 424, 60, 59, 85, 79, 105, 36, 45, 70, 148, GrowthRate.MEDIUM_FAST, 0, false, false, + new PokemonForm("Plant Cloak", "plant", PokemonType.BUG, PokemonType.GRASS, 0.5, 6.5, AbilityId.ANTICIPATION, AbilityId.NONE, AbilityId.OVERCOAT, 424, 60, 59, 85, 79, 105, 36, 45, 70, 148, false, null, true), + new PokemonForm("Sandy Cloak", "sandy", PokemonType.BUG, PokemonType.GROUND, 0.5, 6.5, AbilityId.ANTICIPATION, AbilityId.NONE, AbilityId.OVERCOAT, 424, 60, 79, 105, 59, 85, 36, 45, 70, 148, false, null, true), + new PokemonForm("Trash Cloak", "trash", PokemonType.BUG, PokemonType.STEEL, 0.5, 6.5, AbilityId.ANTICIPATION, AbilityId.NONE, AbilityId.OVERCOAT, 424, 60, 69, 95, 69, 95, 36, 45, 70, 148, false, null, true) + ), + new PokemonSpecies(SpeciesId.MOTHIM, 4, false, false, false, "Moth Pokémon", PokemonType.BUG, PokemonType.FLYING, 0.9, 23.3, AbilityId.SWARM, AbilityId.NONE, AbilityId.TINTED_LENS, 424, 70, 94, 50, 94, 50, 66, 45, 70, 148, GrowthRate.MEDIUM_FAST, 100, false), + new PokemonSpecies(SpeciesId.COMBEE, 4, false, false, false, "Tiny Bee Pokémon", PokemonType.BUG, PokemonType.FLYING, 0.3, 5.5, AbilityId.HONEY_GATHER, AbilityId.NONE, AbilityId.HUSTLE, 244, 30, 30, 42, 30, 42, 70, 120, 50, 49, GrowthRate.MEDIUM_SLOW, 87.5, true), + new PokemonSpecies(SpeciesId.VESPIQUEN, 4, false, false, false, "Beehive Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.2, 38.5, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.UNNERVE, 474, 70, 80, 102, 80, 102, 40, 45, 50, 166, GrowthRate.MEDIUM_SLOW, 0, false), + new PokemonSpecies(SpeciesId.PACHIRISU, 4, false, false, false, "EleSquirrel Pokémon", PokemonType.ELECTRIC, null, 0.4, 3.9, AbilityId.RUN_AWAY, AbilityId.PICKUP, AbilityId.VOLT_ABSORB, 405, 60, 45, 70, 45, 90, 95, 200, 100, 142, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.BUIZEL, 4, false, false, false, "Sea Weasel Pokémon", PokemonType.WATER, null, 0.7, 29.5, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.WATER_VEIL, 330, 55, 65, 35, 60, 30, 85, 190, 70, 66, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.FLOATZEL, 4, false, false, false, "Sea Weasel Pokémon", PokemonType.WATER, null, 1.1, 33.5, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.WATER_VEIL, 495, 85, 105, 55, 85, 50, 115, 75, 70, 173, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.CHERUBI, 4, false, false, false, "Cherry Pokémon", PokemonType.GRASS, null, 0.4, 3.3, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.NONE, 275, 45, 35, 45, 62, 53, 35, 190, 50, 55, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CHERRIM, 4, false, false, false, "Blossom Pokémon", PokemonType.GRASS, null, 0.5, 9.3, AbilityId.FLOWER_GIFT, AbilityId.NONE, AbilityId.NONE, 450, 70, 60, 70, 87, 78, 85, 75, 50, 158, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Overcast Form", "overcast", PokemonType.GRASS, null, 0.5, 9.3, AbilityId.FLOWER_GIFT, AbilityId.NONE, AbilityId.NONE, 450, 70, 60, 70, 87, 78, 85, 75, 50, 158, false, null, true), + new PokemonForm("Sunshine Form", "sunshine", PokemonType.GRASS, null, 0.5, 9.3, AbilityId.FLOWER_GIFT, AbilityId.NONE, AbilityId.NONE, 450, 70, 60, 70, 87, 78, 85, 75, 50, 158) + ), + new PokemonSpecies(SpeciesId.SHELLOS, 4, false, false, false, "Sea Slug Pokémon", PokemonType.WATER, null, 0.3, 6.3, AbilityId.STICKY_HOLD, AbilityId.STORM_DRAIN, AbilityId.SAND_FORCE, 325, 76, 48, 48, 57, 62, 34, 190, 50, 65, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("East Sea", "east", PokemonType.WATER, null, 0.3, 6.3, AbilityId.STICKY_HOLD, AbilityId.STORM_DRAIN, AbilityId.SAND_FORCE, 325, 76, 48, 48, 57, 62, 34, 190, 50, 65, false, null, true), + new PokemonForm("West Sea", "west", PokemonType.WATER, null, 0.3, 6.3, AbilityId.STICKY_HOLD, AbilityId.STORM_DRAIN, AbilityId.SAND_FORCE, 325, 76, 48, 48, 57, 62, 34, 190, 50, 65, false, null, true) + ), + new PokemonSpecies(SpeciesId.GASTRODON, 4, false, false, false, "Sea Slug Pokémon", PokemonType.WATER, PokemonType.GROUND, 0.9, 29.9, AbilityId.STICKY_HOLD, AbilityId.STORM_DRAIN, AbilityId.SAND_FORCE, 475, 111, 83, 68, 92, 82, 39, 75, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("East Sea", "east", PokemonType.WATER, PokemonType.GROUND, 0.9, 29.9, AbilityId.STICKY_HOLD, AbilityId.STORM_DRAIN, AbilityId.SAND_FORCE, 475, 111, 83, 68, 92, 82, 39, 75, 50, 166, false, null, true), + new PokemonForm("West Sea", "west", PokemonType.WATER, PokemonType.GROUND, 0.9, 29.9, AbilityId.STICKY_HOLD, AbilityId.STORM_DRAIN, AbilityId.SAND_FORCE, 475, 111, 83, 68, 92, 82, 39, 75, 50, 166, false, null, true) + ), + new PokemonSpecies(SpeciesId.AMBIPOM, 4, false, false, false, "Long Tail Pokémon", PokemonType.NORMAL, null, 1.2, 20.3, AbilityId.TECHNICIAN, AbilityId.PICKUP, AbilityId.SKILL_LINK, 482, 75, 100, 66, 60, 66, 115, 45, 100, 169, GrowthRate.FAST, 50, true), + new PokemonSpecies(SpeciesId.DRIFLOON, 4, false, false, false, "Balloon Pokémon", PokemonType.GHOST, PokemonType.FLYING, 0.4, 1.2, AbilityId.AFTERMATH, AbilityId.UNBURDEN, AbilityId.FLARE_BOOST, 348, 90, 50, 34, 60, 44, 70, 125, 50, 70, GrowthRate.FLUCTUATING, 50, false), + new PokemonSpecies(SpeciesId.DRIFBLIM, 4, false, false, false, "Blimp Pokémon", PokemonType.GHOST, PokemonType.FLYING, 1.2, 15, AbilityId.AFTERMATH, AbilityId.UNBURDEN, AbilityId.FLARE_BOOST, 498, 150, 80, 44, 90, 54, 80, 60, 50, 174, GrowthRate.FLUCTUATING, 50, false), + new PokemonSpecies(SpeciesId.BUNEARY, 4, false, false, false, "Rabbit Pokémon", PokemonType.NORMAL, null, 0.4, 5.5, AbilityId.RUN_AWAY, AbilityId.KLUTZ, AbilityId.LIMBER, 350, 55, 66, 44, 44, 56, 85, 190, 0, 70, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.LOPUNNY, 4, false, false, false, "Rabbit Pokémon", PokemonType.NORMAL, null, 1.2, 33.3, AbilityId.CUTE_CHARM, AbilityId.KLUTZ, AbilityId.LIMBER, 480, 65, 76, 84, 54, 96, 105, 60, 140, 168, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.NORMAL, null, 1.2, 33.3, AbilityId.CUTE_CHARM, AbilityId.KLUTZ, AbilityId.LIMBER, 480, 65, 76, 84, 54, 96, 105, 60, 140, 168, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.NORMAL, PokemonType.FIGHTING, 1.3, 28.3, AbilityId.SCRAPPY, AbilityId.SCRAPPY, AbilityId.SCRAPPY, 580, 65, 136, 94, 54, 96, 135, 60, 140, 168) + ), + new PokemonSpecies(SpeciesId.MISMAGIUS, 4, false, false, false, "Magical Pokémon", PokemonType.GHOST, null, 0.9, 4.4, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 495, 60, 60, 60, 105, 105, 105, 45, 35, 173, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.HONCHKROW, 4, false, false, false, "Big Boss Pokémon", PokemonType.DARK, PokemonType.FLYING, 0.9, 27.3, AbilityId.INSOMNIA, AbilityId.SUPER_LUCK, AbilityId.MOXIE, 505, 100, 125, 52, 105, 52, 71, 30, 35, 177, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.GLAMEOW, 4, false, false, false, "Catty Pokémon", PokemonType.NORMAL, null, 0.5, 3.9, AbilityId.LIMBER, AbilityId.OWN_TEMPO, AbilityId.KEEN_EYE, 310, 49, 55, 42, 42, 37, 85, 190, 70, 62, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.PURUGLY, 4, false, false, false, "Tiger Cat Pokémon", PokemonType.NORMAL, null, 1, 43.8, AbilityId.THICK_FAT, AbilityId.OWN_TEMPO, AbilityId.DEFIANT, 452, 71, 82, 64, 64, 59, 112, 75, 70, 158, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.CHINGLING, 4, false, false, false, "Bell Pokémon", PokemonType.PSYCHIC, null, 0.2, 0.6, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 285, 45, 30, 50, 65, 50, 45, 120, 70, 57, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.STUNKY, 4, false, false, false, "Skunk Pokémon", PokemonType.POISON, PokemonType.DARK, 0.4, 19.2, AbilityId.STENCH, AbilityId.AFTERMATH, AbilityId.KEEN_EYE, 329, 63, 63, 47, 41, 41, 74, 225, 50, 66, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SKUNTANK, 4, false, false, false, "Skunk Pokémon", PokemonType.POISON, PokemonType.DARK, 1, 38, AbilityId.STENCH, AbilityId.AFTERMATH, AbilityId.KEEN_EYE, 479, 103, 93, 67, 71, 61, 84, 60, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BRONZOR, 4, false, false, false, "Bronze Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 0.5, 60.5, AbilityId.LEVITATE, AbilityId.HEATPROOF, AbilityId.HEAVY_METAL, 300, 57, 24, 86, 24, 86, 23, 255, 50, 60, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.BRONZONG, 4, false, false, false, "Bronze Bell Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 1.3, 187, AbilityId.LEVITATE, AbilityId.HEATPROOF, AbilityId.HEAVY_METAL, 500, 67, 89, 116, 79, 116, 33, 90, 50, 175, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.BONSLY, 4, false, false, false, "Bonsai Pokémon", PokemonType.ROCK, null, 0.5, 15, AbilityId.STURDY, AbilityId.ROCK_HEAD, AbilityId.RATTLED, 290, 50, 80, 95, 10, 45, 10, 255, 50, 58, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MIME_JR, 4, false, false, false, "Mime Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 0.6, 13, AbilityId.SOUNDPROOF, AbilityId.FILTER, AbilityId.TECHNICIAN, 310, 20, 25, 45, 70, 90, 60, 145, 50, 62, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.HAPPINY, 4, false, false, false, "Playhouse Pokémon", PokemonType.NORMAL, null, 0.6, 24.4, AbilityId.NATURAL_CURE, AbilityId.SERENE_GRACE, AbilityId.FRIEND_GUARD, 220, 100, 5, 5, 15, 65, 30, 130, 140, 110, GrowthRate.FAST, 0, false), + new PokemonSpecies(SpeciesId.CHATOT, 4, false, false, false, "Music Note Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.5, 1.9, AbilityId.KEEN_EYE, AbilityId.TANGLED_FEET, AbilityId.BIG_PECKS, 411, 76, 65, 45, 92, 42, 91, 30, 35, 144, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SPIRITOMB, 4, false, false, false, "Forbidden Pokémon", PokemonType.GHOST, PokemonType.DARK, 1, 108, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.INFILTRATOR, 485, 50, 92, 108, 92, 108, 35, 100, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GIBLE, 4, false, false, false, "Land Shark Pokémon", PokemonType.DRAGON, PokemonType.GROUND, 0.7, 20.5, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.ROUGH_SKIN, 300, 58, 70, 45, 40, 45, 42, 45, 50, 60, GrowthRate.SLOW, 50, true), + new PokemonSpecies(SpeciesId.GABITE, 4, false, false, false, "Cave Pokémon", PokemonType.DRAGON, PokemonType.GROUND, 1.4, 56, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.ROUGH_SKIN, 410, 68, 90, 65, 50, 55, 82, 45, 50, 144, GrowthRate.SLOW, 50, true), + new PokemonSpecies(SpeciesId.GARCHOMP, 4, false, false, false, "Mach Pokémon", PokemonType.DRAGON, PokemonType.GROUND, 1.9, 95, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.ROUGH_SKIN, 600, 108, 130, 95, 80, 85, 102, 45, 50, 300, GrowthRate.SLOW, 50, true, true, + new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.GROUND, 1.9, 95, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.ROUGH_SKIN, 600, 108, 130, 95, 80, 85, 102, 45, 50, 300, true, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.GROUND, 1.9, 95, AbilityId.SAND_FORCE, AbilityId.NONE, AbilityId.SAND_FORCE, 700, 108, 170, 115, 120, 95, 92, 45, 50, 300, true) + ), + new PokemonSpecies(SpeciesId.MUNCHLAX, 4, false, false, false, "Big Eater Pokémon", PokemonType.NORMAL, null, 0.6, 105, AbilityId.PICKUP, AbilityId.THICK_FAT, AbilityId.GLUTTONY, 390, 135, 85, 40, 40, 85, 5, 50, 50, 78, GrowthRate.SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.RIOLU, 4, false, false, false, "Emanation Pokémon", PokemonType.FIGHTING, null, 0.7, 20.2, AbilityId.STEADFAST, AbilityId.INNER_FOCUS, AbilityId.PRANKSTER, 285, 40, 70, 40, 35, 40, 60, 75, 50, 57, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.LUCARIO, 4, false, false, false, "Aura Pokémon", PokemonType.FIGHTING, PokemonType.STEEL, 1.2, 54, AbilityId.STEADFAST, AbilityId.INNER_FOCUS, AbilityId.JUSTIFIED, 525, 70, 110, 70, 115, 70, 90, 45, 50, 184, GrowthRate.MEDIUM_SLOW, 87.5, false, true, + new PokemonForm("Normal", "", PokemonType.FIGHTING, PokemonType.STEEL, 1.2, 54, AbilityId.STEADFAST, AbilityId.INNER_FOCUS, AbilityId.JUSTIFIED, 525, 70, 110, 70, 115, 70, 90, 45, 50, 184, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.FIGHTING, PokemonType.STEEL, 1.3, 57.5, AbilityId.ADAPTABILITY, AbilityId.ADAPTABILITY, AbilityId.ADAPTABILITY, 625, 70, 145, 88, 140, 70, 112, 45, 50, 184) + ), + new PokemonSpecies(SpeciesId.HIPPOPOTAS, 4, false, false, false, "Hippo Pokémon", PokemonType.GROUND, null, 0.8, 49.5, AbilityId.SAND_STREAM, AbilityId.NONE, AbilityId.SAND_FORCE, 330, 68, 72, 78, 38, 42, 32, 140, 50, 66, GrowthRate.SLOW, 50, true), + new PokemonSpecies(SpeciesId.HIPPOWDON, 4, false, false, false, "Heavyweight Pokémon", PokemonType.GROUND, null, 2, 300, AbilityId.SAND_STREAM, AbilityId.NONE, AbilityId.SAND_FORCE, 525, 108, 112, 118, 68, 72, 47, 60, 50, 184, GrowthRate.SLOW, 50, true), + new PokemonSpecies(SpeciesId.SKORUPI, 4, false, false, false, "Scorpion Pokémon", PokemonType.POISON, PokemonType.BUG, 0.8, 12, AbilityId.BATTLE_ARMOR, AbilityId.SNIPER, AbilityId.KEEN_EYE, 330, 40, 50, 90, 30, 55, 65, 120, 50, 66, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.DRAPION, 4, false, false, false, "Ogre Scorpion Pokémon", PokemonType.POISON, PokemonType.DARK, 1.3, 61.5, AbilityId.BATTLE_ARMOR, AbilityId.SNIPER, AbilityId.KEEN_EYE, 500, 70, 90, 110, 60, 75, 95, 45, 50, 175, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.CROAGUNK, 4, false, false, false, "Toxic Mouth Pokémon", PokemonType.POISON, PokemonType.FIGHTING, 0.7, 23, AbilityId.ANTICIPATION, AbilityId.DRY_SKIN, AbilityId.POISON_TOUCH, 300, 48, 61, 40, 61, 40, 50, 140, 100, 60, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.TOXICROAK, 4, false, false, false, "Toxic Mouth Pokémon", PokemonType.POISON, PokemonType.FIGHTING, 1.3, 44.4, AbilityId.ANTICIPATION, AbilityId.DRY_SKIN, AbilityId.POISON_TOUCH, 490, 83, 106, 65, 86, 65, 85, 75, 50, 172, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.CARNIVINE, 4, false, false, false, "Bug Catcher Pokémon", PokemonType.GRASS, null, 1.4, 27, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 454, 74, 100, 72, 90, 72, 46, 200, 70, 159, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.FINNEON, 4, false, false, false, "Wing Fish Pokémon", PokemonType.WATER, null, 0.4, 7, AbilityId.SWIFT_SWIM, AbilityId.STORM_DRAIN, AbilityId.WATER_VEIL, 330, 49, 49, 56, 49, 61, 66, 190, 70, 66, GrowthRate.ERRATIC, 50, true), + new PokemonSpecies(SpeciesId.LUMINEON, 4, false, false, false, "Neon Pokémon", PokemonType.WATER, null, 1.2, 24, AbilityId.SWIFT_SWIM, AbilityId.STORM_DRAIN, AbilityId.WATER_VEIL, 460, 69, 69, 76, 69, 86, 91, 75, 70, 161, GrowthRate.ERRATIC, 50, true), + new PokemonSpecies(SpeciesId.MANTYKE, 4, false, false, false, "Kite Pokémon", PokemonType.WATER, PokemonType.FLYING, 1, 65, AbilityId.SWIFT_SWIM, AbilityId.WATER_ABSORB, AbilityId.WATER_VEIL, 345, 45, 20, 50, 60, 120, 50, 25, 50, 69, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.SNOVER, 4, false, false, false, "Frost Tree Pokémon", PokemonType.GRASS, PokemonType.ICE, 1, 50.5, AbilityId.SNOW_WARNING, AbilityId.NONE, AbilityId.SOUNDPROOF, 334, 60, 62, 50, 62, 60, 40, 120, 50, 67, GrowthRate.SLOW, 50, true), + new PokemonSpecies(SpeciesId.ABOMASNOW, 4, false, false, false, "Frost Tree Pokémon", PokemonType.GRASS, PokemonType.ICE, 2.2, 135.5, AbilityId.SNOW_WARNING, AbilityId.NONE, AbilityId.SOUNDPROOF, 494, 90, 92, 75, 92, 85, 60, 60, 50, 173, GrowthRate.SLOW, 50, true, true, + new PokemonForm("Normal", "", PokemonType.GRASS, PokemonType.ICE, 2.2, 135.5, AbilityId.SNOW_WARNING, AbilityId.NONE, AbilityId.SOUNDPROOF, 494, 90, 92, 75, 92, 85, 60, 60, 50, 173, true, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.GRASS, PokemonType.ICE, 2.7, 185, AbilityId.SNOW_WARNING, AbilityId.NONE, AbilityId.SNOW_WARNING, 594, 90, 132, 105, 132, 105, 30, 60, 50, 173, true) + ), + new PokemonSpecies(SpeciesId.WEAVILE, 4, false, false, false, "Sharp Claw Pokémon", PokemonType.DARK, PokemonType.ICE, 1.1, 34, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.PICKPOCKET, 510, 70, 120, 65, 45, 85, 125, 45, 35, 179, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.MAGNEZONE, 4, false, false, false, "Magnet Area Pokémon", PokemonType.ELECTRIC, PokemonType.STEEL, 1.2, 180, AbilityId.MAGNET_PULL, AbilityId.STURDY, AbilityId.ANALYTIC, 535, 70, 70, 115, 130, 90, 60, 30, 50, 268, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.LICKILICKY, 4, false, false, false, "Licking Pokémon", PokemonType.NORMAL, null, 1.7, 140, AbilityId.OWN_TEMPO, AbilityId.OBLIVIOUS, AbilityId.CLOUD_NINE, 515, 110, 85, 95, 80, 95, 50, 30, 50, 180, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.RHYPERIOR, 4, false, false, false, "Drill Pokémon", PokemonType.GROUND, PokemonType.ROCK, 2.4, 282.8, AbilityId.LIGHTNING_ROD, AbilityId.SOLID_ROCK, AbilityId.RECKLESS, 535, 115, 140, 130, 55, 55, 40, 30, 50, 268, GrowthRate.SLOW, 50, true), + new PokemonSpecies(SpeciesId.TANGROWTH, 4, false, false, false, "Vine Pokémon", PokemonType.GRASS, null, 2, 128.6, AbilityId.CHLOROPHYLL, AbilityId.LEAF_GUARD, AbilityId.REGENERATOR, 535, 100, 100, 125, 110, 50, 50, 30, 50, 187, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.ELECTIVIRE, 4, false, false, false, "Thunderbolt Pokémon", PokemonType.ELECTRIC, null, 1.8, 138.6, AbilityId.MOTOR_DRIVE, AbilityId.NONE, AbilityId.VITAL_SPIRIT, 540, 75, 123, 67, 95, 85, 95, 30, 50, 270, GrowthRate.MEDIUM_FAST, 75, false), + new PokemonSpecies(SpeciesId.MAGMORTAR, 4, false, false, false, "Blast Pokémon", PokemonType.FIRE, null, 1.6, 68, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.VITAL_SPIRIT, 540, 75, 95, 67, 125, 95, 83, 30, 50, 270, GrowthRate.MEDIUM_FAST, 75, false), + new PokemonSpecies(SpeciesId.TOGEKISS, 4, false, false, false, "Jubilee Pokémon", PokemonType.FAIRY, PokemonType.FLYING, 1.5, 38, AbilityId.HUSTLE, AbilityId.SERENE_GRACE, AbilityId.SUPER_LUCK, 545, 85, 50, 95, 120, 115, 80, 30, 50, 273, GrowthRate.FAST, 87.5, false), + new PokemonSpecies(SpeciesId.YANMEGA, 4, false, false, false, "Ogre Darner Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.9, 51.5, AbilityId.SPEED_BOOST, AbilityId.TINTED_LENS, AbilityId.FRISK, 515, 86, 76, 86, 116, 56, 95, 30, 70, 180, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.LEAFEON, 4, false, false, false, "Verdant Pokémon", PokemonType.GRASS, null, 1, 25.5, AbilityId.LEAF_GUARD, AbilityId.NONE, AbilityId.CHLOROPHYLL, 525, 65, 110, 130, 60, 65, 95, 45, 35, 184, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.GLACEON, 4, false, false, false, "Fresh Snow Pokémon", PokemonType.ICE, null, 0.8, 25.9, AbilityId.SNOW_CLOAK, AbilityId.NONE, AbilityId.ICE_BODY, 525, 65, 60, 110, 130, 95, 65, 45, 35, 184, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.GLISCOR, 4, false, false, false, "Fang Scorpion Pokémon", PokemonType.GROUND, PokemonType.FLYING, 2, 42.5, AbilityId.HYPER_CUTTER, AbilityId.SAND_VEIL, AbilityId.POISON_HEAL, 510, 75, 95, 125, 45, 75, 95, 30, 70, 179, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.MAMOSWINE, 4, false, false, false, "Twin Tusk Pokémon", PokemonType.ICE, PokemonType.GROUND, 2.5, 291, AbilityId.OBLIVIOUS, AbilityId.SNOW_CLOAK, AbilityId.THICK_FAT, 530, 110, 130, 80, 70, 60, 80, 50, 50, 265, GrowthRate.SLOW, 50, true), + new PokemonSpecies(SpeciesId.PORYGON_Z, 4, false, false, false, "Virtual Pokémon", PokemonType.NORMAL, null, 0.9, 34, AbilityId.ADAPTABILITY, AbilityId.DOWNLOAD, AbilityId.ANALYTIC, 535, 85, 80, 70, 135, 75, 90, 30, 50, 268, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.GALLADE, 4, false, false, false, "Blade Pokémon", PokemonType.PSYCHIC, PokemonType.FIGHTING, 1.6, 52, AbilityId.STEADFAST, AbilityId.SHARPNESS, AbilityId.JUSTIFIED, 518, 68, 125, 65, 65, 115, 80, 45, 35, 259, GrowthRate.SLOW, 100, false, true, + new PokemonForm("Normal", "", PokemonType.PSYCHIC, PokemonType.FIGHTING, 1.6, 52, AbilityId.STEADFAST, AbilityId.SHARPNESS, AbilityId.JUSTIFIED, 518, 68, 125, 65, 65, 115, 80, 45, 35, 259, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.PSYCHIC, PokemonType.FIGHTING, 1.6, 56.4, AbilityId.INNER_FOCUS, AbilityId.INNER_FOCUS, AbilityId.INNER_FOCUS, 618, 68, 165, 95, 65, 115, 110, 45, 35, 259) + ), + new PokemonSpecies(SpeciesId.PROBOPASS, 4, false, false, false, "Compass Pokémon", PokemonType.ROCK, PokemonType.STEEL, 1.4, 340, AbilityId.STURDY, AbilityId.MAGNET_PULL, AbilityId.SAND_FORCE, 525, 60, 55, 145, 75, 150, 40, 60, 70, 184, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DUSKNOIR, 4, false, false, false, "Gripper Pokémon", PokemonType.GHOST, null, 2.2, 106.6, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.FRISK, 525, 45, 100, 135, 65, 135, 45, 45, 35, 263, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.FROSLASS, 4, false, false, false, "Snow Land Pokémon", PokemonType.ICE, PokemonType.GHOST, 1.3, 26.6, AbilityId.SNOW_CLOAK, AbilityId.NONE, AbilityId.CURSED_BODY, 480, 70, 80, 70, 80, 70, 110, 75, 50, 168, GrowthRate.MEDIUM_FAST, 0, false), + new PokemonSpecies(SpeciesId.ROTOM, 4, false, false, false, "Plasma Pokémon", PokemonType.ELECTRIC, PokemonType.GHOST, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 440, 50, 50, 77, 95, 77, 91, 45, 50, 154, GrowthRate.MEDIUM_FAST, null, false, false, + new PokemonForm("Normal", "", PokemonType.ELECTRIC, PokemonType.GHOST, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 440, 50, 50, 77, 95, 77, 91, 45, 50, 154, false, null, true), + new PokemonForm("Heat", "heat", PokemonType.ELECTRIC, PokemonType.FIRE, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), + new PokemonForm("Wash", "wash", PokemonType.ELECTRIC, PokemonType.WATER, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), + new PokemonForm("Frost", "frost", PokemonType.ELECTRIC, PokemonType.ICE, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), + new PokemonForm("Fan", "fan", PokemonType.ELECTRIC, PokemonType.FLYING, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), + new PokemonForm("Mow", "mow", PokemonType.ELECTRIC, PokemonType.GRASS, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true) + ), + new PokemonSpecies(SpeciesId.UXIE, 4, true, false, false, "Knowledge Pokémon", PokemonType.PSYCHIC, null, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 580, 75, 75, 130, 75, 130, 95, 3, 140, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.MESPRIT, 4, true, false, false, "Emotion Pokémon", PokemonType.PSYCHIC, null, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 580, 80, 105, 105, 105, 105, 80, 3, 140, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.AZELF, 4, true, false, false, "Willpower Pokémon", PokemonType.PSYCHIC, null, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 580, 75, 125, 70, 125, 70, 115, 3, 140, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.DIALGA, 4, false, true, false, "Temporal Pokémon", PokemonType.STEEL, PokemonType.DRAGON, 5.4, 683, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 100, 120, 120, 150, 100, 90, 3, 0, 340, GrowthRate.SLOW, null, false, false, + new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.DRAGON, 5.4, 683, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 100, 120, 120, 150, 100, 90, 3, 0, 340, false, null, true), + new PokemonForm("Origin Forme", "origin", PokemonType.STEEL, PokemonType.DRAGON, 7, 848.7, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 100, 100, 120, 150, 120, 90, 3, 0, 340) + ), + new PokemonSpecies(SpeciesId.PALKIA, 4, false, true, false, "Spatial Pokémon", PokemonType.WATER, PokemonType.DRAGON, 4.2, 336, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 90, 120, 100, 150, 120, 100, 3, 0, 340, GrowthRate.SLOW, null, false, false, + new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.DRAGON, 4.2, 336, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 90, 120, 100, 150, 120, 100, 3, 0, 340, false, null, true), + new PokemonForm("Origin Forme", "origin", PokemonType.WATER, PokemonType.DRAGON, 6.3, 659, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 90, 100, 100, 150, 120, 120, 3, 0, 340) + ), + new PokemonSpecies(SpeciesId.HEATRAN, 4, true, false, false, "Lava Dome Pokémon", PokemonType.FIRE, PokemonType.STEEL, 1.7, 430, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.FLAME_BODY, 600, 91, 90, 106, 130, 106, 77, 3, 100, 300, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.REGIGIGAS, 4, true, false, false, "Colossal Pokémon", PokemonType.NORMAL, null, 3.7, 420, AbilityId.SLOW_START, AbilityId.NONE, AbilityId.NORMALIZE, 670, 110, 160, 110, 80, 110, 100, 3, 0, 335, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.GIRATINA, 4, false, true, false, "Renegade Pokémon", PokemonType.GHOST, PokemonType.DRAGON, 4.5, 750, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 150, 100, 120, 100, 120, 90, 3, 0, 340, GrowthRate.SLOW, null, false, true, + new PokemonForm("Altered Forme", "altered", PokemonType.GHOST, PokemonType.DRAGON, 4.5, 750, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 150, 100, 120, 100, 120, 90, 3, 0, 340, false, null, true), + new PokemonForm("Origin Forme", "origin", PokemonType.GHOST, PokemonType.DRAGON, 6.9, 650, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.LEVITATE, 680, 150, 120, 100, 120, 100, 90, 3, 0, 340) + ), + new PokemonSpecies(SpeciesId.CRESSELIA, 4, true, false, false, "Lunar Pokémon", PokemonType.PSYCHIC, null, 1.5, 85.6, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 580, 120, 70, 110, 75, 120, 85, 3, 100, 300, GrowthRate.SLOW, 0, false), + new PokemonSpecies(SpeciesId.PHIONE, 4, false, false, true, "Sea Drifter Pokémon", PokemonType.WATER, null, 0.4, 3.1, AbilityId.HYDRATION, AbilityId.NONE, AbilityId.NONE, 480, 80, 80, 80, 80, 80, 80, 30, 70, 240, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.MANAPHY, 4, false, false, true, "Seafaring Pokémon", PokemonType.WATER, null, 0.3, 1.4, AbilityId.HYDRATION, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 70, 300, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.DARKRAI, 4, false, false, true, "Pitch-Black Pokémon", PokemonType.DARK, null, 1.5, 50.5, AbilityId.BAD_DREAMS, AbilityId.NONE, AbilityId.NONE, 600, 70, 90, 90, 135, 90, 125, 3, 0, 300, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.SHAYMIN, 4, false, false, true, "Gratitude Pokémon", PokemonType.GRASS, null, 0.2, 2.1, AbilityId.NATURAL_CURE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 45, 100, 300, GrowthRate.MEDIUM_SLOW, null, false, true, + new PokemonForm("Land Forme", "land", PokemonType.GRASS, null, 0.2, 2.1, AbilityId.NATURAL_CURE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 45, 100, 300, false, null, true), + new PokemonForm("Sky Forme", "sky", PokemonType.GRASS, PokemonType.FLYING, 0.4, 5.2, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 103, 75, 120, 75, 127, 45, 100, 300) + ), + new PokemonSpecies(SpeciesId.ARCEUS, 4, false, false, true, "Alpha Pokémon", PokemonType.NORMAL, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal", "normal", PokemonType.NORMAL, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360, false, null, true), + new PokemonForm("Fighting", "fighting", PokemonType.FIGHTING, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Flying", "flying", PokemonType.FLYING, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Poison", "poison", PokemonType.POISON, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Ground", "ground", PokemonType.GROUND, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Rock", "rock", PokemonType.ROCK, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Bug", "bug", PokemonType.BUG, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Ghost", "ghost", PokemonType.GHOST, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Steel", "steel", PokemonType.STEEL, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Fire", "fire", PokemonType.FIRE, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Water", "water", PokemonType.WATER, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Grass", "grass", PokemonType.GRASS, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Electric", "electric", PokemonType.ELECTRIC, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Psychic", "psychic", PokemonType.PSYCHIC, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Ice", "ice", PokemonType.ICE, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Dragon", "dragon", PokemonType.DRAGON, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Dark", "dark", PokemonType.DARK, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("Fairy", "fairy", PokemonType.FAIRY, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), + new PokemonForm("???", "unknown", PokemonType.UNKNOWN, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360, false, null, false, true) + ), + new PokemonSpecies(SpeciesId.VICTINI, 5, false, false, true, "Victory Pokémon", PokemonType.PSYCHIC, PokemonType.FIRE, 0.4, 4, AbilityId.VICTORY_STAR, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 100, 300, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.SNIVY, 5, false, false, false, "Grass Snake Pokémon", PokemonType.GRASS, null, 0.6, 8.1, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CONTRARY, 308, 45, 45, 55, 45, 55, 63, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.SERVINE, 5, false, false, false, "Grass Snake Pokémon", PokemonType.GRASS, null, 0.8, 16, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CONTRARY, 413, 60, 60, 75, 60, 75, 83, 45, 70, 145, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.SERPERIOR, 5, false, false, false, "Regal Pokémon", PokemonType.GRASS, null, 3.3, 63, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CONTRARY, 528, 75, 75, 95, 75, 95, 113, 45, 70, 264, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.TEPIG, 5, false, false, false, "Fire Pig Pokémon", PokemonType.FIRE, null, 0.5, 9.9, AbilityId.BLAZE, AbilityId.NONE, AbilityId.THICK_FAT, 308, 65, 63, 45, 45, 45, 45, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.PIGNITE, 5, false, false, false, "Fire Pig Pokémon", PokemonType.FIRE, PokemonType.FIGHTING, 1, 55.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.THICK_FAT, 418, 90, 93, 55, 70, 55, 55, 45, 70, 146, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.EMBOAR, 5, false, false, false, "Mega Fire Pig Pokémon", PokemonType.FIRE, PokemonType.FIGHTING, 1.6, 150, AbilityId.BLAZE, AbilityId.NONE, AbilityId.RECKLESS, 528, 110, 123, 65, 100, 65, 65, 45, 70, 264, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.OSHAWOTT, 5, false, false, false, "Sea Otter Pokémon", PokemonType.WATER, null, 0.5, 5.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHELL_ARMOR, 308, 55, 55, 45, 63, 45, 45, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.DEWOTT, 5, false, false, false, "Discipline Pokémon", PokemonType.WATER, null, 0.8, 24.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHELL_ARMOR, 413, 75, 75, 60, 83, 60, 60, 45, 70, 145, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.SAMUROTT, 5, false, false, false, "Formidable Pokémon", PokemonType.WATER, null, 1.5, 94.6, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHELL_ARMOR, 528, 95, 100, 85, 108, 70, 70, 45, 70, 264, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.PATRAT, 5, false, false, false, "Scout Pokémon", PokemonType.NORMAL, null, 0.5, 11.6, AbilityId.RUN_AWAY, AbilityId.KEEN_EYE, AbilityId.ANALYTIC, 255, 45, 55, 39, 35, 39, 42, 255, 70, 51, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.WATCHOG, 5, false, false, false, "Lookout Pokémon", PokemonType.NORMAL, null, 1.1, 27, AbilityId.ILLUMINATE, AbilityId.KEEN_EYE, AbilityId.ANALYTIC, 420, 60, 85, 69, 60, 69, 77, 255, 70, 147, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.LILLIPUP, 5, false, false, false, "Puppy Pokémon", PokemonType.NORMAL, null, 0.4, 4.1, AbilityId.VITAL_SPIRIT, AbilityId.PICKUP, AbilityId.RUN_AWAY, 275, 45, 60, 45, 25, 45, 55, 255, 50, 55, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.HERDIER, 5, false, false, false, "Loyal Dog Pokémon", PokemonType.NORMAL, null, 0.9, 14.7, AbilityId.INTIMIDATE, AbilityId.SAND_RUSH, AbilityId.SCRAPPY, 370, 65, 80, 65, 35, 65, 60, 120, 50, 130, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.STOUTLAND, 5, false, false, false, "Big-Hearted Pokémon", PokemonType.NORMAL, null, 1.2, 61, AbilityId.INTIMIDATE, AbilityId.SAND_RUSH, AbilityId.SCRAPPY, 500, 85, 110, 90, 45, 90, 80, 45, 50, 250, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.PURRLOIN, 5, false, false, false, "Devious Pokémon", PokemonType.DARK, null, 0.4, 10.1, AbilityId.LIMBER, AbilityId.UNBURDEN, AbilityId.PRANKSTER, 281, 41, 50, 37, 50, 37, 66, 255, 50, 56, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.LIEPARD, 5, false, false, false, "Cruel Pokémon", PokemonType.DARK, null, 1.1, 37.5, AbilityId.LIMBER, AbilityId.UNBURDEN, AbilityId.PRANKSTER, 446, 64, 88, 50, 88, 50, 106, 90, 50, 156, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PANSAGE, 5, false, false, false, "Grass Monkey Pokémon", PokemonType.GRASS, null, 0.6, 10.5, AbilityId.GLUTTONY, AbilityId.NONE, AbilityId.OVERGROW, 316, 50, 53, 48, 53, 48, 64, 190, 70, 63, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.SIMISAGE, 5, false, false, false, "Thorn Monkey Pokémon", PokemonType.GRASS, null, 1.1, 30.5, AbilityId.GLUTTONY, AbilityId.NONE, AbilityId.OVERGROW, 498, 75, 98, 63, 98, 63, 101, 75, 70, 174, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.PANSEAR, 5, false, false, false, "High Temp Pokémon", PokemonType.FIRE, null, 0.6, 11, AbilityId.GLUTTONY, AbilityId.NONE, AbilityId.BLAZE, 316, 50, 53, 48, 53, 48, 64, 190, 70, 63, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.SIMISEAR, 5, false, false, false, "Ember Pokémon", PokemonType.FIRE, null, 1, 28, AbilityId.GLUTTONY, AbilityId.NONE, AbilityId.BLAZE, 498, 75, 98, 63, 98, 63, 101, 75, 70, 174, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.PANPOUR, 5, false, false, false, "Spray Pokémon", PokemonType.WATER, null, 0.6, 13.5, AbilityId.GLUTTONY, AbilityId.NONE, AbilityId.TORRENT, 316, 50, 53, 48, 53, 48, 64, 190, 70, 63, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.SIMIPOUR, 5, false, false, false, "Geyser Pokémon", PokemonType.WATER, null, 1, 29, AbilityId.GLUTTONY, AbilityId.NONE, AbilityId.TORRENT, 498, 75, 98, 63, 98, 63, 101, 75, 70, 174, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.MUNNA, 5, false, false, false, "Dream Eater Pokémon", PokemonType.PSYCHIC, null, 0.6, 23.3, AbilityId.FOREWARN, AbilityId.SYNCHRONIZE, AbilityId.TELEPATHY, 292, 76, 25, 45, 67, 55, 24, 190, 50, 58, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.MUSHARNA, 5, false, false, false, "Drowsing Pokémon", PokemonType.PSYCHIC, null, 1.1, 60.5, AbilityId.FOREWARN, AbilityId.SYNCHRONIZE, AbilityId.TELEPATHY, 487, 116, 55, 85, 107, 95, 29, 75, 50, 170, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.PIDOVE, 5, false, false, false, "Tiny Pigeon Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 2.1, AbilityId.BIG_PECKS, AbilityId.SUPER_LUCK, AbilityId.RIVALRY, 264, 50, 55, 50, 36, 30, 43, 255, 50, 53, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.TRANQUILL, 5, false, false, false, "Wild Pigeon Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 15, AbilityId.BIG_PECKS, AbilityId.SUPER_LUCK, AbilityId.RIVALRY, 358, 62, 77, 62, 50, 42, 65, 120, 50, 125, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.UNFEZANT, 5, false, false, false, "Proud Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.2, 29, AbilityId.BIG_PECKS, AbilityId.SUPER_LUCK, AbilityId.RIVALRY, 488, 80, 115, 80, 65, 55, 93, 45, 50, 244, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.BLITZLE, 5, false, false, false, "Electrified Pokémon", PokemonType.ELECTRIC, null, 0.8, 29.8, AbilityId.LIGHTNING_ROD, AbilityId.MOTOR_DRIVE, AbilityId.SAP_SIPPER, 295, 45, 60, 32, 50, 32, 76, 190, 70, 59, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ZEBSTRIKA, 5, false, false, false, "Thunderbolt Pokémon", PokemonType.ELECTRIC, null, 1.6, 79.5, AbilityId.LIGHTNING_ROD, AbilityId.MOTOR_DRIVE, AbilityId.SAP_SIPPER, 497, 75, 100, 63, 80, 63, 116, 75, 70, 174, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ROGGENROLA, 5, false, false, false, "Mantle Pokémon", PokemonType.ROCK, null, 0.4, 18, AbilityId.STURDY, AbilityId.WEAK_ARMOR, AbilityId.SAND_FORCE, 280, 55, 75, 85, 25, 25, 15, 255, 50, 56, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.BOLDORE, 5, false, false, false, "Ore Pokémon", PokemonType.ROCK, null, 0.9, 102, AbilityId.STURDY, AbilityId.WEAK_ARMOR, AbilityId.SAND_FORCE, 390, 70, 105, 105, 50, 40, 20, 120, 50, 137, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.GIGALITH, 5, false, false, false, "Compressed Pokémon", PokemonType.ROCK, null, 1.7, 260, AbilityId.STURDY, AbilityId.SAND_STREAM, AbilityId.SAND_FORCE, 515, 85, 135, 130, 60, 80, 25, 45, 50, 258, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.WOOBAT, 5, false, false, false, "Bat Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 0.4, 2.1, AbilityId.UNAWARE, AbilityId.KLUTZ, AbilityId.SIMPLE, 323, 65, 45, 43, 55, 43, 72, 190, 50, 65, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SWOOBAT, 5, false, false, false, "Courting Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 0.9, 10.5, AbilityId.UNAWARE, AbilityId.KLUTZ, AbilityId.SIMPLE, 425, 67, 57, 55, 77, 55, 114, 45, 50, 149, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DRILBUR, 5, false, false, false, "Mole Pokémon", PokemonType.GROUND, null, 0.3, 8.5, AbilityId.SAND_RUSH, AbilityId.SAND_FORCE, AbilityId.MOLD_BREAKER, 328, 60, 85, 40, 30, 45, 68, 120, 50, 66, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.EXCADRILL, 5, false, false, false, "Subterrene Pokémon", PokemonType.GROUND, PokemonType.STEEL, 0.7, 40.4, AbilityId.SAND_RUSH, AbilityId.SAND_FORCE, AbilityId.MOLD_BREAKER, 508, 110, 135, 60, 50, 65, 88, 60, 50, 178, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.AUDINO, 5, false, false, false, "Hearing Pokémon", PokemonType.NORMAL, null, 1.1, 31, AbilityId.HEALER, AbilityId.REGENERATOR, AbilityId.KLUTZ, 445, 103, 60, 86, 60, 86, 50, 255, 50, 390, GrowthRate.FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.NORMAL, null, 1.1, 31, AbilityId.HEALER, AbilityId.REGENERATOR, AbilityId.KLUTZ, 445, 103, 60, 86, 60, 86, 50, 255, 50, 390, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.NORMAL, PokemonType.FAIRY, 1.5, 32, AbilityId.REGENERATOR, AbilityId.REGENERATOR, AbilityId.REGENERATOR, 545, 103, 60, 126, 80, 126, 50, 255, 50, 390) + ), + new PokemonSpecies(SpeciesId.TIMBURR, 5, false, false, false, "Muscular Pokémon", PokemonType.FIGHTING, null, 0.6, 12.5, AbilityId.GUTS, AbilityId.SHEER_FORCE, AbilityId.IRON_FIST, 305, 75, 80, 55, 25, 35, 35, 180, 70, 61, GrowthRate.MEDIUM_SLOW, 75, false), + new PokemonSpecies(SpeciesId.GURDURR, 5, false, false, false, "Muscular Pokémon", PokemonType.FIGHTING, null, 1.2, 40, AbilityId.GUTS, AbilityId.SHEER_FORCE, AbilityId.IRON_FIST, 405, 85, 105, 85, 40, 50, 40, 90, 50, 142, GrowthRate.MEDIUM_SLOW, 75, false), + new PokemonSpecies(SpeciesId.CONKELDURR, 5, false, false, false, "Muscular Pokémon", PokemonType.FIGHTING, null, 1.4, 87, AbilityId.GUTS, AbilityId.SHEER_FORCE, AbilityId.IRON_FIST, 505, 105, 140, 95, 55, 65, 45, 45, 50, 253, GrowthRate.MEDIUM_SLOW, 75, false), + new PokemonSpecies(SpeciesId.TYMPOLE, 5, false, false, false, "Tadpole Pokémon", PokemonType.WATER, null, 0.5, 4.5, AbilityId.SWIFT_SWIM, AbilityId.HYDRATION, AbilityId.WATER_ABSORB, 294, 50, 50, 40, 50, 40, 64, 255, 50, 59, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.PALPITOAD, 5, false, false, false, "Vibration Pokémon", PokemonType.WATER, PokemonType.GROUND, 0.8, 17, AbilityId.SWIFT_SWIM, AbilityId.HYDRATION, AbilityId.WATER_ABSORB, 384, 75, 65, 55, 65, 55, 69, 120, 50, 134, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SEISMITOAD, 5, false, false, false, "Vibration Pokémon", PokemonType.WATER, PokemonType.GROUND, 1.5, 62, AbilityId.SWIFT_SWIM, AbilityId.POISON_TOUCH, AbilityId.WATER_ABSORB, 509, 105, 95, 75, 85, 75, 74, 45, 50, 255, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.THROH, 5, false, false, false, "Judo Pokémon", PokemonType.FIGHTING, null, 1.3, 55.5, AbilityId.GUTS, AbilityId.INNER_FOCUS, AbilityId.MOLD_BREAKER, 465, 120, 100, 85, 30, 85, 45, 45, 50, 163, GrowthRate.MEDIUM_FAST, 100, false), + new PokemonSpecies(SpeciesId.SAWK, 5, false, false, false, "Karate Pokémon", PokemonType.FIGHTING, null, 1.4, 51, AbilityId.STURDY, AbilityId.INNER_FOCUS, AbilityId.MOLD_BREAKER, 465, 75, 125, 75, 30, 75, 85, 45, 50, 163, GrowthRate.MEDIUM_FAST, 100, false), + new PokemonSpecies(SpeciesId.SEWADDLE, 5, false, false, false, "Sewing Pokémon", PokemonType.BUG, PokemonType.GRASS, 0.3, 2.5, AbilityId.SWARM, AbilityId.CHLOROPHYLL, AbilityId.OVERCOAT, 310, 45, 53, 70, 40, 60, 42, 255, 70, 62, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SWADLOON, 5, false, false, false, "Leaf-Wrapped Pokémon", PokemonType.BUG, PokemonType.GRASS, 0.5, 7.3, AbilityId.LEAF_GUARD, AbilityId.CHLOROPHYLL, AbilityId.OVERCOAT, 380, 55, 63, 90, 50, 80, 42, 120, 70, 133, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.LEAVANNY, 5, false, false, false, "Nurturing Pokémon", PokemonType.BUG, PokemonType.GRASS, 1.2, 20.5, AbilityId.SWARM, AbilityId.CHLOROPHYLL, AbilityId.OVERCOAT, 500, 75, 103, 80, 70, 80, 92, 45, 70, 250, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.VENIPEDE, 5, false, false, false, "Centipede Pokémon", PokemonType.BUG, PokemonType.POISON, 0.4, 5.3, AbilityId.POISON_POINT, AbilityId.SWARM, AbilityId.SPEED_BOOST, 260, 30, 45, 59, 30, 39, 57, 255, 50, 52, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.WHIRLIPEDE, 5, false, false, false, "Curlipede Pokémon", PokemonType.BUG, PokemonType.POISON, 1.2, 58.5, AbilityId.POISON_POINT, AbilityId.SWARM, AbilityId.SPEED_BOOST, 360, 40, 55, 99, 40, 79, 47, 120, 50, 126, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SCOLIPEDE, 5, false, false, false, "Megapede Pokémon", PokemonType.BUG, PokemonType.POISON, 2.5, 200.5, AbilityId.POISON_POINT, AbilityId.SWARM, AbilityId.SPEED_BOOST, 485, 60, 100, 89, 55, 69, 112, 45, 50, 243, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.COTTONEE, 5, false, false, false, "Cotton Puff Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 0.3, 0.6, AbilityId.PRANKSTER, AbilityId.INFILTRATOR, AbilityId.CHLOROPHYLL, 280, 40, 27, 60, 37, 50, 66, 190, 50, 56, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.WHIMSICOTT, 5, false, false, false, "Windveiled Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 0.7, 6.6, AbilityId.PRANKSTER, AbilityId.INFILTRATOR, AbilityId.CHLOROPHYLL, 480, 60, 67, 85, 77, 75, 116, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PETILIL, 5, false, false, false, "Bulb Pokémon", PokemonType.GRASS, null, 0.5, 6.6, AbilityId.CHLOROPHYLL, AbilityId.OWN_TEMPO, AbilityId.LEAF_GUARD, 280, 45, 35, 50, 70, 50, 30, 190, 50, 56, GrowthRate.MEDIUM_FAST, 0, false), + new PokemonSpecies(SpeciesId.LILLIGANT, 5, false, false, false, "Flowering Pokémon", PokemonType.GRASS, null, 1.1, 16.3, AbilityId.CHLOROPHYLL, AbilityId.OWN_TEMPO, AbilityId.LEAF_GUARD, 480, 70, 60, 75, 110, 75, 90, 75, 50, 168, GrowthRate.MEDIUM_FAST, 0, false), + new PokemonSpecies(SpeciesId.BASCULIN, 5, false, false, false, "Hostile Pokémon", PokemonType.WATER, null, 1, 18, AbilityId.RECKLESS, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Red-Striped Form", "red-striped", PokemonType.WATER, null, 1, 18, AbilityId.RECKLESS, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, false, null, true), + new PokemonForm("Blue-Striped Form", "blue-striped", PokemonType.WATER, null, 1, 18, AbilityId.ROCK_HEAD, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, false, null, true), + new PokemonForm("White-Striped Form", "white-striped", PokemonType.WATER, null, 1, 18, AbilityId.RATTLED, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, false, null, true) + ), + new PokemonSpecies(SpeciesId.SANDILE, 5, false, false, false, "Desert Croc Pokémon", PokemonType.GROUND, PokemonType.DARK, 0.7, 15.2, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 292, 50, 72, 35, 35, 35, 65, 180, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.KROKOROK, 5, false, false, false, "Desert Croc Pokémon", PokemonType.GROUND, PokemonType.DARK, 1, 33.4, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 351, 60, 82, 45, 45, 45, 74, 90, 50, 123, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.KROOKODILE, 5, false, false, false, "Intimidation Pokémon", PokemonType.GROUND, PokemonType.DARK, 1.5, 96.3, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 519, 95, 117, 80, 65, 70, 92, 45, 50, 260, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.DARUMAKA, 5, false, false, false, "Zen Charm Pokémon", PokemonType.FIRE, null, 0.6, 37.5, AbilityId.HUSTLE, AbilityId.NONE, AbilityId.INNER_FOCUS, 315, 70, 90, 45, 15, 45, 50, 120, 50, 63, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.DARMANITAN, 5, false, false, false, "Blazing Pokémon", PokemonType.FIRE, null, 1.3, 92.9, AbilityId.SHEER_FORCE, AbilityId.NONE, AbilityId.ZEN_MODE, 480, 105, 140, 55, 30, 55, 95, 60, 50, 168, GrowthRate.MEDIUM_SLOW, 50, false, true, + new PokemonForm("Standard Mode", "", PokemonType.FIRE, null, 1.3, 92.9, AbilityId.SHEER_FORCE, AbilityId.NONE, AbilityId.ZEN_MODE, 480, 105, 140, 55, 30, 55, 95, 60, 50, 168, false, null, true), + new PokemonForm("Zen Mode", "zen", PokemonType.FIRE, PokemonType.PSYCHIC, 1.3, 92.9, AbilityId.SHEER_FORCE, AbilityId.NONE, AbilityId.ZEN_MODE, 540, 105, 30, 105, 140, 105, 55, 60, 50, 189) + ), + new PokemonSpecies(SpeciesId.MARACTUS, 5, false, false, false, "Cactus Pokémon", PokemonType.GRASS, null, 1, 28, AbilityId.WATER_ABSORB, AbilityId.CHLOROPHYLL, AbilityId.STORM_DRAIN, 461, 75, 86, 67, 106, 67, 60, 255, 50, 161, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DWEBBLE, 5, false, false, false, "Rock Inn Pokémon", PokemonType.BUG, PokemonType.ROCK, 0.3, 14.5, AbilityId.STURDY, AbilityId.SHELL_ARMOR, AbilityId.WEAK_ARMOR, 325, 50, 65, 85, 35, 35, 55, 190, 50, 65, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CRUSTLE, 5, false, false, false, "Stone Home Pokémon", PokemonType.BUG, PokemonType.ROCK, 1.4, 200, AbilityId.STURDY, AbilityId.SHELL_ARMOR, AbilityId.WEAK_ARMOR, 485, 70, 105, 125, 65, 75, 45, 75, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SCRAGGY, 5, false, false, false, "Shedding Pokémon", PokemonType.DARK, PokemonType.FIGHTING, 0.6, 11.8, AbilityId.SHED_SKIN, AbilityId.MOXIE, AbilityId.INTIMIDATE, 348, 50, 75, 70, 35, 70, 48, 180, 35, 70, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SCRAFTY, 5, false, false, false, "Hoodlum Pokémon", PokemonType.DARK, PokemonType.FIGHTING, 1.1, 30, AbilityId.SHED_SKIN, AbilityId.MOXIE, AbilityId.INTIMIDATE, 488, 65, 90, 115, 45, 115, 58, 90, 50, 171, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SIGILYPH, 5, false, false, false, "Avianoid Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 1.4, 14, AbilityId.WONDER_SKIN, AbilityId.MAGIC_GUARD, AbilityId.TINTED_LENS, 490, 72, 58, 80, 103, 80, 97, 45, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.YAMASK, 5, false, false, false, "Spirit Pokémon", PokemonType.GHOST, null, 0.5, 1.5, AbilityId.MUMMY, AbilityId.NONE, AbilityId.NONE, 303, 38, 30, 85, 55, 65, 30, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.COFAGRIGUS, 5, false, false, false, "Coffin Pokémon", PokemonType.GHOST, null, 1.7, 76.5, AbilityId.MUMMY, AbilityId.NONE, AbilityId.NONE, 483, 58, 50, 145, 95, 105, 30, 90, 50, 169, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.TIRTOUGA, 5, false, false, false, "Prototurtle Pokémon", PokemonType.WATER, PokemonType.ROCK, 0.7, 16.5, AbilityId.SOLID_ROCK, AbilityId.STURDY, AbilityId.SWIFT_SWIM, 355, 54, 78, 103, 53, 45, 22, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.CARRACOSTA, 5, false, false, false, "Prototurtle Pokémon", PokemonType.WATER, PokemonType.ROCK, 1.2, 81, AbilityId.SOLID_ROCK, AbilityId.STURDY, AbilityId.SWIFT_SWIM, 495, 74, 108, 133, 83, 65, 32, 45, 50, 173, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.ARCHEN, 5, false, false, false, "First Bird Pokémon", PokemonType.ROCK, PokemonType.FLYING, 0.5, 9.5, AbilityId.DEFEATIST, AbilityId.NONE, AbilityId.WIMP_OUT, 401, 55, 112, 45, 74, 45, 70, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), //Custom Hidden + new PokemonSpecies(SpeciesId.ARCHEOPS, 5, false, false, false, "First Bird Pokémon", PokemonType.ROCK, PokemonType.FLYING, 1.4, 32, AbilityId.DEFEATIST, AbilityId.NONE, AbilityId.EMERGENCY_EXIT, 567, 75, 140, 65, 112, 65, 110, 45, 50, 177, GrowthRate.MEDIUM_FAST, 87.5, false), //Custom Hidden + new PokemonSpecies(SpeciesId.TRUBBISH, 5, false, false, false, "Trash Bag Pokémon", PokemonType.POISON, null, 0.6, 31, AbilityId.STENCH, AbilityId.STICKY_HOLD, AbilityId.AFTERMATH, 329, 50, 50, 62, 40, 62, 65, 190, 50, 66, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GARBODOR, 5, false, false, false, "Trash Heap Pokémon", PokemonType.POISON, null, 1.9, 107.3, AbilityId.STENCH, AbilityId.WEAK_ARMOR, AbilityId.AFTERMATH, 474, 80, 95, 82, 60, 82, 75, 60, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.POISON, null, 1.9, 107.3, AbilityId.STENCH, AbilityId.WEAK_ARMOR, AbilityId.AFTERMATH, 474, 80, 95, 82, 60, 82, 75, 60, 50, 166, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.POISON, PokemonType.STEEL, 21, 999.9, AbilityId.TOXIC_DEBRIS, AbilityId.TOXIC_DEBRIS, AbilityId.TOXIC_DEBRIS, 574, 115, 121, 102, 81, 102, 53, 60, 50, 166) + ), + new PokemonSpecies(SpeciesId.ZORUA, 5, false, false, false, "Tricky Fox Pokémon", PokemonType.DARK, null, 0.7, 12.5, AbilityId.ILLUSION, AbilityId.NONE, AbilityId.NONE, 330, 40, 65, 40, 80, 40, 65, 75, 50, 66, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.ZOROARK, 5, false, false, false, "Illusion Fox Pokémon", PokemonType.DARK, null, 1.6, 81.1, AbilityId.ILLUSION, AbilityId.NONE, AbilityId.NONE, 510, 60, 105, 60, 120, 60, 105, 45, 50, 179, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.MINCCINO, 5, false, false, false, "Chinchilla Pokémon", PokemonType.NORMAL, null, 0.4, 5.8, AbilityId.CUTE_CHARM, AbilityId.TECHNICIAN, AbilityId.SKILL_LINK, 300, 55, 50, 40, 40, 40, 75, 255, 50, 60, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.CINCCINO, 5, false, false, false, "Scarf Pokémon", PokemonType.NORMAL, null, 0.5, 7.5, AbilityId.CUTE_CHARM, AbilityId.TECHNICIAN, AbilityId.SKILL_LINK, 470, 75, 95, 60, 65, 60, 115, 60, 50, 165, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.GOTHITA, 5, false, false, false, "Fixation Pokémon", PokemonType.PSYCHIC, null, 0.4, 5.8, AbilityId.FRISK, AbilityId.COMPETITIVE, AbilityId.SHADOW_TAG, 290, 45, 30, 50, 55, 65, 45, 200, 50, 58, GrowthRate.MEDIUM_SLOW, 25, false), + new PokemonSpecies(SpeciesId.GOTHORITA, 5, false, false, false, "Manipulate Pokémon", PokemonType.PSYCHIC, null, 0.7, 18, AbilityId.FRISK, AbilityId.COMPETITIVE, AbilityId.SHADOW_TAG, 390, 60, 45, 70, 75, 85, 55, 100, 50, 137, GrowthRate.MEDIUM_SLOW, 25, false), + new PokemonSpecies(SpeciesId.GOTHITELLE, 5, false, false, false, "Astral Body Pokémon", PokemonType.PSYCHIC, null, 1.5, 44, AbilityId.FRISK, AbilityId.COMPETITIVE, AbilityId.SHADOW_TAG, 490, 70, 55, 95, 95, 110, 65, 50, 50, 245, GrowthRate.MEDIUM_SLOW, 25, false), + new PokemonSpecies(SpeciesId.SOLOSIS, 5, false, false, false, "Cell Pokémon", PokemonType.PSYCHIC, null, 0.3, 1, AbilityId.OVERCOAT, AbilityId.MAGIC_GUARD, AbilityId.REGENERATOR, 290, 45, 30, 40, 105, 50, 20, 200, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.DUOSION, 5, false, false, false, "Mitosis Pokémon", PokemonType.PSYCHIC, null, 0.6, 8, AbilityId.OVERCOAT, AbilityId.MAGIC_GUARD, AbilityId.REGENERATOR, 370, 65, 40, 50, 125, 60, 30, 100, 50, 130, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.REUNICLUS, 5, false, false, false, "Multiplying Pokémon", PokemonType.PSYCHIC, null, 1, 20.1, AbilityId.OVERCOAT, AbilityId.MAGIC_GUARD, AbilityId.REGENERATOR, 490, 110, 65, 75, 125, 85, 30, 50, 50, 245, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.DUCKLETT, 5, false, false, false, "Water Bird Pokémon", PokemonType.WATER, PokemonType.FLYING, 0.5, 5.5, AbilityId.KEEN_EYE, AbilityId.BIG_PECKS, AbilityId.HYDRATION, 305, 62, 44, 50, 44, 50, 55, 190, 70, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SWANNA, 5, false, false, false, "White Bird Pokémon", PokemonType.WATER, PokemonType.FLYING, 1.3, 24.2, AbilityId.KEEN_EYE, AbilityId.BIG_PECKS, AbilityId.HYDRATION, 473, 75, 87, 63, 87, 63, 98, 45, 70, 166, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.VANILLITE, 5, false, false, false, "Fresh Snow Pokémon", PokemonType.ICE, null, 0.4, 5.7, AbilityId.ICE_BODY, AbilityId.SNOW_CLOAK, AbilityId.WEAK_ARMOR, 305, 36, 50, 50, 65, 60, 44, 255, 50, 61, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.VANILLISH, 5, false, false, false, "Icy Snow Pokémon", PokemonType.ICE, null, 1.1, 41, AbilityId.ICE_BODY, AbilityId.SNOW_CLOAK, AbilityId.WEAK_ARMOR, 395, 51, 65, 65, 80, 75, 59, 120, 50, 138, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.VANILLUXE, 5, false, false, false, "Snowstorm Pokémon", PokemonType.ICE, null, 1.3, 57.5, AbilityId.ICE_BODY, AbilityId.SNOW_WARNING, AbilityId.WEAK_ARMOR, 535, 71, 95, 85, 110, 95, 79, 45, 50, 268, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.DEERLING, 5, false, false, false, "Season Pokémon", PokemonType.NORMAL, PokemonType.GRASS, 0.6, 19.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 335, 60, 60, 50, 40, 50, 75, 190, 70, 67, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Spring Form", "spring", PokemonType.NORMAL, PokemonType.GRASS, 0.6, 19.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 335, 60, 60, 50, 40, 50, 75, 190, 70, 67, false, null, true), + new PokemonForm("Summer Form", "summer", PokemonType.NORMAL, PokemonType.GRASS, 0.6, 19.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 335, 60, 60, 50, 40, 50, 75, 190, 70, 67, false, null, true), + new PokemonForm("Autumn Form", "autumn", PokemonType.NORMAL, PokemonType.GRASS, 0.6, 19.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 335, 60, 60, 50, 40, 50, 75, 190, 70, 67, false, null, true), + new PokemonForm("Winter Form", "winter", PokemonType.NORMAL, PokemonType.GRASS, 0.6, 19.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 335, 60, 60, 50, 40, 50, 75, 190, 70, 67, false, null, true) + ), + new PokemonSpecies(SpeciesId.SAWSBUCK, 5, false, false, false, "Season Pokémon", PokemonType.NORMAL, PokemonType.GRASS, 1.9, 92.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 475, 80, 100, 70, 60, 70, 95, 75, 70, 166, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Spring Form", "spring", PokemonType.NORMAL, PokemonType.GRASS, 1.9, 92.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 475, 80, 100, 70, 60, 70, 95, 75, 70, 166, false, null, true), + new PokemonForm("Summer Form", "summer", PokemonType.NORMAL, PokemonType.GRASS, 1.9, 92.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 475, 80, 100, 70, 60, 70, 95, 75, 70, 166, false, null, true), + new PokemonForm("Autumn Form", "autumn", PokemonType.NORMAL, PokemonType.GRASS, 1.9, 92.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 475, 80, 100, 70, 60, 70, 95, 75, 70, 166, false, null, true), + new PokemonForm("Winter Form", "winter", PokemonType.NORMAL, PokemonType.GRASS, 1.9, 92.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 475, 80, 100, 70, 60, 70, 95, 75, 70, 166, false, null, true) + ), + new PokemonSpecies(SpeciesId.EMOLGA, 5, false, false, false, "Sky Squirrel Pokémon", PokemonType.ELECTRIC, PokemonType.FLYING, 0.4, 5, AbilityId.STATIC, AbilityId.NONE, AbilityId.MOTOR_DRIVE, 428, 55, 75, 60, 75, 60, 103, 200, 50, 150, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.KARRABLAST, 5, false, false, false, "Clamping Pokémon", PokemonType.BUG, null, 0.5, 5.9, AbilityId.SWARM, AbilityId.SHED_SKIN, AbilityId.NO_GUARD, 315, 50, 75, 45, 40, 45, 60, 200, 50, 63, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ESCAVALIER, 5, false, false, false, "Cavalry Pokémon", PokemonType.BUG, PokemonType.STEEL, 1, 33, AbilityId.SWARM, AbilityId.SHELL_ARMOR, AbilityId.OVERCOAT, 495, 70, 135, 105, 60, 105, 20, 75, 50, 173, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.FOONGUS, 5, false, false, false, "Mushroom Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.2, 1, AbilityId.EFFECT_SPORE, AbilityId.NONE, AbilityId.REGENERATOR, 294, 69, 55, 45, 55, 55, 15, 190, 50, 59, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.AMOONGUSS, 5, false, false, false, "Mushroom Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.6, 10.5, AbilityId.EFFECT_SPORE, AbilityId.NONE, AbilityId.REGENERATOR, 464, 114, 85, 70, 85, 80, 30, 75, 50, 162, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.FRILLISH, 5, false, false, false, "Floating Pokémon", PokemonType.WATER, PokemonType.GHOST, 1.2, 33, AbilityId.WATER_ABSORB, AbilityId.CURSED_BODY, AbilityId.DAMP, 335, 55, 40, 50, 65, 85, 40, 190, 50, 67, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.JELLICENT, 5, false, false, false, "Floating Pokémon", PokemonType.WATER, PokemonType.GHOST, 2.2, 135, AbilityId.WATER_ABSORB, AbilityId.CURSED_BODY, AbilityId.DAMP, 480, 100, 60, 70, 85, 105, 60, 60, 50, 168, GrowthRate.MEDIUM_FAST, 50, true), + new PokemonSpecies(SpeciesId.ALOMOMOLA, 5, false, false, false, "Caring Pokémon", PokemonType.WATER, null, 1.2, 31.6, AbilityId.HEALER, AbilityId.HYDRATION, AbilityId.REGENERATOR, 470, 165, 75, 80, 40, 45, 65, 75, 70, 165, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.JOLTIK, 5, false, false, false, "Attaching Pokémon", PokemonType.BUG, PokemonType.ELECTRIC, 0.1, 0.6, AbilityId.COMPOUND_EYES, AbilityId.UNNERVE, AbilityId.SWARM, 319, 50, 47, 50, 57, 50, 65, 190, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GALVANTULA, 5, false, false, false, "EleSpider Pokémon", PokemonType.BUG, PokemonType.ELECTRIC, 0.8, 14.3, AbilityId.COMPOUND_EYES, AbilityId.UNNERVE, AbilityId.SWARM, 472, 70, 77, 60, 97, 60, 108, 75, 50, 165, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.FERROSEED, 5, false, false, false, "Thorn Seed Pokémon", PokemonType.GRASS, PokemonType.STEEL, 0.6, 18.8, AbilityId.IRON_BARBS, AbilityId.NONE, AbilityId.ANTICIPATION, 305, 44, 50, 91, 24, 86, 10, 255, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.FERROTHORN, 5, false, false, false, "Thorn Pod Pokémon", PokemonType.GRASS, PokemonType.STEEL, 1, 110, AbilityId.IRON_BARBS, AbilityId.NONE, AbilityId.ANTICIPATION, 489, 74, 94, 131, 54, 116, 20, 90, 50, 171, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.KLINK, 5, false, false, false, "Gear Pokémon", PokemonType.STEEL, null, 0.3, 21, AbilityId.PLUS, AbilityId.MINUS, AbilityId.CLEAR_BODY, 300, 40, 55, 70, 45, 60, 30, 130, 50, 60, GrowthRate.MEDIUM_SLOW, null, false), + new PokemonSpecies(SpeciesId.KLANG, 5, false, false, false, "Gear Pokémon", PokemonType.STEEL, null, 0.6, 51, AbilityId.PLUS, AbilityId.MINUS, AbilityId.CLEAR_BODY, 440, 60, 80, 95, 70, 85, 50, 60, 50, 154, GrowthRate.MEDIUM_SLOW, null, false), + new PokemonSpecies(SpeciesId.KLINKLANG, 5, false, false, false, "Gear Pokémon", PokemonType.STEEL, null, 0.6, 81, AbilityId.PLUS, AbilityId.MINUS, AbilityId.CLEAR_BODY, 520, 60, 100, 115, 70, 85, 90, 30, 50, 260, GrowthRate.MEDIUM_SLOW, null, false), + new PokemonSpecies(SpeciesId.TYNAMO, 5, false, false, false, "EleFish Pokémon", PokemonType.ELECTRIC, null, 0.2, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 275, 35, 55, 40, 45, 40, 60, 190, 70, 55, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.EELEKTRIK, 5, false, false, false, "EleFish Pokémon", PokemonType.ELECTRIC, null, 1.2, 22, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 405, 65, 85, 70, 75, 70, 40, 60, 70, 142, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.EELEKTROSS, 5, false, false, false, "EleFish Pokémon", PokemonType.ELECTRIC, null, 2.1, 80.5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 515, 85, 115, 80, 105, 80, 50, 30, 70, 258, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.ELGYEM, 5, false, false, false, "Cerebral Pokémon", PokemonType.PSYCHIC, null, 0.5, 9, AbilityId.TELEPATHY, AbilityId.SYNCHRONIZE, AbilityId.ANALYTIC, 335, 55, 55, 55, 85, 55, 30, 255, 50, 67, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BEHEEYEM, 5, false, false, false, "Cerebral Pokémon", PokemonType.PSYCHIC, null, 1, 34.5, AbilityId.TELEPATHY, AbilityId.SYNCHRONIZE, AbilityId.ANALYTIC, 485, 75, 75, 75, 125, 95, 40, 90, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.LITWICK, 5, false, false, false, "Candle Pokémon", PokemonType.GHOST, PokemonType.FIRE, 0.3, 3.1, AbilityId.FLASH_FIRE, AbilityId.FLAME_BODY, AbilityId.INFILTRATOR, 275, 50, 30, 55, 65, 55, 20, 190, 50, 55, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.LAMPENT, 5, false, false, false, "Lamp Pokémon", PokemonType.GHOST, PokemonType.FIRE, 0.6, 13, AbilityId.FLASH_FIRE, AbilityId.FLAME_BODY, AbilityId.INFILTRATOR, 370, 60, 40, 60, 95, 60, 55, 90, 50, 130, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.CHANDELURE, 5, false, false, false, "Luring Pokémon", PokemonType.GHOST, PokemonType.FIRE, 1, 34.3, AbilityId.FLASH_FIRE, AbilityId.FLAME_BODY, AbilityId.INFILTRATOR, 520, 60, 55, 90, 145, 90, 80, 45, 50, 260, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.AXEW, 5, false, false, false, "Tusk Pokémon", PokemonType.DRAGON, null, 0.6, 18, AbilityId.RIVALRY, AbilityId.MOLD_BREAKER, AbilityId.UNNERVE, 320, 46, 87, 60, 30, 40, 57, 75, 35, 64, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.FRAXURE, 5, false, false, false, "Axe Jaw Pokémon", PokemonType.DRAGON, null, 1, 36, AbilityId.RIVALRY, AbilityId.MOLD_BREAKER, AbilityId.UNNERVE, 410, 66, 117, 70, 40, 50, 67, 60, 35, 144, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.HAXORUS, 5, false, false, false, "Axe Jaw Pokémon", PokemonType.DRAGON, null, 1.8, 105.5, AbilityId.RIVALRY, AbilityId.MOLD_BREAKER, AbilityId.UNNERVE, 540, 76, 147, 90, 60, 70, 97, 45, 35, 270, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.CUBCHOO, 5, false, false, false, "Chill Pokémon", PokemonType.ICE, null, 0.5, 8.5, AbilityId.SNOW_CLOAK, AbilityId.SLUSH_RUSH, AbilityId.RATTLED, 305, 55, 70, 40, 60, 40, 40, 120, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BEARTIC, 5, false, false, false, "Freezing Pokémon", PokemonType.ICE, null, 2.6, 260, AbilityId.SNOW_CLOAK, AbilityId.SLUSH_RUSH, AbilityId.SWIFT_SWIM, 505, 95, 130, 80, 70, 80, 50, 60, 50, 177, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CRYOGONAL, 5, false, false, false, "Crystallizing Pokémon", PokemonType.ICE, null, 1.1, 148, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 515, 80, 50, 50, 95, 135, 105, 25, 50, 180, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.SHELMET, 5, false, false, false, "Snail Pokémon", PokemonType.BUG, null, 0.4, 7.7, AbilityId.HYDRATION, AbilityId.SHELL_ARMOR, AbilityId.OVERCOAT, 305, 50, 40, 85, 40, 65, 25, 200, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ACCELGOR, 5, false, false, false, "Shell Out Pokémon", PokemonType.BUG, null, 0.8, 25.3, AbilityId.HYDRATION, AbilityId.STICKY_HOLD, AbilityId.UNBURDEN, 495, 80, 70, 40, 100, 60, 145, 75, 50, 173, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.STUNFISK, 5, false, false, false, "Trap Pokémon", PokemonType.GROUND, PokemonType.ELECTRIC, 0.7, 11, AbilityId.STATIC, AbilityId.LIMBER, AbilityId.SAND_VEIL, 471, 109, 66, 84, 81, 99, 32, 75, 70, 165, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MIENFOO, 5, false, false, false, "Martial Arts Pokémon", PokemonType.FIGHTING, null, 0.9, 20, AbilityId.INNER_FOCUS, AbilityId.REGENERATOR, AbilityId.RECKLESS, 350, 45, 85, 50, 55, 50, 65, 180, 50, 70, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.MIENSHAO, 5, false, false, false, "Martial Arts Pokémon", PokemonType.FIGHTING, null, 1.4, 35.5, AbilityId.INNER_FOCUS, AbilityId.REGENERATOR, AbilityId.RECKLESS, 510, 65, 125, 60, 95, 60, 105, 45, 50, 179, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.DRUDDIGON, 5, false, false, false, "Cave Pokémon", PokemonType.DRAGON, null, 1.6, 139, AbilityId.ROUGH_SKIN, AbilityId.SHEER_FORCE, AbilityId.MOLD_BREAKER, 485, 77, 120, 90, 60, 90, 48, 45, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GOLETT, 5, false, false, false, "Automaton Pokémon", PokemonType.GROUND, PokemonType.GHOST, 1, 92, AbilityId.IRON_FIST, AbilityId.KLUTZ, AbilityId.NO_GUARD, 303, 59, 74, 50, 35, 50, 35, 190, 50, 61, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.GOLURK, 5, false, false, false, "Automaton Pokémon", PokemonType.GROUND, PokemonType.GHOST, 2.8, 330, AbilityId.IRON_FIST, AbilityId.KLUTZ, AbilityId.NO_GUARD, 483, 89, 124, 80, 55, 80, 55, 90, 50, 169, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.PAWNIARD, 5, false, false, false, "Sharp Blade Pokémon", PokemonType.DARK, PokemonType.STEEL, 0.5, 10.2, AbilityId.DEFIANT, AbilityId.INNER_FOCUS, AbilityId.PRESSURE, 340, 45, 85, 70, 40, 40, 60, 120, 35, 68, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BISHARP, 5, false, false, false, "Sword Blade Pokémon", PokemonType.DARK, PokemonType.STEEL, 1.6, 70, AbilityId.DEFIANT, AbilityId.INNER_FOCUS, AbilityId.PRESSURE, 490, 65, 125, 100, 60, 70, 70, 45, 35, 172, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BOUFFALANT, 5, false, false, false, "Bash Buffalo Pokémon", PokemonType.NORMAL, null, 1.6, 94.6, AbilityId.RECKLESS, AbilityId.SAP_SIPPER, AbilityId.SOUNDPROOF, 490, 95, 110, 95, 40, 95, 55, 45, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.RUFFLET, 5, false, false, false, "Eaglet Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.5, 10.5, AbilityId.KEEN_EYE, AbilityId.SHEER_FORCE, AbilityId.HUSTLE, 350, 70, 83, 50, 37, 50, 60, 190, 50, 70, GrowthRate.SLOW, 100, false), + new PokemonSpecies(SpeciesId.BRAVIARY, 5, false, false, false, "Valiant Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.5, 41, AbilityId.KEEN_EYE, AbilityId.SHEER_FORCE, AbilityId.DEFIANT, 510, 100, 123, 75, 57, 75, 80, 60, 50, 179, GrowthRate.SLOW, 100, false), + new PokemonSpecies(SpeciesId.VULLABY, 5, false, false, false, "Diapered Pokémon", PokemonType.DARK, PokemonType.FLYING, 0.5, 9, AbilityId.BIG_PECKS, AbilityId.OVERCOAT, AbilityId.WEAK_ARMOR, 370, 70, 55, 75, 45, 65, 60, 190, 35, 74, GrowthRate.SLOW, 0, false), + new PokemonSpecies(SpeciesId.MANDIBUZZ, 5, false, false, false, "Bone Vulture Pokémon", PokemonType.DARK, PokemonType.FLYING, 1.2, 39.5, AbilityId.BIG_PECKS, AbilityId.OVERCOAT, AbilityId.WEAK_ARMOR, 510, 110, 65, 105, 55, 95, 80, 60, 35, 179, GrowthRate.SLOW, 0, false), + new PokemonSpecies(SpeciesId.HEATMOR, 5, false, false, false, "Anteater Pokémon", PokemonType.FIRE, null, 1.4, 58, AbilityId.GLUTTONY, AbilityId.FLASH_FIRE, AbilityId.WHITE_SMOKE, 484, 85, 97, 66, 105, 66, 65, 90, 50, 169, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DURANT, 5, false, false, false, "Iron Ant Pokémon", PokemonType.BUG, PokemonType.STEEL, 0.3, 33, AbilityId.SWARM, AbilityId.HUSTLE, AbilityId.TRUANT, 484, 58, 109, 112, 48, 48, 109, 90, 50, 169, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DEINO, 5, false, false, false, "Irate Pokémon", PokemonType.DARK, PokemonType.DRAGON, 0.8, 17.3, AbilityId.HUSTLE, AbilityId.NONE, AbilityId.NONE, 300, 52, 65, 50, 45, 50, 38, 45, 35, 60, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.ZWEILOUS, 5, false, false, false, "Hostile Pokémon", PokemonType.DARK, PokemonType.DRAGON, 1.4, 50, AbilityId.HUSTLE, AbilityId.NONE, AbilityId.NONE, 420, 72, 85, 70, 65, 70, 58, 45, 35, 147, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.HYDREIGON, 5, false, false, false, "Brutal Pokémon", PokemonType.DARK, PokemonType.DRAGON, 1.8, 160, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 600, 92, 105, 90, 125, 90, 98, 45, 35, 300, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.LARVESTA, 5, false, false, false, "Torch Pokémon", PokemonType.BUG, PokemonType.FIRE, 1.1, 28.8, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.SWARM, 360, 55, 85, 55, 50, 55, 60, 45, 50, 72, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.VOLCARONA, 5, false, false, false, "Sun Pokémon", PokemonType.BUG, PokemonType.FIRE, 1.6, 46, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.SWARM, 550, 85, 60, 65, 135, 105, 100, 15, 50, 275, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.COBALION, 5, true, false, false, "Iron Will Pokémon", PokemonType.STEEL, PokemonType.FIGHTING, 2.1, 250, AbilityId.JUSTIFIED, AbilityId.NONE, AbilityId.NONE, 580, 91, 90, 129, 90, 72, 108, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.TERRAKION, 5, true, false, false, "Cavern Pokémon", PokemonType.ROCK, PokemonType.FIGHTING, 1.9, 260, AbilityId.JUSTIFIED, AbilityId.NONE, AbilityId.NONE, 580, 91, 129, 90, 72, 90, 108, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.VIRIZION, 5, true, false, false, "Grassland Pokémon", PokemonType.GRASS, PokemonType.FIGHTING, 2, 200, AbilityId.JUSTIFIED, AbilityId.NONE, AbilityId.NONE, 580, 91, 90, 72, 90, 129, 108, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.TORNADUS, 5, true, false, false, "Cyclone Pokémon", PokemonType.FLYING, null, 1.5, 63, AbilityId.PRANKSTER, AbilityId.NONE, AbilityId.DEFIANT, 580, 79, 115, 70, 125, 80, 111, 3, 90, 290, GrowthRate.SLOW, 100, false, true, + new PokemonForm("Incarnate Forme", "incarnate", PokemonType.FLYING, null, 1.5, 63, AbilityId.PRANKSTER, AbilityId.NONE, AbilityId.DEFIANT, 580, 79, 115, 70, 125, 80, 111, 3, 90, 290, false, null, true), + new PokemonForm("Therian Forme", "therian", PokemonType.FLYING, null, 1.4, 63, AbilityId.REGENERATOR, AbilityId.NONE, AbilityId.REGENERATOR, 580, 79, 100, 80, 110, 90, 121, 3, 90, 290) + ), + new PokemonSpecies(SpeciesId.THUNDURUS, 5, true, false, false, "Bolt Strike Pokémon", PokemonType.ELECTRIC, PokemonType.FLYING, 1.5, 61, AbilityId.PRANKSTER, AbilityId.NONE, AbilityId.DEFIANT, 580, 79, 115, 70, 125, 80, 111, 3, 90, 290, GrowthRate.SLOW, 100, false, true, + new PokemonForm("Incarnate Forme", "incarnate", PokemonType.ELECTRIC, PokemonType.FLYING, 1.5, 61, AbilityId.PRANKSTER, AbilityId.NONE, AbilityId.DEFIANT, 580, 79, 115, 70, 125, 80, 111, 3, 90, 290, false, null, true), + new PokemonForm("Therian Forme", "therian", PokemonType.ELECTRIC, PokemonType.FLYING, 3, 61, AbilityId.VOLT_ABSORB, AbilityId.NONE, AbilityId.VOLT_ABSORB, 580, 79, 105, 70, 145, 80, 101, 3, 90, 290) + ), + new PokemonSpecies(SpeciesId.RESHIRAM, 5, false, true, false, "Vast White Pokémon", PokemonType.DRAGON, PokemonType.FIRE, 3.2, 330, AbilityId.TURBOBLAZE, AbilityId.NONE, AbilityId.NONE, 680, 100, 120, 100, 150, 120, 90, 3, 0, 340, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.ZEKROM, 5, false, true, false, "Deep Black Pokémon", PokemonType.DRAGON, PokemonType.ELECTRIC, 2.9, 345, AbilityId.TERAVOLT, AbilityId.NONE, AbilityId.NONE, 680, 100, 150, 120, 120, 100, 90, 3, 0, 340, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.LANDORUS, 5, true, false, false, "Abundance Pokémon", PokemonType.GROUND, PokemonType.FLYING, 1.5, 68, AbilityId.SAND_FORCE, AbilityId.NONE, AbilityId.SHEER_FORCE, 600, 89, 125, 90, 115, 80, 101, 3, 90, 300, GrowthRate.SLOW, 100, false, true, + new PokemonForm("Incarnate Forme", "incarnate", PokemonType.GROUND, PokemonType.FLYING, 1.5, 68, AbilityId.SAND_FORCE, AbilityId.NONE, AbilityId.SHEER_FORCE, 600, 89, 125, 90, 115, 80, 101, 3, 90, 300, false, null, true), + new PokemonForm("Therian Forme", "therian", PokemonType.GROUND, PokemonType.FLYING, 1.3, 68, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.INTIMIDATE, 600, 89, 145, 90, 105, 80, 91, 3, 90, 300) + ), + new PokemonSpecies(SpeciesId.KYUREM, 5, false, true, false, "Boundary Pokémon", PokemonType.DRAGON, PokemonType.ICE, 3, 325, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 660, 125, 130, 90, 130, 90, 95, 3, 0, 330, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.ICE, 3, 325, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 660, 125, 130, 90, 130, 90, 95, 3, 0, 330, false, null, true), + new PokemonForm("Black", "black", PokemonType.DRAGON, PokemonType.ICE, 3.3, 325, AbilityId.TERAVOLT, AbilityId.NONE, AbilityId.NONE, 700, 125, 170, 100, 120, 90, 95, 3, 0, 350), + new PokemonForm("White", "white", PokemonType.DRAGON, PokemonType.ICE, 3.6, 325, AbilityId.TURBOBLAZE, AbilityId.NONE, AbilityId.NONE, 700, 125, 120, 90, 170, 100, 95, 3, 0, 350) + ), + new PokemonSpecies(SpeciesId.KELDEO, 5, false, false, true, "Colt Pokémon", PokemonType.WATER, PokemonType.FIGHTING, 1.4, 48.5, AbilityId.JUSTIFIED, AbilityId.NONE, AbilityId.NONE, 580, 91, 72, 90, 129, 90, 108, 3, 35, 290, GrowthRate.SLOW, null, false, true, + new PokemonForm("Ordinary Form", "ordinary", PokemonType.WATER, PokemonType.FIGHTING, 1.4, 48.5, AbilityId.JUSTIFIED, AbilityId.NONE, AbilityId.NONE, 580, 91, 72, 90, 129, 90, 108, 3, 35, 290, false, null, true), + new PokemonForm("Resolute", "resolute", PokemonType.WATER, PokemonType.FIGHTING, 1.4, 48.5, AbilityId.JUSTIFIED, AbilityId.NONE, AbilityId.NONE, 580, 91, 72, 90, 129, 90, 108, 3, 35, 290) + ), + new PokemonSpecies(SpeciesId.MELOETTA, 5, false, false, true, "Melody Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 0.6, 6.5, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 77, 77, 128, 128, 90, 3, 100, 300, GrowthRate.SLOW, null, false, true, + new PokemonForm("Aria Forme", "aria", PokemonType.NORMAL, PokemonType.PSYCHIC, 0.6, 6.5, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 77, 77, 128, 128, 90, 3, 100, 300, false, null, true), + new PokemonForm("Pirouette Forme", "pirouette", PokemonType.NORMAL, PokemonType.FIGHTING, 0.6, 6.5, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 128, 90, 77, 77, 128, 3, 100, 300, false, null, true) + ), + new PokemonSpecies(SpeciesId.GENESECT, 5, false, false, true, "Paleozoic Pokémon", PokemonType.BUG, PokemonType.STEEL, 1.5, 82.5, AbilityId.DOWNLOAD, AbilityId.NONE, AbilityId.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal", "", PokemonType.BUG, PokemonType.STEEL, 1.5, 82.5, AbilityId.DOWNLOAD, AbilityId.NONE, AbilityId.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300, false, null, true), + new PokemonForm("Shock Drive", "shock", PokemonType.BUG, PokemonType.STEEL, 1.5, 82.5, AbilityId.DOWNLOAD, AbilityId.NONE, AbilityId.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300), + new PokemonForm("Burn Drive", "burn", PokemonType.BUG, PokemonType.STEEL, 1.5, 82.5, AbilityId.DOWNLOAD, AbilityId.NONE, AbilityId.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300), + new PokemonForm("Chill Drive", "chill", PokemonType.BUG, PokemonType.STEEL, 1.5, 82.5, AbilityId.DOWNLOAD, AbilityId.NONE, AbilityId.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300), + new PokemonForm("Douse Drive", "douse", PokemonType.BUG, PokemonType.STEEL, 1.5, 82.5, AbilityId.DOWNLOAD, AbilityId.NONE, AbilityId.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300) + ), + new PokemonSpecies(SpeciesId.CHESPIN, 6, false, false, false, "Spiny Nut Pokémon", PokemonType.GRASS, null, 0.4, 9, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.BULLETPROOF, 313, 56, 61, 65, 48, 45, 38, 45, 70, 63, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.QUILLADIN, 6, false, false, false, "Spiny Armor Pokémon", PokemonType.GRASS, null, 0.7, 29, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.BULLETPROOF, 405, 61, 78, 95, 56, 58, 57, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.CHESNAUGHT, 6, false, false, false, "Spiny Armor Pokémon", PokemonType.GRASS, PokemonType.FIGHTING, 1.6, 90, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.BULLETPROOF, 530, 88, 107, 122, 74, 75, 64, 45, 70, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.FENNEKIN, 6, false, false, false, "Fox Pokémon", PokemonType.FIRE, null, 0.4, 9.4, AbilityId.BLAZE, AbilityId.NONE, AbilityId.MAGICIAN, 307, 40, 45, 40, 62, 60, 60, 45, 70, 61, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.BRAIXEN, 6, false, false, false, "Fox Pokémon", PokemonType.FIRE, null, 1, 14.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.MAGICIAN, 409, 59, 59, 58, 90, 70, 73, 45, 70, 143, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.DELPHOX, 6, false, false, false, "Fox Pokémon", PokemonType.FIRE, PokemonType.PSYCHIC, 1.5, 39, AbilityId.BLAZE, AbilityId.NONE, AbilityId.MAGICIAN, 534, 75, 69, 72, 114, 100, 104, 45, 70, 267, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.FROAKIE, 6, false, false, false, "Bubble Frog Pokémon", PokemonType.WATER, null, 0.3, 7, AbilityId.TORRENT, AbilityId.NONE, AbilityId.PROTEAN, 314, 41, 56, 40, 62, 44, 71, 45, 70, 63, GrowthRate.MEDIUM_SLOW, 87.5, false, false, + new PokemonForm("Normal", "", PokemonType.WATER, null, 0.3, 7, AbilityId.TORRENT, AbilityId.NONE, AbilityId.PROTEAN, 314, 41, 56, 40, 62, 44, 71, 45, 70, 63, false, null, true), + new PokemonForm("Battle Bond", "battle-bond", PokemonType.WATER, null, 0.3, 7, AbilityId.TORRENT, AbilityId.NONE, AbilityId.TORRENT, 314, 41, 56, 40, 62, 44, 71, 45, 70, 63, false, "", true) + ), + new PokemonSpecies(SpeciesId.FROGADIER, 6, false, false, false, "Bubble Frog Pokémon", PokemonType.WATER, null, 0.6, 10.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.PROTEAN, 405, 54, 63, 52, 83, 56, 97, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false, false, + new PokemonForm("Normal", "", PokemonType.WATER, null, 0.6, 10.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.PROTEAN, 405, 54, 63, 52, 83, 56, 97, 45, 70, 142, false, null, true), + new PokemonForm("Battle Bond", "battle-bond", PokemonType.WATER, null, 0.6, 10.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.NONE, 405, 54, 63, 52, 83, 56, 97, 45, 70, 142, false, "", true) + ), + new PokemonSpecies(SpeciesId.GRENINJA, 6, false, false, false, "Ninja Pokémon", PokemonType.WATER, PokemonType.DARK, 1.5, 40, AbilityId.TORRENT, AbilityId.NONE, AbilityId.PROTEAN, 530, 72, 95, 67, 103, 71, 122, 45, 70, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, false, + new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.DARK, 1.5, 40, AbilityId.TORRENT, AbilityId.NONE, AbilityId.PROTEAN, 530, 72, 95, 67, 103, 71, 122, 45, 70, 265, false, null, true), + new PokemonForm("Battle Bond", "battle-bond", PokemonType.WATER, PokemonType.DARK, 1.5, 40, AbilityId.BATTLE_BOND, AbilityId.NONE, AbilityId.BATTLE_BOND, 530, 72, 95, 67, 103, 71, 122, 45, 70, 265, false, "", true), + new PokemonForm("Ash", "ash", PokemonType.WATER, PokemonType.DARK, 1.5, 40, AbilityId.BATTLE_BOND, AbilityId.NONE, AbilityId.NONE, 640, 72, 145, 67, 153, 71, 132, 45, 70, 265) + ), + new PokemonSpecies(SpeciesId.BUNNELBY, 6, false, false, false, "Digging Pokémon", PokemonType.NORMAL, null, 0.4, 5, AbilityId.PICKUP, AbilityId.CHEEK_POUCH, AbilityId.HUGE_POWER, 237, 38, 36, 38, 32, 36, 57, 255, 50, 47, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DIGGERSBY, 6, false, false, false, "Digging Pokémon", PokemonType.NORMAL, PokemonType.GROUND, 1, 42.4, AbilityId.PICKUP, AbilityId.CHEEK_POUCH, AbilityId.HUGE_POWER, 423, 85, 56, 77, 50, 77, 78, 127, 50, 148, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.FLETCHLING, 6, false, false, false, "Tiny Robin Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 1.7, AbilityId.BIG_PECKS, AbilityId.NONE, AbilityId.GALE_WINGS, 278, 45, 50, 43, 40, 38, 62, 255, 50, 56, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.FLETCHINDER, 6, false, false, false, "Ember Pokémon", PokemonType.FIRE, PokemonType.FLYING, 0.7, 16, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.GALE_WINGS, 382, 62, 73, 55, 56, 52, 84, 120, 50, 134, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.TALONFLAME, 6, false, false, false, "Scorching Pokémon", PokemonType.FIRE, PokemonType.FLYING, 1.2, 24.5, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.GALE_WINGS, 499, 78, 81, 71, 74, 69, 126, 45, 50, 175, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SCATTERBUG, 6, false, false, false, "Scatterdust Pokémon", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Meadow Pattern", "meadow", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Icy Snow Pattern", "icy-snow", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Polar Pattern", "polar", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Tundra Pattern", "tundra", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Continental Pattern", "continental", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Garden Pattern", "garden", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Elegant Pattern", "elegant", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Modern Pattern", "modern", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Marine Pattern", "marine", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Archipelago Pattern", "archipelago", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("High Plains Pattern", "high-plains", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Sandstorm Pattern", "sandstorm", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("River Pattern", "river", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Monsoon Pattern", "monsoon", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Savanna Pattern", "savanna", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Sun Pattern", "sun", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Ocean Pattern", "ocean", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Jungle Pattern", "jungle", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Fancy Pattern", "fancy", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), + new PokemonForm("Poké Ball Pattern", "poke-ball", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true) + ), + new PokemonSpecies(SpeciesId.SPEWPA, 6, false, false, false, "Scatterdust Pokémon", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.SHED_SKIN, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Meadow Pattern", "meadow", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Icy Snow Pattern", "icy-snow", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Polar Pattern", "polar", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Tundra Pattern", "tundra", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Continental Pattern", "continental", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Garden Pattern", "garden", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Elegant Pattern", "elegant", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Modern Pattern", "modern", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Marine Pattern", "marine", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Archipelago Pattern", "archipelago", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("High Plains Pattern", "high-plains", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Sandstorm Pattern", "sandstorm", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("River Pattern", "river", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Monsoon Pattern", "monsoon", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Savanna Pattern", "savanna", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Sun Pattern", "sun", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Ocean Pattern", "ocean", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Jungle Pattern", "jungle", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Fancy Pattern", "fancy", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), + new PokemonForm("Poké Ball Pattern", "poke-ball", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true) + ), + new PokemonSpecies(SpeciesId.VIVILLON, 6, false, false, false, "Scale Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Meadow Pattern", "meadow", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Icy Snow Pattern", "icy-snow", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Polar Pattern", "polar", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Tundra Pattern", "tundra", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Continental Pattern", "continental", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Garden Pattern", "garden", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Elegant Pattern", "elegant", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Modern Pattern", "modern", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Marine Pattern", "marine", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Archipelago Pattern", "archipelago", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("High Plains Pattern", "high-plains", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Sandstorm Pattern", "sandstorm", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("River Pattern", "river", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Monsoon Pattern", "monsoon", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Savanna Pattern", "savanna", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Sun Pattern", "sun", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Ocean Pattern", "ocean", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Jungle Pattern", "jungle", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Fancy Pattern", "fancy", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), + new PokemonForm("Poké Ball Pattern", "poke-ball", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true) + ), + new PokemonSpecies(SpeciesId.LITLEO, 6, false, false, false, "Lion Cub Pokémon", PokemonType.FIRE, PokemonType.NORMAL, 0.6, 13.5, AbilityId.RIVALRY, AbilityId.UNNERVE, AbilityId.MOXIE, 369, 62, 50, 58, 73, 54, 72, 220, 70, 74, GrowthRate.MEDIUM_SLOW, 12.5, false), + new PokemonSpecies(SpeciesId.PYROAR, 6, false, false, false, "Royal Pokémon", PokemonType.FIRE, PokemonType.NORMAL, 1.5, 81.5, AbilityId.RIVALRY, AbilityId.UNNERVE, AbilityId.MOXIE, 507, 86, 68, 72, 109, 66, 106, 65, 70, 177, GrowthRate.MEDIUM_SLOW, 12.5, true), + new PokemonSpecies(SpeciesId.FLABEBE, 6, false, false, false, "Single Bloom Pokémon", PokemonType.FAIRY, null, 0.1, 0.1, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 303, 44, 38, 39, 61, 79, 42, 225, 70, 61, GrowthRate.MEDIUM_FAST, 0, false, false, + new PokemonForm("Red Flower", "red", PokemonType.FAIRY, null, 0.1, 0.1, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 303, 44, 38, 39, 61, 79, 42, 225, 70, 61, false, null, true), + new PokemonForm("Yellow Flower", "yellow", PokemonType.FAIRY, null, 0.1, 0.1, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 303, 44, 38, 39, 61, 79, 42, 225, 70, 61, false, null, true), + new PokemonForm("Orange Flower", "orange", PokemonType.FAIRY, null, 0.1, 0.1, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 303, 44, 38, 39, 61, 79, 42, 225, 70, 61, false, null, true), + new PokemonForm("Blue Flower", "blue", PokemonType.FAIRY, null, 0.1, 0.1, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 303, 44, 38, 39, 61, 79, 42, 225, 70, 61, false, null, true), + new PokemonForm("White Flower", "white", PokemonType.FAIRY, null, 0.1, 0.1, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 303, 44, 38, 39, 61, 79, 42, 225, 70, 61, false, null, true) + ), + new PokemonSpecies(SpeciesId.FLOETTE, 6, false, false, false, "Single Bloom Pokémon", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, GrowthRate.MEDIUM_FAST, 0, false, false, + new PokemonForm("Red Flower", "red", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, false, null, true), + new PokemonForm("Yellow Flower", "yellow", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, false, null, true), + new PokemonForm("Orange Flower", "orange", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, false, null, true), + new PokemonForm("Blue Flower", "blue", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, false, null, true), + new PokemonForm("White Flower", "white", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, false, null, true) + ), + new PokemonSpecies(SpeciesId.FLORGES, 6, false, false, false, "Garden Pokémon", PokemonType.FAIRY, null, 1.1, 10, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, GrowthRate.MEDIUM_FAST, 0, false, false, + new PokemonForm("Red Flower", "red", PokemonType.FAIRY, null, 1.1, 10, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), + new PokemonForm("Yellow Flower", "yellow", PokemonType.FAIRY, null, 1.1, 10, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), + new PokemonForm("Orange Flower", "orange", PokemonType.FAIRY, null, 1.1, 10, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), + new PokemonForm("Blue Flower", "blue", PokemonType.FAIRY, null, 1.1, 10, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), + new PokemonForm("White Flower", "white", PokemonType.FAIRY, null, 1.1, 10, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true) + ), + new PokemonSpecies(SpeciesId.SKIDDO, 6, false, false, false, "Mount Pokémon", PokemonType.GRASS, null, 0.9, 31, AbilityId.SAP_SIPPER, AbilityId.NONE, AbilityId.GRASS_PELT, 350, 66, 65, 48, 62, 57, 52, 200, 70, 70, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GOGOAT, 6, false, false, false, "Mount Pokémon", PokemonType.GRASS, null, 1.7, 91, AbilityId.SAP_SIPPER, AbilityId.NONE, AbilityId.GRASS_PELT, 531, 123, 100, 62, 97, 81, 68, 45, 70, 186, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PANCHAM, 6, false, false, false, "Playful Pokémon", PokemonType.FIGHTING, null, 0.6, 8, AbilityId.IRON_FIST, AbilityId.MOLD_BREAKER, AbilityId.SCRAPPY, 348, 67, 82, 62, 46, 48, 43, 220, 50, 70, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PANGORO, 6, false, false, false, "Daunting Pokémon", PokemonType.FIGHTING, PokemonType.DARK, 2.1, 136, AbilityId.IRON_FIST, AbilityId.MOLD_BREAKER, AbilityId.SCRAPPY, 495, 95, 124, 78, 69, 71, 58, 65, 50, 173, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.FURFROU, 6, false, false, false, "Poodle Pokémon", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Natural Form", "", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), + new PokemonForm("Heart Trim", "heart", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), + new PokemonForm("Star Trim", "star", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), + new PokemonForm("Diamond Trim", "diamond", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), + new PokemonForm("Debutante Trim", "debutante", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), + new PokemonForm("Matron Trim", "matron", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), + new PokemonForm("Dandy Trim", "dandy", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), + new PokemonForm("La Reine Trim", "la-reine", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), + new PokemonForm("Kabuki Trim", "kabuki", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), + new PokemonForm("Pharaoh Trim", "pharaoh", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true) + ), + new PokemonSpecies(SpeciesId.ESPURR, 6, false, false, false, "Restraint Pokémon", PokemonType.PSYCHIC, null, 0.3, 3.5, AbilityId.KEEN_EYE, AbilityId.INFILTRATOR, AbilityId.OWN_TEMPO, 355, 62, 48, 54, 63, 60, 68, 190, 50, 71, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MEOWSTIC, 6, false, false, false, "Constraint Pokémon", PokemonType.PSYCHIC, null, 0.6, 8.5, AbilityId.KEEN_EYE, AbilityId.INFILTRATOR, AbilityId.PRANKSTER, 466, 74, 48, 76, 83, 81, 104, 75, 50, 163, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Male", "male", PokemonType.PSYCHIC, null, 0.6, 8.5, AbilityId.KEEN_EYE, AbilityId.INFILTRATOR, AbilityId.PRANKSTER, 466, 74, 48, 76, 83, 81, 104, 75, 50, 163, false, "", true), + new PokemonForm("Female", "female", PokemonType.PSYCHIC, null, 0.6, 8.5, AbilityId.KEEN_EYE, AbilityId.INFILTRATOR, AbilityId.COMPETITIVE, 466, 74, 48, 76, 83, 81, 104, 75, 50, 163, false, null, true) + ), + new PokemonSpecies(SpeciesId.HONEDGE, 6, false, false, false, "Sword Pokémon", PokemonType.STEEL, PokemonType.GHOST, 0.8, 2, AbilityId.NO_GUARD, AbilityId.NONE, AbilityId.NONE, 325, 45, 80, 100, 35, 37, 28, 180, 50, 65, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DOUBLADE, 6, false, false, false, "Sword Pokémon", PokemonType.STEEL, PokemonType.GHOST, 0.8, 4.5, AbilityId.NO_GUARD, AbilityId.NONE, AbilityId.NONE, 448, 59, 110, 150, 45, 49, 35, 90, 50, 157, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.AEGISLASH, 6, false, false, false, "Royal Sword Pokémon", PokemonType.STEEL, PokemonType.GHOST, 1.7, 53, AbilityId.STANCE_CHANGE, AbilityId.NONE, AbilityId.NONE, 500, 60, 50, 140, 50, 140, 60, 45, 50, 250, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Shield Forme", "shield", PokemonType.STEEL, PokemonType.GHOST, 1.7, 53, AbilityId.STANCE_CHANGE, AbilityId.NONE, AbilityId.NONE, 500, 60, 50, 140, 50, 140, 60, 45, 50, 250, false, "", true), + new PokemonForm("Blade Forme", "blade", PokemonType.STEEL, PokemonType.GHOST, 1.7, 53, AbilityId.STANCE_CHANGE, AbilityId.NONE, AbilityId.NONE, 500, 60, 140, 50, 140, 50, 60, 45, 50, 250) + ), + new PokemonSpecies(SpeciesId.SPRITZEE, 6, false, false, false, "Perfume Pokémon", PokemonType.FAIRY, null, 0.2, 0.5, AbilityId.HEALER, AbilityId.NONE, AbilityId.AROMA_VEIL, 341, 78, 52, 60, 63, 65, 23, 200, 50, 68, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.AROMATISSE, 6, false, false, false, "Fragrance Pokémon", PokemonType.FAIRY, null, 0.8, 15.5, AbilityId.HEALER, AbilityId.NONE, AbilityId.AROMA_VEIL, 462, 101, 72, 72, 99, 89, 29, 140, 50, 162, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SWIRLIX, 6, false, false, false, "Cotton Candy Pokémon", PokemonType.FAIRY, null, 0.4, 3.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.UNBURDEN, 341, 62, 48, 66, 59, 57, 49, 200, 50, 68, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SLURPUFF, 6, false, false, false, "Meringue Pokémon", PokemonType.FAIRY, null, 0.8, 5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.UNBURDEN, 480, 82, 80, 86, 85, 75, 72, 140, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.INKAY, 6, false, false, false, "Revolving Pokémon", PokemonType.DARK, PokemonType.PSYCHIC, 0.4, 3.5, AbilityId.CONTRARY, AbilityId.SUCTION_CUPS, AbilityId.INFILTRATOR, 288, 53, 54, 53, 37, 46, 45, 190, 50, 58, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MALAMAR, 6, false, false, false, "Overturning Pokémon", PokemonType.DARK, PokemonType.PSYCHIC, 1.5, 47, AbilityId.CONTRARY, AbilityId.SUCTION_CUPS, AbilityId.INFILTRATOR, 482, 86, 92, 88, 68, 75, 73, 80, 50, 169, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BINACLE, 6, false, false, false, "Two-Handed Pokémon", PokemonType.ROCK, PokemonType.WATER, 0.5, 31, AbilityId.TOUGH_CLAWS, AbilityId.SNIPER, AbilityId.PICKPOCKET, 306, 42, 52, 67, 39, 56, 50, 120, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BARBARACLE, 6, false, false, false, "Collective Pokémon", PokemonType.ROCK, PokemonType.WATER, 1.3, 96, AbilityId.TOUGH_CLAWS, AbilityId.SNIPER, AbilityId.PICKPOCKET, 500, 72, 105, 115, 54, 86, 68, 45, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SKRELP, 6, false, false, false, "Mock Kelp Pokémon", PokemonType.POISON, PokemonType.WATER, 0.5, 7.3, AbilityId.POISON_POINT, AbilityId.POISON_TOUCH, AbilityId.ADAPTABILITY, 320, 50, 60, 60, 60, 60, 30, 225, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DRAGALGE, 6, false, false, false, "Mock Kelp Pokémon", PokemonType.POISON, PokemonType.DRAGON, 1.8, 81.5, AbilityId.POISON_POINT, AbilityId.POISON_TOUCH, AbilityId.ADAPTABILITY, 494, 65, 75, 90, 97, 123, 44, 55, 50, 173, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CLAUNCHER, 6, false, false, false, "Water Gun Pokémon", PokemonType.WATER, null, 0.5, 8.3, AbilityId.MEGA_LAUNCHER, AbilityId.NONE, AbilityId.NONE, 330, 50, 53, 62, 58, 63, 44, 225, 50, 66, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.CLAWITZER, 6, false, false, false, "Howitzer Pokémon", PokemonType.WATER, null, 1.3, 35.3, AbilityId.MEGA_LAUNCHER, AbilityId.NONE, AbilityId.NONE, 500, 71, 73, 88, 120, 89, 59, 55, 50, 100, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.HELIOPTILE, 6, false, false, false, "Generator Pokémon", PokemonType.ELECTRIC, PokemonType.NORMAL, 0.5, 6, AbilityId.DRY_SKIN, AbilityId.SAND_VEIL, AbilityId.SOLAR_POWER, 289, 44, 38, 33, 61, 43, 70, 190, 50, 58, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.HELIOLISK, 6, false, false, false, "Generator Pokémon", PokemonType.ELECTRIC, PokemonType.NORMAL, 1, 21, AbilityId.DRY_SKIN, AbilityId.SAND_VEIL, AbilityId.SOLAR_POWER, 481, 62, 55, 52, 109, 94, 109, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.TYRUNT, 6, false, false, false, "Royal Heir Pokémon", PokemonType.ROCK, PokemonType.DRAGON, 0.8, 26, AbilityId.STRONG_JAW, AbilityId.NONE, AbilityId.STURDY, 362, 58, 89, 77, 45, 45, 48, 45, 50, 72, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.TYRANTRUM, 6, false, false, false, "Despot Pokémon", PokemonType.ROCK, PokemonType.DRAGON, 2.5, 270, AbilityId.STRONG_JAW, AbilityId.NONE, AbilityId.ROCK_HEAD, 521, 82, 121, 119, 69, 59, 71, 45, 50, 182, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.AMAURA, 6, false, false, false, "Tundra Pokémon", PokemonType.ROCK, PokemonType.ICE, 1.3, 25.2, AbilityId.REFRIGERATE, AbilityId.NONE, AbilityId.SNOW_WARNING, 362, 77, 59, 50, 67, 63, 46, 45, 50, 72, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.AURORUS, 6, false, false, false, "Tundra Pokémon", PokemonType.ROCK, PokemonType.ICE, 2.7, 225, AbilityId.REFRIGERATE, AbilityId.NONE, AbilityId.SNOW_WARNING, 521, 123, 77, 72, 99, 92, 58, 45, 50, 104, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.SYLVEON, 6, false, false, false, "Intertwining Pokémon", PokemonType.FAIRY, null, 1, 23.5, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.PIXILATE, 525, 95, 65, 65, 110, 130, 60, 45, 50, 184, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.HAWLUCHA, 6, false, false, false, "Wrestling Pokémon", PokemonType.FIGHTING, PokemonType.FLYING, 0.8, 21.5, AbilityId.LIMBER, AbilityId.UNBURDEN, AbilityId.MOLD_BREAKER, 500, 78, 92, 75, 74, 63, 118, 100, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DEDENNE, 6, false, false, false, "Antenna Pokémon", PokemonType.ELECTRIC, PokemonType.FAIRY, 0.2, 2.2, AbilityId.CHEEK_POUCH, AbilityId.PICKUP, AbilityId.PLUS, 431, 67, 58, 57, 81, 67, 101, 180, 50, 151, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CARBINK, 6, false, false, false, "Jewel Pokémon", PokemonType.ROCK, PokemonType.FAIRY, 0.3, 5.7, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.STURDY, 500, 50, 50, 150, 50, 150, 50, 60, 50, 100, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.GOOMY, 6, false, false, false, "Soft Tissue Pokémon", PokemonType.DRAGON, null, 0.3, 2.8, AbilityId.SAP_SIPPER, AbilityId.HYDRATION, AbilityId.GOOEY, 300, 45, 50, 35, 55, 75, 40, 45, 35, 60, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.SLIGGOO, 6, false, false, false, "Soft Tissue Pokémon", PokemonType.DRAGON, null, 0.8, 17.5, AbilityId.SAP_SIPPER, AbilityId.HYDRATION, AbilityId.GOOEY, 452, 68, 75, 53, 83, 113, 60, 45, 35, 158, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.GOODRA, 6, false, false, false, "Dragon Pokémon", PokemonType.DRAGON, null, 2, 150.5, AbilityId.SAP_SIPPER, AbilityId.HYDRATION, AbilityId.GOOEY, 600, 90, 100, 70, 110, 150, 80, 45, 35, 300, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.KLEFKI, 6, false, false, false, "Key Ring Pokémon", PokemonType.STEEL, PokemonType.FAIRY, 0.2, 3, AbilityId.PRANKSTER, AbilityId.NONE, AbilityId.MAGICIAN, 470, 57, 80, 91, 80, 87, 75, 75, 50, 165, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.PHANTUMP, 6, false, false, false, "Stump Pokémon", PokemonType.GHOST, PokemonType.GRASS, 0.4, 7, AbilityId.NATURAL_CURE, AbilityId.FRISK, AbilityId.HARVEST, 309, 43, 70, 48, 50, 60, 38, 120, 50, 62, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.TREVENANT, 6, false, false, false, "Elder Tree Pokémon", PokemonType.GHOST, PokemonType.GRASS, 1.5, 71, AbilityId.NATURAL_CURE, AbilityId.FRISK, AbilityId.HARVEST, 474, 85, 110, 76, 65, 82, 56, 60, 50, 166, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PUMPKABOO, 6, false, false, false, "Pumpkin Pokémon", PokemonType.GHOST, PokemonType.GRASS, 0.4, 5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 335, 49, 66, 70, 44, 55, 51, 120, 50, 67, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Average Size", "", PokemonType.GHOST, PokemonType.GRASS, 0.4, 5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 335, 49, 66, 70, 44, 55, 51, 120, 50, 67, false, null, true), + new PokemonForm("Small Size", "small", PokemonType.GHOST, PokemonType.GRASS, 0.3, 3.5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 335, 44, 66, 70, 44, 55, 56, 120, 50, 67, false, "", true), + new PokemonForm("Large Size", "large", PokemonType.GHOST, PokemonType.GRASS, 0.5, 7.5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 335, 54, 66, 70, 44, 55, 46, 120, 50, 67, false, "", true), + new PokemonForm("Super Size", "super", PokemonType.GHOST, PokemonType.GRASS, 0.8, 15, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 335, 59, 66, 70, 44, 55, 41, 120, 50, 67, false, "", true) + ), + new PokemonSpecies(SpeciesId.GOURGEIST, 6, false, false, false, "Pumpkin Pokémon", PokemonType.GHOST, PokemonType.GRASS, 0.9, 12.5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 494, 65, 90, 122, 58, 75, 84, 60, 50, 173, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Average Size", "", PokemonType.GHOST, PokemonType.GRASS, 0.9, 12.5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 494, 65, 90, 122, 58, 75, 84, 60, 50, 173, false, null, true), + new PokemonForm("Small Size", "small", PokemonType.GHOST, PokemonType.GRASS, 0.7, 9.5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 494, 55, 85, 122, 58, 75, 99, 60, 50, 173, false, "", true), + new PokemonForm("Large Size", "large", PokemonType.GHOST, PokemonType.GRASS, 1.1, 14, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 494, 75, 95, 122, 58, 75, 69, 60, 50, 173, false, "", true), + new PokemonForm("Super Size", "super", PokemonType.GHOST, PokemonType.GRASS, 1.7, 39, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 494, 85, 100, 122, 58, 75, 54, 60, 50, 173, false, "", true) + ), + new PokemonSpecies(SpeciesId.BERGMITE, 6, false, false, false, "Ice Chunk Pokémon", PokemonType.ICE, null, 1, 99.5, AbilityId.OWN_TEMPO, AbilityId.ICE_BODY, AbilityId.STURDY, 304, 55, 69, 85, 32, 35, 28, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.AVALUGG, 6, false, false, false, "Iceberg Pokémon", PokemonType.ICE, null, 2, 505, AbilityId.OWN_TEMPO, AbilityId.ICE_BODY, AbilityId.STURDY, 514, 95, 117, 184, 44, 46, 28, 55, 50, 180, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.NOIBAT, 6, false, false, false, "Sound Wave Pokémon", PokemonType.FLYING, PokemonType.DRAGON, 0.5, 8, AbilityId.FRISK, AbilityId.INFILTRATOR, AbilityId.TELEPATHY, 245, 40, 30, 35, 45, 40, 55, 190, 50, 49, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.NOIVERN, 6, false, false, false, "Sound Wave Pokémon", PokemonType.FLYING, PokemonType.DRAGON, 1.5, 85, AbilityId.FRISK, AbilityId.INFILTRATOR, AbilityId.TELEPATHY, 535, 85, 70, 80, 97, 80, 123, 45, 50, 187, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.XERNEAS, 6, false, true, false, "Life Pokémon", PokemonType.FAIRY, null, 3, 215, AbilityId.FAIRY_AURA, AbilityId.NONE, AbilityId.NONE, 680, 126, 131, 95, 131, 98, 99, 45, 0, 340, GrowthRate.SLOW, null, false, true, + new PokemonForm("Neutral Mode", "neutral", PokemonType.FAIRY, null, 3, 215, AbilityId.FAIRY_AURA, AbilityId.NONE, AbilityId.NONE, 680, 126, 131, 95, 131, 98, 99, 45, 0, 340, false, null, true), + new PokemonForm("Active Mode", "active", PokemonType.FAIRY, null, 3, 215, AbilityId.FAIRY_AURA, AbilityId.NONE, AbilityId.NONE, 680, 126, 131, 95, 131, 98, 99, 45, 0, 340) + ), + new PokemonSpecies(SpeciesId.YVELTAL, 6, false, true, false, "Destruction Pokémon", PokemonType.DARK, PokemonType.FLYING, 5.8, 203, AbilityId.DARK_AURA, AbilityId.NONE, AbilityId.NONE, 680, 126, 131, 95, 131, 98, 99, 45, 0, 340, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.ZYGARDE, 6, false, true, false, "Order Pokémon", PokemonType.DRAGON, PokemonType.GROUND, 5, 305, AbilityId.AURA_BREAK, AbilityId.NONE, AbilityId.NONE, 600, 108, 100, 121, 81, 95, 95, 3, 0, 300, GrowthRate.SLOW, null, false, false, + new PokemonForm("50% Forme", "50", PokemonType.DRAGON, PokemonType.GROUND, 5, 305, AbilityId.AURA_BREAK, AbilityId.NONE, AbilityId.NONE, 600, 108, 100, 121, 81, 95, 95, 3, 0, 300, false, "", true), + new PokemonForm("10% Forme", "10", PokemonType.DRAGON, PokemonType.GROUND, 1.2, 33.5, AbilityId.AURA_BREAK, AbilityId.NONE, AbilityId.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 243, false, null, true), + new PokemonForm("50% Forme Power Construct", "50-pc", PokemonType.DRAGON, PokemonType.GROUND, 5, 305, AbilityId.POWER_CONSTRUCT, AbilityId.NONE, AbilityId.NONE, 600, 108, 100, 121, 81, 95, 95, 3, 0, 300, false, "", true), + new PokemonForm("10% Forme Power Construct", "10-pc", PokemonType.DRAGON, PokemonType.GROUND, 1.2, 33.5, AbilityId.POWER_CONSTRUCT, AbilityId.NONE, AbilityId.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 243, false, "10", true), + new PokemonForm("Complete Forme (50% PC)", "complete", PokemonType.DRAGON, PokemonType.GROUND, 4.5, 610, AbilityId.POWER_CONSTRUCT, AbilityId.NONE, AbilityId.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 354), + new PokemonForm("Complete Forme (10% PC)", "10-complete", PokemonType.DRAGON, PokemonType.GROUND, 4.5, 610, AbilityId.POWER_CONSTRUCT, AbilityId.NONE, AbilityId.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 354, false, "complete") + ), + new PokemonSpecies(SpeciesId.DIANCIE, 6, false, false, true, "Jewel Pokémon", PokemonType.ROCK, PokemonType.FAIRY, 0.7, 8.8, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal", "", PokemonType.ROCK, PokemonType.FAIRY, 0.7, 8.8, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, false, null, true), + new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.ROCK, PokemonType.FAIRY, 1.1, 27.8, AbilityId.MAGIC_BOUNCE, AbilityId.NONE, AbilityId.NONE, 700, 50, 160, 110, 160, 110, 110, 3, 50, 300) + ), + new PokemonSpecies(SpeciesId.HOOPA, 6, false, false, true, "Mischief Pokémon", PokemonType.PSYCHIC, PokemonType.GHOST, 0.5, 9, AbilityId.MAGICIAN, AbilityId.NONE, AbilityId.NONE, 600, 80, 110, 60, 150, 130, 70, 3, 100, 300, GrowthRate.SLOW, null, false, false, + new PokemonForm("Hoopa Confined", "", PokemonType.PSYCHIC, PokemonType.GHOST, 0.5, 9, AbilityId.MAGICIAN, AbilityId.NONE, AbilityId.NONE, 600, 80, 110, 60, 150, 130, 70, 3, 100, 300, false, null, true), + new PokemonForm("Hoopa Unbound", "unbound", PokemonType.PSYCHIC, PokemonType.DARK, 6.5, 490, AbilityId.MAGICIAN, AbilityId.NONE, AbilityId.NONE, 680, 80, 160, 60, 170, 130, 80, 3, 100, 340) + ), + new PokemonSpecies(SpeciesId.VOLCANION, 6, false, false, true, "Steam Pokémon", PokemonType.FIRE, PokemonType.WATER, 1.7, 195, AbilityId.WATER_ABSORB, AbilityId.NONE, AbilityId.NONE, 600, 80, 110, 120, 130, 90, 70, 3, 100, 300, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.ROWLET, 7, false, false, false, "Grass Quill Pokémon", PokemonType.GRASS, PokemonType.FLYING, 0.3, 1.5, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.LONG_REACH, 320, 68, 55, 55, 50, 50, 42, 45, 50, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.DARTRIX, 7, false, false, false, "Blade Quill Pokémon", PokemonType.GRASS, PokemonType.FLYING, 0.7, 16, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.LONG_REACH, 420, 78, 75, 75, 70, 70, 52, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.DECIDUEYE, 7, false, false, false, "Arrow Quill Pokémon", PokemonType.GRASS, PokemonType.GHOST, 1.6, 36.6, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.LONG_REACH, 530, 78, 107, 75, 100, 100, 70, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.LITTEN, 7, false, false, false, "Fire Cat Pokémon", PokemonType.FIRE, null, 0.4, 4.3, AbilityId.BLAZE, AbilityId.NONE, AbilityId.INTIMIDATE, 320, 45, 65, 40, 60, 40, 70, 45, 50, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.TORRACAT, 7, false, false, false, "Fire Cat Pokémon", PokemonType.FIRE, null, 0.7, 25, AbilityId.BLAZE, AbilityId.NONE, AbilityId.INTIMIDATE, 420, 65, 85, 50, 80, 50, 90, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.INCINEROAR, 7, false, false, false, "Heel Pokémon", PokemonType.FIRE, PokemonType.DARK, 1.8, 83, AbilityId.BLAZE, AbilityId.NONE, AbilityId.INTIMIDATE, 530, 95, 115, 90, 80, 90, 60, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.POPPLIO, 7, false, false, false, "Sea Lion Pokémon", PokemonType.WATER, null, 0.4, 7.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.LIQUID_VOICE, 320, 50, 54, 54, 66, 56, 40, 45, 50, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.BRIONNE, 7, false, false, false, "Pop Star Pokémon", PokemonType.WATER, null, 0.6, 17.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.LIQUID_VOICE, 420, 60, 69, 69, 91, 81, 50, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.PRIMARINA, 7, false, false, false, "Soloist Pokémon", PokemonType.WATER, PokemonType.FAIRY, 1.8, 44, AbilityId.TORRENT, AbilityId.NONE, AbilityId.LIQUID_VOICE, 530, 80, 74, 74, 126, 116, 60, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.PIKIPEK, 7, false, false, false, "Woodpecker Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 1.2, AbilityId.KEEN_EYE, AbilityId.SKILL_LINK, AbilityId.PICKUP, 265, 35, 75, 30, 30, 30, 65, 255, 70, 53, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.TRUMBEAK, 7, false, false, false, "Bugle Beak Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 14.8, AbilityId.KEEN_EYE, AbilityId.SKILL_LINK, AbilityId.PICKUP, 355, 55, 85, 50, 40, 50, 75, 120, 70, 124, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.TOUCANNON, 7, false, false, false, "Cannon Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.1, 26, AbilityId.KEEN_EYE, AbilityId.SKILL_LINK, AbilityId.SHEER_FORCE, 485, 80, 120, 75, 75, 75, 60, 45, 70, 243, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.YUNGOOS, 7, false, false, false, "Loitering Pokémon", PokemonType.NORMAL, null, 0.4, 6, AbilityId.STAKEOUT, AbilityId.STRONG_JAW, AbilityId.ADAPTABILITY, 253, 48, 70, 30, 30, 30, 45, 255, 70, 51, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GUMSHOOS, 7, false, false, false, "Stakeout Pokémon", PokemonType.NORMAL, null, 0.7, 14.2, AbilityId.STAKEOUT, AbilityId.STRONG_JAW, AbilityId.ADAPTABILITY, 418, 88, 110, 60, 55, 60, 45, 127, 70, 146, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GRUBBIN, 7, false, false, false, "Larva Pokémon", PokemonType.BUG, null, 0.4, 4.4, AbilityId.SWARM, AbilityId.NONE, AbilityId.NONE, 300, 47, 62, 45, 55, 45, 46, 255, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CHARJABUG, 7, false, false, false, "Battery Pokémon", PokemonType.BUG, PokemonType.ELECTRIC, 0.5, 10.5, AbilityId.BATTERY, AbilityId.NONE, AbilityId.NONE, 400, 57, 82, 95, 55, 75, 36, 120, 50, 140, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.VIKAVOLT, 7, false, false, false, "Stag Beetle Pokémon", PokemonType.BUG, PokemonType.ELECTRIC, 1.5, 45, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 500, 77, 70, 90, 145, 75, 43, 45, 50, 250, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CRABRAWLER, 7, false, false, false, "Boxing Pokémon", PokemonType.FIGHTING, null, 0.6, 7, AbilityId.HYPER_CUTTER, AbilityId.IRON_FIST, AbilityId.ANGER_POINT, 338, 47, 82, 57, 42, 47, 63, 225, 70, 68, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CRABOMINABLE, 7, false, false, false, "Woolly Crab Pokémon", PokemonType.FIGHTING, PokemonType.ICE, 1.7, 180, AbilityId.HYPER_CUTTER, AbilityId.IRON_FIST, AbilityId.ANGER_POINT, 478, 97, 132, 77, 62, 67, 43, 60, 70, 167, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ORICORIO, 7, false, false, false, "Dancing Pokémon", PokemonType.FIRE, PokemonType.FLYING, 0.6, 3.4, AbilityId.DANCER, AbilityId.NONE, AbilityId.NONE, 476, 75, 70, 70, 98, 70, 93, 45, 70, 167, GrowthRate.MEDIUM_FAST, 25, false, false, + new PokemonForm("Baile Style", "baile", PokemonType.FIRE, PokemonType.FLYING, 0.6, 3.4, AbilityId.DANCER, AbilityId.NONE, AbilityId.NONE, 476, 75, 70, 70, 98, 70, 93, 45, 70, 167, false, "", true), + new PokemonForm("Pom-Pom Style", "pompom", PokemonType.ELECTRIC, PokemonType.FLYING, 0.6, 3.4, AbilityId.DANCER, AbilityId.NONE, AbilityId.NONE, 476, 75, 70, 70, 98, 70, 93, 45, 70, 167, false, null, true), + new PokemonForm("Pau Style", "pau", PokemonType.PSYCHIC, PokemonType.FLYING, 0.6, 3.4, AbilityId.DANCER, AbilityId.NONE, AbilityId.NONE, 476, 75, 70, 70, 98, 70, 93, 45, 70, 167, false, null, true), + new PokemonForm("Sensu Style", "sensu", PokemonType.GHOST, PokemonType.FLYING, 0.6, 3.4, AbilityId.DANCER, AbilityId.NONE, AbilityId.NONE, 476, 75, 70, 70, 98, 70, 93, 45, 70, 167, false, null, true) + ), + new PokemonSpecies(SpeciesId.CUTIEFLY, 7, false, false, false, "Bee Fly Pokémon", PokemonType.BUG, PokemonType.FAIRY, 0.1, 0.2, AbilityId.HONEY_GATHER, AbilityId.SHIELD_DUST, AbilityId.SWEET_VEIL, 304, 40, 45, 40, 55, 40, 84, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.RIBOMBEE, 7, false, false, false, "Bee Fly Pokémon", PokemonType.BUG, PokemonType.FAIRY, 0.2, 0.5, AbilityId.HONEY_GATHER, AbilityId.SHIELD_DUST, AbilityId.SWEET_VEIL, 464, 60, 55, 60, 95, 70, 124, 75, 50, 162, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ROCKRUFF, 7, false, false, false, "Puppy Pokémon", PokemonType.ROCK, null, 0.5, 9.2, AbilityId.KEEN_EYE, AbilityId.VITAL_SPIRIT, AbilityId.STEADFAST, 280, 45, 65, 40, 30, 40, 60, 190, 50, 56, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Normal", "", PokemonType.ROCK, null, 0.5, 9.2, AbilityId.KEEN_EYE, AbilityId.VITAL_SPIRIT, AbilityId.STEADFAST, 280, 45, 65, 40, 30, 40, 60, 190, 50, 56, false, null, true), + new PokemonForm("Own Tempo", "own-tempo", PokemonType.ROCK, null, 0.5, 9.2, AbilityId.OWN_TEMPO, AbilityId.NONE, AbilityId.OWN_TEMPO, 280, 45, 65, 40, 30, 40, 60, 190, 50, 56, false, "", true) + ), + new PokemonSpecies(SpeciesId.LYCANROC, 7, false, false, false, "Wolf Pokémon", PokemonType.ROCK, null, 0.8, 25, AbilityId.KEEN_EYE, AbilityId.SAND_RUSH, AbilityId.STEADFAST, 487, 75, 115, 65, 55, 65, 112, 90, 50, 170, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Midday Form", "midday", PokemonType.ROCK, null, 0.8, 25, AbilityId.KEEN_EYE, AbilityId.SAND_RUSH, AbilityId.STEADFAST, 487, 75, 115, 65, 55, 65, 112, 90, 50, 170, false, "", true), + new PokemonForm("Midnight Form", "midnight", PokemonType.ROCK, null, 1.1, 25, AbilityId.KEEN_EYE, AbilityId.VITAL_SPIRIT, AbilityId.NO_GUARD, 487, 85, 115, 75, 55, 75, 82, 90, 50, 170, false, null, true), + new PokemonForm("Dusk Form", "dusk", PokemonType.ROCK, null, 0.8, 25, AbilityId.TOUGH_CLAWS, AbilityId.TOUGH_CLAWS, AbilityId.TOUGH_CLAWS, 487, 75, 117, 65, 55, 65, 110, 90, 50, 170, false, null, true) + ), + new PokemonSpecies(SpeciesId.WISHIWASHI, 7, false, false, false, "Small Fry Pokémon", PokemonType.WATER, null, 0.2, 0.3, AbilityId.SCHOOLING, AbilityId.NONE, AbilityId.NONE, 175, 45, 20, 20, 25, 25, 40, 60, 50, 61, GrowthRate.FAST, 50, false, false, + new PokemonForm("Solo Form", "", PokemonType.WATER, null, 0.2, 0.3, AbilityId.SCHOOLING, AbilityId.NONE, AbilityId.NONE, 175, 45, 20, 20, 25, 25, 40, 60, 50, 61, false, null, true), + new PokemonForm("School", "school", PokemonType.WATER, null, 8.2, 78.6, AbilityId.SCHOOLING, AbilityId.NONE, AbilityId.NONE, 620, 45, 140, 130, 140, 135, 30, 60, 50, 217) + ), + new PokemonSpecies(SpeciesId.MAREANIE, 7, false, false, false, "Brutal Star Pokémon", PokemonType.POISON, PokemonType.WATER, 0.4, 8, AbilityId.MERCILESS, AbilityId.LIMBER, AbilityId.REGENERATOR, 305, 50, 53, 62, 43, 52, 45, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.TOXAPEX, 7, false, false, false, "Brutal Star Pokémon", PokemonType.POISON, PokemonType.WATER, 0.7, 14.5, AbilityId.MERCILESS, AbilityId.LIMBER, AbilityId.REGENERATOR, 495, 50, 63, 152, 53, 142, 35, 75, 50, 173, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MUDBRAY, 7, false, false, false, "Donkey Pokémon", PokemonType.GROUND, null, 1, 110, AbilityId.OWN_TEMPO, AbilityId.STAMINA, AbilityId.INNER_FOCUS, 385, 70, 100, 70, 45, 55, 45, 190, 50, 77, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MUDSDALE, 7, false, false, false, "Draft Horse Pokémon", PokemonType.GROUND, null, 2.5, 920, AbilityId.OWN_TEMPO, AbilityId.STAMINA, AbilityId.INNER_FOCUS, 500, 100, 125, 100, 55, 85, 35, 60, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DEWPIDER, 7, false, false, false, "Water Bubble Pokémon", PokemonType.WATER, PokemonType.BUG, 0.3, 4, AbilityId.WATER_BUBBLE, AbilityId.NONE, AbilityId.WATER_ABSORB, 269, 38, 40, 52, 40, 72, 27, 200, 50, 54, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ARAQUANID, 7, false, false, false, "Water Bubble Pokémon", PokemonType.WATER, PokemonType.BUG, 1.8, 82, AbilityId.WATER_BUBBLE, AbilityId.NONE, AbilityId.WATER_ABSORB, 454, 68, 70, 92, 50, 132, 42, 100, 50, 159, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.FOMANTIS, 7, false, false, false, "Sickle Grass Pokémon", PokemonType.GRASS, null, 0.3, 1.5, AbilityId.LEAF_GUARD, AbilityId.NONE, AbilityId.CONTRARY, 250, 40, 55, 35, 50, 35, 35, 190, 50, 50, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.LURANTIS, 7, false, false, false, "Bloom Sickle Pokémon", PokemonType.GRASS, null, 0.9, 18.5, AbilityId.LEAF_GUARD, AbilityId.NONE, AbilityId.CONTRARY, 480, 70, 105, 90, 80, 90, 45, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MORELULL, 7, false, false, false, "Illuminating Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 0.2, 1.5, AbilityId.ILLUMINATE, AbilityId.EFFECT_SPORE, AbilityId.RAIN_DISH, 285, 40, 35, 55, 65, 75, 15, 190, 50, 57, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SHIINOTIC, 7, false, false, false, "Illuminating Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 1, 11.5, AbilityId.ILLUMINATE, AbilityId.EFFECT_SPORE, AbilityId.RAIN_DISH, 405, 60, 45, 80, 90, 100, 30, 75, 50, 142, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SALANDIT, 7, false, false, false, "Toxic Lizard Pokémon", PokemonType.POISON, PokemonType.FIRE, 0.6, 4.8, AbilityId.CORROSION, AbilityId.NONE, AbilityId.OBLIVIOUS, 320, 48, 44, 40, 71, 40, 77, 120, 50, 64, GrowthRate.MEDIUM_FAST, 87.5, false), + new PokemonSpecies(SpeciesId.SALAZZLE, 7, false, false, false, "Toxic Lizard Pokémon", PokemonType.POISON, PokemonType.FIRE, 1.2, 22.2, AbilityId.CORROSION, AbilityId.NONE, AbilityId.OBLIVIOUS, 480, 68, 64, 60, 111, 60, 117, 45, 50, 168, GrowthRate.MEDIUM_FAST, 0, false), + new PokemonSpecies(SpeciesId.STUFFUL, 7, false, false, false, "Flailing Pokémon", PokemonType.NORMAL, PokemonType.FIGHTING, 0.5, 6.8, AbilityId.FLUFFY, AbilityId.KLUTZ, AbilityId.CUTE_CHARM, 340, 70, 75, 50, 45, 50, 50, 140, 50, 68, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BEWEAR, 7, false, false, false, "Strong Arm Pokémon", PokemonType.NORMAL, PokemonType.FIGHTING, 2.1, 135, AbilityId.FLUFFY, AbilityId.KLUTZ, AbilityId.UNNERVE, 500, 120, 125, 80, 55, 60, 60, 70, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BOUNSWEET, 7, false, false, false, "Fruit Pokémon", PokemonType.GRASS, null, 0.3, 3.2, AbilityId.LEAF_GUARD, AbilityId.OBLIVIOUS, AbilityId.SWEET_VEIL, 210, 42, 30, 38, 30, 38, 32, 235, 50, 42, GrowthRate.MEDIUM_SLOW, 0, false), + new PokemonSpecies(SpeciesId.STEENEE, 7, false, false, false, "Fruit Pokémon", PokemonType.GRASS, null, 0.7, 8.2, AbilityId.LEAF_GUARD, AbilityId.OBLIVIOUS, AbilityId.SWEET_VEIL, 290, 52, 40, 48, 40, 48, 62, 120, 50, 102, GrowthRate.MEDIUM_SLOW, 0, false), + new PokemonSpecies(SpeciesId.TSAREENA, 7, false, false, false, "Fruit Pokémon", PokemonType.GRASS, null, 1.2, 21.4, AbilityId.LEAF_GUARD, AbilityId.QUEENLY_MAJESTY, AbilityId.SWEET_VEIL, 510, 72, 120, 98, 50, 98, 72, 45, 50, 255, GrowthRate.MEDIUM_SLOW, 0, false), + new PokemonSpecies(SpeciesId.COMFEY, 7, false, false, false, "Posy Picker Pokémon", PokemonType.FAIRY, null, 0.1, 0.3, AbilityId.FLOWER_VEIL, AbilityId.TRIAGE, AbilityId.NATURAL_CURE, 485, 51, 52, 90, 82, 110, 100, 60, 50, 170, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.ORANGURU, 7, false, false, false, "Sage Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 1.5, 76, AbilityId.INNER_FOCUS, AbilityId.TELEPATHY, AbilityId.SYMBIOSIS, 490, 90, 60, 80, 90, 110, 60, 45, 50, 172, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.PASSIMIAN, 7, false, false, false, "Teamwork Pokémon", PokemonType.FIGHTING, null, 2, 82.8, AbilityId.RECEIVER, AbilityId.NONE, AbilityId.DEFIANT, 490, 100, 120, 90, 40, 60, 80, 45, 50, 172, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.WIMPOD, 7, false, false, false, "Turn Tail Pokémon", PokemonType.BUG, PokemonType.WATER, 0.5, 12, AbilityId.WIMP_OUT, AbilityId.NONE, AbilityId.RUN_AWAY, 230, 25, 35, 40, 20, 30, 80, 90, 50, 46, GrowthRate.MEDIUM_FAST, 50, false), //Custom Hidden + new PokemonSpecies(SpeciesId.GOLISOPOD, 7, false, false, false, "Hard Scale Pokémon", PokemonType.BUG, PokemonType.WATER, 2, 108, AbilityId.EMERGENCY_EXIT, AbilityId.NONE, AbilityId.ANTICIPATION, 530, 75, 125, 140, 60, 90, 40, 45, 50, 186, GrowthRate.MEDIUM_FAST, 50, false), //Custom Hidden + new PokemonSpecies(SpeciesId.SANDYGAST, 7, false, false, false, "Sand Heap Pokémon", PokemonType.GHOST, PokemonType.GROUND, 0.5, 70, AbilityId.WATER_COMPACTION, AbilityId.NONE, AbilityId.SAND_VEIL, 320, 55, 55, 80, 70, 45, 15, 140, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PALOSSAND, 7, false, false, false, "Sand Castle Pokémon", PokemonType.GHOST, PokemonType.GROUND, 1.3, 250, AbilityId.WATER_COMPACTION, AbilityId.NONE, AbilityId.SAND_VEIL, 480, 85, 75, 110, 100, 75, 35, 60, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PYUKUMUKU, 7, false, false, false, "Sea Cucumber Pokémon", PokemonType.WATER, null, 0.3, 1.2, AbilityId.INNARDS_OUT, AbilityId.NONE, AbilityId.UNAWARE, 410, 55, 60, 130, 30, 130, 5, 60, 50, 144, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.TYPE_NULL, 7, true, false, false, "Synthetic Pokémon", PokemonType.NORMAL, null, 1.9, 120.5, AbilityId.BATTLE_ARMOR, AbilityId.NONE, AbilityId.NONE, 534, 95, 95, 95, 95, 95, 59, 3, 0, 107, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.SILVALLY, 7, true, false, false, "Synthetic Pokémon", PokemonType.NORMAL, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285, GrowthRate.SLOW, null, false, false, + new PokemonForm("Type: Normal", "normal", PokemonType.NORMAL, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285, false, "", true), + new PokemonForm("Type: Fighting", "fighting", PokemonType.FIGHTING, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Flying", "flying", PokemonType.FLYING, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Poison", "poison", PokemonType.POISON, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Ground", "ground", PokemonType.GROUND, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Rock", "rock", PokemonType.ROCK, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Bug", "bug", PokemonType.BUG, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Ghost", "ghost", PokemonType.GHOST, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Steel", "steel", PokemonType.STEEL, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Fire", "fire", PokemonType.FIRE, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Water", "water", PokemonType.WATER, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Grass", "grass", PokemonType.GRASS, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Electric", "electric", PokemonType.ELECTRIC, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Psychic", "psychic", PokemonType.PSYCHIC, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Ice", "ice", PokemonType.ICE, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Dragon", "dragon", PokemonType.DRAGON, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Dark", "dark", PokemonType.DARK, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), + new PokemonForm("Type: Fairy", "fairy", PokemonType.FAIRY, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285) + ), + new PokemonSpecies(SpeciesId.MINIOR, 7, false, false, false, "Meteor Pokémon", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, GrowthRate.MEDIUM_SLOW, null, false, false, + new PokemonForm("Red Meteor Form", "red-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), + new PokemonForm("Orange Meteor Form", "orange-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), + new PokemonForm("Yellow Meteor Form", "yellow-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), + new PokemonForm("Green Meteor Form", "green-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), + new PokemonForm("Blue Meteor Form", "blue-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), + new PokemonForm("Indigo Meteor Form", "indigo-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), + new PokemonForm("Violet Meteor Form", "violet-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), + new PokemonForm("Red Core Form", "red", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), + new PokemonForm("Orange Core Form", "orange", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), + new PokemonForm("Yellow Core Form", "yellow", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), + new PokemonForm("Green Core Form", "green", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), + new PokemonForm("Blue Core Form", "blue", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), + new PokemonForm("Indigo Core Form", "indigo", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), + new PokemonForm("Violet Core Form", "violet", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true) + ), + new PokemonSpecies(SpeciesId.KOMALA, 7, false, false, false, "Drowsing Pokémon", PokemonType.NORMAL, null, 0.4, 19.9, AbilityId.COMATOSE, AbilityId.NONE, AbilityId.NONE, 480, 65, 115, 65, 75, 95, 65, 45, 70, 168, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.TURTONATOR, 7, false, false, false, "Blast Turtle Pokémon", PokemonType.FIRE, PokemonType.DRAGON, 2, 212, AbilityId.SHELL_ARMOR, AbilityId.NONE, AbilityId.NONE, 485, 60, 78, 135, 91, 85, 36, 70, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.TOGEDEMARU, 7, false, false, false, "Roly-Poly Pokémon", PokemonType.ELECTRIC, PokemonType.STEEL, 0.3, 3.3, AbilityId.IRON_BARBS, AbilityId.LIGHTNING_ROD, AbilityId.STURDY, 435, 65, 98, 63, 40, 73, 96, 180, 50, 152, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MIMIKYU, 7, false, false, false, "Disguise Pokémon", PokemonType.GHOST, PokemonType.FAIRY, 0.2, 0.7, AbilityId.DISGUISE, AbilityId.NONE, AbilityId.NONE, 476, 55, 90, 80, 50, 105, 96, 45, 50, 167, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Disguised Form", "disguised", PokemonType.GHOST, PokemonType.FAIRY, 0.2, 0.7, AbilityId.DISGUISE, AbilityId.NONE, AbilityId.NONE, 476, 55, 90, 80, 50, 105, 96, 45, 50, 167, false, null, true), + new PokemonForm("Busted Form", "busted", PokemonType.GHOST, PokemonType.FAIRY, 0.2, 0.7, AbilityId.DISGUISE, AbilityId.NONE, AbilityId.NONE, 476, 55, 90, 80, 50, 105, 96, 45, 50, 167) + ), + new PokemonSpecies(SpeciesId.BRUXISH, 7, false, false, false, "Gnash Teeth Pokémon", PokemonType.WATER, PokemonType.PSYCHIC, 0.9, 19, AbilityId.DAZZLING, AbilityId.STRONG_JAW, AbilityId.WONDER_SKIN, 475, 68, 105, 70, 70, 70, 92, 80, 70, 166, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DRAMPA, 7, false, false, false, "Placid Pokémon", PokemonType.NORMAL, PokemonType.DRAGON, 3, 185, AbilityId.BERSERK, AbilityId.SAP_SIPPER, AbilityId.CLOUD_NINE, 485, 78, 60, 85, 135, 91, 36, 70, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DHELMISE, 7, false, false, false, "Sea Creeper Pokémon", PokemonType.GHOST, PokemonType.GRASS, 3.9, 210, AbilityId.STEELWORKER, AbilityId.NONE, AbilityId.NONE, 517, 70, 131, 100, 86, 90, 40, 25, 50, 181, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.JANGMO_O, 7, false, false, false, "Scaly Pokémon", PokemonType.DRAGON, null, 0.6, 29.7, AbilityId.BULLETPROOF, AbilityId.SOUNDPROOF, AbilityId.OVERCOAT, 300, 45, 55, 65, 45, 45, 45, 45, 50, 60, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.HAKAMO_O, 7, false, false, false, "Scaly Pokémon", PokemonType.DRAGON, PokemonType.FIGHTING, 1.2, 47, AbilityId.BULLETPROOF, AbilityId.SOUNDPROOF, AbilityId.OVERCOAT, 420, 55, 75, 90, 65, 70, 65, 45, 50, 147, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.KOMMO_O, 7, false, false, false, "Scaly Pokémon", PokemonType.DRAGON, PokemonType.FIGHTING, 1.6, 78.2, AbilityId.BULLETPROOF, AbilityId.SOUNDPROOF, AbilityId.OVERCOAT, 600, 75, 110, 125, 100, 105, 85, 45, 50, 300, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.TAPU_KOKO, 7, true, false, false, "Land Spirit Pokémon", PokemonType.ELECTRIC, PokemonType.FAIRY, 1.8, 20.5, AbilityId.ELECTRIC_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 115, 85, 95, 75, 130, 3, 50, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.TAPU_LELE, 7, true, false, false, "Land Spirit Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.2, 18.6, AbilityId.PSYCHIC_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 85, 75, 130, 115, 95, 3, 50, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.TAPU_BULU, 7, true, false, false, "Land Spirit Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 1.9, 45.5, AbilityId.GRASSY_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 130, 115, 85, 95, 75, 3, 50, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.TAPU_FINI, 7, true, false, false, "Land Spirit Pokémon", PokemonType.WATER, PokemonType.FAIRY, 1.3, 21.2, AbilityId.MISTY_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 75, 115, 95, 130, 85, 3, 50, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.COSMOG, 7, true, false, false, "Nebula Pokémon", PokemonType.PSYCHIC, null, 0.2, 0.1, AbilityId.UNAWARE, AbilityId.NONE, AbilityId.NONE, 200, 43, 29, 31, 29, 31, 37, 3, 0, 40, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.COSMOEM, 7, true, false, false, "Protostar Pokémon", PokemonType.PSYCHIC, null, 0.1, 999.9, AbilityId.STURDY, AbilityId.NONE, AbilityId.NONE, 400, 43, 29, 131, 29, 131, 37, 3, 0, 140, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.SOLGALEO, 7, false, true, false, "Sunne Pokémon", PokemonType.PSYCHIC, PokemonType.STEEL, 3.4, 230, AbilityId.FULL_METAL_BODY, AbilityId.NONE, AbilityId.NONE, 680, 137, 137, 107, 113, 89, 97, 3, 0, 340, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.LUNALA, 7, false, true, false, "Moone Pokémon", PokemonType.PSYCHIC, PokemonType.GHOST, 4, 120, AbilityId.SHADOW_SHIELD, AbilityId.NONE, AbilityId.NONE, 680, 137, 113, 89, 137, 107, 97, 3, 0, 340, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.NIHILEGO, 7, true, false, false, "Parasite Pokémon", PokemonType.ROCK, PokemonType.POISON, 1.2, 55.5, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 109, 53, 47, 127, 131, 103, 45, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.BUZZWOLE, 7, true, false, false, "Swollen Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 2.4, 333.6, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 107, 139, 139, 53, 53, 79, 45, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.PHEROMOSA, 7, true, false, false, "Lissome Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 1.8, 25, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 71, 137, 37, 137, 37, 151, 45, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.XURKITREE, 7, true, false, false, "Glowing Pokémon", PokemonType.ELECTRIC, null, 3.8, 100, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 83, 89, 71, 173, 71, 83, 45, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.CELESTEELA, 7, true, false, false, "Launch Pokémon", PokemonType.STEEL, PokemonType.FLYING, 9.2, 999.9, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 97, 101, 103, 107, 101, 61, 45, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.KARTANA, 7, true, false, false, "Drawn Sword Pokémon", PokemonType.GRASS, PokemonType.STEEL, 0.3, 0.1, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 59, 181, 131, 59, 31, 109, 45, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.GUZZLORD, 7, true, false, false, "Junkivore Pokémon", PokemonType.DARK, PokemonType.DRAGON, 5.5, 888, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 223, 101, 53, 97, 53, 43, 45, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.NECROZMA, 7, false, true, false, "Prism Pokémon", PokemonType.PSYCHIC, null, 2.4, 230, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 600, 97, 107, 101, 127, 89, 79, 3, 0, 300, GrowthRate.SLOW, null, false, false, + new PokemonForm("Normal", "", PokemonType.PSYCHIC, null, 2.4, 230, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 600, 97, 107, 101, 127, 89, 79, 3, 0, 300, false, null, true), + new PokemonForm("Dusk Mane", "dusk-mane", PokemonType.PSYCHIC, PokemonType.STEEL, 3.8, 460, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 680, 97, 157, 127, 113, 109, 77, 3, 0, 340), + new PokemonForm("Dawn Wings", "dawn-wings", PokemonType.PSYCHIC, PokemonType.GHOST, 4.2, 350, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 680, 97, 113, 109, 157, 127, 77, 3, 0, 340), + new PokemonForm("Ultra", "ultra", PokemonType.PSYCHIC, PokemonType.DRAGON, 7.5, 230, AbilityId.NEUROFORCE, AbilityId.NONE, AbilityId.NONE, 754, 97, 167, 97, 167, 97, 129, 3, 0, 377) + ), + new PokemonSpecies(SpeciesId.MAGEARNA, 7, false, false, true, "Artificial Pokémon", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, GrowthRate.SLOW, null, false, false, + new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, false, null, true), + new PokemonForm("Original", "original", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, false, null, true) + ), + new PokemonSpecies(SpeciesId.MARSHADOW, 7, false, false, true, "Gloomdweller Pokémon", PokemonType.FIGHTING, PokemonType.GHOST, 0.7, 22.2, AbilityId.TECHNICIAN, AbilityId.NONE, AbilityId.NONE, 600, 90, 125, 80, 90, 90, 125, 3, 0, 300, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal", "", PokemonType.FIGHTING, PokemonType.GHOST, 0.7, 22.2, AbilityId.TECHNICIAN, AbilityId.NONE, AbilityId.NONE, 600, 90, 125, 80, 90, 90, 125, 3, 0, 300, false, null, true), + new PokemonForm("Zenith", "zenith", PokemonType.FIGHTING, PokemonType.GHOST, 0.7, 22.2, AbilityId.TECHNICIAN, AbilityId.NONE, AbilityId.NONE, 600, 90, 125, 80, 90, 90, 125, 3, 0, 300, false, null, false, true) + ), + new PokemonSpecies(SpeciesId.POIPOLE, 7, true, false, false, "Poison Pin Pokémon", PokemonType.POISON, null, 0.6, 1.8, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 420, 67, 73, 67, 73, 67, 73, 45, 0, 210, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.NAGANADEL, 7, true, false, false, "Poison Pin Pokémon", PokemonType.POISON, PokemonType.DRAGON, 3.6, 150, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 540, 73, 73, 73, 127, 73, 121, 45, 0, 270, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.STAKATAKA, 7, true, false, false, "Rampart Pokémon", PokemonType.ROCK, PokemonType.STEEL, 5.5, 820, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 61, 131, 211, 53, 101, 13, 30, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.BLACEPHALON, 7, true, false, false, "Fireworks Pokémon", PokemonType.FIRE, PokemonType.GHOST, 1.8, 13, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 53, 127, 53, 151, 79, 107, 30, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.ZERAORA, 7, false, false, true, "Thunderclap Pokémon", PokemonType.ELECTRIC, null, 1.5, 44.5, AbilityId.VOLT_ABSORB, AbilityId.NONE, AbilityId.NONE, 600, 88, 112, 75, 102, 80, 143, 3, 0, 300, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.MELTAN, 7, false, false, true, "Hex Nut Pokémon", PokemonType.STEEL, null, 0.2, 8, AbilityId.MAGNET_PULL, AbilityId.NONE, AbilityId.NONE, 300, 46, 65, 65, 55, 35, 34, 3, 0, 150, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.MELMETAL, 7, false, false, true, "Hex Nut Pokémon", PokemonType.STEEL, null, 2.5, 800, AbilityId.IRON_FIST, AbilityId.NONE, AbilityId.NONE, 600, 135, 143, 143, 80, 65, 34, 3, 0, 300, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal", "", PokemonType.STEEL, null, 2.5, 800, AbilityId.IRON_FIST, AbilityId.NONE, AbilityId.NONE, 600, 135, 143, 143, 80, 65, 34, 3, 0, 300, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.STEEL, null, 25, 999.9, AbilityId.IRON_FIST, AbilityId.NONE, AbilityId.NONE, 700, 170, 158, 158, 95, 75, 44, 3, 0, 300) + ), + new PokemonSpecies(SpeciesId.GROOKEY, 8, false, false, false, "Chimp Pokémon", PokemonType.GRASS, null, 0.3, 5, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.GRASSY_SURGE, 310, 50, 65, 50, 40, 40, 65, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.THWACKEY, 8, false, false, false, "Beat Pokémon", PokemonType.GRASS, null, 0.7, 14, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.GRASSY_SURGE, 420, 70, 85, 70, 55, 60, 80, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.RILLABOOM, 8, false, false, false, "Drummer Pokémon", PokemonType.GRASS, null, 2.1, 90, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.GRASSY_SURGE, 530, 100, 125, 90, 60, 70, 85, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, + new PokemonForm("Normal", "", PokemonType.GRASS, null, 2.1, 90, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.GRASSY_SURGE, 530, 100, 125, 90, 60, 70, 85, 45, 50, 265, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.GRASS, null, 28, 999.9, AbilityId.GRASSY_SURGE, AbilityId.NONE, AbilityId.GRASSY_SURGE, 630, 125, 140, 105, 90, 85, 85, 45, 50, 265) + ), + new PokemonSpecies(SpeciesId.SCORBUNNY, 8, false, false, false, "Rabbit Pokémon", PokemonType.FIRE, null, 0.3, 4.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.LIBERO, 310, 50, 71, 40, 40, 40, 69, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.RABOOT, 8, false, false, false, "Rabbit Pokémon", PokemonType.FIRE, null, 0.6, 9, AbilityId.BLAZE, AbilityId.NONE, AbilityId.LIBERO, 420, 65, 86, 60, 55, 60, 94, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.CINDERACE, 8, false, false, false, "Striker Pokémon", PokemonType.FIRE, null, 1.4, 33, AbilityId.BLAZE, AbilityId.NONE, AbilityId.LIBERO, 530, 80, 116, 75, 65, 75, 119, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, + new PokemonForm("Normal", "", PokemonType.FIRE, null, 1.4, 33, AbilityId.BLAZE, AbilityId.NONE, AbilityId.LIBERO, 530, 80, 116, 75, 65, 75, 119, 45, 50, 265, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.FIRE, null, 27, 999.9, AbilityId.LIBERO, AbilityId.NONE, AbilityId.LIBERO, 630, 100, 141, 80, 95, 80, 134, 45, 50, 265) + ), + new PokemonSpecies(SpeciesId.SOBBLE, 8, false, false, false, "Water Lizard Pokémon", PokemonType.WATER, null, 0.3, 4, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SNIPER, 310, 50, 40, 40, 70, 40, 70, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.DRIZZILE, 8, false, false, false, "Water Lizard Pokémon", PokemonType.WATER, null, 0.7, 11.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SNIPER, 420, 65, 60, 55, 95, 55, 90, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.INTELEON, 8, false, false, false, "Secret Agent Pokémon", PokemonType.WATER, null, 1.9, 45.2, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SNIPER, 530, 70, 85, 65, 125, 65, 120, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, + new PokemonForm("Normal", "", PokemonType.WATER, null, 1.9, 45.2, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SNIPER, 530, 70, 85, 65, 125, 65, 120, 45, 50, 265, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.WATER, null, 40, 999.9, AbilityId.SNIPER, AbilityId.NONE, AbilityId.SNIPER, 630, 95, 117, 67, 147, 67, 137, 45, 50, 265) + ), + new PokemonSpecies(SpeciesId.SKWOVET, 8, false, false, false, "Cheeky Pokémon", PokemonType.NORMAL, null, 0.3, 2.5, AbilityId.CHEEK_POUCH, AbilityId.NONE, AbilityId.GLUTTONY, 275, 70, 55, 55, 35, 35, 25, 255, 50, 55, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GREEDENT, 8, false, false, false, "Greedy Pokémon", PokemonType.NORMAL, null, 0.6, 6, AbilityId.CHEEK_POUCH, AbilityId.NONE, AbilityId.GLUTTONY, 460, 120, 95, 95, 55, 75, 20, 90, 50, 161, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ROOKIDEE, 8, false, false, false, "Tiny Bird Pokémon", PokemonType.FLYING, null, 0.2, 1.8, AbilityId.KEEN_EYE, AbilityId.UNNERVE, AbilityId.BIG_PECKS, 245, 38, 47, 35, 33, 35, 57, 255, 50, 49, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.CORVISQUIRE, 8, false, false, false, "Raven Pokémon", PokemonType.FLYING, null, 0.8, 16, AbilityId.KEEN_EYE, AbilityId.UNNERVE, AbilityId.BIG_PECKS, 365, 68, 67, 55, 43, 55, 77, 120, 50, 128, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.CORVIKNIGHT, 8, false, false, false, "Raven Pokémon", PokemonType.FLYING, PokemonType.STEEL, 2.2, 75, AbilityId.PRESSURE, AbilityId.UNNERVE, AbilityId.MIRROR_ARMOR, 495, 98, 87, 105, 53, 85, 67, 45, 50, 248, GrowthRate.MEDIUM_SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.FLYING, PokemonType.STEEL, 2.2, 75, AbilityId.PRESSURE, AbilityId.UNNERVE, AbilityId.MIRROR_ARMOR, 495, 98, 87, 105, 53, 85, 67, 45, 50, 248, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.FLYING, PokemonType.STEEL, 14, 999.9, AbilityId.MIRROR_ARMOR, AbilityId.MIRROR_ARMOR, AbilityId.MIRROR_ARMOR, 595, 118, 112, 135, 63, 90, 77, 45, 50, 248) + ), + new PokemonSpecies(SpeciesId.BLIPBUG, 8, false, false, false, "Larva Pokémon", PokemonType.BUG, null, 0.4, 8, AbilityId.SWARM, AbilityId.COMPOUND_EYES, AbilityId.TELEPATHY, 180, 25, 20, 20, 25, 45, 45, 255, 50, 36, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DOTTLER, 8, false, false, false, "Radome Pokémon", PokemonType.BUG, PokemonType.PSYCHIC, 0.4, 19.5, AbilityId.SWARM, AbilityId.COMPOUND_EYES, AbilityId.TELEPATHY, 335, 50, 35, 80, 50, 90, 30, 120, 50, 117, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ORBEETLE, 8, false, false, false, "Seven Spot Pokémon", PokemonType.BUG, PokemonType.PSYCHIC, 0.4, 40.8, AbilityId.SWARM, AbilityId.FRISK, AbilityId.TELEPATHY, 505, 60, 45, 110, 80, 120, 90, 45, 50, 253, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.BUG, PokemonType.PSYCHIC, 0.4, 40.8, AbilityId.SWARM, AbilityId.FRISK, AbilityId.TELEPATHY, 505, 60, 45, 110, 80, 120, 90, 45, 50, 253, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.BUG, PokemonType.PSYCHIC, 14, 999.9, AbilityId.TRACE, AbilityId.TRACE, AbilityId.TRACE, 605, 75, 50, 140, 100, 150, 90, 45, 50, 253) + ), + new PokemonSpecies(SpeciesId.NICKIT, 8, false, false, false, "Fox Pokémon", PokemonType.DARK, null, 0.6, 8.9, AbilityId.RUN_AWAY, AbilityId.UNBURDEN, AbilityId.STAKEOUT, 245, 40, 28, 28, 47, 52, 50, 255, 50, 49, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.THIEVUL, 8, false, false, false, "Fox Pokémon", PokemonType.DARK, null, 1.2, 19.9, AbilityId.RUN_AWAY, AbilityId.UNBURDEN, AbilityId.STAKEOUT, 455, 70, 58, 58, 87, 92, 90, 127, 50, 159, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.GOSSIFLEUR, 8, false, false, false, "Flowering Pokémon", PokemonType.GRASS, null, 0.4, 2.2, AbilityId.COTTON_DOWN, AbilityId.REGENERATOR, AbilityId.EFFECT_SPORE, 250, 40, 40, 60, 40, 60, 10, 190, 50, 50, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ELDEGOSS, 8, false, false, false, "Cotton Bloom Pokémon", PokemonType.GRASS, null, 0.5, 2.5, AbilityId.COTTON_DOWN, AbilityId.REGENERATOR, AbilityId.EFFECT_SPORE, 460, 60, 50, 90, 80, 120, 60, 75, 50, 161, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.WOOLOO, 8, false, false, false, "Sheep Pokémon", PokemonType.NORMAL, null, 0.6, 6, AbilityId.FLUFFY, AbilityId.RUN_AWAY, AbilityId.BULLETPROOF, 270, 42, 40, 55, 40, 45, 48, 255, 50, 122, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DUBWOOL, 8, false, false, false, "Sheep Pokémon", PokemonType.NORMAL, null, 1.3, 43, AbilityId.FLUFFY, AbilityId.STEADFAST, AbilityId.BULLETPROOF, 490, 72, 80, 100, 60, 90, 88, 127, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CHEWTLE, 8, false, false, false, "Snapping Pokémon", PokemonType.WATER, null, 0.3, 8.5, AbilityId.STRONG_JAW, AbilityId.SHELL_ARMOR, AbilityId.SWIFT_SWIM, 284, 50, 64, 50, 38, 38, 44, 255, 50, 57, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DREDNAW, 8, false, false, false, "Bite Pokémon", PokemonType.WATER, PokemonType.ROCK, 1, 115.5, AbilityId.STRONG_JAW, AbilityId.SHELL_ARMOR, AbilityId.SWIFT_SWIM, 485, 90, 115, 90, 48, 68, 74, 75, 50, 170, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.ROCK, 1, 115.5, AbilityId.STRONG_JAW, AbilityId.SHELL_ARMOR, AbilityId.SWIFT_SWIM, 485, 90, 115, 90, 48, 68, 74, 75, 50, 170, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.WATER, PokemonType.ROCK, 24, 999.9, AbilityId.STRONG_JAW, AbilityId.STRONG_JAW, AbilityId.STRONG_JAW, 585, 115, 137, 115, 61, 83, 74, 75, 50, 170) + ), + new PokemonSpecies(SpeciesId.YAMPER, 8, false, false, false, "Puppy Pokémon", PokemonType.ELECTRIC, null, 0.3, 13.5, AbilityId.BALL_FETCH, AbilityId.NONE, AbilityId.RATTLED, 270, 59, 45, 50, 40, 50, 26, 255, 50, 54, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.BOLTUND, 8, false, false, false, "Dog Pokémon", PokemonType.ELECTRIC, null, 1, 34, AbilityId.STRONG_JAW, AbilityId.NONE, AbilityId.COMPETITIVE, 490, 69, 90, 60, 90, 60, 121, 45, 50, 172, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.ROLYCOLY, 8, false, false, false, "Coal Pokémon", PokemonType.ROCK, null, 0.3, 12, AbilityId.STEAM_ENGINE, AbilityId.HEATPROOF, AbilityId.FLASH_FIRE, 240, 30, 40, 50, 40, 50, 30, 255, 50, 48, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.CARKOL, 8, false, false, false, "Coal Pokémon", PokemonType.ROCK, PokemonType.FIRE, 1.1, 78, AbilityId.STEAM_ENGINE, AbilityId.FLAME_BODY, AbilityId.FLASH_FIRE, 410, 80, 60, 90, 60, 70, 50, 120, 50, 144, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.COALOSSAL, 8, false, false, false, "Coal Pokémon", PokemonType.ROCK, PokemonType.FIRE, 2.8, 310.5, AbilityId.STEAM_ENGINE, AbilityId.FLAME_BODY, AbilityId.FLASH_FIRE, 510, 110, 80, 120, 80, 90, 30, 45, 50, 255, GrowthRate.MEDIUM_SLOW, 50, false, true, + new PokemonForm("Normal", "", PokemonType.ROCK, PokemonType.FIRE, 2.8, 310.5, AbilityId.STEAM_ENGINE, AbilityId.FLAME_BODY, AbilityId.FLASH_FIRE, 510, 110, 80, 120, 80, 90, 30, 45, 50, 255, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.ROCK, PokemonType.FIRE, 42, 999.9, AbilityId.STEAM_ENGINE, AbilityId.STEAM_ENGINE, AbilityId.STEAM_ENGINE, 610, 140, 100, 132, 95, 100, 43, 45, 50, 255) + ), + new PokemonSpecies(SpeciesId.APPLIN, 8, false, false, false, "Apple Core Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.2, 0.5, AbilityId.RIPEN, AbilityId.GLUTTONY, AbilityId.BULLETPROOF, 260, 40, 40, 80, 40, 40, 20, 255, 50, 52, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(SpeciesId.FLAPPLE, 8, false, false, false, "Apple Wing Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.3, 1, AbilityId.RIPEN, AbilityId.GLUTTONY, AbilityId.HUSTLE, 485, 70, 110, 80, 95, 60, 70, 45, 50, 170, GrowthRate.ERRATIC, 50, false, true, + new PokemonForm("Normal", "", PokemonType.GRASS, PokemonType.DRAGON, 0.3, 1, AbilityId.RIPEN, AbilityId.GLUTTONY, AbilityId.HUSTLE, 485, 70, 110, 80, 95, 60, 70, 45, 50, 170, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.GRASS, PokemonType.DRAGON, 24, 999.9, AbilityId.HUSTLE, AbilityId.HUSTLE, AbilityId.HUSTLE, 585, 100, 125, 90, 105, 70, 95, 45, 50, 170) + ), + new PokemonSpecies(SpeciesId.APPLETUN, 8, false, false, false, "Apple Nectar Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 13, AbilityId.RIPEN, AbilityId.GLUTTONY, AbilityId.THICK_FAT, 485, 110, 85, 80, 100, 80, 30, 45, 50, 170, GrowthRate.ERRATIC, 50, false, true, + new PokemonForm("Normal", "", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 13, AbilityId.RIPEN, AbilityId.GLUTTONY, AbilityId.THICK_FAT, 485, 110, 85, 80, 100, 80, 30, 45, 50, 170, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.GRASS, PokemonType.DRAGON, 24, 999.9, AbilityId.THICK_FAT, AbilityId.THICK_FAT, AbilityId.THICK_FAT, 585, 150, 100, 95, 115, 95, 30, 45, 50, 170) + ), + new PokemonSpecies(SpeciesId.SILICOBRA, 8, false, false, false, "Sand Snake Pokémon", PokemonType.GROUND, null, 2.2, 7.6, AbilityId.SAND_SPIT, AbilityId.SHED_SKIN, AbilityId.SAND_VEIL, 315, 52, 57, 75, 35, 50, 46, 255, 50, 63, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SANDACONDA, 8, false, false, false, "Sand Snake Pokémon", PokemonType.GROUND, null, 3.8, 65.5, AbilityId.SAND_SPIT, AbilityId.SHED_SKIN, AbilityId.SAND_VEIL, 510, 72, 107, 125, 65, 70, 71, 120, 50, 179, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.GROUND, null, 3.8, 65.5, AbilityId.SAND_SPIT, AbilityId.SHED_SKIN, AbilityId.SAND_VEIL, 510, 72, 107, 125, 65, 70, 71, 120, 50, 179, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.GROUND, null, 22, 999.9, AbilityId.SAND_SPIT, AbilityId.SAND_SPIT, AbilityId.SAND_SPIT, 610, 102, 137, 140, 70, 80, 81, 120, 50, 179) + ), + new PokemonSpecies(SpeciesId.CRAMORANT, 8, false, false, false, "Gulp Pokémon", PokemonType.FLYING, PokemonType.WATER, 0.8, 18, AbilityId.GULP_MISSILE, AbilityId.NONE, AbilityId.NONE, 475, 70, 85, 55, 85, 95, 85, 45, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Normal", "", PokemonType.FLYING, PokemonType.WATER, 0.8, 18, AbilityId.GULP_MISSILE, AbilityId.NONE, AbilityId.NONE, 475, 70, 85, 55, 85, 95, 85, 45, 50, 166, false, null, true), + new PokemonForm("Gulping Form", "gulping", PokemonType.FLYING, PokemonType.WATER, 0.8, 18, AbilityId.GULP_MISSILE, AbilityId.NONE, AbilityId.NONE, 475, 70, 85, 55, 85, 95, 85, 45, 50, 166), + new PokemonForm("Gorging Form", "gorging", PokemonType.FLYING, PokemonType.WATER, 0.8, 18, AbilityId.GULP_MISSILE, AbilityId.NONE, AbilityId.NONE, 475, 70, 85, 55, 85, 95, 85, 45, 50, 166) + ), + new PokemonSpecies(SpeciesId.ARROKUDA, 8, false, false, false, "Rush Pokémon", PokemonType.WATER, null, 0.5, 1, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.PROPELLER_TAIL, 280, 41, 63, 40, 40, 30, 66, 255, 50, 56, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.BARRASKEWDA, 8, false, false, false, "Skewer Pokémon", PokemonType.WATER, null, 1.3, 30, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.PROPELLER_TAIL, 490, 61, 123, 60, 60, 50, 136, 60, 50, 172, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.TOXEL, 8, false, false, false, "Baby Pokémon", PokemonType.ELECTRIC, PokemonType.POISON, 0.4, 11, AbilityId.RATTLED, AbilityId.STATIC, AbilityId.KLUTZ, 242, 40, 38, 35, 54, 35, 40, 75, 50, 48, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.TOXTRICITY, 8, false, false, false, "Punk Pokémon", PokemonType.ELECTRIC, PokemonType.POISON, 1.6, 40, AbilityId.PUNK_ROCK, AbilityId.PLUS, AbilityId.TECHNICIAN, 502, 75, 98, 70, 114, 70, 75, 45, 50, 176, GrowthRate.MEDIUM_SLOW, 50, false, true, + new PokemonForm("Amped Form", "amped", PokemonType.ELECTRIC, PokemonType.POISON, 1.6, 40, AbilityId.PUNK_ROCK, AbilityId.PLUS, AbilityId.TECHNICIAN, 502, 75, 98, 70, 114, 70, 75, 45, 50, 176, false, "", true), + new PokemonForm("Low-Key Form", "lowkey", PokemonType.ELECTRIC, PokemonType.POISON, 1.6, 40, AbilityId.PUNK_ROCK, AbilityId.MINUS, AbilityId.TECHNICIAN, 502, 75, 98, 70, 114, 70, 75, 45, 50, 176, false, "lowkey", true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.ELECTRIC, PokemonType.POISON, 24, 999.9, AbilityId.PUNK_ROCK, AbilityId.PUNK_ROCK, AbilityId.PUNK_ROCK, 602, 114, 105, 82, 137, 82, 82, 45, 50, 176) + ), + new PokemonSpecies(SpeciesId.SIZZLIPEDE, 8, false, false, false, "Radiator Pokémon", PokemonType.FIRE, PokemonType.BUG, 0.7, 1, AbilityId.FLASH_FIRE, AbilityId.WHITE_SMOKE, AbilityId.FLAME_BODY, 305, 50, 65, 45, 50, 50, 45, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CENTISKORCH, 8, false, false, false, "Radiator Pokémon", PokemonType.FIRE, PokemonType.BUG, 3, 120, AbilityId.FLASH_FIRE, AbilityId.WHITE_SMOKE, AbilityId.FLAME_BODY, 525, 100, 115, 65, 90, 90, 65, 75, 50, 184, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.FIRE, PokemonType.BUG, 3, 120, AbilityId.FLASH_FIRE, AbilityId.WHITE_SMOKE, AbilityId.FLAME_BODY, 525, 100, 115, 65, 90, 90, 65, 75, 50, 184, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.FIRE, PokemonType.BUG, 75, 999.9, AbilityId.FLASH_FIRE, AbilityId.FLASH_FIRE, AbilityId.FLASH_FIRE, 625, 130, 125, 75, 94, 100, 101, 75, 50, 184) + ), + new PokemonSpecies(SpeciesId.CLOBBOPUS, 8, false, false, false, "Tantrum Pokémon", PokemonType.FIGHTING, null, 0.6, 4, AbilityId.LIMBER, AbilityId.NONE, AbilityId.TECHNICIAN, 310, 50, 68, 60, 50, 50, 32, 180, 50, 62, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.GRAPPLOCT, 8, false, false, false, "Jujitsu Pokémon", PokemonType.FIGHTING, null, 1.6, 39, AbilityId.LIMBER, AbilityId.NONE, AbilityId.TECHNICIAN, 480, 80, 118, 90, 70, 80, 42, 45, 50, 168, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SINISTEA, 8, false, false, false, "Black Tea Pokémon", PokemonType.GHOST, null, 0.1, 0.2, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, GrowthRate.MEDIUM_FAST, null, false, false, + new PokemonForm("Phony Form", "phony", PokemonType.GHOST, null, 0.1, 0.2, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, "", true), + new PokemonForm("Antique Form", "antique", PokemonType.GHOST, null, 0.1, 0.2, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, "", true) + ), + new PokemonSpecies(SpeciesId.POLTEAGEIST, 8, false, false, false, "Black Tea Pokémon", PokemonType.GHOST, null, 0.2, 0.4, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 508, 60, 65, 65, 134, 114, 70, 60, 50, 178, GrowthRate.MEDIUM_FAST, null, false, false, + new PokemonForm("Phony Form", "phony", PokemonType.GHOST, null, 0.2, 0.4, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 508, 60, 65, 65, 134, 114, 70, 60, 50, 178, false, "", true), + new PokemonForm("Antique Form", "antique", PokemonType.GHOST, null, 0.2, 0.4, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 508, 60, 65, 65, 134, 114, 70, 60, 50, 178, false, "", true) + ), + new PokemonSpecies(SpeciesId.HATENNA, 8, false, false, false, "Calm Pokémon", PokemonType.PSYCHIC, null, 0.4, 3.4, AbilityId.HEALER, AbilityId.ANTICIPATION, AbilityId.MAGIC_BOUNCE, 265, 42, 30, 45, 56, 53, 39, 235, 50, 53, GrowthRate.SLOW, 0, false), + new PokemonSpecies(SpeciesId.HATTREM, 8, false, false, false, "Serene Pokémon", PokemonType.PSYCHIC, null, 0.6, 4.8, AbilityId.HEALER, AbilityId.ANTICIPATION, AbilityId.MAGIC_BOUNCE, 370, 57, 40, 65, 86, 73, 49, 120, 50, 130, GrowthRate.SLOW, 0, false), + new PokemonSpecies(SpeciesId.HATTERENE, 8, false, false, false, "Silent Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 2.1, 5.1, AbilityId.HEALER, AbilityId.ANTICIPATION, AbilityId.MAGIC_BOUNCE, 510, 57, 90, 95, 136, 103, 29, 45, 50, 255, GrowthRate.SLOW, 0, false, true, + new PokemonForm("Normal", "", PokemonType.PSYCHIC, PokemonType.FAIRY, 2.1, 5.1, AbilityId.HEALER, AbilityId.ANTICIPATION, AbilityId.MAGIC_BOUNCE, 510, 57, 90, 95, 136, 103, 29, 45, 50, 255, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.PSYCHIC, PokemonType.FAIRY, 26, 999.9, AbilityId.MAGIC_BOUNCE, AbilityId.MAGIC_BOUNCE, AbilityId.MAGIC_BOUNCE, 610, 87, 100, 110, 146, 118, 49, 45, 50, 255) + ), + new PokemonSpecies(SpeciesId.IMPIDIMP, 8, false, false, false, "Wily Pokémon", PokemonType.DARK, PokemonType.FAIRY, 0.4, 5.5, AbilityId.PRANKSTER, AbilityId.FRISK, AbilityId.PICKPOCKET, 265, 45, 45, 30, 55, 40, 50, 255, 50, 53, GrowthRate.MEDIUM_FAST, 100, false), + new PokemonSpecies(SpeciesId.MORGREM, 8, false, false, false, "Devious Pokémon", PokemonType.DARK, PokemonType.FAIRY, 0.8, 12.5, AbilityId.PRANKSTER, AbilityId.FRISK, AbilityId.PICKPOCKET, 370, 65, 60, 45, 75, 55, 70, 120, 50, 130, GrowthRate.MEDIUM_FAST, 100, false), + new PokemonSpecies(SpeciesId.GRIMMSNARL, 8, false, false, false, "Bulk Up Pokémon", PokemonType.DARK, PokemonType.FAIRY, 1.5, 61, AbilityId.PRANKSTER, AbilityId.FRISK, AbilityId.PICKPOCKET, 510, 95, 120, 65, 95, 75, 60, 45, 50, 255, GrowthRate.MEDIUM_FAST, 100, false, true, + new PokemonForm("Normal", "", PokemonType.DARK, PokemonType.FAIRY, 1.5, 61, AbilityId.PRANKSTER, AbilityId.FRISK, AbilityId.PICKPOCKET, 510, 95, 120, 65, 95, 75, 60, 45, 50, 255, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.DARK, PokemonType.FAIRY, 32, 999.9, AbilityId.PRANKSTER, AbilityId.PRANKSTER, AbilityId.PRANKSTER, 610, 130, 138, 75, 110, 92, 65, 45, 50, 255) + ), + new PokemonSpecies(SpeciesId.OBSTAGOON, 8, false, false, false, "Blocking Pokémon", PokemonType.DARK, PokemonType.NORMAL, 1.6, 46, AbilityId.RECKLESS, AbilityId.GUTS, AbilityId.DEFIANT, 520, 93, 90, 101, 60, 81, 95, 45, 50, 260, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PERRSERKER, 8, false, false, false, "Viking Pokémon", PokemonType.STEEL, null, 0.8, 28, AbilityId.BATTLE_ARMOR, AbilityId.TOUGH_CLAWS, AbilityId.STEELY_SPIRIT, 440, 70, 110, 100, 50, 60, 50, 90, 50, 154, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CURSOLA, 8, false, false, false, "Coral Pokémon", PokemonType.GHOST, null, 1, 0.4, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.PERISH_BODY, 510, 60, 95, 50, 145, 130, 30, 30, 50, 179, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.SIRFETCHD, 8, false, false, false, "Wild Duck Pokémon", PokemonType.FIGHTING, null, 0.8, 117, AbilityId.STEADFAST, AbilityId.NONE, AbilityId.SCRAPPY, 507, 62, 135, 95, 68, 82, 65, 45, 50, 177, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MR_RIME, 8, false, false, false, "Comedian Pokémon", PokemonType.ICE, PokemonType.PSYCHIC, 1.5, 58.2, AbilityId.TANGLED_FEET, AbilityId.SCREEN_CLEANER, AbilityId.ICE_BODY, 520, 80, 85, 75, 110, 100, 70, 45, 50, 182, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.RUNERIGUS, 8, false, false, false, "Grudge Pokémon", PokemonType.GROUND, PokemonType.GHOST, 1.6, 66.6, AbilityId.WANDERING_SPIRIT, AbilityId.NONE, AbilityId.NONE, 483, 58, 95, 145, 50, 105, 30, 90, 50, 169, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.MILCERY, 8, false, false, false, "Cream Pokémon", PokemonType.FAIRY, null, 0.2, 0.3, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 270, 45, 40, 40, 50, 61, 34, 200, 50, 54, GrowthRate.MEDIUM_FAST, 0, false), + new PokemonSpecies(SpeciesId.ALCREMIE, 8, false, false, false, "Cream Pokémon", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, GrowthRate.MEDIUM_FAST, 0, false, true, + new PokemonForm("Vanilla Cream", "vanilla-cream", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, "", true), + new PokemonForm("Ruby Cream", "ruby-cream", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), + new PokemonForm("Matcha Cream", "matcha-cream", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), + new PokemonForm("Mint Cream", "mint-cream", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), + new PokemonForm("Lemon Cream", "lemon-cream", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), + new PokemonForm("Salted Cream", "salted-cream", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), + new PokemonForm("Ruby Swirl", "ruby-swirl", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), + new PokemonForm("Caramel Swirl", "caramel-swirl", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), + new PokemonForm("Rainbow Swirl", "rainbow-swirl", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.FAIRY, null, 30, 999.9, AbilityId.MISTY_SURGE, AbilityId.NONE, AbilityId.MISTY_SURGE, 595, 105, 70, 85, 130, 141, 64, 100, 50, 173) + ), + new PokemonSpecies(SpeciesId.FALINKS, 8, false, false, false, "Formation Pokémon", PokemonType.FIGHTING, null, 3, 62, AbilityId.BATTLE_ARMOR, AbilityId.NONE, AbilityId.DEFIANT, 470, 65, 100, 100, 70, 60, 75, 45, 50, 165, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.PINCURCHIN, 8, false, false, false, "Sea Urchin Pokémon", PokemonType.ELECTRIC, null, 0.3, 1, AbilityId.LIGHTNING_ROD, AbilityId.NONE, AbilityId.ELECTRIC_SURGE, 435, 48, 101, 95, 91, 85, 15, 75, 50, 152, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SNOM, 8, false, false, false, "Worm Pokémon", PokemonType.ICE, PokemonType.BUG, 0.3, 3.8, AbilityId.SHIELD_DUST, AbilityId.NONE, AbilityId.ICE_SCALES, 185, 30, 25, 35, 45, 30, 20, 190, 50, 37, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.FROSMOTH, 8, false, false, false, "Frost Moth Pokémon", PokemonType.ICE, PokemonType.BUG, 1.3, 42, AbilityId.SHIELD_DUST, AbilityId.NONE, AbilityId.ICE_SCALES, 475, 70, 65, 60, 125, 90, 65, 75, 50, 166, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.STONJOURNER, 8, false, false, false, "Big Rock Pokémon", PokemonType.ROCK, null, 2.5, 520, AbilityId.POWER_SPOT, AbilityId.NONE, AbilityId.NONE, 470, 100, 125, 135, 20, 20, 70, 60, 50, 165, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.EISCUE, 8, false, false, false, "Penguin Pokémon", PokemonType.ICE, null, 1.4, 89, AbilityId.ICE_FACE, AbilityId.NONE, AbilityId.NONE, 470, 75, 80, 110, 65, 90, 50, 60, 50, 165, GrowthRate.SLOW, 50, false, false, + new PokemonForm("Ice Face", "", PokemonType.ICE, null, 1.4, 89, AbilityId.ICE_FACE, AbilityId.NONE, AbilityId.NONE, 470, 75, 80, 110, 65, 90, 50, 60, 50, 165, false, null, true), + new PokemonForm("No Ice", "no-ice", PokemonType.ICE, null, 1.4, 89, AbilityId.ICE_FACE, AbilityId.NONE, AbilityId.NONE, 470, 75, 80, 70, 65, 50, 130, 60, 50, 165) + ), + new PokemonSpecies(SpeciesId.INDEEDEE, 8, false, false, false, "Emotion Pokémon", PokemonType.PSYCHIC, PokemonType.NORMAL, 0.9, 28, AbilityId.INNER_FOCUS, AbilityId.SYNCHRONIZE, AbilityId.PSYCHIC_SURGE, 475, 60, 65, 55, 105, 95, 95, 30, 140, 166, GrowthRate.FAST, 50, false, false, + new PokemonForm("Male", "male", PokemonType.PSYCHIC, PokemonType.NORMAL, 0.9, 28, AbilityId.INNER_FOCUS, AbilityId.SYNCHRONIZE, AbilityId.PSYCHIC_SURGE, 475, 60, 65, 55, 105, 95, 95, 30, 140, 166, false, "", true), + new PokemonForm("Female", "female", PokemonType.PSYCHIC, PokemonType.NORMAL, 0.9, 28, AbilityId.OWN_TEMPO, AbilityId.SYNCHRONIZE, AbilityId.PSYCHIC_SURGE, 475, 70, 55, 65, 95, 105, 85, 30, 140, 166, false, null, true) + ), + new PokemonSpecies(SpeciesId.MORPEKO, 8, false, false, false, "Two-Sided Pokémon", PokemonType.ELECTRIC, PokemonType.DARK, 0.3, 3, AbilityId.HUNGER_SWITCH, AbilityId.NONE, AbilityId.NONE, 436, 58, 95, 58, 70, 58, 97, 180, 50, 153, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Full Belly Mode", "full-belly", PokemonType.ELECTRIC, PokemonType.DARK, 0.3, 3, AbilityId.HUNGER_SWITCH, AbilityId.NONE, AbilityId.NONE, 436, 58, 95, 58, 70, 58, 97, 180, 50, 153, false, "", true), + new PokemonForm("Hangry Mode", "hangry", PokemonType.ELECTRIC, PokemonType.DARK, 0.3, 3, AbilityId.HUNGER_SWITCH, AbilityId.NONE, AbilityId.NONE, 436, 58, 95, 58, 70, 58, 97, 180, 50, 153) + ), + new PokemonSpecies(SpeciesId.CUFANT, 8, false, false, false, "Copperderm Pokémon", PokemonType.STEEL, null, 1.2, 100, AbilityId.SHEER_FORCE, AbilityId.NONE, AbilityId.HEAVY_METAL, 330, 72, 80, 49, 40, 49, 40, 190, 50, 66, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.COPPERAJAH, 8, false, false, false, "Copperderm Pokémon", PokemonType.STEEL, null, 3, 650, AbilityId.SHEER_FORCE, AbilityId.NONE, AbilityId.HEAVY_METAL, 500, 122, 130, 69, 80, 69, 30, 90, 50, 175, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.STEEL, null, 3, 650, AbilityId.SHEER_FORCE, AbilityId.NONE, AbilityId.HEAVY_METAL, 500, 122, 130, 69, 80, 69, 30, 90, 50, 175, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.STEEL, PokemonType.GROUND, 23, 999.9, AbilityId.MOLD_BREAKER, AbilityId.NONE, AbilityId.MOLD_BREAKER, 600, 177, 155, 79, 90, 79, 20, 90, 50, 175) + ), + new PokemonSpecies(SpeciesId.DRACOZOLT, 8, false, false, false, "Fossil Pokémon", PokemonType.ELECTRIC, PokemonType.DRAGON, 1.8, 190, AbilityId.VOLT_ABSORB, AbilityId.HUSTLE, AbilityId.SAND_RUSH, 505, 90, 100, 90, 80, 70, 75, 45, 50, 177, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.ARCTOZOLT, 8, false, false, false, "Fossil Pokémon", PokemonType.ELECTRIC, PokemonType.ICE, 2.3, 150, AbilityId.VOLT_ABSORB, AbilityId.STATIC, AbilityId.SLUSH_RUSH, 505, 90, 100, 90, 90, 80, 55, 45, 50, 177, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.DRACOVISH, 8, false, false, false, "Fossil Pokémon", PokemonType.WATER, PokemonType.DRAGON, 2.3, 215, AbilityId.WATER_ABSORB, AbilityId.STRONG_JAW, AbilityId.SAND_RUSH, 505, 90, 90, 100, 70, 80, 75, 45, 50, 177, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.ARCTOVISH, 8, false, false, false, "Fossil Pokémon", PokemonType.WATER, PokemonType.ICE, 2, 175, AbilityId.WATER_ABSORB, AbilityId.ICE_BODY, AbilityId.SLUSH_RUSH, 505, 90, 90, 100, 80, 90, 55, 45, 50, 177, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.DURALUDON, 8, false, false, false, "Alloy Pokémon", PokemonType.STEEL, PokemonType.DRAGON, 1.8, 40, AbilityId.LIGHT_METAL, AbilityId.HEAVY_METAL, AbilityId.STALWART, 535, 70, 95, 115, 120, 50, 85, 45, 50, 187, GrowthRate.MEDIUM_FAST, 50, false, true, + new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.DRAGON, 1.8, 40, AbilityId.LIGHT_METAL, AbilityId.HEAVY_METAL, AbilityId.STALWART, 535, 70, 95, 115, 120, 50, 85, 45, 50, 187, false, null, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.STEEL, PokemonType.DRAGON, 43, 999.9, AbilityId.LIGHTNING_ROD, AbilityId.LIGHTNING_ROD, AbilityId.LIGHTNING_ROD, 635, 100, 110, 120, 175, 60, 70, 45, 50, 187) + ), + new PokemonSpecies(SpeciesId.DREEPY, 8, false, false, false, "Lingering Pokémon", PokemonType.DRAGON, PokemonType.GHOST, 0.5, 2, AbilityId.CLEAR_BODY, AbilityId.INFILTRATOR, AbilityId.CURSED_BODY, 270, 28, 60, 30, 40, 30, 82, 45, 50, 54, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.DRAKLOAK, 8, false, false, false, "Caretaker Pokémon", PokemonType.DRAGON, PokemonType.GHOST, 1.4, 11, AbilityId.CLEAR_BODY, AbilityId.INFILTRATOR, AbilityId.CURSED_BODY, 410, 68, 80, 50, 60, 50, 102, 45, 50, 144, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.DRAGAPULT, 8, false, false, false, "Stealth Pokémon", PokemonType.DRAGON, PokemonType.GHOST, 3, 50, AbilityId.CLEAR_BODY, AbilityId.INFILTRATOR, AbilityId.CURSED_BODY, 600, 88, 120, 75, 100, 75, 142, 45, 50, 300, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.ZACIAN, 8, false, true, false, "Warrior Pokémon", PokemonType.FAIRY, null, 2.8, 110, AbilityId.INTREPID_SWORD, AbilityId.NONE, AbilityId.NONE, 660, 92, 120, 115, 80, 115, 138, 10, 0, 335, GrowthRate.SLOW, null, false, false, + new PokemonForm("Hero of Many Battles", "hero-of-many-battles", PokemonType.FAIRY, null, 2.8, 110, AbilityId.INTREPID_SWORD, AbilityId.NONE, AbilityId.NONE, 660, 92, 120, 115, 80, 115, 138, 10, 0, 335, false, "", true), + new PokemonForm("Crowned", "crowned", PokemonType.FAIRY, PokemonType.STEEL, 2.8, 355, AbilityId.INTREPID_SWORD, AbilityId.NONE, AbilityId.NONE, 700, 92, 150, 115, 80, 115, 148, 10, 0, 360) + ), + new PokemonSpecies(SpeciesId.ZAMAZENTA, 8, false, true, false, "Warrior Pokémon", PokemonType.FIGHTING, null, 2.9, 210, AbilityId.DAUNTLESS_SHIELD, AbilityId.NONE, AbilityId.NONE, 660, 92, 120, 115, 80, 115, 138, 10, 0, 335, GrowthRate.SLOW, null, false, false, + new PokemonForm("Hero of Many Battles", "hero-of-many-battles", PokemonType.FIGHTING, null, 2.9, 210, AbilityId.DAUNTLESS_SHIELD, AbilityId.NONE, AbilityId.NONE, 660, 92, 120, 115, 80, 115, 138, 10, 0, 335, false, "", true), + new PokemonForm("Crowned", "crowned", PokemonType.FIGHTING, PokemonType.STEEL, 2.9, 785, AbilityId.DAUNTLESS_SHIELD, AbilityId.NONE, AbilityId.NONE, 700, 92, 120, 140, 80, 140, 128, 10, 0, 360) + ), + new PokemonSpecies(SpeciesId.ETERNATUS, 8, false, true, false, "Gigantic Pokémon", PokemonType.POISON, PokemonType.DRAGON, 20, 950, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 690, 140, 85, 95, 145, 95, 130, 255, 0, 345, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal", "", PokemonType.POISON, PokemonType.DRAGON, 20, 950, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 690, 140, 85, 95, 145, 95, 130, 255, 0, 345, false, null, true), + new PokemonForm("E-Max", "eternamax", PokemonType.POISON, PokemonType.DRAGON, 100, 999.9, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 1125, 255, 115, 250, 125, 250, 130, 255, 0, 345) + ), + new PokemonSpecies(SpeciesId.KUBFU, 8, true, false, false, "Wushu Pokémon", PokemonType.FIGHTING, null, 0.6, 12, AbilityId.INNER_FOCUS, AbilityId.NONE, AbilityId.NONE, 385, 60, 90, 60, 53, 50, 72, 3, 50, 77, GrowthRate.SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.URSHIFU, 8, true, false, false, "Wushu Pokémon", PokemonType.FIGHTING, PokemonType.DARK, 1.9, 105, AbilityId.UNSEEN_FIST, AbilityId.NONE, AbilityId.NONE, 550, 100, 130, 100, 63, 60, 97, 3, 50, 275, GrowthRate.SLOW, 87.5, false, true, + new PokemonForm("Single Strike Style", "single-strike", PokemonType.FIGHTING, PokemonType.DARK, 1.9, 105, AbilityId.UNSEEN_FIST, AbilityId.NONE, AbilityId.NONE, 550, 100, 130, 100, 63, 60, 97, 3, 50, 275, false, "", true), + new PokemonForm("Rapid Strike Style", "rapid-strike", PokemonType.FIGHTING, PokemonType.WATER, 1.9, 105, AbilityId.UNSEEN_FIST, AbilityId.NONE, AbilityId.NONE, 550, 100, 130, 100, 63, 60, 97, 3, 50, 275, false, null, true), + new PokemonForm("G-Max Single Strike Style", SpeciesFormKey.GIGANTAMAX_SINGLE, PokemonType.FIGHTING, PokemonType.DARK, 29, 999.9, AbilityId.UNSEEN_FIST, AbilityId.NONE, AbilityId.NONE, 650, 125, 145, 115, 83, 70, 112, 3, 50, 275), + new PokemonForm("G-Max Rapid Strike Style", SpeciesFormKey.GIGANTAMAX_RAPID, PokemonType.FIGHTING, PokemonType.WATER, 26, 999.9, AbilityId.UNSEEN_FIST, AbilityId.NONE, AbilityId.NONE, 650, 125, 145, 115, 83, 70, 112, 3, 50, 275) + ), + new PokemonSpecies(SpeciesId.ZARUDE, 8, false, false, true, "Rogue Monkey Pokémon", PokemonType.DARK, PokemonType.GRASS, 1.8, 70, AbilityId.LEAF_GUARD, AbilityId.NONE, AbilityId.NONE, 600, 105, 120, 105, 70, 95, 105, 3, 0, 300, GrowthRate.SLOW, null, false, false, + new PokemonForm("Normal", "", PokemonType.DARK, PokemonType.GRASS, 1.8, 70, AbilityId.LEAF_GUARD, AbilityId.NONE, AbilityId.NONE, 600, 105, 120, 105, 70, 95, 105, 3, 0, 300, false, null, true), + new PokemonForm("Dada", "dada", PokemonType.DARK, PokemonType.GRASS, 1.8, 70, AbilityId.LEAF_GUARD, AbilityId.NONE, AbilityId.NONE, 600, 105, 120, 105, 70, 95, 105, 3, 0, 300, false, null, true) + ), + new PokemonSpecies(SpeciesId.REGIELEKI, 8, true, false, false, "Electron Pokémon", PokemonType.ELECTRIC, null, 1.2, 145, AbilityId.TRANSISTOR, AbilityId.NONE, AbilityId.NONE, 580, 80, 100, 50, 100, 50, 200, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.REGIDRAGO, 8, true, false, false, "Dragon Orb Pokémon", PokemonType.DRAGON, null, 2.1, 200, AbilityId.DRAGONS_MAW, AbilityId.NONE, AbilityId.NONE, 580, 200, 100, 50, 100, 50, 80, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.GLASTRIER, 8, true, false, false, "Wild Horse Pokémon", PokemonType.ICE, null, 2.2, 800, AbilityId.CHILLING_NEIGH, AbilityId.NONE, AbilityId.NONE, 580, 100, 145, 130, 65, 110, 30, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.SPECTRIER, 8, true, false, false, "Swift Horse Pokémon", PokemonType.GHOST, null, 2, 44.5, AbilityId.GRIM_NEIGH, AbilityId.NONE, AbilityId.NONE, 580, 100, 65, 60, 145, 80, 130, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.CALYREX, 8, false, true, false, "King Pokémon", PokemonType.PSYCHIC, PokemonType.GRASS, 1.1, 7.7, AbilityId.UNNERVE, AbilityId.NONE, AbilityId.NONE, 500, 100, 80, 80, 80, 80, 80, 3, 100, 250, GrowthRate.SLOW, null, false, true, + new PokemonForm("Normal", "", PokemonType.PSYCHIC, PokemonType.GRASS, 1.1, 7.7, AbilityId.UNNERVE, AbilityId.NONE, AbilityId.NONE, 500, 100, 80, 80, 80, 80, 80, 3, 100, 250, false, null, true), + new PokemonForm("Ice", "ice", PokemonType.PSYCHIC, PokemonType.ICE, 2.4, 809.1, AbilityId.AS_ONE_GLASTRIER, AbilityId.NONE, AbilityId.NONE, 680, 100, 165, 150, 85, 130, 50, 3, 100, 340), + new PokemonForm("Shadow", "shadow", PokemonType.PSYCHIC, PokemonType.GHOST, 2.4, 53.6, AbilityId.AS_ONE_SPECTRIER, AbilityId.NONE, AbilityId.NONE, 680, 100, 85, 80, 165, 100, 150, 3, 100, 340) + ), + new PokemonSpecies(SpeciesId.WYRDEER, 8, false, false, false, "Big Horn Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 1.8, 95.1, AbilityId.INTIMIDATE, AbilityId.FRISK, AbilityId.SAP_SIPPER, 525, 103, 105, 72, 105, 75, 65, 45, 50, 263, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.KLEAVOR, 8, false, false, false, "Axe Pokémon", PokemonType.BUG, PokemonType.ROCK, 1.8, 89, AbilityId.SWARM, AbilityId.SHEER_FORCE, AbilityId.SHARPNESS, 500, 70, 135, 95, 45, 70, 85, 15, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.URSALUNA, 8, false, false, false, "Peat Pokémon", PokemonType.GROUND, PokemonType.NORMAL, 2.4, 290, AbilityId.GUTS, AbilityId.BULLETPROOF, AbilityId.UNNERVE, 550, 130, 140, 105, 45, 80, 50, 20, 50, 275, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BASCULEGION, 8, false, false, false, "Big Fish Pokémon", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 112, 65, 80, 75, 78, 45, 50, 265, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Male", "male", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 112, 65, 80, 75, 78, 45, 50, 265, false, "", true), + new PokemonForm("Female", "female", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 92, 65, 100, 75, 78, 45, 50, 265, false, null, true) + ), + new PokemonSpecies(SpeciesId.SNEASLER, 8, false, false, false, "Free Climb Pokémon", PokemonType.FIGHTING, PokemonType.POISON, 1.3, 43, AbilityId.PRESSURE, AbilityId.UNBURDEN, AbilityId.POISON_TOUCH, 510, 80, 130, 60, 40, 80, 120, 20, 50, 102, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.OVERQWIL, 8, false, false, false, "Pin Cluster Pokémon", PokemonType.DARK, PokemonType.POISON, 2.5, 60.5, AbilityId.POISON_POINT, AbilityId.SWIFT_SWIM, AbilityId.INTIMIDATE, 510, 85, 115, 95, 65, 65, 85, 45, 50, 179, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ENAMORUS, 8, true, false, false, "Love-Hate Pokémon", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.CONTRARY, 580, 74, 115, 70, 135, 80, 106, 3, 50, 116, GrowthRate.SLOW, 0, false, true, + new PokemonForm("Incarnate Forme", "incarnate", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.CONTRARY, 580, 74, 115, 70, 135, 80, 106, 3, 50, 116, false, null, true), + new PokemonForm("Therian Forme", "therian", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.OVERCOAT, 580, 74, 115, 110, 135, 100, 46, 3, 50, 116) + ), + new PokemonSpecies(SpeciesId.SPRIGATITO, 9, false, false, false, "Grass Cat Pokémon", PokemonType.GRASS, null, 0.4, 4.1, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.PROTEAN, 310, 40, 61, 54, 45, 45, 65, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.FLORAGATO, 9, false, false, false, "Grass Cat Pokémon", PokemonType.GRASS, null, 0.9, 12.2, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.PROTEAN, 410, 61, 80, 63, 60, 63, 83, 45, 50, 144, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.MEOWSCARADA, 9, false, false, false, "Magician Pokémon", PokemonType.GRASS, PokemonType.DARK, 1.5, 31.2, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.PROTEAN, 530, 76, 110, 70, 81, 70, 123, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.FUECOCO, 9, false, false, false, "Fire Croc Pokémon", PokemonType.FIRE, null, 0.4, 9.8, AbilityId.BLAZE, AbilityId.NONE, AbilityId.UNAWARE, 310, 67, 45, 59, 63, 40, 36, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.CROCALOR, 9, false, false, false, "Fire Croc Pokémon", PokemonType.FIRE, null, 1, 30.7, AbilityId.BLAZE, AbilityId.NONE, AbilityId.UNAWARE, 411, 81, 55, 78, 90, 58, 49, 45, 50, 144, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.SKELEDIRGE, 9, false, false, false, "Singer Pokémon", PokemonType.FIRE, PokemonType.GHOST, 1.6, 326.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.UNAWARE, 530, 104, 75, 100, 110, 75, 66, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.QUAXLY, 9, false, false, false, "Duckling Pokémon", PokemonType.WATER, null, 0.5, 6.1, AbilityId.TORRENT, AbilityId.NONE, AbilityId.MOXIE, 310, 55, 65, 45, 50, 45, 50, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.QUAXWELL, 9, false, false, false, "Practicing Pokémon", PokemonType.WATER, null, 1.2, 21.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.MOXIE, 410, 70, 85, 65, 65, 60, 65, 45, 50, 144, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.QUAQUAVAL, 9, false, false, false, "Dancer Pokémon", PokemonType.WATER, PokemonType.FIGHTING, 1.8, 61.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.MOXIE, 530, 85, 120, 80, 85, 75, 85, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.LECHONK, 9, false, false, false, "Hog Pokémon", PokemonType.NORMAL, null, 0.5, 10.2, AbilityId.AROMA_VEIL, AbilityId.GLUTTONY, AbilityId.THICK_FAT, 254, 54, 45, 40, 35, 45, 35, 255, 50, 51, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.OINKOLOGNE, 9, false, false, false, "Hog Pokémon", PokemonType.NORMAL, null, 1, 120, AbilityId.LINGERING_AROMA, AbilityId.GLUTTONY, AbilityId.THICK_FAT, 489, 110, 100, 75, 59, 80, 65, 100, 50, 171, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Male", "male", PokemonType.NORMAL, null, 1, 120, AbilityId.LINGERING_AROMA, AbilityId.GLUTTONY, AbilityId.THICK_FAT, 489, 110, 100, 75, 59, 80, 65, 100, 50, 171, false, "", true), + new PokemonForm("Female", "female", PokemonType.NORMAL, null, 1, 120, AbilityId.AROMA_VEIL, AbilityId.GLUTTONY, AbilityId.THICK_FAT, 489, 115, 90, 70, 59, 90, 65, 100, 50, 171, false, null, true) + ), + new PokemonSpecies(SpeciesId.TAROUNTULA, 9, false, false, false, "String Ball Pokémon", PokemonType.BUG, null, 0.3, 4, AbilityId.INSOMNIA, AbilityId.NONE, AbilityId.STAKEOUT, 210, 35, 41, 45, 29, 40, 20, 255, 50, 42, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(SpeciesId.SPIDOPS, 9, false, false, false, "Trap Pokémon", PokemonType.BUG, null, 1, 16.5, AbilityId.INSOMNIA, AbilityId.NONE, AbilityId.STAKEOUT, 404, 60, 79, 92, 52, 86, 35, 120, 50, 141, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(SpeciesId.NYMBLE, 9, false, false, false, "Grasshopper Pokémon", PokemonType.BUG, null, 0.2, 1, AbilityId.SWARM, AbilityId.NONE, AbilityId.TINTED_LENS, 210, 33, 46, 40, 21, 25, 45, 190, 20, 42, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.LOKIX, 9, false, false, false, "Grasshopper Pokémon", PokemonType.BUG, PokemonType.DARK, 1, 17.5, AbilityId.SWARM, AbilityId.NONE, AbilityId.TINTED_LENS, 450, 71, 102, 78, 52, 55, 92, 30, 0, 158, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PAWMI, 9, false, false, false, "Mouse Pokémon", PokemonType.ELECTRIC, null, 0.3, 2.5, AbilityId.STATIC, AbilityId.NATURAL_CURE, AbilityId.IRON_FIST, 240, 45, 50, 20, 40, 25, 60, 190, 50, 48, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PAWMO, 9, false, false, false, "Mouse Pokémon", PokemonType.ELECTRIC, PokemonType.FIGHTING, 0.4, 6.5, AbilityId.VOLT_ABSORB, AbilityId.NATURAL_CURE, AbilityId.IRON_FIST, 350, 60, 75, 40, 50, 40, 85, 80, 50, 123, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.PAWMOT, 9, false, false, false, "Hands-On Pokémon", PokemonType.ELECTRIC, PokemonType.FIGHTING, 0.9, 41, AbilityId.VOLT_ABSORB, AbilityId.NATURAL_CURE, AbilityId.IRON_FIST, 490, 70, 115, 70, 70, 60, 105, 45, 50, 245, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.TANDEMAUS, 9, false, false, false, "Couple Pokémon", PokemonType.NORMAL, null, 0.3, 1.8, AbilityId.RUN_AWAY, AbilityId.PICKUP, AbilityId.OWN_TEMPO, 305, 50, 50, 45, 40, 45, 75, 150, 50, 61, GrowthRate.FAST, null, false), + new PokemonSpecies(SpeciesId.MAUSHOLD, 9, false, false, false, "Family Pokémon", PokemonType.NORMAL, null, 0.3, 2.3, AbilityId.FRIEND_GUARD, AbilityId.CHEEK_POUCH, AbilityId.TECHNICIAN, 470, 74, 75, 70, 65, 75, 111, 75, 50, 165, GrowthRate.FAST, null, false, false, + new PokemonForm("Family of Four", "four", PokemonType.NORMAL, null, 0.3, 2.8, AbilityId.FRIEND_GUARD, AbilityId.CHEEK_POUCH, AbilityId.TECHNICIAN, 470, 74, 75, 70, 65, 75, 111, 75, 50, 165), + new PokemonForm("Family of Three", "three", PokemonType.NORMAL, null, 0.3, 2.3, AbilityId.FRIEND_GUARD, AbilityId.CHEEK_POUCH, AbilityId.TECHNICIAN, 470, 74, 75, 70, 65, 75, 111, 75, 50, 165) + ), + new PokemonSpecies(SpeciesId.FIDOUGH, 9, false, false, false, "Puppy Pokémon", PokemonType.FAIRY, null, 0.3, 10.9, AbilityId.OWN_TEMPO, AbilityId.NONE, AbilityId.KLUTZ, 312, 37, 55, 70, 30, 55, 65, 190, 50, 62, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.DACHSBUN, 9, false, false, false, "Dog Pokémon", PokemonType.FAIRY, null, 0.5, 14.9, AbilityId.WELL_BAKED_BODY, AbilityId.NONE, AbilityId.AROMA_VEIL, 477, 57, 80, 115, 50, 80, 95, 90, 50, 167, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SMOLIV, 9, false, false, false, "Olive Pokémon", PokemonType.GRASS, PokemonType.NORMAL, 0.3, 6.5, AbilityId.EARLY_BIRD, AbilityId.NONE, AbilityId.HARVEST, 260, 41, 35, 45, 58, 51, 30, 255, 50, 52, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.DOLLIV, 9, false, false, false, "Olive Pokémon", PokemonType.GRASS, PokemonType.NORMAL, 0.6, 11.9, AbilityId.EARLY_BIRD, AbilityId.NONE, AbilityId.HARVEST, 354, 52, 53, 60, 78, 78, 33, 120, 50, 124, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.ARBOLIVA, 9, false, false, false, "Olive Pokémon", PokemonType.GRASS, PokemonType.NORMAL, 1.4, 48.2, AbilityId.SEED_SOWER, AbilityId.NONE, AbilityId.HARVEST, 510, 78, 69, 90, 125, 109, 39, 45, 50, 255, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SQUAWKABILLY, 9, false, false, false, "Parrot Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 2.4, AbilityId.INTIMIDATE, AbilityId.HUSTLE, AbilityId.GUTS, 417, 82, 96, 51, 45, 51, 92, 190, 50, 146, GrowthRate.ERRATIC, 50, false, false, + new PokemonForm("Green Plumage", "green-plumage", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 2.4, AbilityId.INTIMIDATE, AbilityId.HUSTLE, AbilityId.GUTS, 417, 82, 96, 51, 45, 51, 92, 190, 50, 146, false, null, true), + new PokemonForm("Blue Plumage", "blue-plumage", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 2.4, AbilityId.INTIMIDATE, AbilityId.HUSTLE, AbilityId.GUTS, 417, 82, 96, 51, 45, 51, 92, 190, 50, 146, false, null, true), + new PokemonForm("Yellow Plumage", "yellow-plumage", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 2.4, AbilityId.INTIMIDATE, AbilityId.HUSTLE, AbilityId.SHEER_FORCE, 417, 82, 96, 51, 45, 51, 92, 190, 50, 146, false, null, true), + new PokemonForm("White Plumage", "white-plumage", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 2.4, AbilityId.INTIMIDATE, AbilityId.HUSTLE, AbilityId.SHEER_FORCE, 417, 82, 96, 51, 45, 51, 92, 190, 50, 146, false, null, true) + ), + new PokemonSpecies(SpeciesId.NACLI, 9, false, false, false, "Rock Salt Pokémon", PokemonType.ROCK, null, 0.4, 16, AbilityId.PURIFYING_SALT, AbilityId.STURDY, AbilityId.CLEAR_BODY, 280, 55, 55, 75, 35, 35, 25, 255, 50, 56, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.NACLSTACK, 9, false, false, false, "Rock Salt Pokémon", PokemonType.ROCK, null, 0.6, 105, AbilityId.PURIFYING_SALT, AbilityId.STURDY, AbilityId.CLEAR_BODY, 355, 60, 60, 100, 35, 65, 35, 120, 50, 124, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.GARGANACL, 9, false, false, false, "Rock Salt Pokémon", PokemonType.ROCK, null, 2.3, 240, AbilityId.PURIFYING_SALT, AbilityId.STURDY, AbilityId.CLEAR_BODY, 500, 100, 100, 130, 45, 90, 35, 45, 50, 250, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.CHARCADET, 9, false, false, false, "Fire Child Pokémon", PokemonType.FIRE, null, 0.6, 10.5, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.FLAME_BODY, 255, 40, 50, 40, 50, 40, 35, 90, 50, 51, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.ARMAROUGE, 9, false, false, false, "Fire Warrior Pokémon", PokemonType.FIRE, PokemonType.PSYCHIC, 1.5, 85, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.WEAK_ARMOR, 525, 85, 60, 100, 125, 80, 75, 25, 20, 263, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.CERULEDGE, 9, false, false, false, "Fire Blades Pokémon", PokemonType.FIRE, PokemonType.GHOST, 1.6, 62, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.WEAK_ARMOR, 525, 75, 125, 80, 60, 100, 85, 25, 20, 263, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.TADBULB, 9, false, false, false, "EleTadpole Pokémon", PokemonType.ELECTRIC, null, 0.3, 0.4, AbilityId.OWN_TEMPO, AbilityId.STATIC, AbilityId.DAMP, 272, 61, 31, 41, 59, 35, 45, 190, 50, 54, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BELLIBOLT, 9, false, false, false, "EleFrog Pokémon", PokemonType.ELECTRIC, null, 1.2, 113, AbilityId.ELECTROMORPHOSIS, AbilityId.STATIC, AbilityId.DAMP, 495, 109, 64, 91, 103, 83, 45, 50, 50, 173, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.WATTREL, 9, false, false, false, "Storm Petrel Pokémon", PokemonType.ELECTRIC, PokemonType.FLYING, 0.4, 3.6, AbilityId.WIND_POWER, AbilityId.VOLT_ABSORB, AbilityId.COMPETITIVE, 280, 40, 40, 35, 55, 40, 70, 180, 50, 56, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.KILOWATTREL, 9, false, false, false, "Frigatebird Pokémon", PokemonType.ELECTRIC, PokemonType.FLYING, 1.4, 38.6, AbilityId.WIND_POWER, AbilityId.VOLT_ABSORB, AbilityId.COMPETITIVE, 490, 70, 70, 60, 105, 60, 125, 90, 50, 172, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.MASCHIFF, 9, false, false, false, "Rascal Pokémon", PokemonType.DARK, null, 0.5, 16, AbilityId.INTIMIDATE, AbilityId.RUN_AWAY, AbilityId.STAKEOUT, 340, 60, 78, 60, 40, 51, 51, 150, 50, 68, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.MABOSSTIFF, 9, false, false, false, "Boss Pokémon", PokemonType.DARK, null, 1.1, 61, AbilityId.INTIMIDATE, AbilityId.GUARD_DOG, AbilityId.STAKEOUT, 505, 80, 120, 90, 60, 70, 85, 75, 50, 177, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.SHROODLE, 9, false, false, false, "Toxic Mouse Pokémon", PokemonType.POISON, PokemonType.NORMAL, 0.2, 0.7, AbilityId.UNBURDEN, AbilityId.PICKPOCKET, AbilityId.PRANKSTER, 290, 40, 65, 35, 40, 35, 75, 190, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.GRAFAIAI, 9, false, false, false, "Toxic Monkey Pokémon", PokemonType.POISON, PokemonType.NORMAL, 0.7, 27.2, AbilityId.UNBURDEN, AbilityId.POISON_TOUCH, AbilityId.PRANKSTER, 485, 63, 95, 65, 80, 72, 110, 90, 50, 170, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.BRAMBLIN, 9, false, false, false, "Tumbleweed Pokémon", PokemonType.GRASS, PokemonType.GHOST, 0.6, 0.6, AbilityId.WIND_RIDER, AbilityId.NONE, AbilityId.INFILTRATOR, 275, 40, 65, 30, 45, 35, 60, 190, 50, 55, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BRAMBLEGHAST, 9, false, false, false, "Tumbleweed Pokémon", PokemonType.GRASS, PokemonType.GHOST, 1.2, 6, AbilityId.WIND_RIDER, AbilityId.NONE, AbilityId.INFILTRATOR, 480, 55, 115, 70, 80, 70, 90, 45, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.TOEDSCOOL, 9, false, false, false, "Woodear Pokémon", PokemonType.GROUND, PokemonType.GRASS, 0.9, 33, AbilityId.MYCELIUM_MIGHT, AbilityId.NONE, AbilityId.NONE, 335, 40, 40, 35, 50, 100, 70, 190, 50, 67, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.TOEDSCRUEL, 9, false, false, false, "Woodear Pokémon", PokemonType.GROUND, PokemonType.GRASS, 1.9, 58, AbilityId.MYCELIUM_MIGHT, AbilityId.NONE, AbilityId.NONE, 515, 80, 70, 65, 80, 120, 100, 90, 50, 180, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.KLAWF, 9, false, false, false, "Ambush Pokémon", PokemonType.ROCK, null, 1.3, 79, AbilityId.ANGER_SHELL, AbilityId.SHELL_ARMOR, AbilityId.REGENERATOR, 450, 70, 100, 115, 35, 55, 75, 120, 50, 158, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CAPSAKID, 9, false, false, false, "Spicy Pepper Pokémon", PokemonType.GRASS, null, 0.3, 3, AbilityId.CHLOROPHYLL, AbilityId.INSOMNIA, AbilityId.KLUTZ, 304, 50, 62, 40, 62, 40, 50, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.SCOVILLAIN, 9, false, false, false, "Spicy Pepper Pokémon", PokemonType.GRASS, PokemonType.FIRE, 0.9, 15, AbilityId.CHLOROPHYLL, AbilityId.INSOMNIA, AbilityId.MOODY, 486, 65, 108, 65, 108, 65, 75, 75, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.RELLOR, 9, false, false, false, "Rolling Pokémon", PokemonType.BUG, null, 0.2, 1, AbilityId.COMPOUND_EYES, AbilityId.NONE, AbilityId.SHED_SKIN, 270, 41, 50, 60, 31, 58, 30, 190, 50, 54, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.RABSCA, 9, false, false, false, "Rolling Pokémon", PokemonType.BUG, PokemonType.PSYCHIC, 0.3, 3.5, AbilityId.SYNCHRONIZE, AbilityId.NONE, AbilityId.TELEPATHY, 470, 75, 50, 85, 115, 100, 45, 45, 50, 165, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.FLITTLE, 9, false, false, false, "Frill Pokémon", PokemonType.PSYCHIC, null, 0.2, 1.5, AbilityId.ANTICIPATION, AbilityId.FRISK, AbilityId.SPEED_BOOST, 255, 30, 35, 30, 55, 30, 75, 120, 50, 51, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.ESPATHRA, 9, false, false, false, "Ostrich Pokémon", PokemonType.PSYCHIC, null, 1.9, 90, AbilityId.OPPORTUNIST, AbilityId.FRISK, AbilityId.SPEED_BOOST, 481, 95, 60, 60, 101, 60, 105, 60, 50, 168, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.TINKATINK, 9, false, false, false, "Metalsmith Pokémon", PokemonType.FAIRY, PokemonType.STEEL, 0.4, 8.9, AbilityId.MOLD_BREAKER, AbilityId.OWN_TEMPO, AbilityId.PICKPOCKET, 297, 50, 45, 45, 35, 64, 58, 190, 50, 59, GrowthRate.MEDIUM_SLOW, 0, false), + new PokemonSpecies(SpeciesId.TINKATUFF, 9, false, false, false, "Hammer Pokémon", PokemonType.FAIRY, PokemonType.STEEL, 0.7, 59.1, AbilityId.MOLD_BREAKER, AbilityId.OWN_TEMPO, AbilityId.PICKPOCKET, 380, 65, 55, 55, 45, 82, 78, 90, 50, 133, GrowthRate.MEDIUM_SLOW, 0, false), + new PokemonSpecies(SpeciesId.TINKATON, 9, false, false, false, "Hammer Pokémon", PokemonType.FAIRY, PokemonType.STEEL, 0.7, 112.8, AbilityId.MOLD_BREAKER, AbilityId.OWN_TEMPO, AbilityId.PICKPOCKET, 506, 85, 75, 77, 70, 105, 94, 45, 50, 253, GrowthRate.MEDIUM_SLOW, 0, false), + new PokemonSpecies(SpeciesId.WIGLETT, 9, false, false, false, "Garden Eel Pokémon", PokemonType.WATER, null, 1.2, 1.8, AbilityId.GOOEY, AbilityId.RATTLED, AbilityId.SAND_VEIL, 245, 10, 55, 25, 35, 25, 95, 255, 50, 49, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.WUGTRIO, 9, false, false, false, "Garden Eel Pokémon", PokemonType.WATER, null, 1.2, 5.4, AbilityId.GOOEY, AbilityId.RATTLED, AbilityId.SAND_VEIL, 425, 35, 100, 50, 50, 70, 120, 50, 50, 149, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BOMBIRDIER, 9, false, false, false, "Item Drop Pokémon", PokemonType.FLYING, PokemonType.DARK, 1.5, 42.9, AbilityId.BIG_PECKS, AbilityId.KEEN_EYE, AbilityId.ROCKY_PAYLOAD, 485, 70, 103, 85, 60, 85, 82, 25, 50, 243, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.FINIZEN, 9, false, false, false, "Dolphin Pokémon", PokemonType.WATER, null, 1.3, 60.2, AbilityId.WATER_VEIL, AbilityId.NONE, AbilityId.NONE, 315, 70, 45, 40, 45, 40, 75, 200, 50, 63, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.PALAFIN, 9, false, false, false, "Dolphin Pokémon", PokemonType.WATER, null, 1.3, 60.2, AbilityId.ZERO_TO_HERO, AbilityId.NONE, AbilityId.NONE, 457, 100, 70, 72, 53, 62, 100, 45, 50, 160, GrowthRate.SLOW, 50, false, true, + new PokemonForm("Zero Form", "zero", PokemonType.WATER, null, 1.3, 60.2, AbilityId.ZERO_TO_HERO, AbilityId.NONE, AbilityId.ZERO_TO_HERO, 457, 100, 70, 72, 53, 62, 100, 45, 50, 160, false, null, true), + new PokemonForm("Hero Form", "hero", PokemonType.WATER, null, 1.8, 97.4, AbilityId.ZERO_TO_HERO, AbilityId.NONE, AbilityId.ZERO_TO_HERO, 650, 100, 160, 97, 106, 87, 100, 45, 50, 160) + ), + new PokemonSpecies(SpeciesId.VAROOM, 9, false, false, false, "Single-Cyl Pokémon", PokemonType.STEEL, PokemonType.POISON, 1, 35, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.SLOW_START, 300, 45, 70, 63, 30, 45, 47, 190, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.REVAVROOM, 9, false, false, false, "Multi-Cyl Pokémon", PokemonType.STEEL, PokemonType.POISON, 1.8, 120, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.POISON, 1.8, 120, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, false, null, true), + new PokemonForm("Segin Starmobile", "segin-starmobile", PokemonType.STEEL, PokemonType.DARK, 1.8, 240, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.INTIMIDATE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), + new PokemonForm("Schedar Starmobile", "schedar-starmobile", PokemonType.STEEL, PokemonType.FIRE, 1.8, 240, AbilityId.SPEED_BOOST, AbilityId.NONE, AbilityId.SPEED_BOOST, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), + new PokemonForm("Navi Starmobile", "navi-starmobile", PokemonType.STEEL, PokemonType.POISON, 1.8, 240, AbilityId.TOXIC_DEBRIS, AbilityId.NONE, AbilityId.TOXIC_DEBRIS, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), + new PokemonForm("Ruchbah Starmobile", "ruchbah-starmobile", PokemonType.STEEL, PokemonType.FAIRY, 1.8, 240, AbilityId.MISTY_SURGE, AbilityId.NONE, AbilityId.MISTY_SURGE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), + new PokemonForm("Caph Starmobile", "caph-starmobile", PokemonType.STEEL, PokemonType.FIGHTING, 1.8, 240, AbilityId.STAMINA, AbilityId.NONE, AbilityId.STAMINA, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true) + ), + new PokemonSpecies(SpeciesId.CYCLIZAR, 9, false, false, false, "Mount Pokémon", PokemonType.DRAGON, PokemonType.NORMAL, 1.6, 63, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.REGENERATOR, 501, 70, 95, 65, 85, 65, 121, 190, 50, 175, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.ORTHWORM, 9, false, false, false, "Earthworm Pokémon", PokemonType.STEEL, null, 2.5, 310, AbilityId.EARTH_EATER, AbilityId.NONE, AbilityId.SAND_VEIL, 480, 70, 85, 145, 60, 55, 65, 25, 50, 240, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.GLIMMET, 9, false, false, false, "Ore Pokémon", PokemonType.ROCK, PokemonType.POISON, 0.7, 8, AbilityId.TOXIC_DEBRIS, AbilityId.NONE, AbilityId.CORROSION, 350, 48, 35, 42, 105, 60, 60, 70, 50, 70, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.GLIMMORA, 9, false, false, false, "Ore Pokémon", PokemonType.ROCK, PokemonType.POISON, 1.5, 45, AbilityId.TOXIC_DEBRIS, AbilityId.NONE, AbilityId.CORROSION, 525, 83, 55, 90, 130, 81, 86, 25, 50, 184, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.GREAVARD, 9, false, false, false, "Ghost Dog Pokémon", PokemonType.GHOST, null, 0.6, 35, AbilityId.PICKUP, AbilityId.NONE, AbilityId.FLUFFY, 290, 50, 61, 60, 30, 55, 34, 120, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.HOUNDSTONE, 9, false, false, false, "Ghost Dog Pokémon", PokemonType.GHOST, null, 2, 15, AbilityId.SAND_RUSH, AbilityId.NONE, AbilityId.FLUFFY, 488, 72, 101, 100, 50, 97, 68, 60, 50, 171, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.FLAMIGO, 9, false, false, false, "Synchronize Pokémon", PokemonType.FLYING, PokemonType.FIGHTING, 1.6, 37, AbilityId.SCRAPPY, AbilityId.TANGLED_FEET, AbilityId.COSTAR, 500, 82, 115, 74, 75, 64, 90, 100, 50, 175, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.CETODDLE, 9, false, false, false, "Terra Whale Pokémon", PokemonType.ICE, null, 1.2, 45, AbilityId.THICK_FAT, AbilityId.SNOW_CLOAK, AbilityId.SHEER_FORCE, 334, 108, 68, 45, 30, 40, 43, 150, 50, 67, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.CETITAN, 9, false, false, false, "Terra Whale Pokémon", PokemonType.ICE, null, 4.5, 700, AbilityId.THICK_FAT, AbilityId.SLUSH_RUSH, AbilityId.SHEER_FORCE, 521, 170, 113, 65, 45, 55, 73, 50, 50, 182, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.VELUZA, 9, false, false, false, "Jettison Pokémon", PokemonType.WATER, PokemonType.PSYCHIC, 2.5, 90, AbilityId.MOLD_BREAKER, AbilityId.NONE, AbilityId.SHARPNESS, 478, 90, 102, 73, 78, 65, 70, 100, 50, 167, GrowthRate.FAST, 50, false), + new PokemonSpecies(SpeciesId.DONDOZO, 9, false, false, false, "Big Catfish Pokémon", PokemonType.WATER, null, 12, 220, AbilityId.UNAWARE, AbilityId.OBLIVIOUS, AbilityId.WATER_VEIL, 530, 150, 100, 115, 65, 65, 35, 25, 50, 265, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.TATSUGIRI, 9, false, false, false, "Mimicry Pokémon", PokemonType.DRAGON, PokemonType.WATER, 0.3, 8, AbilityId.COMMANDER, AbilityId.NONE, AbilityId.STORM_DRAIN, 475, 68, 50, 60, 120, 95, 82, 100, 50, 166, GrowthRate.MEDIUM_SLOW, 50, false, false, + new PokemonForm("Curly Form", "curly", PokemonType.DRAGON, PokemonType.WATER, 0.3, 8, AbilityId.COMMANDER, AbilityId.NONE, AbilityId.STORM_DRAIN, 475, 68, 50, 60, 120, 95, 82, 100, 50, 166, false, null, true), + new PokemonForm("Droopy Form", "droopy", PokemonType.DRAGON, PokemonType.WATER, 0.3, 8, AbilityId.COMMANDER, AbilityId.NONE, AbilityId.STORM_DRAIN, 475, 68, 50, 60, 120, 95, 82, 100, 50, 166, false, null, true), + new PokemonForm("Stretchy Form", "stretchy", PokemonType.DRAGON, PokemonType.WATER, 0.3, 8, AbilityId.COMMANDER, AbilityId.NONE, AbilityId.STORM_DRAIN, 475, 68, 50, 60, 120, 95, 82, 100, 50, 166, false, null, true) + ), + new PokemonSpecies(SpeciesId.ANNIHILAPE, 9, false, false, false, "Rage Monkey Pokémon", PokemonType.FIGHTING, PokemonType.GHOST, 1.2, 56, AbilityId.VITAL_SPIRIT, AbilityId.INNER_FOCUS, AbilityId.DEFIANT, 535, 110, 115, 80, 50, 90, 90, 45, 50, 268, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.CLODSIRE, 9, false, false, false, "Spiny Fish Pokémon", PokemonType.POISON, PokemonType.GROUND, 1.8, 223, AbilityId.POISON_POINT, AbilityId.WATER_ABSORB, AbilityId.UNAWARE, 430, 130, 75, 60, 45, 100, 20, 90, 50, 151, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.FARIGIRAF, 9, false, false, false, "Long Neck Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 3.2, 160, AbilityId.CUD_CHEW, AbilityId.ARMOR_TAIL, AbilityId.SAP_SIPPER, 520, 120, 90, 70, 110, 70, 60, 45, 50, 260, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.DUDUNSPARCE, 9, false, false, false, "Land Snake Pokémon", PokemonType.NORMAL, null, 3.6, 39.2, AbilityId.SERENE_GRACE, AbilityId.RUN_AWAY, AbilityId.RATTLED, 520, 125, 100, 80, 85, 75, 55, 45, 50, 182, GrowthRate.MEDIUM_FAST, 50, false, false, + new PokemonForm("Two-Segment Form", "two-segment", PokemonType.NORMAL, null, 3.6, 39.2, AbilityId.SERENE_GRACE, AbilityId.RUN_AWAY, AbilityId.RATTLED, 520, 125, 100, 80, 85, 75, 55, 45, 50, 182, false, ""), + new PokemonForm("Three-Segment Form", "three-segment", PokemonType.NORMAL, null, 4.5, 47.4, AbilityId.SERENE_GRACE, AbilityId.RUN_AWAY, AbilityId.RATTLED, 520, 125, 100, 80, 85, 75, 55, 45, 50, 182) + ), + new PokemonSpecies(SpeciesId.KINGAMBIT, 9, false, false, false, "Big Blade Pokémon", PokemonType.DARK, PokemonType.STEEL, 2, 120, AbilityId.DEFIANT, AbilityId.SUPREME_OVERLORD, AbilityId.PRESSURE, 550, 100, 135, 120, 60, 85, 50, 25, 50, 275, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GREAT_TUSK, 9, false, false, false, "Paradox Pokémon", PokemonType.GROUND, PokemonType.FIGHTING, 2.2, 320, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 570, 115, 131, 131, 53, 53, 87, 30, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.SCREAM_TAIL, 9, false, false, false, "Paradox Pokémon", PokemonType.FAIRY, PokemonType.PSYCHIC, 1.2, 8, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 570, 115, 65, 99, 65, 115, 111, 50, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.BRUTE_BONNET, 9, false, false, false, "Paradox Pokémon", PokemonType.GRASS, PokemonType.DARK, 1.2, 21, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 570, 111, 127, 99, 79, 99, 55, 50, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.FLUTTER_MANE, 9, false, false, false, "Paradox Pokémon", PokemonType.GHOST, PokemonType.FAIRY, 1.4, 4, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 570, 55, 55, 55, 135, 135, 135, 30, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.SLITHER_WING, 9, false, false, false, "Paradox Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 3.2, 92, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 570, 85, 135, 79, 85, 105, 81, 30, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.SANDY_SHOCKS, 9, false, false, false, "Paradox Pokémon", PokemonType.ELECTRIC, PokemonType.GROUND, 2.3, 60, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 570, 85, 81, 97, 121, 85, 101, 30, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.IRON_TREADS, 9, false, false, false, "Paradox Pokémon", PokemonType.GROUND, PokemonType.STEEL, 0.9, 240, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 570, 90, 112, 120, 72, 70, 106, 30, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.IRON_BUNDLE, 9, false, false, false, "Paradox Pokémon", PokemonType.ICE, PokemonType.WATER, 0.6, 11, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 570, 56, 80, 114, 124, 60, 136, 50, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.IRON_HANDS, 9, false, false, false, "Paradox Pokémon", PokemonType.FIGHTING, PokemonType.ELECTRIC, 1.8, 380.7, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 570, 154, 140, 108, 50, 68, 50, 50, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.IRON_JUGULIS, 9, false, false, false, "Paradox Pokémon", PokemonType.DARK, PokemonType.FLYING, 1.3, 111, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 570, 94, 80, 86, 122, 80, 108, 30, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.IRON_MOTH, 9, false, false, false, "Paradox Pokémon", PokemonType.FIRE, PokemonType.POISON, 1.2, 36, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 570, 80, 70, 60, 140, 110, 110, 30, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.IRON_THORNS, 9, false, false, false, "Paradox Pokémon", PokemonType.ROCK, PokemonType.ELECTRIC, 1.6, 303, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 570, 100, 134, 110, 70, 84, 72, 30, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.FRIGIBAX, 9, false, false, false, "Ice Fin Pokémon", PokemonType.DRAGON, PokemonType.ICE, 0.5, 17, AbilityId.THERMAL_EXCHANGE, AbilityId.NONE, AbilityId.ICE_BODY, 320, 65, 75, 45, 35, 45, 55, 45, 50, 64, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.ARCTIBAX, 9, false, false, false, "Ice Fin Pokémon", PokemonType.DRAGON, PokemonType.ICE, 0.8, 30, AbilityId.THERMAL_EXCHANGE, AbilityId.NONE, AbilityId.ICE_BODY, 423, 90, 95, 66, 45, 65, 62, 25, 50, 148, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.BAXCALIBUR, 9, false, false, false, "Ice Dragon Pokémon", PokemonType.DRAGON, PokemonType.ICE, 2.1, 210, AbilityId.THERMAL_EXCHANGE, AbilityId.NONE, AbilityId.ICE_BODY, 600, 115, 145, 92, 75, 86, 87, 10, 50, 300, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.GIMMIGHOUL, 9, false, false, false, "Coin Chest Pokémon", PokemonType.GHOST, null, 0.3, 5, AbilityId.RATTLED, AbilityId.NONE, AbilityId.NONE, 300, 45, 30, 70, 75, 70, 10, 45, 50, 60, GrowthRate.SLOW, null, false, false, + new PokemonForm("Chest Form", "chest", PokemonType.GHOST, null, 0.3, 5, AbilityId.RATTLED, AbilityId.NONE, AbilityId.NONE, 300, 45, 30, 70, 75, 70, 10, 45, 50, 60, false, "", true), + new PokemonForm("Roaming Form", "roaming", PokemonType.GHOST, null, 0.1, 1, AbilityId.RUN_AWAY, AbilityId.NONE, AbilityId.NONE, 300, 45, 30, 25, 75, 45, 80, 45, 50, 60, false, null, true) + ), + new PokemonSpecies(SpeciesId.GHOLDENGO, 9, false, false, false, "Coin Entity Pokémon", PokemonType.STEEL, PokemonType.GHOST, 1.2, 30, AbilityId.GOOD_AS_GOLD, AbilityId.NONE, AbilityId.NONE, 550, 87, 60, 95, 133, 91, 84, 45, 50, 275, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.WO_CHIEN, 9, true, false, false, "Ruinous Pokémon", PokemonType.DARK, PokemonType.GRASS, 1.5, 74.2, AbilityId.TABLETS_OF_RUIN, AbilityId.NONE, AbilityId.NONE, 570, 85, 85, 100, 95, 135, 70, 6, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.CHIEN_PAO, 9, true, false, false, "Ruinous Pokémon", PokemonType.DARK, PokemonType.ICE, 1.9, 152.2, AbilityId.SWORD_OF_RUIN, AbilityId.NONE, AbilityId.NONE, 570, 80, 120, 80, 90, 65, 135, 6, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.TING_LU, 9, true, false, false, "Ruinous Pokémon", PokemonType.DARK, PokemonType.GROUND, 2.7, 699.7, AbilityId.VESSEL_OF_RUIN, AbilityId.NONE, AbilityId.NONE, 570, 155, 110, 125, 55, 80, 45, 6, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.CHI_YU, 9, true, false, false, "Ruinous Pokémon", PokemonType.DARK, PokemonType.FIRE, 0.4, 4.9, AbilityId.BEADS_OF_RUIN, AbilityId.NONE, AbilityId.NONE, 570, 55, 80, 80, 135, 120, 100, 6, 0, 285, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.ROARING_MOON, 9, false, false, false, "Paradox Pokémon", PokemonType.DRAGON, PokemonType.DARK, 2, 380, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 590, 105, 139, 71, 55, 101, 119, 10, 0, 295, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.IRON_VALIANT, 9, false, false, false, "Paradox Pokémon", PokemonType.FAIRY, PokemonType.FIGHTING, 1.4, 35, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 590, 74, 130, 90, 120, 60, 116, 10, 0, 295, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.KORAIDON, 9, false, true, false, "Paradox Pokémon", PokemonType.FIGHTING, PokemonType.DRAGON, 2.5, 303, AbilityId.ORICHALCUM_PULSE, AbilityId.NONE, AbilityId.NONE, 670, 100, 135, 115, 85, 100, 135, 3, 0, 335, GrowthRate.SLOW, null, false, false, + new PokemonForm("Apex Build", "apex-build", PokemonType.FIGHTING, PokemonType.DRAGON, 2.5, 303, AbilityId.ORICHALCUM_PULSE, AbilityId.NONE, AbilityId.NONE, 670, 100, 135, 115, 85, 100, 135, 3, 0, 335, false, null, true) + ), + new PokemonSpecies(SpeciesId.MIRAIDON, 9, false, true, false, "Paradox Pokémon", PokemonType.ELECTRIC, PokemonType.DRAGON, 3.5, 240, AbilityId.HADRON_ENGINE, AbilityId.NONE, AbilityId.NONE, 670, 100, 85, 100, 135, 115, 135, 3, 0, 335, GrowthRate.SLOW, null, false, false, + new PokemonForm("Ultimate Mode", "ultimate-mode", PokemonType.ELECTRIC, PokemonType.DRAGON, 3.5, 240, AbilityId.HADRON_ENGINE, AbilityId.NONE, AbilityId.NONE, 670, 100, 85, 100, 135, 115, 135, 3, 0, 335, false, null, true) + ), + new PokemonSpecies(SpeciesId.WALKING_WAKE, 9, false, false, false, "Paradox Pokémon", PokemonType.WATER, PokemonType.DRAGON, 3.5, 280, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 590, 99, 83, 91, 125, 83, 109, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Gouging Fire and Raging Bolt + new PokemonSpecies(SpeciesId.IRON_LEAVES, 9, false, false, false, "Paradox Pokémon", PokemonType.GRASS, PokemonType.PSYCHIC, 1.5, 125, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 590, 90, 130, 88, 70, 108, 104, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Iron Boulder and Iron Crown + new PokemonSpecies(SpeciesId.DIPPLIN, 9, false, false, false, "Candy Apple Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 4.4, AbilityId.SUPERSWEET_SYRUP, AbilityId.GLUTTONY, AbilityId.STICKY_HOLD, 485, 80, 80, 110, 95, 80, 40, 45, 50, 170, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(SpeciesId.POLTCHAGEIST, 9, false, false, false, "Matcha Pokémon", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, GrowthRate.SLOW, null, false, false, + new PokemonForm("Counterfeit Form", "counterfeit", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, true), + new PokemonForm("Artisan Form", "artisan", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, "counterfeit", true) + ), + new PokemonSpecies(SpeciesId.SINISTCHA, 9, false, false, false, "Matcha Pokémon", PokemonType.GRASS, PokemonType.GHOST, 0.2, 2.2, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 508, 71, 60, 106, 121, 80, 70, 60, 50, 178, GrowthRate.SLOW, null, false, false, + new PokemonForm("Unremarkable Form", "unremarkable", PokemonType.GRASS, PokemonType.GHOST, 0.2, 2.2, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 508, 71, 60, 106, 121, 80, 70, 60, 50, 178, false, null, true), + new PokemonForm("Masterpiece Form", "masterpiece", PokemonType.GRASS, PokemonType.GHOST, 0.2, 2.2, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 508, 71, 60, 106, 121, 80, 70, 60, 50, 178, false, "unremarkable", true) + ), + new PokemonSpecies(SpeciesId.OKIDOGI, 9, true, false, false, "Retainer Pokémon", PokemonType.POISON, PokemonType.FIGHTING, 1.8, 92.2, AbilityId.TOXIC_CHAIN, AbilityId.NONE, AbilityId.GUARD_DOG, 555, 88, 128, 115, 58, 86, 80, 3, 0, 276, GrowthRate.SLOW, 100, false), + new PokemonSpecies(SpeciesId.MUNKIDORI, 9, true, false, false, "Retainer Pokémon", PokemonType.POISON, PokemonType.PSYCHIC, 1, 12.2, AbilityId.TOXIC_CHAIN, AbilityId.NONE, AbilityId.FRISK, 555, 88, 75, 66, 130, 90, 106, 3, 0, 276, GrowthRate.SLOW, 100, false), + new PokemonSpecies(SpeciesId.FEZANDIPITI, 9, true, false, false, "Retainer Pokémon", PokemonType.POISON, PokemonType.FAIRY, 1.4, 30.1, AbilityId.TOXIC_CHAIN, AbilityId.NONE, AbilityId.TECHNICIAN, 555, 88, 91, 82, 70, 125, 99, 3, 0, 276, GrowthRate.SLOW, 100, false), + new PokemonSpecies(SpeciesId.OGERPON, 9, true, false, false, "Mask Pokémon", PokemonType.GRASS, null, 1.2, 39.8, AbilityId.DEFIANT, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275, GrowthRate.SLOW, 0, false, false, + new PokemonForm("Teal Mask", "teal-mask", PokemonType.GRASS, null, 1.2, 39.8, AbilityId.DEFIANT, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275, false, null, true), + new PokemonForm("Wellspring Mask", "wellspring-mask", PokemonType.GRASS, PokemonType.WATER, 1.2, 39.8, AbilityId.WATER_ABSORB, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275), + new PokemonForm("Hearthflame Mask", "hearthflame-mask", PokemonType.GRASS, PokemonType.FIRE, 1.2, 39.8, AbilityId.MOLD_BREAKER, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275), + new PokemonForm("Cornerstone Mask", "cornerstone-mask", PokemonType.GRASS, PokemonType.ROCK, 1.2, 39.8, AbilityId.STURDY, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275), + new PokemonForm("Teal Mask Terastallized", "teal-mask-tera", PokemonType.GRASS, null, 1.2, 39.8, AbilityId.EMBODY_ASPECT_TEAL, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275), + new PokemonForm("Wellspring Mask Terastallized", "wellspring-mask-tera", PokemonType.GRASS, PokemonType.WATER, 1.2, 39.8, AbilityId.EMBODY_ASPECT_WELLSPRING, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275), + new PokemonForm("Hearthflame Mask Terastallized", "hearthflame-mask-tera", PokemonType.GRASS, PokemonType.FIRE, 1.2, 39.8, AbilityId.EMBODY_ASPECT_HEARTHFLAME, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275), + new PokemonForm("Cornerstone Mask Terastallized", "cornerstone-mask-tera", PokemonType.GRASS, PokemonType.ROCK, 1.2, 39.8, AbilityId.EMBODY_ASPECT_CORNERSTONE, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275) + ), + new PokemonSpecies(SpeciesId.ARCHALUDON, 9, false, false, false, "Alloy Pokémon", PokemonType.STEEL, PokemonType.DRAGON, 2, 60, AbilityId.STAMINA, AbilityId.STURDY, AbilityId.STALWART, 600, 90, 105, 130, 125, 65, 85, 10, 50, 300, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.HYDRAPPLE, 9, false, false, false, "Apple Hydra Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 1.8, 93, AbilityId.SUPERSWEET_SYRUP, AbilityId.REGENERATOR, AbilityId.STICKY_HOLD, 540, 106, 80, 110, 120, 80, 44, 10, 50, 270, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(SpeciesId.GOUGING_FIRE, 9, false, false, false, "Paradox Pokémon", PokemonType.FIRE, PokemonType.DRAGON, 3.5, 590, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 590, 105, 115, 121, 65, 93, 91, 10, 0, 295, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.RAGING_BOLT, 9, false, false, false, "Paradox Pokémon", PokemonType.ELECTRIC, PokemonType.DRAGON, 5.2, 480, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 590, 125, 73, 91, 137, 89, 75, 10, 0, 295, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.IRON_BOULDER, 9, false, false, false, "Paradox Pokémon", PokemonType.ROCK, PokemonType.PSYCHIC, 1.5, 162.5, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 590, 90, 120, 80, 68, 108, 124, 10, 0, 295, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.IRON_CROWN, 9, false, false, false, "Paradox Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 1.6, 156, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 590, 90, 72, 100, 122, 108, 98, 10, 0, 295, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.TERAPAGOS, 9, false, true, false, "Tera Pokémon", PokemonType.NORMAL, null, 0.2, 6.5, AbilityId.TERA_SHIFT, AbilityId.NONE, AbilityId.NONE, 450, 90, 65, 85, 65, 85, 60, 5, 50, 90, GrowthRate.SLOW, 50, false, false, + new PokemonForm("Normal Form", "", PokemonType.NORMAL, null, 0.2, 6.5, AbilityId.TERA_SHIFT, AbilityId.NONE, AbilityId.NONE, 450, 90, 65, 85, 65, 85, 60, 5, 50, 90, false, null, true), + new PokemonForm("Terastal Form", "terastal", PokemonType.NORMAL, null, 0.3, 16, AbilityId.TERA_SHELL, AbilityId.NONE, AbilityId.NONE, 600, 95, 95, 110, 105, 110, 85, 5, 50, 120), + new PokemonForm("Stellar Form", "stellar", PokemonType.NORMAL, null, 1.7, 77, AbilityId.TERAFORM_ZERO, AbilityId.NONE, AbilityId.NONE, 700, 160, 105, 110, 130, 110, 85, 5, 50, 140) + ), + new PokemonSpecies(SpeciesId.PECHARUNT, 9, false, false, true, "Subjugation Pokémon", PokemonType.POISON, PokemonType.GHOST, 0.3, 0.3, AbilityId.POISON_PUPPETEER, AbilityId.NONE, AbilityId.NONE, 600, 88, 88, 160, 88, 88, 88, 3, 0, 300, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.ALOLA_RATTATA, 7, false, false, false, "Mouse Pokémon", PokemonType.DARK, PokemonType.NORMAL, 0.3, 3.8, AbilityId.GLUTTONY, AbilityId.HUSTLE, AbilityId.THICK_FAT, 253, 30, 56, 35, 25, 35, 72, 255, 70, 51, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_RATICATE, 7, false, false, false, "Mouse Pokémon", PokemonType.DARK, PokemonType.NORMAL, 0.7, 25.5, AbilityId.GLUTTONY, AbilityId.HUSTLE, AbilityId.THICK_FAT, 413, 75, 71, 70, 40, 80, 77, 127, 70, 145, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_RAICHU, 7, false, false, false, "Mouse Pokémon", PokemonType.ELECTRIC, PokemonType.PSYCHIC, 0.7, 21, AbilityId.SURGE_SURFER, AbilityId.NONE, AbilityId.NONE, 485, 60, 85, 50, 95, 85, 110, 75, 50, 243, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_SANDSHREW, 7, false, false, false, "Mouse Pokémon", PokemonType.ICE, PokemonType.STEEL, 0.7, 40, AbilityId.SNOW_CLOAK, AbilityId.NONE, AbilityId.SLUSH_RUSH, 300, 50, 75, 90, 10, 35, 40, 255, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_SANDSLASH, 7, false, false, false, "Mouse Pokémon", PokemonType.ICE, PokemonType.STEEL, 1.2, 55, AbilityId.SNOW_CLOAK, AbilityId.NONE, AbilityId.SLUSH_RUSH, 450, 75, 100, 120, 25, 65, 65, 90, 50, 158, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_VULPIX, 7, false, false, false, "Fox Pokémon", PokemonType.ICE, null, 0.6, 9.9, AbilityId.SNOW_CLOAK, AbilityId.NONE, AbilityId.SNOW_WARNING, 299, 38, 41, 40, 50, 65, 65, 190, 50, 60, GrowthRate.MEDIUM_FAST, 25, false), + new PokemonSpecies(SpeciesId.ALOLA_NINETALES, 7, false, false, false, "Fox Pokémon", PokemonType.ICE, PokemonType.FAIRY, 1.1, 19.9, AbilityId.SNOW_CLOAK, AbilityId.NONE, AbilityId.SNOW_WARNING, 505, 73, 67, 75, 81, 100, 109, 75, 50, 177, GrowthRate.MEDIUM_FAST, 25, false), + new PokemonSpecies(SpeciesId.ALOLA_DIGLETT, 7, false, false, false, "Mole Pokémon", PokemonType.GROUND, PokemonType.STEEL, 0.2, 1, AbilityId.SAND_VEIL, AbilityId.TANGLING_HAIR, AbilityId.SAND_FORCE, 265, 10, 55, 30, 35, 45, 90, 255, 50, 53, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_DUGTRIO, 7, false, false, false, "Mole Pokémon", PokemonType.GROUND, PokemonType.STEEL, 0.7, 66.6, AbilityId.SAND_VEIL, AbilityId.TANGLING_HAIR, AbilityId.SAND_FORCE, 425, 35, 100, 60, 50, 70, 110, 50, 50, 149, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_MEOWTH, 7, false, false, false, "Scratch Cat Pokémon", PokemonType.DARK, null, 0.4, 4.2, AbilityId.PICKUP, AbilityId.TECHNICIAN, AbilityId.RATTLED, 290, 40, 35, 35, 50, 40, 90, 255, 50, 58, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_PERSIAN, 7, false, false, false, "Classy Cat Pokémon", PokemonType.DARK, null, 1.1, 33, AbilityId.FUR_COAT, AbilityId.TECHNICIAN, AbilityId.RATTLED, 440, 65, 60, 60, 75, 65, 115, 90, 50, 154, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_GEODUDE, 7, false, false, false, "Rock Pokémon", PokemonType.ROCK, PokemonType.ELECTRIC, 0.4, 20.3, AbilityId.MAGNET_PULL, AbilityId.STURDY, AbilityId.GALVANIZE, 300, 40, 80, 100, 30, 30, 20, 255, 70, 60, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_GRAVELER, 7, false, false, false, "Rock Pokémon", PokemonType.ROCK, PokemonType.ELECTRIC, 1, 110, AbilityId.MAGNET_PULL, AbilityId.STURDY, AbilityId.GALVANIZE, 390, 55, 95, 115, 45, 45, 35, 120, 70, 137, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_GOLEM, 7, false, false, false, "Megaton Pokémon", PokemonType.ROCK, PokemonType.ELECTRIC, 1.7, 316, AbilityId.MAGNET_PULL, AbilityId.STURDY, AbilityId.GALVANIZE, 495, 80, 120, 130, 55, 65, 45, 45, 70, 223, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_GRIMER, 7, false, false, false, "Sludge Pokémon", PokemonType.POISON, PokemonType.DARK, 0.7, 42, AbilityId.POISON_TOUCH, AbilityId.GLUTTONY, AbilityId.POWER_OF_ALCHEMY, 325, 80, 80, 50, 40, 50, 25, 190, 70, 65, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_MUK, 7, false, false, false, "Sludge Pokémon", PokemonType.POISON, PokemonType.DARK, 1, 52, AbilityId.POISON_TOUCH, AbilityId.GLUTTONY, AbilityId.POWER_OF_ALCHEMY, 500, 105, 105, 75, 65, 100, 50, 75, 70, 175, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_EXEGGUTOR, 7, false, false, false, "Coconut Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 10.9, 415.6, AbilityId.FRISK, AbilityId.NONE, AbilityId.HARVEST, 530, 95, 105, 85, 125, 75, 45, 45, 50, 186, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.ALOLA_MAROWAK, 7, false, false, false, "Bone Keeper Pokémon", PokemonType.FIRE, PokemonType.GHOST, 1, 34, AbilityId.CURSED_BODY, AbilityId.LIGHTNING_ROD, AbilityId.ROCK_HEAD, 425, 60, 80, 110, 50, 80, 45, 75, 50, 149, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.ETERNAL_FLOETTE, 6, true, false, false, "Single Bloom Pokémon", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 551, 74, 65, 67, 125, 128, 92, 120, 70, 243, GrowthRate.MEDIUM_FAST, 0, false), //Marked as Sub-Legend, for casing purposes + new PokemonSpecies(SpeciesId.GALAR_MEOWTH, 8, false, false, false, "Scratch Cat Pokémon", PokemonType.STEEL, null, 0.4, 7.5, AbilityId.PICKUP, AbilityId.TOUGH_CLAWS, AbilityId.UNNERVE, 290, 50, 65, 55, 40, 40, 40, 255, 50, 58, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GALAR_PONYTA, 8, false, false, false, "Fire Horse Pokémon", PokemonType.PSYCHIC, null, 0.8, 24, AbilityId.RUN_AWAY, AbilityId.PASTEL_VEIL, AbilityId.ANTICIPATION, 410, 50, 85, 55, 65, 65, 90, 190, 50, 82, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GALAR_RAPIDASH, 8, false, false, false, "Fire Horse Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.7, 80, AbilityId.RUN_AWAY, AbilityId.PASTEL_VEIL, AbilityId.ANTICIPATION, 500, 65, 100, 70, 80, 80, 105, 60, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GALAR_SLOWPOKE, 8, false, false, false, "Dopey Pokémon", PokemonType.PSYCHIC, null, 1.2, 36, AbilityId.GLUTTONY, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 315, 90, 65, 65, 40, 40, 15, 190, 50, 63, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GALAR_SLOWBRO, 8, false, false, false, "Hermit Crab Pokémon", PokemonType.POISON, PokemonType.PSYCHIC, 1.6, 70.5, AbilityId.QUICK_DRAW, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 490, 95, 100, 95, 100, 70, 30, 75, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GALAR_FARFETCHD, 8, false, false, false, "Wild Duck Pokémon", PokemonType.FIGHTING, null, 0.8, 42, AbilityId.STEADFAST, AbilityId.NONE, AbilityId.SCRAPPY, 377, 52, 95, 55, 58, 62, 55, 45, 50, 132, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GALAR_WEEZING, 8, false, false, false, "Poison Gas Pokémon", PokemonType.POISON, PokemonType.FAIRY, 3, 16, AbilityId.LEVITATE, AbilityId.NEUTRALIZING_GAS, AbilityId.MISTY_SURGE, 490, 65, 90, 120, 85, 70, 60, 60, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GALAR_MR_MIME, 8, false, false, false, "Barrier Pokémon", PokemonType.ICE, PokemonType.PSYCHIC, 1.4, 56.8, AbilityId.VITAL_SPIRIT, AbilityId.SCREEN_CLEANER, AbilityId.ICE_BODY, 460, 50, 65, 65, 90, 90, 100, 45, 50, 161, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GALAR_ARTICUNO, 8, true, false, false, "Freeze Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 1.7, 50.9, AbilityId.COMPETITIVE, AbilityId.NONE, AbilityId.NONE, 580, 90, 85, 85, 125, 100, 95, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.GALAR_ZAPDOS, 8, true, false, false, "Electric Pokémon", PokemonType.FIGHTING, PokemonType.FLYING, 1.6, 58.2, AbilityId.DEFIANT, AbilityId.NONE, AbilityId.NONE, 580, 90, 125, 90, 85, 90, 100, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.GALAR_MOLTRES, 8, true, false, false, "Flame Pokémon", PokemonType.DARK, PokemonType.FLYING, 2, 66, AbilityId.BERSERK, AbilityId.NONE, AbilityId.NONE, 580, 90, 85, 90, 100, 125, 90, 3, 35, 290, GrowthRate.SLOW, null, false), + new PokemonSpecies(SpeciesId.GALAR_SLOWKING, 8, false, false, false, "Royal Pokémon", PokemonType.POISON, PokemonType.PSYCHIC, 1.8, 79.5, AbilityId.CURIOUS_MEDICINE, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 490, 95, 65, 80, 110, 110, 30, 70, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GALAR_CORSOLA, 8, false, false, false, "Coral Pokémon", PokemonType.GHOST, null, 0.6, 0.5, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 410, 60, 55, 100, 65, 100, 30, 60, 50, 144, GrowthRate.FAST, 25, false), + new PokemonSpecies(SpeciesId.GALAR_ZIGZAGOON, 8, false, false, false, "Tiny Raccoon Pokémon", PokemonType.DARK, PokemonType.NORMAL, 0.4, 17.5, AbilityId.PICKUP, AbilityId.GLUTTONY, AbilityId.QUICK_FEET, 240, 38, 30, 41, 30, 41, 60, 255, 50, 56, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GALAR_LINOONE, 8, false, false, false, "Rushing Pokémon", PokemonType.DARK, PokemonType.NORMAL, 0.5, 32.5, AbilityId.PICKUP, AbilityId.GLUTTONY, AbilityId.QUICK_FEET, 420, 78, 70, 61, 50, 61, 100, 90, 50, 147, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GALAR_DARUMAKA, 8, false, false, false, "Zen Charm Pokémon", PokemonType.ICE, null, 0.7, 40, AbilityId.HUSTLE, AbilityId.NONE, AbilityId.INNER_FOCUS, 315, 70, 90, 45, 15, 45, 50, 120, 50, 63, GrowthRate.MEDIUM_SLOW, 50, false), + new PokemonSpecies(SpeciesId.GALAR_DARMANITAN, 8, false, false, false, "Blazing Pokémon", PokemonType.ICE, null, 1.7, 120, AbilityId.GORILLA_TACTICS, AbilityId.NONE, AbilityId.ZEN_MODE, 480, 105, 140, 55, 30, 55, 95, 60, 50, 168, GrowthRate.MEDIUM_SLOW, 50, false, true, + new PokemonForm("Standard Mode", "", PokemonType.ICE, null, 1.7, 120, AbilityId.GORILLA_TACTICS, AbilityId.NONE, AbilityId.ZEN_MODE, 480, 105, 140, 55, 30, 55, 95, 60, 50, 168, false, null, true), + new PokemonForm("Zen Mode", "zen", PokemonType.ICE, PokemonType.FIRE, 1.7, 120, AbilityId.GORILLA_TACTICS, AbilityId.NONE, AbilityId.ZEN_MODE, 540, 105, 160, 55, 30, 55, 135, 60, 50, 189) + ), + new PokemonSpecies(SpeciesId.GALAR_YAMASK, 8, false, false, false, "Spirit Pokémon", PokemonType.GROUND, PokemonType.GHOST, 0.5, 1.5, AbilityId.WANDERING_SPIRIT, AbilityId.NONE, AbilityId.NONE, 303, 38, 55, 85, 30, 65, 30, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.GALAR_STUNFISK, 8, false, false, false, "Trap Pokémon", PokemonType.GROUND, PokemonType.STEEL, 0.7, 20.5, AbilityId.MIMICRY, AbilityId.NONE, AbilityId.NONE, 471, 109, 81, 99, 66, 84, 32, 75, 70, 165, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.HISUI_GROWLITHE, 8, false, false, false, "Puppy Pokémon", PokemonType.FIRE, PokemonType.ROCK, 0.8, 22.7, AbilityId.INTIMIDATE, AbilityId.FLASH_FIRE, AbilityId.ROCK_HEAD, 350, 60, 75, 45, 65, 50, 55, 190, 50, 70, GrowthRate.SLOW, 75, false), + new PokemonSpecies(SpeciesId.HISUI_ARCANINE, 8, false, false, false, "Legendary Pokémon", PokemonType.FIRE, PokemonType.ROCK, 2, 168, AbilityId.INTIMIDATE, AbilityId.FLASH_FIRE, AbilityId.ROCK_HEAD, 555, 95, 115, 80, 95, 80, 90, 85, 50, 194, GrowthRate.SLOW, 75, false), + new PokemonSpecies(SpeciesId.HISUI_VOLTORB, 8, false, false, false, "Ball Pokémon", PokemonType.ELECTRIC, PokemonType.GRASS, 0.5, 13, AbilityId.SOUNDPROOF, AbilityId.STATIC, AbilityId.AFTERMATH, 330, 40, 30, 50, 55, 55, 100, 190, 80, 66, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.HISUI_ELECTRODE, 8, false, false, false, "Ball Pokémon", PokemonType.ELECTRIC, PokemonType.GRASS, 1.2, 81, AbilityId.SOUNDPROOF, AbilityId.STATIC, AbilityId.AFTERMATH, 490, 60, 50, 70, 80, 80, 150, 60, 70, 172, GrowthRate.MEDIUM_FAST, null, false), + new PokemonSpecies(SpeciesId.HISUI_TYPHLOSION, 8, false, false, false, "Volcano Pokémon", PokemonType.FIRE, PokemonType.GHOST, 1.6, 69.8, AbilityId.BLAZE, AbilityId.NONE, AbilityId.FRISK, 534, 73, 84, 78, 119, 85, 95, 45, 70, 240, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.HISUI_QWILFISH, 8, false, false, false, "Balloon Pokémon", PokemonType.DARK, PokemonType.POISON, 0.5, 3.9, AbilityId.POISON_POINT, AbilityId.SWIFT_SWIM, AbilityId.INTIMIDATE, 440, 65, 95, 85, 55, 55, 85, 45, 50, 88, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.HISUI_SNEASEL, 8, false, false, false, "Sharp Claw Pokémon", PokemonType.FIGHTING, PokemonType.POISON, 0.9, 27, AbilityId.INNER_FOCUS, AbilityId.KEEN_EYE, AbilityId.PICKPOCKET, 430, 55, 95, 55, 35, 75, 115, 60, 35, 86, GrowthRate.MEDIUM_SLOW, 50, true), + new PokemonSpecies(SpeciesId.HISUI_SAMUROTT, 8, false, false, false, "Formidable Pokémon", PokemonType.WATER, PokemonType.DARK, 1.5, 58.2, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHARPNESS, 528, 90, 108, 80, 100, 65, 85, 45, 80, 238, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.HISUI_LILLIGANT, 8, false, false, false, "Flowering Pokémon", PokemonType.GRASS, PokemonType.FIGHTING, 1.2, 19.2, AbilityId.CHLOROPHYLL, AbilityId.HUSTLE, AbilityId.LEAF_GUARD, 480, 70, 105, 75, 50, 75, 105, 75, 50, 168, GrowthRate.MEDIUM_FAST, 0, false), + new PokemonSpecies(SpeciesId.HISUI_ZORUA, 8, false, false, false, "Tricky Fox Pokémon", PokemonType.NORMAL, PokemonType.GHOST, 0.7, 12.5, AbilityId.ILLUSION, AbilityId.NONE, AbilityId.NONE, 330, 35, 60, 40, 85, 40, 70, 75, 50, 66, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.HISUI_ZOROARK, 8, false, false, false, "Illusion Fox Pokémon", PokemonType.NORMAL, PokemonType.GHOST, 1.6, 83, AbilityId.ILLUSION, AbilityId.NONE, AbilityId.NONE, 510, 55, 100, 60, 125, 60, 110, 45, 50, 179, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.HISUI_BRAVIARY, 8, false, false, false, "Valiant Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 1.7, 43.4, AbilityId.KEEN_EYE, AbilityId.SHEER_FORCE, AbilityId.TINTED_LENS, 510, 110, 83, 70, 112, 70, 65, 60, 50, 179, GrowthRate.SLOW, 100, false), + new PokemonSpecies(SpeciesId.HISUI_SLIGGOO, 8, false, false, false, "Soft Tissue Pokémon", PokemonType.STEEL, PokemonType.DRAGON, 0.7, 68.5, AbilityId.SAP_SIPPER, AbilityId.SHELL_ARMOR, AbilityId.GOOEY, 452, 58, 75, 83, 83, 113, 40, 45, 35, 158, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.HISUI_GOODRA, 8, false, false, false, "Dragon Pokémon", PokemonType.STEEL, PokemonType.DRAGON, 1.7, 334.1, AbilityId.SAP_SIPPER, AbilityId.SHELL_ARMOR, AbilityId.GOOEY, 600, 80, 100, 100, 110, 150, 60, 45, 35, 270, GrowthRate.SLOW, 50, false), + new PokemonSpecies(SpeciesId.HISUI_AVALUGG, 8, false, false, false, "Iceberg Pokémon", PokemonType.ICE, PokemonType.ROCK, 1.4, 262.4, AbilityId.STRONG_JAW, AbilityId.ICE_BODY, AbilityId.STURDY, 514, 95, 127, 184, 34, 36, 38, 55, 50, 180, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.HISUI_DECIDUEYE, 8, false, false, false, "Arrow Quill Pokémon", PokemonType.GRASS, PokemonType.FIGHTING, 1.6, 37, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.SCRAPPY, 530, 88, 112, 80, 95, 95, 60, 45, 50, 239, GrowthRate.MEDIUM_SLOW, 87.5, false), + new PokemonSpecies(SpeciesId.PALDEA_TAUROS, 9, false, false, false, "Wild Bull Pokémon", PokemonType.FIGHTING, null, 1.4, 115, AbilityId.INTIMIDATE, AbilityId.ANGER_POINT, AbilityId.CUD_CHEW, 490, 75, 110, 105, 30, 70, 100, 45, 50, 172, GrowthRate.SLOW, 100, false, false, + new PokemonForm("Combat Breed", "combat", PokemonType.FIGHTING, null, 1.4, 115, AbilityId.INTIMIDATE, AbilityId.ANGER_POINT, AbilityId.CUD_CHEW, 490, 75, 110, 105, 30, 70, 100, 45, 50, 172, false, "", true), + new PokemonForm("Blaze Breed", "blaze", PokemonType.FIGHTING, PokemonType.FIRE, 1.4, 85, AbilityId.INTIMIDATE, AbilityId.ANGER_POINT, AbilityId.CUD_CHEW, 490, 75, 110, 105, 30, 70, 100, 45, 50, 172, false, null, true), + new PokemonForm("Aqua Breed", "aqua", PokemonType.FIGHTING, PokemonType.WATER, 1.4, 110, AbilityId.INTIMIDATE, AbilityId.ANGER_POINT, AbilityId.CUD_CHEW, 490, 75, 110, 105, 30, 70, 100, 45, 50, 172, false, null, true) + ), + new PokemonSpecies(SpeciesId.PALDEA_WOOPER, 9, false, false, false, "Water Fish Pokémon", PokemonType.POISON, PokemonType.GROUND, 0.4, 11, AbilityId.POISON_POINT, AbilityId.WATER_ABSORB, AbilityId.UNAWARE, 210, 55, 45, 45, 25, 25, 15, 255, 50, 42, GrowthRate.MEDIUM_FAST, 50, false), + new PokemonSpecies(SpeciesId.BLOODMOON_URSALUNA, 9, true, false, false, "Peat Pokémon", PokemonType.GROUND, PokemonType.NORMAL, 2.7, 333, AbilityId.MINDS_EYE, AbilityId.NONE, AbilityId.NONE, 555, 113, 70, 120, 135, 65, 52, 75, 50, 278, GrowthRate.MEDIUM_FAST, 50, false) + ); +} diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 0dc8bf4850c..55a3cc4e916 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -7,8 +7,9 @@ import { AnimBlendType, AnimFocus, AnimFrameTarget, ChargeAnim, CommonAnim } fro import { MoveFlags } from "#enums/move-flags"; import { MoveId } from "#enums/move-id"; import type { Pokemon } from "#field/pokemon"; -import { animationFileName, coerceArray, getFrameMs, isNullOrUndefined, type nil } from "#utils/common"; +import { coerceArray, getFrameMs, isNullOrUndefined, type nil } from "#utils/common"; import { getEnumKeys, getEnumValues } from "#utils/enums"; +import { toKebabCase } from "#utils/strings"; import Phaser from "phaser"; export class AnimConfig { @@ -412,7 +413,7 @@ export function initCommonAnims(): Promise { const commonAnimId = commonAnimIds[ca]; commonAnimFetches.push( globalScene - .cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/_/g, "-")}.json`) + .cachedFetch(`./battle-anims/common-${toKebabCase(commonAnimNames[ca])}.json`) .then(response => response.json()) .then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas))), ); @@ -450,7 +451,7 @@ export function initMoveAnim(move: MoveId): Promise { const fetchAnimAndResolve = (move: MoveId) => { globalScene - .cachedFetch(`./battle-anims/${animationFileName(move)}.json`) + .cachedFetch(`./battle-anims/${toKebabCase(MoveId[move])}.json`) .then(response => { const contentType = response.headers.get("content-type"); if (!response.ok || contentType?.indexOf("application/json") === -1) { @@ -506,7 +507,7 @@ function useDefaultAnim(move: MoveId, defaultMoveAnim: MoveId) { * @remarks use {@linkcode useDefaultAnim} to use a default animation */ function logMissingMoveAnim(move: MoveId, ...optionalParams: any[]) { - const moveName = animationFileName(move); + const moveName = toKebabCase(MoveId[move]); console.warn(`Could not load animation file for move '${moveName}'`, ...optionalParams); } @@ -524,7 +525,7 @@ export async function initEncounterAnims(encounterAnim: EncounterAnim | Encounte } encounterAnimFetches.push( globalScene - .cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/_/g, "-")}.json`) + .cachedFetch(`./battle-anims/encounter-${toKebabCase(encounterAnimNames[anim])}.json`) .then(response => response.json()) .then(cas => encounterAnims.set(anim, new AnimConfig(cas))), ); @@ -548,7 +549,7 @@ export function initMoveChargeAnim(chargeAnim: ChargeAnim): Promise { } else { chargeAnims.set(chargeAnim, null); globalScene - .cachedFetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/_/g, "-")}.json`) + .cachedFetch(`./battle-anims/${toKebabCase(ChargeAnim[chargeAnim])}.json`) .then(response => response.json()) .then(ca => { if (Array.isArray(ca)) { @@ -1405,7 +1406,9 @@ export async function populateAnims() { const chargeAnimIds = getEnumValues(ChargeAnim); const commonNamePattern = /name: (?:Common:)?(Opp )?(.*)/; const moveNameToId = {}; + // Exclude MoveId.NONE; for (const move of getEnumValues(MoveId).slice(1)) { + // KARATE_CHOP => KARATECHOP const moveName = MoveId[move].toUpperCase().replace(/_/g, ""); moveNameToId[moveName] = move; } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index c8ddfe32f0b..2f540f5e1b9 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -8,6 +8,7 @@ import { SpeciesFormChangeAbilityTrigger } from "#data/form-change-triggers"; import { getStatusEffectHealText } from "#data/status-effect"; import { TerrainType } from "#data/terrain"; import { AbilityId } from "#enums/ability-id"; +import type { BattlerIndex } from "#enums/battler-index"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { HitResult } from "#enums/hit-result"; @@ -31,41 +32,126 @@ import type { MoveEffectPhase } from "#phases/move-effect-phase"; import type { MovePhase } from "#phases/move-phase"; import type { StatStageChangeCallback } from "#phases/stat-stage-change-phase"; import i18next from "#plugins/i18n"; +import type { + AbilityBattlerTagType, + BattlerTagTypeData, + ContactSetStatusProtectedTagType, + ContactStatStageChangeProtectedTagType, + CritStageBoostTagType, + DamageProtectedTagType, + EndureTagType, + HighestStatBoostTagType, + MoveRestrictionBattlerTagType, + ProtectionBattlerTagType, + RemovedTypeTagType, + SemiInvulnerableTagType, + TrappingBattlerTagType, + TypeBoostTagType, +} from "#types/battler-tags"; +import type { Mutable } from "#types/type-helpers"; import { BooleanHolder, coerceArray, getFrameMs, isNullOrUndefined, NumberHolder, toDmgValue } from "#utils/common"; +/** + * @module + * BattlerTags are used to represent semi-persistent effects that can be attached to a Pokemon. + * Note that before serialization, a new tag object is created, and then `loadTag` is called on the + * tag with the object that was serialized. + * + * This means it is straightforward to avoid serializing fields. + * Fields that are not set in the constructor and not set in `loadTag` will thus not be serialized. + * + * Any battler tag that can persist across sessions must extend SerializableBattlerTag in its class definition signature. + * Only tags that persist across waves (meaning their effect can last >1 turn) should be considered + * serializable. + * + * Serializable battler tags have strict requirements for their fields. + * Properties that are not necessary to reconstruct the tag must not be serialized. This can be avoided + * by using a private property. If access to the property is needed outside of the class, then + * a getter (and potentially, a setter) should be used instead. + * + * If a property that is intended to be private must be serialized, then it should instead + * be declared as a public readonly propety. Then, in the `loadTag` method (or any method inside the class that needs to adjust the property) + * use `(this as Mutable).propertyName = value;` + * These rules ensure that Typescript is aware of the shape of the serialized version of the class. + * + * If any new serializable fields *are* added, then the class *must* override the + * `loadTag` method to set the new fields. Its signature *must* match the example below: + * ``` + * class ExampleTag extends SerializableBattlerTag { + * // Example, if we add 2 new fields that should be serialized: + * public a: string; + * public b: number; + * // Then we must also define a loadTag method with one of the following signatures + * public override loadTag(source: BaseBattlerTag & Pick(source: BaseBattlerTag & Pick): void; + * } + * ``` + * Notes + * - If the class has any subclasses, then the second form of `loadTag` *must* be used. + */ + +/** Interface containing the serializable fields of BattlerTag */ +interface BaseBattlerTag { + /** The tag's remaining duration */ + turnCount: number; + /** The {@linkcode MoveId} that created this tag, or `undefined` if not set by a move */ + sourceMove?: MoveId; + /** The {@linkcode Pokemon.id | PID} of the Pokemon that added this tag, or `undefined` if not set by a pokemon */ + sourceId?: number; +} + /** * A {@linkcode BattlerTag} represents a semi-persistent effect that can be attached to a {@linkcode Pokemon}. * Tags can trigger various effects throughout a turn, and are cleared on switching out * or through their respective {@linkcode BattlerTag.lapse | lapse} methods. */ -export class BattlerTag { - public tagType: BattlerTagType; - public lapseTypes: BattlerTagLapseType[]; +export class BattlerTag implements BaseBattlerTag { + public readonly tagType: BattlerTagType; + public turnCount: number; - public sourceMove: MoveId; + public sourceMove?: MoveId; public sourceId?: number; - public isBatonPassable: boolean; + + //#region non-serializable fields + // Fields that should never be serialized, as they must not change after instantiation + #isBatonPassable = false; + public get isBatonPassable(): boolean { + return this.#isBatonPassable; + } + + #lapseTypes: readonly [BattlerTagLapseType, ...BattlerTagLapseType[]]; + public get lapseTypes(): readonly BattlerTagLapseType[] { + return this.#lapseTypes; + } + //#endregion non-serializable fields constructor( tagType: BattlerTagType, - lapseType: BattlerTagLapseType | BattlerTagLapseType[], + lapseType: BattlerTagLapseType | [BattlerTagLapseType, ...BattlerTagLapseType[]], turnCount: number, sourceMove?: MoveId, sourceId?: number, isBatonPassable = false, ) { this.tagType = tagType; - this.lapseTypes = coerceArray(lapseType); + this.#lapseTypes = coerceArray(lapseType); this.turnCount = turnCount; - this.sourceMove = sourceMove!; // TODO: is this bang correct? + // We intentionally don't want to set source move to `MoveId.NONE` here, so a raw boolean comparison is OK. + if (sourceMove) { + this.sourceMove = sourceMove; + } this.sourceId = sourceId; - this.isBatonPassable = isBatonPassable; + this.#isBatonPassable = isBatonPassable; } canAdd(_pokemon: Pokemon): boolean { return true; } + /** + * Apply effects that occur when the tag is added to a {@linkcode Pokemon} + * @param _pokemon - The {@linkcode Pokemon} the tag was added to + */ onAdd(_pokemon: Pokemon): void {} onRemove(_pokemon: Pokemon): void {} @@ -99,9 +185,9 @@ export class BattlerTag { /** * Load the data for a given {@linkcode BattlerTag} or JSON representation thereof. * Should be inherited from by any battler tag with custom attributes. - * @param source The battler tag to load + * @param source - An object containing the fields needed to reconstruct this tag. */ - loadTag(source: BattlerTag | any): void { + public loadTag(source: BaseBattlerTag & Pick): void { this.turnCount = source.turnCount; this.sourceMove = source.sourceMove; this.sourceId = source.sourceId; @@ -116,12 +202,22 @@ export class BattlerTag { } } -export interface WeatherBattlerTag { - weatherTypes: WeatherType[]; +export class SerializableBattlerTag extends BattlerTag { + /** Nonexistent, dummy field to allow typescript to distinguish this class from `BattlerTag` */ + private declare __SerializableBattlerTag: never; } -export interface TerrainBattlerTag { - terrainTypes: TerrainType[]; +/** + * Interface for a generic serializable battler tag, i.e. one that does not have a + * dedicated subclass. + * + * @remarks + * Used to ensure type safety when serializing battler tags, + * allowing typescript to properly infer the type of the tag. + * @see BattlerTagTypeMap + */ +interface GenericSerializableBattlerTag extends SerializableBattlerTag { + tagType: T; } /** @@ -132,8 +228,8 @@ export interface TerrainBattlerTag { * match a condition. A restricted move gets cancelled before it is used. * Players and enemies should not be allowed to select restricted moves. */ -export abstract class MoveRestrictionBattlerTag extends BattlerTag { - /** @override */ +export abstract class MoveRestrictionBattlerTag extends SerializableBattlerTag { + public declare readonly tagType: MoveRestrictionBattlerTagType; override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { // Cancel the affected pokemon's selected move @@ -154,32 +250,32 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag { } /** - * Gets whether this tag is restricting a move. + * Determine whether a move's usage is restricted by this tag * - * @param move - {@linkcode MoveId} ID to check restriction for. + * @param move - The {@linkcode MoveId} being checked * @param user - The {@linkcode Pokemon} involved * @returns `true` if the move is restricted by this tag, otherwise `false`. */ public abstract isMoveRestricted(move: MoveId, user?: Pokemon): boolean; /** - * Checks if this tag is restricting a move based on a user's decisions during the target selection phase + * Check if this tag is restricting a move based on a user's decisions during the target selection phase * - * @param {MoveId} _move {@linkcode MoveId} move ID to check restriction for - * @param {Pokemon} _user {@linkcode Pokemon} the user of the above move - * @param {Pokemon} _target {@linkcode Pokemon} the target of the above move - * @returns {boolean} `false` unless overridden by the child tag + * @param _move - {@linkcode MoveId} to check restriction for + * @param _user - The user of the move + * @param _target - The pokemon targeted by the move + * @returns Whether the move is restricted by this tag */ isMoveTargetRestricted(_move: MoveId, _user: Pokemon, _target: Pokemon): boolean { return false; } /** - * Gets the text to display when the player attempts to select a move that is restricted by this tag. + * Get the text to display when the player attempts to select a move that is restricted by this tag. * - * @param {Pokemon} pokemon {@linkcode Pokemon} for which the player is attempting to select the restricted move - * @param {MoveId} move {@linkcode MoveId} ID of the move that is having its selection denied - * @returns {string} text to display when the player attempts to select the restricted move + * @param pokemon - The pokemon for which the player is attempting to select the restricted move + * @param move - The {@linkcode MoveId | ID} of the Move that is having its selection denied + * @returns The text to display when the player attempts to select the restricted move */ abstract selectionDeniedText(pokemon: Pokemon, move: MoveId): string; @@ -188,9 +284,9 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag { * Because restriction effects also prevent selection of the move, this situation can only arise if a * pokemon first selects a move, then gets outsped by a pokemon using a move that restricts the selected move. * - * @param {Pokemon} _pokemon {@linkcode Pokemon} attempting to use the restricted move - * @param {MoveId} _move {@linkcode MoveId} ID of the move being interrupted - * @returns {string} text to display when the move is interrupted + * @param _pokemon - The pokemon attempting to use the restricted move + * @param _move - The {@linkcode MoveId | ID} of the move being interrupted + * @returns The text to display when the move is interrupted */ interruptedText(_pokemon: Pokemon, _move: MoveId): string { return ""; @@ -200,9 +296,10 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag { /** * Tag representing the "Throat Chop" effect. Pokemon with this tag cannot use sound-based moves. * @see {@link https://bulbapedia.bulbagarden.net/wiki/Throat_Chop_(move) | Throat Chop} - * @extends MoveRestrictionBattlerTag + * @sealed */ export class ThroatChoppedTag extends MoveRestrictionBattlerTag { + public override readonly tagType = BattlerTagType.THROAT_CHOPPED; constructor() { super( BattlerTagType.THROAT_CHOPPED, @@ -213,10 +310,9 @@ export class ThroatChoppedTag extends MoveRestrictionBattlerTag { } /** - * Checks if a {@linkcode MoveId | move} is restricted by Throat Chop. - * @override - * @param {MoveId} move the {@linkcode MoveId | move} to check for sound-based restriction - * @returns true if the move is sound-based + * Check if a move is restricted by Throat Chop. + * @param move - The {@linkcode MoveId | ID } of the move to check for sound-based restriction + * @returns Whether the move is sound based */ override isMoveRestricted(move: MoveId): boolean { return allMoves[move].hasFlag(MoveFlags.SOUND_BASED); @@ -224,10 +320,9 @@ export class ThroatChoppedTag extends MoveRestrictionBattlerTag { /** * Shows a message when the player attempts to select a move that is restricted by Throat Chop. - * @override - * @param {Pokemon} _pokemon the {@linkcode Pokemon} that is attempting to select the restricted move - * @param {MoveId} move the {@linkcode MoveId | move} that is being restricted - * @returns the message to display when the player attempts to select the restricted move + * @param _pokemon - The {@linkcode Pokemon} that is attempting to select the restricted move + * @param move - The {@linkcode MoveId | move} that is being restricted + * @returns The message to display when the player attempts to select the restricted move */ override selectionDeniedText(_pokemon: Pokemon, move: MoveId): string { return i18next.t("battle:moveCannotBeSelected", { @@ -237,10 +332,9 @@ export class ThroatChoppedTag extends MoveRestrictionBattlerTag { /** * Shows a message when a move is interrupted by Throat Chop. - * @override - * @param {Pokemon} pokemon the interrupted {@linkcode Pokemon} - * @param {MoveId} _move the {@linkcode MoveId | move} that was interrupted - * @returns the message to display when the move is interrupted + * @param pokemon - The interrupted {@linkcode Pokemon} + * @param _move - The {@linkcode MoveId | ID } of the move that was interrupted + * @returns The message to display when the move is interrupted */ override interruptedText(pokemon: Pokemon, _move: MoveId): string { return i18next.t("battle:throatChopInterruptedMove", { @@ -252,10 +346,13 @@ export class ThroatChoppedTag extends MoveRestrictionBattlerTag { /** * Tag representing the "disabling" effect performed by {@linkcode MoveId.DISABLE} and {@linkcode AbilityId.CURSED_BODY}. * When the tag is added, the last-used move of the tag holder is set as the disabled move. + * + * @sealed */ export class DisabledTag extends MoveRestrictionBattlerTag { + public override readonly tagType = BattlerTagType.DISABLED; /** The move being disabled. Gets set when {@linkcode onAdd} is called for this tag. */ - private moveId: MoveId = MoveId.NONE; + public readonly moveId: MoveId = MoveId.NONE; constructor(sourceId: number) { super( @@ -267,14 +364,11 @@ export class DisabledTag extends MoveRestrictionBattlerTag { ); } - /** @override */ override isMoveRestricted(move: MoveId): boolean { return move === this.moveId; } /** - * @override - * * Attempt to disable the target's last move by setting this tag's {@linkcode moveId} * and showing a message. */ @@ -287,7 +381,7 @@ export class DisabledTag extends MoveRestrictionBattlerTag { } super.onAdd(pokemon); - this.moveId = move.move; + (this as Mutable).moveId = move.move; globalScene.phaseManager.queueMessage( i18next.t("battlerTags:disabledOnAdd", { @@ -297,7 +391,6 @@ export class DisabledTag extends MoveRestrictionBattlerTag { ); } - /** @override */ override onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); @@ -309,16 +402,14 @@ export class DisabledTag extends MoveRestrictionBattlerTag { ); } - /** @override */ override selectionDeniedText(_pokemon: Pokemon, move: MoveId): string { return i18next.t("battle:moveDisabled", { moveName: allMoves[move].name }); } /** - * @override - * @param {Pokemon} pokemon {@linkcode Pokemon} attempting to use the restricted move - * @param {MoveId} move {@linkcode MoveId} ID of the move being interrupted - * @returns {string} text to display when the move is interrupted + * @param pokemon - {@linkcode Pokemon} attempting to use the restricted move + * @param move - {@linkcode MoveId | ID} of the move being interrupted + * @returns The text to display when the move is interrupted */ override interruptedText(pokemon: Pokemon, move: MoveId): string { return i18next.t("battle:disableInterruptedMove", { @@ -327,19 +418,21 @@ export class DisabledTag extends MoveRestrictionBattlerTag { }); } - /** @override */ - override loadTag(source: BattlerTag | any): void { + public override loadTag(source: BaseBattlerTag & Pick): void { super.loadTag(source); - this.moveId = source.moveId; + (this as Mutable).moveId = source.moveId; } } /** * Tag used by Gorilla Tactics to restrict the user to using only one move. + * + * @sealed */ export class GorillaTacticsTag extends MoveRestrictionBattlerTag { - private moveId = MoveId.NONE; - + public override readonly tagType = BattlerTagType.GORILLA_TACTICS; + /** ID of the move that the user is locked into using*/ + public readonly moveId: MoveId = MoveId.NONE; constructor() { super(BattlerTagType.GORILLA_TACTICS, BattlerTagLapseType.CUSTOM, 0); } @@ -367,18 +460,17 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag { super.onAdd(pokemon); // Bang is justified as tag is not added if prior move doesn't exist - this.moveId = pokemon.getLastNonVirtualMove()!.move; + (this as Mutable).moveId = pokemon.getLastNonVirtualMove()!.move; pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false); } /** * Loads the Gorilla Tactics Battler Tag along with its unique class variable moveId - * @override - * @param source Gorilla Tactics' {@linkcode BattlerTag} information + * @param source - Object containing the fields needed to reconstruct this tag. */ - public override loadTag(source: BattlerTag | any): void { + public override loadTag(source: BaseBattlerTag & Pick): void { super.loadTag(source); - this.moveId = source.moveId; + (this as Mutable).moveId = source.moveId; } /** @@ -397,7 +489,8 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag { /** * BattlerTag that represents the "recharge" effects of moves like Hyper Beam. */ -export class RechargingTag extends BattlerTag { +export class RechargingTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.RECHARGING; constructor(sourceMove: MoveId) { super(BattlerTagType.RECHARGING, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END], 2, sourceMove); } @@ -430,6 +523,8 @@ export class RechargingTag extends BattlerTag { * @see {@link https://bulbapedia.bulbagarden.net/wiki/Beak_Blast_(move) | Beak Blast} */ export class BeakBlastChargingTag extends BattlerTag { + public override readonly tagType = BattlerTagType.BEAK_BLAST_CHARGING; + public declare readonly sourceMove: MoveId.BEAK_BLAST; constructor() { super( BattlerTagType.BEAK_BLAST_CHARGING, @@ -454,8 +549,8 @@ export class BeakBlastChargingTag extends BattlerTag { /** * Inflicts `BURN` status on attackers that make contact, and causes this tag * to be removed after the source makes a move (or the turn ends, whichever comes first) - * @param pokemon {@linkcode Pokemon} the owner of this tag - * @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle + * @param pokemon - The owner of this tag + * @param lapseType - The type of functionality invoked in battle * @returns `true` if invoked with the `AFTER_HIT` lapse type */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -482,6 +577,7 @@ export class BeakBlastChargingTag extends BattlerTag { * @see {@link https://bulbapedia.bulbagarden.net/wiki/Shell_Trap_(move) | Shell Trap} */ export class ShellTrapTag extends BattlerTag { + public override readonly tagType = BattlerTagType.SHELL_TRAP; public activated = false; constructor() { @@ -498,8 +594,8 @@ export class ShellTrapTag extends BattlerTag { /** * "Activates" the shell trap, causing the tag owner to move next. - * @param pokemon {@linkcode Pokemon} the owner of this tag - * @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle + * @param pokemon - The owner of this tag + * @param lapseType - The type of functionality invoked in battle * @returns `true` if invoked with the `AFTER_HIT` lapse type */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -529,7 +625,8 @@ export class ShellTrapTag extends BattlerTag { } } -export class TrappedTag extends BattlerTag { +export class TrappedTag extends SerializableBattlerTag { + public declare readonly tagType: TrappingBattlerTagType; constructor( tagType: BattlerTagType, lapseType: BattlerTagLapseType, @@ -546,13 +643,13 @@ export class TrappedTag extends BattlerTag { console.warn(`Failed to get source Pokemon for TrappedTag canAdd; id: ${this.sourceId}`); return false; } - - const move = allMoves[this.sourceMove]; + if (this.sourceMove && allMoves[this.sourceMove]?.hitsSubstitute(source, pokemon)) { + return false; + } const isGhost = pokemon.isOfType(PokemonType.GHOST); const isTrapped = pokemon.getTag(TrappedTag); - const hasSubstitute = move.hitsSubstitute(source, pokemon); - return !isTrapped && !isGhost && !hasSubstitute; + return !isTrapped && !isGhost; } onAdd(pokemon: Pokemon): void { @@ -591,9 +688,9 @@ export class TrappedTag extends BattlerTag { * BattlerTag implementing No Retreat's trapping effect. * This is treated separately from other trapping effects to prevent * Ghost-type Pokemon from being able to reuse the move. - * @extends TrappedTag */ class NoRetreatTag extends TrappedTag { + public override readonly tagType = BattlerTagType.NO_RETREAT; constructor(sourceId: number) { super(BattlerTagType.NO_RETREAT, BattlerTagLapseType.CUSTOM, 0, MoveId.NO_RETREAT, sourceId); } @@ -608,6 +705,7 @@ class NoRetreatTag extends TrappedTag { * BattlerTag that represents the {@link https://bulbapedia.bulbagarden.net/wiki/Flinch Flinch} status condition */ export class FlinchedTag extends BattlerTag { + public override readonly tagType = BattlerTagType.FLINCHED; constructor(sourceMove: MoveId) { super(BattlerTagType.FLINCHED, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END], 1, sourceMove); } @@ -639,6 +737,7 @@ export class FlinchedTag extends BattlerTag { } export class InterruptedTag extends BattlerTag { + public override readonly tagType = BattlerTagType.INTERRUPTED; constructor(sourceMove: MoveId) { super(BattlerTagType.INTERRUPTED, BattlerTagLapseType.PRE_MOVE, 0, sourceMove); } @@ -668,7 +767,8 @@ export class InterruptedTag extends BattlerTag { /** * BattlerTag that represents the {@link https://bulbapedia.bulbagarden.net/wiki/Confusion_(status_condition) Confusion} status condition */ -export class ConfusedTag extends BattlerTag { +export class ConfusedTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.CONFUSED; constructor(turnCount: number, sourceMove: MoveId) { super(BattlerTagType.CONFUSED, BattlerTagLapseType.MOVE, turnCount, sourceMove, undefined, true); } @@ -752,10 +852,10 @@ export class ConfusedTag extends BattlerTag { /** * Tag applied to the {@linkcode Move.DESTINY_BOND} user. - * @extends BattlerTag * @see {@linkcode apply} */ -export class DestinyBondTag extends BattlerTag { +export class DestinyBondTag extends SerializableBattlerTag { + public readonly tagType = BattlerTagType.DESTINY_BOND; constructor(sourceMove: MoveId, sourceId: number) { super(BattlerTagType.DESTINY_BOND, BattlerTagLapseType.PRE_MOVE, 1, sourceMove, sourceId, true); } @@ -765,9 +865,9 @@ export class DestinyBondTag extends BattlerTag { * or after receiving fatal damage. When the damage is fatal, * the attacking Pokemon is taken down as well, unless it's a boss. * - * @param {Pokemon} pokemon Pokemon that is attacking the Destiny Bond user. - * @param {BattlerTagLapseType} lapseType CUSTOM or PRE_MOVE - * @returns false if the tag source fainted or one turn has passed since the application + * @param pokemon - The Pokemon that is attacking the Destiny Bond user. + * @param lapseType - CUSTOM or PRE_MOVE + * @returns `false` if the tag source fainted or one turn has passed since the application */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType !== BattlerTagLapseType.CUSTOM) { @@ -811,7 +911,9 @@ export class DestinyBondTag extends BattlerTag { } } -export class InfatuatedTag extends BattlerTag { +// Technically serializable as in a double battle, a pokemon could be infatuated by its ally +export class InfatuatedTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.INFATUATED; constructor(sourceMove: number, sourceId: number) { super(BattlerTagType.INFATUATED, BattlerTagLapseType.MOVE, 1, sourceMove, sourceId); } @@ -901,8 +1003,15 @@ export class InfatuatedTag extends BattlerTag { } } -export class SeedTag extends BattlerTag { - private sourceIndex: number; +/** + * Battler tag for the "Seeded" effect applied by {@linkcode MoveId.LEECH_SEED | Leech Seed} and + * {@linkcode MoveId.SAPPY_SEED | Sappy Seed} + * + * @sealed + */ +export class SeedTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.SEEDED; + public readonly sourceIndex: BattlerIndex; constructor(sourceId: number) { super(BattlerTagType.SEEDED, BattlerTagLapseType.TURN_END, 1, MoveId.LEECH_SEED, sourceId, true); @@ -910,11 +1019,11 @@ export class SeedTag extends BattlerTag { /** * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag + * @param source - An object containing the fields needed to reconstruct this tag. */ - loadTag(source: BattlerTag | any): void { + public override loadTag(source: BaseBattlerTag & Pick): void { super.loadTag(source); - this.sourceIndex = source.sourceIndex; + (this as Mutable).sourceIndex = source.sourceIndex; } canAdd(pokemon: Pokemon): boolean { @@ -935,7 +1044,7 @@ export class SeedTag extends BattlerTag { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - this.sourceIndex = source.getBattlerIndex(); + (this as Mutable).sourceIndex = source.getBattlerIndex(); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -990,9 +1099,10 @@ export class SeedTag extends BattlerTag { /** * BattlerTag representing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Powder_(move) | Powder}. * When the afflicted Pokemon uses a Fire-type move, the move is cancelled, and the - * Pokemon takes damage equal to 1/4 of it's maximum HP (rounded down). + * Pokemon takes damage equal to 1/4 of its maximum HP (rounded down). */ export class PowderTag extends BattlerTag { + public override readonly tagType = BattlerTagType.POWDER; constructor() { super(BattlerTagType.POWDER, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END], 1); } @@ -1051,7 +1161,8 @@ export class PowderTag extends BattlerTag { } } -export class NightmareTag extends BattlerTag { +export class NightmareTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.NIGHTMARE; constructor() { super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.TURN_END, 1, MoveId.NIGHTMARE); } @@ -1104,7 +1215,8 @@ export class NightmareTag extends BattlerTag { } } -export class FrenzyTag extends BattlerTag { +export class FrenzyTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.FRENZY; constructor(turnCount: number, sourceMove: MoveId, sourceId: number) { super(BattlerTagType.FRENZY, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); } @@ -1122,8 +1234,11 @@ export class FrenzyTag extends BattlerTag { /** * Applies the effects of {@linkcode MoveId.ENCORE} onto the target Pokemon. * Encore forces the target Pokemon to use its most-recent move for 3 turns. + * @sealed */ export class EncoreTag extends MoveRestrictionBattlerTag { + public override readonly tagType = BattlerTagType.ENCORE; + /** The ID of the move the user is locked into using */ public moveId: MoveId; constructor(sourceId: number) { @@ -1136,12 +1251,12 @@ export class EncoreTag extends MoveRestrictionBattlerTag { ); } - loadTag(source: BattlerTag | any): void { + public override loadTag(source: BaseBattlerTag & Pick): void { super.loadTag(source); - this.moveId = source.moveId as MoveId; + this.moveId = source.moveId; } - canAdd(pokemon: Pokemon): boolean { + override canAdd(pokemon: Pokemon): boolean { const lastMove = pokemon.getLastNonVirtualMove(); if (!lastMove) { return false; @@ -1156,10 +1271,7 @@ export class EncoreTag extends MoveRestrictionBattlerTag { return true; } - onAdd(pokemon: Pokemon): void { - // TODO: shouldn't this be `onAdd`? - super.onRemove(pokemon); - + override onAdd(pokemon: Pokemon): void { globalScene.phaseManager.queueMessage( i18next.t("battlerTags:encoreOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), @@ -1199,7 +1311,7 @@ export class EncoreTag extends MoveRestrictionBattlerTag { /** * Checks if the move matches the moveId stored within the tag and returns a boolean value - * @param move {@linkcode MoveId} the move selected + * @param move - The ID of the move selected * @param user N/A * @returns `true` if the move does not match with the moveId stored and as a result, restricted */ @@ -1223,6 +1335,7 @@ export class EncoreTag extends MoveRestrictionBattlerTag { } export class HelpingHandTag extends BattlerTag { + public override readonly tagType = BattlerTagType.HELPING_HAND; constructor(sourceId: number) { super(BattlerTagType.HELPING_HAND, BattlerTagLapseType.TURN_END, 1, MoveId.HELPING_HAND, sourceId); } @@ -1245,16 +1358,16 @@ export class HelpingHandTag extends BattlerTag { /** * Applies the Ingrain tag to a pokemon - * @extends TrappedTag */ export class IngrainTag extends TrappedTag { + public override readonly tagType = BattlerTagType.INGRAIN; constructor(sourceId: number) { super(BattlerTagType.INGRAIN, BattlerTagLapseType.TURN_END, 1, MoveId.INGRAIN, sourceId); } /** * Check if the Ingrain tag can be added to the pokemon - * @param pokemon {@linkcode Pokemon} The pokemon to check if the tag can be added to + * @param pokemon - The pokemon to check if the tag can be added to * @returns boolean True if the tag can be added, false otherwise */ canAdd(pokemon: Pokemon): boolean { @@ -1295,6 +1408,7 @@ export class IngrainTag extends TrappedTag { * end of each turn. */ export class OctolockTag extends TrappedTag { + public override readonly tagType = BattlerTagType.OCTOLOCK; constructor(sourceId: number) { super(BattlerTagType.OCTOLOCK, BattlerTagLapseType.TURN_END, 1, MoveId.OCTOLOCK, sourceId); } @@ -1317,7 +1431,8 @@ export class OctolockTag extends TrappedTag { } } -export class AquaRingTag extends BattlerTag { +export class AquaRingTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.AQUA_RING; constructor() { super(BattlerTagType.AQUA_RING, BattlerTagLapseType.TURN_END, 1, MoveId.AQUA_RING, undefined, true); } @@ -1353,7 +1468,8 @@ export class AquaRingTag extends BattlerTag { } /** Tag used to allow moves that interact with {@link MoveId.MINIMIZE} to function */ -export class MinimizeTag extends BattlerTag { +export class MinimizeTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.MINIMIZED; constructor() { super(BattlerTagType.MINIMIZED, BattlerTagLapseType.TURN_END, 1, MoveId.MINIMIZE); } @@ -1371,7 +1487,8 @@ export class MinimizeTag extends BattlerTag { } } -export class DrowsyTag extends BattlerTag { +export class DrowsyTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.DROWSY; constructor() { super(BattlerTagType.DROWSY, BattlerTagLapseType.TURN_END, 2, MoveId.YAWN); } @@ -1405,7 +1522,9 @@ export class DrowsyTag extends BattlerTag { } export abstract class DamagingTrapTag extends TrappedTag { - private commonAnim: CommonAnim; + public declare readonly tagType: TrappingBattlerTagType; + /** The animation to play during the damage sequence */ + #commonAnim: CommonAnim; constructor( tagType: BattlerTagType, @@ -1416,16 +1535,7 @@ export abstract class DamagingTrapTag extends TrappedTag { ) { super(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove, sourceId); - this.commonAnim = commonAnim; - } - - /** - * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag - */ - loadTag(source: BattlerTag | any): void { - super.loadTag(source); - this.commonAnim = source.commonAnim as CommonAnim; + this.#commonAnim = commonAnim; } canAdd(pokemon: Pokemon): boolean { @@ -1443,7 +1553,7 @@ export abstract class DamagingTrapTag extends TrappedTag { moveName: this.getMoveName(), }), ); - phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim); + phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.#commonAnim); const cancelled = new BooleanHolder(false); applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled }); @@ -1459,6 +1569,7 @@ export abstract class DamagingTrapTag extends TrappedTag { // TODO: Condense all these tags into 1 singular tag with a modified message func export class BindTag extends DamagingTrapTag { + public override readonly tagType = BattlerTagType.BIND; constructor(turnCount: number, sourceId: number) { super(BattlerTagType.BIND, CommonAnim.BIND, turnCount, MoveId.BIND, sourceId); } @@ -1479,6 +1590,7 @@ export class BindTag extends DamagingTrapTag { } export class WrapTag extends DamagingTrapTag { + public override readonly tagType = BattlerTagType.WRAP; constructor(turnCount: number, sourceId: number) { super(BattlerTagType.WRAP, CommonAnim.WRAP, turnCount, MoveId.WRAP, sourceId); } @@ -1507,18 +1619,21 @@ export abstract class VortexTrapTag extends DamagingTrapTag { } export class FireSpinTag extends VortexTrapTag { + public override readonly tagType = BattlerTagType.FIRE_SPIN; constructor(turnCount: number, sourceId: number) { super(BattlerTagType.FIRE_SPIN, CommonAnim.FIRE_SPIN, turnCount, MoveId.FIRE_SPIN, sourceId); } } export class WhirlpoolTag extends VortexTrapTag { + public override readonly tagType = BattlerTagType.WHIRLPOOL; constructor(turnCount: number, sourceId: number) { super(BattlerTagType.WHIRLPOOL, CommonAnim.WHIRLPOOL, turnCount, MoveId.WHIRLPOOL, sourceId); } } export class ClampTag extends DamagingTrapTag { + public override readonly tagType = BattlerTagType.CLAMP; constructor(turnCount: number, sourceId: number) { super(BattlerTagType.CLAMP, CommonAnim.CLAMP, turnCount, MoveId.CLAMP, sourceId); } @@ -1538,6 +1653,7 @@ export class ClampTag extends DamagingTrapTag { } export class SandTombTag extends DamagingTrapTag { + public override readonly tagType = BattlerTagType.SAND_TOMB; constructor(turnCount: number, sourceId: number) { super(BattlerTagType.SAND_TOMB, CommonAnim.SAND_TOMB, turnCount, MoveId.SAND_TOMB, sourceId); } @@ -1551,6 +1667,7 @@ export class SandTombTag extends DamagingTrapTag { } export class MagmaStormTag extends DamagingTrapTag { + public override readonly tagType = BattlerTagType.MAGMA_STORM; constructor(turnCount: number, sourceId: number) { super(BattlerTagType.MAGMA_STORM, CommonAnim.MAGMA_STORM, turnCount, MoveId.MAGMA_STORM, sourceId); } @@ -1563,6 +1680,7 @@ export class MagmaStormTag extends DamagingTrapTag { } export class SnapTrapTag extends DamagingTrapTag { + public override readonly tagType = BattlerTagType.SNAP_TRAP; constructor(turnCount: number, sourceId: number) { super(BattlerTagType.SNAP_TRAP, CommonAnim.SNAP_TRAP, turnCount, MoveId.SNAP_TRAP, sourceId); } @@ -1575,6 +1693,7 @@ export class SnapTrapTag extends DamagingTrapTag { } export class ThunderCageTag extends DamagingTrapTag { + public override readonly tagType = BattlerTagType.THUNDER_CAGE; constructor(turnCount: number, sourceId: number) { super(BattlerTagType.THUNDER_CAGE, CommonAnim.THUNDER_CAGE, turnCount, MoveId.THUNDER_CAGE, sourceId); } @@ -1594,6 +1713,7 @@ export class ThunderCageTag extends DamagingTrapTag { } export class InfestationTag extends DamagingTrapTag { + public override readonly tagType = BattlerTagType.INFESTATION; constructor(turnCount: number, sourceId: number) { super(BattlerTagType.INFESTATION, CommonAnim.INFESTATION, turnCount, MoveId.INFESTATION, sourceId); } @@ -1613,7 +1733,8 @@ export class InfestationTag extends DamagingTrapTag { } export class ProtectedTag extends BattlerTag { - constructor(sourceMove: MoveId, tagType: BattlerTagType = BattlerTagType.PROTECTED) { + public declare readonly tagType: ProtectionBattlerTagType; + constructor(sourceMove: MoveId, tagType: ProtectionBattlerTagType = BattlerTagType.PROTECTED) { super(tagType, BattlerTagLapseType.TURN_END, 0, sourceMove); } @@ -1649,14 +1770,13 @@ export class ProtectedTag extends BattlerTag { } /** Class for `BattlerTag`s that apply some effect when hit by a contact move */ -export class ContactProtectedTag extends ProtectedTag { +export abstract class ContactProtectedTag extends ProtectedTag { /** * Function to call when a contact move hits the pokemon with this tag. * @param _attacker - The pokemon using the contact move * @param _user - The pokemon that is being attacked and has the tag - * @param _move - The move used by the attacker */ - onContact(_attacker: Pokemon, _user: Pokemon) {} + abstract onContact(_attacker: Pokemon, _user: Pokemon): void; /** * Lapse the tag and apply `onContact` if the move makes contact and @@ -1686,22 +1806,16 @@ export class ContactProtectedTag extends ProtectedTag { /** * `BattlerTag` class for moves that block damaging moves damage the enemy if the enemy's move makes contact * Used by {@linkcode MoveId.SPIKY_SHIELD} + * + * @sealed */ export class ContactDamageProtectedTag extends ContactProtectedTag { - private damageRatio: number; + public override readonly tagType = BattlerTagType.SPIKY_SHIELD; + #damageRatio: number; constructor(sourceMove: MoveId, damageRatio: number) { super(sourceMove, BattlerTagType.SPIKY_SHIELD); - this.damageRatio = damageRatio; - } - - /** - * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag - */ - loadTag(source: BattlerTag | any): void { - super.loadTag(source); - this.damageRatio = source.damageRatio; + this.#damageRatio = damageRatio; } /** @@ -1713,7 +1827,7 @@ export class ContactDamageProtectedTag extends ContactProtectedTag { const cancelled = new BooleanHolder(false); applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon: user, cancelled }); if (!cancelled.value) { - attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), { + attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.#damageRatio)), { result: HitResult.INDIRECT, }); } @@ -1721,20 +1835,22 @@ export class ContactDamageProtectedTag extends ContactProtectedTag { } /** Base class for `BattlerTag`s that block damaging moves but not status moves */ -export class DamageProtectedTag extends ContactProtectedTag {} +export abstract class DamageProtectedTag extends ContactProtectedTag { + public declare readonly tagType: DamageProtectedTagType; +} export class ContactSetStatusProtectedTag extends DamageProtectedTag { + public declare readonly tagType: ContactSetStatusProtectedTagType; + /** The status effect applied to attackers */ + #statusEffect: StatusEffect; /** - * @param sourceMove The move that caused the tag to be applied - * @param tagType The type of the tag - * @param statusEffect The status effect to apply to the attacker + * @param sourceMove - The move that caused the tag to be applied + * @param tagType - The type of the tag + * @param statusEffect - The status effect applied to attackers */ - constructor( - sourceMove: MoveId, - tagType: BattlerTagType, - private statusEffect: StatusEffect, - ) { + constructor(sourceMove: MoveId, tagType: ContactSetStatusProtectedTagType, statusEffect: StatusEffect) { super(sourceMove, tagType); + this.#statusEffect = statusEffect; } /** @@ -1743,7 +1859,7 @@ export class ContactSetStatusProtectedTag extends DamageProtectedTag { * @param user - The pokemon that is being attacked and has the tag */ override onContact(attacker: Pokemon, user: Pokemon): void { - attacker.trySetStatus(this.statusEffect, true, user); + attacker.trySetStatus(this.#statusEffect, true, user); } } @@ -1752,24 +1868,15 @@ export class ContactSetStatusProtectedTag extends DamageProtectedTag { * Used by {@linkcode MoveId.KINGS_SHIELD}, {@linkcode MoveId.OBSTRUCT}, {@linkcode MoveId.SILK_TRAP} */ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag { - private stat: BattleStat; - private levels: number; + public declare readonly tagType: ContactStatStageChangeProtectedTagType; + #stat: BattleStat; + #levels: number; - constructor(sourceMove: MoveId, tagType: BattlerTagType, stat: BattleStat, levels: number) { + constructor(sourceMove: MoveId, tagType: ContactStatStageChangeProtectedTagType, stat: BattleStat, levels: number) { super(sourceMove, tagType); - this.stat = stat; - this.levels = levels; - } - - /** - * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag - */ - override loadTag(source: BattlerTag | any): void { - super.loadTag(source); - this.stat = source.stat; - this.levels = source.levels; + this.#stat = stat; + this.#levels = levels; } /** @@ -1782,19 +1889,19 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag { "StatStageChangePhase", attacker.getBattlerIndex(), false, - [this.stat], - this.levels, + [this.#stat], + this.#levels, ); } } /** * `BattlerTag` class for effects that cause the affected Pokemon to survive lethal attacks at 1 HP. - * Used for {@link https://bulbapedia.bulbagarden.net/wiki/Endure_(move) | Endure} and - * Endure Tokens. + * Used for {@link https://bulbapedia.bulbagarden.net/wiki/Endure_(move) | Endure} and endure tokens. */ export class EnduringTag extends BattlerTag { - constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, sourceMove: MoveId) { + public declare readonly tagType: EndureTagType; + constructor(tagType: EndureTagType, lapseType: BattlerTagLapseType, sourceMove: MoveId) { super(tagType, lapseType, 0, sourceMove); } @@ -1823,6 +1930,7 @@ export class EnduringTag extends BattlerTag { } export class SturdyTag extends BattlerTag { + public override readonly tagType = BattlerTagType.STURDY; constructor(sourceMove: MoveId) { super(BattlerTagType.STURDY, BattlerTagLapseType.TURN_END, 0, sourceMove); } @@ -1841,7 +1949,8 @@ export class SturdyTag extends BattlerTag { } } -export class PerishSongTag extends BattlerTag { +export class PerishSongTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.PERISH_SONG; constructor(turnCount: number) { super(BattlerTagType.PERISH_SONG, BattlerTagLapseType.TURN_END, turnCount, MoveId.PERISH_SONG, undefined, true); } @@ -1873,6 +1982,7 @@ export class PerishSongTag extends BattlerTag { * @see {@link https://bulbapedia.bulbagarden.net/wiki/Center_of_attention | Center of Attention} */ export class CenterOfAttentionTag extends BattlerTag { + public override readonly tagType = BattlerTagType.CENTER_OF_ATTENTION; public powder: boolean; constructor(sourceMove: MoveId) { @@ -1899,30 +2009,26 @@ export class CenterOfAttentionTag extends BattlerTag { } } -export class AbilityBattlerTag extends BattlerTag { - public ability: AbilityId; - - constructor(tagType: BattlerTagType, ability: AbilityId, lapseType: BattlerTagLapseType, turnCount: number) { - super(tagType, lapseType, turnCount); - - this.ability = ability; +export class AbilityBattlerTag extends SerializableBattlerTag { + public declare readonly tagType: AbilityBattlerTagType; + #ability: AbilityId; + /** The ability that the tag corresponds to */ + public get ability(): AbilityId { + return this.#ability; } - /** - * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag - */ - loadTag(source: BattlerTag | any): void { - super.loadTag(source); - this.ability = source.ability as AbilityId; + constructor(tagType: AbilityBattlerTagType, ability: AbilityId, lapseType: BattlerTagLapseType, turnCount: number) { + super(tagType, lapseType, turnCount); + + this.#ability = ability; } } /** * Tag used by Unburden to double speed - * @extends AbilityBattlerTag */ export class UnburdenTag extends AbilityBattlerTag { + public override readonly tagType = BattlerTagType.UNBURDEN; constructor() { super(BattlerTagType.UNBURDEN, AbilityId.UNBURDEN, BattlerTagLapseType.CUSTOM, 1); } @@ -1935,6 +2041,7 @@ export class UnburdenTag extends AbilityBattlerTag { } export class TruantTag extends AbilityBattlerTag { + public override readonly tagType = BattlerTagType.TRUANT; constructor() { super(BattlerTagType.TRUANT, AbilityId.TRUANT, BattlerTagLapseType.MOVE, 1); } @@ -1947,7 +2054,7 @@ export class TruantTag extends AbilityBattlerTag { const lastMove = pokemon.getLastXMoves()[0]; - if (!lastMove) { + if (!lastMove || lastMove.move === MoveId.NONE) { // Don't interrupt move if last move was `Moves.NONE` OR no prior move was found return true; } @@ -1969,6 +2076,7 @@ export class TruantTag extends AbilityBattlerTag { } export class SlowStartTag extends AbilityBattlerTag { + public override readonly tagType = BattlerTagType.SLOW_START; constructor() { super(BattlerTagType.SLOW_START, AbilityId.SLOW_START, BattlerTagLapseType.TURN_END, 5); } @@ -2006,18 +2114,19 @@ export class SlowStartTag extends AbilityBattlerTag { } export class HighestStatBoostTag extends AbilityBattlerTag { + public declare readonly tagType: HighestStatBoostTagType; public stat: Stat; public multiplier: number; - constructor(tagType: BattlerTagType, ability: AbilityId) { + constructor(tagType: HighestStatBoostTagType, ability: AbilityId) { super(tagType, ability, BattlerTagLapseType.CUSTOM, 1); } /** * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag + * @param source - An object containing the fields needed to reconstruct this tag. */ - loadTag(source: BattlerTag | any): void { + public override loadTag(source: BaseBattlerTag & Pick): void { super.loadTag(source); this.stat = source.stat as Stat; this.multiplier = source.multiplier; @@ -2065,43 +2174,32 @@ export class HighestStatBoostTag extends AbilityBattlerTag { } } -export class WeatherHighestStatBoostTag extends HighestStatBoostTag implements WeatherBattlerTag { - public weatherTypes: WeatherType[]; - - constructor(tagType: BattlerTagType, ability: AbilityId, ...weatherTypes: WeatherType[]) { - super(tagType, ability); - this.weatherTypes = weatherTypes; +export class WeatherHighestStatBoostTag extends HighestStatBoostTag { + #weatherTypes: WeatherType[]; + public get weatherTypes(): WeatherType[] { + return this.#weatherTypes; } - /** - * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag - */ - loadTag(source: BattlerTag | any): void { - super.loadTag(source); - this.weatherTypes = source.weatherTypes.map(w => w as WeatherType); + constructor(tagType: HighestStatBoostTagType, ability: AbilityId, ...weatherTypes: WeatherType[]) { + super(tagType, ability); + this.#weatherTypes = weatherTypes; } } -export class TerrainHighestStatBoostTag extends HighestStatBoostTag implements TerrainBattlerTag { - public terrainTypes: TerrainType[]; - - constructor(tagType: BattlerTagType, ability: AbilityId, ...terrainTypes: TerrainType[]) { - super(tagType, ability); - this.terrainTypes = terrainTypes; +export class TerrainHighestStatBoostTag extends HighestStatBoostTag { + #terrainTypes: TerrainType[]; + public get terrainTypes(): TerrainType[] { + return this.#terrainTypes; } - /** - * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag - */ - loadTag(source: BattlerTag | any): void { - super.loadTag(source); - this.terrainTypes = source.terrainTypes.map(w => w as TerrainType); + constructor(tagType: HighestStatBoostTagType, ability: AbilityId, ...terrainTypes: TerrainType[]) { + super(tagType, ability); + this.#terrainTypes = terrainTypes; } } -export class SemiInvulnerableTag extends BattlerTag { +export class SemiInvulnerableTag extends SerializableBattlerTag { + public declare readonly tagType: SemiInvulnerableTagType; constructor(tagType: BattlerTagType, turnCount: number, sourceMove: MoveId) { super(tagType, BattlerTagLapseType.MOVE_EFFECT, turnCount, sourceMove); } @@ -2121,22 +2219,16 @@ export class SemiInvulnerableTag extends BattlerTag { } } -export class TypeImmuneTag extends BattlerTag { - public immuneType: PokemonType; +export abstract class TypeImmuneTag extends SerializableBattlerTag { + #immuneType: PokemonType; + public get immuneType(): PokemonType { + return this.#immuneType; + } constructor(tagType: BattlerTagType, sourceMove: MoveId, immuneType: PokemonType, length = 1) { super(tagType, BattlerTagLapseType.TURN_END, length, sourceMove, undefined, true); - this.immuneType = immuneType; - } - - /** - * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag - */ - loadTag(source: BattlerTag | any): void { - super.loadTag(source); - this.immuneType = source.immuneType as PokemonType; + this.#immuneType = immuneType; } } @@ -2146,6 +2238,7 @@ export class TypeImmuneTag extends BattlerTag { * @see {@link https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move) | MoveId.TELEKINESIS} */ export class FloatingTag extends TypeImmuneTag { + public override readonly tagType = BattlerTagType.FLOATING; constructor(tagType: BattlerTagType, sourceMove: MoveId, turnCount: number) { super(tagType, sourceMove, PokemonType.GROUND, turnCount); } @@ -2174,10 +2267,21 @@ export class FloatingTag extends TypeImmuneTag { } } -export class TypeBoostTag extends BattlerTag { - public boostedType: PokemonType; - public boostValue: number; - public oneUse: boolean; +export class TypeBoostTag extends SerializableBattlerTag { + public declare readonly tagType: TypeBoostTagType; + #boostedType: PokemonType; + #boostValue: number; + #oneUse: boolean; + + public get boostedType(): PokemonType { + return this.#boostedType; + } + public get boostValue(): number { + return this.#boostValue; + } + public get oneUse(): boolean { + return this.#oneUse; + } constructor( tagType: BattlerTagType, @@ -2188,20 +2292,9 @@ export class TypeBoostTag extends BattlerTag { ) { super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove); - this.boostedType = boostedType; - this.boostValue = boostValue; - this.oneUse = oneUse; - } - - /** - * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag - */ - loadTag(source: BattlerTag | any): void { - super.loadTag(source); - this.boostedType = source.boostedType as PokemonType; - this.boostValue = source.boostValue; - this.oneUse = source.oneUse; + this.#boostedType = boostedType; + this.#boostValue = boostValue; + this.#oneUse = oneUse; } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -2224,14 +2317,25 @@ export class TypeBoostTag extends BattlerTag { } } -export class CritBoostTag extends BattlerTag { - constructor(tagType: BattlerTagType, sourceMove: MoveId) { +export class CritBoostTag extends SerializableBattlerTag { + public declare readonly tagType: CritStageBoostTagType; + /** The number of stages boosted by this tag */ + public readonly critStages: number = 1; + + constructor(tagType: CritStageBoostTagType, sourceMove: MoveId) { super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove, undefined, true); } onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); + // Dragon cheer adds +2 crit stages if the pokemon is a Dragon type when the tag is added + if (this.tagType === BattlerTagType.DRAGON_CHEER && pokemon.getTypes(true).includes(PokemonType.DRAGON)) { + (this as Mutable).critStages = 2; + } else { + (this as Mutable).critStages = 1; + } + globalScene.phaseManager.queueMessage( i18next.t("battlerTags:critBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), @@ -2252,43 +2356,21 @@ export class CritBoostTag extends BattlerTag { }), ); } -} -/** - * Tag for the effects of Dragon Cheer, which boosts the critical hit ratio of the user's allies. - * @extends {CritBoostTag} - */ -export class DragonCheerTag extends CritBoostTag { - /** The types of the user's ally when the tag is added */ - public typesOnAdd: PokemonType[]; - - constructor() { - super(BattlerTagType.CRIT_BOOST, MoveId.DRAGON_CHEER); - } - - onAdd(pokemon: Pokemon): void { - super.onAdd(pokemon); - - this.typesOnAdd = pokemon.getTypes(true); + public override loadTag(source: BaseBattlerTag & Pick): void { + super.loadTag(source); + // TODO: Remove the nullish coalescing once Zod Schemas come in + // For now, this is kept for backwards compatibility with older save files + (this as Mutable).critStages = source.critStages ?? 1; } } -export class SaltCuredTag extends BattlerTag { - private sourceIndex: number; - +export class SaltCuredTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.SALT_CURED; constructor(sourceId: number) { super(BattlerTagType.SALT_CURED, BattlerTagLapseType.TURN_END, 1, MoveId.SALT_CURE, sourceId); } - /** - * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag - */ - loadTag(source: BattlerTag | any): void { - super.loadTag(source); - this.sourceIndex = source.sourceIndex; - } - onAdd(pokemon: Pokemon): void { const source = this.getSourcePokemon(); if (!source) { @@ -2302,7 +2384,6 @@ export class SaltCuredTag extends BattlerTag { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), ); - this.sourceIndex = source.getBattlerIndex(); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -2338,22 +2419,12 @@ export class SaltCuredTag extends BattlerTag { } } -export class CursedTag extends BattlerTag { - private sourceIndex: number; - +export class CursedTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.CURSED; constructor(sourceId: number) { super(BattlerTagType.CURSED, BattlerTagLapseType.TURN_END, 1, MoveId.CURSE, sourceId, true); } - /** - * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag - */ - loadTag(source: BattlerTag | any): void { - super.loadTag(source); - this.sourceIndex = source.sourceIndex; - } - onAdd(pokemon: Pokemon): void { const source = this.getSourcePokemon(); if (!source) { @@ -2362,7 +2433,6 @@ export class CursedTag extends BattlerTag { } super.onAdd(pokemon); - this.sourceIndex = source.getBattlerIndex(); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -2392,11 +2462,13 @@ export class CursedTag extends BattlerTag { return ret; } } + /** * Battler tag for attacks that remove a type post use. */ -export class RemovedTypeTag extends BattlerTag { - constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, sourceMove: MoveId) { +export class RemovedTypeTag extends SerializableBattlerTag { + public declare readonly tagType: RemovedTypeTagType; + constructor(tagType: RemovedTypeTagType, lapseType: BattlerTagLapseType, sourceMove: MoveId) { super(tagType, lapseType, 1, sourceMove); } } @@ -2405,8 +2477,9 @@ export class RemovedTypeTag extends BattlerTag { * Battler tag for effects that ground the source, allowing Ground-type moves to hit them. * @description `IGNORE_FLYING`: Persistent grounding effects (i.e. from Smack Down and Thousand Waves) */ -export class GroundedTag extends BattlerTag { - constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, sourceMove: MoveId) { +export class GroundedTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.IGNORE_FLYING; + constructor(tagType: BattlerTagType.IGNORE_FLYING, lapseType: BattlerTagLapseType, sourceMove: MoveId) { super(tagType, lapseType, 1, sourceMove); } } @@ -2478,15 +2551,16 @@ export class RoostedTag extends BattlerTag { } /** Common attributes of form change abilities that block damage */ -export class FormBlockDamageTag extends BattlerTag { - constructor(tagType: BattlerTagType) { +export class FormBlockDamageTag extends SerializableBattlerTag { + public declare readonly tagType: BattlerTagType.ICE_FACE | BattlerTagType.DISGUISE; + constructor(tagType: BattlerTagType.ICE_FACE | BattlerTagType.DISGUISE) { super(tagType, BattlerTagLapseType.CUSTOM, 1); } /** * Determines if the tag can be added to the Pokémon. - * @param {Pokemon} pokemon The Pokémon to which the tag might be added. - * @returns {boolean} True if the tag can be added, false otherwise. + * @param pokemon - The Pokémon to which the tag might be added. + * @returns `true` if the tag can be added, `false` otherwise. */ canAdd(pokemon: Pokemon): boolean { return pokemon.formIndex === 0; @@ -2508,7 +2582,7 @@ export class FormBlockDamageTag extends BattlerTag { /** * Removes the tag from the Pokémon. * Triggers a form change when the tag is removed. - * @param {Pokemon} pokemon The Pokémon from which the tag is removed. + * @param pokemon - The Pokémon from which the tag is removed. */ onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); @@ -2516,12 +2590,14 @@ export class FormBlockDamageTag extends BattlerTag { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger); } } + /** Provides the additional weather-based effects of the Ice Face ability */ export class IceFaceBlockDamageTag extends FormBlockDamageTag { + public override readonly tagType = BattlerTagType.ICE_FACE; /** * Determines if the tag can be added to the Pokémon. - * @param {Pokemon} pokemon The Pokémon to which the tag might be added. - * @returns {boolean} True if the tag can be added, false otherwise. + * @param pokemon - The Pokémon to which the tag might be added. + * @returns `true` if the tag can be added, `false` otherwise. */ canAdd(pokemon: Pokemon): boolean { const weatherType = globalScene.arena.weather?.weatherType; @@ -2534,21 +2610,19 @@ export class IceFaceBlockDamageTag extends FormBlockDamageTag { /** * Battler tag indicating a Tatsugiri with {@link https://bulbapedia.bulbagarden.net/wiki/Commander_(Ability) | Commander} * has entered the tagged Pokemon's mouth. + * @sealed */ -export class CommandedTag extends BattlerTag { - private _tatsugiriFormKey: string; +export class CommandedTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.COMMANDED; + public readonly tatsugiriFormKey: string; constructor(sourceId: number) { super(BattlerTagType.COMMANDED, BattlerTagLapseType.CUSTOM, 0, MoveId.NONE, sourceId); } - public get tatsugiriFormKey(): string { - return this._tatsugiriFormKey; - } - /** Caches the Tatsugiri's form key and sharply boosts the tagged Pokemon's stats */ override onAdd(pokemon: Pokemon): void { - this._tatsugiriFormKey = this.getSourcePokemon()?.getFormKey() ?? "curly"; + (this as Mutable).tatsugiriFormKey = this.getSourcePokemon()?.getFormKey() ?? "curly"; globalScene.phaseManager.unshiftNew( "StatStageChangePhase", pokemon.getBattlerIndex(), @@ -2565,9 +2639,9 @@ export class CommandedTag extends BattlerTag { } } - override loadTag(source: BattlerTag | any): void { + override loadTag(source: BaseBattlerTag & Pick): void { super.loadTag(source); - this._tatsugiriFormKey = source._tatsugiriFormKey; + (this as Mutable).tatsugiriFormKey = source.tatsugiriFormKey; } } @@ -2580,8 +2654,10 @@ export class CommandedTag extends BattlerTag { * - Stat changes on removal of (all) stacks. * - Removing stacks decreases DEF and SPDEF, independently, by one stage for each stack that successfully changed * the stat when added. + * @sealed */ -export class StockpilingTag extends BattlerTag { +export class StockpilingTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.STOCKPILING; public stockpiledCount = 0; public statChangeCounts: { [Stat.DEF]: number; [Stat.SPDEF]: number } = { [Stat.DEF]: 0, @@ -2604,7 +2680,9 @@ export class StockpilingTag extends BattlerTag { } }; - loadTag(source: BattlerTag | any): void { + public override loadTag( + source: BaseBattlerTag & Pick, + ): void { super.loadTag(source); this.stockpiledCount = source.stockpiledCount || 0; this.statChangeCounts = { @@ -2687,10 +2765,10 @@ export class StockpilingTag extends BattlerTag { /** * Battler tag for Gulp Missile used by Cramorant. - * @extends BattlerTag */ -export class GulpMissileTag extends BattlerTag { - constructor(tagType: BattlerTagType, sourceMove: MoveId) { +export class GulpMissileTag extends SerializableBattlerTag { + public declare readonly tagType: BattlerTagType.GULP_MISSILE_ARROKUDA | BattlerTagType.GULP_MISSILE_PIKACHU; + constructor(tagType: BattlerTagType.GULP_MISSILE_ARROKUDA | BattlerTagType.GULP_MISSILE_PIKACHU, sourceMove: MoveId) { super(tagType, BattlerTagLapseType.HIT, 0, sourceMove); } @@ -2729,11 +2807,12 @@ export class GulpMissileTag extends BattlerTag { /** * Gulp Missile's initial form changes are triggered by using Surf and Dive. - * @param {Pokemon} pokemon The Pokemon with Gulp Missile ability. + * @param pokemon - The Pokemon with Gulp Missile ability. * @returns Whether the BattlerTag can be added. */ canAdd(pokemon: Pokemon): boolean { - const isSurfOrDive = [MoveId.SURF, MoveId.DIVE].includes(this.sourceMove); + // Bang here is OK as if sourceMove was undefined, this would just evaluate to false + const isSurfOrDive = [MoveId.SURF, MoveId.DIVE].includes(this.sourceMove!); const isNormalForm = pokemon.formIndex === 0 && !pokemon.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA) && @@ -2755,52 +2834,46 @@ export class GulpMissileTag extends BattlerTag { } /** - * Tag that makes the target drop all of it type immunities + * Tag that makes the target drop the immunities granted by a particular type * and all accuracy checks ignore its evasiveness stat. * * Applied by moves: {@linkcode MoveId.ODOR_SLEUTH | Odor Sleuth}, * {@linkcode MoveId.MIRACLE_EYE | Miracle Eye} and {@linkcode MoveId.FORESIGHT | Foresight}. * - * @extends BattlerTag * @see {@linkcode ignoreImmunity} */ -export class ExposedTag extends BattlerTag { - private defenderType: PokemonType; - private allowedTypes: PokemonType[]; +export class ExposedTag extends SerializableBattlerTag { + public declare readonly tagType: BattlerTagType.IGNORE_DARK | BattlerTagType.IGNORE_GHOST; + #defenderType: PokemonType; + #allowedTypes: readonly PokemonType[]; - constructor(tagType: BattlerTagType, sourceMove: MoveId, defenderType: PokemonType, allowedTypes: PokemonType[]) { + constructor( + tagType: BattlerTagType.IGNORE_DARK | BattlerTagType.IGNORE_GHOST, + sourceMove: MoveId, + defenderType: PokemonType, + allowedTypes: PokemonType[], + ) { super(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove); - this.defenderType = defenderType; - this.allowedTypes = allowedTypes; + this.#defenderType = defenderType; + this.#allowedTypes = allowedTypes; } /** - * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag - */ - loadTag(source: BattlerTag | any): void { - super.loadTag(source); - this.defenderType = source.defenderType as PokemonType; - this.allowedTypes = source.allowedTypes as PokemonType[]; - } - - /** - * @param types {@linkcode PokemonType} of the defending Pokemon - * @param moveType {@linkcode PokemonType} of the move targetting it + * @param type - The defending type to check against + * @param moveType - The pokemon type of the move being used * @returns `true` if the move should be allowed to target the defender. */ ignoreImmunity(type: PokemonType, moveType: PokemonType): boolean { - return type === this.defenderType && this.allowedTypes.includes(moveType); + return type === this.#defenderType && this.#allowedTypes.includes(moveType); } } /** * Tag that prevents HP recovery from held items and move effects. It also blocks the usage of recovery moves. * Applied by moves: {@linkcode MoveId.HEAL_BLOCK | Heal Block (5 turns)}, {@linkcode MoveId.PSYCHIC_NOISE | Psychic Noise (2 turns)} - * - * @extends MoveRestrictionBattlerTag */ export class HealBlockTag extends MoveRestrictionBattlerTag { + public override readonly tagType = BattlerTagType.HEAL_BLOCK; constructor(turnCount: number, sourceMove: MoveId) { super( BattlerTagType.HEAL_BLOCK, @@ -2818,7 +2891,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { /** * Checks if a move is disabled under Heal Block - * @param {MoveId} move {@linkcode MoveId} the move ID + * @param move - {@linkcode MoveId | ID} of the move being used * @returns `true` if the move has a TRIAGE_MOVE flag and is a status move */ override isMoveRestricted(move: MoveId): boolean { @@ -2828,9 +2901,9 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { /** * Checks if a move is disabled under Heal Block because of its choice of target * Implemented b/c of Pollen Puff - * @param {MoveId} move {@linkcode MoveId} the move ID - * @param {Pokemon} user {@linkcode Pokemon} the move user - * @param {Pokemon} target {@linkcode Pokemon} the target of the move + * @param move - {@linkcode MoveId | ID} of the move being used + * @param user - The pokemon using the move + * @param target - The target of the move * @returns `true` if the move cannot be used because the target is an ally */ override isMoveTargetRestricted(move: MoveId, user: Pokemon, target: Pokemon) { @@ -2851,10 +2924,9 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { } /** - * @override - * @param {Pokemon} pokemon {@linkcode Pokemon} attempting to use the restricted move - * @param {MoveId} move {@linkcode MoveId} ID of the move being interrupted - * @returns {string} text to display when the move is interrupted + * @param pokemon - {@linkcode Pokemon} attempting to use the restricted move + * @param move - {@linkcode MoveId | ID} of the move being interrupted + * @returns Text to display when the move is interrupted */ override interruptedText(pokemon: Pokemon, move: MoveId): string { return i18next.t("battle:moveDisabledHealBlock", { @@ -2880,17 +2952,17 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { /** * Tag that doubles the type effectiveness of Fire-type moves. - * @extends BattlerTag */ -export class TarShotTag extends BattlerTag { +export class TarShotTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.TAR_SHOT; constructor() { super(BattlerTagType.TAR_SHOT, BattlerTagLapseType.CUSTOM, 0); } /** * If the Pokemon is terastallized, the tag cannot be added. - * @param {Pokemon} pokemon the {@linkcode Pokemon} to which the tag is added - * @returns whether the tag is applied + * @param pokemon - The pokemon to check + * @returns Whether the tag can be added */ override canAdd(pokemon: Pokemon): boolean { return !pokemon.isTerastallized; @@ -2910,6 +2982,7 @@ export class TarShotTag extends BattlerTag { * While this tag is in effect, the afflicted Pokemon's moves are changed to Electric type. */ export class ElectrifiedTag extends BattlerTag { + public override readonly tagType = BattlerTagType.ELECTRIFIED; constructor() { super(BattlerTagType.ELECTRIFIED, BattlerTagLapseType.TURN_END, 1, MoveId.ELECTRIFY); } @@ -2928,7 +3001,8 @@ export class ElectrifiedTag extends BattlerTag { * Battler Tag that keeps track of how many times the user has Autotomized * Each count of Autotomization reduces the weight by 100kg */ -export class AutotomizedTag extends BattlerTag { +export class AutotomizedTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.AUTOTOMIZED; public autotomizeCount = 0; constructor(sourceMove: MoveId = MoveId.AUTOTOMIZE) { super(BattlerTagType.AUTOTOMIZED, BattlerTagLapseType.CUSTOM, 1, sourceMove); @@ -2954,20 +3028,45 @@ export class AutotomizedTag extends BattlerTag { onOverlap(pokemon: Pokemon): void { this.onAdd(pokemon); } + + public override loadTag(source: BaseBattlerTag & Pick): void { + super.loadTag(source); + this.autotomizeCount = source.autotomizeCount; + } } /** * Tag implementing the {@link https://bulbapedia.bulbagarden.net/wiki/Substitute_(doll)#Effect | Substitute Doll} effect, * for use with the moves Substitute and Shed Tail. Pokemon with this tag deflect most forms of received attack damage * onto the tag. This tag also grants immunity to most Status moves and several move effects. + * + * @sealed */ -export class SubstituteTag extends BattlerTag { +export class SubstituteTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.SUBSTITUTE; /** The substitute's remaining HP. If HP is depleted, the Substitute fades. */ public hp: number; + + //#region non-serializable properties /** A reference to the sprite representing the Substitute doll */ - public sprite: Phaser.GameObjects.Sprite; + #sprite: Phaser.GameObjects.Sprite; + /** A reference to the sprite representing the Substitute doll */ + public get sprite(): Phaser.GameObjects.Sprite { + return this.#sprite; + } + public set sprite(value: Phaser.GameObjects.Sprite) { + this.#sprite = value; + } /** Is the source Pokemon "in focus," i.e. is it fully visible on the field? */ - public sourceInFocus: boolean; + #sourceInFocus: boolean; + /** Is the source Pokemon "in focus," i.e. is it fully visible on the field? */ + public get sourceInFocus(): boolean { + return this.#sourceInFocus; + } + public set sourceInFocus(value: boolean) { + this.#sourceInFocus = value; + } + //#endregion non-serializable properties constructor(sourceMove: MoveId, sourceId: number) { super( @@ -3078,9 +3177,9 @@ export class SubstituteTag extends BattlerTag { /** * When given a battler tag or json representing one, load the data for it. - * @param {BattlerTag | any} source A battler tag + * @param source - An object containing the necessary properties to load the tag */ - loadTag(source: BattlerTag | any): void { + public override loadTag(source: BaseBattlerTag & Pick): void { super.loadTag(source); this.hp = source.hp; } @@ -3093,6 +3192,7 @@ export class SubstituteTag extends BattlerTag { * Currently used only in MysteryEncounters to provide start of fight stat buffs. */ export class MysteryEncounterPostSummonTag extends BattlerTag { + public override readonly tagType = BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON; constructor() { super(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON, BattlerTagLapseType.CUSTOM, 1); } @@ -3126,15 +3226,11 @@ export class MysteryEncounterPostSummonTag extends BattlerTag { * Torment does not interrupt the move if the move is performed consecutively in the same turn and right after Torment is applied */ export class TormentTag extends MoveRestrictionBattlerTag { + public override readonly tagType = BattlerTagType.TORMENT; constructor(sourceId: number) { super(BattlerTagType.TORMENT, BattlerTagLapseType.AFTER_MOVE, 1, MoveId.TORMENT, sourceId); } - /** - * Adds the battler tag to the target Pokemon and defines the private class variable 'target' - * 'Target' is used to track the Pokemon's current status - * @param {Pokemon} pokemon the Pokemon tormented - */ override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); globalScene.phaseManager.queueMessage( @@ -3147,7 +3243,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { /** * Torment only ends when the affected Pokemon leaves the battle field - * @param {Pokemon} pokemon the Pokemon under the effects of Torment + * @param pokemon - The Pokemon under the effects of Torment * @param _tagType * @returns `true` if still present | `false` if not */ @@ -3156,8 +3252,8 @@ export class TormentTag extends MoveRestrictionBattlerTag { } /** - * This checks if the current move used is identical to the last used move with a {@linkcode MoveResult} of `SUCCESS`/`MISS` - * @param {MoveId} move the move under investigation + * Check if the current move used is identical to the last used move with a {@linkcode MoveResult} of `SUCCESS`/`MISS` + * @param move - The move under investigation * @returns `true` if there is valid consecutive usage | `false` if the moves are different from each other */ public override isMoveRestricted(move: MoveId, user: Pokemon): boolean { @@ -3189,6 +3285,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { * The tag is removed after 4 turns. */ export class TauntTag extends MoveRestrictionBattlerTag { + public override readonly tagType = BattlerTagType.TAUNT; constructor() { super(BattlerTagType.TAUNT, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE], 4, MoveId.TAUNT); } @@ -3214,8 +3311,8 @@ export class TauntTag extends MoveRestrictionBattlerTag { } /** - * Checks if a move is a status move and determines its restriction status on that basis - * @param {MoveId} move the move under investigation + * Check if a move is a status move and determines its restriction status on that basis + * @param move - The move under investigation * @returns `true` if the move is a status move */ override isMoveRestricted(move: MoveId): boolean { @@ -3243,6 +3340,7 @@ export class TauntTag extends MoveRestrictionBattlerTag { * The tag is only removed when the source-user is removed from the field. */ export class ImprisonTag extends MoveRestrictionBattlerTag { + public override readonly tagType = BattlerTagType.IMPRISON; constructor(sourceId: number) { super( BattlerTagType.IMPRISON, @@ -3255,8 +3353,7 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { /** * Checks if the source of Imprison is still active - * @override - * @param pokemon The pokemon this tag is attached to + * @param pokemon - The pokemon this tag is attached to * @returns `true` if the source is still active */ public override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -3273,8 +3370,7 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { /** * Checks if the source of the tag has the parameter move in its moveset and that the source is still active - * @override - * @param {MoveId} move the move under investigation + * @param move - The move under investigation * @returns `false` if either condition is not met */ public override isMoveRestricted(move: MoveId, _user: Pokemon): boolean { @@ -3306,7 +3402,8 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { * For three turns, starting from the turn of hit, at the end of each turn, the target Pokemon's speed will decrease by 1. * The tag can also expire by taking the target Pokemon off the field, or the Pokemon that originally used the move. */ -export class SyrupBombTag extends BattlerTag { +export class SyrupBombTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.SYRUP_BOMB; constructor(sourceId: number) { super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, MoveId.SYRUP_BOMB, sourceId); } @@ -3368,7 +3465,8 @@ export class SyrupBombTag extends BattlerTag { * The effects of Telekinesis can be baton passed to a teammate. * @see {@link https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move) | MoveId.TELEKINESIS} */ -export class TelekinesisTag extends BattlerTag { +export class TelekinesisTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.TELEKINESIS; constructor(sourceMove: MoveId) { super( BattlerTagType.TELEKINESIS, @@ -3391,9 +3489,9 @@ export class TelekinesisTag extends BattlerTag { /** * Tag that swaps the user's base ATK stat with its base DEF stat. - * @extends BattlerTag */ -export class PowerTrickTag extends BattlerTag { +export class PowerTrickTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.POWER_TRICK; constructor(sourceMove: MoveId, sourceId: number) { super(BattlerTagType.POWER_TRICK, BattlerTagLapseType.CUSTOM, 0, sourceMove, sourceId, true); } @@ -3418,7 +3516,7 @@ export class PowerTrickTag extends BattlerTag { /** * Removes the Power Trick tag and reverts any stat changes if the tag is already applied. - * @param {Pokemon} pokemon The {@linkcode Pokemon} that already has the Power Trick tag. + * @param pokemon - The {@linkcode Pokemon} that already has the Power Trick tag. */ onOverlap(pokemon: Pokemon): void { pokemon.removeTag(this.tagType); @@ -3426,7 +3524,7 @@ export class PowerTrickTag extends BattlerTag { /** * Swaps the user's base ATK stat with its base DEF stat. - * @param {Pokemon} pokemon The {@linkcode Pokemon} whose stats will be swapped. + * @param pokemon - The {@linkcode Pokemon} whose stats will be swapped. */ swapStat(pokemon: Pokemon): void { const temp = pokemon.getStat(Stat.ATK, false); @@ -3440,7 +3538,8 @@ export class PowerTrickTag extends BattlerTag { * If this tag is active when the bearer faints from an opponent's move, the tag reduces that move's PP to 0. * Otherwise, it lapses when the bearer makes another move. */ -export class GrudgeTag extends BattlerTag { +export class GrudgeTag extends SerializableBattlerTag { + public override readonly tagType = BattlerTagType.GRUDGE; constructor() { super(BattlerTagType.GRUDGE, [BattlerTagLapseType.CUSTOM, BattlerTagLapseType.PRE_MOVE], 1, MoveId.GRUDGE); } @@ -3458,9 +3557,10 @@ export class GrudgeTag extends BattlerTag { * Activates Grudge's special effect on the attacking Pokemon and lapses the tag. * @param pokemon * @param lapseType - * @param sourcePokemon {@linkcode Pokemon} the source of the move that fainted the tag's bearer + * @param sourcePokemon - The source of the move that fainted the tag's bearer * @returns `false` if Grudge activates its effect or lapses */ + // TODO: Confirm whether this should interact with copying moves override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType, sourcePokemon?: Pokemon): boolean { if (lapseType === BattlerTagLapseType.CUSTOM && sourcePokemon) { if (sourcePokemon.isActive() && pokemon.isOpponent(sourcePokemon)) { @@ -3486,6 +3586,7 @@ export class GrudgeTag extends BattlerTag { * Tag used to heal the user of Psycho Shift of its status effect if Psycho Shift succeeds in transferring its status effect to the target Pokemon */ export class PsychoShiftTag extends BattlerTag { + public override readonly tagType = BattlerTagType.PSYCHO_SHIFT; constructor() { super(BattlerTagType.PSYCHO_SHIFT, BattlerTagLapseType.AFTER_MOVE, 1, MoveId.PSYCHO_SHIFT); } @@ -3510,6 +3611,7 @@ export class PsychoShiftTag extends BattlerTag { * Tag associated with the move Magic Coat. */ export class MagicCoatTag extends BattlerTag { + public override readonly tagType = BattlerTagType.MAGIC_COAT; constructor() { super(BattlerTagType.MAGIC_COAT, BattlerTagLapseType.TURN_END, 1, MoveId.MAGIC_COAT); } @@ -3563,7 +3665,7 @@ export function getBattlerTag( case BattlerTagType.FRENZY: return new FrenzyTag(turnCount, sourceMove, sourceId); case BattlerTagType.CHARGING: - return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove, sourceId); + return new SerializableBattlerTag(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove, sourceId); case BattlerTagType.ENCORE: return new EncoreTag(sourceId); case BattlerTagType.HELPING_HAND: @@ -3643,15 +3745,14 @@ export function getBattlerTag( case BattlerTagType.FIRE_BOOST: return new TypeBoostTag(tagType, sourceMove, PokemonType.FIRE, 1.5, false); case BattlerTagType.CRIT_BOOST: - return new CritBoostTag(tagType, sourceMove); case BattlerTagType.DRAGON_CHEER: - return new DragonCheerTag(); + return new CritBoostTag(tagType, sourceMove); case BattlerTagType.ALWAYS_CRIT: case BattlerTagType.IGNORE_ACCURACY: - return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove); + return new SerializableBattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove); case BattlerTagType.ALWAYS_GET_HIT: case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: - return new BattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove); + return new SerializableBattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove); case BattlerTagType.BYPASS_SLEEP: return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove); case BattlerTagType.IGNORE_FLYING: @@ -3729,19 +3830,18 @@ export function getBattlerTag( return new PsychoShiftTag(); case BattlerTagType.MAGIC_COAT: return new MagicCoatTag(); - case BattlerTagType.NONE: - default: - return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); } } /** * When given a battler tag or json representing one, creates an actual BattlerTag object with the same data. - * @param {BattlerTag | any} source A battler tag - * @return {BattlerTag} The valid battler tag + * @param source - An object containing the data necessary to reconstruct the BattlerTag. + * @returns The valid battler tag */ -export function loadBattlerTag(source: BattlerTag | any): BattlerTag { - const tag = getBattlerTag(source.tagType, source.turnCount, source.sourceMove, source.sourceId); +export function loadBattlerTag(source: BattlerTag | BattlerTagTypeData): BattlerTag { + // TODO: Remove this bang by fixing the signature of `getBattlerTag` + // to allow undefined sourceIds and sourceMoves (with appropriate fallback for tags that require it) + const tag = getBattlerTag(source.tagType, source.turnCount, source.sourceMove!, source.sourceId!); tag.loadTag(source); return tag; } @@ -3749,8 +3849,8 @@ export function loadBattlerTag(source: BattlerTag | any): BattlerTag { /** * Helper function to verify that the current phase is a MoveEffectPhase and provide quick access to commonly used fields * - * @param _pokemon {@linkcode Pokemon} The Pokémon used to access the current phase - * @returns null if current phase is not MoveEffectPhase, otherwise Object containing the {@linkcode MoveEffectPhase}, and its + * @param _pokemon - The Pokémon used to access the current phase (unused) + * @returns `null` if current phase is not MoveEffectPhase, otherwise Object containing the {@linkcode MoveEffectPhase}, and its * corresponding {@linkcode Move} and user {@linkcode Pokemon} */ function getMoveEffectPhaseData(_pokemon: Pokemon): { phase: MoveEffectPhase; attacker: Pokemon; move: Move } | null { @@ -3764,3 +3864,104 @@ function getMoveEffectPhaseData(_pokemon: Pokemon): { phase: MoveEffectPhase; at } return null; } + +/** + * Map from {@linkcode BattlerTagType} to the corresponding {@linkcode BattlerTag} class. + */ +export type BattlerTagTypeMap = { + [BattlerTagType.RECHARGING]: RechargingTag; + [BattlerTagType.SHELL_TRAP]: ShellTrapTag; + [BattlerTagType.FLINCHED]: FlinchedTag; + [BattlerTagType.INTERRUPTED]: InterruptedTag; + [BattlerTagType.CONFUSED]: ConfusedTag; + [BattlerTagType.INFATUATED]: InfatuatedTag; + [BattlerTagType.SEEDED]: SeedTag; + [BattlerTagType.POWDER]: PowderTag; + [BattlerTagType.NIGHTMARE]: NightmareTag; + [BattlerTagType.FRENZY]: FrenzyTag; + [BattlerTagType.CHARGING]: GenericSerializableBattlerTag; + [BattlerTagType.ENCORE]: EncoreTag; + [BattlerTagType.HELPING_HAND]: HelpingHandTag; + [BattlerTagType.INGRAIN]: IngrainTag; + [BattlerTagType.AQUA_RING]: AquaRingTag; + [BattlerTagType.DROWSY]: DrowsyTag; + [BattlerTagType.TRAPPED]: TrappedTag; + [BattlerTagType.NO_RETREAT]: NoRetreatTag; + [BattlerTagType.BIND]: BindTag; + [BattlerTagType.WRAP]: WrapTag; + [BattlerTagType.FIRE_SPIN]: FireSpinTag; + [BattlerTagType.WHIRLPOOL]: WhirlpoolTag; + [BattlerTagType.CLAMP]: ClampTag; + [BattlerTagType.SAND_TOMB]: SandTombTag; + [BattlerTagType.MAGMA_STORM]: MagmaStormTag; + [BattlerTagType.SNAP_TRAP]: SnapTrapTag; + [BattlerTagType.THUNDER_CAGE]: ThunderCageTag; + [BattlerTagType.INFESTATION]: InfestationTag; + [BattlerTagType.PROTECTED]: ProtectedTag; + [BattlerTagType.SPIKY_SHIELD]: ContactDamageProtectedTag; + [BattlerTagType.KINGS_SHIELD]: ContactStatStageChangeProtectedTag; + [BattlerTagType.OBSTRUCT]: ContactStatStageChangeProtectedTag; + [BattlerTagType.SILK_TRAP]: ContactStatStageChangeProtectedTag; + [BattlerTagType.BANEFUL_BUNKER]: ContactSetStatusProtectedTag; + [BattlerTagType.BURNING_BULWARK]: ContactSetStatusProtectedTag; + [BattlerTagType.ENDURING]: EnduringTag; + [BattlerTagType.ENDURE_TOKEN]: EnduringTag; + [BattlerTagType.STURDY]: SturdyTag; + [BattlerTagType.PERISH_SONG]: PerishSongTag; + [BattlerTagType.CENTER_OF_ATTENTION]: CenterOfAttentionTag; + [BattlerTagType.TRUANT]: TruantTag; + [BattlerTagType.SLOW_START]: SlowStartTag; + [BattlerTagType.PROTOSYNTHESIS]: WeatherHighestStatBoostTag; + [BattlerTagType.QUARK_DRIVE]: TerrainHighestStatBoostTag; + [BattlerTagType.FLYING]: SemiInvulnerableTag; + [BattlerTagType.UNDERGROUND]: SemiInvulnerableTag; + [BattlerTagType.UNDERWATER]: SemiInvulnerableTag; + [BattlerTagType.HIDDEN]: SemiInvulnerableTag; + [BattlerTagType.FIRE_BOOST]: TypeBoostTag; + [BattlerTagType.CRIT_BOOST]: CritBoostTag; + [BattlerTagType.DRAGON_CHEER]: CritBoostTag; + [BattlerTagType.ALWAYS_CRIT]: GenericSerializableBattlerTag; + [BattlerTagType.IGNORE_ACCURACY]: GenericSerializableBattlerTag; + [BattlerTagType.ALWAYS_GET_HIT]: GenericSerializableBattlerTag; + [BattlerTagType.RECEIVE_DOUBLE_DAMAGE]: GenericSerializableBattlerTag; + [BattlerTagType.BYPASS_SLEEP]: BattlerTag; + [BattlerTagType.IGNORE_FLYING]: GroundedTag; + [BattlerTagType.ROOSTED]: RoostedTag; + [BattlerTagType.BURNED_UP]: RemovedTypeTag; + [BattlerTagType.DOUBLE_SHOCKED]: RemovedTypeTag; + [BattlerTagType.SALT_CURED]: SaltCuredTag; + [BattlerTagType.CURSED]: CursedTag; + [BattlerTagType.CHARGED]: TypeBoostTag; + [BattlerTagType.FLOATING]: FloatingTag; + [BattlerTagType.MINIMIZED]: MinimizeTag; + [BattlerTagType.DESTINY_BOND]: DestinyBondTag; + [BattlerTagType.ICE_FACE]: IceFaceBlockDamageTag; + [BattlerTagType.DISGUISE]: FormBlockDamageTag; + [BattlerTagType.COMMANDED]: CommandedTag; + [BattlerTagType.STOCKPILING]: StockpilingTag; + [BattlerTagType.OCTOLOCK]: OctolockTag; + [BattlerTagType.DISABLED]: DisabledTag; + [BattlerTagType.IGNORE_GHOST]: ExposedTag; + [BattlerTagType.IGNORE_DARK]: ExposedTag; + [BattlerTagType.GULP_MISSILE_ARROKUDA]: GulpMissileTag; + [BattlerTagType.GULP_MISSILE_PIKACHU]: GulpMissileTag; + [BattlerTagType.BEAK_BLAST_CHARGING]: BeakBlastChargingTag; + [BattlerTagType.TAR_SHOT]: TarShotTag; + [BattlerTagType.ELECTRIFIED]: ElectrifiedTag; + [BattlerTagType.THROAT_CHOPPED]: ThroatChoppedTag; + [BattlerTagType.GORILLA_TACTICS]: GorillaTacticsTag; + [BattlerTagType.UNBURDEN]: UnburdenTag; + [BattlerTagType.SUBSTITUTE]: SubstituteTag; + [BattlerTagType.AUTOTOMIZED]: AutotomizedTag; + [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON]: MysteryEncounterPostSummonTag; + [BattlerTagType.HEAL_BLOCK]: HealBlockTag; + [BattlerTagType.TORMENT]: TormentTag; + [BattlerTagType.TAUNT]: TauntTag; + [BattlerTagType.IMPRISON]: ImprisonTag; + [BattlerTagType.SYRUP_BOMB]: SyrupBombTag; + [BattlerTagType.TELEKINESIS]: TelekinesisTag; + [BattlerTagType.POWER_TRICK]: PowerTrickTag; + [BattlerTagType.GRUDGE]: GrudgeTag; + [BattlerTagType.PSYCHO_SHIFT]: PsychoShiftTag; + [BattlerTagType.MAGIC_COAT]: MagicCoatTag; +}; diff --git a/src/data/challenge.ts b/src/data/challenge.ts index f4106726e79..3dca0c0980b 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -6,7 +6,6 @@ import { pokemonEvolutions } from "#balance/pokemon-evolutions"; import { speciesStarterCosts } from "#balance/starters"; import { pokemonFormChanges } from "#data/pokemon-forms"; import type { PokemonSpecies } from "#data/pokemon-species"; -import { getPokemonSpeciesForm } from "#data/pokemon-species"; import { BattleType } from "#enums/battle-type"; import { ChallengeType } from "#enums/challenge-type"; import { Challenges } from "#enums/challenges"; @@ -24,9 +23,10 @@ import type { Pokemon } from "#field/pokemon"; import { Trainer } from "#field/trainer"; import { PokemonMove } from "#moves/pokemon-move"; import type { DexAttrProps, GameData } from "#system/game-data"; -import { BooleanHolder, type NumberHolder, randSeedItem } from "#utils/common"; +import { BooleanHolder, isBetween, type NumberHolder, randSeedItem } from "#utils/common"; import { deepCopy } from "#utils/data"; -import { getPokemonSpecies } from "#utils/pokemon-utils"; +import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; +import { toCamelCase, toSnakeCase } from "#utils/strings"; import i18next from "i18next"; /** A constant for the default max cost of the starting party before a run */ @@ -67,14 +67,11 @@ export abstract class Challenge { } /** - * Gets the localisation key for the challenge - * @returns {@link string} The i18n key for this challenge + * Gets the localization key for the challenge + * @returns The i18n key for this challenge as camel case. */ geti18nKey(): string { - return Challenges[this.id] - .split("_") - .map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase())) - .join(""); + return toCamelCase(Challenges[this.id]); } /** @@ -105,23 +102,22 @@ export abstract class Challenge { } /** - * Returns the textual representation of a challenge's current value. - * @param overrideValue {@link number} The value to check for. If undefined, gets the current value. - * @returns {@link string} The localised name for the current value. + * Return the textual representation of a challenge's current value. + * @param overrideValue - The value to check for; default {@linkcode this.value} + * @returns The localised text for the current value. */ - getValue(overrideValue?: number): string { - const value = overrideValue ?? this.value; - return i18next.t(`challenges:${this.geti18nKey()}.value.${value}`); + getValue(overrideValue: number = this.value): string { + return i18next.t(`challenges:${this.geti18nKey()}.value.${overrideValue}`); } /** - * Returns the description of a challenge's current value. - * @param overrideValue {@link number} The value to check for. If undefined, gets the current value. - * @returns {@link string} The localised description for the current value. + * Return the description of a challenge's current value. + * @param overrideValue - The value to check for; default {@linkcode this.value} + * @returns The localised description for the current value. */ - getDescription(overrideValue?: number): string { - const value = overrideValue ?? this.value; - return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${value}`, `challenges:${this.geti18nKey()}.desc`])}`; + // TODO: Do we need an override value here? it's currently unused + getDescription(overrideValue: number = this.value): string { + return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${overrideValue}`, `challenges:${this.geti18nKey()}.desc`])}`; } /** @@ -579,31 +575,19 @@ export class SingleGenerationChallenge extends Challenge { return this.value > 0 ? 1 : 0; } - /** - * Returns the textual representation of a challenge's current value. - * @param {value} overrideValue The value to check for. If undefined, gets the current value. - * @returns {string} The localised name for the current value. - */ - getValue(overrideValue?: number): string { - const value = overrideValue ?? this.value; - if (value === 0) { + getValue(overrideValue: number = this.value): string { + if (overrideValue === 0) { return i18next.t("settings:off"); } - return i18next.t(`starterSelectUiHandler:gen${value}`); + return i18next.t(`starterSelectUiHandler:gen${overrideValue}`); } - /** - * Returns the description of a challenge's current value. - * @param {value} overrideValue The value to check for. If undefined, gets the current value. - * @returns {string} The localised description for the current value. - */ - getDescription(overrideValue?: number): string { - const value = overrideValue ?? this.value; - if (value === 0) { + getDescription(overrideValue: number = this.value): string { + if (overrideValue === 0) { return i18next.t("challenges:singleGeneration.desc_default"); } return i18next.t("challenges:singleGeneration.desc", { - gen: i18next.t(`challenges:singleGeneration.gen_${value}`), + gen: i18next.t(`challenges:singleGeneration.gen_${overrideValue}`), }); } @@ -671,29 +655,13 @@ export class SingleTypeChallenge extends Challenge { return this.value > 0 ? 1 : 0; } - /** - * Returns the textual representation of a challenge's current value. - * @param {value} overrideValue The value to check for. If undefined, gets the current value. - * @returns {string} The localised name for the current value. - */ - getValue(overrideValue?: number): string { - if (overrideValue === undefined) { - overrideValue = this.value; - } - return PokemonType[this.value - 1].toLowerCase(); + getValue(overrideValue: number = this.value): string { + return toSnakeCase(PokemonType[overrideValue - 1]); } - /** - * Returns the description of a challenge's current value. - * @param {value} overrideValue The value to check for. If undefined, gets the current value. - * @returns {string} The localised description for the current value. - */ - getDescription(overrideValue?: number): string { - if (overrideValue === undefined) { - overrideValue = this.value; - } - const type = i18next.t(`pokemonInfo:Type.${PokemonType[this.value - 1]}`); - const typeColor = `[color=${TypeColor[PokemonType[this.value - 1]]}][shadow=${TypeShadow[PokemonType[this.value - 1]]}]${type}[/shadow][/color]`; + getDescription(overrideValue: number = this.value): string { + const type = i18next.t(`pokemonInfo:Type.${PokemonType[overrideValue - 1]}`); + const typeColor = `[color=${TypeColor[PokemonType[overrideValue - 1]]}][shadow=${TypeShadow[PokemonType[this.value - 1]]}]${type}[/shadow][/color]`; const defaultDesc = i18next.t("challenges:singleType.desc_default"); const typeDesc = i18next.t("challenges:singleType.desc", { type: typeColor, @@ -714,11 +682,11 @@ export class SingleTypeChallenge extends Challenge { */ export class FreshStartChallenge extends Challenge { constructor() { - super(Challenges.FRESH_START, 1); + super(Challenges.FRESH_START, 2); } applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder): boolean { - if (!defaultStarterSpecies.includes(pokemon.speciesId)) { + if (this.value === 1 && !defaultStarterSpecies.includes(pokemon.speciesId)) { valid.value = false; return true; } @@ -726,27 +694,45 @@ export class FreshStartChallenge extends Challenge { } applyStarterCost(species: SpeciesId, cost: NumberHolder): boolean { - if (defaultStarterSpecies.includes(species)) { - cost.value = speciesStarterCosts[species]; - return true; - } - return false; + cost.value = speciesStarterCosts[species]; + return true; } applyStarterModify(pokemon: Pokemon): boolean { - pokemon.abilityIndex = 0; // Always base ability, not hidden ability + pokemon.abilityIndex = pokemon.abilityIndex % 2; // Always base ability, if you set it to hidden it wraps to first ability pokemon.passive = false; // Passive isn't unlocked pokemon.nature = Nature.HARDY; // Neutral nature - pokemon.moveset = pokemon.species + let validMoves = pokemon.species .getLevelMoves() - .filter(m => m[0] <= 5) - .map(lm => lm[1]) - .slice(0, 4) - .map(m => new PokemonMove(m)); // No egg moves + .filter(m => isBetween(m[0], 1, 5)) + .map(lm => lm[1]); + // Filter egg moves out of the moveset + pokemon.moveset = pokemon.moveset.filter(pm => validMoves.includes(pm.moveId)); + if (pokemon.moveset.length < 4) { + // If there's empty slots fill with remaining valid moves + const existingMoveIds = pokemon.moveset.map(pm => pm.moveId); + validMoves = validMoves.filter(m => !existingMoveIds.includes(m)); + pokemon.moveset = pokemon.moveset.concat(validMoves.map(m => new PokemonMove(m))).slice(0, 4); + } pokemon.luck = 0; // No luck pokemon.shiny = false; // Not shiny pokemon.variant = 0; // Not shiny - pokemon.formIndex = 0; // Froakie should be base form + if (pokemon.species.speciesId === SpeciesId.ZYGARDE && pokemon.formIndex >= 2) { + pokemon.formIndex -= 2; // Sets 10%-PC to 10%-AB and 50%-PC to 50%-AB + } else if ( + pokemon.formIndex > 0 && + [ + SpeciesId.PIKACHU, + SpeciesId.EEVEE, + SpeciesId.PICHU, + SpeciesId.ROTOM, + SpeciesId.MELOETTA, + SpeciesId.FROAKIE, + SpeciesId.ROCKRUFF, + ].includes(pokemon.species.speciesId) + ) { + pokemon.formIndex = 0; // These mons are set to form 0 because they're meant to be unlocks or mid-run form changes + } pokemon.ivs = [15, 15, 15, 15, 15, 15]; // Default IVs of 15 for all stats (Updated to 15 from 10 in 1.2.0) pokemon.teraType = pokemon.species.type1; // Always primary tera type return true; @@ -832,13 +818,7 @@ export class LowerStarterMaxCostChallenge extends Challenge { super(Challenges.LOWER_MAX_STARTER_COST, 9); } - /** - * @override - */ - getValue(overrideValue?: number): string { - if (overrideValue === undefined) { - overrideValue = this.value; - } + getValue(overrideValue: number = this.value): string { return (DEFAULT_PARTY_MAX_COST - overrideValue).toString(); } @@ -866,13 +846,7 @@ export class LowerStarterPointsChallenge extends Challenge { super(Challenges.LOWER_STARTER_POINTS, 9); } - /** - * @override - */ - getValue(overrideValue?: number): string { - if (overrideValue === undefined) { - overrideValue = this.value; - } + getValue(overrideValue: number = this.value): string { return (DEFAULT_PARTY_MAX_COST - overrideValue).toString(); } diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index 9a6f560933a..b5fb0aa8c07 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -2,7 +2,7 @@ import { pokerogueApi } from "#api/pokerogue-api"; import { globalScene } from "#app/global-scene"; import { speciesStarterCosts } from "#balance/starters"; import type { PokemonSpeciesForm } from "#data/pokemon-species"; -import { getPokemonSpeciesForm, PokemonSpecies } from "#data/pokemon-species"; +import { PokemonSpecies } from "#data/pokemon-species"; import { BiomeId } from "#enums/biome-id"; import { PartyMemberStrength } from "#enums/party-member-strength"; import type { SpeciesId } from "#enums/species-id"; @@ -10,7 +10,7 @@ import { PlayerPokemon } from "#field/pokemon"; import type { Starter } from "#ui/starter-select-ui-handler"; import { randSeedGauss, randSeedInt, randSeedItem } from "#utils/common"; import { getEnumValues } from "#utils/enums"; -import { getPokemonSpecies } from "#utils/pokemon-utils"; +import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; export interface DailyRunConfig { seed: number; diff --git a/src/data/dialogue.ts b/src/data/dialogue.ts index 406e72ee82b..361d005e83b 100644 --- a/src/data/dialogue.ts +++ b/src/data/dialogue.ts @@ -1,6 +1,7 @@ import { BattleSpec } from "#enums/battle-spec"; import { TrainerType } from "#enums/trainer-type"; import { trainerConfigs } from "#trainers/trainer-config"; +import { capitalizeFirstLetter } from "#utils/strings"; export interface TrainerTypeMessages { encounter?: string | string[]; @@ -1755,8 +1756,7 @@ export function initTrainerTypeDialogue(): void { trainerConfigs[trainerType][`${messageType}Messages`] = messages[0][messageType]; } if (messages.length > 1) { - trainerConfigs[trainerType][`female${messageType.slice(0, 1).toUpperCase()}${messageType.slice(1)}Messages`] = - messages[1][messageType]; + trainerConfigs[trainerType][`female${capitalizeFirstLetter(messageType)}Messages`] = messages[1][messageType]; } } else { trainerConfigs[trainerType][`${messageType}Messages`] = messages[messageType]; diff --git a/src/data/moves/move-utils.ts b/src/data/moves/move-utils.ts index a0baf7ff9b7..241144599e5 100644 --- a/src/data/moves/move-utils.ts +++ b/src/data/moves/move-utils.ts @@ -27,6 +27,28 @@ export function isFieldTargeted(move: Move): boolean { return false; } +/** + * Determine whether a move is a spread move. + * + * @param move - The {@linkcode Move} to check + * @returns Whether {@linkcode move} is spread-targeted. + * @remarks + * Examples include: + * - Moves targeting all adjacent Pokemon (like Surf) + * - Moves targeting all adjacent enemies (like Air Cutter) + */ + +export function isSpreadMove(move: Move): boolean { + switch (move.moveTarget) { + case MoveTarget.ALL_ENEMIES: + case MoveTarget.ALL_NEAR_ENEMIES: + case MoveTarget.ALL_OTHERS: + case MoveTarget.ALL_NEAR_OTHERS: + return true; + } + return false; +} + export function getMoveTargets(user: Pokemon, move: MoveId, replaceTarget?: MoveTarget): MoveTargetSet { const variableTarget = new NumberHolder(0); user.getOpponents(false).forEach(p => applyMoveAttrs("VariableTargetAttr", user, p, allMoves[move], variableTarget)); diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 653fcc1d0e6..098a2febf77 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -25,6 +25,7 @@ import { getBerryEffectFunc } from "#data/berry"; import { applyChallenges } from "#data/challenge"; import { allAbilities, allHeldItems, allMoves } from "#data/data-lists"; import { SpeciesFormChangeRevertWeatherFormTrigger } from "#data/form-change-triggers"; +import { DelayedAttackTag } from "#data/positional-tags/positional-tag"; import { getNonVolatileStatusEffects, getStatusEffectHealText, @@ -54,6 +55,7 @@ import { MoveFlags } from "#enums/move-flags"; import { MoveTarget } from "#enums/move-target"; import { MultiHitType } from "#enums/multi-hit-type"; import { PokemonType } from "#enums/pokemon-type"; +import { PositionalTagType } from "#enums/positional-tag-type"; import { SpeciesId } from "#enums/species-id"; import { BATTLE_STATS, @@ -83,8 +85,9 @@ import type { AttackMoveResult } from "#types/attack-move-result"; import type { Localizable } from "#types/locales"; import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString } from "#types/move-types"; import type { TurnMove } from "#types/turn-move"; -import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue, toReadableString } from "#utils/common"; +import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common"; import { getEnumValues } from "#utils/enums"; +import { toTitleCase } from "#utils/strings"; import i18next from "i18next"; /** @@ -418,9 +421,8 @@ export abstract class Move implements Localizable { /** * Sets the {@linkcode MoveFlags.MAKES_CONTACT} flag for the calling Move - * @param setFlag Default `true`, set to `false` if the move doesn't make contact - * @see {@linkcode AbilityId.STATIC} - * @returns The {@linkcode Move} that called this function + * @param setFlag - Whether the move should make contact; default `true` + * @returns `this` */ makesContact(setFlag: boolean = true): this { this.setFlag(MoveFlags.MAKES_CONTACT, setFlag); @@ -804,16 +806,14 @@ export abstract class Move implements Localizable { } const power = new NumberHolder(this.power); + + applyMoveAttrs("VariablePowerAttr", source, target, this, power); + const typeChangeMovePowerMultiplier = new NumberHolder(1); const typeChangeHolder = new NumberHolder(this.type); applyAbAttrs("MoveTypeChangeAbAttr", {pokemon: source, opponent: target, move: this, simulated: true, moveType: typeChangeHolder, power: typeChangeMovePowerMultiplier}); - const sourceTeraType = source.getTeraType(); - if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !source.heldItemManager.hasItem(HeldItemId.MULTI_LENS)) { - power.value = 60; - } - const abAttrParams: PreAttackModifyPowerAbAttrParams = { pokemon: source, opponent: target, @@ -828,6 +828,19 @@ export abstract class Move implements Localizable { applyAbAttrs("AllyMoveCategoryPowerBoostAbAttr", {...abAttrParams, pokemon: ally}); } + // Non-priority, single-hit moves of the user's Tera Type are always a bare minimum of 60 power + const sourceTeraType = source.getTeraType(); + if ( + source.isTerastallized + && sourceTeraType === this.type + && power.value < 60 + && this.priority <= 0 + && !this.hasAttr("MultiHitAttr") + && !source.heldItemManager.hasItem(HeldItemId.MULTI_LENS) + ) { + power.value = 60; + } + const fieldAuras = new Set( globalScene.getField(true) .map((p) => p.getAbilityAttrs("FieldMoveTypePowerBoostAbAttr").filter(attr => { @@ -851,7 +864,6 @@ export abstract class Move implements Localizable { power.value *= typeBoost.boostValue; } - applyMoveAttrs("VariablePowerAttr", source, target, this, power); if (!this.hasAttr("TypelessAttr")) { globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power); @@ -3112,54 +3124,110 @@ export class OverrideMoveEffectAttr extends MoveAttr { * Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called. */ declare private _: never; - override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + /** + * Apply the move attribute to override other effects of this move. + * @param user - The {@linkcode Pokemon} using the move + * @param target - The {@linkcode Pokemon} targeted by the move + * @param move - The {@linkcode Move} being used + * @param args - + * `[0]`: A {@linkcode BooleanHolder} containing whether move effects were successfully overridden; should be set to `true` on success \ + * `[1]`: The {@linkcode MoveUseMode} dictating how this move was used. + * @returns `true` if the move effect was successfully overridden. + */ + public override apply(_user: Pokemon, _target: Pokemon, _move: Move, _args: [overridden: BooleanHolder, useMode: MoveUseMode]): boolean { return true; } } +/** Abstract class for moves that add {@linkcode PositionalTag}s to the field. */ +abstract class AddPositionalTagAttr extends OverrideMoveEffectAttr { + protected abstract readonly tagType: PositionalTagType; + + public override getCondition(): MoveConditionFunc { + // Check the arena if another similar positional tag is active and affecting the same slot + return (_user, target, move) => globalScene.arena.positionalTagManager.canAddTag(this.tagType, target.getBattlerIndex()) + } +} + /** - * Attack Move that doesn't hit the turn it is played and doesn't allow for multiple - * uses on the same target. Examples are Future Sight or Doom Desire. - * @extends OverrideMoveEffectAttr - * @param tagType The {@linkcode ArenaTagType} that will be placed on the field when the move is used - * @param chargeAnim The {@linkcode ChargeAnim | Charging Animation} used for the move - * @param chargeText The text to display when the move is used + * Attribute to implement delayed attacks, such as {@linkcode MoveId.FUTURE_SIGHT} or {@linkcode MoveId.DOOM_DESIRE}. + * Delays the attack's effect with a {@linkcode DelayedAttackTag}, + * activating against the given slot after the given turn count has elapsed. */ export class DelayedAttackAttr extends OverrideMoveEffectAttr { - public tagType: ArenaTagType; public chargeAnim: ChargeAnim; private chargeText: string; - constructor(tagType: ArenaTagType, chargeAnim: ChargeAnim, chargeText: string) { + /** + * @param chargeAnim - The {@linkcode ChargeAnim | charging animation} used for the move's charging phase. + * @param chargeKey - The `i18next` locales **key** to show when the delayed attack is used. + * In the displayed text, `{{pokemonName}}` will be populated with the user's name. + */ + constructor(chargeAnim: ChargeAnim, chargeKey: string) { super(); - this.tagType = tagType; this.chargeAnim = chargeAnim; - this.chargeText = chargeText; + this.chargeText = chargeKey; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - // Edge case for the move applied on a pokemon that has fainted - if (!target) { - return true; + public override apply(user: Pokemon, target: Pokemon, move: Move, args: [overridden: BooleanHolder, useMode: MoveUseMode]): boolean { + const useMode = args[1]; + if (useMode === MoveUseMode.DELAYED_ATTACK) { + // don't trigger if already queueing an indirect attack + return false; } - const overridden = args[0] as BooleanHolder; - const virtual = args[1] as boolean; + const overridden = args[0]; + overridden.value = true; - if (!virtual) { - overridden.value = true; - globalScene.phaseManager.unshiftNew("MoveAnimPhase", new MoveChargeAnim(this.chargeAnim, move.id, user)); - globalScene.phaseManager.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user))); - user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER, useMode: MoveUseMode.NORMAL }); - const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - globalScene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex()); - } else { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:tookMoveAttack", { pokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(target.id) ?? undefined), moveName: move.name })); - } + // Display the move animation to foresee an attack + globalScene.phaseManager.unshiftNew("MoveAnimPhase", new MoveChargeAnim(this.chargeAnim, move.id, user)); + globalScene.phaseManager.queueMessage( + i18next.t( + this.chargeText, + { pokemonName: getPokemonNameWithAffix(user) } + ) + ) + user.pushMoveHistory({move: move.id, targets: [target.getBattlerIndex()], result: MoveResult.OTHER, useMode, turn: globalScene.currentBattle.turn}) + user.pushMoveHistory({move: move.id, targets: [target.getBattlerIndex()], result: MoveResult.OTHER, useMode, turn: globalScene.currentBattle.turn}) + // Queue up an attack on the given slot. + globalScene.arena.positionalTagManager.addTag({ + tagType: PositionalTagType.DELAYED_ATTACK, + sourceId: user.id, + targetIndex: target.getBattlerIndex(), + sourceMove: move.id, + turnCount: 3 + }) return true; } + + public override getCondition(): MoveConditionFunc { + // Check the arena if another similar attack is active and affecting the same slot + return (_user, target) => globalScene.arena.positionalTagManager.canAddTag(PositionalTagType.DELAYED_ATTACK, target.getBattlerIndex()) + } +} + +/** + * Attribute to queue a {@linkcode WishTag} to activate in 2 turns. + * The tag whill heal + */ +export class WishAttr extends MoveEffectAttr { + public override apply(user: Pokemon, target: Pokemon, _move: Move): boolean { + globalScene.arena.positionalTagManager.addTag({ + tagType: PositionalTagType.WISH, + healHp: toDmgValue(user.getMaxHp() / 2), + targetIndex: target.getBattlerIndex(), + turnCount: 2, + pokemonName: getPokemonNameWithAffix(user), + }); + return true; + } + + public override getCondition(): MoveConditionFunc { + // Check the arena if another wish is active and affecting the same slot + return (_user, target) => globalScene.arena.positionalTagManager.canAddTag(PositionalTagType.WISH, target.getBattlerIndex()) + } } /** @@ -3177,8 +3245,8 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { * @param user the {@linkcode Pokemon} using this move * @param target n/a * @param move the {@linkcode Move} being used - * @param args - * - [0] a {@linkcode BooleanHolder} indicating whether the move's base + * @param args - + * `[0]`: A {@linkcode BooleanHolder} indicating whether the move's base * effects should be overridden this turn. * @returns `true` if base move effects were overridden; `false` otherwise */ @@ -3565,8 +3633,7 @@ export class CutHpStatStageBoostAttr extends StatStageChangeAttr { /** * Attribute implementing the stat boosting effect of {@link https://bulbapedia.bulbagarden.net/wiki/Order_Up_(move) | Order Up}. * If the user has a Pokemon with {@link https://bulbapedia.bulbagarden.net/wiki/Commander_(Ability) | Commander} in their mouth, - * one of the user's stats are increased by 1 stage, depending on the "commanding" Pokemon's form. This effect does not respect - * effect chance, but Order Up itself may be boosted by Sheer Force. + * one of the user's stats are increased by 1 stage, depending on the "commanding" Pokemon's form. */ export class OrderUpStatBoostAttr extends MoveEffectAttr { constructor() { @@ -8124,7 +8191,7 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr { } const type = validTypes[user.randBattleSeedInt(validTypes.length)]; user.summonData.types = [ type ]; - globalScene.phaseManager.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: toReadableString(PokemonType[type]) })); + globalScene.phaseManager.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: toTitleCase(PokemonType[type]) })); user.updateInfo(); return true; @@ -9191,9 +9258,12 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1) .ballBombMove(), new AttackMove(MoveId.FUTURE_SIGHT, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 2) - .partial() // cannot be used on multiple Pokemon on the same side in a double battle, hits immediately when called by Metronome/etc, should not apply abilities or held items if user is off the field + .attr(DelayedAttackAttr, ChargeAnim.FUTURE_SIGHT_CHARGING, "moveTriggers:foresawAnAttack") .ignoresProtect() - .attr(DelayedAttackAttr, ArenaTagType.FUTURE_SIGHT, ChargeAnim.FUTURE_SIGHT_CHARGING, i18next.t("moveTriggers:foresawAnAttack", { pokemonName: "{USER}" })), + /* + * Should not apply abilities or held items if user is off the field + */ + .edgeCase(), new AttackMove(MoveId.ROCK_SMASH, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, 50, 0, 2) .attr(StatStageChangeAttr, [ Stat.DEF ], -1), new AttackMove(MoveId.WHIRLPOOL, PokemonType.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 2) @@ -9214,7 +9284,7 @@ export function initMoves() { new SelfStatusMove(MoveId.STOCKPILE, PokemonType.NORMAL, -1, 20, -1, 0, 3) .condition(user => (user.getTag(StockpilingTag)?.stockpiledCount ?? 0) < 3) .attr(AddBattlerTagAttr, BattlerTagType.STOCKPILING, true), - new AttackMove(MoveId.SPIT_UP, PokemonType.NORMAL, MoveCategory.SPECIAL, -1, -1, 10, -1, 0, 3) + new AttackMove(MoveId.SPIT_UP, PokemonType.NORMAL, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 3) .condition(hasStockpileStacksCondition) .attr(SpitUpPowerAttr, 100) .attr(RemoveBattlerTagAttr, [ BattlerTagType.STOCKPILING ], true), @@ -9279,8 +9349,8 @@ export function initMoves() { .ignoresSubstitute() .attr(AbilityCopyAttr), new SelfStatusMove(MoveId.WISH, PokemonType.NORMAL, -1, 10, -1, 0, 3) - .triageMove() - .attr(AddArenaTagAttr, ArenaTagType.WISH, 2, true), + .attr(WishAttr) + .triageMove(), new SelfStatusMove(MoveId.ASSIST, PokemonType.NORMAL, -1, 20, -1, 0, 3) .attr(RandomMovesetMoveAttr, invalidAssistMoves, true), new SelfStatusMove(MoveId.INGRAIN, PokemonType.GRASS, -1, 20, -1, 0, 3) @@ -9457,7 +9527,7 @@ export function initMoves() { new AttackMove(MoveId.SAND_TOMB, PokemonType.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 3) .attr(TrapAttr, BattlerTagType.SAND_TOMB) .makesContact(false), - new AttackMove(MoveId.SHEER_COLD, PokemonType.ICE, MoveCategory.SPECIAL, 200, 20, 5, -1, 0, 3) + new AttackMove(MoveId.SHEER_COLD, PokemonType.ICE, MoveCategory.SPECIAL, 200, 30, 5, -1, 0, 3) .attr(IceNoEffectTypeAttr) .attr(OneHitKOAttr) .attr(SheerColdAccuracyAttr), @@ -9529,9 +9599,12 @@ export function initMoves() { .attr(ConfuseAttr) .pulseMove(), new AttackMove(MoveId.DOOM_DESIRE, PokemonType.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 3) - .partial() // cannot be used on multiple Pokemon on the same side in a double battle, hits immediately when called by Metronome/etc, should not apply abilities or held items if user is off the field + .attr(DelayedAttackAttr, ChargeAnim.DOOM_DESIRE_CHARGING, "moveTriggers:choseDoomDesireAsDestiny") .ignoresProtect() - .attr(DelayedAttackAttr, ArenaTagType.DOOM_DESIRE, ChargeAnim.DOOM_DESIRE_CHARGING, i18next.t("moveTriggers:choseDoomDesireAsDestiny", { pokemonName: "{USER}" })), + /* + * Should not apply abilities or held items if user is off the field + */ + .edgeCase(), new AttackMove(MoveId.PSYCHO_BOOST, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true), new SelfStatusMove(MoveId.ROOST, PokemonType.FLYING, -1, 5, -1, 0, 4) @@ -10379,7 +10452,7 @@ export function initMoves() { .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ]) .makesContact(false) .target(MoveTarget.ALL_NEAR_ENEMIES), - new AttackMove(MoveId.THOUSAND_WAVES, PokemonType.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6) + new AttackMove(MoveId.THOUSAND_WAVES, PokemonType.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, 100, 0, 6) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true) .makesContact(false) .target(MoveTarget.ALL_NEAR_ENEMIES), @@ -10786,7 +10859,7 @@ export function initMoves() { new SelfStatusMove(MoveId.NO_RETREAT, PokemonType.FIGHTING, -1, 5, -1, 0, 8) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true) .attr(AddBattlerTagAttr, BattlerTagType.NO_RETREAT, true, false) - .condition((user, target, move) => user.getTag(TrappedTag)?.sourceMove !== MoveId.NO_RETREAT), // fails if the user is currently trapped by No Retreat + .condition((user, target, move) => user.getTag(TrappedTag)?.tagType !== BattlerTagType.NO_RETREAT), // fails if the user is currently trapped by No Retreat new StatusMove(MoveId.TAR_SHOT, PokemonType.ROCK, 100, 15, -1, 0, 8) .attr(StatStageChangeAttr, [ Stat.SPD ], -1) .attr(AddBattlerTagAttr, BattlerTagType.TAR_SHOT, false) @@ -10914,7 +10987,8 @@ export function initMoves() { new StatusMove(MoveId.LIFE_DEW, PokemonType.WATER, -1, 10, -1, 0, 8) .attr(HealAttr, 0.25, true, false) .target(MoveTarget.USER_AND_ALLIES) - .ignoresProtect(), + .ignoresProtect() + .triageMove(), new SelfStatusMove(MoveId.OBSTRUCT, PokemonType.DARK, 100, 10, -1, 4, 8) .attr(ProtectAttr, BattlerTagType.OBSTRUCT) .condition(failIfLastCondition), @@ -10992,7 +11066,8 @@ export function initMoves() { new StatusMove(MoveId.JUNGLE_HEALING, PokemonType.GRASS, -1, 10, -1, 0, 8) .attr(HealAttr, 0.25, true, false) .attr(HealStatusEffectAttr, false, getNonVolatileStatusEffects()) - .target(MoveTarget.USER_AND_ALLIES), + .target(MoveTarget.USER_AND_ALLIES) + .triageMove(), new AttackMove(MoveId.WICKED_BLOW, PokemonType.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8) .attr(CritOnlyAttr) .punchingMove(), @@ -11220,7 +11295,7 @@ export function initMoves() { .makesContact(false), new AttackMove(MoveId.LUMINA_CRASH, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9) .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2), - new AttackMove(MoveId.ORDER_UP, PokemonType.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9) + new AttackMove(MoveId.ORDER_UP, PokemonType.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 9) .attr(OrderUpStatBoostAttr) .makesContact(false), new AttackMove(MoveId.JET_PUNCH, PokemonType.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9) @@ -11403,7 +11478,7 @@ export function initMoves() { .attr(IvyCudgelTypeAttr) .attr(HighCritAttr) .makesContact(false), - new ChargingAttackMove(MoveId.ELECTRO_SHOT, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 130, 100, 10, 100, 0, 9) + new ChargingAttackMove(MoveId.ELECTRO_SHOT, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 130, 100, 10, -1, 0, 9) .chargeText(i18next.t("moveTriggers:absorbedElectricity", { pokemonName: "{USER}" })) .chargeAttr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) .chargeAttr(WeatherInstantChargeAttr, [ WeatherType.RAIN, WeatherType.HEAVY_RAIN ]), diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index c296bd36f73..bb9c32c8d73 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -36,6 +36,7 @@ import { addPokemonDataToDexAndValidateAchievements } from "#mystery-encounters/ import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; +import { PartySizeRequirement } from "#mystery-encounters/mystery-encounter-requirements"; import { PokemonData } from "#system/pokemon-data"; import { MusicPreference } from "#system/settings"; import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; @@ -148,7 +149,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil return true; }) .withOption( - MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) + MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) + .withSceneRequirement(new PartySizeRequirement([2, 6], true)) // Requires 2 valid party members .withHasDexProgress(true) .withDialogue({ buttonLabel: `${namespace}:option.1.label`, @@ -250,7 +252,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil .build(), ) .withOption( - MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) + MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) + .withSceneRequirement(new PartySizeRequirement([2, 6], true)) // Requires 2 valid party members .withHasDexProgress(true) .withDialogue({ buttonLabel: `${namespace}:option.2.label`, diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index 6ee1e198b39..5239da1512b 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -59,7 +59,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter = MysteryEncounterBui ) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) - .withSceneRequirement(new WaveModulusRequirement([1, 2, 3], 10)) // Must be in first 3 waves after boss wave + .withSceneRequirement(new WaveModulusRequirement([2, 3, 4], 10)) // Must be in first 3 waves after boss wave .withSceneRequirement(new MoneyRequirement(0, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost .withAutoHideIntroVisuals(false) .withCatchAllowed(true) diff --git a/src/data/mystery-encounters/mystery-encounter-dialogue.ts b/src/data/mystery-encounters/mystery-encounter-dialogue.ts index 42383940755..385ccb5c246 100644 --- a/src/data/mystery-encounters/mystery-encounter-dialogue.ts +++ b/src/data/mystery-encounters/mystery-encounter-dialogue.ts @@ -1,4 +1,4 @@ -import type { TextStyle } from "#ui/text"; +import type { TextStyle } from "#enums/text-style"; export class TextDisplay { speaker?: string; diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index a3cd81db201..522a62d2def 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -25,7 +25,8 @@ import { StatusEffectRequirement, WaveRangeRequirement, } from "#mystery-encounters/mystery-encounter-requirements"; -import { capitalizeFirstLetter, coerceArray, isNullOrUndefined, randSeedInt } from "#utils/common"; +import { coerceArray, isNullOrUndefined, randSeedInt } from "#utils/common"; +import { capitalizeFirstLetter } from "#utils/strings"; export interface EncounterStartOfBattleEffect { sourcePokemon?: Pokemon; diff --git a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts index 4f4af94a88d..1ae0659b29e 100644 --- a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts @@ -1,6 +1,6 @@ import { globalScene } from "#app/global-scene"; +import type { TextStyle } from "#enums/text-style"; import { UiTheme } from "#enums/ui-theme"; -import type { TextStyle } from "#ui/text"; import { getTextWithColors } from "#ui/text"; import { isNullOrUndefined } from "#utils/common"; import i18next from "i18next"; diff --git a/src/data/nature.ts b/src/data/nature.ts index 4f4e627daf3..b085faebb80 100644 --- a/src/data/nature.ts +++ b/src/data/nature.ts @@ -1,8 +1,9 @@ import { Nature } from "#enums/nature"; import { EFFECTIVE_STATS, getShortenedStatKey, Stat } from "#enums/stat"; +import { TextStyle } from "#enums/text-style"; import { UiTheme } from "#enums/ui-theme"; -import { getBBCodeFrag, TextStyle } from "#ui/text"; -import { toReadableString } from "#utils/common"; +import { getBBCodeFrag } from "#ui/text"; +import { toTitleCase } from "#utils/strings"; import i18next from "i18next"; export function getNatureName( @@ -12,7 +13,7 @@ export function getNatureName( ignoreBBCode = false, uiTheme: UiTheme = UiTheme.DEFAULT, ): string { - let ret = toReadableString(Nature[nature]); + let ret = toTitleCase(Nature[nature]); //Translating nature if (i18next.exists(`nature:${ret}`)) { ret = i18next.t(`nature:${ret}` as any); diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 140b03e6d4c..7bfe02d9086 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -12,14 +12,13 @@ import { pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves, } from "#balance/pokemon-level-moves"; -import { POKERUS_STARTER_COUNT, speciesStarterCosts } from "#balance/starters"; -import { allSpecies } from "#data/data-lists"; -import { GrowthRate } from "#data/exp"; +import { speciesStarterCosts } from "#balance/starters"; +import type { GrowthRate } from "#data/exp"; import { Gender } from "#data/gender"; import { AbilityId } from "#enums/ability-id"; import { DexAttr } from "#enums/dex-attr"; import { PartyMemberStrength } from "#enums/party-member-strength"; -import { PokemonType } from "#enums/pokemon-type"; +import type { PokemonType } from "#enums/pokemon-type"; import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesId } from "#enums/species-id"; import type { Stat } from "#enums/stat"; @@ -29,15 +28,9 @@ import type { Variant, VariantSet } from "#sprites/variant"; import { populateVariantColorCache, variantColorCache, variantData } from "#sprites/variant"; import type { StarterMoveset } from "#system/game-data"; import type { Localizable } from "#types/locales"; -import { - capitalizeString, - isNullOrUndefined, - randSeedFloat, - randSeedGauss, - randSeedInt, - randSeedItem, -} from "#utils/common"; +import { isNullOrUndefined, randSeedFloat, randSeedGauss, randSeedInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; +import { toCamelCase, toPascalCase } from "#utils/strings"; import { argbFromRgba, QuantizerCelebi, rgbaFromArgb } from "@material/material-color-utilities"; import i18next from "i18next"; @@ -80,83 +73,6 @@ export const normalForm: SpeciesId[] = [ SpeciesId.CALYREX, ]; -export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): PokemonSpeciesForm { - const retSpecies: PokemonSpecies = - species >= 2000 - ? allSpecies.find(s => s.speciesId === species)! // TODO: is the bang correct? - : allSpecies[species - 1]; - if (formIndex < retSpecies.forms?.length) { - return retSpecies.forms[formIndex]; - } - return retSpecies; -} - -export function getFusedSpeciesName(speciesAName: string, speciesBName: string): string { - const fragAPattern = /([a-z]{2}.*?[aeiou(?:y$)\-']+)(.*?)$/i; - const fragBPattern = /([a-z]{2}.*?[aeiou(?:y$)\-'])(.*?)$/i; - - const [speciesAPrefixMatch, speciesBPrefixMatch] = [speciesAName, speciesBName].map(n => /^(?:[^ ]+) /.exec(n)); - const [speciesAPrefix, speciesBPrefix] = [speciesAPrefixMatch, speciesBPrefixMatch].map(m => (m ? m[0] : "")); - - if (speciesAPrefix) { - speciesAName = speciesAName.slice(speciesAPrefix.length); - } - if (speciesBPrefix) { - speciesBName = speciesBName.slice(speciesBPrefix.length); - } - - const [speciesASuffixMatch, speciesBSuffixMatch] = [speciesAName, speciesBName].map(n => / (?:[^ ]+)$/.exec(n)); - const [speciesASuffix, speciesBSuffix] = [speciesASuffixMatch, speciesBSuffixMatch].map(m => (m ? m[0] : "")); - - if (speciesASuffix) { - speciesAName = speciesAName.slice(0, -speciesASuffix.length); - } - if (speciesBSuffix) { - speciesBName = speciesBName.slice(0, -speciesBSuffix.length); - } - - const splitNameA = speciesAName.split(/ /g); - const splitNameB = speciesBName.split(/ /g); - - const fragAMatch = fragAPattern.exec(speciesAName); - const fragBMatch = fragBPattern.exec(speciesBName); - - let fragA: string; - let fragB: string; - - fragA = splitNameA.length === 1 ? (fragAMatch ? fragAMatch[1] : speciesAName) : splitNameA[splitNameA.length - 1]; - - if (splitNameB.length === 1) { - if (fragBMatch) { - const lastCharA = fragA.slice(fragA.length - 1); - const prevCharB = fragBMatch[1].slice(fragBMatch.length - 1); - fragB = (/[-']/.test(prevCharB) ? prevCharB : "") + fragBMatch[2] || prevCharB; - if (lastCharA === fragB[0]) { - if (/[aiu]/.test(lastCharA)) { - fragB = fragB.slice(1); - } else { - const newCharMatch = new RegExp(`[^${lastCharA}]`).exec(fragB); - if (newCharMatch?.index !== undefined && newCharMatch.index > 0) { - fragB = fragB.slice(newCharMatch.index); - } - } - } - } else { - fragB = speciesBName; - } - } else { - fragB = splitNameB[splitNameB.length - 1]; - } - - if (splitNameA.length > 1) { - fragA = `${splitNameA.slice(0, splitNameA.length - 1).join(" ")} ${fragA}`; - } - - fragB = `${fragB.slice(0, 1).toLowerCase()}${fragB.slice(1)}`; - - return `${speciesAPrefix || speciesBPrefix}${fragA}${fragB}${speciesBSuffix || speciesASuffix}`; -} - export type PokemonSpeciesFilter = (species: PokemonSpecies) => boolean; export abstract class PokemonSpeciesForm { @@ -904,14 +820,14 @@ export class PokemonSpecies extends PokemonSpeciesForm implements Localizable { * @returns the pokemon-form locale key for the single form name ("Alolan Form", "Eternal Flower" etc) */ getFormNameToDisplay(formIndex = 0, append = false): string { - const formKey = this.forms?.[formIndex!]?.formKey; - const formText = capitalizeString(formKey, "-", false, false) || ""; - const speciesName = capitalizeString(SpeciesId[this.speciesId], "_", true, false); + const formKey = this.forms[formIndex]?.formKey ?? ""; + const formText = toPascalCase(formKey); + const speciesName = toCamelCase(SpeciesId[this.speciesId]); let ret = ""; const region = this.getRegion(); if (this.speciesId === SpeciesId.ARCEUS) { - ret = i18next.t(`pokemonInfo:Type.${formText?.toUpperCase()}`); + ret = i18next.t(`pokemonInfo:Type.${formText.toUpperCase()}`); } else if ( [ SpeciesFormKey.MEGA, @@ -937,7 +853,7 @@ export class PokemonSpecies extends PokemonSpeciesForm implements Localizable { if (i18next.exists(i18key)) { ret = i18next.t(i18key); } else { - const rootSpeciesName = capitalizeString(SpeciesId[this.getRootSpeciesId()], "_", true, false); + const rootSpeciesName = toCamelCase(SpeciesId[this.getRootSpeciesId()]); const i18RootKey = `pokemonForm:${rootSpeciesName}${formText}`; ret = i18next.exists(i18RootKey) ? i18next.t(i18RootKey) : formText; } @@ -1405,1804 +1321,3 @@ export class PokemonForm extends PokemonSpeciesForm { return this.formSpriteKey !== null ? this.formSpriteKey : this.formKey; } } - -/** - * Method to get the daily list of starters with Pokerus. - * @returns A list of starters with Pokerus - */ -export function getPokerusStarters(): PokemonSpecies[] { - const pokerusStarters: PokemonSpecies[] = []; - const date = new Date(); - date.setUTCHours(0, 0, 0, 0); - globalScene.executeWithSeedOffset( - () => { - while (pokerusStarters.length < POKERUS_STARTER_COUNT) { - const randomSpeciesId = Number.parseInt(randSeedItem(Object.keys(speciesStarterCosts)), 10); - const species = getPokemonSpecies(randomSpeciesId); - if (!pokerusStarters.includes(species)) { - pokerusStarters.push(species); - } - } - }, - 0, - date.getTime().toString(), - ); - return pokerusStarters; -} - -// biome-ignore format: manually formatted -export function initSpecies() { - allSpecies.push( - new PokemonSpecies(SpeciesId.BULBASAUR, 1, false, false, false, "Seed Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.7, 6.9, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CHLOROPHYLL, 318, 45, 49, 49, 65, 65, 45, 45, 50, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.IVYSAUR, 1, false, false, false, "Seed Pokémon", PokemonType.GRASS, PokemonType.POISON, 1, 13, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CHLOROPHYLL, 405, 60, 62, 63, 80, 80, 60, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.VENUSAUR, 1, false, false, false, "Seed Pokémon", PokemonType.GRASS, PokemonType.POISON, 2, 100, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CHLOROPHYLL, 525, 80, 82, 83, 100, 100, 80, 45, 50, 263, GrowthRate.MEDIUM_SLOW, 87.5, true, true, - new PokemonForm("Normal", "", PokemonType.GRASS, PokemonType.POISON, 2, 100, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CHLOROPHYLL, 525, 80, 82, 83, 100, 100, 80, 45, 50, 263, true, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.GRASS, PokemonType.POISON, 2.4, 155.5, AbilityId.THICK_FAT, AbilityId.THICK_FAT, AbilityId.THICK_FAT, 625, 80, 100, 123, 122, 120, 80, 45, 50, 263, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.GRASS, PokemonType.POISON, 24, 999.9, AbilityId.EFFECT_SPORE, AbilityId.NONE, AbilityId.EFFECT_SPORE, 625, 120, 122, 90, 108, 105, 80, 45, 50, 263, true), - ), - new PokemonSpecies(SpeciesId.CHARMANDER, 1, false, false, false, "Lizard Pokémon", PokemonType.FIRE, null, 0.6, 8.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SOLAR_POWER, 309, 39, 52, 43, 60, 50, 65, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.CHARMELEON, 1, false, false, false, "Flame Pokémon", PokemonType.FIRE, null, 1.1, 19, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SOLAR_POWER, 405, 58, 64, 58, 80, 65, 80, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.CHARIZARD, 1, false, false, false, "Flame Pokémon", PokemonType.FIRE, PokemonType.FLYING, 1.7, 90.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SOLAR_POWER, 534, 78, 84, 78, 109, 85, 100, 45, 50, 267, GrowthRate.MEDIUM_SLOW, 87.5, false, true, - new PokemonForm("Normal", "", PokemonType.FIRE, PokemonType.FLYING, 1.7, 90.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SOLAR_POWER, 534, 78, 84, 78, 109, 85, 100, 45, 50, 267, false, null, true), - new PokemonForm("Mega X", SpeciesFormKey.MEGA_X, PokemonType.FIRE, PokemonType.DRAGON, 1.7, 110.5, AbilityId.TOUGH_CLAWS, AbilityId.NONE, AbilityId.TOUGH_CLAWS, 634, 78, 130, 111, 130, 85, 100, 45, 50, 267), - new PokemonForm("Mega Y", SpeciesFormKey.MEGA_Y, PokemonType.FIRE, PokemonType.FLYING, 1.7, 100.5, AbilityId.DROUGHT, AbilityId.NONE, AbilityId.DROUGHT, 634, 78, 104, 78, 159, 115, 100, 45, 50, 267), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.FIRE, PokemonType.FLYING, 28, 999.9, AbilityId.BERSERK, AbilityId.NONE, AbilityId.BERSERK, 634, 118, 99, 88, 134, 95, 100, 45, 50, 267), - ), - new PokemonSpecies(SpeciesId.SQUIRTLE, 1, false, false, false, "Tiny Turtle Pokémon", PokemonType.WATER, null, 0.5, 9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.RAIN_DISH, 314, 44, 48, 65, 50, 64, 43, 45, 50, 63, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.WARTORTLE, 1, false, false, false, "Turtle Pokémon", PokemonType.WATER, null, 1, 22.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.RAIN_DISH, 405, 59, 63, 80, 65, 80, 58, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.BLASTOISE, 1, false, false, false, "Shellfish Pokémon", PokemonType.WATER, null, 1.6, 85.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.RAIN_DISH, 530, 79, 83, 100, 85, 105, 78, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, - new PokemonForm("Normal", "", PokemonType.WATER, null, 1.6, 85.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.RAIN_DISH, 530, 79, 83, 100, 85, 105, 78, 45, 50, 265, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.WATER, null, 1.6, 101.1, AbilityId.MEGA_LAUNCHER, AbilityId.NONE, AbilityId.MEGA_LAUNCHER, 630, 79, 103, 120, 135, 115, 78, 45, 50, 265), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.WATER, PokemonType.STEEL, 25, 999.9, AbilityId.SHELL_ARMOR, AbilityId.NONE, AbilityId.SHELL_ARMOR, 630, 119, 108, 125, 105, 110, 63, 45, 50, 265), - ), - new PokemonSpecies(SpeciesId.CATERPIE, 1, false, false, false, "Worm Pokémon", PokemonType.BUG, null, 0.3, 2.9, AbilityId.SHIELD_DUST, AbilityId.NONE, AbilityId.RUN_AWAY, 195, 45, 30, 35, 20, 20, 45, 255, 50, 39, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.METAPOD, 1, false, false, false, "Cocoon Pokémon", PokemonType.BUG, null, 0.7, 9.9, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.SHED_SKIN, 205, 50, 20, 55, 25, 25, 30, 120, 50, 72, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BUTTERFREE, 1, false, false, false, "Butterfly Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.1, 32, AbilityId.COMPOUND_EYES, AbilityId.NONE, AbilityId.TINTED_LENS, 395, 60, 45, 50, 90, 80, 70, 45, 50, 198, GrowthRate.MEDIUM_FAST, 50, true, true, - new PokemonForm("Normal", "", PokemonType.BUG, PokemonType.FLYING, 1.1, 32, AbilityId.COMPOUND_EYES, AbilityId.NONE, AbilityId.TINTED_LENS, 395, 60, 45, 50, 90, 80, 70, 45, 50, 198, true, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.BUG, PokemonType.FLYING, 17, 999.9, AbilityId.COMPOUND_EYES, AbilityId.NONE, AbilityId.COMPOUND_EYES, 495, 80, 40, 75, 120, 95, 85, 45, 50, 198, true), - ), - new PokemonSpecies(SpeciesId.WEEDLE, 1, false, false, false, "Hairy Bug Pokémon", PokemonType.BUG, PokemonType.POISON, 0.3, 3.2, AbilityId.SHIELD_DUST, AbilityId.NONE, AbilityId.RUN_AWAY, 195, 40, 35, 30, 20, 20, 50, 255, 70, 39, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.KAKUNA, 1, false, false, false, "Cocoon Pokémon", PokemonType.BUG, PokemonType.POISON, 0.6, 10, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.SHED_SKIN, 205, 45, 25, 50, 25, 25, 35, 120, 70, 72, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BEEDRILL, 1, false, false, false, "Poison Bee Pokémon", PokemonType.BUG, PokemonType.POISON, 1, 29.5, AbilityId.SWARM, AbilityId.NONE, AbilityId.SNIPER, 395, 65, 90, 40, 45, 80, 75, 45, 70, 198, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.BUG, PokemonType.POISON, 1, 29.5, AbilityId.SWARM, AbilityId.NONE, AbilityId.SNIPER, 395, 65, 90, 40, 45, 80, 75, 45, 70, 198, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.BUG, PokemonType.POISON, 1.4, 40.5, AbilityId.ADAPTABILITY, AbilityId.NONE, AbilityId.ADAPTABILITY, 495, 65, 150, 40, 15, 80, 145, 45, 70, 198), - ), - new PokemonSpecies(SpeciesId.PIDGEY, 1, false, false, false, "Tiny Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 1.8, AbilityId.KEEN_EYE, AbilityId.TANGLED_FEET, AbilityId.BIG_PECKS, 251, 40, 45, 40, 35, 35, 56, 255, 70, 50, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.PIDGEOTTO, 1, false, false, false, "Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.1, 30, AbilityId.KEEN_EYE, AbilityId.TANGLED_FEET, AbilityId.BIG_PECKS, 349, 63, 60, 55, 50, 50, 71, 120, 70, 122, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.PIDGEOT, 1, false, false, false, "Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.5, 39.5, AbilityId.KEEN_EYE, AbilityId.TANGLED_FEET, AbilityId.BIG_PECKS, 479, 83, 80, 75, 70, 70, 101, 45, 70, 240, GrowthRate.MEDIUM_SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.NORMAL, PokemonType.FLYING, 1.5, 39.5, AbilityId.KEEN_EYE, AbilityId.TANGLED_FEET, AbilityId.BIG_PECKS, 479, 83, 80, 75, 70, 70, 101, 45, 70, 240, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.NORMAL, PokemonType.FLYING, 2.2, 50.5, AbilityId.NO_GUARD, AbilityId.NO_GUARD, AbilityId.NO_GUARD, 579, 83, 80, 80, 135, 80, 121, 45, 70, 240), - ), - new PokemonSpecies(SpeciesId.RATTATA, 1, false, false, false, "Mouse Pokémon", PokemonType.NORMAL, null, 0.3, 3.5, AbilityId.RUN_AWAY, AbilityId.GUTS, AbilityId.HUSTLE, 253, 30, 56, 35, 25, 35, 72, 255, 70, 51, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.RATICATE, 1, false, false, false, "Mouse Pokémon", PokemonType.NORMAL, null, 0.7, 18.5, AbilityId.RUN_AWAY, AbilityId.GUTS, AbilityId.HUSTLE, 413, 55, 81, 60, 50, 70, 97, 127, 70, 145, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.SPEAROW, 1, false, false, false, "Tiny Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 2, AbilityId.KEEN_EYE, AbilityId.NONE, AbilityId.SNIPER, 262, 40, 60, 30, 31, 31, 70, 255, 70, 52, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.FEAROW, 1, false, false, false, "Beak Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.2, 38, AbilityId.KEEN_EYE, AbilityId.NONE, AbilityId.SNIPER, 442, 65, 90, 65, 61, 61, 100, 90, 70, 155, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.EKANS, 1, false, false, false, "Snake Pokémon", PokemonType.POISON, null, 2, 6.9, AbilityId.INTIMIDATE, AbilityId.SHED_SKIN, AbilityId.UNNERVE, 288, 35, 60, 44, 40, 54, 55, 255, 70, 58, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ARBOK, 1, false, false, false, "Cobra Pokémon", PokemonType.POISON, null, 3.5, 65, AbilityId.INTIMIDATE, AbilityId.SHED_SKIN, AbilityId.UNNERVE, 448, 60, 95, 69, 65, 79, 80, 90, 70, 157, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PIKACHU, 1, false, false, false, "Mouse Pokémon", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, GrowthRate.MEDIUM_FAST, 50, true, true, - new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 320, 35, 55, 40, 50, 50, 90, 190, 50, 112, true, null, true), - new PokemonForm("Partner", "partner", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), - new PokemonForm("Cosplay", "cosplay", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom - new PokemonForm("Cool Cosplay", "cool-cosplay", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom - new PokemonForm("Beauty Cosplay", "beauty-cosplay", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom - new PokemonForm("Cute Cosplay", "cute-cosplay", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom - new PokemonForm("Smart Cosplay", "smart-cosplay", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom - new PokemonForm("Tough Cosplay", "tough-cosplay", PokemonType.ELECTRIC, null, 0.4, 6, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.ELECTRIC, null, 21, 999.9, AbilityId.LIGHTNING_ROD, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 530, 125, 95, 60, 90, 70, 90, 190, 50, 112), //+100 BST from Partner Form - ), - new PokemonSpecies(SpeciesId.RAICHU, 1, false, false, false, "Mouse Pokémon", PokemonType.ELECTRIC, null, 0.8, 30, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 485, 60, 90, 55, 90, 80, 110, 75, 50, 243, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.SANDSHREW, 1, false, false, false, "Mouse Pokémon", PokemonType.GROUND, null, 0.6, 12, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.SAND_RUSH, 300, 50, 75, 85, 20, 30, 40, 255, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SANDSLASH, 1, false, false, false, "Mouse Pokémon", PokemonType.GROUND, null, 1, 29.5, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.SAND_RUSH, 450, 75, 100, 110, 45, 55, 65, 90, 50, 158, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.NIDORAN_F, 1, false, false, false, "Poison Pin Pokémon", PokemonType.POISON, null, 0.4, 7, AbilityId.POISON_POINT, AbilityId.RIVALRY, AbilityId.HUSTLE, 275, 55, 47, 52, 40, 40, 41, 235, 50, 55, GrowthRate.MEDIUM_SLOW, 0, false), - new PokemonSpecies(SpeciesId.NIDORINA, 1, false, false, false, "Poison Pin Pokémon", PokemonType.POISON, null, 0.8, 20, AbilityId.POISON_POINT, AbilityId.RIVALRY, AbilityId.HUSTLE, 365, 70, 62, 67, 55, 55, 56, 120, 50, 128, GrowthRate.MEDIUM_SLOW, 0, false), - new PokemonSpecies(SpeciesId.NIDOQUEEN, 1, false, false, false, "Drill Pokémon", PokemonType.POISON, PokemonType.GROUND, 1.3, 60, AbilityId.POISON_POINT, AbilityId.RIVALRY, AbilityId.SHEER_FORCE, 505, 90, 92, 87, 75, 85, 76, 45, 50, 253, GrowthRate.MEDIUM_SLOW, 0, false), - new PokemonSpecies(SpeciesId.NIDORAN_M, 1, false, false, false, "Poison Pin Pokémon", PokemonType.POISON, null, 0.5, 9, AbilityId.POISON_POINT, AbilityId.RIVALRY, AbilityId.HUSTLE, 273, 46, 57, 40, 40, 40, 50, 235, 50, 55, GrowthRate.MEDIUM_SLOW, 100, false), - new PokemonSpecies(SpeciesId.NIDORINO, 1, false, false, false, "Poison Pin Pokémon", PokemonType.POISON, null, 0.9, 19.5, AbilityId.POISON_POINT, AbilityId.RIVALRY, AbilityId.HUSTLE, 365, 61, 72, 57, 55, 55, 65, 120, 50, 128, GrowthRate.MEDIUM_SLOW, 100, false), - new PokemonSpecies(SpeciesId.NIDOKING, 1, false, false, false, "Drill Pokémon", PokemonType.POISON, PokemonType.GROUND, 1.4, 62, AbilityId.POISON_POINT, AbilityId.RIVALRY, AbilityId.SHEER_FORCE, 505, 81, 102, 77, 85, 75, 85, 45, 50, 253, GrowthRate.MEDIUM_SLOW, 100, false), - new PokemonSpecies(SpeciesId.CLEFAIRY, 1, false, false, false, "Fairy Pokémon", PokemonType.FAIRY, null, 0.6, 7.5, AbilityId.CUTE_CHARM, AbilityId.MAGIC_GUARD, AbilityId.FRIEND_GUARD, 323, 70, 45, 48, 60, 65, 35, 150, 140, 113, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.CLEFABLE, 1, false, false, false, "Fairy Pokémon", PokemonType.FAIRY, null, 1.3, 40, AbilityId.CUTE_CHARM, AbilityId.MAGIC_GUARD, AbilityId.UNAWARE, 483, 95, 70, 73, 95, 90, 60, 25, 140, 242, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.VULPIX, 1, false, false, false, "Fox Pokémon", PokemonType.FIRE, null, 0.6, 9.9, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.DROUGHT, 299, 38, 41, 40, 50, 65, 65, 190, 50, 60, GrowthRate.MEDIUM_FAST, 25, false), - new PokemonSpecies(SpeciesId.NINETALES, 1, false, false, false, "Fox Pokémon", PokemonType.FIRE, null, 1.1, 19.9, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.DROUGHT, 505, 73, 76, 75, 81, 100, 100, 75, 50, 177, GrowthRate.MEDIUM_FAST, 25, false), - new PokemonSpecies(SpeciesId.JIGGLYPUFF, 1, false, false, false, "Balloon Pokémon", PokemonType.NORMAL, PokemonType.FAIRY, 0.5, 5.5, AbilityId.CUTE_CHARM, AbilityId.COMPETITIVE, AbilityId.FRIEND_GUARD, 270, 115, 45, 20, 45, 25, 20, 170, 50, 95, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.WIGGLYTUFF, 1, false, false, false, "Balloon Pokémon", PokemonType.NORMAL, PokemonType.FAIRY, 1, 12, AbilityId.CUTE_CHARM, AbilityId.COMPETITIVE, AbilityId.FRISK, 435, 140, 70, 45, 85, 50, 45, 50, 50, 218, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.ZUBAT, 1, false, false, false, "Bat Pokémon", PokemonType.POISON, PokemonType.FLYING, 0.8, 7.5, AbilityId.INNER_FOCUS, AbilityId.NONE, AbilityId.INFILTRATOR, 245, 40, 45, 35, 30, 40, 55, 255, 50, 49, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.GOLBAT, 1, false, false, false, "Bat Pokémon", PokemonType.POISON, PokemonType.FLYING, 1.6, 55, AbilityId.INNER_FOCUS, AbilityId.NONE, AbilityId.INFILTRATOR, 455, 75, 80, 70, 65, 75, 90, 90, 50, 159, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.ODDISH, 1, false, false, false, "Weed Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.5, 5.4, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.RUN_AWAY, 320, 45, 50, 55, 75, 65, 30, 255, 50, 64, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.GLOOM, 1, false, false, false, "Weed Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.8, 8.6, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.STENCH, 395, 60, 65, 70, 85, 75, 40, 120, 50, 138, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.VILEPLUME, 1, false, false, false, "Flower Pokémon", PokemonType.GRASS, PokemonType.POISON, 1.2, 18.6, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.EFFECT_SPORE, 490, 75, 80, 85, 110, 90, 50, 45, 50, 245, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.PARAS, 1, false, false, false, "Mushroom Pokémon", PokemonType.BUG, PokemonType.GRASS, 0.3, 5.4, AbilityId.EFFECT_SPORE, AbilityId.DRY_SKIN, AbilityId.DAMP, 285, 35, 70, 55, 45, 55, 25, 190, 70, 57, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PARASECT, 1, false, false, false, "Mushroom Pokémon", PokemonType.BUG, PokemonType.GRASS, 1, 29.5, AbilityId.EFFECT_SPORE, AbilityId.DRY_SKIN, AbilityId.DAMP, 405, 60, 95, 80, 60, 80, 30, 75, 70, 142, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.VENONAT, 1, false, false, false, "Insect Pokémon", PokemonType.BUG, PokemonType.POISON, 1, 30, AbilityId.COMPOUND_EYES, AbilityId.TINTED_LENS, AbilityId.RUN_AWAY, 305, 60, 55, 50, 40, 55, 45, 190, 70, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.VENOMOTH, 1, false, false, false, "Poison Moth Pokémon", PokemonType.BUG, PokemonType.POISON, 1.5, 12.5, AbilityId.SHIELD_DUST, AbilityId.TINTED_LENS, AbilityId.WONDER_SKIN, 450, 70, 65, 60, 90, 75, 90, 75, 70, 158, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DIGLETT, 1, false, false, false, "Mole Pokémon", PokemonType.GROUND, null, 0.2, 0.8, AbilityId.SAND_VEIL, AbilityId.ARENA_TRAP, AbilityId.SAND_FORCE, 265, 10, 55, 25, 35, 45, 95, 255, 50, 53, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DUGTRIO, 1, false, false, false, "Mole Pokémon", PokemonType.GROUND, null, 0.7, 33.3, AbilityId.SAND_VEIL, AbilityId.ARENA_TRAP, AbilityId.SAND_FORCE, 425, 35, 100, 50, 50, 70, 120, 50, 50, 149, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MEOWTH, 1, false, false, false, "Scratch Cat Pokémon", PokemonType.NORMAL, null, 0.4, 4.2, AbilityId.PICKUP, AbilityId.TECHNICIAN, AbilityId.UNNERVE, 290, 40, 45, 35, 40, 40, 90, 255, 50, 58, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.NORMAL, null, 0.4, 4.2, AbilityId.PICKUP, AbilityId.TECHNICIAN, AbilityId.UNNERVE, 290, 40, 45, 35, 40, 40, 90, 255, 50, 58, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.NORMAL, null, 33, 999.9, AbilityId.TECHNICIAN, AbilityId.TECHNICIAN, AbilityId.TECHNICIAN, 540, 115, 110, 65, 65, 70, 115, 255, 50, 58), //+100 BST from Persian - ), - new PokemonSpecies(SpeciesId.PERSIAN, 1, false, false, false, "Classy Cat Pokémon", PokemonType.NORMAL, null, 1, 32, AbilityId.LIMBER, AbilityId.TECHNICIAN, AbilityId.UNNERVE, 440, 65, 70, 60, 65, 65, 115, 90, 50, 154, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PSYDUCK, 1, false, false, false, "Duck Pokémon", PokemonType.WATER, null, 0.8, 19.6, AbilityId.DAMP, AbilityId.CLOUD_NINE, AbilityId.SWIFT_SWIM, 320, 50, 52, 48, 65, 50, 55, 190, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GOLDUCK, 1, false, false, false, "Duck Pokémon", PokemonType.WATER, null, 1.7, 76.6, AbilityId.DAMP, AbilityId.CLOUD_NINE, AbilityId.SWIFT_SWIM, 500, 80, 82, 78, 95, 80, 85, 75, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MANKEY, 1, false, false, false, "Pig Monkey Pokémon", PokemonType.FIGHTING, null, 0.5, 28, AbilityId.VITAL_SPIRIT, AbilityId.ANGER_POINT, AbilityId.DEFIANT, 305, 40, 80, 35, 35, 45, 70, 190, 70, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PRIMEAPE, 1, false, false, false, "Pig Monkey Pokémon", PokemonType.FIGHTING, null, 1, 32, AbilityId.VITAL_SPIRIT, AbilityId.ANGER_POINT, AbilityId.DEFIANT, 455, 65, 105, 60, 60, 70, 95, 75, 70, 159, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GROWLITHE, 1, false, false, false, "Puppy Pokémon", PokemonType.FIRE, null, 0.7, 19, AbilityId.INTIMIDATE, AbilityId.FLASH_FIRE, AbilityId.JUSTIFIED, 350, 55, 70, 45, 70, 50, 60, 190, 50, 70, GrowthRate.SLOW, 75, false), - new PokemonSpecies(SpeciesId.ARCANINE, 1, false, false, false, "Legendary Pokémon", PokemonType.FIRE, null, 1.9, 155, AbilityId.INTIMIDATE, AbilityId.FLASH_FIRE, AbilityId.JUSTIFIED, 555, 90, 110, 80, 100, 80, 95, 75, 50, 194, GrowthRate.SLOW, 75, false), - new PokemonSpecies(SpeciesId.POLIWAG, 1, false, false, false, "Tadpole Pokémon", PokemonType.WATER, null, 0.6, 12.4, AbilityId.WATER_ABSORB, AbilityId.DAMP, AbilityId.SWIFT_SWIM, 300, 40, 50, 40, 40, 40, 90, 255, 50, 60, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.POLIWHIRL, 1, false, false, false, "Tadpole Pokémon", PokemonType.WATER, null, 1, 20, AbilityId.WATER_ABSORB, AbilityId.DAMP, AbilityId.SWIFT_SWIM, 385, 65, 65, 65, 50, 50, 90, 120, 50, 135, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.POLIWRATH, 1, false, false, false, "Tadpole Pokémon", PokemonType.WATER, PokemonType.FIGHTING, 1.3, 54, AbilityId.WATER_ABSORB, AbilityId.DAMP, AbilityId.SWIFT_SWIM, 510, 90, 95, 95, 70, 90, 70, 45, 50, 255, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.ABRA, 1, false, false, false, "Psi Pokémon", PokemonType.PSYCHIC, null, 0.9, 19.5, AbilityId.SYNCHRONIZE, AbilityId.INNER_FOCUS, AbilityId.MAGIC_GUARD, 310, 25, 20, 15, 105, 55, 90, 200, 50, 62, GrowthRate.MEDIUM_SLOW, 75, false), - new PokemonSpecies(SpeciesId.KADABRA, 1, false, false, false, "Psi Pokémon", PokemonType.PSYCHIC, null, 1.3, 56.5, AbilityId.SYNCHRONIZE, AbilityId.INNER_FOCUS, AbilityId.MAGIC_GUARD, 400, 40, 35, 30, 120, 70, 105, 100, 50, 140, GrowthRate.MEDIUM_SLOW, 75, true), - new PokemonSpecies(SpeciesId.ALAKAZAM, 1, false, false, false, "Psi Pokémon", PokemonType.PSYCHIC, null, 1.5, 48, AbilityId.SYNCHRONIZE, AbilityId.INNER_FOCUS, AbilityId.MAGIC_GUARD, 500, 55, 50, 45, 135, 95, 120, 50, 50, 250, GrowthRate.MEDIUM_SLOW, 75, true, true, - new PokemonForm("Normal", "", PokemonType.PSYCHIC, null, 1.5, 48, AbilityId.SYNCHRONIZE, AbilityId.INNER_FOCUS, AbilityId.MAGIC_GUARD, 500, 55, 50, 45, 135, 95, 120, 50, 50, 250, true, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.PSYCHIC, null, 1.2, 48, AbilityId.TRACE, AbilityId.TRACE, AbilityId.TRACE, 600, 55, 50, 65, 175, 105, 150, 50, 50, 250, true), - ), - new PokemonSpecies(SpeciesId.MACHOP, 1, false, false, false, "Superpower Pokémon", PokemonType.FIGHTING, null, 0.8, 19.5, AbilityId.GUTS, AbilityId.NO_GUARD, AbilityId.STEADFAST, 305, 70, 80, 50, 35, 35, 35, 180, 50, 61, GrowthRate.MEDIUM_SLOW, 75, false), - new PokemonSpecies(SpeciesId.MACHOKE, 1, false, false, false, "Superpower Pokémon", PokemonType.FIGHTING, null, 1.5, 70.5, AbilityId.GUTS, AbilityId.NO_GUARD, AbilityId.STEADFAST, 405, 80, 100, 70, 50, 60, 45, 90, 50, 142, GrowthRate.MEDIUM_SLOW, 75, false), - new PokemonSpecies(SpeciesId.MACHAMP, 1, false, false, false, "Superpower Pokémon", PokemonType.FIGHTING, null, 1.6, 130, AbilityId.GUTS, AbilityId.NO_GUARD, AbilityId.STEADFAST, 505, 90, 130, 80, 65, 85, 55, 45, 50, 253, GrowthRate.MEDIUM_SLOW, 75, false, true, - new PokemonForm("Normal", "", PokemonType.FIGHTING, null, 1.6, 130, AbilityId.GUTS, AbilityId.NO_GUARD, AbilityId.STEADFAST, 505, 90, 130, 80, 65, 85, 55, 45, 50, 253, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.FIGHTING, null, 25, 999.9, AbilityId.GUTS, AbilityId.GUTS, AbilityId.GUTS, 605, 120, 170, 85, 75, 90, 65, 45, 50, 253), - ), - new PokemonSpecies(SpeciesId.BELLSPROUT, 1, false, false, false, "Flower Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.7, 4, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.GLUTTONY, 300, 50, 75, 35, 70, 30, 40, 255, 70, 60, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.WEEPINBELL, 1, false, false, false, "Flycatcher Pokémon", PokemonType.GRASS, PokemonType.POISON, 1, 6.4, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.GLUTTONY, 390, 65, 90, 50, 85, 45, 55, 120, 70, 137, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.VICTREEBEL, 1, false, false, false, "Flycatcher Pokémon", PokemonType.GRASS, PokemonType.POISON, 1.7, 15.5, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.GLUTTONY, 490, 80, 105, 65, 100, 70, 70, 45, 70, 245, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.TENTACOOL, 1, false, false, false, "Jellyfish Pokémon", PokemonType.WATER, PokemonType.POISON, 0.9, 45.5, AbilityId.CLEAR_BODY, AbilityId.LIQUID_OOZE, AbilityId.RAIN_DISH, 335, 40, 40, 35, 50, 100, 70, 190, 50, 67, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.TENTACRUEL, 1, false, false, false, "Jellyfish Pokémon", PokemonType.WATER, PokemonType.POISON, 1.6, 55, AbilityId.CLEAR_BODY, AbilityId.LIQUID_OOZE, AbilityId.RAIN_DISH, 515, 80, 70, 65, 80, 120, 100, 60, 50, 180, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.GEODUDE, 1, false, false, false, "Rock Pokémon", PokemonType.ROCK, PokemonType.GROUND, 0.4, 20, AbilityId.ROCK_HEAD, AbilityId.STURDY, AbilityId.SAND_VEIL, 300, 40, 80, 100, 30, 30, 20, 255, 70, 60, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.GRAVELER, 1, false, false, false, "Rock Pokémon", PokemonType.ROCK, PokemonType.GROUND, 1, 105, AbilityId.ROCK_HEAD, AbilityId.STURDY, AbilityId.SAND_VEIL, 390, 55, 95, 115, 45, 45, 35, 120, 70, 137, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.GOLEM, 1, false, false, false, "Megaton Pokémon", PokemonType.ROCK, PokemonType.GROUND, 1.4, 300, AbilityId.ROCK_HEAD, AbilityId.STURDY, AbilityId.SAND_VEIL, 495, 80, 120, 130, 55, 65, 45, 45, 70, 248, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.PONYTA, 1, false, false, false, "Fire Horse Pokémon", PokemonType.FIRE, null, 1, 30, AbilityId.RUN_AWAY, AbilityId.FLASH_FIRE, AbilityId.FLAME_BODY, 410, 50, 85, 55, 65, 65, 90, 190, 50, 82, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.RAPIDASH, 1, false, false, false, "Fire Horse Pokémon", PokemonType.FIRE, null, 1.7, 95, AbilityId.RUN_AWAY, AbilityId.FLASH_FIRE, AbilityId.FLAME_BODY, 500, 65, 100, 70, 80, 80, 105, 60, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SLOWPOKE, 1, false, false, false, "Dopey Pokémon", PokemonType.WATER, PokemonType.PSYCHIC, 1.2, 36, AbilityId.OBLIVIOUS, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 315, 90, 65, 65, 40, 40, 15, 190, 50, 63, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SLOWBRO, 1, false, false, false, "Hermit Crab Pokémon", PokemonType.WATER, PokemonType.PSYCHIC, 1.6, 78.5, AbilityId.OBLIVIOUS, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 490, 95, 75, 110, 100, 80, 30, 75, 50, 172, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.PSYCHIC, 1.6, 78.5, AbilityId.OBLIVIOUS, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 490, 95, 75, 110, 100, 80, 30, 75, 50, 172, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.WATER, PokemonType.PSYCHIC, 2, 120, AbilityId.SHELL_ARMOR, AbilityId.SHELL_ARMOR, AbilityId.SHELL_ARMOR, 590, 95, 75, 180, 130, 80, 30, 75, 50, 172), - ), - new PokemonSpecies(SpeciesId.MAGNEMITE, 1, false, false, false, "Magnet Pokémon", PokemonType.ELECTRIC, PokemonType.STEEL, 0.3, 6, AbilityId.MAGNET_PULL, AbilityId.STURDY, AbilityId.ANALYTIC, 325, 25, 35, 70, 95, 55, 45, 190, 50, 65, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.MAGNETON, 1, false, false, false, "Magnet Pokémon", PokemonType.ELECTRIC, PokemonType.STEEL, 1, 60, AbilityId.MAGNET_PULL, AbilityId.STURDY, AbilityId.ANALYTIC, 465, 50, 60, 95, 120, 70, 70, 60, 50, 163, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.FARFETCHD, 1, false, false, false, "Wild Duck Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.8, 15, AbilityId.KEEN_EYE, AbilityId.INNER_FOCUS, AbilityId.DEFIANT, 377, 52, 90, 55, 58, 62, 60, 45, 50, 132, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DODUO, 1, false, false, false, "Twin Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.4, 39.2, AbilityId.RUN_AWAY, AbilityId.EARLY_BIRD, AbilityId.TANGLED_FEET, 310, 35, 85, 45, 35, 35, 75, 190, 70, 62, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.DODRIO, 1, false, false, false, "Triple Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.8, 85.2, AbilityId.RUN_AWAY, AbilityId.EARLY_BIRD, AbilityId.TANGLED_FEET, 470, 60, 110, 70, 60, 60, 110, 45, 70, 165, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.SEEL, 1, false, false, false, "Sea Lion Pokémon", PokemonType.WATER, null, 1.1, 90, AbilityId.THICK_FAT, AbilityId.HYDRATION, AbilityId.ICE_BODY, 325, 65, 45, 55, 45, 70, 45, 190, 70, 65, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DEWGONG, 1, false, false, false, "Sea Lion Pokémon", PokemonType.WATER, PokemonType.ICE, 1.7, 120, AbilityId.THICK_FAT, AbilityId.HYDRATION, AbilityId.ICE_BODY, 475, 90, 70, 80, 70, 95, 70, 75, 70, 166, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GRIMER, 1, false, false, false, "Sludge Pokémon", PokemonType.POISON, null, 0.9, 30, AbilityId.STENCH, AbilityId.STICKY_HOLD, AbilityId.POISON_TOUCH, 325, 80, 80, 50, 40, 50, 25, 190, 70, 65, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MUK, 1, false, false, false, "Sludge Pokémon", PokemonType.POISON, null, 1.2, 30, AbilityId.STENCH, AbilityId.STICKY_HOLD, AbilityId.POISON_TOUCH, 500, 105, 105, 75, 65, 100, 50, 75, 70, 175, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SHELLDER, 1, false, false, false, "Bivalve Pokémon", PokemonType.WATER, null, 0.3, 4, AbilityId.SHELL_ARMOR, AbilityId.SKILL_LINK, AbilityId.OVERCOAT, 305, 30, 65, 100, 45, 25, 40, 190, 50, 61, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.CLOYSTER, 1, false, false, false, "Bivalve Pokémon", PokemonType.WATER, PokemonType.ICE, 1.5, 132.5, AbilityId.SHELL_ARMOR, AbilityId.SKILL_LINK, AbilityId.OVERCOAT, 525, 50, 95, 180, 85, 45, 70, 60, 50, 184, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.GASTLY, 1, false, false, false, "Gas Pokémon", PokemonType.GHOST, PokemonType.POISON, 1.3, 0.1, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 310, 30, 35, 30, 100, 35, 80, 190, 50, 62, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.HAUNTER, 1, false, false, false, "Gas Pokémon", PokemonType.GHOST, PokemonType.POISON, 1.6, 0.1, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 405, 45, 50, 45, 115, 55, 95, 90, 50, 142, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.GENGAR, 1, false, false, false, "Shadow Pokémon", PokemonType.GHOST, PokemonType.POISON, 1.5, 40.5, AbilityId.CURSED_BODY, AbilityId.NONE, AbilityId.NONE, 500, 60, 65, 60, 130, 75, 110, 45, 50, 250, GrowthRate.MEDIUM_SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.GHOST, PokemonType.POISON, 1.5, 40.5, AbilityId.CURSED_BODY, AbilityId.NONE, AbilityId.NONE, 500, 60, 65, 60, 130, 75, 110, 45, 50, 250, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.GHOST, PokemonType.POISON, 1.4, 40.5, AbilityId.SHADOW_TAG, AbilityId.NONE, AbilityId.NONE, 600, 60, 65, 80, 170, 95, 130, 45, 50, 250), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.GHOST, PokemonType.POISON, 20, 999.9, AbilityId.CURSED_BODY, AbilityId.NONE, AbilityId.NONE, 600, 140, 65, 70, 140, 85, 100, 45, 50, 250), - ), - new PokemonSpecies(SpeciesId.ONIX, 1, false, false, false, "Rock Snake Pokémon", PokemonType.ROCK, PokemonType.GROUND, 8.8, 210, AbilityId.ROCK_HEAD, AbilityId.STURDY, AbilityId.WEAK_ARMOR, 385, 35, 45, 160, 30, 45, 70, 45, 50, 77, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DROWZEE, 1, false, false, false, "Hypnosis Pokémon", PokemonType.PSYCHIC, null, 1, 32.4, AbilityId.INSOMNIA, AbilityId.FOREWARN, AbilityId.INNER_FOCUS, 328, 60, 48, 45, 43, 90, 42, 190, 70, 66, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.HYPNO, 1, false, false, false, "Hypnosis Pokémon", PokemonType.PSYCHIC, null, 1.6, 75.6, AbilityId.INSOMNIA, AbilityId.FOREWARN, AbilityId.INNER_FOCUS, 483, 85, 73, 70, 73, 115, 67, 75, 70, 169, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.KRABBY, 1, false, false, false, "River Crab Pokémon", PokemonType.WATER, null, 0.4, 6.5, AbilityId.HYPER_CUTTER, AbilityId.SHELL_ARMOR, AbilityId.SHEER_FORCE, 325, 30, 105, 90, 25, 25, 50, 225, 50, 65, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.KINGLER, 1, false, false, false, "Pincer Pokémon", PokemonType.WATER, null, 1.3, 60, AbilityId.HYPER_CUTTER, AbilityId.SHELL_ARMOR, AbilityId.SHEER_FORCE, 475, 55, 130, 115, 50, 50, 75, 60, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.WATER, null, 1.3, 60, AbilityId.HYPER_CUTTER, AbilityId.SHELL_ARMOR, AbilityId.SHEER_FORCE, 475, 55, 130, 115, 50, 50, 75, 60, 50, 166, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.WATER, null, 19, 999.9, AbilityId.TOUGH_CLAWS, AbilityId.TOUGH_CLAWS, AbilityId.TOUGH_CLAWS, 575, 92, 145, 140, 60, 65, 73, 60, 50, 166), - ), - new PokemonSpecies(SpeciesId.VOLTORB, 1, false, false, false, "Ball Pokémon", PokemonType.ELECTRIC, null, 0.5, 10.4, AbilityId.SOUNDPROOF, AbilityId.STATIC, AbilityId.AFTERMATH, 330, 40, 30, 50, 55, 55, 100, 190, 70, 66, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.ELECTRODE, 1, false, false, false, "Ball Pokémon", PokemonType.ELECTRIC, null, 1.2, 66.6, AbilityId.SOUNDPROOF, AbilityId.STATIC, AbilityId.AFTERMATH, 490, 60, 50, 70, 80, 80, 150, 60, 70, 172, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.EXEGGCUTE, 1, false, false, false, "Egg Pokémon", PokemonType.GRASS, PokemonType.PSYCHIC, 0.4, 2.5, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.HARVEST, 325, 60, 40, 80, 60, 45, 40, 90, 50, 65, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.EXEGGUTOR, 1, false, false, false, "Coconut Pokémon", PokemonType.GRASS, PokemonType.PSYCHIC, 2, 120, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.HARVEST, 530, 95, 95, 85, 125, 75, 55, 45, 50, 186, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.CUBONE, 1, false, false, false, "Lonely Pokémon", PokemonType.GROUND, null, 0.4, 6.5, AbilityId.ROCK_HEAD, AbilityId.LIGHTNING_ROD, AbilityId.BATTLE_ARMOR, 320, 50, 50, 95, 40, 50, 35, 190, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MAROWAK, 1, false, false, false, "Bone Keeper Pokémon", PokemonType.GROUND, null, 1, 45, AbilityId.ROCK_HEAD, AbilityId.LIGHTNING_ROD, AbilityId.BATTLE_ARMOR, 425, 60, 80, 110, 50, 80, 45, 75, 50, 149, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.HITMONLEE, 1, false, false, false, "Kicking Pokémon", PokemonType.FIGHTING, null, 1.5, 49.8, AbilityId.LIMBER, AbilityId.RECKLESS, AbilityId.UNBURDEN, 455, 50, 120, 53, 35, 110, 87, 45, 50, 159, GrowthRate.MEDIUM_FAST, 100, false), - new PokemonSpecies(SpeciesId.HITMONCHAN, 1, false, false, false, "Punching Pokémon", PokemonType.FIGHTING, null, 1.4, 50.2, AbilityId.KEEN_EYE, AbilityId.IRON_FIST, AbilityId.INNER_FOCUS, 455, 50, 105, 79, 35, 110, 76, 45, 50, 159, GrowthRate.MEDIUM_FAST, 100, false), - new PokemonSpecies(SpeciesId.LICKITUNG, 1, false, false, false, "Licking Pokémon", PokemonType.NORMAL, null, 1.2, 65.5, AbilityId.OWN_TEMPO, AbilityId.OBLIVIOUS, AbilityId.CLOUD_NINE, 385, 90, 55, 75, 60, 75, 30, 45, 50, 77, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.KOFFING, 1, false, false, false, "Poison Gas Pokémon", PokemonType.POISON, null, 0.6, 1, AbilityId.LEVITATE, AbilityId.NEUTRALIZING_GAS, AbilityId.STENCH, 340, 40, 65, 95, 60, 45, 35, 190, 50, 68, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.WEEZING, 1, false, false, false, "Poison Gas Pokémon", PokemonType.POISON, null, 1.2, 9.5, AbilityId.LEVITATE, AbilityId.NEUTRALIZING_GAS, AbilityId.STENCH, 490, 65, 90, 120, 85, 70, 60, 60, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.RHYHORN, 1, false, false, false, "Spikes Pokémon", PokemonType.GROUND, PokemonType.ROCK, 1, 115, AbilityId.LIGHTNING_ROD, AbilityId.ROCK_HEAD, AbilityId.RECKLESS, 345, 80, 85, 95, 30, 30, 25, 120, 50, 69, GrowthRate.SLOW, 50, true), - new PokemonSpecies(SpeciesId.RHYDON, 1, false, false, false, "Drill Pokémon", PokemonType.GROUND, PokemonType.ROCK, 1.9, 120, AbilityId.LIGHTNING_ROD, AbilityId.ROCK_HEAD, AbilityId.RECKLESS, 485, 105, 130, 120, 45, 45, 40, 60, 50, 170, GrowthRate.SLOW, 50, true), - new PokemonSpecies(SpeciesId.CHANSEY, 1, false, false, false, "Egg Pokémon", PokemonType.NORMAL, null, 1.1, 34.6, AbilityId.NATURAL_CURE, AbilityId.SERENE_GRACE, AbilityId.HEALER, 450, 250, 5, 5, 35, 105, 50, 30, 140, 395, GrowthRate.FAST, 0, false), - new PokemonSpecies(SpeciesId.TANGELA, 1, false, false, false, "Vine Pokémon", PokemonType.GRASS, null, 1, 35, AbilityId.CHLOROPHYLL, AbilityId.LEAF_GUARD, AbilityId.REGENERATOR, 435, 65, 55, 115, 100, 40, 60, 45, 50, 87, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.KANGASKHAN, 1, false, false, false, "Parent Pokémon", PokemonType.NORMAL, null, 2.2, 80, AbilityId.EARLY_BIRD, AbilityId.SCRAPPY, AbilityId.INNER_FOCUS, 490, 105, 95, 80, 40, 80, 90, 45, 50, 172, GrowthRate.MEDIUM_FAST, 0, false, true, - new PokemonForm("Normal", "", PokemonType.NORMAL, null, 2.2, 80, AbilityId.EARLY_BIRD, AbilityId.SCRAPPY, AbilityId.INNER_FOCUS, 490, 105, 95, 80, 40, 80, 90, 45, 50, 172, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.NORMAL, null, 2.2, 100, AbilityId.PARENTAL_BOND, AbilityId.PARENTAL_BOND, AbilityId.PARENTAL_BOND, 590, 105, 125, 100, 60, 100, 100, 45, 50, 172), - ), - new PokemonSpecies(SpeciesId.HORSEA, 1, false, false, false, "Dragon Pokémon", PokemonType.WATER, null, 0.4, 8, AbilityId.SWIFT_SWIM, AbilityId.SNIPER, AbilityId.DAMP, 295, 30, 40, 70, 70, 25, 60, 225, 50, 59, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SEADRA, 1, false, false, false, "Dragon Pokémon", PokemonType.WATER, null, 1.2, 25, AbilityId.POISON_POINT, AbilityId.SNIPER, AbilityId.DAMP, 440, 55, 65, 95, 95, 45, 85, 75, 50, 154, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GOLDEEN, 1, false, false, false, "Goldfish Pokémon", PokemonType.WATER, null, 0.6, 15, AbilityId.SWIFT_SWIM, AbilityId.WATER_VEIL, AbilityId.LIGHTNING_ROD, 320, 45, 67, 60, 35, 50, 63, 225, 50, 64, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.SEAKING, 1, false, false, false, "Goldfish Pokémon", PokemonType.WATER, null, 1.3, 39, AbilityId.SWIFT_SWIM, AbilityId.WATER_VEIL, AbilityId.LIGHTNING_ROD, 450, 80, 92, 65, 65, 80, 68, 60, 50, 158, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.STARYU, 1, false, false, false, "Star Shape Pokémon", PokemonType.WATER, null, 0.8, 34.5, AbilityId.ILLUMINATE, AbilityId.NATURAL_CURE, AbilityId.ANALYTIC, 340, 30, 45, 55, 70, 55, 85, 225, 50, 68, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.STARMIE, 1, false, false, false, "Mysterious Pokémon", PokemonType.WATER, PokemonType.PSYCHIC, 1.1, 80, AbilityId.ILLUMINATE, AbilityId.NATURAL_CURE, AbilityId.ANALYTIC, 520, 60, 75, 85, 100, 85, 115, 60, 50, 182, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.MR_MIME, 1, false, false, false, "Barrier Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.3, 54.5, AbilityId.SOUNDPROOF, AbilityId.FILTER, AbilityId.TECHNICIAN, 460, 40, 45, 65, 100, 120, 90, 45, 50, 161, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SCYTHER, 1, false, false, false, "Mantis Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.5, 56, AbilityId.SWARM, AbilityId.TECHNICIAN, AbilityId.STEADFAST, 500, 70, 110, 80, 55, 80, 105, 45, 50, 100, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.JYNX, 1, false, false, false, "Human Shape Pokémon", PokemonType.ICE, PokemonType.PSYCHIC, 1.4, 40.6, AbilityId.OBLIVIOUS, AbilityId.FOREWARN, AbilityId.DRY_SKIN, 455, 65, 50, 35, 115, 95, 95, 45, 50, 159, GrowthRate.MEDIUM_FAST, 0, false), - new PokemonSpecies(SpeciesId.ELECTABUZZ, 1, false, false, false, "Electric Pokémon", PokemonType.ELECTRIC, null, 1.1, 30, AbilityId.STATIC, AbilityId.NONE, AbilityId.VITAL_SPIRIT, 490, 65, 83, 57, 95, 85, 105, 45, 50, 172, GrowthRate.MEDIUM_FAST, 75, false), - new PokemonSpecies(SpeciesId.MAGMAR, 1, false, false, false, "Spitfire Pokémon", PokemonType.FIRE, null, 1.3, 44.5, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.VITAL_SPIRIT, 495, 65, 95, 57, 100, 85, 93, 45, 50, 173, GrowthRate.MEDIUM_FAST, 75, false), - new PokemonSpecies(SpeciesId.PINSIR, 1, false, false, false, "Stag Beetle Pokémon", PokemonType.BUG, null, 1.5, 55, AbilityId.HYPER_CUTTER, AbilityId.MOLD_BREAKER, AbilityId.MOXIE, 500, 65, 125, 100, 55, 70, 85, 45, 50, 175, GrowthRate.SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.BUG, null, 1.5, 55, AbilityId.HYPER_CUTTER, AbilityId.MOLD_BREAKER, AbilityId.MOXIE, 500, 65, 125, 100, 55, 70, 85, 45, 50, 175, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.BUG, PokemonType.FLYING, 1.7, 59, AbilityId.AERILATE, AbilityId.AERILATE, AbilityId.AERILATE, 600, 65, 155, 120, 65, 90, 105, 45, 50, 175), - ), - new PokemonSpecies(SpeciesId.TAUROS, 1, false, false, false, "Wild Bull Pokémon", PokemonType.NORMAL, null, 1.4, 88.4, AbilityId.INTIMIDATE, AbilityId.ANGER_POINT, AbilityId.SHEER_FORCE, 490, 75, 100, 95, 40, 70, 110, 45, 50, 172, GrowthRate.SLOW, 100, false), - new PokemonSpecies(SpeciesId.MAGIKARP, 1, false, false, false, "Fish Pokémon", PokemonType.WATER, null, 0.9, 10, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.RATTLED, 200, 20, 10, 55, 15, 20, 80, 255, 50, 40, GrowthRate.SLOW, 50, true), - new PokemonSpecies(SpeciesId.GYARADOS, 1, false, false, false, "Atrocious Pokémon", PokemonType.WATER, PokemonType.FLYING, 6.5, 235, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.MOXIE, 540, 95, 125, 79, 60, 100, 81, 45, 50, 189, GrowthRate.SLOW, 50, true, true, - new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.FLYING, 6.5, 235, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.MOXIE, 540, 95, 125, 79, 60, 100, 81, 45, 50, 189, true, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.WATER, PokemonType.DARK, 6.5, 305, AbilityId.MOLD_BREAKER, AbilityId.MOLD_BREAKER, AbilityId.MOLD_BREAKER, 640, 95, 155, 109, 70, 130, 81, 45, 50, 189, true), - ), - new PokemonSpecies(SpeciesId.LAPRAS, 1, false, false, false, "Transport Pokémon", PokemonType.WATER, PokemonType.ICE, 2.5, 220, AbilityId.WATER_ABSORB, AbilityId.SHELL_ARMOR, AbilityId.HYDRATION, 535, 130, 85, 80, 85, 95, 60, 45, 50, 187, GrowthRate.SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.ICE, 2.5, 220, AbilityId.WATER_ABSORB, AbilityId.SHELL_ARMOR, AbilityId.HYDRATION, 535, 130, 85, 80, 85, 95, 60, 45, 50, 187, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.WATER, PokemonType.ICE, 24, 999.9, AbilityId.SHIELD_DUST, AbilityId.SHIELD_DUST, AbilityId.SHIELD_DUST, 635, 170, 97, 85, 107, 111, 65, 45, 50, 187), - ), - new PokemonSpecies(SpeciesId.DITTO, 1, false, false, false, "Transform Pokémon", PokemonType.NORMAL, null, 0.3, 4, AbilityId.LIMBER, AbilityId.NONE, AbilityId.IMPOSTER, 288, 48, 48, 48, 48, 48, 48, 35, 50, 101, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.EEVEE, 1, false, false, false, "Evolution Pokémon", PokemonType.NORMAL, null, 0.3, 6.5, AbilityId.RUN_AWAY, AbilityId.ADAPTABILITY, AbilityId.ANTICIPATION, 325, 55, 55, 50, 45, 65, 55, 45, 50, 65, GrowthRate.MEDIUM_FAST, 87.5, false, true, - new PokemonForm("Normal", "", PokemonType.NORMAL, null, 0.3, 6.5, AbilityId.RUN_AWAY, AbilityId.ADAPTABILITY, AbilityId.ANTICIPATION, 325, 55, 55, 50, 45, 65, 55, 45, 50, 65, false, null, true), - new PokemonForm("Partner", "partner", PokemonType.NORMAL, null, 0.3, 6.5, AbilityId.RUN_AWAY, AbilityId.ADAPTABILITY, AbilityId.ANTICIPATION, 435, 65, 75, 70, 65, 85, 75, 45, 50, 65, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.NORMAL, null, 18, 999.9, AbilityId.PROTEAN, AbilityId.PROTEAN, AbilityId.PROTEAN, 535, 110, 95, 70, 90, 85, 85, 45, 50, 65), //+100 BST from Partner Form - ), - new PokemonSpecies(SpeciesId.VAPOREON, 1, false, false, false, "Bubble Jet Pokémon", PokemonType.WATER, null, 1, 29, AbilityId.WATER_ABSORB, AbilityId.NONE, AbilityId.HYDRATION, 525, 130, 65, 60, 110, 95, 65, 45, 50, 184, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.JOLTEON, 1, false, false, false, "Lightning Pokémon", PokemonType.ELECTRIC, null, 0.8, 24.5, AbilityId.VOLT_ABSORB, AbilityId.NONE, AbilityId.QUICK_FEET, 525, 65, 65, 60, 110, 95, 130, 45, 50, 184, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.FLAREON, 1, false, false, false, "Flame Pokémon", PokemonType.FIRE, null, 0.9, 25, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.GUTS, 525, 65, 130, 60, 95, 110, 65, 45, 50, 184, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.PORYGON, 1, false, false, false, "Virtual Pokémon", PokemonType.NORMAL, null, 0.8, 36.5, AbilityId.TRACE, AbilityId.DOWNLOAD, AbilityId.ANALYTIC, 395, 65, 60, 70, 85, 75, 40, 45, 50, 79, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.OMANYTE, 1, false, false, false, "Spiral Pokémon", PokemonType.ROCK, PokemonType.WATER, 0.4, 7.5, AbilityId.SWIFT_SWIM, AbilityId.SHELL_ARMOR, AbilityId.WEAK_ARMOR, 355, 35, 40, 100, 90, 55, 35, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.OMASTAR, 1, false, false, false, "Spiral Pokémon", PokemonType.ROCK, PokemonType.WATER, 1, 35, AbilityId.SWIFT_SWIM, AbilityId.SHELL_ARMOR, AbilityId.WEAK_ARMOR, 495, 70, 60, 125, 115, 70, 55, 45, 50, 173, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.KABUTO, 1, false, false, false, "Shellfish Pokémon", PokemonType.ROCK, PokemonType.WATER, 0.5, 11.5, AbilityId.SWIFT_SWIM, AbilityId.BATTLE_ARMOR, AbilityId.WEAK_ARMOR, 355, 30, 80, 90, 55, 45, 55, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.KABUTOPS, 1, false, false, false, "Shellfish Pokémon", PokemonType.ROCK, PokemonType.WATER, 1.3, 40.5, AbilityId.SWIFT_SWIM, AbilityId.BATTLE_ARMOR, AbilityId.WEAK_ARMOR, 495, 60, 115, 105, 65, 70, 80, 45, 50, 173, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.AERODACTYL, 1, false, false, false, "Fossil Pokémon", PokemonType.ROCK, PokemonType.FLYING, 1.8, 59, AbilityId.ROCK_HEAD, AbilityId.PRESSURE, AbilityId.UNNERVE, 515, 80, 105, 65, 60, 75, 130, 45, 50, 180, GrowthRate.SLOW, 87.5, false, true, - new PokemonForm("Normal", "", PokemonType.ROCK, PokemonType.FLYING, 1.8, 59, AbilityId.ROCK_HEAD, AbilityId.PRESSURE, AbilityId.UNNERVE, 515, 80, 105, 65, 60, 75, 130, 45, 50, 180, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.ROCK, PokemonType.FLYING, 2.1, 79, AbilityId.TOUGH_CLAWS, AbilityId.TOUGH_CLAWS, AbilityId.TOUGH_CLAWS, 615, 80, 135, 85, 70, 95, 150, 45, 50, 180), - ), - new PokemonSpecies(SpeciesId.SNORLAX, 1, false, false, false, "Sleeping Pokémon", PokemonType.NORMAL, null, 2.1, 460, AbilityId.IMMUNITY, AbilityId.THICK_FAT, AbilityId.GLUTTONY, 540, 160, 110, 65, 65, 110, 30, 25, 50, 189, GrowthRate.SLOW, 87.5, false, true, - new PokemonForm("Normal", "", PokemonType.NORMAL, null, 2.1, 460, AbilityId.IMMUNITY, AbilityId.THICK_FAT, AbilityId.GLUTTONY, 540, 160, 110, 65, 65, 110, 30, 25, 50, 189, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.NORMAL, null, 35, 999.9, AbilityId.HARVEST, AbilityId.HARVEST, AbilityId.HARVEST, 640, 210, 135, 70, 90, 115, 20, 25, 50, 189), - ), - new PokemonSpecies(SpeciesId.ARTICUNO, 1, true, false, false, "Freeze Pokémon", PokemonType.ICE, PokemonType.FLYING, 1.7, 55.4, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.SNOW_CLOAK, 580, 90, 85, 100, 95, 125, 85, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.ZAPDOS, 1, true, false, false, "Electric Pokémon", PokemonType.ELECTRIC, PokemonType.FLYING, 1.6, 52.6, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.STATIC, 580, 90, 90, 85, 125, 90, 100, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.MOLTRES, 1, true, false, false, "Flame Pokémon", PokemonType.FIRE, PokemonType.FLYING, 2, 60, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.FLAME_BODY, 580, 90, 100, 90, 125, 85, 90, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.DRATINI, 1, false, false, false, "Dragon Pokémon", PokemonType.DRAGON, null, 1.8, 3.3, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.MARVEL_SCALE, 300, 41, 64, 45, 50, 50, 50, 45, 35, 60, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.DRAGONAIR, 1, false, false, false, "Dragon Pokémon", PokemonType.DRAGON, null, 4, 16.5, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.MARVEL_SCALE, 420, 61, 84, 65, 70, 70, 70, 45, 35, 147, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.DRAGONITE, 1, false, false, false, "Dragon Pokémon", PokemonType.DRAGON, PokemonType.FLYING, 2.2, 210, AbilityId.INNER_FOCUS, AbilityId.NONE, AbilityId.MULTISCALE, 600, 91, 134, 95, 100, 100, 80, 45, 35, 300, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.MEWTWO, 1, false, true, false, "Genetic Pokémon", PokemonType.PSYCHIC, null, 2, 122, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.UNNERVE, 680, 106, 110, 90, 154, 90, 130, 3, 0, 340, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal", "", PokemonType.PSYCHIC, null, 2, 122, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.UNNERVE, 680, 106, 110, 90, 154, 90, 130, 3, 0, 340, false, null, true), - new PokemonForm("Mega X", SpeciesFormKey.MEGA_X, PokemonType.PSYCHIC, PokemonType.FIGHTING, 2.3, 127, AbilityId.STEADFAST, AbilityId.NONE, AbilityId.STEADFAST, 780, 106, 190, 100, 154, 100, 130, 3, 0, 340), - new PokemonForm("Mega Y", SpeciesFormKey.MEGA_Y, PokemonType.PSYCHIC, null, 1.5, 33, AbilityId.INSOMNIA, AbilityId.NONE, AbilityId.INSOMNIA, 780, 106, 150, 70, 194, 120, 140, 3, 0, 340), - ), - new PokemonSpecies(SpeciesId.MEW, 1, false, false, true, "New Species Pokémon", PokemonType.PSYCHIC, null, 0.4, 4, AbilityId.SYNCHRONIZE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 45, 100, 300, GrowthRate.MEDIUM_SLOW, null, false), - new PokemonSpecies(SpeciesId.CHIKORITA, 2, false, false, false, "Leaf Pokémon", PokemonType.GRASS, null, 0.9, 6.4, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.LEAF_GUARD, 318, 45, 49, 65, 49, 65, 45, 45, 70, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.BAYLEEF, 2, false, false, false, "Leaf Pokémon", PokemonType.GRASS, null, 1.2, 15.8, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.LEAF_GUARD, 405, 60, 62, 80, 63, 80, 60, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.MEGANIUM, 2, false, false, false, "Herb Pokémon", PokemonType.GRASS, null, 1.8, 100.5, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.LEAF_GUARD, 525, 80, 82, 100, 83, 100, 80, 45, 70, 263, GrowthRate.MEDIUM_SLOW, 87.5, true), - new PokemonSpecies(SpeciesId.CYNDAQUIL, 2, false, false, false, "Fire Mouse Pokémon", PokemonType.FIRE, null, 0.5, 7.9, AbilityId.BLAZE, AbilityId.NONE, AbilityId.FLASH_FIRE, 309, 39, 52, 43, 60, 50, 65, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.QUILAVA, 2, false, false, false, "Volcano Pokémon", PokemonType.FIRE, null, 0.9, 19, AbilityId.BLAZE, AbilityId.NONE, AbilityId.FLASH_FIRE, 405, 58, 64, 58, 80, 65, 80, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.TYPHLOSION, 2, false, false, false, "Volcano Pokémon", PokemonType.FIRE, null, 1.7, 79.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.FLASH_FIRE, 534, 78, 84, 78, 109, 85, 100, 45, 70, 267, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.TOTODILE, 2, false, false, false, "Big Jaw Pokémon", PokemonType.WATER, null, 0.6, 9.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHEER_FORCE, 314, 50, 65, 64, 44, 48, 43, 45, 70, 63, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.CROCONAW, 2, false, false, false, "Big Jaw Pokémon", PokemonType.WATER, null, 1.1, 25, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHEER_FORCE, 405, 65, 80, 80, 59, 63, 58, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.FERALIGATR, 2, false, false, false, "Big Jaw Pokémon", PokemonType.WATER, null, 2.3, 88.8, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHEER_FORCE, 530, 85, 105, 100, 79, 83, 78, 45, 70, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.SENTRET, 2, false, false, false, "Scout Pokémon", PokemonType.NORMAL, null, 0.8, 6, AbilityId.RUN_AWAY, AbilityId.KEEN_EYE, AbilityId.FRISK, 215, 35, 46, 34, 35, 45, 20, 255, 70, 43, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.FURRET, 2, false, false, false, "Long Body Pokémon", PokemonType.NORMAL, null, 1.8, 32.5, AbilityId.RUN_AWAY, AbilityId.KEEN_EYE, AbilityId.FRISK, 415, 85, 76, 64, 45, 55, 90, 90, 70, 145, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.HOOTHOOT, 2, false, false, false, "Owl Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.7, 21.2, AbilityId.INSOMNIA, AbilityId.KEEN_EYE, AbilityId.TINTED_LENS, 262, 60, 30, 30, 36, 56, 50, 255, 50, 52, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.NOCTOWL, 2, false, false, false, "Owl Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.6, 40.8, AbilityId.INSOMNIA, AbilityId.KEEN_EYE, AbilityId.TINTED_LENS, 452, 100, 50, 50, 86, 96, 70, 90, 50, 158, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.LEDYBA, 2, false, false, false, "Five Star Pokémon", PokemonType.BUG, PokemonType.FLYING, 1, 10.8, AbilityId.SWARM, AbilityId.EARLY_BIRD, AbilityId.RATTLED, 265, 40, 20, 30, 40, 80, 55, 255, 70, 53, GrowthRate.FAST, 50, true), - new PokemonSpecies(SpeciesId.LEDIAN, 2, false, false, false, "Five Star Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.4, 35.6, AbilityId.SWARM, AbilityId.EARLY_BIRD, AbilityId.IRON_FIST, 390, 55, 35, 50, 55, 110, 85, 90, 70, 137, GrowthRate.FAST, 50, true), - new PokemonSpecies(SpeciesId.SPINARAK, 2, false, false, false, "String Spit Pokémon", PokemonType.BUG, PokemonType.POISON, 0.5, 8.5, AbilityId.SWARM, AbilityId.INSOMNIA, AbilityId.SNIPER, 250, 40, 60, 40, 40, 40, 30, 255, 70, 50, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.ARIADOS, 2, false, false, false, "Long Leg Pokémon", PokemonType.BUG, PokemonType.POISON, 1.1, 33.5, AbilityId.SWARM, AbilityId.INSOMNIA, AbilityId.SNIPER, 400, 70, 90, 70, 60, 70, 40, 90, 70, 140, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.CROBAT, 2, false, false, false, "Bat Pokémon", PokemonType.POISON, PokemonType.FLYING, 1.8, 75, AbilityId.INNER_FOCUS, AbilityId.NONE, AbilityId.INFILTRATOR, 535, 85, 90, 80, 70, 80, 130, 90, 50, 268, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CHINCHOU, 2, false, false, false, "Angler Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 0.5, 12, AbilityId.VOLT_ABSORB, AbilityId.ILLUMINATE, AbilityId.WATER_ABSORB, 330, 75, 38, 38, 56, 56, 67, 190, 50, 66, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.LANTURN, 2, false, false, false, "Light Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 1.2, 22.5, AbilityId.VOLT_ABSORB, AbilityId.ILLUMINATE, AbilityId.WATER_ABSORB, 460, 125, 58, 58, 76, 76, 67, 75, 50, 161, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.PICHU, 2, false, false, false, "Tiny Mouse Pokémon", PokemonType.ELECTRIC, null, 0.3, 2, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.4, 2, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), - new PokemonForm("Spiky-Eared", "spiky", PokemonType.ELECTRIC, null, 1.4, 2, AbilityId.STATIC, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), - ), - new PokemonSpecies(SpeciesId.CLEFFA, 2, false, false, false, "Star Shape Pokémon", PokemonType.FAIRY, null, 0.3, 3, AbilityId.CUTE_CHARM, AbilityId.MAGIC_GUARD, AbilityId.FRIEND_GUARD, 218, 50, 25, 28, 45, 55, 15, 150, 140, 44, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.IGGLYBUFF, 2, false, false, false, "Balloon Pokémon", PokemonType.NORMAL, PokemonType.FAIRY, 0.3, 1, AbilityId.CUTE_CHARM, AbilityId.COMPETITIVE, AbilityId.FRIEND_GUARD, 210, 90, 30, 15, 40, 20, 15, 170, 50, 42, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.TOGEPI, 2, false, false, false, "Spike Ball Pokémon", PokemonType.FAIRY, null, 0.3, 1.5, AbilityId.HUSTLE, AbilityId.SERENE_GRACE, AbilityId.SUPER_LUCK, 245, 35, 20, 65, 40, 65, 20, 190, 50, 49, GrowthRate.FAST, 87.5, false), - new PokemonSpecies(SpeciesId.TOGETIC, 2, false, false, false, "Happiness Pokémon", PokemonType.FAIRY, PokemonType.FLYING, 0.6, 3.2, AbilityId.HUSTLE, AbilityId.SERENE_GRACE, AbilityId.SUPER_LUCK, 405, 55, 40, 85, 80, 105, 40, 75, 50, 142, GrowthRate.FAST, 87.5, false), - new PokemonSpecies(SpeciesId.NATU, 2, false, false, false, "Tiny Bird Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 0.2, 2, AbilityId.SYNCHRONIZE, AbilityId.EARLY_BIRD, AbilityId.MAGIC_BOUNCE, 320, 40, 50, 45, 70, 45, 70, 190, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.XATU, 2, false, false, false, "Mystic Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 1.5, 15, AbilityId.SYNCHRONIZE, AbilityId.EARLY_BIRD, AbilityId.MAGIC_BOUNCE, 470, 65, 75, 70, 95, 70, 95, 75, 50, 165, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.MAREEP, 2, false, false, false, "Wool Pokémon", PokemonType.ELECTRIC, null, 0.6, 7.8, AbilityId.STATIC, AbilityId.NONE, AbilityId.PLUS, 280, 55, 40, 40, 65, 45, 35, 235, 70, 56, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.FLAAFFY, 2, false, false, false, "Wool Pokémon", PokemonType.ELECTRIC, null, 0.8, 13.3, AbilityId.STATIC, AbilityId.NONE, AbilityId.PLUS, 365, 70, 55, 55, 80, 60, 45, 120, 70, 128, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.AMPHAROS, 2, false, false, false, "Light Pokémon", PokemonType.ELECTRIC, null, 1.4, 61.5, AbilityId.STATIC, AbilityId.NONE, AbilityId.PLUS, 510, 90, 75, 85, 115, 90, 55, 45, 70, 255, GrowthRate.MEDIUM_SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.4, 61.5, AbilityId.STATIC, AbilityId.NONE, AbilityId.PLUS, 510, 90, 75, 85, 115, 90, 55, 45, 70, 255, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.ELECTRIC, PokemonType.DRAGON, 1.4, 61.5, AbilityId.MOLD_BREAKER, AbilityId.NONE, AbilityId.MOLD_BREAKER, 610, 90, 95, 105, 165, 110, 45, 45, 70, 255), - ), - new PokemonSpecies(SpeciesId.BELLOSSOM, 2, false, false, false, "Flower Pokémon", PokemonType.GRASS, null, 0.4, 5.8, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.HEALER, 490, 75, 80, 95, 90, 100, 50, 45, 50, 245, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.MARILL, 2, false, false, false, "Aqua Mouse Pokémon", PokemonType.WATER, PokemonType.FAIRY, 0.4, 8.5, AbilityId.THICK_FAT, AbilityId.HUGE_POWER, AbilityId.SAP_SIPPER, 250, 70, 20, 50, 20, 50, 40, 190, 50, 88, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.AZUMARILL, 2, false, false, false, "Aqua Rabbit Pokémon", PokemonType.WATER, PokemonType.FAIRY, 0.8, 28.5, AbilityId.THICK_FAT, AbilityId.HUGE_POWER, AbilityId.SAP_SIPPER, 420, 100, 50, 80, 60, 80, 50, 75, 50, 210, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.SUDOWOODO, 2, false, false, false, "Imitation Pokémon", PokemonType.ROCK, null, 1.2, 38, AbilityId.STURDY, AbilityId.ROCK_HEAD, AbilityId.RATTLED, 410, 70, 100, 115, 30, 65, 30, 65, 50, 144, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.POLITOED, 2, false, false, false, "Frog Pokémon", PokemonType.WATER, null, 1.1, 33.9, AbilityId.WATER_ABSORB, AbilityId.DAMP, AbilityId.DRIZZLE, 500, 90, 75, 75, 90, 100, 70, 45, 50, 250, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.HOPPIP, 2, false, false, false, "Cottonweed Pokémon", PokemonType.GRASS, PokemonType.FLYING, 0.4, 0.5, AbilityId.CHLOROPHYLL, AbilityId.LEAF_GUARD, AbilityId.INFILTRATOR, 250, 35, 35, 40, 35, 55, 50, 255, 70, 50, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SKIPLOOM, 2, false, false, false, "Cottonweed Pokémon", PokemonType.GRASS, PokemonType.FLYING, 0.6, 1, AbilityId.CHLOROPHYLL, AbilityId.LEAF_GUARD, AbilityId.INFILTRATOR, 340, 55, 45, 50, 45, 65, 80, 120, 70, 119, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.JUMPLUFF, 2, false, false, false, "Cottonweed Pokémon", PokemonType.GRASS, PokemonType.FLYING, 0.8, 3, AbilityId.CHLOROPHYLL, AbilityId.LEAF_GUARD, AbilityId.INFILTRATOR, 460, 75, 55, 70, 55, 95, 110, 45, 70, 230, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.AIPOM, 2, false, false, false, "Long Tail Pokémon", PokemonType.NORMAL, null, 0.8, 11.5, AbilityId.RUN_AWAY, AbilityId.PICKUP, AbilityId.SKILL_LINK, 360, 55, 70, 55, 40, 55, 85, 45, 70, 72, GrowthRate.FAST, 50, true), - new PokemonSpecies(SpeciesId.SUNKERN, 2, false, false, false, "Seed Pokémon", PokemonType.GRASS, null, 0.3, 1.8, AbilityId.CHLOROPHYLL, AbilityId.SOLAR_POWER, AbilityId.EARLY_BIRD, 180, 30, 30, 30, 30, 30, 30, 235, 70, 36, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SUNFLORA, 2, false, false, false, "Sun Pokémon", PokemonType.GRASS, null, 0.8, 8.5, AbilityId.CHLOROPHYLL, AbilityId.SOLAR_POWER, AbilityId.EARLY_BIRD, 425, 75, 75, 55, 105, 85, 30, 120, 70, 149, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.YANMA, 2, false, false, false, "Clear Wing Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.2, 38, AbilityId.SPEED_BOOST, AbilityId.COMPOUND_EYES, AbilityId.FRISK, 390, 65, 65, 45, 75, 45, 95, 75, 70, 78, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.WOOPER, 2, false, false, false, "Water Fish Pokémon", PokemonType.WATER, PokemonType.GROUND, 0.4, 8.5, AbilityId.DAMP, AbilityId.WATER_ABSORB, AbilityId.UNAWARE, 210, 55, 45, 45, 25, 25, 15, 255, 50, 42, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.QUAGSIRE, 2, false, false, false, "Water Fish Pokémon", PokemonType.WATER, PokemonType.GROUND, 1.4, 75, AbilityId.DAMP, AbilityId.WATER_ABSORB, AbilityId.UNAWARE, 430, 95, 85, 85, 65, 65, 35, 90, 50, 151, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.ESPEON, 2, false, false, false, "Sun Pokémon", PokemonType.PSYCHIC, null, 0.9, 26.5, AbilityId.SYNCHRONIZE, AbilityId.NONE, AbilityId.MAGIC_BOUNCE, 525, 65, 65, 60, 130, 95, 110, 45, 50, 184, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.UMBREON, 2, false, false, false, "Moonlight Pokémon", PokemonType.DARK, null, 1, 27, AbilityId.SYNCHRONIZE, AbilityId.NONE, AbilityId.INNER_FOCUS, 525, 95, 65, 110, 60, 130, 65, 45, 35, 184, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.MURKROW, 2, false, false, false, "Darkness Pokémon", PokemonType.DARK, PokemonType.FLYING, 0.5, 2.1, AbilityId.INSOMNIA, AbilityId.SUPER_LUCK, AbilityId.PRANKSTER, 405, 60, 85, 42, 85, 42, 91, 30, 35, 81, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.SLOWKING, 2, false, false, false, "Royal Pokémon", PokemonType.WATER, PokemonType.PSYCHIC, 2, 79.5, AbilityId.OBLIVIOUS, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 490, 95, 75, 80, 100, 110, 30, 70, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MISDREAVUS, 2, false, false, false, "Screech Pokémon", PokemonType.GHOST, null, 0.7, 1, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 435, 60, 60, 60, 85, 85, 85, 45, 35, 87, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.UNOWN, 2, false, false, false, "Symbol Pokémon", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, GrowthRate.MEDIUM_FAST, null, false, false, - new PokemonForm("A", "a", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("B", "b", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("C", "c", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("D", "d", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("E", "e", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("F", "f", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("G", "g", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("H", "h", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("I", "i", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("J", "j", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("K", "k", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("L", "l", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("M", "m", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("N", "n", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("O", "o", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("P", "p", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("Q", "q", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("R", "r", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("S", "s", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("T", "t", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("U", "u", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("V", "v", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("W", "w", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("X", "x", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("Y", "y", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("Z", "z", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("!", "exclamation", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - new PokemonForm("?", "question", PokemonType.PSYCHIC, null, 0.5, 5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 336, 48, 72, 48, 72, 48, 48, 225, 70, 118, false, null, true), - ), - new PokemonSpecies(SpeciesId.WOBBUFFET, 2, false, false, false, "Patient Pokémon", PokemonType.PSYCHIC, null, 1.3, 28.5, AbilityId.SHADOW_TAG, AbilityId.NONE, AbilityId.TELEPATHY, 405, 190, 33, 58, 33, 58, 33, 45, 50, 142, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.GIRAFARIG, 2, false, false, false, "Long Neck Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 1.5, 41.5, AbilityId.INNER_FOCUS, AbilityId.EARLY_BIRD, AbilityId.SAP_SIPPER, 455, 70, 80, 65, 90, 65, 85, 60, 70, 159, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.PINECO, 2, false, false, false, "Bagworm Pokémon", PokemonType.BUG, null, 0.6, 7.2, AbilityId.STURDY, AbilityId.NONE, AbilityId.OVERCOAT, 290, 50, 65, 90, 35, 35, 15, 190, 70, 58, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.FORRETRESS, 2, false, false, false, "Bagworm Pokémon", PokemonType.BUG, PokemonType.STEEL, 1.2, 125.8, AbilityId.STURDY, AbilityId.NONE, AbilityId.OVERCOAT, 465, 75, 90, 140, 60, 60, 40, 75, 70, 163, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DUNSPARCE, 2, false, false, false, "Land Snake Pokémon", PokemonType.NORMAL, null, 1.5, 14, AbilityId.SERENE_GRACE, AbilityId.RUN_AWAY, AbilityId.RATTLED, 415, 100, 70, 70, 65, 65, 45, 190, 50, 145, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GLIGAR, 2, false, false, false, "Fly Scorpion Pokémon", PokemonType.GROUND, PokemonType.FLYING, 1.1, 64.8, AbilityId.HYPER_CUTTER, AbilityId.SAND_VEIL, AbilityId.IMMUNITY, 430, 65, 75, 105, 35, 65, 85, 60, 70, 86, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.STEELIX, 2, false, false, false, "Iron Snake Pokémon", PokemonType.STEEL, PokemonType.GROUND, 9.2, 400, AbilityId.ROCK_HEAD, AbilityId.STURDY, AbilityId.SHEER_FORCE, 510, 75, 85, 200, 55, 65, 30, 25, 50, 179, GrowthRate.MEDIUM_FAST, 50, true, true, - new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.GROUND, 9.2, 400, AbilityId.ROCK_HEAD, AbilityId.STURDY, AbilityId.SHEER_FORCE, 510, 75, 85, 200, 55, 65, 30, 25, 50, 179, true, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.STEEL, PokemonType.GROUND, 10.5, 740, AbilityId.SAND_FORCE, AbilityId.SAND_FORCE, AbilityId.SAND_FORCE, 610, 75, 125, 230, 55, 95, 30, 25, 50, 179, true), - ), - new PokemonSpecies(SpeciesId.SNUBBULL, 2, false, false, false, "Fairy Pokémon", PokemonType.FAIRY, null, 0.6, 7.8, AbilityId.INTIMIDATE, AbilityId.RUN_AWAY, AbilityId.RATTLED, 300, 60, 80, 50, 40, 40, 30, 190, 70, 60, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.GRANBULL, 2, false, false, false, "Fairy Pokémon", PokemonType.FAIRY, null, 1.4, 48.7, AbilityId.INTIMIDATE, AbilityId.QUICK_FEET, AbilityId.RATTLED, 450, 90, 120, 75, 60, 60, 45, 75, 70, 158, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.QWILFISH, 2, false, false, false, "Balloon Pokémon", PokemonType.WATER, PokemonType.POISON, 0.5, 3.9, AbilityId.POISON_POINT, AbilityId.SWIFT_SWIM, AbilityId.INTIMIDATE, 440, 65, 95, 85, 55, 55, 85, 45, 50, 88, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SCIZOR, 2, false, false, false, "Pincer Pokémon", PokemonType.BUG, PokemonType.STEEL, 1.8, 118, AbilityId.SWARM, AbilityId.TECHNICIAN, AbilityId.LIGHT_METAL, 500, 70, 130, 100, 55, 80, 65, 25, 50, 175, GrowthRate.MEDIUM_FAST, 50, true, true, - new PokemonForm("Normal", "", PokemonType.BUG, PokemonType.STEEL, 1.8, 118, AbilityId.SWARM, AbilityId.TECHNICIAN, AbilityId.LIGHT_METAL, 500, 70, 130, 100, 55, 80, 65, 25, 50, 175, true, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.BUG, PokemonType.STEEL, 2, 125, AbilityId.TECHNICIAN, AbilityId.TECHNICIAN, AbilityId.TECHNICIAN, 600, 70, 150, 140, 65, 100, 75, 25, 50, 175, true), - ), - new PokemonSpecies(SpeciesId.SHUCKLE, 2, false, false, false, "Mold Pokémon", PokemonType.BUG, PokemonType.ROCK, 0.6, 20.5, AbilityId.STURDY, AbilityId.GLUTTONY, AbilityId.CONTRARY, 505, 20, 10, 230, 10, 230, 5, 190, 50, 177, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.HERACROSS, 2, false, false, false, "Single Horn Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 1.5, 54, AbilityId.SWARM, AbilityId.GUTS, AbilityId.MOXIE, 500, 80, 125, 75, 40, 95, 85, 45, 50, 175, GrowthRate.SLOW, 50, true, true, - new PokemonForm("Normal", "", PokemonType.BUG, PokemonType.FIGHTING, 1.5, 54, AbilityId.SWARM, AbilityId.GUTS, AbilityId.MOXIE, 500, 80, 125, 75, 40, 95, 85, 45, 50, 175, true, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.BUG, PokemonType.FIGHTING, 1.7, 62.5, AbilityId.SKILL_LINK, AbilityId.SKILL_LINK, AbilityId.SKILL_LINK, 600, 80, 185, 115, 40, 105, 75, 45, 50, 175, true), - ), - new PokemonSpecies(SpeciesId.SNEASEL, 2, false, false, false, "Sharp Claw Pokémon", PokemonType.DARK, PokemonType.ICE, 0.9, 28, AbilityId.INNER_FOCUS, AbilityId.KEEN_EYE, AbilityId.PICKPOCKET, 430, 55, 95, 55, 35, 75, 115, 60, 35, 86, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.TEDDIURSA, 2, false, false, false, "Little Bear Pokémon", PokemonType.NORMAL, null, 0.6, 8.8, AbilityId.PICKUP, AbilityId.QUICK_FEET, AbilityId.HONEY_GATHER, 330, 60, 80, 50, 50, 50, 40, 120, 70, 66, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.URSARING, 2, false, false, false, "Hibernator Pokémon", PokemonType.NORMAL, null, 1.8, 125.8, AbilityId.GUTS, AbilityId.QUICK_FEET, AbilityId.UNNERVE, 500, 90, 130, 75, 75, 75, 55, 60, 70, 175, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.SLUGMA, 2, false, false, false, "Lava Pokémon", PokemonType.FIRE, null, 0.7, 35, AbilityId.MAGMA_ARMOR, AbilityId.FLAME_BODY, AbilityId.WEAK_ARMOR, 250, 40, 40, 40, 70, 40, 20, 190, 70, 50, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MAGCARGO, 2, false, false, false, "Lava Pokémon", PokemonType.FIRE, PokemonType.ROCK, 0.8, 55, AbilityId.MAGMA_ARMOR, AbilityId.FLAME_BODY, AbilityId.WEAK_ARMOR, 430, 60, 50, 120, 90, 80, 30, 75, 70, 151, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SWINUB, 2, false, false, false, "Pig Pokémon", PokemonType.ICE, PokemonType.GROUND, 0.4, 6.5, AbilityId.OBLIVIOUS, AbilityId.SNOW_CLOAK, AbilityId.THICK_FAT, 250, 50, 50, 40, 30, 30, 50, 225, 50, 50, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.PILOSWINE, 2, false, false, false, "Swine Pokémon", PokemonType.ICE, PokemonType.GROUND, 1.1, 55.8, AbilityId.OBLIVIOUS, AbilityId.SNOW_CLOAK, AbilityId.THICK_FAT, 450, 100, 100, 80, 60, 60, 50, 75, 50, 158, GrowthRate.SLOW, 50, true), - new PokemonSpecies(SpeciesId.CORSOLA, 2, false, false, false, "Coral Pokémon", PokemonType.WATER, PokemonType.ROCK, 0.6, 5, AbilityId.HUSTLE, AbilityId.NATURAL_CURE, AbilityId.REGENERATOR, 410, 65, 55, 95, 65, 95, 35, 60, 50, 144, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.REMORAID, 2, false, false, false, "Jet Pokémon", PokemonType.WATER, null, 0.6, 12, AbilityId.HUSTLE, AbilityId.SNIPER, AbilityId.MOODY, 300, 35, 65, 35, 65, 35, 65, 190, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.OCTILLERY, 2, false, false, false, "Jet Pokémon", PokemonType.WATER, null, 0.9, 28.5, AbilityId.SUCTION_CUPS, AbilityId.SNIPER, AbilityId.MOODY, 480, 75, 105, 75, 105, 75, 45, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.DELIBIRD, 2, false, false, false, "Delivery Pokémon", PokemonType.ICE, PokemonType.FLYING, 0.9, 16, AbilityId.VITAL_SPIRIT, AbilityId.HUSTLE, AbilityId.INSOMNIA, 330, 45, 55, 45, 65, 45, 75, 45, 50, 116, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.MANTINE, 2, false, false, false, "Kite Pokémon", PokemonType.WATER, PokemonType.FLYING, 2.1, 220, AbilityId.SWIFT_SWIM, AbilityId.WATER_ABSORB, AbilityId.WATER_VEIL, 485, 85, 40, 70, 80, 140, 70, 25, 50, 170, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.SKARMORY, 2, false, false, false, "Armor Bird Pokémon", PokemonType.STEEL, PokemonType.FLYING, 1.7, 50.5, AbilityId.KEEN_EYE, AbilityId.STURDY, AbilityId.WEAK_ARMOR, 465, 65, 80, 140, 40, 70, 70, 25, 50, 163, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.HOUNDOUR, 2, false, false, false, "Dark Pokémon", PokemonType.DARK, PokemonType.FIRE, 0.6, 10.8, AbilityId.EARLY_BIRD, AbilityId.FLASH_FIRE, AbilityId.UNNERVE, 330, 45, 60, 30, 80, 50, 65, 120, 35, 66, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.HOUNDOOM, 2, false, false, false, "Dark Pokémon", PokemonType.DARK, PokemonType.FIRE, 1.4, 35, AbilityId.EARLY_BIRD, AbilityId.FLASH_FIRE, AbilityId.UNNERVE, 500, 75, 90, 50, 110, 80, 95, 45, 35, 175, GrowthRate.SLOW, 50, true, true, - new PokemonForm("Normal", "", PokemonType.DARK, PokemonType.FIRE, 1.4, 35, AbilityId.EARLY_BIRD, AbilityId.FLASH_FIRE, AbilityId.UNNERVE, 500, 75, 90, 50, 110, 80, 95, 45, 35, 175, true, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DARK, PokemonType.FIRE, 1.9, 49.5, AbilityId.SOLAR_POWER, AbilityId.SOLAR_POWER, AbilityId.SOLAR_POWER, 600, 75, 90, 90, 140, 90, 115, 45, 35, 175, true), - ), - new PokemonSpecies(SpeciesId.KINGDRA, 2, false, false, false, "Dragon Pokémon", PokemonType.WATER, PokemonType.DRAGON, 1.8, 152, AbilityId.SWIFT_SWIM, AbilityId.SNIPER, AbilityId.DAMP, 540, 75, 95, 95, 95, 95, 85, 45, 50, 270, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PHANPY, 2, false, false, false, "Long Nose Pokémon", PokemonType.GROUND, null, 0.5, 33.5, AbilityId.PICKUP, AbilityId.NONE, AbilityId.SAND_VEIL, 330, 90, 60, 60, 40, 40, 40, 120, 70, 66, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DONPHAN, 2, false, false, false, "Armor Pokémon", PokemonType.GROUND, null, 1.1, 120, AbilityId.STURDY, AbilityId.NONE, AbilityId.SAND_VEIL, 500, 90, 120, 120, 60, 60, 50, 60, 70, 175, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.PORYGON2, 2, false, false, false, "Virtual Pokémon", PokemonType.NORMAL, null, 0.6, 32.5, AbilityId.TRACE, AbilityId.DOWNLOAD, AbilityId.ANALYTIC, 515, 85, 80, 90, 105, 95, 60, 45, 50, 180, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.STANTLER, 2, false, false, false, "Big Horn Pokémon", PokemonType.NORMAL, null, 1.4, 71.2, AbilityId.INTIMIDATE, AbilityId.FRISK, AbilityId.SAP_SIPPER, 465, 73, 95, 62, 85, 65, 85, 45, 70, 163, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.SMEARGLE, 2, false, false, false, "Painter Pokémon", PokemonType.NORMAL, null, 1.2, 58, AbilityId.OWN_TEMPO, AbilityId.TECHNICIAN, AbilityId.MOODY, 250, 55, 20, 35, 20, 45, 75, 45, 70, 88, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.TYROGUE, 2, false, false, false, "Scuffle Pokémon", PokemonType.FIGHTING, null, 0.7, 21, AbilityId.GUTS, AbilityId.STEADFAST, AbilityId.VITAL_SPIRIT, 210, 35, 35, 35, 35, 35, 35, 75, 50, 42, GrowthRate.MEDIUM_FAST, 100, false), - new PokemonSpecies(SpeciesId.HITMONTOP, 2, false, false, false, "Handstand Pokémon", PokemonType.FIGHTING, null, 1.4, 48, AbilityId.INTIMIDATE, AbilityId.TECHNICIAN, AbilityId.STEADFAST, 455, 50, 95, 95, 35, 110, 70, 45, 50, 159, GrowthRate.MEDIUM_FAST, 100, false), - new PokemonSpecies(SpeciesId.SMOOCHUM, 2, false, false, false, "Kiss Pokémon", PokemonType.ICE, PokemonType.PSYCHIC, 0.4, 6, AbilityId.OBLIVIOUS, AbilityId.FOREWARN, AbilityId.HYDRATION, 305, 45, 30, 15, 85, 65, 65, 45, 50, 61, GrowthRate.MEDIUM_FAST, 0, false), - new PokemonSpecies(SpeciesId.ELEKID, 2, false, false, false, "Electric Pokémon", PokemonType.ELECTRIC, null, 0.6, 23.5, AbilityId.STATIC, AbilityId.NONE, AbilityId.VITAL_SPIRIT, 360, 45, 63, 37, 65, 55, 95, 45, 50, 72, GrowthRate.MEDIUM_FAST, 75, false), - new PokemonSpecies(SpeciesId.MAGBY, 2, false, false, false, "Live Coal Pokémon", PokemonType.FIRE, null, 0.7, 21.4, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.VITAL_SPIRIT, 365, 45, 75, 37, 70, 55, 83, 45, 50, 73, GrowthRate.MEDIUM_FAST, 75, false), - new PokemonSpecies(SpeciesId.MILTANK, 2, false, false, false, "Milk Cow Pokémon", PokemonType.NORMAL, null, 1.2, 75.5, AbilityId.THICK_FAT, AbilityId.SCRAPPY, AbilityId.SAP_SIPPER, 490, 95, 80, 105, 40, 70, 100, 45, 50, 172, GrowthRate.SLOW, 0, false), - new PokemonSpecies(SpeciesId.BLISSEY, 2, false, false, false, "Happiness Pokémon", PokemonType.NORMAL, null, 1.5, 46.8, AbilityId.NATURAL_CURE, AbilityId.SERENE_GRACE, AbilityId.HEALER, 540, 255, 10, 10, 75, 135, 55, 30, 140, 608, GrowthRate.FAST, 0, false), - new PokemonSpecies(SpeciesId.RAIKOU, 2, true, false, false, "Thunder Pokémon", PokemonType.ELECTRIC, null, 1.9, 178, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.INNER_FOCUS, 580, 90, 85, 75, 115, 100, 115, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.ENTEI, 2, true, false, false, "Volcano Pokémon", PokemonType.FIRE, null, 2.1, 198, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.INNER_FOCUS, 580, 115, 115, 85, 90, 75, 100, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.SUICUNE, 2, true, false, false, "Aurora Pokémon", PokemonType.WATER, null, 2, 187, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.INNER_FOCUS, 580, 100, 75, 115, 90, 115, 85, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.LARVITAR, 2, false, false, false, "Rock Skin Pokémon", PokemonType.ROCK, PokemonType.GROUND, 0.6, 72, AbilityId.GUTS, AbilityId.NONE, AbilityId.SAND_VEIL, 300, 50, 64, 50, 45, 50, 41, 45, 35, 60, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.PUPITAR, 2, false, false, false, "Hard Shell Pokémon", PokemonType.ROCK, PokemonType.GROUND, 1.2, 152, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.SHED_SKIN, 410, 70, 84, 70, 65, 70, 51, 45, 35, 144, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.TYRANITAR, 2, false, false, false, "Armor Pokémon", PokemonType.ROCK, PokemonType.DARK, 2, 202, AbilityId.SAND_STREAM, AbilityId.NONE, AbilityId.UNNERVE, 600, 100, 134, 110, 95, 100, 61, 45, 35, 300, GrowthRate.SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.ROCK, PokemonType.DARK, 2, 202, AbilityId.SAND_STREAM, AbilityId.NONE, AbilityId.UNNERVE, 600, 100, 134, 110, 95, 100, 61, 45, 35, 300, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.ROCK, PokemonType.DARK, 2.5, 255, AbilityId.SAND_STREAM, AbilityId.NONE, AbilityId.SAND_STREAM, 700, 100, 164, 150, 95, 120, 71, 45, 35, 300), - ), - new PokemonSpecies(SpeciesId.LUGIA, 2, false, true, false, "Diving Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 5.2, 216, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.MULTISCALE, 680, 106, 90, 130, 90, 154, 110, 3, 0, 340, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.HO_OH, 2, false, true, false, "Rainbow Pokémon", PokemonType.FIRE, PokemonType.FLYING, 3.8, 199, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.REGENERATOR, 680, 106, 130, 90, 110, 154, 90, 3, 0, 340, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.CELEBI, 2, false, false, true, "Time Travel Pokémon", PokemonType.PSYCHIC, PokemonType.GRASS, 0.6, 5, AbilityId.NATURAL_CURE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 45, 100, 300, GrowthRate.MEDIUM_SLOW, null, false), - new PokemonSpecies(SpeciesId.TREECKO, 3, false, false, false, "Wood Gecko Pokémon", PokemonType.GRASS, null, 0.5, 5, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.UNBURDEN, 310, 40, 45, 35, 65, 55, 70, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.GROVYLE, 3, false, false, false, "Wood Gecko Pokémon", PokemonType.GRASS, null, 0.9, 21.6, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.UNBURDEN, 405, 50, 65, 45, 85, 65, 95, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.SCEPTILE, 3, false, false, false, "Forest Pokémon", PokemonType.GRASS, null, 1.7, 52.2, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.UNBURDEN, 530, 70, 85, 65, 105, 85, 120, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, - new PokemonForm("Normal", "", PokemonType.GRASS, null, 1.7, 52.2, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.UNBURDEN, 530, 70, 85, 65, 105, 85, 120, 45, 50, 265, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.GRASS, PokemonType.DRAGON, 1.9, 55.2, AbilityId.LIGHTNING_ROD, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 630, 70, 110, 75, 145, 85, 145, 45, 50, 265), - ), - new PokemonSpecies(SpeciesId.TORCHIC, 3, false, false, false, "Chick Pokémon", PokemonType.FIRE, null, 0.4, 2.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SPEED_BOOST, 310, 45, 60, 40, 70, 50, 45, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, true), - new PokemonSpecies(SpeciesId.COMBUSKEN, 3, false, false, false, "Young Fowl Pokémon", PokemonType.FIRE, PokemonType.FIGHTING, 0.9, 19.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SPEED_BOOST, 405, 60, 85, 60, 85, 60, 55, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, true), - new PokemonSpecies(SpeciesId.BLAZIKEN, 3, false, false, false, "Blaze Pokémon", PokemonType.FIRE, PokemonType.FIGHTING, 1.9, 52, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SPEED_BOOST, 530, 80, 120, 70, 110, 70, 80, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, true, true, - new PokemonForm("Normal", "", PokemonType.FIRE, PokemonType.FIGHTING, 1.9, 52, AbilityId.BLAZE, AbilityId.NONE, AbilityId.SPEED_BOOST, 530, 80, 120, 70, 110, 70, 80, 45, 50, 265, true, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.FIRE, PokemonType.FIGHTING, 1.9, 52, AbilityId.SPEED_BOOST, AbilityId.NONE, AbilityId.SPEED_BOOST, 630, 80, 160, 80, 130, 80, 100, 45, 50, 265, true), - ), - new PokemonSpecies(SpeciesId.MUDKIP, 3, false, false, false, "Mud Fish Pokémon", PokemonType.WATER, null, 0.4, 7.6, AbilityId.TORRENT, AbilityId.NONE, AbilityId.DAMP, 310, 50, 70, 50, 50, 50, 40, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.MARSHTOMP, 3, false, false, false, "Mud Fish Pokémon", PokemonType.WATER, PokemonType.GROUND, 0.7, 28, AbilityId.TORRENT, AbilityId.NONE, AbilityId.DAMP, 405, 70, 85, 70, 60, 70, 50, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.SWAMPERT, 3, false, false, false, "Mud Fish Pokémon", PokemonType.WATER, PokemonType.GROUND, 1.5, 81.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.DAMP, 535, 100, 110, 90, 85, 90, 60, 45, 50, 268, GrowthRate.MEDIUM_SLOW, 87.5, false, true, - new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.GROUND, 1.5, 81.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.DAMP, 535, 100, 110, 90, 85, 90, 60, 45, 50, 268, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.WATER, PokemonType.GROUND, 1.9, 102, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.SWIFT_SWIM, 635, 100, 150, 110, 95, 110, 70, 45, 50, 268), - ), - new PokemonSpecies(SpeciesId.POOCHYENA, 3, false, false, false, "Bite Pokémon", PokemonType.DARK, null, 0.5, 13.6, AbilityId.RUN_AWAY, AbilityId.QUICK_FEET, AbilityId.RATTLED, 220, 35, 55, 35, 30, 30, 35, 255, 70, 56, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MIGHTYENA, 3, false, false, false, "Bite Pokémon", PokemonType.DARK, null, 1, 37, AbilityId.INTIMIDATE, AbilityId.QUICK_FEET, AbilityId.MOXIE, 420, 70, 90, 70, 60, 60, 70, 127, 70, 147, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ZIGZAGOON, 3, false, false, false, "Tiny Raccoon Pokémon", PokemonType.NORMAL, null, 0.4, 17.5, AbilityId.PICKUP, AbilityId.GLUTTONY, AbilityId.QUICK_FEET, 240, 38, 30, 41, 30, 41, 60, 255, 50, 56, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.LINOONE, 3, false, false, false, "Rushing Pokémon", PokemonType.NORMAL, null, 0.5, 32.5, AbilityId.PICKUP, AbilityId.GLUTTONY, AbilityId.QUICK_FEET, 420, 78, 70, 61, 50, 61, 100, 90, 50, 147, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.WURMPLE, 3, false, false, false, "Worm Pokémon", PokemonType.BUG, null, 0.3, 3.6, AbilityId.SHIELD_DUST, AbilityId.NONE, AbilityId.RUN_AWAY, 195, 45, 45, 35, 20, 30, 20, 255, 70, 56, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SILCOON, 3, false, false, false, "Cocoon Pokémon", PokemonType.BUG, null, 0.6, 10, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.SHED_SKIN, 205, 50, 35, 55, 25, 25, 15, 120, 70, 72, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BEAUTIFLY, 3, false, false, false, "Butterfly Pokémon", PokemonType.BUG, PokemonType.FLYING, 1, 28.4, AbilityId.SWARM, AbilityId.NONE, AbilityId.RIVALRY, 395, 60, 70, 50, 100, 50, 65, 45, 70, 198, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.CASCOON, 3, false, false, false, "Cocoon Pokémon", PokemonType.BUG, null, 0.7, 11.5, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.SHED_SKIN, 205, 50, 35, 55, 25, 25, 15, 120, 70, 72, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DUSTOX, 3, false, false, false, "Poison Moth Pokémon", PokemonType.BUG, PokemonType.POISON, 1.2, 31.6, AbilityId.SHIELD_DUST, AbilityId.NONE, AbilityId.COMPOUND_EYES, 385, 60, 50, 70, 50, 90, 65, 45, 70, 193, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.LOTAD, 3, false, false, false, "Water Weed Pokémon", PokemonType.WATER, PokemonType.GRASS, 0.5, 2.6, AbilityId.SWIFT_SWIM, AbilityId.RAIN_DISH, AbilityId.OWN_TEMPO, 220, 40, 30, 30, 40, 50, 30, 255, 50, 44, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.LOMBRE, 3, false, false, false, "Jolly Pokémon", PokemonType.WATER, PokemonType.GRASS, 1.2, 32.5, AbilityId.SWIFT_SWIM, AbilityId.RAIN_DISH, AbilityId.OWN_TEMPO, 340, 60, 50, 50, 60, 70, 50, 120, 50, 119, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.LUDICOLO, 3, false, false, false, "Carefree Pokémon", PokemonType.WATER, PokemonType.GRASS, 1.5, 55, AbilityId.SWIFT_SWIM, AbilityId.RAIN_DISH, AbilityId.OWN_TEMPO, 480, 80, 70, 70, 90, 100, 70, 45, 50, 240, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.SEEDOT, 3, false, false, false, "Acorn Pokémon", PokemonType.GRASS, null, 0.5, 4, AbilityId.CHLOROPHYLL, AbilityId.EARLY_BIRD, AbilityId.PICKPOCKET, 220, 40, 40, 50, 30, 30, 30, 255, 50, 44, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.NUZLEAF, 3, false, false, false, "Wily Pokémon", PokemonType.GRASS, PokemonType.DARK, 1, 28, AbilityId.CHLOROPHYLL, AbilityId.EARLY_BIRD, AbilityId.PICKPOCKET, 340, 70, 70, 40, 60, 40, 60, 120, 50, 119, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.SHIFTRY, 3, false, false, false, "Wicked Pokémon", PokemonType.GRASS, PokemonType.DARK, 1.3, 59.6, AbilityId.CHLOROPHYLL, AbilityId.WIND_RIDER, AbilityId.PICKPOCKET, 480, 90, 100, 60, 90, 60, 80, 45, 50, 240, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.TAILLOW, 3, false, false, false, "Tiny Swallow Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 2.3, AbilityId.GUTS, AbilityId.NONE, AbilityId.SCRAPPY, 270, 40, 55, 30, 30, 30, 85, 200, 70, 54, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SWELLOW, 3, false, false, false, "Swallow Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.7, 19.8, AbilityId.GUTS, AbilityId.NONE, AbilityId.SCRAPPY, 455, 60, 85, 60, 75, 50, 125, 45, 70, 159, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.WINGULL, 3, false, false, false, "Seagull Pokémon", PokemonType.WATER, PokemonType.FLYING, 0.6, 9.5, AbilityId.KEEN_EYE, AbilityId.HYDRATION, AbilityId.RAIN_DISH, 270, 40, 30, 30, 55, 30, 85, 190, 50, 54, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PELIPPER, 3, false, false, false, "Water Bird Pokémon", PokemonType.WATER, PokemonType.FLYING, 1.2, 28, AbilityId.KEEN_EYE, AbilityId.DRIZZLE, AbilityId.RAIN_DISH, 440, 60, 50, 100, 95, 70, 65, 45, 50, 154, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.RALTS, 3, false, false, false, "Feeling Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 0.4, 6.6, AbilityId.SYNCHRONIZE, AbilityId.TRACE, AbilityId.TELEPATHY, 198, 28, 25, 25, 45, 35, 40, 235, 35, 40, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.KIRLIA, 3, false, false, false, "Emotion Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 0.8, 20.2, AbilityId.SYNCHRONIZE, AbilityId.TRACE, AbilityId.TELEPATHY, 278, 38, 35, 35, 65, 55, 50, 120, 35, 97, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.GARDEVOIR, 3, false, false, false, "Embrace Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.6, 48.4, AbilityId.SYNCHRONIZE, AbilityId.TRACE, AbilityId.TELEPATHY, 518, 68, 65, 65, 125, 115, 80, 45, 35, 259, GrowthRate.SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.6, 48.4, AbilityId.SYNCHRONIZE, AbilityId.TRACE, AbilityId.TELEPATHY, 518, 68, 65, 65, 125, 115, 80, 45, 35, 259, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.PSYCHIC, PokemonType.FAIRY, 1.6, 48.4, AbilityId.PIXILATE, AbilityId.PIXILATE, AbilityId.PIXILATE, 618, 68, 85, 65, 165, 135, 100, 45, 35, 259), - ), - new PokemonSpecies(SpeciesId.SURSKIT, 3, false, false, false, "Pond Skater Pokémon", PokemonType.BUG, PokemonType.WATER, 0.5, 1.7, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.RAIN_DISH, 269, 40, 30, 32, 50, 52, 65, 200, 70, 54, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MASQUERAIN, 3, false, false, false, "Eyeball Pokémon", PokemonType.BUG, PokemonType.FLYING, 0.8, 3.6, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.UNNERVE, 454, 70, 60, 62, 100, 82, 80, 75, 70, 159, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SHROOMISH, 3, false, false, false, "Mushroom Pokémon", PokemonType.GRASS, null, 0.4, 4.5, AbilityId.EFFECT_SPORE, AbilityId.POISON_HEAL, AbilityId.QUICK_FEET, 295, 60, 40, 60, 40, 60, 35, 255, 70, 59, GrowthRate.FLUCTUATING, 50, false), - new PokemonSpecies(SpeciesId.BRELOOM, 3, false, false, false, "Mushroom Pokémon", PokemonType.GRASS, PokemonType.FIGHTING, 1.2, 39.2, AbilityId.EFFECT_SPORE, AbilityId.POISON_HEAL, AbilityId.TECHNICIAN, 460, 60, 130, 80, 60, 60, 70, 90, 70, 161, GrowthRate.FLUCTUATING, 50, false), - new PokemonSpecies(SpeciesId.SLAKOTH, 3, false, false, false, "Slacker Pokémon", PokemonType.NORMAL, null, 0.8, 24, AbilityId.TRUANT, AbilityId.NONE, AbilityId.STALL, 280, 60, 60, 60, 35, 35, 30, 255, 70, 56, GrowthRate.SLOW, 50, false), //Custom Hidden - new PokemonSpecies(SpeciesId.VIGOROTH, 3, false, false, false, "Wild Monkey Pokémon", PokemonType.NORMAL, null, 1.4, 46.5, AbilityId.VITAL_SPIRIT, AbilityId.NONE, AbilityId.INSOMNIA, 440, 80, 80, 80, 55, 55, 90, 120, 70, 154, GrowthRate.SLOW, 50, false), //Custom Hidden - new PokemonSpecies(SpeciesId.SLAKING, 3, false, false, false, "Lazy Pokémon", PokemonType.NORMAL, null, 2, 130.5, AbilityId.TRUANT, AbilityId.NONE, AbilityId.STALL, 670, 150, 160, 100, 95, 65, 100, 45, 70, 285, GrowthRate.SLOW, 50, false), //Custom Hidden - new PokemonSpecies(SpeciesId.NINCADA, 3, false, false, false, "Trainee Pokémon", PokemonType.BUG, PokemonType.GROUND, 0.5, 5.5, AbilityId.COMPOUND_EYES, AbilityId.NONE, AbilityId.RUN_AWAY, 266, 31, 45, 90, 30, 30, 40, 255, 50, 53, GrowthRate.ERRATIC, 50, false), - new PokemonSpecies(SpeciesId.NINJASK, 3, false, false, false, "Ninja Pokémon", PokemonType.BUG, PokemonType.FLYING, 0.8, 12, AbilityId.SPEED_BOOST, AbilityId.NONE, AbilityId.INFILTRATOR, 456, 61, 90, 45, 50, 50, 160, 120, 50, 160, GrowthRate.ERRATIC, 50, false), - new PokemonSpecies(SpeciesId.SHEDINJA, 3, false, false, false, "Shed Pokémon", PokemonType.BUG, PokemonType.GHOST, 0.8, 1.2, AbilityId.WONDER_GUARD, AbilityId.NONE, AbilityId.NONE, 236, 1, 90, 45, 30, 30, 40, 45, 50, 83, GrowthRate.ERRATIC, null, false), - new PokemonSpecies(SpeciesId.WHISMUR, 3, false, false, false, "Whisper Pokémon", PokemonType.NORMAL, null, 0.6, 16.3, AbilityId.SOUNDPROOF, AbilityId.NONE, AbilityId.RATTLED, 240, 64, 51, 23, 51, 23, 28, 190, 50, 48, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.LOUDRED, 3, false, false, false, "Big Voice Pokémon", PokemonType.NORMAL, null, 1, 40.5, AbilityId.SOUNDPROOF, AbilityId.NONE, AbilityId.SCRAPPY, 360, 84, 71, 43, 71, 43, 48, 120, 50, 126, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.EXPLOUD, 3, false, false, false, "Loud Noise Pokémon", PokemonType.NORMAL, null, 1.5, 84, AbilityId.SOUNDPROOF, AbilityId.NONE, AbilityId.SCRAPPY, 490, 104, 91, 63, 91, 73, 68, 45, 50, 245, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.MAKUHITA, 3, false, false, false, "Guts Pokémon", PokemonType.FIGHTING, null, 1, 86.4, AbilityId.THICK_FAT, AbilityId.GUTS, AbilityId.SHEER_FORCE, 237, 72, 60, 30, 20, 30, 25, 180, 70, 47, GrowthRate.FLUCTUATING, 75, false), - new PokemonSpecies(SpeciesId.HARIYAMA, 3, false, false, false, "Arm Thrust Pokémon", PokemonType.FIGHTING, null, 2.3, 253.8, AbilityId.THICK_FAT, AbilityId.GUTS, AbilityId.SHEER_FORCE, 474, 144, 120, 60, 40, 60, 50, 200, 70, 166, GrowthRate.FLUCTUATING, 75, false), - new PokemonSpecies(SpeciesId.AZURILL, 3, false, false, false, "Polka Dot Pokémon", PokemonType.NORMAL, PokemonType.FAIRY, 0.2, 2, AbilityId.THICK_FAT, AbilityId.HUGE_POWER, AbilityId.SAP_SIPPER, 190, 50, 20, 40, 20, 40, 20, 150, 50, 38, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.NOSEPASS, 3, false, false, false, "Compass Pokémon", PokemonType.ROCK, null, 1, 97, AbilityId.STURDY, AbilityId.MAGNET_PULL, AbilityId.SAND_FORCE, 375, 30, 45, 135, 45, 90, 30, 255, 70, 75, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SKITTY, 3, false, false, false, "Kitten Pokémon", PokemonType.NORMAL, null, 0.6, 11, AbilityId.CUTE_CHARM, AbilityId.NORMALIZE, AbilityId.WONDER_SKIN, 260, 50, 45, 45, 35, 35, 50, 255, 70, 52, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.DELCATTY, 3, false, false, false, "Prim Pokémon", PokemonType.NORMAL, null, 1.1, 32.6, AbilityId.CUTE_CHARM, AbilityId.NORMALIZE, AbilityId.WONDER_SKIN, 400, 70, 65, 65, 55, 55, 90, 60, 70, 140, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.SABLEYE, 3, false, false, false, "Darkness Pokémon", PokemonType.DARK, PokemonType.GHOST, 0.5, 11, AbilityId.KEEN_EYE, AbilityId.STALL, AbilityId.PRANKSTER, 380, 50, 75, 75, 65, 65, 50, 45, 35, 133, GrowthRate.MEDIUM_SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.DARK, PokemonType.GHOST, 0.5, 11, AbilityId.KEEN_EYE, AbilityId.STALL, AbilityId.PRANKSTER, 380, 50, 75, 75, 65, 65, 50, 45, 35, 133, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DARK, PokemonType.GHOST, 0.5, 161, AbilityId.MAGIC_BOUNCE, AbilityId.MAGIC_BOUNCE, AbilityId.MAGIC_BOUNCE, 480, 50, 85, 125, 85, 115, 20, 45, 35, 133), - ), - new PokemonSpecies(SpeciesId.MAWILE, 3, false, false, false, "Deceiver Pokémon", PokemonType.STEEL, PokemonType.FAIRY, 0.6, 11.5, AbilityId.HYPER_CUTTER, AbilityId.INTIMIDATE, AbilityId.SHEER_FORCE, 380, 50, 85, 85, 55, 55, 50, 45, 50, 133, GrowthRate.FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.FAIRY, 0.6, 11.5, AbilityId.HYPER_CUTTER, AbilityId.INTIMIDATE, AbilityId.SHEER_FORCE, 380, 50, 85, 85, 55, 55, 50, 45, 50, 133, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.STEEL, PokemonType.FAIRY, 1, 23.5, AbilityId.HUGE_POWER, AbilityId.HUGE_POWER, AbilityId.HUGE_POWER, 480, 50, 105, 125, 55, 95, 50, 45, 50, 133), - ), - new PokemonSpecies(SpeciesId.ARON, 3, false, false, false, "Iron Armor Pokémon", PokemonType.STEEL, PokemonType.ROCK, 0.4, 60, AbilityId.STURDY, AbilityId.ROCK_HEAD, AbilityId.HEAVY_METAL, 330, 50, 70, 100, 40, 40, 30, 180, 35, 66, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.LAIRON, 3, false, false, false, "Iron Armor Pokémon", PokemonType.STEEL, PokemonType.ROCK, 0.9, 120, AbilityId.STURDY, AbilityId.ROCK_HEAD, AbilityId.HEAVY_METAL, 430, 60, 90, 140, 50, 50, 40, 90, 35, 151, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.AGGRON, 3, false, false, false, "Iron Armor Pokémon", PokemonType.STEEL, PokemonType.ROCK, 2.1, 360, AbilityId.STURDY, AbilityId.ROCK_HEAD, AbilityId.HEAVY_METAL, 530, 70, 110, 180, 60, 60, 50, 45, 35, 265, GrowthRate.SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.ROCK, 2.1, 360, AbilityId.STURDY, AbilityId.ROCK_HEAD, AbilityId.HEAVY_METAL, 530, 70, 110, 180, 60, 60, 50, 45, 35, 265, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.STEEL, null, 2.2, 395, AbilityId.FILTER, AbilityId.FILTER, AbilityId.FILTER, 630, 70, 140, 230, 60, 80, 50, 45, 35, 265), - ), - new PokemonSpecies(SpeciesId.MEDITITE, 3, false, false, false, "Meditate Pokémon", PokemonType.FIGHTING, PokemonType.PSYCHIC, 0.6, 11.2, AbilityId.PURE_POWER, AbilityId.NONE, AbilityId.TELEPATHY, 280, 30, 40, 55, 40, 55, 60, 180, 70, 56, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.MEDICHAM, 3, false, false, false, "Meditate Pokémon", PokemonType.FIGHTING, PokemonType.PSYCHIC, 1.3, 31.5, AbilityId.PURE_POWER, AbilityId.NONE, AbilityId.TELEPATHY, 410, 60, 60, 75, 60, 75, 80, 90, 70, 144, GrowthRate.MEDIUM_FAST, 50, true, true, - new PokemonForm("Normal", "", PokemonType.FIGHTING, PokemonType.PSYCHIC, 1.3, 31.5, AbilityId.PURE_POWER, AbilityId.NONE, AbilityId.TELEPATHY, 410, 60, 60, 75, 60, 75, 80, 90, 70, 144, true, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.FIGHTING, PokemonType.PSYCHIC, 1.3, 31.5, AbilityId.PURE_POWER, AbilityId.NONE, AbilityId.PURE_POWER, 510, 60, 100, 85, 80, 85, 100, 90, 70, 144, true), - ), - new PokemonSpecies(SpeciesId.ELECTRIKE, 3, false, false, false, "Lightning Pokémon", PokemonType.ELECTRIC, null, 0.6, 15.2, AbilityId.STATIC, AbilityId.LIGHTNING_ROD, AbilityId.MINUS, 295, 40, 45, 40, 65, 40, 65, 120, 50, 59, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.MANECTRIC, 3, false, false, false, "Discharge Pokémon", PokemonType.ELECTRIC, null, 1.5, 40.2, AbilityId.STATIC, AbilityId.LIGHTNING_ROD, AbilityId.MINUS, 475, 70, 75, 60, 105, 60, 105, 45, 50, 166, GrowthRate.SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.5, 40.2, AbilityId.STATIC, AbilityId.LIGHTNING_ROD, AbilityId.MINUS, 475, 70, 75, 60, 105, 60, 105, 45, 50, 166, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.ELECTRIC, null, 1.8, 44, AbilityId.INTIMIDATE, AbilityId.INTIMIDATE, AbilityId.INTIMIDATE, 575, 70, 75, 80, 135, 80, 135, 45, 50, 166), - ), - new PokemonSpecies(SpeciesId.PLUSLE, 3, false, false, false, "Cheering Pokémon", PokemonType.ELECTRIC, null, 0.4, 4.2, AbilityId.PLUS, AbilityId.NONE, AbilityId.LIGHTNING_ROD, 405, 60, 50, 40, 85, 75, 95, 200, 70, 142, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MINUN, 3, false, false, false, "Cheering Pokémon", PokemonType.ELECTRIC, null, 0.4, 4.2, AbilityId.MINUS, AbilityId.NONE, AbilityId.VOLT_ABSORB, 405, 60, 40, 50, 75, 85, 95, 200, 70, 142, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.VOLBEAT, 3, false, false, false, "Firefly Pokémon", PokemonType.BUG, null, 0.7, 17.7, AbilityId.ILLUMINATE, AbilityId.SWARM, AbilityId.PRANKSTER, 430, 65, 73, 75, 47, 85, 85, 150, 70, 151, GrowthRate.ERRATIC, 100, false), - new PokemonSpecies(SpeciesId.ILLUMISE, 3, false, false, false, "Firefly Pokémon", PokemonType.BUG, null, 0.6, 17.7, AbilityId.OBLIVIOUS, AbilityId.TINTED_LENS, AbilityId.PRANKSTER, 430, 65, 47, 75, 73, 85, 85, 150, 70, 151, GrowthRate.FLUCTUATING, 0, false), - new PokemonSpecies(SpeciesId.ROSELIA, 3, false, false, false, "Thorn Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.3, 2, AbilityId.NATURAL_CURE, AbilityId.POISON_POINT, AbilityId.LEAF_GUARD, 400, 50, 60, 45, 100, 80, 65, 150, 50, 140, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.GULPIN, 3, false, false, false, "Stomach Pokémon", PokemonType.POISON, null, 0.4, 10.3, AbilityId.LIQUID_OOZE, AbilityId.STICKY_HOLD, AbilityId.GLUTTONY, 302, 70, 43, 53, 43, 53, 40, 225, 70, 60, GrowthRate.FLUCTUATING, 50, true), - new PokemonSpecies(SpeciesId.SWALOT, 3, false, false, false, "Poison Bag Pokémon", PokemonType.POISON, null, 1.7, 80, AbilityId.LIQUID_OOZE, AbilityId.STICKY_HOLD, AbilityId.GLUTTONY, 467, 100, 73, 83, 73, 83, 55, 75, 70, 163, GrowthRate.FLUCTUATING, 50, true), - new PokemonSpecies(SpeciesId.CARVANHA, 3, false, false, false, "Savage Pokémon", PokemonType.WATER, PokemonType.DARK, 0.8, 20.8, AbilityId.ROUGH_SKIN, AbilityId.NONE, AbilityId.SPEED_BOOST, 305, 45, 90, 20, 65, 20, 65, 225, 35, 61, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.SHARPEDO, 3, false, false, false, "Brutal Pokémon", PokemonType.WATER, PokemonType.DARK, 1.8, 88.8, AbilityId.ROUGH_SKIN, AbilityId.NONE, AbilityId.SPEED_BOOST, 460, 70, 120, 40, 95, 40, 95, 60, 35, 161, GrowthRate.SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.DARK, 1.8, 88.8, AbilityId.ROUGH_SKIN, AbilityId.NONE, AbilityId.SPEED_BOOST, 460, 70, 120, 40, 95, 40, 95, 60, 35, 161, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.WATER, PokemonType.DARK, 2.5, 130.3, AbilityId.STRONG_JAW, AbilityId.NONE, AbilityId.STRONG_JAW, 560, 70, 140, 70, 110, 65, 105, 60, 35, 161), - ), - new PokemonSpecies(SpeciesId.WAILMER, 3, false, false, false, "Ball Whale Pokémon", PokemonType.WATER, null, 2, 130, AbilityId.WATER_VEIL, AbilityId.OBLIVIOUS, AbilityId.PRESSURE, 400, 130, 70, 35, 70, 35, 60, 125, 50, 80, GrowthRate.FLUCTUATING, 50, false), - new PokemonSpecies(SpeciesId.WAILORD, 3, false, false, false, "Float Whale Pokémon", PokemonType.WATER, null, 14.5, 398, AbilityId.WATER_VEIL, AbilityId.OBLIVIOUS, AbilityId.PRESSURE, 500, 170, 90, 45, 90, 45, 60, 60, 50, 175, GrowthRate.FLUCTUATING, 50, false), - new PokemonSpecies(SpeciesId.NUMEL, 3, false, false, false, "Numb Pokémon", PokemonType.FIRE, PokemonType.GROUND, 0.7, 24, AbilityId.OBLIVIOUS, AbilityId.SIMPLE, AbilityId.OWN_TEMPO, 305, 60, 60, 40, 65, 45, 35, 255, 70, 61, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.CAMERUPT, 3, false, false, false, "Eruption Pokémon", PokemonType.FIRE, PokemonType.GROUND, 1.9, 220, AbilityId.MAGMA_ARMOR, AbilityId.SOLID_ROCK, AbilityId.ANGER_POINT, 460, 70, 100, 70, 105, 75, 40, 150, 70, 161, GrowthRate.MEDIUM_FAST, 50, true, true, - new PokemonForm("Normal", "", PokemonType.FIRE, PokemonType.GROUND, 1.9, 220, AbilityId.MAGMA_ARMOR, AbilityId.SOLID_ROCK, AbilityId.ANGER_POINT, 460, 70, 100, 70, 105, 75, 40, 150, 70, 161, true, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.FIRE, PokemonType.GROUND, 2.5, 320.5, AbilityId.SHEER_FORCE, AbilityId.SHEER_FORCE, AbilityId.SHEER_FORCE, 560, 70, 120, 100, 145, 105, 20, 150, 70, 161), - ), - new PokemonSpecies(SpeciesId.TORKOAL, 3, false, false, false, "Coal Pokémon", PokemonType.FIRE, null, 0.5, 80.4, AbilityId.WHITE_SMOKE, AbilityId.DROUGHT, AbilityId.SHELL_ARMOR, 470, 70, 85, 140, 85, 70, 20, 90, 50, 165, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SPOINK, 3, false, false, false, "Bounce Pokémon", PokemonType.PSYCHIC, null, 0.7, 30.6, AbilityId.THICK_FAT, AbilityId.OWN_TEMPO, AbilityId.GLUTTONY, 330, 60, 25, 35, 70, 80, 60, 255, 70, 66, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.GRUMPIG, 3, false, false, false, "Manipulate Pokémon", PokemonType.PSYCHIC, null, 0.9, 71.5, AbilityId.THICK_FAT, AbilityId.OWN_TEMPO, AbilityId.GLUTTONY, 470, 80, 45, 65, 90, 110, 80, 60, 70, 165, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.SPINDA, 3, false, false, false, "Spot Panda Pokémon", PokemonType.NORMAL, null, 1.1, 5, AbilityId.OWN_TEMPO, AbilityId.TANGLED_FEET, AbilityId.CONTRARY, 360, 60, 60, 60, 60, 60, 60, 255, 70, 126, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.TRAPINCH, 3, false, false, false, "Ant Pit Pokémon", PokemonType.GROUND, null, 0.7, 15, AbilityId.HYPER_CUTTER, AbilityId.ARENA_TRAP, AbilityId.SHEER_FORCE, 290, 45, 100, 45, 45, 45, 10, 255, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.VIBRAVA, 3, false, false, false, "Vibration Pokémon", PokemonType.GROUND, PokemonType.DRAGON, 1.1, 15.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 340, 50, 70, 50, 50, 50, 70, 120, 50, 119, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.FLYGON, 3, false, false, false, "Mystic Pokémon", PokemonType.GROUND, PokemonType.DRAGON, 2, 82, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 520, 80, 100, 80, 80, 80, 100, 45, 50, 260, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.CACNEA, 3, false, false, false, "Cactus Pokémon", PokemonType.GRASS, null, 0.4, 51.3, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.WATER_ABSORB, 335, 50, 85, 40, 85, 40, 35, 190, 35, 67, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.CACTURNE, 3, false, false, false, "Scarecrow Pokémon", PokemonType.GRASS, PokemonType.DARK, 1.3, 77.4, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.WATER_ABSORB, 475, 70, 115, 60, 115, 60, 55, 60, 35, 166, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.SWABLU, 3, false, false, false, "Cotton Bird Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.4, 1.2, AbilityId.NATURAL_CURE, AbilityId.NONE, AbilityId.CLOUD_NINE, 310, 45, 40, 60, 40, 75, 50, 255, 50, 62, GrowthRate.ERRATIC, 50, false), - new PokemonSpecies(SpeciesId.ALTARIA, 3, false, false, false, "Humming Pokémon", PokemonType.DRAGON, PokemonType.FLYING, 1.1, 20.6, AbilityId.NATURAL_CURE, AbilityId.NONE, AbilityId.CLOUD_NINE, 490, 75, 70, 90, 70, 105, 80, 45, 50, 172, GrowthRate.ERRATIC, 50, false, true, - new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.FLYING, 1.1, 20.6, AbilityId.NATURAL_CURE, AbilityId.NONE, AbilityId.CLOUD_NINE, 490, 75, 70, 90, 70, 105, 80, 45, 50, 172, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.FAIRY, 1.5, 20.6, AbilityId.PIXILATE, AbilityId.NONE, AbilityId.PIXILATE, 590, 75, 110, 110, 110, 105, 80, 45, 50, 172), - ), - new PokemonSpecies(SpeciesId.ZANGOOSE, 3, false, false, false, "Cat Ferret Pokémon", PokemonType.NORMAL, null, 1.3, 40.3, AbilityId.IMMUNITY, AbilityId.NONE, AbilityId.TOXIC_BOOST, 458, 73, 115, 60, 60, 60, 90, 90, 70, 160, GrowthRate.ERRATIC, 50, false), - new PokemonSpecies(SpeciesId.SEVIPER, 3, false, false, false, "Fang Snake Pokémon", PokemonType.POISON, null, 2.7, 52.5, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.INFILTRATOR, 458, 73, 100, 60, 100, 60, 65, 90, 70, 160, GrowthRate.FLUCTUATING, 50, false), - new PokemonSpecies(SpeciesId.LUNATONE, 3, false, false, false, "Meteorite Pokémon", PokemonType.ROCK, PokemonType.PSYCHIC, 1, 168, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 460, 90, 55, 65, 95, 85, 70, 45, 50, 161, GrowthRate.FAST, null, false), - new PokemonSpecies(SpeciesId.SOLROCK, 3, false, false, false, "Meteorite Pokémon", PokemonType.ROCK, PokemonType.PSYCHIC, 1.2, 154, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 460, 90, 95, 85, 55, 65, 70, 45, 50, 161, GrowthRate.FAST, null, false), - new PokemonSpecies(SpeciesId.BARBOACH, 3, false, false, false, "Whiskers Pokémon", PokemonType.WATER, PokemonType.GROUND, 0.4, 1.9, AbilityId.OBLIVIOUS, AbilityId.ANTICIPATION, AbilityId.HYDRATION, 288, 50, 48, 43, 46, 41, 60, 190, 50, 58, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.WHISCASH, 3, false, false, false, "Whiskers Pokémon", PokemonType.WATER, PokemonType.GROUND, 0.9, 23.6, AbilityId.OBLIVIOUS, AbilityId.ANTICIPATION, AbilityId.HYDRATION, 468, 110, 78, 73, 76, 71, 60, 75, 50, 164, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CORPHISH, 3, false, false, false, "Ruffian Pokémon", PokemonType.WATER, null, 0.6, 11.5, AbilityId.HYPER_CUTTER, AbilityId.SHELL_ARMOR, AbilityId.ADAPTABILITY, 308, 43, 80, 65, 50, 35, 35, 205, 50, 62, GrowthRate.FLUCTUATING, 50, false), - new PokemonSpecies(SpeciesId.CRAWDAUNT, 3, false, false, false, "Rogue Pokémon", PokemonType.WATER, PokemonType.DARK, 1.1, 32.8, AbilityId.HYPER_CUTTER, AbilityId.SHELL_ARMOR, AbilityId.ADAPTABILITY, 468, 63, 120, 85, 90, 55, 55, 155, 50, 164, GrowthRate.FLUCTUATING, 50, false), - new PokemonSpecies(SpeciesId.BALTOY, 3, false, false, false, "Clay Doll Pokémon", PokemonType.GROUND, PokemonType.PSYCHIC, 0.5, 21.5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 300, 40, 40, 55, 40, 70, 55, 255, 50, 60, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.CLAYDOL, 3, false, false, false, "Clay Doll Pokémon", PokemonType.GROUND, PokemonType.PSYCHIC, 1.5, 108, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 500, 60, 70, 105, 70, 120, 75, 90, 50, 175, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.LILEEP, 3, false, false, false, "Sea Lily Pokémon", PokemonType.ROCK, PokemonType.GRASS, 1, 23.8, AbilityId.SUCTION_CUPS, AbilityId.NONE, AbilityId.STORM_DRAIN, 355, 66, 41, 77, 61, 87, 23, 45, 50, 71, GrowthRate.ERRATIC, 87.5, false), - new PokemonSpecies(SpeciesId.CRADILY, 3, false, false, false, "Barnacle Pokémon", PokemonType.ROCK, PokemonType.GRASS, 1.5, 60.4, AbilityId.SUCTION_CUPS, AbilityId.NONE, AbilityId.STORM_DRAIN, 495, 86, 81, 97, 81, 107, 43, 45, 50, 173, GrowthRate.ERRATIC, 87.5, false), - new PokemonSpecies(SpeciesId.ANORITH, 3, false, false, false, "Old Shrimp Pokémon", PokemonType.ROCK, PokemonType.BUG, 0.7, 12.5, AbilityId.BATTLE_ARMOR, AbilityId.NONE, AbilityId.SWIFT_SWIM, 355, 45, 95, 50, 40, 50, 75, 45, 50, 71, GrowthRate.ERRATIC, 87.5, false), - new PokemonSpecies(SpeciesId.ARMALDO, 3, false, false, false, "Plate Pokémon", PokemonType.ROCK, PokemonType.BUG, 1.5, 68.2, AbilityId.BATTLE_ARMOR, AbilityId.NONE, AbilityId.SWIFT_SWIM, 495, 75, 125, 100, 70, 80, 45, 45, 50, 173, GrowthRate.ERRATIC, 87.5, false), - new PokemonSpecies(SpeciesId.FEEBAS, 3, false, false, false, "Fish Pokémon", PokemonType.WATER, null, 0.6, 7.4, AbilityId.SWIFT_SWIM, AbilityId.OBLIVIOUS, AbilityId.ADAPTABILITY, 200, 20, 15, 20, 10, 55, 80, 255, 50, 40, GrowthRate.ERRATIC, 50, false), - new PokemonSpecies(SpeciesId.MILOTIC, 3, false, false, false, "Tender Pokémon", PokemonType.WATER, null, 6.2, 162, AbilityId.MARVEL_SCALE, AbilityId.COMPETITIVE, AbilityId.CUTE_CHARM, 540, 95, 60, 79, 100, 125, 81, 60, 50, 189, GrowthRate.ERRATIC, 50, true), - new PokemonSpecies(SpeciesId.CASTFORM, 3, false, false, false, "Weather Pokémon", PokemonType.NORMAL, null, 0.3, 0.8, AbilityId.FORECAST, AbilityId.NONE, AbilityId.NONE, 420, 70, 70, 70, 70, 70, 70, 45, 70, 147, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal Form", "", PokemonType.NORMAL, null, 0.3, 0.8, AbilityId.FORECAST, AbilityId.NONE, AbilityId.NONE, 420, 70, 70, 70, 70, 70, 70, 45, 70, 147, false, null, true), - new PokemonForm("Sunny Form", "sunny", PokemonType.FIRE, null, 0.3, 0.8, AbilityId.FORECAST, AbilityId.NONE, AbilityId.NONE, 420, 70, 70, 70, 70, 70, 70, 45, 70, 147), - new PokemonForm("Rainy Form", "rainy", PokemonType.WATER, null, 0.3, 0.8, AbilityId.FORECAST, AbilityId.NONE, AbilityId.NONE, 420, 70, 70, 70, 70, 70, 70, 45, 70, 147), - new PokemonForm("Snowy Form", "snowy", PokemonType.ICE, null, 0.3, 0.8, AbilityId.FORECAST, AbilityId.NONE, AbilityId.NONE, 420, 70, 70, 70, 70, 70, 70, 45, 70, 147), - ), - new PokemonSpecies(SpeciesId.KECLEON, 3, false, false, false, "Color Swap Pokémon", PokemonType.NORMAL, null, 1, 22, AbilityId.COLOR_CHANGE, AbilityId.NONE, AbilityId.PROTEAN, 440, 60, 90, 70, 60, 120, 40, 200, 70, 154, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SHUPPET, 3, false, false, false, "Puppet Pokémon", PokemonType.GHOST, null, 0.6, 2.3, AbilityId.INSOMNIA, AbilityId.FRISK, AbilityId.CURSED_BODY, 295, 44, 75, 35, 63, 33, 45, 225, 35, 59, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.BANETTE, 3, false, false, false, "Marionette Pokémon", PokemonType.GHOST, null, 1.1, 12.5, AbilityId.INSOMNIA, AbilityId.FRISK, AbilityId.CURSED_BODY, 455, 64, 115, 65, 83, 63, 65, 45, 35, 159, GrowthRate.FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.GHOST, null, 1.1, 12.5, AbilityId.INSOMNIA, AbilityId.FRISK, AbilityId.CURSED_BODY, 455, 64, 115, 65, 83, 63, 65, 45, 35, 159, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.GHOST, null, 1.2, 13, AbilityId.PRANKSTER, AbilityId.PRANKSTER, AbilityId.PRANKSTER, 555, 64, 165, 75, 93, 83, 75, 45, 35, 159), - ), - new PokemonSpecies(SpeciesId.DUSKULL, 3, false, false, false, "Requiem Pokémon", PokemonType.GHOST, null, 0.8, 15, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.FRISK, 295, 20, 40, 90, 30, 90, 25, 190, 35, 59, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.DUSCLOPS, 3, false, false, false, "Beckon Pokémon", PokemonType.GHOST, null, 1.6, 30.6, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.FRISK, 455, 40, 70, 130, 60, 130, 25, 90, 35, 159, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.TROPIUS, 3, false, false, false, "Fruit Pokémon", PokemonType.GRASS, PokemonType.FLYING, 2, 100, AbilityId.CHLOROPHYLL, AbilityId.SOLAR_POWER, AbilityId.HARVEST, 460, 99, 68, 83, 72, 87, 51, 200, 70, 161, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.CHIMECHO, 3, false, false, false, "Wind Chime Pokémon", PokemonType.PSYCHIC, null, 0.6, 1, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 455, 75, 50, 80, 95, 90, 65, 45, 70, 159, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.ABSOL, 3, false, false, false, "Disaster Pokémon", PokemonType.DARK, null, 1.2, 47, AbilityId.PRESSURE, AbilityId.SUPER_LUCK, AbilityId.JUSTIFIED, 465, 65, 130, 60, 75, 60, 75, 30, 35, 163, GrowthRate.MEDIUM_SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.DARK, null, 1.2, 47, AbilityId.PRESSURE, AbilityId.SUPER_LUCK, AbilityId.JUSTIFIED, 465, 65, 130, 60, 75, 60, 75, 30, 35, 163, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DARK, null, 1.2, 49, AbilityId.MAGIC_BOUNCE, AbilityId.MAGIC_BOUNCE, AbilityId.MAGIC_BOUNCE, 565, 65, 150, 60, 115, 60, 115, 30, 35, 163), - ), - new PokemonSpecies(SpeciesId.WYNAUT, 3, false, false, false, "Bright Pokémon", PokemonType.PSYCHIC, null, 0.6, 14, AbilityId.SHADOW_TAG, AbilityId.NONE, AbilityId.TELEPATHY, 260, 95, 23, 48, 23, 48, 23, 125, 50, 52, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SNORUNT, 3, false, false, false, "Snow Hat Pokémon", PokemonType.ICE, null, 0.7, 16.8, AbilityId.INNER_FOCUS, AbilityId.ICE_BODY, AbilityId.MOODY, 300, 50, 50, 50, 50, 50, 50, 190, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GLALIE, 3, false, false, false, "Face Pokémon", PokemonType.ICE, null, 1.5, 256.5, AbilityId.INNER_FOCUS, AbilityId.ICE_BODY, AbilityId.MOODY, 480, 80, 80, 80, 80, 80, 80, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.ICE, null, 1.5, 256.5, AbilityId.INNER_FOCUS, AbilityId.ICE_BODY, AbilityId.MOODY, 480, 80, 80, 80, 80, 80, 80, 75, 50, 168, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.ICE, null, 2.1, 350.2, AbilityId.REFRIGERATE, AbilityId.REFRIGERATE, AbilityId.REFRIGERATE, 580, 80, 120, 80, 120, 80, 100, 75, 50, 168), - ), - new PokemonSpecies(SpeciesId.SPHEAL, 3, false, false, false, "Clap Pokémon", PokemonType.ICE, PokemonType.WATER, 0.8, 39.5, AbilityId.THICK_FAT, AbilityId.ICE_BODY, AbilityId.OBLIVIOUS, 290, 70, 40, 50, 55, 50, 25, 255, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SEALEO, 3, false, false, false, "Ball Roll Pokémon", PokemonType.ICE, PokemonType.WATER, 1.1, 87.6, AbilityId.THICK_FAT, AbilityId.ICE_BODY, AbilityId.OBLIVIOUS, 410, 90, 60, 70, 75, 70, 45, 120, 50, 144, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.WALREIN, 3, false, false, false, "Ice Break Pokémon", PokemonType.ICE, PokemonType.WATER, 1.4, 150.6, AbilityId.THICK_FAT, AbilityId.ICE_BODY, AbilityId.OBLIVIOUS, 530, 110, 80, 90, 95, 90, 65, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.CLAMPERL, 3, false, false, false, "Bivalve Pokémon", PokemonType.WATER, null, 0.4, 52.5, AbilityId.SHELL_ARMOR, AbilityId.NONE, AbilityId.RATTLED, 345, 35, 64, 85, 74, 55, 32, 255, 70, 69, GrowthRate.ERRATIC, 50, false), - new PokemonSpecies(SpeciesId.HUNTAIL, 3, false, false, false, "Deep Sea Pokémon", PokemonType.WATER, null, 1.7, 27, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.WATER_VEIL, 485, 55, 104, 105, 94, 75, 52, 60, 70, 170, GrowthRate.ERRATIC, 50, false), - new PokemonSpecies(SpeciesId.GOREBYSS, 3, false, false, false, "South Sea Pokémon", PokemonType.WATER, null, 1.8, 22.6, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.HYDRATION, 485, 55, 84, 105, 114, 75, 52, 60, 70, 170, GrowthRate.ERRATIC, 50, false), - new PokemonSpecies(SpeciesId.RELICANTH, 3, false, false, false, "Longevity Pokémon", PokemonType.WATER, PokemonType.ROCK, 1, 23.4, AbilityId.SWIFT_SWIM, AbilityId.ROCK_HEAD, AbilityId.STURDY, 485, 100, 90, 130, 45, 65, 55, 25, 50, 170, GrowthRate.SLOW, 87.5, true), - new PokemonSpecies(SpeciesId.LUVDISC, 3, false, false, false, "Rendezvous Pokémon", PokemonType.WATER, null, 0.6, 8.7, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.HYDRATION, 330, 43, 30, 55, 40, 65, 97, 225, 70, 116, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.BAGON, 3, false, false, false, "Rock Head Pokémon", PokemonType.DRAGON, null, 0.6, 42.1, AbilityId.ROCK_HEAD, AbilityId.NONE, AbilityId.SHEER_FORCE, 300, 45, 75, 60, 40, 30, 50, 45, 35, 60, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.SHELGON, 3, false, false, false, "Endurance Pokémon", PokemonType.DRAGON, null, 1.1, 110.5, AbilityId.ROCK_HEAD, AbilityId.NONE, AbilityId.OVERCOAT, 420, 65, 95, 100, 60, 50, 50, 45, 35, 147, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.SALAMENCE, 3, false, false, false, "Dragon Pokémon", PokemonType.DRAGON, PokemonType.FLYING, 1.5, 102.6, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.MOXIE, 600, 95, 135, 80, 110, 80, 100, 45, 35, 300, GrowthRate.SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.FLYING, 1.5, 102.6, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.MOXIE, 600, 95, 135, 80, 110, 80, 100, 45, 35, 300, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.FLYING, 1.8, 112.6, AbilityId.AERILATE, AbilityId.NONE, AbilityId.AERILATE, 700, 95, 145, 130, 120, 90, 120, 45, 35, 300), - ), - new PokemonSpecies(SpeciesId.BELDUM, 3, false, false, false, "Iron Ball Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 0.6, 95.2, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.LIGHT_METAL, 300, 40, 55, 80, 35, 60, 30, 45, 35, 60, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Frigibax - new PokemonSpecies(SpeciesId.METANG, 3, false, false, false, "Iron Claw Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 1.2, 202.5, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.LIGHT_METAL, 420, 60, 75, 100, 55, 80, 50, 25, 35, 147, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Arctibax - new PokemonSpecies(SpeciesId.METAGROSS, 3, false, false, false, "Iron Leg Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 1.6, 550, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.LIGHT_METAL, 600, 80, 135, 130, 95, 90, 70, 10, 35, 300, GrowthRate.SLOW, null, false, true, //Custom Catchrate, matching Baxcalibur - new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.PSYCHIC, 1.6, 550, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.LIGHT_METAL, 600, 80, 135, 130, 95, 90, 70, 3, 35, 300, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.STEEL, PokemonType.PSYCHIC, 2.5, 942.9, AbilityId.TOUGH_CLAWS, AbilityId.NONE, AbilityId.TOUGH_CLAWS, 700, 80, 145, 150, 105, 110, 110, 3, 35, 300), - ), - new PokemonSpecies(SpeciesId.REGIROCK, 3, true, false, false, "Rock Peak Pokémon", PokemonType.ROCK, null, 1.7, 230, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.STURDY, 580, 80, 100, 200, 50, 100, 50, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.REGICE, 3, true, false, false, "Iceberg Pokémon", PokemonType.ICE, null, 1.8, 175, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.ICE_BODY, 580, 80, 50, 100, 100, 200, 50, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.REGISTEEL, 3, true, false, false, "Iron Pokémon", PokemonType.STEEL, null, 1.9, 205, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.LIGHT_METAL, 580, 80, 75, 150, 75, 150, 50, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.LATIAS, 3, true, false, false, "Eon Pokémon", PokemonType.DRAGON, PokemonType.PSYCHIC, 1.4, 40, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 600, 80, 80, 90, 110, 130, 110, 3, 90, 300, GrowthRate.SLOW, 0, false, true, - new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.PSYCHIC, 1.4, 40, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 600, 80, 80, 90, 110, 130, 110, 3, 90, 300, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.PSYCHIC, 1.8, 52, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 700, 80, 100, 120, 140, 150, 110, 3, 90, 300), - ), - new PokemonSpecies(SpeciesId.LATIOS, 3, true, false, false, "Eon Pokémon", PokemonType.DRAGON, PokemonType.PSYCHIC, 2, 60, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 600, 80, 90, 80, 130, 110, 110, 3, 90, 300, GrowthRate.SLOW, 100, false, true, - new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.PSYCHIC, 2, 60, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 600, 80, 90, 80, 130, 110, 110, 3, 90, 300, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.PSYCHIC, 2.3, 70, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 700, 80, 130, 100, 160, 120, 110, 3, 90, 300), - ), - new PokemonSpecies(SpeciesId.KYOGRE, 3, false, true, false, "Sea Basin Pokémon", PokemonType.WATER, null, 4.5, 352, AbilityId.DRIZZLE, AbilityId.NONE, AbilityId.NONE, 670, 100, 100, 90, 150, 140, 90, 3, 0, 335, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal", "", PokemonType.WATER, null, 4.5, 352, AbilityId.DRIZZLE, AbilityId.NONE, AbilityId.NONE, 670, 100, 100, 90, 150, 140, 90, 3, 0, 335, false, null, true), - new PokemonForm("Primal", "primal", PokemonType.WATER, null, 9.8, 430, AbilityId.PRIMORDIAL_SEA, AbilityId.NONE, AbilityId.NONE, 770, 100, 150, 90, 180, 160, 90, 3, 0, 335), - ), - new PokemonSpecies(SpeciesId.GROUDON, 3, false, true, false, "Continent Pokémon", PokemonType.GROUND, null, 3.5, 950, AbilityId.DROUGHT, AbilityId.NONE, AbilityId.NONE, 670, 100, 150, 140, 100, 90, 90, 3, 0, 335, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal", "", PokemonType.GROUND, null, 3.5, 950, AbilityId.DROUGHT, AbilityId.NONE, AbilityId.NONE, 670, 100, 150, 140, 100, 90, 90, 3, 0, 335, false, null, true), - new PokemonForm("Primal", "primal", PokemonType.GROUND, PokemonType.FIRE, 5, 999.7, AbilityId.DESOLATE_LAND, AbilityId.NONE, AbilityId.NONE, 770, 100, 180, 160, 150, 90, 90, 3, 0, 335), - ), - new PokemonSpecies(SpeciesId.RAYQUAZA, 3, false, true, false, "Sky High Pokémon", PokemonType.DRAGON, PokemonType.FLYING, 7, 206.5, AbilityId.AIR_LOCK, AbilityId.NONE, AbilityId.NONE, 680, 105, 150, 90, 150, 90, 95, 3, 0, 340, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.FLYING, 7, 206.5, AbilityId.AIR_LOCK, AbilityId.NONE, AbilityId.NONE, 680, 105, 150, 90, 150, 90, 95, 3, 0, 340, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.FLYING, 10.8, 392, AbilityId.DELTA_STREAM, AbilityId.NONE, AbilityId.NONE, 780, 105, 180, 100, 180, 100, 115, 3, 0, 340), - ), - new PokemonSpecies(SpeciesId.JIRACHI, 3, false, false, true, "Wish Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 0.3, 1.1, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 100, 300, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.DEOXYS, 3, false, false, true, "DNA Pokémon", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 150, 50, 150, 50, 150, 3, 0, 300, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal Forme", "normal", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 150, 50, 150, 50, 150, 3, 0, 300, false, "", true), - new PokemonForm("Attack Forme", "attack", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 180, 20, 180, 20, 150, 3, 0, 300), - new PokemonForm("Defense Forme", "defense", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 70, 160, 70, 160, 90, 3, 0, 300), - new PokemonForm("Speed Forme", "speed", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 95, 90, 95, 90, 180, 3, 0, 300), - ), - new PokemonSpecies(SpeciesId.TURTWIG, 4, false, false, false, "Tiny Leaf Pokémon", PokemonType.GRASS, null, 0.4, 10.2, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.SHELL_ARMOR, 318, 55, 68, 64, 45, 55, 31, 45, 70, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.GROTLE, 4, false, false, false, "Grove Pokémon", PokemonType.GRASS, null, 1.1, 97, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.SHELL_ARMOR, 405, 75, 89, 85, 55, 65, 36, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.TORTERRA, 4, false, false, false, "Continent Pokémon", PokemonType.GRASS, PokemonType.GROUND, 2.2, 310, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.SHELL_ARMOR, 525, 95, 109, 105, 75, 85, 56, 45, 70, 263, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.CHIMCHAR, 4, false, false, false, "Chimp Pokémon", PokemonType.FIRE, null, 0.5, 6.2, AbilityId.BLAZE, AbilityId.NONE, AbilityId.IRON_FIST, 309, 44, 58, 44, 58, 44, 61, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.MONFERNO, 4, false, false, false, "Playful Pokémon", PokemonType.FIRE, PokemonType.FIGHTING, 0.9, 22, AbilityId.BLAZE, AbilityId.NONE, AbilityId.IRON_FIST, 405, 64, 78, 52, 78, 52, 81, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.INFERNAPE, 4, false, false, false, "Flame Pokémon", PokemonType.FIRE, PokemonType.FIGHTING, 1.2, 55, AbilityId.BLAZE, AbilityId.NONE, AbilityId.IRON_FIST, 534, 76, 104, 71, 104, 71, 108, 45, 70, 267, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.PIPLUP, 4, false, false, false, "Penguin Pokémon", PokemonType.WATER, null, 0.4, 5.2, AbilityId.TORRENT, AbilityId.NONE, AbilityId.COMPETITIVE, 314, 53, 51, 53, 61, 56, 40, 45, 70, 63, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.PRINPLUP, 4, false, false, false, "Penguin Pokémon", PokemonType.WATER, null, 0.8, 23, AbilityId.TORRENT, AbilityId.NONE, AbilityId.COMPETITIVE, 405, 64, 66, 68, 81, 76, 50, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.EMPOLEON, 4, false, false, false, "Emperor Pokémon", PokemonType.WATER, PokemonType.STEEL, 1.7, 84.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.COMPETITIVE, 530, 84, 86, 88, 111, 101, 60, 45, 70, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.STARLY, 4, false, false, false, "Starling Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 2, AbilityId.KEEN_EYE, AbilityId.NONE, AbilityId.RECKLESS, 245, 40, 55, 30, 30, 30, 60, 255, 70, 49, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.STARAVIA, 4, false, false, false, "Starling Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 15.5, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.RECKLESS, 340, 55, 75, 50, 40, 40, 80, 120, 70, 119, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.STARAPTOR, 4, false, false, false, "Predator Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.2, 24.9, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.RECKLESS, 485, 85, 120, 70, 50, 60, 100, 45, 70, 243, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.BIDOOF, 4, false, false, false, "Plump Mouse Pokémon", PokemonType.NORMAL, null, 0.5, 20, AbilityId.SIMPLE, AbilityId.UNAWARE, AbilityId.MOODY, 250, 59, 45, 40, 35, 40, 31, 255, 70, 50, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.BIBAREL, 4, false, false, false, "Beaver Pokémon", PokemonType.NORMAL, PokemonType.WATER, 1, 31.5, AbilityId.SIMPLE, AbilityId.UNAWARE, AbilityId.MOODY, 410, 79, 85, 60, 55, 60, 71, 127, 70, 144, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.KRICKETOT, 4, false, false, false, "Cricket Pokémon", PokemonType.BUG, null, 0.3, 2.2, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.RUN_AWAY, 194, 37, 25, 41, 25, 41, 25, 255, 70, 39, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.KRICKETUNE, 4, false, false, false, "Cricket Pokémon", PokemonType.BUG, null, 1, 25.5, AbilityId.SWARM, AbilityId.NONE, AbilityId.TECHNICIAN, 384, 77, 85, 51, 55, 51, 65, 45, 70, 134, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.SHINX, 4, false, false, false, "Flash Pokémon", PokemonType.ELECTRIC, null, 0.5, 9.5, AbilityId.RIVALRY, AbilityId.INTIMIDATE, AbilityId.GUTS, 263, 45, 65, 34, 40, 34, 45, 235, 50, 53, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.LUXIO, 4, false, false, false, "Spark Pokémon", PokemonType.ELECTRIC, null, 0.9, 30.5, AbilityId.RIVALRY, AbilityId.INTIMIDATE, AbilityId.GUTS, 363, 60, 85, 49, 60, 49, 60, 120, 100, 127, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.LUXRAY, 4, false, false, false, "Gleam Eyes Pokémon", PokemonType.ELECTRIC, null, 1.4, 42, AbilityId.RIVALRY, AbilityId.INTIMIDATE, AbilityId.GUTS, 523, 80, 120, 79, 95, 79, 70, 45, 50, 262, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.BUDEW, 4, false, false, false, "Bud Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.2, 1.2, AbilityId.NATURAL_CURE, AbilityId.POISON_POINT, AbilityId.LEAF_GUARD, 280, 40, 30, 35, 50, 70, 55, 255, 50, 56, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.ROSERADE, 4, false, false, false, "Bouquet Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.9, 14.5, AbilityId.NATURAL_CURE, AbilityId.POISON_POINT, AbilityId.TECHNICIAN, 515, 60, 70, 65, 125, 105, 90, 75, 50, 258, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.CRANIDOS, 4, false, false, false, "Head Butt Pokémon", PokemonType.ROCK, null, 0.9, 31.5, AbilityId.MOLD_BREAKER, AbilityId.NONE, AbilityId.SHEER_FORCE, 350, 67, 125, 40, 30, 30, 58, 45, 70, 70, GrowthRate.ERRATIC, 87.5, false), - new PokemonSpecies(SpeciesId.RAMPARDOS, 4, false, false, false, "Head Butt Pokémon", PokemonType.ROCK, null, 1.6, 102.5, AbilityId.MOLD_BREAKER, AbilityId.NONE, AbilityId.SHEER_FORCE, 495, 97, 165, 60, 65, 50, 58, 45, 70, 173, GrowthRate.ERRATIC, 87.5, false), - new PokemonSpecies(SpeciesId.SHIELDON, 4, false, false, false, "Shield Pokémon", PokemonType.ROCK, PokemonType.STEEL, 0.5, 57, AbilityId.STURDY, AbilityId.NONE, AbilityId.SOUNDPROOF, 350, 30, 42, 118, 42, 88, 30, 45, 70, 70, GrowthRate.ERRATIC, 87.5, false), - new PokemonSpecies(SpeciesId.BASTIODON, 4, false, false, false, "Shield Pokémon", PokemonType.ROCK, PokemonType.STEEL, 1.3, 149.5, AbilityId.STURDY, AbilityId.NONE, AbilityId.SOUNDPROOF, 495, 60, 52, 168, 47, 138, 30, 45, 70, 173, GrowthRate.ERRATIC, 87.5, false), - new PokemonSpecies(SpeciesId.BURMY, 4, false, false, false, "Bagworm Pokémon", PokemonType.BUG, null, 0.2, 3.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.OVERCOAT, 224, 40, 29, 45, 29, 45, 36, 120, 70, 45, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Plant Cloak", "plant", PokemonType.BUG, null, 0.2, 3.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.OVERCOAT, 224, 40, 29, 45, 29, 45, 36, 120, 70, 45, false, null, true), - new PokemonForm("Sandy Cloak", "sandy", PokemonType.BUG, null, 0.2, 3.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.OVERCOAT, 224, 40, 29, 45, 29, 45, 36, 120, 70, 45, false, null, true), - new PokemonForm("Trash Cloak", "trash", PokemonType.BUG, null, 0.2, 3.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.OVERCOAT, 224, 40, 29, 45, 29, 45, 36, 120, 70, 45, false, null, true), - ), - new PokemonSpecies(SpeciesId.WORMADAM, 4, false, false, false, "Bagworm Pokémon", PokemonType.BUG, PokemonType.GRASS, 0.5, 6.5, AbilityId.ANTICIPATION, AbilityId.NONE, AbilityId.OVERCOAT, 424, 60, 59, 85, 79, 105, 36, 45, 70, 148, GrowthRate.MEDIUM_FAST, 0, false, false, - new PokemonForm("Plant Cloak", "plant", PokemonType.BUG, PokemonType.GRASS, 0.5, 6.5, AbilityId.ANTICIPATION, AbilityId.NONE, AbilityId.OVERCOAT, 424, 60, 59, 85, 79, 105, 36, 45, 70, 148, false, null, true), - new PokemonForm("Sandy Cloak", "sandy", PokemonType.BUG, PokemonType.GROUND, 0.5, 6.5, AbilityId.ANTICIPATION, AbilityId.NONE, AbilityId.OVERCOAT, 424, 60, 79, 105, 59, 85, 36, 45, 70, 148, false, null, true), - new PokemonForm("Trash Cloak", "trash", PokemonType.BUG, PokemonType.STEEL, 0.5, 6.5, AbilityId.ANTICIPATION, AbilityId.NONE, AbilityId.OVERCOAT, 424, 60, 69, 95, 69, 95, 36, 45, 70, 148, false, null, true), - ), - new PokemonSpecies(SpeciesId.MOTHIM, 4, false, false, false, "Moth Pokémon", PokemonType.BUG, PokemonType.FLYING, 0.9, 23.3, AbilityId.SWARM, AbilityId.NONE, AbilityId.TINTED_LENS, 424, 70, 94, 50, 94, 50, 66, 45, 70, 148, GrowthRate.MEDIUM_FAST, 100, false), - new PokemonSpecies(SpeciesId.COMBEE, 4, false, false, false, "Tiny Bee Pokémon", PokemonType.BUG, PokemonType.FLYING, 0.3, 5.5, AbilityId.HONEY_GATHER, AbilityId.NONE, AbilityId.HUSTLE, 244, 30, 30, 42, 30, 42, 70, 120, 50, 49, GrowthRate.MEDIUM_SLOW, 87.5, true), - new PokemonSpecies(SpeciesId.VESPIQUEN, 4, false, false, false, "Beehive Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.2, 38.5, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.UNNERVE, 474, 70, 80, 102, 80, 102, 40, 45, 50, 166, GrowthRate.MEDIUM_SLOW, 0, false), - new PokemonSpecies(SpeciesId.PACHIRISU, 4, false, false, false, "EleSquirrel Pokémon", PokemonType.ELECTRIC, null, 0.4, 3.9, AbilityId.RUN_AWAY, AbilityId.PICKUP, AbilityId.VOLT_ABSORB, 405, 60, 45, 70, 45, 90, 95, 200, 100, 142, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.BUIZEL, 4, false, false, false, "Sea Weasel Pokémon", PokemonType.WATER, null, 0.7, 29.5, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.WATER_VEIL, 330, 55, 65, 35, 60, 30, 85, 190, 70, 66, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.FLOATZEL, 4, false, false, false, "Sea Weasel Pokémon", PokemonType.WATER, null, 1.1, 33.5, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.WATER_VEIL, 495, 85, 105, 55, 85, 50, 115, 75, 70, 173, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.CHERUBI, 4, false, false, false, "Cherry Pokémon", PokemonType.GRASS, null, 0.4, 3.3, AbilityId.CHLOROPHYLL, AbilityId.NONE, AbilityId.NONE, 275, 45, 35, 45, 62, 53, 35, 190, 50, 55, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CHERRIM, 4, false, false, false, "Blossom Pokémon", PokemonType.GRASS, null, 0.5, 9.3, AbilityId.FLOWER_GIFT, AbilityId.NONE, AbilityId.NONE, 450, 70, 60, 70, 87, 78, 85, 75, 50, 158, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Overcast Form", "overcast", PokemonType.GRASS, null, 0.5, 9.3, AbilityId.FLOWER_GIFT, AbilityId.NONE, AbilityId.NONE, 450, 70, 60, 70, 87, 78, 85, 75, 50, 158, false, null, true), - new PokemonForm("Sunshine Form", "sunshine", PokemonType.GRASS, null, 0.5, 9.3, AbilityId.FLOWER_GIFT, AbilityId.NONE, AbilityId.NONE, 450, 70, 60, 70, 87, 78, 85, 75, 50, 158), - ), - new PokemonSpecies(SpeciesId.SHELLOS, 4, false, false, false, "Sea Slug Pokémon", PokemonType.WATER, null, 0.3, 6.3, AbilityId.STICKY_HOLD, AbilityId.STORM_DRAIN, AbilityId.SAND_FORCE, 325, 76, 48, 48, 57, 62, 34, 190, 50, 65, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("East Sea", "east", PokemonType.WATER, null, 0.3, 6.3, AbilityId.STICKY_HOLD, AbilityId.STORM_DRAIN, AbilityId.SAND_FORCE, 325, 76, 48, 48, 57, 62, 34, 190, 50, 65, false, null, true), - new PokemonForm("West Sea", "west", PokemonType.WATER, null, 0.3, 6.3, AbilityId.STICKY_HOLD, AbilityId.STORM_DRAIN, AbilityId.SAND_FORCE, 325, 76, 48, 48, 57, 62, 34, 190, 50, 65, false, null, true), - ), - new PokemonSpecies(SpeciesId.GASTRODON, 4, false, false, false, "Sea Slug Pokémon", PokemonType.WATER, PokemonType.GROUND, 0.9, 29.9, AbilityId.STICKY_HOLD, AbilityId.STORM_DRAIN, AbilityId.SAND_FORCE, 475, 111, 83, 68, 92, 82, 39, 75, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("East Sea", "east", PokemonType.WATER, PokemonType.GROUND, 0.9, 29.9, AbilityId.STICKY_HOLD, AbilityId.STORM_DRAIN, AbilityId.SAND_FORCE, 475, 111, 83, 68, 92, 82, 39, 75, 50, 166, false, null, true), - new PokemonForm("West Sea", "west", PokemonType.WATER, PokemonType.GROUND, 0.9, 29.9, AbilityId.STICKY_HOLD, AbilityId.STORM_DRAIN, AbilityId.SAND_FORCE, 475, 111, 83, 68, 92, 82, 39, 75, 50, 166, false, null, true), - ), - new PokemonSpecies(SpeciesId.AMBIPOM, 4, false, false, false, "Long Tail Pokémon", PokemonType.NORMAL, null, 1.2, 20.3, AbilityId.TECHNICIAN, AbilityId.PICKUP, AbilityId.SKILL_LINK, 482, 75, 100, 66, 60, 66, 115, 45, 100, 169, GrowthRate.FAST, 50, true), - new PokemonSpecies(SpeciesId.DRIFLOON, 4, false, false, false, "Balloon Pokémon", PokemonType.GHOST, PokemonType.FLYING, 0.4, 1.2, AbilityId.AFTERMATH, AbilityId.UNBURDEN, AbilityId.FLARE_BOOST, 348, 90, 50, 34, 60, 44, 70, 125, 50, 70, GrowthRate.FLUCTUATING, 50, false), - new PokemonSpecies(SpeciesId.DRIFBLIM, 4, false, false, false, "Blimp Pokémon", PokemonType.GHOST, PokemonType.FLYING, 1.2, 15, AbilityId.AFTERMATH, AbilityId.UNBURDEN, AbilityId.FLARE_BOOST, 498, 150, 80, 44, 90, 54, 80, 60, 50, 174, GrowthRate.FLUCTUATING, 50, false), - new PokemonSpecies(SpeciesId.BUNEARY, 4, false, false, false, "Rabbit Pokémon", PokemonType.NORMAL, null, 0.4, 5.5, AbilityId.RUN_AWAY, AbilityId.KLUTZ, AbilityId.LIMBER, 350, 55, 66, 44, 44, 56, 85, 190, 0, 70, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.LOPUNNY, 4, false, false, false, "Rabbit Pokémon", PokemonType.NORMAL, null, 1.2, 33.3, AbilityId.CUTE_CHARM, AbilityId.KLUTZ, AbilityId.LIMBER, 480, 65, 76, 84, 54, 96, 105, 60, 140, 168, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.NORMAL, null, 1.2, 33.3, AbilityId.CUTE_CHARM, AbilityId.KLUTZ, AbilityId.LIMBER, 480, 65, 76, 84, 54, 96, 105, 60, 140, 168, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.NORMAL, PokemonType.FIGHTING, 1.3, 28.3, AbilityId.SCRAPPY, AbilityId.SCRAPPY, AbilityId.SCRAPPY, 580, 65, 136, 94, 54, 96, 135, 60, 140, 168), - ), - new PokemonSpecies(SpeciesId.MISMAGIUS, 4, false, false, false, "Magical Pokémon", PokemonType.GHOST, null, 0.9, 4.4, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 495, 60, 60, 60, 105, 105, 105, 45, 35, 173, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.HONCHKROW, 4, false, false, false, "Big Boss Pokémon", PokemonType.DARK, PokemonType.FLYING, 0.9, 27.3, AbilityId.INSOMNIA, AbilityId.SUPER_LUCK, AbilityId.MOXIE, 505, 100, 125, 52, 105, 52, 71, 30, 35, 177, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.GLAMEOW, 4, false, false, false, "Catty Pokémon", PokemonType.NORMAL, null, 0.5, 3.9, AbilityId.LIMBER, AbilityId.OWN_TEMPO, AbilityId.KEEN_EYE, 310, 49, 55, 42, 42, 37, 85, 190, 70, 62, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.PURUGLY, 4, false, false, false, "Tiger Cat Pokémon", PokemonType.NORMAL, null, 1, 43.8, AbilityId.THICK_FAT, AbilityId.OWN_TEMPO, AbilityId.DEFIANT, 452, 71, 82, 64, 64, 59, 112, 75, 70, 158, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.CHINGLING, 4, false, false, false, "Bell Pokémon", PokemonType.PSYCHIC, null, 0.2, 0.6, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 285, 45, 30, 50, 65, 50, 45, 120, 70, 57, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.STUNKY, 4, false, false, false, "Skunk Pokémon", PokemonType.POISON, PokemonType.DARK, 0.4, 19.2, AbilityId.STENCH, AbilityId.AFTERMATH, AbilityId.KEEN_EYE, 329, 63, 63, 47, 41, 41, 74, 225, 50, 66, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SKUNTANK, 4, false, false, false, "Skunk Pokémon", PokemonType.POISON, PokemonType.DARK, 1, 38, AbilityId.STENCH, AbilityId.AFTERMATH, AbilityId.KEEN_EYE, 479, 103, 93, 67, 71, 61, 84, 60, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BRONZOR, 4, false, false, false, "Bronze Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 0.5, 60.5, AbilityId.LEVITATE, AbilityId.HEATPROOF, AbilityId.HEAVY_METAL, 300, 57, 24, 86, 24, 86, 23, 255, 50, 60, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.BRONZONG, 4, false, false, false, "Bronze Bell Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 1.3, 187, AbilityId.LEVITATE, AbilityId.HEATPROOF, AbilityId.HEAVY_METAL, 500, 67, 89, 116, 79, 116, 33, 90, 50, 175, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.BONSLY, 4, false, false, false, "Bonsai Pokémon", PokemonType.ROCK, null, 0.5, 15, AbilityId.STURDY, AbilityId.ROCK_HEAD, AbilityId.RATTLED, 290, 50, 80, 95, 10, 45, 10, 255, 50, 58, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MIME_JR, 4, false, false, false, "Mime Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 0.6, 13, AbilityId.SOUNDPROOF, AbilityId.FILTER, AbilityId.TECHNICIAN, 310, 20, 25, 45, 70, 90, 60, 145, 50, 62, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.HAPPINY, 4, false, false, false, "Playhouse Pokémon", PokemonType.NORMAL, null, 0.6, 24.4, AbilityId.NATURAL_CURE, AbilityId.SERENE_GRACE, AbilityId.FRIEND_GUARD, 220, 100, 5, 5, 15, 65, 30, 130, 140, 110, GrowthRate.FAST, 0, false), - new PokemonSpecies(SpeciesId.CHATOT, 4, false, false, false, "Music Note Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.5, 1.9, AbilityId.KEEN_EYE, AbilityId.TANGLED_FEET, AbilityId.BIG_PECKS, 411, 76, 65, 45, 92, 42, 91, 30, 35, 144, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SPIRITOMB, 4, false, false, false, "Forbidden Pokémon", PokemonType.GHOST, PokemonType.DARK, 1, 108, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.INFILTRATOR, 485, 50, 92, 108, 92, 108, 35, 100, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GIBLE, 4, false, false, false, "Land Shark Pokémon", PokemonType.DRAGON, PokemonType.GROUND, 0.7, 20.5, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.ROUGH_SKIN, 300, 58, 70, 45, 40, 45, 42, 45, 50, 60, GrowthRate.SLOW, 50, true), - new PokemonSpecies(SpeciesId.GABITE, 4, false, false, false, "Cave Pokémon", PokemonType.DRAGON, PokemonType.GROUND, 1.4, 56, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.ROUGH_SKIN, 410, 68, 90, 65, 50, 55, 82, 45, 50, 144, GrowthRate.SLOW, 50, true), - new PokemonSpecies(SpeciesId.GARCHOMP, 4, false, false, false, "Mach Pokémon", PokemonType.DRAGON, PokemonType.GROUND, 1.9, 95, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.ROUGH_SKIN, 600, 108, 130, 95, 80, 85, 102, 45, 50, 300, GrowthRate.SLOW, 50, true, true, - new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.GROUND, 1.9, 95, AbilityId.SAND_VEIL, AbilityId.NONE, AbilityId.ROUGH_SKIN, 600, 108, 130, 95, 80, 85, 102, 45, 50, 300, true, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.GROUND, 1.9, 95, AbilityId.SAND_FORCE, AbilityId.NONE, AbilityId.SAND_FORCE, 700, 108, 170, 115, 120, 95, 92, 45, 50, 300, true), - ), - new PokemonSpecies(SpeciesId.MUNCHLAX, 4, false, false, false, "Big Eater Pokémon", PokemonType.NORMAL, null, 0.6, 105, AbilityId.PICKUP, AbilityId.THICK_FAT, AbilityId.GLUTTONY, 390, 135, 85, 40, 40, 85, 5, 50, 50, 78, GrowthRate.SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.RIOLU, 4, false, false, false, "Emanation Pokémon", PokemonType.FIGHTING, null, 0.7, 20.2, AbilityId.STEADFAST, AbilityId.INNER_FOCUS, AbilityId.PRANKSTER, 285, 40, 70, 40, 35, 40, 60, 75, 50, 57, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.LUCARIO, 4, false, false, false, "Aura Pokémon", PokemonType.FIGHTING, PokemonType.STEEL, 1.2, 54, AbilityId.STEADFAST, AbilityId.INNER_FOCUS, AbilityId.JUSTIFIED, 525, 70, 110, 70, 115, 70, 90, 45, 50, 184, GrowthRate.MEDIUM_SLOW, 87.5, false, true, - new PokemonForm("Normal", "", PokemonType.FIGHTING, PokemonType.STEEL, 1.2, 54, AbilityId.STEADFAST, AbilityId.INNER_FOCUS, AbilityId.JUSTIFIED, 525, 70, 110, 70, 115, 70, 90, 45, 50, 184, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.FIGHTING, PokemonType.STEEL, 1.3, 57.5, AbilityId.ADAPTABILITY, AbilityId.ADAPTABILITY, AbilityId.ADAPTABILITY, 625, 70, 145, 88, 140, 70, 112, 45, 50, 184), - ), - new PokemonSpecies(SpeciesId.HIPPOPOTAS, 4, false, false, false, "Hippo Pokémon", PokemonType.GROUND, null, 0.8, 49.5, AbilityId.SAND_STREAM, AbilityId.NONE, AbilityId.SAND_FORCE, 330, 68, 72, 78, 38, 42, 32, 140, 50, 66, GrowthRate.SLOW, 50, true), - new PokemonSpecies(SpeciesId.HIPPOWDON, 4, false, false, false, "Heavyweight Pokémon", PokemonType.GROUND, null, 2, 300, AbilityId.SAND_STREAM, AbilityId.NONE, AbilityId.SAND_FORCE, 525, 108, 112, 118, 68, 72, 47, 60, 50, 184, GrowthRate.SLOW, 50, true), - new PokemonSpecies(SpeciesId.SKORUPI, 4, false, false, false, "Scorpion Pokémon", PokemonType.POISON, PokemonType.BUG, 0.8, 12, AbilityId.BATTLE_ARMOR, AbilityId.SNIPER, AbilityId.KEEN_EYE, 330, 40, 50, 90, 30, 55, 65, 120, 50, 66, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.DRAPION, 4, false, false, false, "Ogre Scorpion Pokémon", PokemonType.POISON, PokemonType.DARK, 1.3, 61.5, AbilityId.BATTLE_ARMOR, AbilityId.SNIPER, AbilityId.KEEN_EYE, 500, 70, 90, 110, 60, 75, 95, 45, 50, 175, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.CROAGUNK, 4, false, false, false, "Toxic Mouth Pokémon", PokemonType.POISON, PokemonType.FIGHTING, 0.7, 23, AbilityId.ANTICIPATION, AbilityId.DRY_SKIN, AbilityId.POISON_TOUCH, 300, 48, 61, 40, 61, 40, 50, 140, 100, 60, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.TOXICROAK, 4, false, false, false, "Toxic Mouth Pokémon", PokemonType.POISON, PokemonType.FIGHTING, 1.3, 44.4, AbilityId.ANTICIPATION, AbilityId.DRY_SKIN, AbilityId.POISON_TOUCH, 490, 83, 106, 65, 86, 65, 85, 75, 50, 172, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.CARNIVINE, 4, false, false, false, "Bug Catcher Pokémon", PokemonType.GRASS, null, 1.4, 27, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 454, 74, 100, 72, 90, 72, 46, 200, 70, 159, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.FINNEON, 4, false, false, false, "Wing Fish Pokémon", PokemonType.WATER, null, 0.4, 7, AbilityId.SWIFT_SWIM, AbilityId.STORM_DRAIN, AbilityId.WATER_VEIL, 330, 49, 49, 56, 49, 61, 66, 190, 70, 66, GrowthRate.ERRATIC, 50, true), - new PokemonSpecies(SpeciesId.LUMINEON, 4, false, false, false, "Neon Pokémon", PokemonType.WATER, null, 1.2, 24, AbilityId.SWIFT_SWIM, AbilityId.STORM_DRAIN, AbilityId.WATER_VEIL, 460, 69, 69, 76, 69, 86, 91, 75, 70, 161, GrowthRate.ERRATIC, 50, true), - new PokemonSpecies(SpeciesId.MANTYKE, 4, false, false, false, "Kite Pokémon", PokemonType.WATER, PokemonType.FLYING, 1, 65, AbilityId.SWIFT_SWIM, AbilityId.WATER_ABSORB, AbilityId.WATER_VEIL, 345, 45, 20, 50, 60, 120, 50, 25, 50, 69, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.SNOVER, 4, false, false, false, "Frost Tree Pokémon", PokemonType.GRASS, PokemonType.ICE, 1, 50.5, AbilityId.SNOW_WARNING, AbilityId.NONE, AbilityId.SOUNDPROOF, 334, 60, 62, 50, 62, 60, 40, 120, 50, 67, GrowthRate.SLOW, 50, true), - new PokemonSpecies(SpeciesId.ABOMASNOW, 4, false, false, false, "Frost Tree Pokémon", PokemonType.GRASS, PokemonType.ICE, 2.2, 135.5, AbilityId.SNOW_WARNING, AbilityId.NONE, AbilityId.SOUNDPROOF, 494, 90, 92, 75, 92, 85, 60, 60, 50, 173, GrowthRate.SLOW, 50, true, true, - new PokemonForm("Normal", "", PokemonType.GRASS, PokemonType.ICE, 2.2, 135.5, AbilityId.SNOW_WARNING, AbilityId.NONE, AbilityId.SOUNDPROOF, 494, 90, 92, 75, 92, 85, 60, 60, 50, 173, true, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.GRASS, PokemonType.ICE, 2.7, 185, AbilityId.SNOW_WARNING, AbilityId.NONE, AbilityId.SNOW_WARNING, 594, 90, 132, 105, 132, 105, 30, 60, 50, 173, true), - ), - new PokemonSpecies(SpeciesId.WEAVILE, 4, false, false, false, "Sharp Claw Pokémon", PokemonType.DARK, PokemonType.ICE, 1.1, 34, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.PICKPOCKET, 510, 70, 120, 65, 45, 85, 125, 45, 35, 179, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.MAGNEZONE, 4, false, false, false, "Magnet Area Pokémon", PokemonType.ELECTRIC, PokemonType.STEEL, 1.2, 180, AbilityId.MAGNET_PULL, AbilityId.STURDY, AbilityId.ANALYTIC, 535, 70, 70, 115, 130, 90, 60, 30, 50, 268, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.LICKILICKY, 4, false, false, false, "Licking Pokémon", PokemonType.NORMAL, null, 1.7, 140, AbilityId.OWN_TEMPO, AbilityId.OBLIVIOUS, AbilityId.CLOUD_NINE, 515, 110, 85, 95, 80, 95, 50, 30, 50, 180, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.RHYPERIOR, 4, false, false, false, "Drill Pokémon", PokemonType.GROUND, PokemonType.ROCK, 2.4, 282.8, AbilityId.LIGHTNING_ROD, AbilityId.SOLID_ROCK, AbilityId.RECKLESS, 535, 115, 140, 130, 55, 55, 40, 30, 50, 268, GrowthRate.SLOW, 50, true), - new PokemonSpecies(SpeciesId.TANGROWTH, 4, false, false, false, "Vine Pokémon", PokemonType.GRASS, null, 2, 128.6, AbilityId.CHLOROPHYLL, AbilityId.LEAF_GUARD, AbilityId.REGENERATOR, 535, 100, 100, 125, 110, 50, 50, 30, 50, 187, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.ELECTIVIRE, 4, false, false, false, "Thunderbolt Pokémon", PokemonType.ELECTRIC, null, 1.8, 138.6, AbilityId.MOTOR_DRIVE, AbilityId.NONE, AbilityId.VITAL_SPIRIT, 540, 75, 123, 67, 95, 85, 95, 30, 50, 270, GrowthRate.MEDIUM_FAST, 75, false), - new PokemonSpecies(SpeciesId.MAGMORTAR, 4, false, false, false, "Blast Pokémon", PokemonType.FIRE, null, 1.6, 68, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.VITAL_SPIRIT, 540, 75, 95, 67, 125, 95, 83, 30, 50, 270, GrowthRate.MEDIUM_FAST, 75, false), - new PokemonSpecies(SpeciesId.TOGEKISS, 4, false, false, false, "Jubilee Pokémon", PokemonType.FAIRY, PokemonType.FLYING, 1.5, 38, AbilityId.HUSTLE, AbilityId.SERENE_GRACE, AbilityId.SUPER_LUCK, 545, 85, 50, 95, 120, 115, 80, 30, 50, 273, GrowthRate.FAST, 87.5, false), - new PokemonSpecies(SpeciesId.YANMEGA, 4, false, false, false, "Ogre Darner Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.9, 51.5, AbilityId.SPEED_BOOST, AbilityId.TINTED_LENS, AbilityId.FRISK, 515, 86, 76, 86, 116, 56, 95, 30, 70, 180, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.LEAFEON, 4, false, false, false, "Verdant Pokémon", PokemonType.GRASS, null, 1, 25.5, AbilityId.LEAF_GUARD, AbilityId.NONE, AbilityId.CHLOROPHYLL, 525, 65, 110, 130, 60, 65, 95, 45, 35, 184, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.GLACEON, 4, false, false, false, "Fresh Snow Pokémon", PokemonType.ICE, null, 0.8, 25.9, AbilityId.SNOW_CLOAK, AbilityId.NONE, AbilityId.ICE_BODY, 525, 65, 60, 110, 130, 95, 65, 45, 35, 184, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.GLISCOR, 4, false, false, false, "Fang Scorpion Pokémon", PokemonType.GROUND, PokemonType.FLYING, 2, 42.5, AbilityId.HYPER_CUTTER, AbilityId.SAND_VEIL, AbilityId.POISON_HEAL, 510, 75, 95, 125, 45, 75, 95, 30, 70, 179, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.MAMOSWINE, 4, false, false, false, "Twin Tusk Pokémon", PokemonType.ICE, PokemonType.GROUND, 2.5, 291, AbilityId.OBLIVIOUS, AbilityId.SNOW_CLOAK, AbilityId.THICK_FAT, 530, 110, 130, 80, 70, 60, 80, 50, 50, 265, GrowthRate.SLOW, 50, true), - new PokemonSpecies(SpeciesId.PORYGON_Z, 4, false, false, false, "Virtual Pokémon", PokemonType.NORMAL, null, 0.9, 34, AbilityId.ADAPTABILITY, AbilityId.DOWNLOAD, AbilityId.ANALYTIC, 535, 85, 80, 70, 135, 75, 90, 30, 50, 268, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.GALLADE, 4, false, false, false, "Blade Pokémon", PokemonType.PSYCHIC, PokemonType.FIGHTING, 1.6, 52, AbilityId.STEADFAST, AbilityId.SHARPNESS, AbilityId.JUSTIFIED, 518, 68, 125, 65, 65, 115, 80, 45, 35, 259, GrowthRate.SLOW, 100, false, true, - new PokemonForm("Normal", "", PokemonType.PSYCHIC, PokemonType.FIGHTING, 1.6, 52, AbilityId.STEADFAST, AbilityId.SHARPNESS, AbilityId.JUSTIFIED, 518, 68, 125, 65, 65, 115, 80, 45, 35, 259, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.PSYCHIC, PokemonType.FIGHTING, 1.6, 56.4, AbilityId.INNER_FOCUS, AbilityId.INNER_FOCUS, AbilityId.INNER_FOCUS, 618, 68, 165, 95, 65, 115, 110, 45, 35, 259), - ), - new PokemonSpecies(SpeciesId.PROBOPASS, 4, false, false, false, "Compass Pokémon", PokemonType.ROCK, PokemonType.STEEL, 1.4, 340, AbilityId.STURDY, AbilityId.MAGNET_PULL, AbilityId.SAND_FORCE, 525, 60, 55, 145, 75, 150, 40, 60, 70, 184, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DUSKNOIR, 4, false, false, false, "Gripper Pokémon", PokemonType.GHOST, null, 2.2, 106.6, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.FRISK, 525, 45, 100, 135, 65, 135, 45, 45, 35, 263, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.FROSLASS, 4, false, false, false, "Snow Land Pokémon", PokemonType.ICE, PokemonType.GHOST, 1.3, 26.6, AbilityId.SNOW_CLOAK, AbilityId.NONE, AbilityId.CURSED_BODY, 480, 70, 80, 70, 80, 70, 110, 75, 50, 168, GrowthRate.MEDIUM_FAST, 0, false), - new PokemonSpecies(SpeciesId.ROTOM, 4, false, false, false, "Plasma Pokémon", PokemonType.ELECTRIC, PokemonType.GHOST, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 440, 50, 50, 77, 95, 77, 91, 45, 50, 154, GrowthRate.MEDIUM_FAST, null, false, false, - new PokemonForm("Normal", "", PokemonType.ELECTRIC, PokemonType.GHOST, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 440, 50, 50, 77, 95, 77, 91, 45, 50, 154, false, null, true), - new PokemonForm("Heat", "heat", PokemonType.ELECTRIC, PokemonType.FIRE, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), - new PokemonForm("Wash", "wash", PokemonType.ELECTRIC, PokemonType.WATER, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), - new PokemonForm("Frost", "frost", PokemonType.ELECTRIC, PokemonType.ICE, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), - new PokemonForm("Fan", "fan", PokemonType.ELECTRIC, PokemonType.FLYING, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), - new PokemonForm("Mow", "mow", PokemonType.ELECTRIC, PokemonType.GRASS, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 520, 50, 65, 107, 105, 107, 86, 45, 50, 182, false, null, true), - ), - new PokemonSpecies(SpeciesId.UXIE, 4, true, false, false, "Knowledge Pokémon", PokemonType.PSYCHIC, null, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 580, 75, 75, 130, 75, 130, 95, 3, 140, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.MESPRIT, 4, true, false, false, "Emotion Pokémon", PokemonType.PSYCHIC, null, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 580, 80, 105, 105, 105, 105, 80, 3, 140, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.AZELF, 4, true, false, false, "Willpower Pokémon", PokemonType.PSYCHIC, null, 0.3, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 580, 75, 125, 70, 125, 70, 115, 3, 140, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.DIALGA, 4, false, true, false, "Temporal Pokémon", PokemonType.STEEL, PokemonType.DRAGON, 5.4, 683, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 100, 120, 120, 150, 100, 90, 3, 0, 340, GrowthRate.SLOW, null, false, false, - new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.DRAGON, 5.4, 683, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 100, 120, 120, 150, 100, 90, 3, 0, 340, false, null, true), - new PokemonForm("Origin Forme", "origin", PokemonType.STEEL, PokemonType.DRAGON, 7, 848.7, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 100, 100, 120, 150, 120, 90, 3, 0, 340), - ), - new PokemonSpecies(SpeciesId.PALKIA, 4, false, true, false, "Spatial Pokémon", PokemonType.WATER, PokemonType.DRAGON, 4.2, 336, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 90, 120, 100, 150, 120, 100, 3, 0, 340, GrowthRate.SLOW, null, false, false, - new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.DRAGON, 4.2, 336, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 90, 120, 100, 150, 120, 100, 3, 0, 340, false, null, true), - new PokemonForm("Origin Forme", "origin", PokemonType.WATER, PokemonType.DRAGON, 6.3, 659, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 90, 100, 100, 150, 120, 120, 3, 0, 340), - ), - new PokemonSpecies(SpeciesId.HEATRAN, 4, true, false, false, "Lava Dome Pokémon", PokemonType.FIRE, PokemonType.STEEL, 1.7, 430, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.FLAME_BODY, 600, 91, 90, 106, 130, 106, 77, 3, 100, 300, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.REGIGIGAS, 4, true, false, false, "Colossal Pokémon", PokemonType.NORMAL, null, 3.7, 420, AbilityId.SLOW_START, AbilityId.NONE, AbilityId.NORMALIZE, 670, 110, 160, 110, 80, 110, 100, 3, 0, 335, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.GIRATINA, 4, false, true, false, "Renegade Pokémon", PokemonType.GHOST, PokemonType.DRAGON, 4.5, 750, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 150, 100, 120, 100, 120, 90, 3, 0, 340, GrowthRate.SLOW, null, false, true, - new PokemonForm("Altered Forme", "altered", PokemonType.GHOST, PokemonType.DRAGON, 4.5, 750, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.TELEPATHY, 680, 150, 100, 120, 100, 120, 90, 3, 0, 340, false, null, true), - new PokemonForm("Origin Forme", "origin", PokemonType.GHOST, PokemonType.DRAGON, 6.9, 650, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.LEVITATE, 680, 150, 120, 100, 120, 100, 90, 3, 0, 340), - ), - new PokemonSpecies(SpeciesId.CRESSELIA, 4, true, false, false, "Lunar Pokémon", PokemonType.PSYCHIC, null, 1.5, 85.6, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 580, 120, 70, 110, 75, 120, 85, 3, 100, 300, GrowthRate.SLOW, 0, false), - new PokemonSpecies(SpeciesId.PHIONE, 4, false, false, true, "Sea Drifter Pokémon", PokemonType.WATER, null, 0.4, 3.1, AbilityId.HYDRATION, AbilityId.NONE, AbilityId.NONE, 480, 80, 80, 80, 80, 80, 80, 30, 70, 240, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.MANAPHY, 4, false, false, true, "Seafaring Pokémon", PokemonType.WATER, null, 0.3, 1.4, AbilityId.HYDRATION, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 70, 300, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.DARKRAI, 4, false, false, true, "Pitch-Black Pokémon", PokemonType.DARK, null, 1.5, 50.5, AbilityId.BAD_DREAMS, AbilityId.NONE, AbilityId.NONE, 600, 70, 90, 90, 135, 90, 125, 3, 0, 300, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.SHAYMIN, 4, false, false, true, "Gratitude Pokémon", PokemonType.GRASS, null, 0.2, 2.1, AbilityId.NATURAL_CURE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 45, 100, 300, GrowthRate.MEDIUM_SLOW, null, false, true, - new PokemonForm("Land Forme", "land", PokemonType.GRASS, null, 0.2, 2.1, AbilityId.NATURAL_CURE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 45, 100, 300, false, null, true), - new PokemonForm("Sky Forme", "sky", PokemonType.GRASS, PokemonType.FLYING, 0.4, 5.2, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 103, 75, 120, 75, 127, 45, 100, 300), - ), - new PokemonSpecies(SpeciesId.ARCEUS, 4, false, false, true, "Alpha Pokémon", PokemonType.NORMAL, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal", "normal", PokemonType.NORMAL, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360, false, null, true), - new PokemonForm("Fighting", "fighting", PokemonType.FIGHTING, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Flying", "flying", PokemonType.FLYING, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Poison", "poison", PokemonType.POISON, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Ground", "ground", PokemonType.GROUND, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Rock", "rock", PokemonType.ROCK, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Bug", "bug", PokemonType.BUG, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Ghost", "ghost", PokemonType.GHOST, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Steel", "steel", PokemonType.STEEL, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Fire", "fire", PokemonType.FIRE, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Water", "water", PokemonType.WATER, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Grass", "grass", PokemonType.GRASS, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Electric", "electric", PokemonType.ELECTRIC, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Psychic", "psychic", PokemonType.PSYCHIC, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Ice", "ice", PokemonType.ICE, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Dragon", "dragon", PokemonType.DRAGON, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Dark", "dark", PokemonType.DARK, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("Fairy", "fairy", PokemonType.FAIRY, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360), - new PokemonForm("???", "unknown", PokemonType.UNKNOWN, null, 3.2, 320, AbilityId.MULTITYPE, AbilityId.NONE, AbilityId.NONE, 720, 120, 120, 120, 120, 120, 120, 3, 0, 360, false, null, false, true), - ), - new PokemonSpecies(SpeciesId.VICTINI, 5, false, false, true, "Victory Pokémon", PokemonType.PSYCHIC, PokemonType.FIRE, 0.4, 4, AbilityId.VICTORY_STAR, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 100, 300, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.SNIVY, 5, false, false, false, "Grass Snake Pokémon", PokemonType.GRASS, null, 0.6, 8.1, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CONTRARY, 308, 45, 45, 55, 45, 55, 63, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.SERVINE, 5, false, false, false, "Grass Snake Pokémon", PokemonType.GRASS, null, 0.8, 16, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CONTRARY, 413, 60, 60, 75, 60, 75, 83, 45, 70, 145, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.SERPERIOR, 5, false, false, false, "Regal Pokémon", PokemonType.GRASS, null, 3.3, 63, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.CONTRARY, 528, 75, 75, 95, 75, 95, 113, 45, 70, 264, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.TEPIG, 5, false, false, false, "Fire Pig Pokémon", PokemonType.FIRE, null, 0.5, 9.9, AbilityId.BLAZE, AbilityId.NONE, AbilityId.THICK_FAT, 308, 65, 63, 45, 45, 45, 45, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.PIGNITE, 5, false, false, false, "Fire Pig Pokémon", PokemonType.FIRE, PokemonType.FIGHTING, 1, 55.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.THICK_FAT, 418, 90, 93, 55, 70, 55, 55, 45, 70, 146, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.EMBOAR, 5, false, false, false, "Mega Fire Pig Pokémon", PokemonType.FIRE, PokemonType.FIGHTING, 1.6, 150, AbilityId.BLAZE, AbilityId.NONE, AbilityId.RECKLESS, 528, 110, 123, 65, 100, 65, 65, 45, 70, 264, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.OSHAWOTT, 5, false, false, false, "Sea Otter Pokémon", PokemonType.WATER, null, 0.5, 5.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHELL_ARMOR, 308, 55, 55, 45, 63, 45, 45, 45, 70, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.DEWOTT, 5, false, false, false, "Discipline Pokémon", PokemonType.WATER, null, 0.8, 24.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHELL_ARMOR, 413, 75, 75, 60, 83, 60, 60, 45, 70, 145, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.SAMUROTT, 5, false, false, false, "Formidable Pokémon", PokemonType.WATER, null, 1.5, 94.6, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHELL_ARMOR, 528, 95, 100, 85, 108, 70, 70, 45, 70, 264, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.PATRAT, 5, false, false, false, "Scout Pokémon", PokemonType.NORMAL, null, 0.5, 11.6, AbilityId.RUN_AWAY, AbilityId.KEEN_EYE, AbilityId.ANALYTIC, 255, 45, 55, 39, 35, 39, 42, 255, 70, 51, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.WATCHOG, 5, false, false, false, "Lookout Pokémon", PokemonType.NORMAL, null, 1.1, 27, AbilityId.ILLUMINATE, AbilityId.KEEN_EYE, AbilityId.ANALYTIC, 420, 60, 85, 69, 60, 69, 77, 255, 70, 147, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.LILLIPUP, 5, false, false, false, "Puppy Pokémon", PokemonType.NORMAL, null, 0.4, 4.1, AbilityId.VITAL_SPIRIT, AbilityId.PICKUP, AbilityId.RUN_AWAY, 275, 45, 60, 45, 25, 45, 55, 255, 50, 55, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.HERDIER, 5, false, false, false, "Loyal Dog Pokémon", PokemonType.NORMAL, null, 0.9, 14.7, AbilityId.INTIMIDATE, AbilityId.SAND_RUSH, AbilityId.SCRAPPY, 370, 65, 80, 65, 35, 65, 60, 120, 50, 130, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.STOUTLAND, 5, false, false, false, "Big-Hearted Pokémon", PokemonType.NORMAL, null, 1.2, 61, AbilityId.INTIMIDATE, AbilityId.SAND_RUSH, AbilityId.SCRAPPY, 500, 85, 110, 90, 45, 90, 80, 45, 50, 250, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.PURRLOIN, 5, false, false, false, "Devious Pokémon", PokemonType.DARK, null, 0.4, 10.1, AbilityId.LIMBER, AbilityId.UNBURDEN, AbilityId.PRANKSTER, 281, 41, 50, 37, 50, 37, 66, 255, 50, 56, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.LIEPARD, 5, false, false, false, "Cruel Pokémon", PokemonType.DARK, null, 1.1, 37.5, AbilityId.LIMBER, AbilityId.UNBURDEN, AbilityId.PRANKSTER, 446, 64, 88, 50, 88, 50, 106, 90, 50, 156, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PANSAGE, 5, false, false, false, "Grass Monkey Pokémon", PokemonType.GRASS, null, 0.6, 10.5, AbilityId.GLUTTONY, AbilityId.NONE, AbilityId.OVERGROW, 316, 50, 53, 48, 53, 48, 64, 190, 70, 63, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.SIMISAGE, 5, false, false, false, "Thorn Monkey Pokémon", PokemonType.GRASS, null, 1.1, 30.5, AbilityId.GLUTTONY, AbilityId.NONE, AbilityId.OVERGROW, 498, 75, 98, 63, 98, 63, 101, 75, 70, 174, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.PANSEAR, 5, false, false, false, "High Temp Pokémon", PokemonType.FIRE, null, 0.6, 11, AbilityId.GLUTTONY, AbilityId.NONE, AbilityId.BLAZE, 316, 50, 53, 48, 53, 48, 64, 190, 70, 63, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.SIMISEAR, 5, false, false, false, "Ember Pokémon", PokemonType.FIRE, null, 1, 28, AbilityId.GLUTTONY, AbilityId.NONE, AbilityId.BLAZE, 498, 75, 98, 63, 98, 63, 101, 75, 70, 174, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.PANPOUR, 5, false, false, false, "Spray Pokémon", PokemonType.WATER, null, 0.6, 13.5, AbilityId.GLUTTONY, AbilityId.NONE, AbilityId.TORRENT, 316, 50, 53, 48, 53, 48, 64, 190, 70, 63, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.SIMIPOUR, 5, false, false, false, "Geyser Pokémon", PokemonType.WATER, null, 1, 29, AbilityId.GLUTTONY, AbilityId.NONE, AbilityId.TORRENT, 498, 75, 98, 63, 98, 63, 101, 75, 70, 174, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.MUNNA, 5, false, false, false, "Dream Eater Pokémon", PokemonType.PSYCHIC, null, 0.6, 23.3, AbilityId.FOREWARN, AbilityId.SYNCHRONIZE, AbilityId.TELEPATHY, 292, 76, 25, 45, 67, 55, 24, 190, 50, 58, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.MUSHARNA, 5, false, false, false, "Drowsing Pokémon", PokemonType.PSYCHIC, null, 1.1, 60.5, AbilityId.FOREWARN, AbilityId.SYNCHRONIZE, AbilityId.TELEPATHY, 487, 116, 55, 85, 107, 95, 29, 75, 50, 170, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.PIDOVE, 5, false, false, false, "Tiny Pigeon Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 2.1, AbilityId.BIG_PECKS, AbilityId.SUPER_LUCK, AbilityId.RIVALRY, 264, 50, 55, 50, 36, 30, 43, 255, 50, 53, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.TRANQUILL, 5, false, false, false, "Wild Pigeon Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 15, AbilityId.BIG_PECKS, AbilityId.SUPER_LUCK, AbilityId.RIVALRY, 358, 62, 77, 62, 50, 42, 65, 120, 50, 125, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.UNFEZANT, 5, false, false, false, "Proud Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.2, 29, AbilityId.BIG_PECKS, AbilityId.SUPER_LUCK, AbilityId.RIVALRY, 488, 80, 115, 80, 65, 55, 93, 45, 50, 244, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.BLITZLE, 5, false, false, false, "Electrified Pokémon", PokemonType.ELECTRIC, null, 0.8, 29.8, AbilityId.LIGHTNING_ROD, AbilityId.MOTOR_DRIVE, AbilityId.SAP_SIPPER, 295, 45, 60, 32, 50, 32, 76, 190, 70, 59, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ZEBSTRIKA, 5, false, false, false, "Thunderbolt Pokémon", PokemonType.ELECTRIC, null, 1.6, 79.5, AbilityId.LIGHTNING_ROD, AbilityId.MOTOR_DRIVE, AbilityId.SAP_SIPPER, 497, 75, 100, 63, 80, 63, 116, 75, 70, 174, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ROGGENROLA, 5, false, false, false, "Mantle Pokémon", PokemonType.ROCK, null, 0.4, 18, AbilityId.STURDY, AbilityId.WEAK_ARMOR, AbilityId.SAND_FORCE, 280, 55, 75, 85, 25, 25, 15, 255, 50, 56, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.BOLDORE, 5, false, false, false, "Ore Pokémon", PokemonType.ROCK, null, 0.9, 102, AbilityId.STURDY, AbilityId.WEAK_ARMOR, AbilityId.SAND_FORCE, 390, 70, 105, 105, 50, 40, 20, 120, 50, 137, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.GIGALITH, 5, false, false, false, "Compressed Pokémon", PokemonType.ROCK, null, 1.7, 260, AbilityId.STURDY, AbilityId.SAND_STREAM, AbilityId.SAND_FORCE, 515, 85, 135, 130, 60, 80, 25, 45, 50, 258, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.WOOBAT, 5, false, false, false, "Bat Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 0.4, 2.1, AbilityId.UNAWARE, AbilityId.KLUTZ, AbilityId.SIMPLE, 323, 65, 45, 43, 55, 43, 72, 190, 50, 65, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SWOOBAT, 5, false, false, false, "Courting Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 0.9, 10.5, AbilityId.UNAWARE, AbilityId.KLUTZ, AbilityId.SIMPLE, 425, 67, 57, 55, 77, 55, 114, 45, 50, 149, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DRILBUR, 5, false, false, false, "Mole Pokémon", PokemonType.GROUND, null, 0.3, 8.5, AbilityId.SAND_RUSH, AbilityId.SAND_FORCE, AbilityId.MOLD_BREAKER, 328, 60, 85, 40, 30, 45, 68, 120, 50, 66, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.EXCADRILL, 5, false, false, false, "Subterrene Pokémon", PokemonType.GROUND, PokemonType.STEEL, 0.7, 40.4, AbilityId.SAND_RUSH, AbilityId.SAND_FORCE, AbilityId.MOLD_BREAKER, 508, 110, 135, 60, 50, 65, 88, 60, 50, 178, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.AUDINO, 5, false, false, false, "Hearing Pokémon", PokemonType.NORMAL, null, 1.1, 31, AbilityId.HEALER, AbilityId.REGENERATOR, AbilityId.KLUTZ, 445, 103, 60, 86, 60, 86, 50, 255, 50, 390, GrowthRate.FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.NORMAL, null, 1.1, 31, AbilityId.HEALER, AbilityId.REGENERATOR, AbilityId.KLUTZ, 445, 103, 60, 86, 60, 86, 50, 255, 50, 390, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.NORMAL, PokemonType.FAIRY, 1.5, 32, AbilityId.REGENERATOR, AbilityId.REGENERATOR, AbilityId.REGENERATOR, 545, 103, 60, 126, 80, 126, 50, 255, 50, 390), //Custom Ability, base form Hidden Ability - ), - new PokemonSpecies(SpeciesId.TIMBURR, 5, false, false, false, "Muscular Pokémon", PokemonType.FIGHTING, null, 0.6, 12.5, AbilityId.GUTS, AbilityId.SHEER_FORCE, AbilityId.IRON_FIST, 305, 75, 80, 55, 25, 35, 35, 180, 70, 61, GrowthRate.MEDIUM_SLOW, 75, false), - new PokemonSpecies(SpeciesId.GURDURR, 5, false, false, false, "Muscular Pokémon", PokemonType.FIGHTING, null, 1.2, 40, AbilityId.GUTS, AbilityId.SHEER_FORCE, AbilityId.IRON_FIST, 405, 85, 105, 85, 40, 50, 40, 90, 50, 142, GrowthRate.MEDIUM_SLOW, 75, false), - new PokemonSpecies(SpeciesId.CONKELDURR, 5, false, false, false, "Muscular Pokémon", PokemonType.FIGHTING, null, 1.4, 87, AbilityId.GUTS, AbilityId.SHEER_FORCE, AbilityId.IRON_FIST, 505, 105, 140, 95, 55, 65, 45, 45, 50, 253, GrowthRate.MEDIUM_SLOW, 75, false), - new PokemonSpecies(SpeciesId.TYMPOLE, 5, false, false, false, "Tadpole Pokémon", PokemonType.WATER, null, 0.5, 4.5, AbilityId.SWIFT_SWIM, AbilityId.HYDRATION, AbilityId.WATER_ABSORB, 294, 50, 50, 40, 50, 40, 64, 255, 50, 59, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.PALPITOAD, 5, false, false, false, "Vibration Pokémon", PokemonType.WATER, PokemonType.GROUND, 0.8, 17, AbilityId.SWIFT_SWIM, AbilityId.HYDRATION, AbilityId.WATER_ABSORB, 384, 75, 65, 55, 65, 55, 69, 120, 50, 134, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SEISMITOAD, 5, false, false, false, "Vibration Pokémon", PokemonType.WATER, PokemonType.GROUND, 1.5, 62, AbilityId.SWIFT_SWIM, AbilityId.POISON_TOUCH, AbilityId.WATER_ABSORB, 509, 105, 95, 75, 85, 75, 74, 45, 50, 255, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.THROH, 5, false, false, false, "Judo Pokémon", PokemonType.FIGHTING, null, 1.3, 55.5, AbilityId.GUTS, AbilityId.INNER_FOCUS, AbilityId.MOLD_BREAKER, 465, 120, 100, 85, 30, 85, 45, 45, 50, 163, GrowthRate.MEDIUM_FAST, 100, false), - new PokemonSpecies(SpeciesId.SAWK, 5, false, false, false, "Karate Pokémon", PokemonType.FIGHTING, null, 1.4, 51, AbilityId.STURDY, AbilityId.INNER_FOCUS, AbilityId.MOLD_BREAKER, 465, 75, 125, 75, 30, 75, 85, 45, 50, 163, GrowthRate.MEDIUM_FAST, 100, false), - new PokemonSpecies(SpeciesId.SEWADDLE, 5, false, false, false, "Sewing Pokémon", PokemonType.BUG, PokemonType.GRASS, 0.3, 2.5, AbilityId.SWARM, AbilityId.CHLOROPHYLL, AbilityId.OVERCOAT, 310, 45, 53, 70, 40, 60, 42, 255, 70, 62, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SWADLOON, 5, false, false, false, "Leaf-Wrapped Pokémon", PokemonType.BUG, PokemonType.GRASS, 0.5, 7.3, AbilityId.LEAF_GUARD, AbilityId.CHLOROPHYLL, AbilityId.OVERCOAT, 380, 55, 63, 90, 50, 80, 42, 120, 70, 133, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.LEAVANNY, 5, false, false, false, "Nurturing Pokémon", PokemonType.BUG, PokemonType.GRASS, 1.2, 20.5, AbilityId.SWARM, AbilityId.CHLOROPHYLL, AbilityId.OVERCOAT, 500, 75, 103, 80, 70, 80, 92, 45, 70, 250, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.VENIPEDE, 5, false, false, false, "Centipede Pokémon", PokemonType.BUG, PokemonType.POISON, 0.4, 5.3, AbilityId.POISON_POINT, AbilityId.SWARM, AbilityId.SPEED_BOOST, 260, 30, 45, 59, 30, 39, 57, 255, 50, 52, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.WHIRLIPEDE, 5, false, false, false, "Curlipede Pokémon", PokemonType.BUG, PokemonType.POISON, 1.2, 58.5, AbilityId.POISON_POINT, AbilityId.SWARM, AbilityId.SPEED_BOOST, 360, 40, 55, 99, 40, 79, 47, 120, 50, 126, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SCOLIPEDE, 5, false, false, false, "Megapede Pokémon", PokemonType.BUG, PokemonType.POISON, 2.5, 200.5, AbilityId.POISON_POINT, AbilityId.SWARM, AbilityId.SPEED_BOOST, 485, 60, 100, 89, 55, 69, 112, 45, 50, 243, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.COTTONEE, 5, false, false, false, "Cotton Puff Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 0.3, 0.6, AbilityId.PRANKSTER, AbilityId.INFILTRATOR, AbilityId.CHLOROPHYLL, 280, 40, 27, 60, 37, 50, 66, 190, 50, 56, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.WHIMSICOTT, 5, false, false, false, "Windveiled Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 0.7, 6.6, AbilityId.PRANKSTER, AbilityId.INFILTRATOR, AbilityId.CHLOROPHYLL, 480, 60, 67, 85, 77, 75, 116, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PETILIL, 5, false, false, false, "Bulb Pokémon", PokemonType.GRASS, null, 0.5, 6.6, AbilityId.CHLOROPHYLL, AbilityId.OWN_TEMPO, AbilityId.LEAF_GUARD, 280, 45, 35, 50, 70, 50, 30, 190, 50, 56, GrowthRate.MEDIUM_FAST, 0, false), - new PokemonSpecies(SpeciesId.LILLIGANT, 5, false, false, false, "Flowering Pokémon", PokemonType.GRASS, null, 1.1, 16.3, AbilityId.CHLOROPHYLL, AbilityId.OWN_TEMPO, AbilityId.LEAF_GUARD, 480, 70, 60, 75, 110, 75, 90, 75, 50, 168, GrowthRate.MEDIUM_FAST, 0, false), - new PokemonSpecies(SpeciesId.BASCULIN, 5, false, false, false, "Hostile Pokémon", PokemonType.WATER, null, 1, 18, AbilityId.RECKLESS, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Red-Striped Form", "red-striped", PokemonType.WATER, null, 1, 18, AbilityId.RECKLESS, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, false, null, true), - new PokemonForm("Blue-Striped Form", "blue-striped", PokemonType.WATER, null, 1, 18, AbilityId.ROCK_HEAD, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, false, null, true), - new PokemonForm("White-Striped Form", "white-striped", PokemonType.WATER, null, 1, 18, AbilityId.RATTLED, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, false, null, true), - ), - new PokemonSpecies(SpeciesId.SANDILE, 5, false, false, false, "Desert Croc Pokémon", PokemonType.GROUND, PokemonType.DARK, 0.7, 15.2, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 292, 50, 72, 35, 35, 35, 65, 180, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.KROKOROK, 5, false, false, false, "Desert Croc Pokémon", PokemonType.GROUND, PokemonType.DARK, 1, 33.4, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 351, 60, 82, 45, 45, 45, 74, 90, 50, 123, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.KROOKODILE, 5, false, false, false, "Intimidation Pokémon", PokemonType.GROUND, PokemonType.DARK, 1.5, 96.3, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 519, 95, 117, 80, 65, 70, 92, 45, 50, 260, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.DARUMAKA, 5, false, false, false, "Zen Charm Pokémon", PokemonType.FIRE, null, 0.6, 37.5, AbilityId.HUSTLE, AbilityId.NONE, AbilityId.INNER_FOCUS, 315, 70, 90, 45, 15, 45, 50, 120, 50, 63, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.DARMANITAN, 5, false, false, false, "Blazing Pokémon", PokemonType.FIRE, null, 1.3, 92.9, AbilityId.SHEER_FORCE, AbilityId.NONE, AbilityId.ZEN_MODE, 480, 105, 140, 55, 30, 55, 95, 60, 50, 168, GrowthRate.MEDIUM_SLOW, 50, false, true, - new PokemonForm("Standard Mode", "", PokemonType.FIRE, null, 1.3, 92.9, AbilityId.SHEER_FORCE, AbilityId.NONE, AbilityId.ZEN_MODE, 480, 105, 140, 55, 30, 55, 95, 60, 50, 168, false, null, true), - new PokemonForm("Zen Mode", "zen", PokemonType.FIRE, PokemonType.PSYCHIC, 1.3, 92.9, AbilityId.SHEER_FORCE, AbilityId.NONE, AbilityId.ZEN_MODE, 540, 105, 30, 105, 140, 105, 55, 60, 50, 189), - ), - new PokemonSpecies(SpeciesId.MARACTUS, 5, false, false, false, "Cactus Pokémon", PokemonType.GRASS, null, 1, 28, AbilityId.WATER_ABSORB, AbilityId.CHLOROPHYLL, AbilityId.STORM_DRAIN, 461, 75, 86, 67, 106, 67, 60, 255, 50, 161, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DWEBBLE, 5, false, false, false, "Rock Inn Pokémon", PokemonType.BUG, PokemonType.ROCK, 0.3, 14.5, AbilityId.STURDY, AbilityId.SHELL_ARMOR, AbilityId.WEAK_ARMOR, 325, 50, 65, 85, 35, 35, 55, 190, 50, 65, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CRUSTLE, 5, false, false, false, "Stone Home Pokémon", PokemonType.BUG, PokemonType.ROCK, 1.4, 200, AbilityId.STURDY, AbilityId.SHELL_ARMOR, AbilityId.WEAK_ARMOR, 485, 70, 105, 125, 65, 75, 45, 75, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SCRAGGY, 5, false, false, false, "Shedding Pokémon", PokemonType.DARK, PokemonType.FIGHTING, 0.6, 11.8, AbilityId.SHED_SKIN, AbilityId.MOXIE, AbilityId.INTIMIDATE, 348, 50, 75, 70, 35, 70, 48, 180, 35, 70, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SCRAFTY, 5, false, false, false, "Hoodlum Pokémon", PokemonType.DARK, PokemonType.FIGHTING, 1.1, 30, AbilityId.SHED_SKIN, AbilityId.MOXIE, AbilityId.INTIMIDATE, 488, 65, 90, 115, 45, 115, 58, 90, 50, 171, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SIGILYPH, 5, false, false, false, "Avianoid Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 1.4, 14, AbilityId.WONDER_SKIN, AbilityId.MAGIC_GUARD, AbilityId.TINTED_LENS, 490, 72, 58, 80, 103, 80, 97, 45, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.YAMASK, 5, false, false, false, "Spirit Pokémon", PokemonType.GHOST, null, 0.5, 1.5, AbilityId.MUMMY, AbilityId.NONE, AbilityId.NONE, 303, 38, 30, 85, 55, 65, 30, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.COFAGRIGUS, 5, false, false, false, "Coffin Pokémon", PokemonType.GHOST, null, 1.7, 76.5, AbilityId.MUMMY, AbilityId.NONE, AbilityId.NONE, 483, 58, 50, 145, 95, 105, 30, 90, 50, 169, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.TIRTOUGA, 5, false, false, false, "Prototurtle Pokémon", PokemonType.WATER, PokemonType.ROCK, 0.7, 16.5, AbilityId.SOLID_ROCK, AbilityId.STURDY, AbilityId.SWIFT_SWIM, 355, 54, 78, 103, 53, 45, 22, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.CARRACOSTA, 5, false, false, false, "Prototurtle Pokémon", PokemonType.WATER, PokemonType.ROCK, 1.2, 81, AbilityId.SOLID_ROCK, AbilityId.STURDY, AbilityId.SWIFT_SWIM, 495, 74, 108, 133, 83, 65, 32, 45, 50, 173, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.ARCHEN, 5, false, false, false, "First Bird Pokémon", PokemonType.ROCK, PokemonType.FLYING, 0.5, 9.5, AbilityId.DEFEATIST, AbilityId.NONE, AbilityId.WIMP_OUT, 401, 55, 112, 45, 74, 45, 70, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), //Custom Hidden - new PokemonSpecies(SpeciesId.ARCHEOPS, 5, false, false, false, "First Bird Pokémon", PokemonType.ROCK, PokemonType.FLYING, 1.4, 32, AbilityId.DEFEATIST, AbilityId.NONE, AbilityId.EMERGENCY_EXIT, 567, 75, 140, 65, 112, 65, 110, 45, 50, 177, GrowthRate.MEDIUM_FAST, 87.5, false), //Custom Hidden - new PokemonSpecies(SpeciesId.TRUBBISH, 5, false, false, false, "Trash Bag Pokémon", PokemonType.POISON, null, 0.6, 31, AbilityId.STENCH, AbilityId.STICKY_HOLD, AbilityId.AFTERMATH, 329, 50, 50, 62, 40, 62, 65, 190, 50, 66, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GARBODOR, 5, false, false, false, "Trash Heap Pokémon", PokemonType.POISON, null, 1.9, 107.3, AbilityId.STENCH, AbilityId.WEAK_ARMOR, AbilityId.AFTERMATH, 474, 80, 95, 82, 60, 82, 75, 60, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.POISON, null, 1.9, 107.3, AbilityId.STENCH, AbilityId.WEAK_ARMOR, AbilityId.AFTERMATH, 474, 80, 95, 82, 60, 82, 75, 60, 50, 166, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.POISON, PokemonType.STEEL, 21, 999.9, AbilityId.TOXIC_DEBRIS, AbilityId.TOXIC_DEBRIS, AbilityId.TOXIC_DEBRIS, 574, 115, 121, 102, 81, 102, 53, 60, 50, 166), - ), - new PokemonSpecies(SpeciesId.ZORUA, 5, false, false, false, "Tricky Fox Pokémon", PokemonType.DARK, null, 0.7, 12.5, AbilityId.ILLUSION, AbilityId.NONE, AbilityId.NONE, 330, 40, 65, 40, 80, 40, 65, 75, 50, 66, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.ZOROARK, 5, false, false, false, "Illusion Fox Pokémon", PokemonType.DARK, null, 1.6, 81.1, AbilityId.ILLUSION, AbilityId.NONE, AbilityId.NONE, 510, 60, 105, 60, 120, 60, 105, 45, 50, 179, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.MINCCINO, 5, false, false, false, "Chinchilla Pokémon", PokemonType.NORMAL, null, 0.4, 5.8, AbilityId.CUTE_CHARM, AbilityId.TECHNICIAN, AbilityId.SKILL_LINK, 300, 55, 50, 40, 40, 40, 75, 255, 50, 60, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.CINCCINO, 5, false, false, false, "Scarf Pokémon", PokemonType.NORMAL, null, 0.5, 7.5, AbilityId.CUTE_CHARM, AbilityId.TECHNICIAN, AbilityId.SKILL_LINK, 470, 75, 95, 60, 65, 60, 115, 60, 50, 165, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.GOTHITA, 5, false, false, false, "Fixation Pokémon", PokemonType.PSYCHIC, null, 0.4, 5.8, AbilityId.FRISK, AbilityId.COMPETITIVE, AbilityId.SHADOW_TAG, 290, 45, 30, 50, 55, 65, 45, 200, 50, 58, GrowthRate.MEDIUM_SLOW, 25, false), - new PokemonSpecies(SpeciesId.GOTHORITA, 5, false, false, false, "Manipulate Pokémon", PokemonType.PSYCHIC, null, 0.7, 18, AbilityId.FRISK, AbilityId.COMPETITIVE, AbilityId.SHADOW_TAG, 390, 60, 45, 70, 75, 85, 55, 100, 50, 137, GrowthRate.MEDIUM_SLOW, 25, false), - new PokemonSpecies(SpeciesId.GOTHITELLE, 5, false, false, false, "Astral Body Pokémon", PokemonType.PSYCHIC, null, 1.5, 44, AbilityId.FRISK, AbilityId.COMPETITIVE, AbilityId.SHADOW_TAG, 490, 70, 55, 95, 95, 110, 65, 50, 50, 245, GrowthRate.MEDIUM_SLOW, 25, false), - new PokemonSpecies(SpeciesId.SOLOSIS, 5, false, false, false, "Cell Pokémon", PokemonType.PSYCHIC, null, 0.3, 1, AbilityId.OVERCOAT, AbilityId.MAGIC_GUARD, AbilityId.REGENERATOR, 290, 45, 30, 40, 105, 50, 20, 200, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.DUOSION, 5, false, false, false, "Mitosis Pokémon", PokemonType.PSYCHIC, null, 0.6, 8, AbilityId.OVERCOAT, AbilityId.MAGIC_GUARD, AbilityId.REGENERATOR, 370, 65, 40, 50, 125, 60, 30, 100, 50, 130, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.REUNICLUS, 5, false, false, false, "Multiplying Pokémon", PokemonType.PSYCHIC, null, 1, 20.1, AbilityId.OVERCOAT, AbilityId.MAGIC_GUARD, AbilityId.REGENERATOR, 490, 110, 65, 75, 125, 85, 30, 50, 50, 245, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.DUCKLETT, 5, false, false, false, "Water Bird Pokémon", PokemonType.WATER, PokemonType.FLYING, 0.5, 5.5, AbilityId.KEEN_EYE, AbilityId.BIG_PECKS, AbilityId.HYDRATION, 305, 62, 44, 50, 44, 50, 55, 190, 70, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SWANNA, 5, false, false, false, "White Bird Pokémon", PokemonType.WATER, PokemonType.FLYING, 1.3, 24.2, AbilityId.KEEN_EYE, AbilityId.BIG_PECKS, AbilityId.HYDRATION, 473, 75, 87, 63, 87, 63, 98, 45, 70, 166, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.VANILLITE, 5, false, false, false, "Fresh Snow Pokémon", PokemonType.ICE, null, 0.4, 5.7, AbilityId.ICE_BODY, AbilityId.SNOW_CLOAK, AbilityId.WEAK_ARMOR, 305, 36, 50, 50, 65, 60, 44, 255, 50, 61, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.VANILLISH, 5, false, false, false, "Icy Snow Pokémon", PokemonType.ICE, null, 1.1, 41, AbilityId.ICE_BODY, AbilityId.SNOW_CLOAK, AbilityId.WEAK_ARMOR, 395, 51, 65, 65, 80, 75, 59, 120, 50, 138, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.VANILLUXE, 5, false, false, false, "Snowstorm Pokémon", PokemonType.ICE, null, 1.3, 57.5, AbilityId.ICE_BODY, AbilityId.SNOW_WARNING, AbilityId.WEAK_ARMOR, 535, 71, 95, 85, 110, 95, 79, 45, 50, 268, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.DEERLING, 5, false, false, false, "Season Pokémon", PokemonType.NORMAL, PokemonType.GRASS, 0.6, 19.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 335, 60, 60, 50, 40, 50, 75, 190, 70, 67, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Spring Form", "spring", PokemonType.NORMAL, PokemonType.GRASS, 0.6, 19.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 335, 60, 60, 50, 40, 50, 75, 190, 70, 67, false, null, true), - new PokemonForm("Summer Form", "summer", PokemonType.NORMAL, PokemonType.GRASS, 0.6, 19.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 335, 60, 60, 50, 40, 50, 75, 190, 70, 67, false, null, true), - new PokemonForm("Autumn Form", "autumn", PokemonType.NORMAL, PokemonType.GRASS, 0.6, 19.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 335, 60, 60, 50, 40, 50, 75, 190, 70, 67, false, null, true), - new PokemonForm("Winter Form", "winter", PokemonType.NORMAL, PokemonType.GRASS, 0.6, 19.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 335, 60, 60, 50, 40, 50, 75, 190, 70, 67, false, null, true), - ), - new PokemonSpecies(SpeciesId.SAWSBUCK, 5, false, false, false, "Season Pokémon", PokemonType.NORMAL, PokemonType.GRASS, 1.9, 92.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 475, 80, 100, 70, 60, 70, 95, 75, 70, 166, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Spring Form", "spring", PokemonType.NORMAL, PokemonType.GRASS, 1.9, 92.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 475, 80, 100, 70, 60, 70, 95, 75, 70, 166, false, null, true), - new PokemonForm("Summer Form", "summer", PokemonType.NORMAL, PokemonType.GRASS, 1.9, 92.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 475, 80, 100, 70, 60, 70, 95, 75, 70, 166, false, null, true), - new PokemonForm("Autumn Form", "autumn", PokemonType.NORMAL, PokemonType.GRASS, 1.9, 92.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 475, 80, 100, 70, 60, 70, 95, 75, 70, 166, false, null, true), - new PokemonForm("Winter Form", "winter", PokemonType.NORMAL, PokemonType.GRASS, 1.9, 92.5, AbilityId.CHLOROPHYLL, AbilityId.SAP_SIPPER, AbilityId.SERENE_GRACE, 475, 80, 100, 70, 60, 70, 95, 75, 70, 166, false, null, true), - ), - new PokemonSpecies(SpeciesId.EMOLGA, 5, false, false, false, "Sky Squirrel Pokémon", PokemonType.ELECTRIC, PokemonType.FLYING, 0.4, 5, AbilityId.STATIC, AbilityId.NONE, AbilityId.MOTOR_DRIVE, 428, 55, 75, 60, 75, 60, 103, 200, 50, 150, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.KARRABLAST, 5, false, false, false, "Clamping Pokémon", PokemonType.BUG, null, 0.5, 5.9, AbilityId.SWARM, AbilityId.SHED_SKIN, AbilityId.NO_GUARD, 315, 50, 75, 45, 40, 45, 60, 200, 50, 63, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ESCAVALIER, 5, false, false, false, "Cavalry Pokémon", PokemonType.BUG, PokemonType.STEEL, 1, 33, AbilityId.SWARM, AbilityId.SHELL_ARMOR, AbilityId.OVERCOAT, 495, 70, 135, 105, 60, 105, 20, 75, 50, 173, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.FOONGUS, 5, false, false, false, "Mushroom Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.2, 1, AbilityId.EFFECT_SPORE, AbilityId.NONE, AbilityId.REGENERATOR, 294, 69, 55, 45, 55, 55, 15, 190, 50, 59, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.AMOONGUSS, 5, false, false, false, "Mushroom Pokémon", PokemonType.GRASS, PokemonType.POISON, 0.6, 10.5, AbilityId.EFFECT_SPORE, AbilityId.NONE, AbilityId.REGENERATOR, 464, 114, 85, 70, 85, 80, 30, 75, 50, 162, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.FRILLISH, 5, false, false, false, "Floating Pokémon", PokemonType.WATER, PokemonType.GHOST, 1.2, 33, AbilityId.WATER_ABSORB, AbilityId.CURSED_BODY, AbilityId.DAMP, 335, 55, 40, 50, 65, 85, 40, 190, 50, 67, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.JELLICENT, 5, false, false, false, "Floating Pokémon", PokemonType.WATER, PokemonType.GHOST, 2.2, 135, AbilityId.WATER_ABSORB, AbilityId.CURSED_BODY, AbilityId.DAMP, 480, 100, 60, 70, 85, 105, 60, 60, 50, 168, GrowthRate.MEDIUM_FAST, 50, true), - new PokemonSpecies(SpeciesId.ALOMOMOLA, 5, false, false, false, "Caring Pokémon", PokemonType.WATER, null, 1.2, 31.6, AbilityId.HEALER, AbilityId.HYDRATION, AbilityId.REGENERATOR, 470, 165, 75, 80, 40, 45, 65, 75, 70, 165, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.JOLTIK, 5, false, false, false, "Attaching Pokémon", PokemonType.BUG, PokemonType.ELECTRIC, 0.1, 0.6, AbilityId.COMPOUND_EYES, AbilityId.UNNERVE, AbilityId.SWARM, 319, 50, 47, 50, 57, 50, 65, 190, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GALVANTULA, 5, false, false, false, "EleSpider Pokémon", PokemonType.BUG, PokemonType.ELECTRIC, 0.8, 14.3, AbilityId.COMPOUND_EYES, AbilityId.UNNERVE, AbilityId.SWARM, 472, 70, 77, 60, 97, 60, 108, 75, 50, 165, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.FERROSEED, 5, false, false, false, "Thorn Seed Pokémon", PokemonType.GRASS, PokemonType.STEEL, 0.6, 18.8, AbilityId.IRON_BARBS, AbilityId.NONE, AbilityId.ANTICIPATION, 305, 44, 50, 91, 24, 86, 10, 255, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.FERROTHORN, 5, false, false, false, "Thorn Pod Pokémon", PokemonType.GRASS, PokemonType.STEEL, 1, 110, AbilityId.IRON_BARBS, AbilityId.NONE, AbilityId.ANTICIPATION, 489, 74, 94, 131, 54, 116, 20, 90, 50, 171, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.KLINK, 5, false, false, false, "Gear Pokémon", PokemonType.STEEL, null, 0.3, 21, AbilityId.PLUS, AbilityId.MINUS, AbilityId.CLEAR_BODY, 300, 40, 55, 70, 45, 60, 30, 130, 50, 60, GrowthRate.MEDIUM_SLOW, null, false), - new PokemonSpecies(SpeciesId.KLANG, 5, false, false, false, "Gear Pokémon", PokemonType.STEEL, null, 0.6, 51, AbilityId.PLUS, AbilityId.MINUS, AbilityId.CLEAR_BODY, 440, 60, 80, 95, 70, 85, 50, 60, 50, 154, GrowthRate.MEDIUM_SLOW, null, false), - new PokemonSpecies(SpeciesId.KLINKLANG, 5, false, false, false, "Gear Pokémon", PokemonType.STEEL, null, 0.6, 81, AbilityId.PLUS, AbilityId.MINUS, AbilityId.CLEAR_BODY, 520, 60, 100, 115, 70, 85, 90, 30, 50, 260, GrowthRate.MEDIUM_SLOW, null, false), - new PokemonSpecies(SpeciesId.TYNAMO, 5, false, false, false, "EleFish Pokémon", PokemonType.ELECTRIC, null, 0.2, 0.3, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 275, 35, 55, 40, 45, 40, 60, 190, 70, 55, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.EELEKTRIK, 5, false, false, false, "EleFish Pokémon", PokemonType.ELECTRIC, null, 1.2, 22, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 405, 65, 85, 70, 75, 70, 40, 60, 70, 142, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.EELEKTROSS, 5, false, false, false, "EleFish Pokémon", PokemonType.ELECTRIC, null, 2.1, 80.5, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 515, 85, 115, 80, 105, 80, 50, 30, 70, 258, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.ELGYEM, 5, false, false, false, "Cerebral Pokémon", PokemonType.PSYCHIC, null, 0.5, 9, AbilityId.TELEPATHY, AbilityId.SYNCHRONIZE, AbilityId.ANALYTIC, 335, 55, 55, 55, 85, 55, 30, 255, 50, 67, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BEHEEYEM, 5, false, false, false, "Cerebral Pokémon", PokemonType.PSYCHIC, null, 1, 34.5, AbilityId.TELEPATHY, AbilityId.SYNCHRONIZE, AbilityId.ANALYTIC, 485, 75, 75, 75, 125, 95, 40, 90, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.LITWICK, 5, false, false, false, "Candle Pokémon", PokemonType.GHOST, PokemonType.FIRE, 0.3, 3.1, AbilityId.FLASH_FIRE, AbilityId.FLAME_BODY, AbilityId.INFILTRATOR, 275, 50, 30, 55, 65, 55, 20, 190, 50, 55, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.LAMPENT, 5, false, false, false, "Lamp Pokémon", PokemonType.GHOST, PokemonType.FIRE, 0.6, 13, AbilityId.FLASH_FIRE, AbilityId.FLAME_BODY, AbilityId.INFILTRATOR, 370, 60, 40, 60, 95, 60, 55, 90, 50, 130, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.CHANDELURE, 5, false, false, false, "Luring Pokémon", PokemonType.GHOST, PokemonType.FIRE, 1, 34.3, AbilityId.FLASH_FIRE, AbilityId.FLAME_BODY, AbilityId.INFILTRATOR, 520, 60, 55, 90, 145, 90, 80, 45, 50, 260, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.AXEW, 5, false, false, false, "Tusk Pokémon", PokemonType.DRAGON, null, 0.6, 18, AbilityId.RIVALRY, AbilityId.MOLD_BREAKER, AbilityId.UNNERVE, 320, 46, 87, 60, 30, 40, 57, 75, 35, 64, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.FRAXURE, 5, false, false, false, "Axe Jaw Pokémon", PokemonType.DRAGON, null, 1, 36, AbilityId.RIVALRY, AbilityId.MOLD_BREAKER, AbilityId.UNNERVE, 410, 66, 117, 70, 40, 50, 67, 60, 35, 144, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.HAXORUS, 5, false, false, false, "Axe Jaw Pokémon", PokemonType.DRAGON, null, 1.8, 105.5, AbilityId.RIVALRY, AbilityId.MOLD_BREAKER, AbilityId.UNNERVE, 540, 76, 147, 90, 60, 70, 97, 45, 35, 270, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.CUBCHOO, 5, false, false, false, "Chill Pokémon", PokemonType.ICE, null, 0.5, 8.5, AbilityId.SNOW_CLOAK, AbilityId.SLUSH_RUSH, AbilityId.RATTLED, 305, 55, 70, 40, 60, 40, 40, 120, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BEARTIC, 5, false, false, false, "Freezing Pokémon", PokemonType.ICE, null, 2.6, 260, AbilityId.SNOW_CLOAK, AbilityId.SLUSH_RUSH, AbilityId.SWIFT_SWIM, 505, 95, 130, 80, 70, 80, 50, 60, 50, 177, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CRYOGONAL, 5, false, false, false, "Crystallizing Pokémon", PokemonType.ICE, null, 1.1, 148, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 515, 80, 50, 50, 95, 135, 105, 25, 50, 180, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.SHELMET, 5, false, false, false, "Snail Pokémon", PokemonType.BUG, null, 0.4, 7.7, AbilityId.HYDRATION, AbilityId.SHELL_ARMOR, AbilityId.OVERCOAT, 305, 50, 40, 85, 40, 65, 25, 200, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ACCELGOR, 5, false, false, false, "Shell Out Pokémon", PokemonType.BUG, null, 0.8, 25.3, AbilityId.HYDRATION, AbilityId.STICKY_HOLD, AbilityId.UNBURDEN, 495, 80, 70, 40, 100, 60, 145, 75, 50, 173, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.STUNFISK, 5, false, false, false, "Trap Pokémon", PokemonType.GROUND, PokemonType.ELECTRIC, 0.7, 11, AbilityId.STATIC, AbilityId.LIMBER, AbilityId.SAND_VEIL, 471, 109, 66, 84, 81, 99, 32, 75, 70, 165, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MIENFOO, 5, false, false, false, "Martial Arts Pokémon", PokemonType.FIGHTING, null, 0.9, 20, AbilityId.INNER_FOCUS, AbilityId.REGENERATOR, AbilityId.RECKLESS, 350, 45, 85, 50, 55, 50, 65, 180, 50, 70, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.MIENSHAO, 5, false, false, false, "Martial Arts Pokémon", PokemonType.FIGHTING, null, 1.4, 35.5, AbilityId.INNER_FOCUS, AbilityId.REGENERATOR, AbilityId.RECKLESS, 510, 65, 125, 60, 95, 60, 105, 45, 50, 179, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.DRUDDIGON, 5, false, false, false, "Cave Pokémon", PokemonType.DRAGON, null, 1.6, 139, AbilityId.ROUGH_SKIN, AbilityId.SHEER_FORCE, AbilityId.MOLD_BREAKER, 485, 77, 120, 90, 60, 90, 48, 45, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GOLETT, 5, false, false, false, "Automaton Pokémon", PokemonType.GROUND, PokemonType.GHOST, 1, 92, AbilityId.IRON_FIST, AbilityId.KLUTZ, AbilityId.NO_GUARD, 303, 59, 74, 50, 35, 50, 35, 190, 50, 61, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.GOLURK, 5, false, false, false, "Automaton Pokémon", PokemonType.GROUND, PokemonType.GHOST, 2.8, 330, AbilityId.IRON_FIST, AbilityId.KLUTZ, AbilityId.NO_GUARD, 483, 89, 124, 80, 55, 80, 55, 90, 50, 169, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.PAWNIARD, 5, false, false, false, "Sharp Blade Pokémon", PokemonType.DARK, PokemonType.STEEL, 0.5, 10.2, AbilityId.DEFIANT, AbilityId.INNER_FOCUS, AbilityId.PRESSURE, 340, 45, 85, 70, 40, 40, 60, 120, 35, 68, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BISHARP, 5, false, false, false, "Sword Blade Pokémon", PokemonType.DARK, PokemonType.STEEL, 1.6, 70, AbilityId.DEFIANT, AbilityId.INNER_FOCUS, AbilityId.PRESSURE, 490, 65, 125, 100, 60, 70, 70, 45, 35, 172, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BOUFFALANT, 5, false, false, false, "Bash Buffalo Pokémon", PokemonType.NORMAL, null, 1.6, 94.6, AbilityId.RECKLESS, AbilityId.SAP_SIPPER, AbilityId.SOUNDPROOF, 490, 95, 110, 95, 40, 95, 55, 45, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.RUFFLET, 5, false, false, false, "Eaglet Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.5, 10.5, AbilityId.KEEN_EYE, AbilityId.SHEER_FORCE, AbilityId.HUSTLE, 350, 70, 83, 50, 37, 50, 60, 190, 50, 70, GrowthRate.SLOW, 100, false), - new PokemonSpecies(SpeciesId.BRAVIARY, 5, false, false, false, "Valiant Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.5, 41, AbilityId.KEEN_EYE, AbilityId.SHEER_FORCE, AbilityId.DEFIANT, 510, 100, 123, 75, 57, 75, 80, 60, 50, 179, GrowthRate.SLOW, 100, false), - new PokemonSpecies(SpeciesId.VULLABY, 5, false, false, false, "Diapered Pokémon", PokemonType.DARK, PokemonType.FLYING, 0.5, 9, AbilityId.BIG_PECKS, AbilityId.OVERCOAT, AbilityId.WEAK_ARMOR, 370, 70, 55, 75, 45, 65, 60, 190, 35, 74, GrowthRate.SLOW, 0, false), - new PokemonSpecies(SpeciesId.MANDIBUZZ, 5, false, false, false, "Bone Vulture Pokémon", PokemonType.DARK, PokemonType.FLYING, 1.2, 39.5, AbilityId.BIG_PECKS, AbilityId.OVERCOAT, AbilityId.WEAK_ARMOR, 510, 110, 65, 105, 55, 95, 80, 60, 35, 179, GrowthRate.SLOW, 0, false), - new PokemonSpecies(SpeciesId.HEATMOR, 5, false, false, false, "Anteater Pokémon", PokemonType.FIRE, null, 1.4, 58, AbilityId.GLUTTONY, AbilityId.FLASH_FIRE, AbilityId.WHITE_SMOKE, 484, 85, 97, 66, 105, 66, 65, 90, 50, 169, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DURANT, 5, false, false, false, "Iron Ant Pokémon", PokemonType.BUG, PokemonType.STEEL, 0.3, 33, AbilityId.SWARM, AbilityId.HUSTLE, AbilityId.TRUANT, 484, 58, 109, 112, 48, 48, 109, 90, 50, 169, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DEINO, 5, false, false, false, "Irate Pokémon", PokemonType.DARK, PokemonType.DRAGON, 0.8, 17.3, AbilityId.HUSTLE, AbilityId.NONE, AbilityId.NONE, 300, 52, 65, 50, 45, 50, 38, 45, 35, 60, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.ZWEILOUS, 5, false, false, false, "Hostile Pokémon", PokemonType.DARK, PokemonType.DRAGON, 1.4, 50, AbilityId.HUSTLE, AbilityId.NONE, AbilityId.NONE, 420, 72, 85, 70, 65, 70, 58, 45, 35, 147, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.HYDREIGON, 5, false, false, false, "Brutal Pokémon", PokemonType.DARK, PokemonType.DRAGON, 1.8, 160, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 600, 92, 105, 90, 125, 90, 98, 45, 35, 300, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.LARVESTA, 5, false, false, false, "Torch Pokémon", PokemonType.BUG, PokemonType.FIRE, 1.1, 28.8, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.SWARM, 360, 55, 85, 55, 50, 55, 60, 45, 50, 72, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.VOLCARONA, 5, false, false, false, "Sun Pokémon", PokemonType.BUG, PokemonType.FIRE, 1.6, 46, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.SWARM, 550, 85, 60, 65, 135, 105, 100, 15, 50, 275, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.COBALION, 5, true, false, false, "Iron Will Pokémon", PokemonType.STEEL, PokemonType.FIGHTING, 2.1, 250, AbilityId.JUSTIFIED, AbilityId.NONE, AbilityId.NONE, 580, 91, 90, 129, 90, 72, 108, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.TERRAKION, 5, true, false, false, "Cavern Pokémon", PokemonType.ROCK, PokemonType.FIGHTING, 1.9, 260, AbilityId.JUSTIFIED, AbilityId.NONE, AbilityId.NONE, 580, 91, 129, 90, 72, 90, 108, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.VIRIZION, 5, true, false, false, "Grassland Pokémon", PokemonType.GRASS, PokemonType.FIGHTING, 2, 200, AbilityId.JUSTIFIED, AbilityId.NONE, AbilityId.NONE, 580, 91, 90, 72, 90, 129, 108, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.TORNADUS, 5, true, false, false, "Cyclone Pokémon", PokemonType.FLYING, null, 1.5, 63, AbilityId.PRANKSTER, AbilityId.NONE, AbilityId.DEFIANT, 580, 79, 115, 70, 125, 80, 111, 3, 90, 290, GrowthRate.SLOW, 100, false, true, - new PokemonForm("Incarnate Forme", "incarnate", PokemonType.FLYING, null, 1.5, 63, AbilityId.PRANKSTER, AbilityId.NONE, AbilityId.DEFIANT, 580, 79, 115, 70, 125, 80, 111, 3, 90, 290, false, null, true), - new PokemonForm("Therian Forme", "therian", PokemonType.FLYING, null, 1.4, 63, AbilityId.REGENERATOR, AbilityId.NONE, AbilityId.REGENERATOR, 580, 79, 100, 80, 110, 90, 121, 3, 90, 290), - ), - new PokemonSpecies(SpeciesId.THUNDURUS, 5, true, false, false, "Bolt Strike Pokémon", PokemonType.ELECTRIC, PokemonType.FLYING, 1.5, 61, AbilityId.PRANKSTER, AbilityId.NONE, AbilityId.DEFIANT, 580, 79, 115, 70, 125, 80, 111, 3, 90, 290, GrowthRate.SLOW, 100, false, true, - new PokemonForm("Incarnate Forme", "incarnate", PokemonType.ELECTRIC, PokemonType.FLYING, 1.5, 61, AbilityId.PRANKSTER, AbilityId.NONE, AbilityId.DEFIANT, 580, 79, 115, 70, 125, 80, 111, 3, 90, 290, false, null, true), - new PokemonForm("Therian Forme", "therian", PokemonType.ELECTRIC, PokemonType.FLYING, 3, 61, AbilityId.VOLT_ABSORB, AbilityId.NONE, AbilityId.VOLT_ABSORB, 580, 79, 105, 70, 145, 80, 101, 3, 90, 290), - ), - new PokemonSpecies(SpeciesId.RESHIRAM, 5, false, true, false, "Vast White Pokémon", PokemonType.DRAGON, PokemonType.FIRE, 3.2, 330, AbilityId.TURBOBLAZE, AbilityId.NONE, AbilityId.NONE, 680, 100, 120, 100, 150, 120, 90, 3, 0, 340, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.ZEKROM, 5, false, true, false, "Deep Black Pokémon", PokemonType.DRAGON, PokemonType.ELECTRIC, 2.9, 345, AbilityId.TERAVOLT, AbilityId.NONE, AbilityId.NONE, 680, 100, 150, 120, 120, 100, 90, 3, 0, 340, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.LANDORUS, 5, true, false, false, "Abundance Pokémon", PokemonType.GROUND, PokemonType.FLYING, 1.5, 68, AbilityId.SAND_FORCE, AbilityId.NONE, AbilityId.SHEER_FORCE, 600, 89, 125, 90, 115, 80, 101, 3, 90, 300, GrowthRate.SLOW, 100, false, true, - new PokemonForm("Incarnate Forme", "incarnate", PokemonType.GROUND, PokemonType.FLYING, 1.5, 68, AbilityId.SAND_FORCE, AbilityId.NONE, AbilityId.SHEER_FORCE, 600, 89, 125, 90, 115, 80, 101, 3, 90, 300, false, null, true), - new PokemonForm("Therian Forme", "therian", PokemonType.GROUND, PokemonType.FLYING, 1.3, 68, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.INTIMIDATE, 600, 89, 145, 90, 105, 80, 91, 3, 90, 300), - ), - new PokemonSpecies(SpeciesId.KYUREM, 5, false, true, false, "Boundary Pokémon", PokemonType.DRAGON, PokemonType.ICE, 3, 325, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 660, 125, 130, 90, 130, 90, 95, 3, 0, 330, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.ICE, 3, 325, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 660, 125, 130, 90, 130, 90, 95, 3, 0, 330, false, null, true), - new PokemonForm("Black", "black", PokemonType.DRAGON, PokemonType.ICE, 3.3, 325, AbilityId.TERAVOLT, AbilityId.NONE, AbilityId.NONE, 700, 125, 170, 100, 120, 90, 95, 3, 0, 350), - new PokemonForm("White", "white", PokemonType.DRAGON, PokemonType.ICE, 3.6, 325, AbilityId.TURBOBLAZE, AbilityId.NONE, AbilityId.NONE, 700, 125, 120, 90, 170, 100, 95, 3, 0, 350), - ), - new PokemonSpecies(SpeciesId.KELDEO, 5, false, false, true, "Colt Pokémon", PokemonType.WATER, PokemonType.FIGHTING, 1.4, 48.5, AbilityId.JUSTIFIED, AbilityId.NONE, AbilityId.NONE, 580, 91, 72, 90, 129, 90, 108, 3, 35, 290, GrowthRate.SLOW, null, false, true, - new PokemonForm("Ordinary Form", "ordinary", PokemonType.WATER, PokemonType.FIGHTING, 1.4, 48.5, AbilityId.JUSTIFIED, AbilityId.NONE, AbilityId.NONE, 580, 91, 72, 90, 129, 90, 108, 3, 35, 290, false, null, true), - new PokemonForm("Resolute", "resolute", PokemonType.WATER, PokemonType.FIGHTING, 1.4, 48.5, AbilityId.JUSTIFIED, AbilityId.NONE, AbilityId.NONE, 580, 91, 72, 90, 129, 90, 108, 3, 35, 290), - ), - new PokemonSpecies(SpeciesId.MELOETTA, 5, false, false, true, "Melody Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 0.6, 6.5, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 77, 77, 128, 128, 90, 3, 100, 300, GrowthRate.SLOW, null, false, true, - new PokemonForm("Aria Forme", "aria", PokemonType.NORMAL, PokemonType.PSYCHIC, 0.6, 6.5, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 77, 77, 128, 128, 90, 3, 100, 300, false, null, true), - new PokemonForm("Pirouette Forme", "pirouette", PokemonType.NORMAL, PokemonType.FIGHTING, 0.6, 6.5, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 128, 90, 77, 77, 128, 3, 100, 300, false, null, true), - ), - new PokemonSpecies(SpeciesId.GENESECT, 5, false, false, true, "Paleozoic Pokémon", PokemonType.BUG, PokemonType.STEEL, 1.5, 82.5, AbilityId.DOWNLOAD, AbilityId.NONE, AbilityId.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal", "", PokemonType.BUG, PokemonType.STEEL, 1.5, 82.5, AbilityId.DOWNLOAD, AbilityId.NONE, AbilityId.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300, false, null, true), - new PokemonForm("Shock Drive", "shock", PokemonType.BUG, PokemonType.STEEL, 1.5, 82.5, AbilityId.DOWNLOAD, AbilityId.NONE, AbilityId.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300), - new PokemonForm("Burn Drive", "burn", PokemonType.BUG, PokemonType.STEEL, 1.5, 82.5, AbilityId.DOWNLOAD, AbilityId.NONE, AbilityId.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300), - new PokemonForm("Chill Drive", "chill", PokemonType.BUG, PokemonType.STEEL, 1.5, 82.5, AbilityId.DOWNLOAD, AbilityId.NONE, AbilityId.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300), - new PokemonForm("Douse Drive", "douse", PokemonType.BUG, PokemonType.STEEL, 1.5, 82.5, AbilityId.DOWNLOAD, AbilityId.NONE, AbilityId.NONE, 600, 71, 120, 95, 120, 95, 99, 3, 0, 300), - ), - new PokemonSpecies(SpeciesId.CHESPIN, 6, false, false, false, "Spiny Nut Pokémon", PokemonType.GRASS, null, 0.4, 9, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.BULLETPROOF, 313, 56, 61, 65, 48, 45, 38, 45, 70, 63, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.QUILLADIN, 6, false, false, false, "Spiny Armor Pokémon", PokemonType.GRASS, null, 0.7, 29, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.BULLETPROOF, 405, 61, 78, 95, 56, 58, 57, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.CHESNAUGHT, 6, false, false, false, "Spiny Armor Pokémon", PokemonType.GRASS, PokemonType.FIGHTING, 1.6, 90, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.BULLETPROOF, 530, 88, 107, 122, 74, 75, 64, 45, 70, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.FENNEKIN, 6, false, false, false, "Fox Pokémon", PokemonType.FIRE, null, 0.4, 9.4, AbilityId.BLAZE, AbilityId.NONE, AbilityId.MAGICIAN, 307, 40, 45, 40, 62, 60, 60, 45, 70, 61, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.BRAIXEN, 6, false, false, false, "Fox Pokémon", PokemonType.FIRE, null, 1, 14.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.MAGICIAN, 409, 59, 59, 58, 90, 70, 73, 45, 70, 143, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.DELPHOX, 6, false, false, false, "Fox Pokémon", PokemonType.FIRE, PokemonType.PSYCHIC, 1.5, 39, AbilityId.BLAZE, AbilityId.NONE, AbilityId.MAGICIAN, 534, 75, 69, 72, 114, 100, 104, 45, 70, 267, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.FROAKIE, 6, false, false, false, "Bubble Frog Pokémon", PokemonType.WATER, null, 0.3, 7, AbilityId.TORRENT, AbilityId.NONE, AbilityId.PROTEAN, 314, 41, 56, 40, 62, 44, 71, 45, 70, 63, GrowthRate.MEDIUM_SLOW, 87.5, false, false, - new PokemonForm("Normal", "", PokemonType.WATER, null, 0.3, 7, AbilityId.TORRENT, AbilityId.NONE, AbilityId.PROTEAN, 314, 41, 56, 40, 62, 44, 71, 45, 70, 63, false, null, true), - new PokemonForm("Battle Bond", "battle-bond", PokemonType.WATER, null, 0.3, 7, AbilityId.TORRENT, AbilityId.NONE, AbilityId.TORRENT, 314, 41, 56, 40, 62, 44, 71, 45, 70, 63, false, "", true), - ), - new PokemonSpecies(SpeciesId.FROGADIER, 6, false, false, false, "Bubble Frog Pokémon", PokemonType.WATER, null, 0.6, 10.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.PROTEAN, 405, 54, 63, 52, 83, 56, 97, 45, 70, 142, GrowthRate.MEDIUM_SLOW, 87.5, false, false, - new PokemonForm("Normal", "", PokemonType.WATER, null, 0.6, 10.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.PROTEAN, 405, 54, 63, 52, 83, 56, 97, 45, 70, 142, false, null, true), - new PokemonForm("Battle Bond", "battle-bond", PokemonType.WATER, null, 0.6, 10.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.NONE, 405, 54, 63, 52, 83, 56, 97, 45, 70, 142, false, "", true), - ), - new PokemonSpecies(SpeciesId.GRENINJA, 6, false, false, false, "Ninja Pokémon", PokemonType.WATER, PokemonType.DARK, 1.5, 40, AbilityId.TORRENT, AbilityId.NONE, AbilityId.PROTEAN, 530, 72, 95, 67, 103, 71, 122, 45, 70, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, false, - new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.DARK, 1.5, 40, AbilityId.TORRENT, AbilityId.NONE, AbilityId.PROTEAN, 530, 72, 95, 67, 103, 71, 122, 45, 70, 265, false, null, true), - new PokemonForm("Battle Bond", "battle-bond", PokemonType.WATER, PokemonType.DARK, 1.5, 40, AbilityId.BATTLE_BOND, AbilityId.NONE, AbilityId.BATTLE_BOND, 530, 72, 95, 67, 103, 71, 122, 45, 70, 265, false, "", true), - new PokemonForm("Ash", "ash", PokemonType.WATER, PokemonType.DARK, 1.5, 40, AbilityId.BATTLE_BOND, AbilityId.NONE, AbilityId.NONE, 640, 72, 145, 67, 153, 71, 132, 45, 70, 265), - ), - new PokemonSpecies(SpeciesId.BUNNELBY, 6, false, false, false, "Digging Pokémon", PokemonType.NORMAL, null, 0.4, 5, AbilityId.PICKUP, AbilityId.CHEEK_POUCH, AbilityId.HUGE_POWER, 237, 38, 36, 38, 32, 36, 57, 255, 50, 47, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DIGGERSBY, 6, false, false, false, "Digging Pokémon", PokemonType.NORMAL, PokemonType.GROUND, 1, 42.4, AbilityId.PICKUP, AbilityId.CHEEK_POUCH, AbilityId.HUGE_POWER, 423, 85, 56, 77, 50, 77, 78, 127, 50, 148, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.FLETCHLING, 6, false, false, false, "Tiny Robin Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 1.7, AbilityId.BIG_PECKS, AbilityId.NONE, AbilityId.GALE_WINGS, 278, 45, 50, 43, 40, 38, 62, 255, 50, 56, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.FLETCHINDER, 6, false, false, false, "Ember Pokémon", PokemonType.FIRE, PokemonType.FLYING, 0.7, 16, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.GALE_WINGS, 382, 62, 73, 55, 56, 52, 84, 120, 50, 134, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.TALONFLAME, 6, false, false, false, "Scorching Pokémon", PokemonType.FIRE, PokemonType.FLYING, 1.2, 24.5, AbilityId.FLAME_BODY, AbilityId.NONE, AbilityId.GALE_WINGS, 499, 78, 81, 71, 74, 69, 126, 45, 50, 175, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SCATTERBUG, 6, false, false, false, "Scatterdust Pokémon", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Meadow Pattern", "meadow", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Icy Snow Pattern", "icy-snow", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Polar Pattern", "polar", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Tundra Pattern", "tundra", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Continental Pattern", "continental", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Garden Pattern", "garden", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Elegant Pattern", "elegant", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Modern Pattern", "modern", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Marine Pattern", "marine", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Archipelago Pattern", "archipelago", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("High Plains Pattern", "high-plains", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Sandstorm Pattern", "sandstorm", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("River Pattern", "river", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Monsoon Pattern", "monsoon", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Savanna Pattern", "savanna", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Sun Pattern", "sun", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Ocean Pattern", "ocean", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Jungle Pattern", "jungle", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Fancy Pattern", "fancy", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - new PokemonForm("Poké Ball Pattern", "poke-ball", PokemonType.BUG, null, 0.3, 2.5, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 200, 38, 35, 40, 27, 25, 35, 255, 70, 40, false, "", true), - ), - new PokemonSpecies(SpeciesId.SPEWPA, 6, false, false, false, "Scatterdust Pokémon", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.SHED_SKIN, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Meadow Pattern", "meadow", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Icy Snow Pattern", "icy-snow", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Polar Pattern", "polar", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Tundra Pattern", "tundra", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Continental Pattern", "continental", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Garden Pattern", "garden", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Elegant Pattern", "elegant", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Modern Pattern", "modern", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Marine Pattern", "marine", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Archipelago Pattern", "archipelago", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("High Plains Pattern", "high-plains", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Sandstorm Pattern", "sandstorm", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("River Pattern", "river", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Monsoon Pattern", "monsoon", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Savanna Pattern", "savanna", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Sun Pattern", "sun", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Ocean Pattern", "ocean", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Jungle Pattern", "jungle", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Fancy Pattern", "fancy", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - new PokemonForm("Poké Ball Pattern", "poke-ball", PokemonType.BUG, null, 0.3, 8.4, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.FRIEND_GUARD, 213, 45, 22, 60, 27, 30, 29, 120, 70, 75, false, "", true), - ), - new PokemonSpecies(SpeciesId.VIVILLON, 6, false, false, false, "Scale Pokémon", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Meadow Pattern", "meadow", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Icy Snow Pattern", "icy-snow", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Polar Pattern", "polar", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Tundra Pattern", "tundra", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Continental Pattern", "continental", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Garden Pattern", "garden", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Elegant Pattern", "elegant", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Modern Pattern", "modern", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Marine Pattern", "marine", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Archipelago Pattern", "archipelago", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("High Plains Pattern", "high-plains", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Sandstorm Pattern", "sandstorm", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("River Pattern", "river", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Monsoon Pattern", "monsoon", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Savanna Pattern", "savanna", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Sun Pattern", "sun", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Ocean Pattern", "ocean", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Jungle Pattern", "jungle", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Fancy Pattern", "fancy", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - new PokemonForm("Poké Ball Pattern", "poke-ball", PokemonType.BUG, PokemonType.FLYING, 1.2, 17, AbilityId.SHIELD_DUST, AbilityId.COMPOUND_EYES, AbilityId.FRIEND_GUARD, 411, 80, 52, 50, 90, 50, 89, 45, 70, 206, false, null, true), - ), - new PokemonSpecies(SpeciesId.LITLEO, 6, false, false, false, "Lion Cub Pokémon", PokemonType.FIRE, PokemonType.NORMAL, 0.6, 13.5, AbilityId.RIVALRY, AbilityId.UNNERVE, AbilityId.MOXIE, 369, 62, 50, 58, 73, 54, 72, 220, 70, 74, GrowthRate.MEDIUM_SLOW, 12.5, false), - new PokemonSpecies(SpeciesId.PYROAR, 6, false, false, false, "Royal Pokémon", PokemonType.FIRE, PokemonType.NORMAL, 1.5, 81.5, AbilityId.RIVALRY, AbilityId.UNNERVE, AbilityId.MOXIE, 507, 86, 68, 72, 109, 66, 106, 65, 70, 177, GrowthRate.MEDIUM_SLOW, 12.5, true), - new PokemonSpecies(SpeciesId.FLABEBE, 6, false, false, false, "Single Bloom Pokémon", PokemonType.FAIRY, null, 0.1, 0.1, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 303, 44, 38, 39, 61, 79, 42, 225, 70, 61, GrowthRate.MEDIUM_FAST, 0, false, false, - new PokemonForm("Red Flower", "red", PokemonType.FAIRY, null, 0.1, 0.1, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 303, 44, 38, 39, 61, 79, 42, 225, 70, 61, false, null, true), - new PokemonForm("Yellow Flower", "yellow", PokemonType.FAIRY, null, 0.1, 0.1, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 303, 44, 38, 39, 61, 79, 42, 225, 70, 61, false, null, true), - new PokemonForm("Orange Flower", "orange", PokemonType.FAIRY, null, 0.1, 0.1, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 303, 44, 38, 39, 61, 79, 42, 225, 70, 61, false, null, true), - new PokemonForm("Blue Flower", "blue", PokemonType.FAIRY, null, 0.1, 0.1, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 303, 44, 38, 39, 61, 79, 42, 225, 70, 61, false, null, true), - new PokemonForm("White Flower", "white", PokemonType.FAIRY, null, 0.1, 0.1, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 303, 44, 38, 39, 61, 79, 42, 225, 70, 61, false, null, true), - ), - new PokemonSpecies(SpeciesId.FLOETTE, 6, false, false, false, "Single Bloom Pokémon", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, GrowthRate.MEDIUM_FAST, 0, false, false, - new PokemonForm("Red Flower", "red", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, false, null, true), - new PokemonForm("Yellow Flower", "yellow", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, false, null, true), - new PokemonForm("Orange Flower", "orange", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, false, null, true), - new PokemonForm("Blue Flower", "blue", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, false, null, true), - new PokemonForm("White Flower", "white", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 371, 54, 45, 47, 75, 98, 52, 120, 70, 130, false, null, true), - ), - new PokemonSpecies(SpeciesId.FLORGES, 6, false, false, false, "Garden Pokémon", PokemonType.FAIRY, null, 1.1, 10, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, GrowthRate.MEDIUM_FAST, 0, false, false, - new PokemonForm("Red Flower", "red", PokemonType.FAIRY, null, 1.1, 10, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), - new PokemonForm("Yellow Flower", "yellow", PokemonType.FAIRY, null, 1.1, 10, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), - new PokemonForm("Orange Flower", "orange", PokemonType.FAIRY, null, 1.1, 10, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), - new PokemonForm("Blue Flower", "blue", PokemonType.FAIRY, null, 1.1, 10, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), - new PokemonForm("White Flower", "white", PokemonType.FAIRY, null, 1.1, 10, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 552, 78, 65, 68, 112, 154, 75, 45, 70, 276, false, null, true), - ), - new PokemonSpecies(SpeciesId.SKIDDO, 6, false, false, false, "Mount Pokémon", PokemonType.GRASS, null, 0.9, 31, AbilityId.SAP_SIPPER, AbilityId.NONE, AbilityId.GRASS_PELT, 350, 66, 65, 48, 62, 57, 52, 200, 70, 70, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GOGOAT, 6, false, false, false, "Mount Pokémon", PokemonType.GRASS, null, 1.7, 91, AbilityId.SAP_SIPPER, AbilityId.NONE, AbilityId.GRASS_PELT, 531, 123, 100, 62, 97, 81, 68, 45, 70, 186, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PANCHAM, 6, false, false, false, "Playful Pokémon", PokemonType.FIGHTING, null, 0.6, 8, AbilityId.IRON_FIST, AbilityId.MOLD_BREAKER, AbilityId.SCRAPPY, 348, 67, 82, 62, 46, 48, 43, 220, 50, 70, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PANGORO, 6, false, false, false, "Daunting Pokémon", PokemonType.FIGHTING, PokemonType.DARK, 2.1, 136, AbilityId.IRON_FIST, AbilityId.MOLD_BREAKER, AbilityId.SCRAPPY, 495, 95, 124, 78, 69, 71, 58, 65, 50, 173, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.FURFROU, 6, false, false, false, "Poodle Pokémon", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Natural Form", "", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), - new PokemonForm("Heart Trim", "heart", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), - new PokemonForm("Star Trim", "star", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), - new PokemonForm("Diamond Trim", "diamond", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), - new PokemonForm("Debutante Trim", "debutante", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), - new PokemonForm("Matron Trim", "matron", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), - new PokemonForm("Dandy Trim", "dandy", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), - new PokemonForm("La Reine Trim", "la-reine", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), - new PokemonForm("Kabuki Trim", "kabuki", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), - new PokemonForm("Pharaoh Trim", "pharaoh", PokemonType.NORMAL, null, 1.2, 28, AbilityId.FUR_COAT, AbilityId.NONE, AbilityId.NONE, 472, 75, 80, 60, 65, 90, 102, 160, 70, 165, false, null, true), - ), - new PokemonSpecies(SpeciesId.ESPURR, 6, false, false, false, "Restraint Pokémon", PokemonType.PSYCHIC, null, 0.3, 3.5, AbilityId.KEEN_EYE, AbilityId.INFILTRATOR, AbilityId.OWN_TEMPO, 355, 62, 48, 54, 63, 60, 68, 190, 50, 71, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MEOWSTIC, 6, false, false, false, "Constraint Pokémon", PokemonType.PSYCHIC, null, 0.6, 8.5, AbilityId.KEEN_EYE, AbilityId.INFILTRATOR, AbilityId.PRANKSTER, 466, 74, 48, 76, 83, 81, 104, 75, 50, 163, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Male", "male", PokemonType.PSYCHIC, null, 0.6, 8.5, AbilityId.KEEN_EYE, AbilityId.INFILTRATOR, AbilityId.PRANKSTER, 466, 74, 48, 76, 83, 81, 104, 75, 50, 163, false, "", true), - new PokemonForm("Female", "female", PokemonType.PSYCHIC, null, 0.6, 8.5, AbilityId.KEEN_EYE, AbilityId.INFILTRATOR, AbilityId.COMPETITIVE, 466, 74, 48, 76, 83, 81, 104, 75, 50, 163, false, null, true), - ), - new PokemonSpecies(SpeciesId.HONEDGE, 6, false, false, false, "Sword Pokémon", PokemonType.STEEL, PokemonType.GHOST, 0.8, 2, AbilityId.NO_GUARD, AbilityId.NONE, AbilityId.NONE, 325, 45, 80, 100, 35, 37, 28, 180, 50, 65, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DOUBLADE, 6, false, false, false, "Sword Pokémon", PokemonType.STEEL, PokemonType.GHOST, 0.8, 4.5, AbilityId.NO_GUARD, AbilityId.NONE, AbilityId.NONE, 448, 59, 110, 150, 45, 49, 35, 90, 50, 157, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.AEGISLASH, 6, false, false, false, "Royal Sword Pokémon", PokemonType.STEEL, PokemonType.GHOST, 1.7, 53, AbilityId.STANCE_CHANGE, AbilityId.NONE, AbilityId.NONE, 500, 60, 50, 140, 50, 140, 60, 45, 50, 250, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Shield Forme", "shield", PokemonType.STEEL, PokemonType.GHOST, 1.7, 53, AbilityId.STANCE_CHANGE, AbilityId.NONE, AbilityId.NONE, 500, 60, 50, 140, 50, 140, 60, 45, 50, 250, false, "", true), - new PokemonForm("Blade Forme", "blade", PokemonType.STEEL, PokemonType.GHOST, 1.7, 53, AbilityId.STANCE_CHANGE, AbilityId.NONE, AbilityId.NONE, 500, 60, 140, 50, 140, 50, 60, 45, 50, 250), - ), - new PokemonSpecies(SpeciesId.SPRITZEE, 6, false, false, false, "Perfume Pokémon", PokemonType.FAIRY, null, 0.2, 0.5, AbilityId.HEALER, AbilityId.NONE, AbilityId.AROMA_VEIL, 341, 78, 52, 60, 63, 65, 23, 200, 50, 68, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.AROMATISSE, 6, false, false, false, "Fragrance Pokémon", PokemonType.FAIRY, null, 0.8, 15.5, AbilityId.HEALER, AbilityId.NONE, AbilityId.AROMA_VEIL, 462, 101, 72, 72, 99, 89, 29, 140, 50, 162, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SWIRLIX, 6, false, false, false, "Cotton Candy Pokémon", PokemonType.FAIRY, null, 0.4, 3.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.UNBURDEN, 341, 62, 48, 66, 59, 57, 49, 200, 50, 68, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SLURPUFF, 6, false, false, false, "Meringue Pokémon", PokemonType.FAIRY, null, 0.8, 5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.UNBURDEN, 480, 82, 80, 86, 85, 75, 72, 140, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.INKAY, 6, false, false, false, "Revolving Pokémon", PokemonType.DARK, PokemonType.PSYCHIC, 0.4, 3.5, AbilityId.CONTRARY, AbilityId.SUCTION_CUPS, AbilityId.INFILTRATOR, 288, 53, 54, 53, 37, 46, 45, 190, 50, 58, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MALAMAR, 6, false, false, false, "Overturning Pokémon", PokemonType.DARK, PokemonType.PSYCHIC, 1.5, 47, AbilityId.CONTRARY, AbilityId.SUCTION_CUPS, AbilityId.INFILTRATOR, 482, 86, 92, 88, 68, 75, 73, 80, 50, 169, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BINACLE, 6, false, false, false, "Two-Handed Pokémon", PokemonType.ROCK, PokemonType.WATER, 0.5, 31, AbilityId.TOUGH_CLAWS, AbilityId.SNIPER, AbilityId.PICKPOCKET, 306, 42, 52, 67, 39, 56, 50, 120, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BARBARACLE, 6, false, false, false, "Collective Pokémon", PokemonType.ROCK, PokemonType.WATER, 1.3, 96, AbilityId.TOUGH_CLAWS, AbilityId.SNIPER, AbilityId.PICKPOCKET, 500, 72, 105, 115, 54, 86, 68, 45, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SKRELP, 6, false, false, false, "Mock Kelp Pokémon", PokemonType.POISON, PokemonType.WATER, 0.5, 7.3, AbilityId.POISON_POINT, AbilityId.POISON_TOUCH, AbilityId.ADAPTABILITY, 320, 50, 60, 60, 60, 60, 30, 225, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DRAGALGE, 6, false, false, false, "Mock Kelp Pokémon", PokemonType.POISON, PokemonType.DRAGON, 1.8, 81.5, AbilityId.POISON_POINT, AbilityId.POISON_TOUCH, AbilityId.ADAPTABILITY, 494, 65, 75, 90, 97, 123, 44, 55, 50, 173, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CLAUNCHER, 6, false, false, false, "Water Gun Pokémon", PokemonType.WATER, null, 0.5, 8.3, AbilityId.MEGA_LAUNCHER, AbilityId.NONE, AbilityId.NONE, 330, 50, 53, 62, 58, 63, 44, 225, 50, 66, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.CLAWITZER, 6, false, false, false, "Howitzer Pokémon", PokemonType.WATER, null, 1.3, 35.3, AbilityId.MEGA_LAUNCHER, AbilityId.NONE, AbilityId.NONE, 500, 71, 73, 88, 120, 89, 59, 55, 50, 100, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.HELIOPTILE, 6, false, false, false, "Generator Pokémon", PokemonType.ELECTRIC, PokemonType.NORMAL, 0.5, 6, AbilityId.DRY_SKIN, AbilityId.SAND_VEIL, AbilityId.SOLAR_POWER, 289, 44, 38, 33, 61, 43, 70, 190, 50, 58, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.HELIOLISK, 6, false, false, false, "Generator Pokémon", PokemonType.ELECTRIC, PokemonType.NORMAL, 1, 21, AbilityId.DRY_SKIN, AbilityId.SAND_VEIL, AbilityId.SOLAR_POWER, 481, 62, 55, 52, 109, 94, 109, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.TYRUNT, 6, false, false, false, "Royal Heir Pokémon", PokemonType.ROCK, PokemonType.DRAGON, 0.8, 26, AbilityId.STRONG_JAW, AbilityId.NONE, AbilityId.STURDY, 362, 58, 89, 77, 45, 45, 48, 45, 50, 72, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.TYRANTRUM, 6, false, false, false, "Despot Pokémon", PokemonType.ROCK, PokemonType.DRAGON, 2.5, 270, AbilityId.STRONG_JAW, AbilityId.NONE, AbilityId.ROCK_HEAD, 521, 82, 121, 119, 69, 59, 71, 45, 50, 182, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.AMAURA, 6, false, false, false, "Tundra Pokémon", PokemonType.ROCK, PokemonType.ICE, 1.3, 25.2, AbilityId.REFRIGERATE, AbilityId.NONE, AbilityId.SNOW_WARNING, 362, 77, 59, 50, 67, 63, 46, 45, 50, 72, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.AURORUS, 6, false, false, false, "Tundra Pokémon", PokemonType.ROCK, PokemonType.ICE, 2.7, 225, AbilityId.REFRIGERATE, AbilityId.NONE, AbilityId.SNOW_WARNING, 521, 123, 77, 72, 99, 92, 58, 45, 50, 104, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.SYLVEON, 6, false, false, false, "Intertwining Pokémon", PokemonType.FAIRY, null, 1, 23.5, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.PIXILATE, 525, 95, 65, 65, 110, 130, 60, 45, 50, 184, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.HAWLUCHA, 6, false, false, false, "Wrestling Pokémon", PokemonType.FIGHTING, PokemonType.FLYING, 0.8, 21.5, AbilityId.LIMBER, AbilityId.UNBURDEN, AbilityId.MOLD_BREAKER, 500, 78, 92, 75, 74, 63, 118, 100, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DEDENNE, 6, false, false, false, "Antenna Pokémon", PokemonType.ELECTRIC, PokemonType.FAIRY, 0.2, 2.2, AbilityId.CHEEK_POUCH, AbilityId.PICKUP, AbilityId.PLUS, 431, 67, 58, 57, 81, 67, 101, 180, 50, 151, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CARBINK, 6, false, false, false, "Jewel Pokémon", PokemonType.ROCK, PokemonType.FAIRY, 0.3, 5.7, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.STURDY, 500, 50, 50, 150, 50, 150, 50, 60, 50, 100, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.GOOMY, 6, false, false, false, "Soft Tissue Pokémon", PokemonType.DRAGON, null, 0.3, 2.8, AbilityId.SAP_SIPPER, AbilityId.HYDRATION, AbilityId.GOOEY, 300, 45, 50, 35, 55, 75, 40, 45, 35, 60, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.SLIGGOO, 6, false, false, false, "Soft Tissue Pokémon", PokemonType.DRAGON, null, 0.8, 17.5, AbilityId.SAP_SIPPER, AbilityId.HYDRATION, AbilityId.GOOEY, 452, 68, 75, 53, 83, 113, 60, 45, 35, 158, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.GOODRA, 6, false, false, false, "Dragon Pokémon", PokemonType.DRAGON, null, 2, 150.5, AbilityId.SAP_SIPPER, AbilityId.HYDRATION, AbilityId.GOOEY, 600, 90, 100, 70, 110, 150, 80, 45, 35, 300, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.KLEFKI, 6, false, false, false, "Key Ring Pokémon", PokemonType.STEEL, PokemonType.FAIRY, 0.2, 3, AbilityId.PRANKSTER, AbilityId.NONE, AbilityId.MAGICIAN, 470, 57, 80, 91, 80, 87, 75, 75, 50, 165, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.PHANTUMP, 6, false, false, false, "Stump Pokémon", PokemonType.GHOST, PokemonType.GRASS, 0.4, 7, AbilityId.NATURAL_CURE, AbilityId.FRISK, AbilityId.HARVEST, 309, 43, 70, 48, 50, 60, 38, 120, 50, 62, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.TREVENANT, 6, false, false, false, "Elder Tree Pokémon", PokemonType.GHOST, PokemonType.GRASS, 1.5, 71, AbilityId.NATURAL_CURE, AbilityId.FRISK, AbilityId.HARVEST, 474, 85, 110, 76, 65, 82, 56, 60, 50, 166, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PUMPKABOO, 6, false, false, false, "Pumpkin Pokémon", PokemonType.GHOST, PokemonType.GRASS, 0.4, 5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 335, 49, 66, 70, 44, 55, 51, 120, 50, 67, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Average Size", "", PokemonType.GHOST, PokemonType.GRASS, 0.4, 5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 335, 49, 66, 70, 44, 55, 51, 120, 50, 67, false, null, true), - new PokemonForm("Small Size", "small", PokemonType.GHOST, PokemonType.GRASS, 0.3, 3.5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 335, 44, 66, 70, 44, 55, 56, 120, 50, 67, false, "", true), - new PokemonForm("Large Size", "large", PokemonType.GHOST, PokemonType.GRASS, 0.5, 7.5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 335, 54, 66, 70, 44, 55, 46, 120, 50, 67, false, "", true), - new PokemonForm("Super Size", "super", PokemonType.GHOST, PokemonType.GRASS, 0.8, 15, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 335, 59, 66, 70, 44, 55, 41, 120, 50, 67, false, "", true), - ), - new PokemonSpecies(SpeciesId.GOURGEIST, 6, false, false, false, "Pumpkin Pokémon", PokemonType.GHOST, PokemonType.GRASS, 0.9, 12.5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 494, 65, 90, 122, 58, 75, 84, 60, 50, 173, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Average Size", "", PokemonType.GHOST, PokemonType.GRASS, 0.9, 12.5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 494, 65, 90, 122, 58, 75, 84, 60, 50, 173, false, null, true), - new PokemonForm("Small Size", "small", PokemonType.GHOST, PokemonType.GRASS, 0.7, 9.5, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 494, 55, 85, 122, 58, 75, 99, 60, 50, 173, false, "", true), - new PokemonForm("Large Size", "large", PokemonType.GHOST, PokemonType.GRASS, 1.1, 14, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 494, 75, 95, 122, 58, 75, 69, 60, 50, 173, false, "", true), - new PokemonForm("Super Size", "super", PokemonType.GHOST, PokemonType.GRASS, 1.7, 39, AbilityId.PICKUP, AbilityId.FRISK, AbilityId.INSOMNIA, 494, 85, 100, 122, 58, 75, 54, 60, 50, 173, false, "", true), - ), - new PokemonSpecies(SpeciesId.BERGMITE, 6, false, false, false, "Ice Chunk Pokémon", PokemonType.ICE, null, 1, 99.5, AbilityId.OWN_TEMPO, AbilityId.ICE_BODY, AbilityId.STURDY, 304, 55, 69, 85, 32, 35, 28, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.AVALUGG, 6, false, false, false, "Iceberg Pokémon", PokemonType.ICE, null, 2, 505, AbilityId.OWN_TEMPO, AbilityId.ICE_BODY, AbilityId.STURDY, 514, 95, 117, 184, 44, 46, 28, 55, 50, 180, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.NOIBAT, 6, false, false, false, "Sound Wave Pokémon", PokemonType.FLYING, PokemonType.DRAGON, 0.5, 8, AbilityId.FRISK, AbilityId.INFILTRATOR, AbilityId.TELEPATHY, 245, 40, 30, 35, 45, 40, 55, 190, 50, 49, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.NOIVERN, 6, false, false, false, "Sound Wave Pokémon", PokemonType.FLYING, PokemonType.DRAGON, 1.5, 85, AbilityId.FRISK, AbilityId.INFILTRATOR, AbilityId.TELEPATHY, 535, 85, 70, 80, 97, 80, 123, 45, 50, 187, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.XERNEAS, 6, false, true, false, "Life Pokémon", PokemonType.FAIRY, null, 3, 215, AbilityId.FAIRY_AURA, AbilityId.NONE, AbilityId.NONE, 680, 126, 131, 95, 131, 98, 99, 45, 0, 340, GrowthRate.SLOW, null, false, true, - new PokemonForm("Neutral Mode", "neutral", PokemonType.FAIRY, null, 3, 215, AbilityId.FAIRY_AURA, AbilityId.NONE, AbilityId.NONE, 680, 126, 131, 95, 131, 98, 99, 45, 0, 340, false, null, true), - new PokemonForm("Active Mode", "active", PokemonType.FAIRY, null, 3, 215, AbilityId.FAIRY_AURA, AbilityId.NONE, AbilityId.NONE, 680, 126, 131, 95, 131, 98, 99, 45, 0, 340) - ), - new PokemonSpecies(SpeciesId.YVELTAL, 6, false, true, false, "Destruction Pokémon", PokemonType.DARK, PokemonType.FLYING, 5.8, 203, AbilityId.DARK_AURA, AbilityId.NONE, AbilityId.NONE, 680, 126, 131, 95, 131, 98, 99, 45, 0, 340, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.ZYGARDE, 6, false, true, false, "Order Pokémon", PokemonType.DRAGON, PokemonType.GROUND, 5, 305, AbilityId.AURA_BREAK, AbilityId.NONE, AbilityId.NONE, 600, 108, 100, 121, 81, 95, 95, 3, 0, 300, GrowthRate.SLOW, null, false, false, - new PokemonForm("50% Forme", "50", PokemonType.DRAGON, PokemonType.GROUND, 5, 305, AbilityId.AURA_BREAK, AbilityId.NONE, AbilityId.NONE, 600, 108, 100, 121, 81, 95, 95, 3, 0, 300, false, "", true), - new PokemonForm("10% Forme", "10", PokemonType.DRAGON, PokemonType.GROUND, 1.2, 33.5, AbilityId.AURA_BREAK, AbilityId.NONE, AbilityId.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 243, false, null, true), - new PokemonForm("50% Forme Power Construct", "50-pc", PokemonType.DRAGON, PokemonType.GROUND, 5, 305, AbilityId.POWER_CONSTRUCT, AbilityId.NONE, AbilityId.NONE, 600, 108, 100, 121, 81, 95, 95, 3, 0, 300, false, "", true), - new PokemonForm("10% Forme Power Construct", "10-pc", PokemonType.DRAGON, PokemonType.GROUND, 1.2, 33.5, AbilityId.POWER_CONSTRUCT, AbilityId.NONE, AbilityId.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 243, false, "10", true), - new PokemonForm("Complete Forme (50% PC)", "complete", PokemonType.DRAGON, PokemonType.GROUND, 4.5, 610, AbilityId.POWER_CONSTRUCT, AbilityId.NONE, AbilityId.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 354), - new PokemonForm("Complete Forme (10% PC)", "10-complete", PokemonType.DRAGON, PokemonType.GROUND, 4.5, 610, AbilityId.POWER_CONSTRUCT, AbilityId.NONE, AbilityId.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 354, false, "complete"), - ), - new PokemonSpecies(SpeciesId.DIANCIE, 6, false, false, true, "Jewel Pokémon", PokemonType.ROCK, PokemonType.FAIRY, 0.7, 8.8, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal", "", PokemonType.ROCK, PokemonType.FAIRY, 0.7, 8.8, AbilityId.CLEAR_BODY, AbilityId.NONE, AbilityId.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, false, null, true), - new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.ROCK, PokemonType.FAIRY, 1.1, 27.8, AbilityId.MAGIC_BOUNCE, AbilityId.NONE, AbilityId.NONE, 700, 50, 160, 110, 160, 110, 110, 3, 50, 300), - ), - new PokemonSpecies(SpeciesId.HOOPA, 6, false, false, true, "Mischief Pokémon", PokemonType.PSYCHIC, PokemonType.GHOST, 0.5, 9, AbilityId.MAGICIAN, AbilityId.NONE, AbilityId.NONE, 600, 80, 110, 60, 150, 130, 70, 3, 100, 300, GrowthRate.SLOW, null, false, false, - new PokemonForm("Hoopa Confined", "", PokemonType.PSYCHIC, PokemonType.GHOST, 0.5, 9, AbilityId.MAGICIAN, AbilityId.NONE, AbilityId.NONE, 600, 80, 110, 60, 150, 130, 70, 3, 100, 300, false, null, true), - new PokemonForm("Hoopa Unbound", "unbound", PokemonType.PSYCHIC, PokemonType.DARK, 6.5, 490, AbilityId.MAGICIAN, AbilityId.NONE, AbilityId.NONE, 680, 80, 160, 60, 170, 130, 80, 3, 100, 340), - ), - new PokemonSpecies(SpeciesId.VOLCANION, 6, false, false, true, "Steam Pokémon", PokemonType.FIRE, PokemonType.WATER, 1.7, 195, AbilityId.WATER_ABSORB, AbilityId.NONE, AbilityId.NONE, 600, 80, 110, 120, 130, 90, 70, 3, 100, 300, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.ROWLET, 7, false, false, false, "Grass Quill Pokémon", PokemonType.GRASS, PokemonType.FLYING, 0.3, 1.5, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.LONG_REACH, 320, 68, 55, 55, 50, 50, 42, 45, 50, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.DARTRIX, 7, false, false, false, "Blade Quill Pokémon", PokemonType.GRASS, PokemonType.FLYING, 0.7, 16, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.LONG_REACH, 420, 78, 75, 75, 70, 70, 52, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.DECIDUEYE, 7, false, false, false, "Arrow Quill Pokémon", PokemonType.GRASS, PokemonType.GHOST, 1.6, 36.6, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.LONG_REACH, 530, 78, 107, 75, 100, 100, 70, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.LITTEN, 7, false, false, false, "Fire Cat Pokémon", PokemonType.FIRE, null, 0.4, 4.3, AbilityId.BLAZE, AbilityId.NONE, AbilityId.INTIMIDATE, 320, 45, 65, 40, 60, 40, 70, 45, 50, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.TORRACAT, 7, false, false, false, "Fire Cat Pokémon", PokemonType.FIRE, null, 0.7, 25, AbilityId.BLAZE, AbilityId.NONE, AbilityId.INTIMIDATE, 420, 65, 85, 50, 80, 50, 90, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.INCINEROAR, 7, false, false, false, "Heel Pokémon", PokemonType.FIRE, PokemonType.DARK, 1.8, 83, AbilityId.BLAZE, AbilityId.NONE, AbilityId.INTIMIDATE, 530, 95, 115, 90, 80, 90, 60, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.POPPLIO, 7, false, false, false, "Sea Lion Pokémon", PokemonType.WATER, null, 0.4, 7.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.LIQUID_VOICE, 320, 50, 54, 54, 66, 56, 40, 45, 50, 64, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.BRIONNE, 7, false, false, false, "Pop Star Pokémon", PokemonType.WATER, null, 0.6, 17.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.LIQUID_VOICE, 420, 60, 69, 69, 91, 81, 50, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.PRIMARINA, 7, false, false, false, "Soloist Pokémon", PokemonType.WATER, PokemonType.FAIRY, 1.8, 44, AbilityId.TORRENT, AbilityId.NONE, AbilityId.LIQUID_VOICE, 530, 80, 74, 74, 126, 116, 60, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.PIKIPEK, 7, false, false, false, "Woodpecker Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.3, 1.2, AbilityId.KEEN_EYE, AbilityId.SKILL_LINK, AbilityId.PICKUP, 265, 35, 75, 30, 30, 30, 65, 255, 70, 53, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.TRUMBEAK, 7, false, false, false, "Bugle Beak Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 14.8, AbilityId.KEEN_EYE, AbilityId.SKILL_LINK, AbilityId.PICKUP, 355, 55, 85, 50, 40, 50, 75, 120, 70, 124, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.TOUCANNON, 7, false, false, false, "Cannon Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 1.1, 26, AbilityId.KEEN_EYE, AbilityId.SKILL_LINK, AbilityId.SHEER_FORCE, 485, 80, 120, 75, 75, 75, 60, 45, 70, 243, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.YUNGOOS, 7, false, false, false, "Loitering Pokémon", PokemonType.NORMAL, null, 0.4, 6, AbilityId.STAKEOUT, AbilityId.STRONG_JAW, AbilityId.ADAPTABILITY, 253, 48, 70, 30, 30, 30, 45, 255, 70, 51, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GUMSHOOS, 7, false, false, false, "Stakeout Pokémon", PokemonType.NORMAL, null, 0.7, 14.2, AbilityId.STAKEOUT, AbilityId.STRONG_JAW, AbilityId.ADAPTABILITY, 418, 88, 110, 60, 55, 60, 45, 127, 70, 146, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GRUBBIN, 7, false, false, false, "Larva Pokémon", PokemonType.BUG, null, 0.4, 4.4, AbilityId.SWARM, AbilityId.NONE, AbilityId.NONE, 300, 47, 62, 45, 55, 45, 46, 255, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CHARJABUG, 7, false, false, false, "Battery Pokémon", PokemonType.BUG, PokemonType.ELECTRIC, 0.5, 10.5, AbilityId.BATTERY, AbilityId.NONE, AbilityId.NONE, 400, 57, 82, 95, 55, 75, 36, 120, 50, 140, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.VIKAVOLT, 7, false, false, false, "Stag Beetle Pokémon", PokemonType.BUG, PokemonType.ELECTRIC, 1.5, 45, AbilityId.LEVITATE, AbilityId.NONE, AbilityId.NONE, 500, 77, 70, 90, 145, 75, 43, 45, 50, 250, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CRABRAWLER, 7, false, false, false, "Boxing Pokémon", PokemonType.FIGHTING, null, 0.6, 7, AbilityId.HYPER_CUTTER, AbilityId.IRON_FIST, AbilityId.ANGER_POINT, 338, 47, 82, 57, 42, 47, 63, 225, 70, 68, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CRABOMINABLE, 7, false, false, false, "Woolly Crab Pokémon", PokemonType.FIGHTING, PokemonType.ICE, 1.7, 180, AbilityId.HYPER_CUTTER, AbilityId.IRON_FIST, AbilityId.ANGER_POINT, 478, 97, 132, 77, 62, 67, 43, 60, 70, 167, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ORICORIO, 7, false, false, false, "Dancing Pokémon", PokemonType.FIRE, PokemonType.FLYING, 0.6, 3.4, AbilityId.DANCER, AbilityId.NONE, AbilityId.NONE, 476, 75, 70, 70, 98, 70, 93, 45, 70, 167, GrowthRate.MEDIUM_FAST, 25, false, false, - new PokemonForm("Baile Style", "baile", PokemonType.FIRE, PokemonType.FLYING, 0.6, 3.4, AbilityId.DANCER, AbilityId.NONE, AbilityId.NONE, 476, 75, 70, 70, 98, 70, 93, 45, 70, 167, false, "", true), - new PokemonForm("Pom-Pom Style", "pompom", PokemonType.ELECTRIC, PokemonType.FLYING, 0.6, 3.4, AbilityId.DANCER, AbilityId.NONE, AbilityId.NONE, 476, 75, 70, 70, 98, 70, 93, 45, 70, 167, false, null, true), - new PokemonForm("Pau Style", "pau", PokemonType.PSYCHIC, PokemonType.FLYING, 0.6, 3.4, AbilityId.DANCER, AbilityId.NONE, AbilityId.NONE, 476, 75, 70, 70, 98, 70, 93, 45, 70, 167, false, null, true), - new PokemonForm("Sensu Style", "sensu", PokemonType.GHOST, PokemonType.FLYING, 0.6, 3.4, AbilityId.DANCER, AbilityId.NONE, AbilityId.NONE, 476, 75, 70, 70, 98, 70, 93, 45, 70, 167, false, null, true), - ), - new PokemonSpecies(SpeciesId.CUTIEFLY, 7, false, false, false, "Bee Fly Pokémon", PokemonType.BUG, PokemonType.FAIRY, 0.1, 0.2, AbilityId.HONEY_GATHER, AbilityId.SHIELD_DUST, AbilityId.SWEET_VEIL, 304, 40, 45, 40, 55, 40, 84, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.RIBOMBEE, 7, false, false, false, "Bee Fly Pokémon", PokemonType.BUG, PokemonType.FAIRY, 0.2, 0.5, AbilityId.HONEY_GATHER, AbilityId.SHIELD_DUST, AbilityId.SWEET_VEIL, 464, 60, 55, 60, 95, 70, 124, 75, 50, 162, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ROCKRUFF, 7, false, false, false, "Puppy Pokémon", PokemonType.ROCK, null, 0.5, 9.2, AbilityId.KEEN_EYE, AbilityId.VITAL_SPIRIT, AbilityId.STEADFAST, 280, 45, 65, 40, 30, 40, 60, 190, 50, 56, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Normal", "", PokemonType.ROCK, null, 0.5, 9.2, AbilityId.KEEN_EYE, AbilityId.VITAL_SPIRIT, AbilityId.STEADFAST, 280, 45, 65, 40, 30, 40, 60, 190, 50, 56, false, null, true), - new PokemonForm("Own Tempo", "own-tempo", PokemonType.ROCK, null, 0.5, 9.2, AbilityId.OWN_TEMPO, AbilityId.NONE, AbilityId.OWN_TEMPO, 280, 45, 65, 40, 30, 40, 60, 190, 50, 56, false, "", true), - ), - new PokemonSpecies(SpeciesId.LYCANROC, 7, false, false, false, "Wolf Pokémon", PokemonType.ROCK, null, 0.8, 25, AbilityId.KEEN_EYE, AbilityId.SAND_RUSH, AbilityId.STEADFAST, 487, 75, 115, 65, 55, 65, 112, 90, 50, 170, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Midday Form", "midday", PokemonType.ROCK, null, 0.8, 25, AbilityId.KEEN_EYE, AbilityId.SAND_RUSH, AbilityId.STEADFAST, 487, 75, 115, 65, 55, 65, 112, 90, 50, 170, false, "", true), - new PokemonForm("Midnight Form", "midnight", PokemonType.ROCK, null, 1.1, 25, AbilityId.KEEN_EYE, AbilityId.VITAL_SPIRIT, AbilityId.NO_GUARD, 487, 85, 115, 75, 55, 75, 82, 90, 50, 170, false, null, true), - new PokemonForm("Dusk Form", "dusk", PokemonType.ROCK, null, 0.8, 25, AbilityId.TOUGH_CLAWS, AbilityId.TOUGH_CLAWS, AbilityId.TOUGH_CLAWS, 487, 75, 117, 65, 55, 65, 110, 90, 50, 170, false, null, true), - ), - new PokemonSpecies(SpeciesId.WISHIWASHI, 7, false, false, false, "Small Fry Pokémon", PokemonType.WATER, null, 0.2, 0.3, AbilityId.SCHOOLING, AbilityId.NONE, AbilityId.NONE, 175, 45, 20, 20, 25, 25, 40, 60, 50, 61, GrowthRate.FAST, 50, false, false, - new PokemonForm("Solo Form", "", PokemonType.WATER, null, 0.2, 0.3, AbilityId.SCHOOLING, AbilityId.NONE, AbilityId.NONE, 175, 45, 20, 20, 25, 25, 40, 60, 50, 61, false, null, true), - new PokemonForm("School", "school", PokemonType.WATER, null, 8.2, 78.6, AbilityId.SCHOOLING, AbilityId.NONE, AbilityId.NONE, 620, 45, 140, 130, 140, 135, 30, 60, 50, 217), - ), - new PokemonSpecies(SpeciesId.MAREANIE, 7, false, false, false, "Brutal Star Pokémon", PokemonType.POISON, PokemonType.WATER, 0.4, 8, AbilityId.MERCILESS, AbilityId.LIMBER, AbilityId.REGENERATOR, 305, 50, 53, 62, 43, 52, 45, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.TOXAPEX, 7, false, false, false, "Brutal Star Pokémon", PokemonType.POISON, PokemonType.WATER, 0.7, 14.5, AbilityId.MERCILESS, AbilityId.LIMBER, AbilityId.REGENERATOR, 495, 50, 63, 152, 53, 142, 35, 75, 50, 173, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MUDBRAY, 7, false, false, false, "Donkey Pokémon", PokemonType.GROUND, null, 1, 110, AbilityId.OWN_TEMPO, AbilityId.STAMINA, AbilityId.INNER_FOCUS, 385, 70, 100, 70, 45, 55, 45, 190, 50, 77, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MUDSDALE, 7, false, false, false, "Draft Horse Pokémon", PokemonType.GROUND, null, 2.5, 920, AbilityId.OWN_TEMPO, AbilityId.STAMINA, AbilityId.INNER_FOCUS, 500, 100, 125, 100, 55, 85, 35, 60, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DEWPIDER, 7, false, false, false, "Water Bubble Pokémon", PokemonType.WATER, PokemonType.BUG, 0.3, 4, AbilityId.WATER_BUBBLE, AbilityId.NONE, AbilityId.WATER_ABSORB, 269, 38, 40, 52, 40, 72, 27, 200, 50, 54, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ARAQUANID, 7, false, false, false, "Water Bubble Pokémon", PokemonType.WATER, PokemonType.BUG, 1.8, 82, AbilityId.WATER_BUBBLE, AbilityId.NONE, AbilityId.WATER_ABSORB, 454, 68, 70, 92, 50, 132, 42, 100, 50, 159, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.FOMANTIS, 7, false, false, false, "Sickle Grass Pokémon", PokemonType.GRASS, null, 0.3, 1.5, AbilityId.LEAF_GUARD, AbilityId.NONE, AbilityId.CONTRARY, 250, 40, 55, 35, 50, 35, 35, 190, 50, 50, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.LURANTIS, 7, false, false, false, "Bloom Sickle Pokémon", PokemonType.GRASS, null, 0.9, 18.5, AbilityId.LEAF_GUARD, AbilityId.NONE, AbilityId.CONTRARY, 480, 70, 105, 90, 80, 90, 45, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MORELULL, 7, false, false, false, "Illuminating Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 0.2, 1.5, AbilityId.ILLUMINATE, AbilityId.EFFECT_SPORE, AbilityId.RAIN_DISH, 285, 40, 35, 55, 65, 75, 15, 190, 50, 57, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SHIINOTIC, 7, false, false, false, "Illuminating Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 1, 11.5, AbilityId.ILLUMINATE, AbilityId.EFFECT_SPORE, AbilityId.RAIN_DISH, 405, 60, 45, 80, 90, 100, 30, 75, 50, 142, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SALANDIT, 7, false, false, false, "Toxic Lizard Pokémon", PokemonType.POISON, PokemonType.FIRE, 0.6, 4.8, AbilityId.CORROSION, AbilityId.NONE, AbilityId.OBLIVIOUS, 320, 48, 44, 40, 71, 40, 77, 120, 50, 64, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(SpeciesId.SALAZZLE, 7, false, false, false, "Toxic Lizard Pokémon", PokemonType.POISON, PokemonType.FIRE, 1.2, 22.2, AbilityId.CORROSION, AbilityId.NONE, AbilityId.OBLIVIOUS, 480, 68, 64, 60, 111, 60, 117, 45, 50, 168, GrowthRate.MEDIUM_FAST, 0, false), - new PokemonSpecies(SpeciesId.STUFFUL, 7, false, false, false, "Flailing Pokémon", PokemonType.NORMAL, PokemonType.FIGHTING, 0.5, 6.8, AbilityId.FLUFFY, AbilityId.KLUTZ, AbilityId.CUTE_CHARM, 340, 70, 75, 50, 45, 50, 50, 140, 50, 68, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BEWEAR, 7, false, false, false, "Strong Arm Pokémon", PokemonType.NORMAL, PokemonType.FIGHTING, 2.1, 135, AbilityId.FLUFFY, AbilityId.KLUTZ, AbilityId.UNNERVE, 500, 120, 125, 80, 55, 60, 60, 70, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BOUNSWEET, 7, false, false, false, "Fruit Pokémon", PokemonType.GRASS, null, 0.3, 3.2, AbilityId.LEAF_GUARD, AbilityId.OBLIVIOUS, AbilityId.SWEET_VEIL, 210, 42, 30, 38, 30, 38, 32, 235, 50, 42, GrowthRate.MEDIUM_SLOW, 0, false), - new PokemonSpecies(SpeciesId.STEENEE, 7, false, false, false, "Fruit Pokémon", PokemonType.GRASS, null, 0.7, 8.2, AbilityId.LEAF_GUARD, AbilityId.OBLIVIOUS, AbilityId.SWEET_VEIL, 290, 52, 40, 48, 40, 48, 62, 120, 50, 102, GrowthRate.MEDIUM_SLOW, 0, false), - new PokemonSpecies(SpeciesId.TSAREENA, 7, false, false, false, "Fruit Pokémon", PokemonType.GRASS, null, 1.2, 21.4, AbilityId.LEAF_GUARD, AbilityId.QUEENLY_MAJESTY, AbilityId.SWEET_VEIL, 510, 72, 120, 98, 50, 98, 72, 45, 50, 255, GrowthRate.MEDIUM_SLOW, 0, false), - new PokemonSpecies(SpeciesId.COMFEY, 7, false, false, false, "Posy Picker Pokémon", PokemonType.FAIRY, null, 0.1, 0.3, AbilityId.FLOWER_VEIL, AbilityId.TRIAGE, AbilityId.NATURAL_CURE, 485, 51, 52, 90, 82, 110, 100, 60, 50, 170, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.ORANGURU, 7, false, false, false, "Sage Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 1.5, 76, AbilityId.INNER_FOCUS, AbilityId.TELEPATHY, AbilityId.SYMBIOSIS, 490, 90, 60, 80, 90, 110, 60, 45, 50, 172, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.PASSIMIAN, 7, false, false, false, "Teamwork Pokémon", PokemonType.FIGHTING, null, 2, 82.8, AbilityId.RECEIVER, AbilityId.NONE, AbilityId.DEFIANT, 490, 100, 120, 90, 40, 60, 80, 45, 50, 172, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.WIMPOD, 7, false, false, false, "Turn Tail Pokémon", PokemonType.BUG, PokemonType.WATER, 0.5, 12, AbilityId.WIMP_OUT, AbilityId.NONE, AbilityId.RUN_AWAY, 230, 25, 35, 40, 20, 30, 80, 90, 50, 46, GrowthRate.MEDIUM_FAST, 50, false), //Custom Hidden - new PokemonSpecies(SpeciesId.GOLISOPOD, 7, false, false, false, "Hard Scale Pokémon", PokemonType.BUG, PokemonType.WATER, 2, 108, AbilityId.EMERGENCY_EXIT, AbilityId.NONE, AbilityId.ANTICIPATION, 530, 75, 125, 140, 60, 90, 40, 45, 50, 186, GrowthRate.MEDIUM_FAST, 50, false), //Custom Hidden - new PokemonSpecies(SpeciesId.SANDYGAST, 7, false, false, false, "Sand Heap Pokémon", PokemonType.GHOST, PokemonType.GROUND, 0.5, 70, AbilityId.WATER_COMPACTION, AbilityId.NONE, AbilityId.SAND_VEIL, 320, 55, 55, 80, 70, 45, 15, 140, 50, 64, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PALOSSAND, 7, false, false, false, "Sand Castle Pokémon", PokemonType.GHOST, PokemonType.GROUND, 1.3, 250, AbilityId.WATER_COMPACTION, AbilityId.NONE, AbilityId.SAND_VEIL, 480, 85, 75, 110, 100, 75, 35, 60, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PYUKUMUKU, 7, false, false, false, "Sea Cucumber Pokémon", PokemonType.WATER, null, 0.3, 1.2, AbilityId.INNARDS_OUT, AbilityId.NONE, AbilityId.UNAWARE, 410, 55, 60, 130, 30, 130, 5, 60, 50, 144, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.TYPE_NULL, 7, true, false, false, "Synthetic Pokémon", PokemonType.NORMAL, null, 1.9, 120.5, AbilityId.BATTLE_ARMOR, AbilityId.NONE, AbilityId.NONE, 534, 95, 95, 95, 95, 95, 59, 3, 0, 107, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.SILVALLY, 7, true, false, false, "Synthetic Pokémon", PokemonType.NORMAL, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285, GrowthRate.SLOW, null, false, false, - new PokemonForm("Type: Normal", "normal", PokemonType.NORMAL, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285, false, "", true), - new PokemonForm("Type: Fighting", "fighting", PokemonType.FIGHTING, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Flying", "flying", PokemonType.FLYING, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Poison", "poison", PokemonType.POISON, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Ground", "ground", PokemonType.GROUND, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Rock", "rock", PokemonType.ROCK, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Bug", "bug", PokemonType.BUG, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Ghost", "ghost", PokemonType.GHOST, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Steel", "steel", PokemonType.STEEL, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Fire", "fire", PokemonType.FIRE, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Water", "water", PokemonType.WATER, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Grass", "grass", PokemonType.GRASS, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Electric", "electric", PokemonType.ELECTRIC, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Psychic", "psychic", PokemonType.PSYCHIC, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Ice", "ice", PokemonType.ICE, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Dragon", "dragon", PokemonType.DRAGON, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Dark", "dark", PokemonType.DARK, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - new PokemonForm("Type: Fairy", "fairy", PokemonType.FAIRY, null, 2.3, 100.5, AbilityId.RKS_SYSTEM, AbilityId.NONE, AbilityId.NONE, 570, 95, 95, 95, 95, 95, 95, 3, 0, 285), - ), - new PokemonSpecies(SpeciesId.MINIOR, 7, false, false, false, "Meteor Pokémon", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, GrowthRate.MEDIUM_SLOW, null, false, false, - new PokemonForm("Red Meteor Form", "red-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), - new PokemonForm("Orange Meteor Form", "orange-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), - new PokemonForm("Yellow Meteor Form", "yellow-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), - new PokemonForm("Green Meteor Form", "green-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), - new PokemonForm("Blue Meteor Form", "blue-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), - new PokemonForm("Indigo Meteor Form", "indigo-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), - new PokemonForm("Violet Meteor Form", "violet-meteor", PokemonType.ROCK, PokemonType.FLYING, 0.3, 40, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 440, 60, 60, 100, 60, 100, 60, 30, 70, 154, false, null, true), - new PokemonForm("Red Core Form", "red", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), - new PokemonForm("Orange Core Form", "orange", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), - new PokemonForm("Yellow Core Form", "yellow", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), - new PokemonForm("Green Core Form", "green", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), - new PokemonForm("Blue Core Form", "blue", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), - new PokemonForm("Indigo Core Form", "indigo", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), - new PokemonForm("Violet Core Form", "violet", PokemonType.ROCK, PokemonType.FLYING, 0.3, 0.3, AbilityId.SHIELDS_DOWN, AbilityId.NONE, AbilityId.NONE, 500, 60, 100, 60, 100, 60, 120, 30, 70, 175, false, null, true), - ), - new PokemonSpecies(SpeciesId.KOMALA, 7, false, false, false, "Drowsing Pokémon", PokemonType.NORMAL, null, 0.4, 19.9, AbilityId.COMATOSE, AbilityId.NONE, AbilityId.NONE, 480, 65, 115, 65, 75, 95, 65, 45, 70, 168, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.TURTONATOR, 7, false, false, false, "Blast Turtle Pokémon", PokemonType.FIRE, PokemonType.DRAGON, 2, 212, AbilityId.SHELL_ARMOR, AbilityId.NONE, AbilityId.NONE, 485, 60, 78, 135, 91, 85, 36, 70, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.TOGEDEMARU, 7, false, false, false, "Roly-Poly Pokémon", PokemonType.ELECTRIC, PokemonType.STEEL, 0.3, 3.3, AbilityId.IRON_BARBS, AbilityId.LIGHTNING_ROD, AbilityId.STURDY, 435, 65, 98, 63, 40, 73, 96, 180, 50, 152, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MIMIKYU, 7, false, false, false, "Disguise Pokémon", PokemonType.GHOST, PokemonType.FAIRY, 0.2, 0.7, AbilityId.DISGUISE, AbilityId.NONE, AbilityId.NONE, 476, 55, 90, 80, 50, 105, 96, 45, 50, 167, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Disguised Form", "disguised", PokemonType.GHOST, PokemonType.FAIRY, 0.2, 0.7, AbilityId.DISGUISE, AbilityId.NONE, AbilityId.NONE, 476, 55, 90, 80, 50, 105, 96, 45, 50, 167, false, null, true), - new PokemonForm("Busted Form", "busted", PokemonType.GHOST, PokemonType.FAIRY, 0.2, 0.7, AbilityId.DISGUISE, AbilityId.NONE, AbilityId.NONE, 476, 55, 90, 80, 50, 105, 96, 45, 50, 167), - ), - new PokemonSpecies(SpeciesId.BRUXISH, 7, false, false, false, "Gnash Teeth Pokémon", PokemonType.WATER, PokemonType.PSYCHIC, 0.9, 19, AbilityId.DAZZLING, AbilityId.STRONG_JAW, AbilityId.WONDER_SKIN, 475, 68, 105, 70, 70, 70, 92, 80, 70, 166, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DRAMPA, 7, false, false, false, "Placid Pokémon", PokemonType.NORMAL, PokemonType.DRAGON, 3, 185, AbilityId.BERSERK, AbilityId.SAP_SIPPER, AbilityId.CLOUD_NINE, 485, 78, 60, 85, 135, 91, 36, 70, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DHELMISE, 7, false, false, false, "Sea Creeper Pokémon", PokemonType.GHOST, PokemonType.GRASS, 3.9, 210, AbilityId.STEELWORKER, AbilityId.NONE, AbilityId.NONE, 517, 70, 131, 100, 86, 90, 40, 25, 50, 181, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.JANGMO_O, 7, false, false, false, "Scaly Pokémon", PokemonType.DRAGON, null, 0.6, 29.7, AbilityId.BULLETPROOF, AbilityId.SOUNDPROOF, AbilityId.OVERCOAT, 300, 45, 55, 65, 45, 45, 45, 45, 50, 60, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.HAKAMO_O, 7, false, false, false, "Scaly Pokémon", PokemonType.DRAGON, PokemonType.FIGHTING, 1.2, 47, AbilityId.BULLETPROOF, AbilityId.SOUNDPROOF, AbilityId.OVERCOAT, 420, 55, 75, 90, 65, 70, 65, 45, 50, 147, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.KOMMO_O, 7, false, false, false, "Scaly Pokémon", PokemonType.DRAGON, PokemonType.FIGHTING, 1.6, 78.2, AbilityId.BULLETPROOF, AbilityId.SOUNDPROOF, AbilityId.OVERCOAT, 600, 75, 110, 125, 100, 105, 85, 45, 50, 300, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.TAPU_KOKO, 7, true, false, false, "Land Spirit Pokémon", PokemonType.ELECTRIC, PokemonType.FAIRY, 1.8, 20.5, AbilityId.ELECTRIC_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 115, 85, 95, 75, 130, 3, 50, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.TAPU_LELE, 7, true, false, false, "Land Spirit Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.2, 18.6, AbilityId.PSYCHIC_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 85, 75, 130, 115, 95, 3, 50, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.TAPU_BULU, 7, true, false, false, "Land Spirit Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 1.9, 45.5, AbilityId.GRASSY_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 130, 115, 85, 95, 75, 3, 50, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.TAPU_FINI, 7, true, false, false, "Land Spirit Pokémon", PokemonType.WATER, PokemonType.FAIRY, 1.3, 21.2, AbilityId.MISTY_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 75, 115, 95, 130, 85, 3, 50, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.COSMOG, 7, true, false, false, "Nebula Pokémon", PokemonType.PSYCHIC, null, 0.2, 0.1, AbilityId.UNAWARE, AbilityId.NONE, AbilityId.NONE, 200, 43, 29, 31, 29, 31, 37, 3, 0, 40, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.COSMOEM, 7, true, false, false, "Protostar Pokémon", PokemonType.PSYCHIC, null, 0.1, 999.9, AbilityId.STURDY, AbilityId.NONE, AbilityId.NONE, 400, 43, 29, 131, 29, 131, 37, 3, 0, 140, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.SOLGALEO, 7, false, true, false, "Sunne Pokémon", PokemonType.PSYCHIC, PokemonType.STEEL, 3.4, 230, AbilityId.FULL_METAL_BODY, AbilityId.NONE, AbilityId.NONE, 680, 137, 137, 107, 113, 89, 97, 3, 0, 340, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.LUNALA, 7, false, true, false, "Moone Pokémon", PokemonType.PSYCHIC, PokemonType.GHOST, 4, 120, AbilityId.SHADOW_SHIELD, AbilityId.NONE, AbilityId.NONE, 680, 137, 113, 89, 137, 107, 97, 3, 0, 340, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.NIHILEGO, 7, true, false, false, "Parasite Pokémon", PokemonType.ROCK, PokemonType.POISON, 1.2, 55.5, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 109, 53, 47, 127, 131, 103, 45, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.BUZZWOLE, 7, true, false, false, "Swollen Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 2.4, 333.6, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 107, 139, 139, 53, 53, 79, 45, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.PHEROMOSA, 7, true, false, false, "Lissome Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 1.8, 25, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 71, 137, 37, 137, 37, 151, 45, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.XURKITREE, 7, true, false, false, "Glowing Pokémon", PokemonType.ELECTRIC, null, 3.8, 100, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 83, 89, 71, 173, 71, 83, 45, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.CELESTEELA, 7, true, false, false, "Launch Pokémon", PokemonType.STEEL, PokemonType.FLYING, 9.2, 999.9, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 97, 101, 103, 107, 101, 61, 45, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.KARTANA, 7, true, false, false, "Drawn Sword Pokémon", PokemonType.GRASS, PokemonType.STEEL, 0.3, 0.1, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 59, 181, 131, 59, 31, 109, 45, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.GUZZLORD, 7, true, false, false, "Junkivore Pokémon", PokemonType.DARK, PokemonType.DRAGON, 5.5, 888, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 223, 101, 53, 97, 53, 43, 45, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.NECROZMA, 7, false, true, false, "Prism Pokémon", PokemonType.PSYCHIC, null, 2.4, 230, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 600, 97, 107, 101, 127, 89, 79, 3, 0, 300, GrowthRate.SLOW, null, false, false, - new PokemonForm("Normal", "", PokemonType.PSYCHIC, null, 2.4, 230, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 600, 97, 107, 101, 127, 89, 79, 3, 0, 300, false, null, true), - new PokemonForm("Dusk Mane", "dusk-mane", PokemonType.PSYCHIC, PokemonType.STEEL, 3.8, 460, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 680, 97, 157, 127, 113, 109, 77, 3, 0, 340), - new PokemonForm("Dawn Wings", "dawn-wings", PokemonType.PSYCHIC, PokemonType.GHOST, 4.2, 350, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 680, 97, 113, 109, 157, 127, 77, 3, 0, 340), - new PokemonForm("Ultra", "ultra", PokemonType.PSYCHIC, PokemonType.DRAGON, 7.5, 230, AbilityId.NEUROFORCE, AbilityId.NONE, AbilityId.NONE, 754, 97, 167, 97, 167, 97, 129, 3, 0, 377), - ), - new PokemonSpecies(SpeciesId.MAGEARNA, 7, false, false, true, "Artificial Pokémon", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, GrowthRate.SLOW, null, false, false, - new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, false, null, true), - new PokemonForm("Original", "original", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, false, null, true), - ), - new PokemonSpecies(SpeciesId.MARSHADOW, 7, false, false, true, "Gloomdweller Pokémon", PokemonType.FIGHTING, PokemonType.GHOST, 0.7, 22.2, AbilityId.TECHNICIAN, AbilityId.NONE, AbilityId.NONE, 600, 90, 125, 80, 90, 90, 125, 3, 0, 300, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal", "", PokemonType.FIGHTING, PokemonType.GHOST, 0.7, 22.2, AbilityId.TECHNICIAN, AbilityId.NONE, AbilityId.NONE, 600, 90, 125, 80, 90, 90, 125, 3, 0, 300, false, null, true), - new PokemonForm("Zenith", "zenith", PokemonType.FIGHTING, PokemonType.GHOST, 0.7, 22.2, AbilityId.TECHNICIAN, AbilityId.NONE, AbilityId.NONE, 600, 90, 125, 80, 90, 90, 125, 3, 0, 300, false, null, false, true) - ), - new PokemonSpecies(SpeciesId.POIPOLE, 7, true, false, false, "Poison Pin Pokémon", PokemonType.POISON, null, 0.6, 1.8, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 420, 67, 73, 67, 73, 67, 73, 45, 0, 210, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.NAGANADEL, 7, true, false, false, "Poison Pin Pokémon", PokemonType.POISON, PokemonType.DRAGON, 3.6, 150, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 540, 73, 73, 73, 127, 73, 121, 45, 0, 270, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.STAKATAKA, 7, true, false, false, "Rampart Pokémon", PokemonType.ROCK, PokemonType.STEEL, 5.5, 820, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 61, 131, 211, 53, 101, 13, 30, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.BLACEPHALON, 7, true, false, false, "Fireworks Pokémon", PokemonType.FIRE, PokemonType.GHOST, 1.8, 13, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 53, 127, 53, 151, 79, 107, 30, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.ZERAORA, 7, false, false, true, "Thunderclap Pokémon", PokemonType.ELECTRIC, null, 1.5, 44.5, AbilityId.VOLT_ABSORB, AbilityId.NONE, AbilityId.NONE, 600, 88, 112, 75, 102, 80, 143, 3, 0, 300, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.MELTAN, 7, false, false, true, "Hex Nut Pokémon", PokemonType.STEEL, null, 0.2, 8, AbilityId.MAGNET_PULL, AbilityId.NONE, AbilityId.NONE, 300, 46, 65, 65, 55, 35, 34, 3, 0, 150, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.MELMETAL, 7, false, false, true, "Hex Nut Pokémon", PokemonType.STEEL, null, 2.5, 800, AbilityId.IRON_FIST, AbilityId.NONE, AbilityId.NONE, 600, 135, 143, 143, 80, 65, 34, 3, 0, 300, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal", "", PokemonType.STEEL, null, 2.5, 800, AbilityId.IRON_FIST, AbilityId.NONE, AbilityId.NONE, 600, 135, 143, 143, 80, 65, 34, 3, 0, 300, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.STEEL, null, 25, 999.9, AbilityId.IRON_FIST, AbilityId.NONE, AbilityId.NONE, 700, 170, 158, 158, 95, 75, 44, 3, 0, 300), - ), - new PokemonSpecies(SpeciesId.GROOKEY, 8, false, false, false, "Chimp Pokémon", PokemonType.GRASS, null, 0.3, 5, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.GRASSY_SURGE, 310, 50, 65, 50, 40, 40, 65, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.THWACKEY, 8, false, false, false, "Beat Pokémon", PokemonType.GRASS, null, 0.7, 14, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.GRASSY_SURGE, 420, 70, 85, 70, 55, 60, 80, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.RILLABOOM, 8, false, false, false, "Drummer Pokémon", PokemonType.GRASS, null, 2.1, 90, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.GRASSY_SURGE, 530, 100, 125, 90, 60, 70, 85, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, - new PokemonForm("Normal", "", PokemonType.GRASS, null, 2.1, 90, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.GRASSY_SURGE, 530, 100, 125, 90, 60, 70, 85, 45, 50, 265, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.GRASS, null, 28, 999.9, AbilityId.GRASSY_SURGE, AbilityId.NONE, AbilityId.GRASSY_SURGE, 630, 125, 140, 105, 90, 85, 85, 45, 50, 265), - ), - new PokemonSpecies(SpeciesId.SCORBUNNY, 8, false, false, false, "Rabbit Pokémon", PokemonType.FIRE, null, 0.3, 4.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.LIBERO, 310, 50, 71, 40, 40, 40, 69, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.RABOOT, 8, false, false, false, "Rabbit Pokémon", PokemonType.FIRE, null, 0.6, 9, AbilityId.BLAZE, AbilityId.NONE, AbilityId.LIBERO, 420, 65, 86, 60, 55, 60, 94, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.CINDERACE, 8, false, false, false, "Striker Pokémon", PokemonType.FIRE, null, 1.4, 33, AbilityId.BLAZE, AbilityId.NONE, AbilityId.LIBERO, 530, 80, 116, 75, 65, 75, 119, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, - new PokemonForm("Normal", "", PokemonType.FIRE, null, 1.4, 33, AbilityId.BLAZE, AbilityId.NONE, AbilityId.LIBERO, 530, 80, 116, 75, 65, 75, 119, 45, 50, 265, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.FIRE, null, 27, 999.9, AbilityId.LIBERO, AbilityId.NONE, AbilityId.LIBERO, 630, 100, 141, 80, 95, 80, 134, 45, 50, 265), - ), - new PokemonSpecies(SpeciesId.SOBBLE, 8, false, false, false, "Water Lizard Pokémon", PokemonType.WATER, null, 0.3, 4, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SNIPER, 310, 50, 40, 40, 70, 40, 70, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.DRIZZILE, 8, false, false, false, "Water Lizard Pokémon", PokemonType.WATER, null, 0.7, 11.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SNIPER, 420, 65, 60, 55, 95, 55, 90, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.INTELEON, 8, false, false, false, "Secret Agent Pokémon", PokemonType.WATER, null, 1.9, 45.2, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SNIPER, 530, 70, 85, 65, 125, 65, 120, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, - new PokemonForm("Normal", "", PokemonType.WATER, null, 1.9, 45.2, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SNIPER, 530, 70, 85, 65, 125, 65, 120, 45, 50, 265, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.WATER, null, 40, 999.9, AbilityId.SNIPER, AbilityId.NONE, AbilityId.SNIPER, 630, 95, 117, 67, 147, 67, 137, 45, 50, 265), - ), - new PokemonSpecies(SpeciesId.SKWOVET, 8, false, false, false, "Cheeky Pokémon", PokemonType.NORMAL, null, 0.3, 2.5, AbilityId.CHEEK_POUCH, AbilityId.NONE, AbilityId.GLUTTONY, 275, 70, 55, 55, 35, 35, 25, 255, 50, 55, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GREEDENT, 8, false, false, false, "Greedy Pokémon", PokemonType.NORMAL, null, 0.6, 6, AbilityId.CHEEK_POUCH, AbilityId.NONE, AbilityId.GLUTTONY, 460, 120, 95, 95, 55, 75, 20, 90, 50, 161, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ROOKIDEE, 8, false, false, false, "Tiny Bird Pokémon", PokemonType.FLYING, null, 0.2, 1.8, AbilityId.KEEN_EYE, AbilityId.UNNERVE, AbilityId.BIG_PECKS, 245, 38, 47, 35, 33, 35, 57, 255, 50, 49, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.CORVISQUIRE, 8, false, false, false, "Raven Pokémon", PokemonType.FLYING, null, 0.8, 16, AbilityId.KEEN_EYE, AbilityId.UNNERVE, AbilityId.BIG_PECKS, 365, 68, 67, 55, 43, 55, 77, 120, 50, 128, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.CORVIKNIGHT, 8, false, false, false, "Raven Pokémon", PokemonType.FLYING, PokemonType.STEEL, 2.2, 75, AbilityId.PRESSURE, AbilityId.UNNERVE, AbilityId.MIRROR_ARMOR, 495, 98, 87, 105, 53, 85, 67, 45, 50, 248, GrowthRate.MEDIUM_SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.FLYING, PokemonType.STEEL, 2.2, 75, AbilityId.PRESSURE, AbilityId.UNNERVE, AbilityId.MIRROR_ARMOR, 495, 98, 87, 105, 53, 85, 67, 45, 50, 248, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.FLYING, PokemonType.STEEL, 14, 999.9, AbilityId.MIRROR_ARMOR, AbilityId.MIRROR_ARMOR, AbilityId.MIRROR_ARMOR, 595, 118, 112, 135, 63, 90, 77, 45, 50, 248), - ), - new PokemonSpecies(SpeciesId.BLIPBUG, 8, false, false, false, "Larva Pokémon", PokemonType.BUG, null, 0.4, 8, AbilityId.SWARM, AbilityId.COMPOUND_EYES, AbilityId.TELEPATHY, 180, 25, 20, 20, 25, 45, 45, 255, 50, 36, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DOTTLER, 8, false, false, false, "Radome Pokémon", PokemonType.BUG, PokemonType.PSYCHIC, 0.4, 19.5, AbilityId.SWARM, AbilityId.COMPOUND_EYES, AbilityId.TELEPATHY, 335, 50, 35, 80, 50, 90, 30, 120, 50, 117, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ORBEETLE, 8, false, false, false, "Seven Spot Pokémon", PokemonType.BUG, PokemonType.PSYCHIC, 0.4, 40.8, AbilityId.SWARM, AbilityId.FRISK, AbilityId.TELEPATHY, 505, 60, 45, 110, 80, 120, 90, 45, 50, 253, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.BUG, PokemonType.PSYCHIC, 0.4, 40.8, AbilityId.SWARM, AbilityId.FRISK, AbilityId.TELEPATHY, 505, 60, 45, 110, 80, 120, 90, 45, 50, 253, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.BUG, PokemonType.PSYCHIC, 14, 999.9, AbilityId.TRACE, AbilityId.TRACE, AbilityId.TRACE, 605, 75, 50, 140, 100, 150, 90, 45, 50, 253), - ), - new PokemonSpecies(SpeciesId.NICKIT, 8, false, false, false, "Fox Pokémon", PokemonType.DARK, null, 0.6, 8.9, AbilityId.RUN_AWAY, AbilityId.UNBURDEN, AbilityId.STAKEOUT, 245, 40, 28, 28, 47, 52, 50, 255, 50, 49, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.THIEVUL, 8, false, false, false, "Fox Pokémon", PokemonType.DARK, null, 1.2, 19.9, AbilityId.RUN_AWAY, AbilityId.UNBURDEN, AbilityId.STAKEOUT, 455, 70, 58, 58, 87, 92, 90, 127, 50, 159, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.GOSSIFLEUR, 8, false, false, false, "Flowering Pokémon", PokemonType.GRASS, null, 0.4, 2.2, AbilityId.COTTON_DOWN, AbilityId.REGENERATOR, AbilityId.EFFECT_SPORE, 250, 40, 40, 60, 40, 60, 10, 190, 50, 50, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ELDEGOSS, 8, false, false, false, "Cotton Bloom Pokémon", PokemonType.GRASS, null, 0.5, 2.5, AbilityId.COTTON_DOWN, AbilityId.REGENERATOR, AbilityId.EFFECT_SPORE, 460, 60, 50, 90, 80, 120, 60, 75, 50, 161, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.WOOLOO, 8, false, false, false, "Sheep Pokémon", PokemonType.NORMAL, null, 0.6, 6, AbilityId.FLUFFY, AbilityId.RUN_AWAY, AbilityId.BULLETPROOF, 270, 42, 40, 55, 40, 45, 48, 255, 50, 122, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DUBWOOL, 8, false, false, false, "Sheep Pokémon", PokemonType.NORMAL, null, 1.3, 43, AbilityId.FLUFFY, AbilityId.STEADFAST, AbilityId.BULLETPROOF, 490, 72, 80, 100, 60, 90, 88, 127, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CHEWTLE, 8, false, false, false, "Snapping Pokémon", PokemonType.WATER, null, 0.3, 8.5, AbilityId.STRONG_JAW, AbilityId.SHELL_ARMOR, AbilityId.SWIFT_SWIM, 284, 50, 64, 50, 38, 38, 44, 255, 50, 57, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DREDNAW, 8, false, false, false, "Bite Pokémon", PokemonType.WATER, PokemonType.ROCK, 1, 115.5, AbilityId.STRONG_JAW, AbilityId.SHELL_ARMOR, AbilityId.SWIFT_SWIM, 485, 90, 115, 90, 48, 68, 74, 75, 50, 170, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.WATER, PokemonType.ROCK, 1, 115.5, AbilityId.STRONG_JAW, AbilityId.SHELL_ARMOR, AbilityId.SWIFT_SWIM, 485, 90, 115, 90, 48, 68, 74, 75, 50, 170, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.WATER, PokemonType.ROCK, 24, 999.9, AbilityId.STRONG_JAW, AbilityId.STRONG_JAW, AbilityId.STRONG_JAW, 585, 115, 137, 115, 61, 83, 74, 75, 50, 170), - ), - new PokemonSpecies(SpeciesId.YAMPER, 8, false, false, false, "Puppy Pokémon", PokemonType.ELECTRIC, null, 0.3, 13.5, AbilityId.BALL_FETCH, AbilityId.NONE, AbilityId.RATTLED, 270, 59, 45, 50, 40, 50, 26, 255, 50, 54, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.BOLTUND, 8, false, false, false, "Dog Pokémon", PokemonType.ELECTRIC, null, 1, 34, AbilityId.STRONG_JAW, AbilityId.NONE, AbilityId.COMPETITIVE, 490, 69, 90, 60, 90, 60, 121, 45, 50, 172, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.ROLYCOLY, 8, false, false, false, "Coal Pokémon", PokemonType.ROCK, null, 0.3, 12, AbilityId.STEAM_ENGINE, AbilityId.HEATPROOF, AbilityId.FLASH_FIRE, 240, 30, 40, 50, 40, 50, 30, 255, 50, 48, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.CARKOL, 8, false, false, false, "Coal Pokémon", PokemonType.ROCK, PokemonType.FIRE, 1.1, 78, AbilityId.STEAM_ENGINE, AbilityId.FLAME_BODY, AbilityId.FLASH_FIRE, 410, 80, 60, 90, 60, 70, 50, 120, 50, 144, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.COALOSSAL, 8, false, false, false, "Coal Pokémon", PokemonType.ROCK, PokemonType.FIRE, 2.8, 310.5, AbilityId.STEAM_ENGINE, AbilityId.FLAME_BODY, AbilityId.FLASH_FIRE, 510, 110, 80, 120, 80, 90, 30, 45, 50, 255, GrowthRate.MEDIUM_SLOW, 50, false, true, - new PokemonForm("Normal", "", PokemonType.ROCK, PokemonType.FIRE, 2.8, 310.5, AbilityId.STEAM_ENGINE, AbilityId.FLAME_BODY, AbilityId.FLASH_FIRE, 510, 110, 80, 120, 80, 90, 30, 45, 50, 255, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.ROCK, PokemonType.FIRE, 42, 999.9, AbilityId.STEAM_ENGINE, AbilityId.STEAM_ENGINE, AbilityId.STEAM_ENGINE, 610, 140, 100, 132, 95, 100, 43, 45, 50, 255), - ), - new PokemonSpecies(SpeciesId.APPLIN, 8, false, false, false, "Apple Core Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.2, 0.5, AbilityId.RIPEN, AbilityId.GLUTTONY, AbilityId.BULLETPROOF, 260, 40, 40, 80, 40, 40, 20, 255, 50, 52, GrowthRate.ERRATIC, 50, false), - new PokemonSpecies(SpeciesId.FLAPPLE, 8, false, false, false, "Apple Wing Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.3, 1, AbilityId.RIPEN, AbilityId.GLUTTONY, AbilityId.HUSTLE, 485, 70, 110, 80, 95, 60, 70, 45, 50, 170, GrowthRate.ERRATIC, 50, false, true, - new PokemonForm("Normal", "", PokemonType.GRASS, PokemonType.DRAGON, 0.3, 1, AbilityId.RIPEN, AbilityId.GLUTTONY, AbilityId.HUSTLE, 485, 70, 110, 80, 95, 60, 70, 45, 50, 170, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.GRASS, PokemonType.DRAGON, 24, 999.9, AbilityId.HUSTLE, AbilityId.HUSTLE, AbilityId.HUSTLE, 585, 100, 125, 90, 105, 70, 95, 45, 50, 170), - ), - new PokemonSpecies(SpeciesId.APPLETUN, 8, false, false, false, "Apple Nectar Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 13, AbilityId.RIPEN, AbilityId.GLUTTONY, AbilityId.THICK_FAT, 485, 110, 85, 80, 100, 80, 30, 45, 50, 170, GrowthRate.ERRATIC, 50, false, true, - new PokemonForm("Normal", "", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 13, AbilityId.RIPEN, AbilityId.GLUTTONY, AbilityId.THICK_FAT, 485, 110, 85, 80, 100, 80, 30, 45, 50, 170, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.GRASS, PokemonType.DRAGON, 24, 999.9, AbilityId.THICK_FAT, AbilityId.THICK_FAT, AbilityId.THICK_FAT, 585, 150, 100, 95, 115, 95, 30, 45, 50, 170), - ), - new PokemonSpecies(SpeciesId.SILICOBRA, 8, false, false, false, "Sand Snake Pokémon", PokemonType.GROUND, null, 2.2, 7.6, AbilityId.SAND_SPIT, AbilityId.SHED_SKIN, AbilityId.SAND_VEIL, 315, 52, 57, 75, 35, 50, 46, 255, 50, 63, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SANDACONDA, 8, false, false, false, "Sand Snake Pokémon", PokemonType.GROUND, null, 3.8, 65.5, AbilityId.SAND_SPIT, AbilityId.SHED_SKIN, AbilityId.SAND_VEIL, 510, 72, 107, 125, 65, 70, 71, 120, 50, 179, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.GROUND, null, 3.8, 65.5, AbilityId.SAND_SPIT, AbilityId.SHED_SKIN, AbilityId.SAND_VEIL, 510, 72, 107, 125, 65, 70, 71, 120, 50, 179, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.GROUND, null, 22, 999.9, AbilityId.SAND_SPIT, AbilityId.SAND_SPIT, AbilityId.SAND_SPIT, 610, 102, 137, 140, 70, 80, 81, 120, 50, 179), - ), - new PokemonSpecies(SpeciesId.CRAMORANT, 8, false, false, false, "Gulp Pokémon", PokemonType.FLYING, PokemonType.WATER, 0.8, 18, AbilityId.GULP_MISSILE, AbilityId.NONE, AbilityId.NONE, 475, 70, 85, 55, 85, 95, 85, 45, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Normal", "", PokemonType.FLYING, PokemonType.WATER, 0.8, 18, AbilityId.GULP_MISSILE, AbilityId.NONE, AbilityId.NONE, 475, 70, 85, 55, 85, 95, 85, 45, 50, 166, false, null, true), - new PokemonForm("Gulping Form", "gulping", PokemonType.FLYING, PokemonType.WATER, 0.8, 18, AbilityId.GULP_MISSILE, AbilityId.NONE, AbilityId.NONE, 475, 70, 85, 55, 85, 95, 85, 45, 50, 166), - new PokemonForm("Gorging Form", "gorging", PokemonType.FLYING, PokemonType.WATER, 0.8, 18, AbilityId.GULP_MISSILE, AbilityId.NONE, AbilityId.NONE, 475, 70, 85, 55, 85, 95, 85, 45, 50, 166), - ), - new PokemonSpecies(SpeciesId.ARROKUDA, 8, false, false, false, "Rush Pokémon", PokemonType.WATER, null, 0.5, 1, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.PROPELLER_TAIL, 280, 41, 63, 40, 40, 30, 66, 255, 50, 56, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.BARRASKEWDA, 8, false, false, false, "Skewer Pokémon", PokemonType.WATER, null, 1.3, 30, AbilityId.SWIFT_SWIM, AbilityId.NONE, AbilityId.PROPELLER_TAIL, 490, 61, 123, 60, 60, 50, 136, 60, 50, 172, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.TOXEL, 8, false, false, false, "Baby Pokémon", PokemonType.ELECTRIC, PokemonType.POISON, 0.4, 11, AbilityId.RATTLED, AbilityId.STATIC, AbilityId.KLUTZ, 242, 40, 38, 35, 54, 35, 40, 75, 50, 48, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.TOXTRICITY, 8, false, false, false, "Punk Pokémon", PokemonType.ELECTRIC, PokemonType.POISON, 1.6, 40, AbilityId.PUNK_ROCK, AbilityId.PLUS, AbilityId.TECHNICIAN, 502, 75, 98, 70, 114, 70, 75, 45, 50, 176, GrowthRate.MEDIUM_SLOW, 50, false, true, - new PokemonForm("Amped Form", "amped", PokemonType.ELECTRIC, PokemonType.POISON, 1.6, 40, AbilityId.PUNK_ROCK, AbilityId.PLUS, AbilityId.TECHNICIAN, 502, 75, 98, 70, 114, 70, 75, 45, 50, 176, false, "", true), - new PokemonForm("Low-Key Form", "lowkey", PokemonType.ELECTRIC, PokemonType.POISON, 1.6, 40, AbilityId.PUNK_ROCK, AbilityId.MINUS, AbilityId.TECHNICIAN, 502, 75, 98, 70, 114, 70, 75, 45, 50, 176, false, "lowkey", true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.ELECTRIC, PokemonType.POISON, 24, 999.9, AbilityId.PUNK_ROCK, AbilityId.PUNK_ROCK, AbilityId.PUNK_ROCK, 602, 114, 105, 82, 137, 82, 82, 45, 50, 176), - ), - new PokemonSpecies(SpeciesId.SIZZLIPEDE, 8, false, false, false, "Radiator Pokémon", PokemonType.FIRE, PokemonType.BUG, 0.7, 1, AbilityId.FLASH_FIRE, AbilityId.WHITE_SMOKE, AbilityId.FLAME_BODY, 305, 50, 65, 45, 50, 50, 45, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CENTISKORCH, 8, false, false, false, "Radiator Pokémon", PokemonType.FIRE, PokemonType.BUG, 3, 120, AbilityId.FLASH_FIRE, AbilityId.WHITE_SMOKE, AbilityId.FLAME_BODY, 525, 100, 115, 65, 90, 90, 65, 75, 50, 184, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.FIRE, PokemonType.BUG, 3, 120, AbilityId.FLASH_FIRE, AbilityId.WHITE_SMOKE, AbilityId.FLAME_BODY, 525, 100, 115, 65, 90, 90, 65, 75, 50, 184, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.FIRE, PokemonType.BUG, 75, 999.9, AbilityId.FLASH_FIRE, AbilityId.FLASH_FIRE, AbilityId.FLASH_FIRE, 625, 130, 125, 75, 94, 100, 101, 75, 50, 184), - ), - new PokemonSpecies(SpeciesId.CLOBBOPUS, 8, false, false, false, "Tantrum Pokémon", PokemonType.FIGHTING, null, 0.6, 4, AbilityId.LIMBER, AbilityId.NONE, AbilityId.TECHNICIAN, 310, 50, 68, 60, 50, 50, 32, 180, 50, 62, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.GRAPPLOCT, 8, false, false, false, "Jujitsu Pokémon", PokemonType.FIGHTING, null, 1.6, 39, AbilityId.LIMBER, AbilityId.NONE, AbilityId.TECHNICIAN, 480, 80, 118, 90, 70, 80, 42, 45, 50, 168, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SINISTEA, 8, false, false, false, "Black Tea Pokémon", PokemonType.GHOST, null, 0.1, 0.2, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, GrowthRate.MEDIUM_FAST, null, false, false, - new PokemonForm("Phony Form", "phony", PokemonType.GHOST, null, 0.1, 0.2, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, "", true), - new PokemonForm("Antique Form", "antique", PokemonType.GHOST, null, 0.1, 0.2, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, "", true, true), - ), - new PokemonSpecies(SpeciesId.POLTEAGEIST, 8, false, false, false, "Black Tea Pokémon", PokemonType.GHOST, null, 0.2, 0.4, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 508, 60, 65, 65, 134, 114, 70, 60, 50, 178, GrowthRate.MEDIUM_FAST, null, false, false, - new PokemonForm("Phony Form", "phony", PokemonType.GHOST, null, 0.2, 0.4, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 508, 60, 65, 65, 134, 114, 70, 60, 50, 178, false, "", true), - new PokemonForm("Antique Form", "antique", PokemonType.GHOST, null, 0.2, 0.4, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 508, 60, 65, 65, 134, 114, 70, 60, 50, 178, false, "", true, true), - ), - new PokemonSpecies(SpeciesId.HATENNA, 8, false, false, false, "Calm Pokémon", PokemonType.PSYCHIC, null, 0.4, 3.4, AbilityId.HEALER, AbilityId.ANTICIPATION, AbilityId.MAGIC_BOUNCE, 265, 42, 30, 45, 56, 53, 39, 235, 50, 53, GrowthRate.SLOW, 0, false), - new PokemonSpecies(SpeciesId.HATTREM, 8, false, false, false, "Serene Pokémon", PokemonType.PSYCHIC, null, 0.6, 4.8, AbilityId.HEALER, AbilityId.ANTICIPATION, AbilityId.MAGIC_BOUNCE, 370, 57, 40, 65, 86, 73, 49, 120, 50, 130, GrowthRate.SLOW, 0, false), - new PokemonSpecies(SpeciesId.HATTERENE, 8, false, false, false, "Silent Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 2.1, 5.1, AbilityId.HEALER, AbilityId.ANTICIPATION, AbilityId.MAGIC_BOUNCE, 510, 57, 90, 95, 136, 103, 29, 45, 50, 255, GrowthRate.SLOW, 0, false, true, - new PokemonForm("Normal", "", PokemonType.PSYCHIC, PokemonType.FAIRY, 2.1, 5.1, AbilityId.HEALER, AbilityId.ANTICIPATION, AbilityId.MAGIC_BOUNCE, 510, 57, 90, 95, 136, 103, 29, 45, 50, 255, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.PSYCHIC, PokemonType.FAIRY, 26, 999.9, AbilityId.MAGIC_BOUNCE, AbilityId.MAGIC_BOUNCE, AbilityId.MAGIC_BOUNCE, 610, 87, 100, 110, 146, 118, 49, 45, 50, 255), - ), - new PokemonSpecies(SpeciesId.IMPIDIMP, 8, false, false, false, "Wily Pokémon", PokemonType.DARK, PokemonType.FAIRY, 0.4, 5.5, AbilityId.PRANKSTER, AbilityId.FRISK, AbilityId.PICKPOCKET, 265, 45, 45, 30, 55, 40, 50, 255, 50, 53, GrowthRate.MEDIUM_FAST, 100, false), - new PokemonSpecies(SpeciesId.MORGREM, 8, false, false, false, "Devious Pokémon", PokemonType.DARK, PokemonType.FAIRY, 0.8, 12.5, AbilityId.PRANKSTER, AbilityId.FRISK, AbilityId.PICKPOCKET, 370, 65, 60, 45, 75, 55, 70, 120, 50, 130, GrowthRate.MEDIUM_FAST, 100, false), - new PokemonSpecies(SpeciesId.GRIMMSNARL, 8, false, false, false, "Bulk Up Pokémon", PokemonType.DARK, PokemonType.FAIRY, 1.5, 61, AbilityId.PRANKSTER, AbilityId.FRISK, AbilityId.PICKPOCKET, 510, 95, 120, 65, 95, 75, 60, 45, 50, 255, GrowthRate.MEDIUM_FAST, 100, false, true, - new PokemonForm("Normal", "", PokemonType.DARK, PokemonType.FAIRY, 1.5, 61, AbilityId.PRANKSTER, AbilityId.FRISK, AbilityId.PICKPOCKET, 510, 95, 120, 65, 95, 75, 60, 45, 50, 255, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.DARK, PokemonType.FAIRY, 32, 999.9, AbilityId.PRANKSTER, AbilityId.PRANKSTER, AbilityId.PRANKSTER, 610, 130, 138, 75, 110, 92, 65, 45, 50, 255), - ), - new PokemonSpecies(SpeciesId.OBSTAGOON, 8, false, false, false, "Blocking Pokémon", PokemonType.DARK, PokemonType.NORMAL, 1.6, 46, AbilityId.RECKLESS, AbilityId.GUTS, AbilityId.DEFIANT, 520, 93, 90, 101, 60, 81, 95, 45, 50, 260, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PERRSERKER, 8, false, false, false, "Viking Pokémon", PokemonType.STEEL, null, 0.8, 28, AbilityId.BATTLE_ARMOR, AbilityId.TOUGH_CLAWS, AbilityId.STEELY_SPIRIT, 440, 70, 110, 100, 50, 60, 50, 90, 50, 154, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CURSOLA, 8, false, false, false, "Coral Pokémon", PokemonType.GHOST, null, 1, 0.4, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.PERISH_BODY, 510, 60, 95, 50, 145, 130, 30, 30, 50, 179, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.SIRFETCHD, 8, false, false, false, "Wild Duck Pokémon", PokemonType.FIGHTING, null, 0.8, 117, AbilityId.STEADFAST, AbilityId.NONE, AbilityId.SCRAPPY, 507, 62, 135, 95, 68, 82, 65, 45, 50, 177, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MR_RIME, 8, false, false, false, "Comedian Pokémon", PokemonType.ICE, PokemonType.PSYCHIC, 1.5, 58.2, AbilityId.TANGLED_FEET, AbilityId.SCREEN_CLEANER, AbilityId.ICE_BODY, 520, 80, 85, 75, 110, 100, 70, 45, 50, 182, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.RUNERIGUS, 8, false, false, false, "Grudge Pokémon", PokemonType.GROUND, PokemonType.GHOST, 1.6, 66.6, AbilityId.WANDERING_SPIRIT, AbilityId.NONE, AbilityId.NONE, 483, 58, 95, 145, 50, 105, 30, 90, 50, 169, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.MILCERY, 8, false, false, false, "Cream Pokémon", PokemonType.FAIRY, null, 0.2, 0.3, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 270, 45, 40, 40, 50, 61, 34, 200, 50, 54, GrowthRate.MEDIUM_FAST, 0, false), - new PokemonSpecies(SpeciesId.ALCREMIE, 8, false, false, false, "Cream Pokémon", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, GrowthRate.MEDIUM_FAST, 0, false, true, - new PokemonForm("Vanilla Cream", "vanilla-cream", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, "", true), - new PokemonForm("Ruby Cream", "ruby-cream", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), - new PokemonForm("Matcha Cream", "matcha-cream", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), - new PokemonForm("Mint Cream", "mint-cream", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), - new PokemonForm("Lemon Cream", "lemon-cream", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), - new PokemonForm("Salted Cream", "salted-cream", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), - new PokemonForm("Ruby Swirl", "ruby-swirl", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), - new PokemonForm("Caramel Swirl", "caramel-swirl", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), - new PokemonForm("Rainbow Swirl", "rainbow-swirl", PokemonType.FAIRY, null, 0.3, 0.5, AbilityId.SWEET_VEIL, AbilityId.NONE, AbilityId.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.FAIRY, null, 30, 999.9, AbilityId.MISTY_SURGE, AbilityId.NONE, AbilityId.MISTY_SURGE, 595, 105, 70, 85, 130, 141, 64, 100, 50, 173), - ), - new PokemonSpecies(SpeciesId.FALINKS, 8, false, false, false, "Formation Pokémon", PokemonType.FIGHTING, null, 3, 62, AbilityId.BATTLE_ARMOR, AbilityId.NONE, AbilityId.DEFIANT, 470, 65, 100, 100, 70, 60, 75, 45, 50, 165, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.PINCURCHIN, 8, false, false, false, "Sea Urchin Pokémon", PokemonType.ELECTRIC, null, 0.3, 1, AbilityId.LIGHTNING_ROD, AbilityId.NONE, AbilityId.ELECTRIC_SURGE, 435, 48, 101, 95, 91, 85, 15, 75, 50, 152, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SNOM, 8, false, false, false, "Worm Pokémon", PokemonType.ICE, PokemonType.BUG, 0.3, 3.8, AbilityId.SHIELD_DUST, AbilityId.NONE, AbilityId.ICE_SCALES, 185, 30, 25, 35, 45, 30, 20, 190, 50, 37, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.FROSMOTH, 8, false, false, false, "Frost Moth Pokémon", PokemonType.ICE, PokemonType.BUG, 1.3, 42, AbilityId.SHIELD_DUST, AbilityId.NONE, AbilityId.ICE_SCALES, 475, 70, 65, 60, 125, 90, 65, 75, 50, 166, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.STONJOURNER, 8, false, false, false, "Big Rock Pokémon", PokemonType.ROCK, null, 2.5, 520, AbilityId.POWER_SPOT, AbilityId.NONE, AbilityId.NONE, 470, 100, 125, 135, 20, 20, 70, 60, 50, 165, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.EISCUE, 8, false, false, false, "Penguin Pokémon", PokemonType.ICE, null, 1.4, 89, AbilityId.ICE_FACE, AbilityId.NONE, AbilityId.NONE, 470, 75, 80, 110, 65, 90, 50, 60, 50, 165, GrowthRate.SLOW, 50, false, false, - new PokemonForm("Ice Face", "", PokemonType.ICE, null, 1.4, 89, AbilityId.ICE_FACE, AbilityId.NONE, AbilityId.NONE, 470, 75, 80, 110, 65, 90, 50, 60, 50, 165, false, null, true), - new PokemonForm("No Ice", "no-ice", PokemonType.ICE, null, 1.4, 89, AbilityId.ICE_FACE, AbilityId.NONE, AbilityId.NONE, 470, 75, 80, 70, 65, 50, 130, 60, 50, 165), - ), - new PokemonSpecies(SpeciesId.INDEEDEE, 8, false, false, false, "Emotion Pokémon", PokemonType.PSYCHIC, PokemonType.NORMAL, 0.9, 28, AbilityId.INNER_FOCUS, AbilityId.SYNCHRONIZE, AbilityId.PSYCHIC_SURGE, 475, 60, 65, 55, 105, 95, 95, 30, 140, 166, GrowthRate.FAST, 50, false, false, - new PokemonForm("Male", "male", PokemonType.PSYCHIC, PokemonType.NORMAL, 0.9, 28, AbilityId.INNER_FOCUS, AbilityId.SYNCHRONIZE, AbilityId.PSYCHIC_SURGE, 475, 60, 65, 55, 105, 95, 95, 30, 140, 166, false, "", true), - new PokemonForm("Female", "female", PokemonType.PSYCHIC, PokemonType.NORMAL, 0.9, 28, AbilityId.OWN_TEMPO, AbilityId.SYNCHRONIZE, AbilityId.PSYCHIC_SURGE, 475, 70, 55, 65, 95, 105, 85, 30, 140, 166, false, null, true), - ), - new PokemonSpecies(SpeciesId.MORPEKO, 8, false, false, false, "Two-Sided Pokémon", PokemonType.ELECTRIC, PokemonType.DARK, 0.3, 3, AbilityId.HUNGER_SWITCH, AbilityId.NONE, AbilityId.NONE, 436, 58, 95, 58, 70, 58, 97, 180, 50, 153, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Full Belly Mode", "full-belly", PokemonType.ELECTRIC, PokemonType.DARK, 0.3, 3, AbilityId.HUNGER_SWITCH, AbilityId.NONE, AbilityId.NONE, 436, 58, 95, 58, 70, 58, 97, 180, 50, 153, false, "", true), - new PokemonForm("Hangry Mode", "hangry", PokemonType.ELECTRIC, PokemonType.DARK, 0.3, 3, AbilityId.HUNGER_SWITCH, AbilityId.NONE, AbilityId.NONE, 436, 58, 95, 58, 70, 58, 97, 180, 50, 153), - ), - new PokemonSpecies(SpeciesId.CUFANT, 8, false, false, false, "Copperderm Pokémon", PokemonType.STEEL, null, 1.2, 100, AbilityId.SHEER_FORCE, AbilityId.NONE, AbilityId.HEAVY_METAL, 330, 72, 80, 49, 40, 49, 40, 190, 50, 66, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.COPPERAJAH, 8, false, false, false, "Copperderm Pokémon", PokemonType.STEEL, null, 3, 650, AbilityId.SHEER_FORCE, AbilityId.NONE, AbilityId.HEAVY_METAL, 500, 122, 130, 69, 80, 69, 30, 90, 50, 175, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.STEEL, null, 3, 650, AbilityId.SHEER_FORCE, AbilityId.NONE, AbilityId.HEAVY_METAL, 500, 122, 130, 69, 80, 69, 30, 90, 50, 175, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.STEEL, PokemonType.GROUND, 23, 999.9, AbilityId.MOLD_BREAKER, AbilityId.NONE, AbilityId.MOLD_BREAKER, 600, 177, 155, 79, 90, 79, 20, 90, 50, 175), - ), - new PokemonSpecies(SpeciesId.DRACOZOLT, 8, false, false, false, "Fossil Pokémon", PokemonType.ELECTRIC, PokemonType.DRAGON, 1.8, 190, AbilityId.VOLT_ABSORB, AbilityId.HUSTLE, AbilityId.SAND_RUSH, 505, 90, 100, 90, 80, 70, 75, 45, 50, 177, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.ARCTOZOLT, 8, false, false, false, "Fossil Pokémon", PokemonType.ELECTRIC, PokemonType.ICE, 2.3, 150, AbilityId.VOLT_ABSORB, AbilityId.STATIC, AbilityId.SLUSH_RUSH, 505, 90, 100, 90, 90, 80, 55, 45, 50, 177, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.DRACOVISH, 8, false, false, false, "Fossil Pokémon", PokemonType.WATER, PokemonType.DRAGON, 2.3, 215, AbilityId.WATER_ABSORB, AbilityId.STRONG_JAW, AbilityId.SAND_RUSH, 505, 90, 90, 100, 70, 80, 75, 45, 50, 177, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.ARCTOVISH, 8, false, false, false, "Fossil Pokémon", PokemonType.WATER, PokemonType.ICE, 2, 175, AbilityId.WATER_ABSORB, AbilityId.ICE_BODY, AbilityId.SLUSH_RUSH, 505, 90, 90, 100, 80, 90, 55, 45, 50, 177, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.DURALUDON, 8, false, false, false, "Alloy Pokémon", PokemonType.STEEL, PokemonType.DRAGON, 1.8, 40, AbilityId.LIGHT_METAL, AbilityId.HEAVY_METAL, AbilityId.STALWART, 535, 70, 95, 115, 120, 50, 85, 45, 50, 187, GrowthRate.MEDIUM_FAST, 50, false, true, - new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.DRAGON, 1.8, 40, AbilityId.LIGHT_METAL, AbilityId.HEAVY_METAL, AbilityId.STALWART, 535, 70, 95, 115, 120, 50, 85, 45, 50, 187, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, PokemonType.STEEL, PokemonType.DRAGON, 43, 999.9, AbilityId.LIGHTNING_ROD, AbilityId.LIGHTNING_ROD, AbilityId.LIGHTNING_ROD, 635, 100, 110, 120, 175, 60, 70, 45, 50, 187), - ), - new PokemonSpecies(SpeciesId.DREEPY, 8, false, false, false, "Lingering Pokémon", PokemonType.DRAGON, PokemonType.GHOST, 0.5, 2, AbilityId.CLEAR_BODY, AbilityId.INFILTRATOR, AbilityId.CURSED_BODY, 270, 28, 60, 30, 40, 30, 82, 45, 50, 54, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.DRAKLOAK, 8, false, false, false, "Caretaker Pokémon", PokemonType.DRAGON, PokemonType.GHOST, 1.4, 11, AbilityId.CLEAR_BODY, AbilityId.INFILTRATOR, AbilityId.CURSED_BODY, 410, 68, 80, 50, 60, 50, 102, 45, 50, 144, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.DRAGAPULT, 8, false, false, false, "Stealth Pokémon", PokemonType.DRAGON, PokemonType.GHOST, 3, 50, AbilityId.CLEAR_BODY, AbilityId.INFILTRATOR, AbilityId.CURSED_BODY, 600, 88, 120, 75, 100, 75, 142, 45, 50, 300, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.ZACIAN, 8, false, true, false, "Warrior Pokémon", PokemonType.FAIRY, null, 2.8, 110, AbilityId.INTREPID_SWORD, AbilityId.NONE, AbilityId.NONE, 660, 92, 120, 115, 80, 115, 138, 10, 0, 335, GrowthRate.SLOW, null, false, false, - new PokemonForm("Hero of Many Battles", "hero-of-many-battles", PokemonType.FAIRY, null, 2.8, 110, AbilityId.INTREPID_SWORD, AbilityId.NONE, AbilityId.NONE, 660, 92, 120, 115, 80, 115, 138, 10, 0, 335, false, "", true), - new PokemonForm("Crowned", "crowned", PokemonType.FAIRY, PokemonType.STEEL, 2.8, 355, AbilityId.INTREPID_SWORD, AbilityId.NONE, AbilityId.NONE, 700, 92, 150, 115, 80, 115, 148, 10, 0, 360), - ), - new PokemonSpecies(SpeciesId.ZAMAZENTA, 8, false, true, false, "Warrior Pokémon", PokemonType.FIGHTING, null, 2.9, 210, AbilityId.DAUNTLESS_SHIELD, AbilityId.NONE, AbilityId.NONE, 660, 92, 120, 115, 80, 115, 138, 10, 0, 335, GrowthRate.SLOW, null, false, false, - new PokemonForm("Hero of Many Battles", "hero-of-many-battles", PokemonType.FIGHTING, null, 2.9, 210, AbilityId.DAUNTLESS_SHIELD, AbilityId.NONE, AbilityId.NONE, 660, 92, 120, 115, 80, 115, 138, 10, 0, 335, false, "", true), - new PokemonForm("Crowned", "crowned", PokemonType.FIGHTING, PokemonType.STEEL, 2.9, 785, AbilityId.DAUNTLESS_SHIELD, AbilityId.NONE, AbilityId.NONE, 700, 92, 120, 140, 80, 140, 128, 10, 0, 360), - ), - new PokemonSpecies(SpeciesId.ETERNATUS, 8, false, true, false, "Gigantic Pokémon", PokemonType.POISON, PokemonType.DRAGON, 20, 950, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 690, 140, 85, 95, 145, 95, 130, 255, 0, 345, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal", "", PokemonType.POISON, PokemonType.DRAGON, 20, 950, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 690, 140, 85, 95, 145, 95, 130, 255, 0, 345, false, null, true), - new PokemonForm("E-Max", "eternamax", PokemonType.POISON, PokemonType.DRAGON, 100, 999.9, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 1125, 255, 115, 250, 125, 250, 130, 255, 0, 345), - ), - new PokemonSpecies(SpeciesId.KUBFU, 8, true, false, false, "Wushu Pokémon", PokemonType.FIGHTING, null, 0.6, 12, AbilityId.INNER_FOCUS, AbilityId.NONE, AbilityId.NONE, 385, 60, 90, 60, 53, 50, 72, 3, 50, 77, GrowthRate.SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.URSHIFU, 8, true, false, false, "Wushu Pokémon", PokemonType.FIGHTING, PokemonType.DARK, 1.9, 105, AbilityId.UNSEEN_FIST, AbilityId.NONE, AbilityId.NONE, 550, 100, 130, 100, 63, 60, 97, 3, 50, 275, GrowthRate.SLOW, 87.5, false, true, - new PokemonForm("Single Strike Style", "single-strike", PokemonType.FIGHTING, PokemonType.DARK, 1.9, 105, AbilityId.UNSEEN_FIST, AbilityId.NONE, AbilityId.NONE, 550, 100, 130, 100, 63, 60, 97, 3, 50, 275, false, "", true), - new PokemonForm("Rapid Strike Style", "rapid-strike", PokemonType.FIGHTING, PokemonType.WATER, 1.9, 105, AbilityId.UNSEEN_FIST, AbilityId.NONE, AbilityId.NONE, 550, 100, 130, 100, 63, 60, 97, 3, 50, 275, false, null, true), - new PokemonForm("G-Max Single Strike Style", SpeciesFormKey.GIGANTAMAX_SINGLE, PokemonType.FIGHTING, PokemonType.DARK, 29, 999.9, AbilityId.UNSEEN_FIST, AbilityId.NONE, AbilityId.NONE, 650, 125, 145, 115, 83, 70, 112, 3, 50, 275), - new PokemonForm("G-Max Rapid Strike Style", SpeciesFormKey.GIGANTAMAX_RAPID, PokemonType.FIGHTING, PokemonType.WATER, 26, 999.9, AbilityId.UNSEEN_FIST, AbilityId.NONE, AbilityId.NONE, 650, 125, 145, 115, 83, 70, 112, 3, 50, 275), - ), - new PokemonSpecies(SpeciesId.ZARUDE, 8, false, false, true, "Rogue Monkey Pokémon", PokemonType.DARK, PokemonType.GRASS, 1.8, 70, AbilityId.LEAF_GUARD, AbilityId.NONE, AbilityId.NONE, 600, 105, 120, 105, 70, 95, 105, 3, 0, 300, GrowthRate.SLOW, null, false, false, - new PokemonForm("Normal", "", PokemonType.DARK, PokemonType.GRASS, 1.8, 70, AbilityId.LEAF_GUARD, AbilityId.NONE, AbilityId.NONE, 600, 105, 120, 105, 70, 95, 105, 3, 0, 300, false, null, true), - new PokemonForm("Dada", "dada", PokemonType.DARK, PokemonType.GRASS, 1.8, 70, AbilityId.LEAF_GUARD, AbilityId.NONE, AbilityId.NONE, 600, 105, 120, 105, 70, 95, 105, 3, 0, 300, false, null, true), - ), - new PokemonSpecies(SpeciesId.REGIELEKI, 8, true, false, false, "Electron Pokémon", PokemonType.ELECTRIC, null, 1.2, 145, AbilityId.TRANSISTOR, AbilityId.NONE, AbilityId.NONE, 580, 80, 100, 50, 100, 50, 200, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.REGIDRAGO, 8, true, false, false, "Dragon Orb Pokémon", PokemonType.DRAGON, null, 2.1, 200, AbilityId.DRAGONS_MAW, AbilityId.NONE, AbilityId.NONE, 580, 200, 100, 50, 100, 50, 80, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.GLASTRIER, 8, true, false, false, "Wild Horse Pokémon", PokemonType.ICE, null, 2.2, 800, AbilityId.CHILLING_NEIGH, AbilityId.NONE, AbilityId.NONE, 580, 100, 145, 130, 65, 110, 30, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.SPECTRIER, 8, true, false, false, "Swift Horse Pokémon", PokemonType.GHOST, null, 2, 44.5, AbilityId.GRIM_NEIGH, AbilityId.NONE, AbilityId.NONE, 580, 100, 65, 60, 145, 80, 130, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.CALYREX, 8, false, true, false, "King Pokémon", PokemonType.PSYCHIC, PokemonType.GRASS, 1.1, 7.7, AbilityId.UNNERVE, AbilityId.NONE, AbilityId.NONE, 500, 100, 80, 80, 80, 80, 80, 3, 100, 250, GrowthRate.SLOW, null, false, true, - new PokemonForm("Normal", "", PokemonType.PSYCHIC, PokemonType.GRASS, 1.1, 7.7, AbilityId.UNNERVE, AbilityId.NONE, AbilityId.NONE, 500, 100, 80, 80, 80, 80, 80, 3, 100, 250, false, null, true), - new PokemonForm("Ice", "ice", PokemonType.PSYCHIC, PokemonType.ICE, 2.4, 809.1, AbilityId.AS_ONE_GLASTRIER, AbilityId.NONE, AbilityId.NONE, 680, 100, 165, 150, 85, 130, 50, 3, 100, 340), - new PokemonForm("Shadow", "shadow", PokemonType.PSYCHIC, PokemonType.GHOST, 2.4, 53.6, AbilityId.AS_ONE_SPECTRIER, AbilityId.NONE, AbilityId.NONE, 680, 100, 85, 80, 165, 100, 150, 3, 100, 340), - ), - new PokemonSpecies(SpeciesId.WYRDEER, 8, false, false, false, "Big Horn Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 1.8, 95.1, AbilityId.INTIMIDATE, AbilityId.FRISK, AbilityId.SAP_SIPPER, 525, 103, 105, 72, 105, 75, 65, 45, 50, 263, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.KLEAVOR, 8, false, false, false, "Axe Pokémon", PokemonType.BUG, PokemonType.ROCK, 1.8, 89, AbilityId.SWARM, AbilityId.SHEER_FORCE, AbilityId.SHARPNESS, 500, 70, 135, 95, 45, 70, 85, 15, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.URSALUNA, 8, false, false, false, "Peat Pokémon", PokemonType.GROUND, PokemonType.NORMAL, 2.4, 290, AbilityId.GUTS, AbilityId.BULLETPROOF, AbilityId.UNNERVE, 550, 130, 140, 105, 45, 80, 50, 20, 50, 275, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BASCULEGION, 8, false, false, false, "Big Fish Pokémon", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 112, 65, 80, 75, 78, 45, 50, 265, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Male", "male", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 112, 65, 80, 75, 78, 45, 50, 265, false, "", true), - new PokemonForm("Female", "female", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 92, 65, 100, 75, 78, 45, 50, 265, false, null, true), - ), - new PokemonSpecies(SpeciesId.SNEASLER, 8, false, false, false, "Free Climb Pokémon", PokemonType.FIGHTING, PokemonType.POISON, 1.3, 43, AbilityId.PRESSURE, AbilityId.UNBURDEN, AbilityId.POISON_TOUCH, 510, 80, 130, 60, 40, 80, 120, 20, 50, 102, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.OVERQWIL, 8, false, false, false, "Pin Cluster Pokémon", PokemonType.DARK, PokemonType.POISON, 2.5, 60.5, AbilityId.POISON_POINT, AbilityId.SWIFT_SWIM, AbilityId.INTIMIDATE, 510, 85, 115, 95, 65, 65, 85, 45, 50, 179, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ENAMORUS, 8, true, false, false, "Love-Hate Pokémon", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.CONTRARY, 580, 74, 115, 70, 135, 80, 106, 3, 50, 116, GrowthRate.SLOW, 0, false, true, - new PokemonForm("Incarnate Forme", "incarnate", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.CONTRARY, 580, 74, 115, 70, 135, 80, 106, 3, 50, 116, false, null, true), - new PokemonForm("Therian Forme", "therian", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.OVERCOAT, 580, 74, 115, 110, 135, 100, 46, 3, 50, 116), - ), - new PokemonSpecies(SpeciesId.SPRIGATITO, 9, false, false, false, "Grass Cat Pokémon", PokemonType.GRASS, null, 0.4, 4.1, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.PROTEAN, 310, 40, 61, 54, 45, 45, 65, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.FLORAGATO, 9, false, false, false, "Grass Cat Pokémon", PokemonType.GRASS, null, 0.9, 12.2, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.PROTEAN, 410, 61, 80, 63, 60, 63, 83, 45, 50, 144, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.MEOWSCARADA, 9, false, false, false, "Magician Pokémon", PokemonType.GRASS, PokemonType.DARK, 1.5, 31.2, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.PROTEAN, 530, 76, 110, 70, 81, 70, 123, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.FUECOCO, 9, false, false, false, "Fire Croc Pokémon", PokemonType.FIRE, null, 0.4, 9.8, AbilityId.BLAZE, AbilityId.NONE, AbilityId.UNAWARE, 310, 67, 45, 59, 63, 40, 36, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.CROCALOR, 9, false, false, false, "Fire Croc Pokémon", PokemonType.FIRE, null, 1, 30.7, AbilityId.BLAZE, AbilityId.NONE, AbilityId.UNAWARE, 411, 81, 55, 78, 90, 58, 49, 45, 50, 144, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.SKELEDIRGE, 9, false, false, false, "Singer Pokémon", PokemonType.FIRE, PokemonType.GHOST, 1.6, 326.5, AbilityId.BLAZE, AbilityId.NONE, AbilityId.UNAWARE, 530, 104, 75, 100, 110, 75, 66, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.QUAXLY, 9, false, false, false, "Duckling Pokémon", PokemonType.WATER, null, 0.5, 6.1, AbilityId.TORRENT, AbilityId.NONE, AbilityId.MOXIE, 310, 55, 65, 45, 50, 45, 50, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.QUAXWELL, 9, false, false, false, "Practicing Pokémon", PokemonType.WATER, null, 1.2, 21.5, AbilityId.TORRENT, AbilityId.NONE, AbilityId.MOXIE, 410, 70, 85, 65, 65, 60, 65, 45, 50, 144, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.QUAQUAVAL, 9, false, false, false, "Dancer Pokémon", PokemonType.WATER, PokemonType.FIGHTING, 1.8, 61.9, AbilityId.TORRENT, AbilityId.NONE, AbilityId.MOXIE, 530, 85, 120, 80, 85, 75, 85, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.LECHONK, 9, false, false, false, "Hog Pokémon", PokemonType.NORMAL, null, 0.5, 10.2, AbilityId.AROMA_VEIL, AbilityId.GLUTTONY, AbilityId.THICK_FAT, 254, 54, 45, 40, 35, 45, 35, 255, 50, 51, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.OINKOLOGNE, 9, false, false, false, "Hog Pokémon", PokemonType.NORMAL, null, 1, 120, AbilityId.LINGERING_AROMA, AbilityId.GLUTTONY, AbilityId.THICK_FAT, 489, 110, 100, 75, 59, 80, 65, 100, 50, 171, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Male", "male", PokemonType.NORMAL, null, 1, 120, AbilityId.LINGERING_AROMA, AbilityId.GLUTTONY, AbilityId.THICK_FAT, 489, 110, 100, 75, 59, 80, 65, 100, 50, 171, false, "", true), - new PokemonForm("Female", "female", PokemonType.NORMAL, null, 1, 120, AbilityId.AROMA_VEIL, AbilityId.GLUTTONY, AbilityId.THICK_FAT, 489, 115, 90, 70, 59, 90, 65, 100, 50, 171, false, null, true), - ), - new PokemonSpecies(SpeciesId.TAROUNTULA, 9, false, false, false, "String Ball Pokémon", PokemonType.BUG, null, 0.3, 4, AbilityId.INSOMNIA, AbilityId.NONE, AbilityId.STAKEOUT, 210, 35, 41, 45, 29, 40, 20, 255, 50, 42, GrowthRate.ERRATIC, 50, false), - new PokemonSpecies(SpeciesId.SPIDOPS, 9, false, false, false, "Trap Pokémon", PokemonType.BUG, null, 1, 16.5, AbilityId.INSOMNIA, AbilityId.NONE, AbilityId.STAKEOUT, 404, 60, 79, 92, 52, 86, 35, 120, 50, 141, GrowthRate.ERRATIC, 50, false), - new PokemonSpecies(SpeciesId.NYMBLE, 9, false, false, false, "Grasshopper Pokémon", PokemonType.BUG, null, 0.2, 1, AbilityId.SWARM, AbilityId.NONE, AbilityId.TINTED_LENS, 210, 33, 46, 40, 21, 25, 45, 190, 20, 42, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.LOKIX, 9, false, false, false, "Grasshopper Pokémon", PokemonType.BUG, PokemonType.DARK, 1, 17.5, AbilityId.SWARM, AbilityId.NONE, AbilityId.TINTED_LENS, 450, 71, 102, 78, 52, 55, 92, 30, 0, 158, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PAWMI, 9, false, false, false, "Mouse Pokémon", PokemonType.ELECTRIC, null, 0.3, 2.5, AbilityId.STATIC, AbilityId.NATURAL_CURE, AbilityId.IRON_FIST, 240, 45, 50, 20, 40, 25, 60, 190, 50, 48, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PAWMO, 9, false, false, false, "Mouse Pokémon", PokemonType.ELECTRIC, PokemonType.FIGHTING, 0.4, 6.5, AbilityId.VOLT_ABSORB, AbilityId.NATURAL_CURE, AbilityId.IRON_FIST, 350, 60, 75, 40, 50, 40, 85, 80, 50, 123, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.PAWMOT, 9, false, false, false, "Hands-On Pokémon", PokemonType.ELECTRIC, PokemonType.FIGHTING, 0.9, 41, AbilityId.VOLT_ABSORB, AbilityId.NATURAL_CURE, AbilityId.IRON_FIST, 490, 70, 115, 70, 70, 60, 105, 45, 50, 245, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.TANDEMAUS, 9, false, false, false, "Couple Pokémon", PokemonType.NORMAL, null, 0.3, 1.8, AbilityId.RUN_AWAY, AbilityId.PICKUP, AbilityId.OWN_TEMPO, 305, 50, 50, 45, 40, 45, 75, 150, 50, 61, GrowthRate.FAST, null, false), - new PokemonSpecies(SpeciesId.MAUSHOLD, 9, false, false, false, "Family Pokémon", PokemonType.NORMAL, null, 0.3, 2.3, AbilityId.FRIEND_GUARD, AbilityId.CHEEK_POUCH, AbilityId.TECHNICIAN, 470, 74, 75, 70, 65, 75, 111, 75, 50, 165, GrowthRate.FAST, null, false, false, - new PokemonForm("Family of Four", "four", PokemonType.NORMAL, null, 0.3, 2.8, AbilityId.FRIEND_GUARD, AbilityId.CHEEK_POUCH, AbilityId.TECHNICIAN, 470, 74, 75, 70, 65, 75, 111, 75, 50, 165), - new PokemonForm("Family of Three", "three", PokemonType.NORMAL, null, 0.3, 2.3, AbilityId.FRIEND_GUARD, AbilityId.CHEEK_POUCH, AbilityId.TECHNICIAN, 470, 74, 75, 70, 65, 75, 111, 75, 50, 165), - ), - new PokemonSpecies(SpeciesId.FIDOUGH, 9, false, false, false, "Puppy Pokémon", PokemonType.FAIRY, null, 0.3, 10.9, AbilityId.OWN_TEMPO, AbilityId.NONE, AbilityId.KLUTZ, 312, 37, 55, 70, 30, 55, 65, 190, 50, 62, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.DACHSBUN, 9, false, false, false, "Dog Pokémon", PokemonType.FAIRY, null, 0.5, 14.9, AbilityId.WELL_BAKED_BODY, AbilityId.NONE, AbilityId.AROMA_VEIL, 477, 57, 80, 115, 50, 80, 95, 90, 50, 167, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SMOLIV, 9, false, false, false, "Olive Pokémon", PokemonType.GRASS, PokemonType.NORMAL, 0.3, 6.5, AbilityId.EARLY_BIRD, AbilityId.NONE, AbilityId.HARVEST, 260, 41, 35, 45, 58, 51, 30, 255, 50, 52, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.DOLLIV, 9, false, false, false, "Olive Pokémon", PokemonType.GRASS, PokemonType.NORMAL, 0.6, 11.9, AbilityId.EARLY_BIRD, AbilityId.NONE, AbilityId.HARVEST, 354, 52, 53, 60, 78, 78, 33, 120, 50, 124, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.ARBOLIVA, 9, false, false, false, "Olive Pokémon", PokemonType.GRASS, PokemonType.NORMAL, 1.4, 48.2, AbilityId.SEED_SOWER, AbilityId.NONE, AbilityId.HARVEST, 510, 78, 69, 90, 125, 109, 39, 45, 50, 255, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SQUAWKABILLY, 9, false, false, false, "Parrot Pokémon", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 2.4, AbilityId.INTIMIDATE, AbilityId.HUSTLE, AbilityId.GUTS, 417, 82, 96, 51, 45, 51, 92, 190, 50, 146, GrowthRate.ERRATIC, 50, false, false, - new PokemonForm("Green Plumage", "green-plumage", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 2.4, AbilityId.INTIMIDATE, AbilityId.HUSTLE, AbilityId.GUTS, 417, 82, 96, 51, 45, 51, 92, 190, 50, 146, false, null, true), - new PokemonForm("Blue Plumage", "blue-plumage", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 2.4, AbilityId.INTIMIDATE, AbilityId.HUSTLE, AbilityId.GUTS, 417, 82, 96, 51, 45, 51, 92, 190, 50, 146, false, null, true), - new PokemonForm("Yellow Plumage", "yellow-plumage", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 2.4, AbilityId.INTIMIDATE, AbilityId.HUSTLE, AbilityId.SHEER_FORCE, 417, 82, 96, 51, 45, 51, 92, 190, 50, 146, false, null, true), - new PokemonForm("White Plumage", "white-plumage", PokemonType.NORMAL, PokemonType.FLYING, 0.6, 2.4, AbilityId.INTIMIDATE, AbilityId.HUSTLE, AbilityId.SHEER_FORCE, 417, 82, 96, 51, 45, 51, 92, 190, 50, 146, false, null, true), - ), - new PokemonSpecies(SpeciesId.NACLI, 9, false, false, false, "Rock Salt Pokémon", PokemonType.ROCK, null, 0.4, 16, AbilityId.PURIFYING_SALT, AbilityId.STURDY, AbilityId.CLEAR_BODY, 280, 55, 55, 75, 35, 35, 25, 255, 50, 56, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.NACLSTACK, 9, false, false, false, "Rock Salt Pokémon", PokemonType.ROCK, null, 0.6, 105, AbilityId.PURIFYING_SALT, AbilityId.STURDY, AbilityId.CLEAR_BODY, 355, 60, 60, 100, 35, 65, 35, 120, 50, 124, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.GARGANACL, 9, false, false, false, "Rock Salt Pokémon", PokemonType.ROCK, null, 2.3, 240, AbilityId.PURIFYING_SALT, AbilityId.STURDY, AbilityId.CLEAR_BODY, 500, 100, 100, 130, 45, 90, 35, 45, 50, 250, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.CHARCADET, 9, false, false, false, "Fire Child Pokémon", PokemonType.FIRE, null, 0.6, 10.5, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.FLAME_BODY, 255, 40, 50, 40, 50, 40, 35, 90, 50, 51, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.ARMAROUGE, 9, false, false, false, "Fire Warrior Pokémon", PokemonType.FIRE, PokemonType.PSYCHIC, 1.5, 85, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.WEAK_ARMOR, 525, 85, 60, 100, 125, 80, 75, 25, 20, 263, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.CERULEDGE, 9, false, false, false, "Fire Blades Pokémon", PokemonType.FIRE, PokemonType.GHOST, 1.6, 62, AbilityId.FLASH_FIRE, AbilityId.NONE, AbilityId.WEAK_ARMOR, 525, 75, 125, 80, 60, 100, 85, 25, 20, 263, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.TADBULB, 9, false, false, false, "EleTadpole Pokémon", PokemonType.ELECTRIC, null, 0.3, 0.4, AbilityId.OWN_TEMPO, AbilityId.STATIC, AbilityId.DAMP, 272, 61, 31, 41, 59, 35, 45, 190, 50, 54, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BELLIBOLT, 9, false, false, false, "EleFrog Pokémon", PokemonType.ELECTRIC, null, 1.2, 113, AbilityId.ELECTROMORPHOSIS, AbilityId.STATIC, AbilityId.DAMP, 495, 109, 64, 91, 103, 83, 45, 50, 50, 173, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.WATTREL, 9, false, false, false, "Storm Petrel Pokémon", PokemonType.ELECTRIC, PokemonType.FLYING, 0.4, 3.6, AbilityId.WIND_POWER, AbilityId.VOLT_ABSORB, AbilityId.COMPETITIVE, 280, 40, 40, 35, 55, 40, 70, 180, 50, 56, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.KILOWATTREL, 9, false, false, false, "Frigatebird Pokémon", PokemonType.ELECTRIC, PokemonType.FLYING, 1.4, 38.6, AbilityId.WIND_POWER, AbilityId.VOLT_ABSORB, AbilityId.COMPETITIVE, 490, 70, 70, 60, 105, 60, 125, 90, 50, 172, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.MASCHIFF, 9, false, false, false, "Rascal Pokémon", PokemonType.DARK, null, 0.5, 16, AbilityId.INTIMIDATE, AbilityId.RUN_AWAY, AbilityId.STAKEOUT, 340, 60, 78, 60, 40, 51, 51, 150, 50, 68, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.MABOSSTIFF, 9, false, false, false, "Boss Pokémon", PokemonType.DARK, null, 1.1, 61, AbilityId.INTIMIDATE, AbilityId.GUARD_DOG, AbilityId.STAKEOUT, 505, 80, 120, 90, 60, 70, 85, 75, 50, 177, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.SHROODLE, 9, false, false, false, "Toxic Mouse Pokémon", PokemonType.POISON, PokemonType.NORMAL, 0.2, 0.7, AbilityId.UNBURDEN, AbilityId.PICKPOCKET, AbilityId.PRANKSTER, 290, 40, 65, 35, 40, 35, 75, 190, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.GRAFAIAI, 9, false, false, false, "Toxic Monkey Pokémon", PokemonType.POISON, PokemonType.NORMAL, 0.7, 27.2, AbilityId.UNBURDEN, AbilityId.POISON_TOUCH, AbilityId.PRANKSTER, 485, 63, 95, 65, 80, 72, 110, 90, 50, 170, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.BRAMBLIN, 9, false, false, false, "Tumbleweed Pokémon", PokemonType.GRASS, PokemonType.GHOST, 0.6, 0.6, AbilityId.WIND_RIDER, AbilityId.NONE, AbilityId.INFILTRATOR, 275, 40, 65, 30, 45, 35, 60, 190, 50, 55, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BRAMBLEGHAST, 9, false, false, false, "Tumbleweed Pokémon", PokemonType.GRASS, PokemonType.GHOST, 1.2, 6, AbilityId.WIND_RIDER, AbilityId.NONE, AbilityId.INFILTRATOR, 480, 55, 115, 70, 80, 70, 90, 45, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.TOEDSCOOL, 9, false, false, false, "Woodear Pokémon", PokemonType.GROUND, PokemonType.GRASS, 0.9, 33, AbilityId.MYCELIUM_MIGHT, AbilityId.NONE, AbilityId.NONE, 335, 40, 40, 35, 50, 100, 70, 190, 50, 67, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.TOEDSCRUEL, 9, false, false, false, "Woodear Pokémon", PokemonType.GROUND, PokemonType.GRASS, 1.9, 58, AbilityId.MYCELIUM_MIGHT, AbilityId.NONE, AbilityId.NONE, 515, 80, 70, 65, 80, 120, 100, 90, 50, 180, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.KLAWF, 9, false, false, false, "Ambush Pokémon", PokemonType.ROCK, null, 1.3, 79, AbilityId.ANGER_SHELL, AbilityId.SHELL_ARMOR, AbilityId.REGENERATOR, 450, 70, 100, 115, 35, 55, 75, 120, 50, 158, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CAPSAKID, 9, false, false, false, "Spicy Pepper Pokémon", PokemonType.GRASS, null, 0.3, 3, AbilityId.CHLOROPHYLL, AbilityId.INSOMNIA, AbilityId.KLUTZ, 304, 50, 62, 40, 62, 40, 50, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.SCOVILLAIN, 9, false, false, false, "Spicy Pepper Pokémon", PokemonType.GRASS, PokemonType.FIRE, 0.9, 15, AbilityId.CHLOROPHYLL, AbilityId.INSOMNIA, AbilityId.MOODY, 486, 65, 108, 65, 108, 65, 75, 75, 50, 170, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.RELLOR, 9, false, false, false, "Rolling Pokémon", PokemonType.BUG, null, 0.2, 1, AbilityId.COMPOUND_EYES, AbilityId.NONE, AbilityId.SHED_SKIN, 270, 41, 50, 60, 31, 58, 30, 190, 50, 54, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.RABSCA, 9, false, false, false, "Rolling Pokémon", PokemonType.BUG, PokemonType.PSYCHIC, 0.3, 3.5, AbilityId.SYNCHRONIZE, AbilityId.NONE, AbilityId.TELEPATHY, 470, 75, 50, 85, 115, 100, 45, 45, 50, 165, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.FLITTLE, 9, false, false, false, "Frill Pokémon", PokemonType.PSYCHIC, null, 0.2, 1.5, AbilityId.ANTICIPATION, AbilityId.FRISK, AbilityId.SPEED_BOOST, 255, 30, 35, 30, 55, 30, 75, 120, 50, 51, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.ESPATHRA, 9, false, false, false, "Ostrich Pokémon", PokemonType.PSYCHIC, null, 1.9, 90, AbilityId.OPPORTUNIST, AbilityId.FRISK, AbilityId.SPEED_BOOST, 481, 95, 60, 60, 101, 60, 105, 60, 50, 168, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.TINKATINK, 9, false, false, false, "Metalsmith Pokémon", PokemonType.FAIRY, PokemonType.STEEL, 0.4, 8.9, AbilityId.MOLD_BREAKER, AbilityId.OWN_TEMPO, AbilityId.PICKPOCKET, 297, 50, 45, 45, 35, 64, 58, 190, 50, 59, GrowthRate.MEDIUM_SLOW, 0, false), - new PokemonSpecies(SpeciesId.TINKATUFF, 9, false, false, false, "Hammer Pokémon", PokemonType.FAIRY, PokemonType.STEEL, 0.7, 59.1, AbilityId.MOLD_BREAKER, AbilityId.OWN_TEMPO, AbilityId.PICKPOCKET, 380, 65, 55, 55, 45, 82, 78, 90, 50, 133, GrowthRate.MEDIUM_SLOW, 0, false), - new PokemonSpecies(SpeciesId.TINKATON, 9, false, false, false, "Hammer Pokémon", PokemonType.FAIRY, PokemonType.STEEL, 0.7, 112.8, AbilityId.MOLD_BREAKER, AbilityId.OWN_TEMPO, AbilityId.PICKPOCKET, 506, 85, 75, 77, 70, 105, 94, 45, 50, 253, GrowthRate.MEDIUM_SLOW, 0, false), - new PokemonSpecies(SpeciesId.WIGLETT, 9, false, false, false, "Garden Eel Pokémon", PokemonType.WATER, null, 1.2, 1.8, AbilityId.GOOEY, AbilityId.RATTLED, AbilityId.SAND_VEIL, 245, 10, 55, 25, 35, 25, 95, 255, 50, 49, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.WUGTRIO, 9, false, false, false, "Garden Eel Pokémon", PokemonType.WATER, null, 1.2, 5.4, AbilityId.GOOEY, AbilityId.RATTLED, AbilityId.SAND_VEIL, 425, 35, 100, 50, 50, 70, 120, 50, 50, 149, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BOMBIRDIER, 9, false, false, false, "Item Drop Pokémon", PokemonType.FLYING, PokemonType.DARK, 1.5, 42.9, AbilityId.BIG_PECKS, AbilityId.KEEN_EYE, AbilityId.ROCKY_PAYLOAD, 485, 70, 103, 85, 60, 85, 82, 25, 50, 243, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.FINIZEN, 9, false, false, false, "Dolphin Pokémon", PokemonType.WATER, null, 1.3, 60.2, AbilityId.WATER_VEIL, AbilityId.NONE, AbilityId.NONE, 315, 70, 45, 40, 45, 40, 75, 200, 50, 63, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.PALAFIN, 9, false, false, false, "Dolphin Pokémon", PokemonType.WATER, null, 1.3, 60.2, AbilityId.ZERO_TO_HERO, AbilityId.NONE, AbilityId.NONE, 457, 100, 70, 72, 53, 62, 100, 45, 50, 160, GrowthRate.SLOW, 50, false, true, - new PokemonForm("Zero Form", "zero", PokemonType.WATER, null, 1.3, 60.2, AbilityId.ZERO_TO_HERO, AbilityId.NONE, AbilityId.ZERO_TO_HERO, 457, 100, 70, 72, 53, 62, 100, 45, 50, 160, false, null, true), - new PokemonForm("Hero Form", "hero", PokemonType.WATER, null, 1.8, 97.4, AbilityId.ZERO_TO_HERO, AbilityId.NONE, AbilityId.ZERO_TO_HERO, 650, 100, 160, 97, 106, 87, 100, 45, 50, 160), - ), - new PokemonSpecies(SpeciesId.VAROOM, 9, false, false, false, "Single-Cyl Pokémon", PokemonType.STEEL, PokemonType.POISON, 1, 35, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.SLOW_START, 300, 45, 70, 63, 30, 45, 47, 190, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.REVAVROOM, 9, false, false, false, "Multi-Cyl Pokémon", PokemonType.STEEL, PokemonType.POISON, 1.8, 120, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.POISON, 1.8, 120, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, false, null, true), - new PokemonForm("Segin Starmobile", "segin-starmobile", PokemonType.STEEL, PokemonType.DARK, 1.8, 240, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.INTIMIDATE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), - new PokemonForm("Schedar Starmobile", "schedar-starmobile", PokemonType.STEEL, PokemonType.FIRE, 1.8, 240, AbilityId.SPEED_BOOST, AbilityId.NONE, AbilityId.SPEED_BOOST, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), - new PokemonForm("Navi Starmobile", "navi-starmobile", PokemonType.STEEL, PokemonType.POISON, 1.8, 240, AbilityId.TOXIC_DEBRIS, AbilityId.NONE, AbilityId.TOXIC_DEBRIS, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), - new PokemonForm("Ruchbah Starmobile", "ruchbah-starmobile", PokemonType.STEEL, PokemonType.FAIRY, 1.8, 240, AbilityId.MISTY_SURGE, AbilityId.NONE, AbilityId.MISTY_SURGE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), - new PokemonForm("Caph Starmobile", "caph-starmobile", PokemonType.STEEL, PokemonType.FIGHTING, 1.8, 240, AbilityId.STAMINA, AbilityId.NONE, AbilityId.STAMINA, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), - ), - new PokemonSpecies(SpeciesId.CYCLIZAR, 9, false, false, false, "Mount Pokémon", PokemonType.DRAGON, PokemonType.NORMAL, 1.6, 63, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.REGENERATOR, 501, 70, 95, 65, 85, 65, 121, 190, 50, 175, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.ORTHWORM, 9, false, false, false, "Earthworm Pokémon", PokemonType.STEEL, null, 2.5, 310, AbilityId.EARTH_EATER, AbilityId.NONE, AbilityId.SAND_VEIL, 480, 70, 85, 145, 60, 55, 65, 25, 50, 240, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.GLIMMET, 9, false, false, false, "Ore Pokémon", PokemonType.ROCK, PokemonType.POISON, 0.7, 8, AbilityId.TOXIC_DEBRIS, AbilityId.NONE, AbilityId.CORROSION, 350, 48, 35, 42, 105, 60, 60, 70, 50, 70, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.GLIMMORA, 9, false, false, false, "Ore Pokémon", PokemonType.ROCK, PokemonType.POISON, 1.5, 45, AbilityId.TOXIC_DEBRIS, AbilityId.NONE, AbilityId.CORROSION, 525, 83, 55, 90, 130, 81, 86, 25, 50, 184, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.GREAVARD, 9, false, false, false, "Ghost Dog Pokémon", PokemonType.GHOST, null, 0.6, 35, AbilityId.PICKUP, AbilityId.NONE, AbilityId.FLUFFY, 290, 50, 61, 60, 30, 55, 34, 120, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.HOUNDSTONE, 9, false, false, false, "Ghost Dog Pokémon", PokemonType.GHOST, null, 2, 15, AbilityId.SAND_RUSH, AbilityId.NONE, AbilityId.FLUFFY, 488, 72, 101, 100, 50, 97, 68, 60, 50, 171, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.FLAMIGO, 9, false, false, false, "Synchronize Pokémon", PokemonType.FLYING, PokemonType.FIGHTING, 1.6, 37, AbilityId.SCRAPPY, AbilityId.TANGLED_FEET, AbilityId.COSTAR, 500, 82, 115, 74, 75, 64, 90, 100, 50, 175, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.CETODDLE, 9, false, false, false, "Terra Whale Pokémon", PokemonType.ICE, null, 1.2, 45, AbilityId.THICK_FAT, AbilityId.SNOW_CLOAK, AbilityId.SHEER_FORCE, 334, 108, 68, 45, 30, 40, 43, 150, 50, 67, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.CETITAN, 9, false, false, false, "Terra Whale Pokémon", PokemonType.ICE, null, 4.5, 700, AbilityId.THICK_FAT, AbilityId.SLUSH_RUSH, AbilityId.SHEER_FORCE, 521, 170, 113, 65, 45, 55, 73, 50, 50, 182, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.VELUZA, 9, false, false, false, "Jettison Pokémon", PokemonType.WATER, PokemonType.PSYCHIC, 2.5, 90, AbilityId.MOLD_BREAKER, AbilityId.NONE, AbilityId.SHARPNESS, 478, 90, 102, 73, 78, 65, 70, 100, 50, 167, GrowthRate.FAST, 50, false), - new PokemonSpecies(SpeciesId.DONDOZO, 9, false, false, false, "Big Catfish Pokémon", PokemonType.WATER, null, 12, 220, AbilityId.UNAWARE, AbilityId.OBLIVIOUS, AbilityId.WATER_VEIL, 530, 150, 100, 115, 65, 65, 35, 25, 50, 265, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.TATSUGIRI, 9, false, false, false, "Mimicry Pokémon", PokemonType.DRAGON, PokemonType.WATER, 0.3, 8, AbilityId.COMMANDER, AbilityId.NONE, AbilityId.STORM_DRAIN, 475, 68, 50, 60, 120, 95, 82, 100, 50, 166, GrowthRate.MEDIUM_SLOW, 50, false, false, - new PokemonForm("Curly Form", "curly", PokemonType.DRAGON, PokemonType.WATER, 0.3, 8, AbilityId.COMMANDER, AbilityId.NONE, AbilityId.STORM_DRAIN, 475, 68, 50, 60, 120, 95, 82, 100, 50, 166, false, null, true), - new PokemonForm("Droopy Form", "droopy", PokemonType.DRAGON, PokemonType.WATER, 0.3, 8, AbilityId.COMMANDER, AbilityId.NONE, AbilityId.STORM_DRAIN, 475, 68, 50, 60, 120, 95, 82, 100, 50, 166, false, null, true), - new PokemonForm("Stretchy Form", "stretchy", PokemonType.DRAGON, PokemonType.WATER, 0.3, 8, AbilityId.COMMANDER, AbilityId.NONE, AbilityId.STORM_DRAIN, 475, 68, 50, 60, 120, 95, 82, 100, 50, 166, false, null, true), - ), - new PokemonSpecies(SpeciesId.ANNIHILAPE, 9, false, false, false, "Rage Monkey Pokémon", PokemonType.FIGHTING, PokemonType.GHOST, 1.2, 56, AbilityId.VITAL_SPIRIT, AbilityId.INNER_FOCUS, AbilityId.DEFIANT, 535, 110, 115, 80, 50, 90, 90, 45, 50, 268, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.CLODSIRE, 9, false, false, false, "Spiny Fish Pokémon", PokemonType.POISON, PokemonType.GROUND, 1.8, 223, AbilityId.POISON_POINT, AbilityId.WATER_ABSORB, AbilityId.UNAWARE, 430, 130, 75, 60, 45, 100, 20, 90, 50, 151, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.FARIGIRAF, 9, false, false, false, "Long Neck Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 3.2, 160, AbilityId.CUD_CHEW, AbilityId.ARMOR_TAIL, AbilityId.SAP_SIPPER, 520, 120, 90, 70, 110, 70, 60, 45, 50, 260, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.DUDUNSPARCE, 9, false, false, false, "Land Snake Pokémon", PokemonType.NORMAL, null, 3.6, 39.2, AbilityId.SERENE_GRACE, AbilityId.RUN_AWAY, AbilityId.RATTLED, 520, 125, 100, 80, 85, 75, 55, 45, 50, 182, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Two-Segment Form", "two-segment", PokemonType.NORMAL, null, 3.6, 39.2, AbilityId.SERENE_GRACE, AbilityId.RUN_AWAY, AbilityId.RATTLED, 520, 125, 100, 80, 85, 75, 55, 45, 50, 182, false, ""), - new PokemonForm("Three-Segment Form", "three-segment", PokemonType.NORMAL, null, 4.5, 47.4, AbilityId.SERENE_GRACE, AbilityId.RUN_AWAY, AbilityId.RATTLED, 520, 125, 100, 80, 85, 75, 55, 45, 50, 182), - ), - new PokemonSpecies(SpeciesId.KINGAMBIT, 9, false, false, false, "Big Blade Pokémon", PokemonType.DARK, PokemonType.STEEL, 2, 120, AbilityId.DEFIANT, AbilityId.SUPREME_OVERLORD, AbilityId.PRESSURE, 550, 100, 135, 120, 60, 85, 50, 25, 50, 275, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GREAT_TUSK, 9, false, false, false, "Paradox Pokémon", PokemonType.GROUND, PokemonType.FIGHTING, 2.2, 320, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 570, 115, 131, 131, 53, 53, 87, 30, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.SCREAM_TAIL, 9, false, false, false, "Paradox Pokémon", PokemonType.FAIRY, PokemonType.PSYCHIC, 1.2, 8, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 570, 115, 65, 99, 65, 115, 111, 50, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.BRUTE_BONNET, 9, false, false, false, "Paradox Pokémon", PokemonType.GRASS, PokemonType.DARK, 1.2, 21, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 570, 111, 127, 99, 79, 99, 55, 50, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.FLUTTER_MANE, 9, false, false, false, "Paradox Pokémon", PokemonType.GHOST, PokemonType.FAIRY, 1.4, 4, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 570, 55, 55, 55, 135, 135, 135, 30, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.SLITHER_WING, 9, false, false, false, "Paradox Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 3.2, 92, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 570, 85, 135, 79, 85, 105, 81, 30, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.SANDY_SHOCKS, 9, false, false, false, "Paradox Pokémon", PokemonType.ELECTRIC, PokemonType.GROUND, 2.3, 60, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 570, 85, 81, 97, 121, 85, 101, 30, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.IRON_TREADS, 9, false, false, false, "Paradox Pokémon", PokemonType.GROUND, PokemonType.STEEL, 0.9, 240, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 570, 90, 112, 120, 72, 70, 106, 30, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.IRON_BUNDLE, 9, false, false, false, "Paradox Pokémon", PokemonType.ICE, PokemonType.WATER, 0.6, 11, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 570, 56, 80, 114, 124, 60, 136, 50, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.IRON_HANDS, 9, false, false, false, "Paradox Pokémon", PokemonType.FIGHTING, PokemonType.ELECTRIC, 1.8, 380.7, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 570, 154, 140, 108, 50, 68, 50, 50, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.IRON_JUGULIS, 9, false, false, false, "Paradox Pokémon", PokemonType.DARK, PokemonType.FLYING, 1.3, 111, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 570, 94, 80, 86, 122, 80, 108, 30, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.IRON_MOTH, 9, false, false, false, "Paradox Pokémon", PokemonType.FIRE, PokemonType.POISON, 1.2, 36, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 570, 80, 70, 60, 140, 110, 110, 30, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.IRON_THORNS, 9, false, false, false, "Paradox Pokémon", PokemonType.ROCK, PokemonType.ELECTRIC, 1.6, 303, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 570, 100, 134, 110, 70, 84, 72, 30, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.FRIGIBAX, 9, false, false, false, "Ice Fin Pokémon", PokemonType.DRAGON, PokemonType.ICE, 0.5, 17, AbilityId.THERMAL_EXCHANGE, AbilityId.NONE, AbilityId.ICE_BODY, 320, 65, 75, 45, 35, 45, 55, 45, 50, 64, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.ARCTIBAX, 9, false, false, false, "Ice Fin Pokémon", PokemonType.DRAGON, PokemonType.ICE, 0.8, 30, AbilityId.THERMAL_EXCHANGE, AbilityId.NONE, AbilityId.ICE_BODY, 423, 90, 95, 66, 45, 65, 62, 25, 50, 148, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.BAXCALIBUR, 9, false, false, false, "Ice Dragon Pokémon", PokemonType.DRAGON, PokemonType.ICE, 2.1, 210, AbilityId.THERMAL_EXCHANGE, AbilityId.NONE, AbilityId.ICE_BODY, 600, 115, 145, 92, 75, 86, 87, 10, 50, 300, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.GIMMIGHOUL, 9, false, false, false, "Coin Chest Pokémon", PokemonType.GHOST, null, 0.3, 5, AbilityId.RATTLED, AbilityId.NONE, AbilityId.NONE, 300, 45, 30, 70, 75, 70, 10, 45, 50, 60, GrowthRate.SLOW, null, false, false, - new PokemonForm("Chest Form", "chest", PokemonType.GHOST, null, 0.3, 5, AbilityId.RATTLED, AbilityId.NONE, AbilityId.NONE, 300, 45, 30, 70, 75, 70, 10, 45, 50, 60, false, "", true), - new PokemonForm("Roaming Form", "roaming", PokemonType.GHOST, null, 0.1, 1, AbilityId.RUN_AWAY, AbilityId.NONE, AbilityId.NONE, 300, 45, 30, 25, 75, 45, 80, 45, 50, 60, false, null, true), - ), - new PokemonSpecies(SpeciesId.GHOLDENGO, 9, false, false, false, "Coin Entity Pokémon", PokemonType.STEEL, PokemonType.GHOST, 1.2, 30, AbilityId.GOOD_AS_GOLD, AbilityId.NONE, AbilityId.NONE, 550, 87, 60, 95, 133, 91, 84, 45, 50, 275, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.WO_CHIEN, 9, true, false, false, "Ruinous Pokémon", PokemonType.DARK, PokemonType.GRASS, 1.5, 74.2, AbilityId.TABLETS_OF_RUIN, AbilityId.NONE, AbilityId.NONE, 570, 85, 85, 100, 95, 135, 70, 6, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.CHIEN_PAO, 9, true, false, false, "Ruinous Pokémon", PokemonType.DARK, PokemonType.ICE, 1.9, 152.2, AbilityId.SWORD_OF_RUIN, AbilityId.NONE, AbilityId.NONE, 570, 80, 120, 80, 90, 65, 135, 6, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.TING_LU, 9, true, false, false, "Ruinous Pokémon", PokemonType.DARK, PokemonType.GROUND, 2.7, 699.7, AbilityId.VESSEL_OF_RUIN, AbilityId.NONE, AbilityId.NONE, 570, 155, 110, 125, 55, 80, 45, 6, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.CHI_YU, 9, true, false, false, "Ruinous Pokémon", PokemonType.DARK, PokemonType.FIRE, 0.4, 4.9, AbilityId.BEADS_OF_RUIN, AbilityId.NONE, AbilityId.NONE, 570, 55, 80, 80, 135, 120, 100, 6, 0, 285, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.ROARING_MOON, 9, false, false, false, "Paradox Pokémon", PokemonType.DRAGON, PokemonType.DARK, 2, 380, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 590, 105, 139, 71, 55, 101, 119, 10, 0, 295, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.IRON_VALIANT, 9, false, false, false, "Paradox Pokémon", PokemonType.FAIRY, PokemonType.FIGHTING, 1.4, 35, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 590, 74, 130, 90, 120, 60, 116, 10, 0, 295, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.KORAIDON, 9, false, true, false, "Paradox Pokémon", PokemonType.FIGHTING, PokemonType.DRAGON, 2.5, 303, AbilityId.ORICHALCUM_PULSE, AbilityId.NONE, AbilityId.NONE, 670, 100, 135, 115, 85, 100, 135, 3, 0, 335, GrowthRate.SLOW, null, false, false, - new PokemonForm("Apex Build", "apex-build", PokemonType.FIGHTING, PokemonType.DRAGON, 2.5, 303, AbilityId.ORICHALCUM_PULSE, AbilityId.NONE, AbilityId.NONE, 670, 100, 135, 115, 85, 100, 135, 3, 0, 335, false, null, true), - ), - new PokemonSpecies(SpeciesId.MIRAIDON, 9, false, true, false, "Paradox Pokémon", PokemonType.ELECTRIC, PokemonType.DRAGON, 3.5, 240, AbilityId.HADRON_ENGINE, AbilityId.NONE, AbilityId.NONE, 670, 100, 85, 100, 135, 115, 135, 3, 0, 335, GrowthRate.SLOW, null, false, false, - new PokemonForm("Ultimate Mode", "ultimate-mode", PokemonType.ELECTRIC, PokemonType.DRAGON, 3.5, 240, AbilityId.HADRON_ENGINE, AbilityId.NONE, AbilityId.NONE, 670, 100, 85, 100, 135, 115, 135, 3, 0, 335, false, null, true), - ), - new PokemonSpecies(SpeciesId.WALKING_WAKE, 9, false, false, false, "Paradox Pokémon", PokemonType.WATER, PokemonType.DRAGON, 3.5, 280, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 590, 99, 83, 91, 125, 83, 109, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Gouging Fire and Raging Bolt - new PokemonSpecies(SpeciesId.IRON_LEAVES, 9, false, false, false, "Paradox Pokémon", PokemonType.GRASS, PokemonType.PSYCHIC, 1.5, 125, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 590, 90, 130, 88, 70, 108, 104, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Iron Boulder and Iron Crown - new PokemonSpecies(SpeciesId.DIPPLIN, 9, false, false, false, "Candy Apple Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 4.4, AbilityId.SUPERSWEET_SYRUP, AbilityId.GLUTTONY, AbilityId.STICKY_HOLD, 485, 80, 80, 110, 95, 80, 40, 45, 50, 170, GrowthRate.ERRATIC, 50, false), - new PokemonSpecies(SpeciesId.POLTCHAGEIST, 9, false, false, false, "Matcha Pokémon", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, GrowthRate.SLOW, null, false, false, - new PokemonForm("Counterfeit Form", "counterfeit", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, true), - new PokemonForm("Artisan Form", "artisan", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, false, true), - ), - new PokemonSpecies(SpeciesId.SINISTCHA, 9, false, false, false, "Matcha Pokémon", PokemonType.GRASS, PokemonType.GHOST, 0.2, 2.2, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 508, 71, 60, 106, 121, 80, 70, 60, 50, 178, GrowthRate.SLOW, null, false, false, - new PokemonForm("Unremarkable Form", "unremarkable", PokemonType.GRASS, PokemonType.GHOST, 0.2, 2.2, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 508, 71, 60, 106, 121, 80, 70, 60, 50, 178), - new PokemonForm("Masterpiece Form", "masterpiece", PokemonType.GRASS, PokemonType.GHOST, 0.2, 2.2, AbilityId.HOSPITALITY, AbilityId.NONE, AbilityId.HEATPROOF, 508, 71, 60, 106, 121, 80, 70, 60, 50, 178, false, null, false, true), - ), - new PokemonSpecies(SpeciesId.OKIDOGI, 9, true, false, false, "Retainer Pokémon", PokemonType.POISON, PokemonType.FIGHTING, 1.8, 92.2, AbilityId.TOXIC_CHAIN, AbilityId.NONE, AbilityId.GUARD_DOG, 555, 88, 128, 115, 58, 86, 80, 3, 0, 276, GrowthRate.SLOW, 100, false), - new PokemonSpecies(SpeciesId.MUNKIDORI, 9, true, false, false, "Retainer Pokémon", PokemonType.POISON, PokemonType.PSYCHIC, 1, 12.2, AbilityId.TOXIC_CHAIN, AbilityId.NONE, AbilityId.FRISK, 555, 88, 75, 66, 130, 90, 106, 3, 0, 276, GrowthRate.SLOW, 100, false), - new PokemonSpecies(SpeciesId.FEZANDIPITI, 9, true, false, false, "Retainer Pokémon", PokemonType.POISON, PokemonType.FAIRY, 1.4, 30.1, AbilityId.TOXIC_CHAIN, AbilityId.NONE, AbilityId.TECHNICIAN, 555, 88, 91, 82, 70, 125, 99, 3, 0, 276, GrowthRate.SLOW, 100, false), - new PokemonSpecies(SpeciesId.OGERPON, 9, true, false, false, "Mask Pokémon", PokemonType.GRASS, null, 1.2, 39.8, AbilityId.DEFIANT, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275, GrowthRate.SLOW, 0, false, false, - new PokemonForm("Teal Mask", "teal-mask", PokemonType.GRASS, null, 1.2, 39.8, AbilityId.DEFIANT, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275, false, null, true), - new PokemonForm("Wellspring Mask", "wellspring-mask", PokemonType.GRASS, PokemonType.WATER, 1.2, 39.8, AbilityId.WATER_ABSORB, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275), - new PokemonForm("Hearthflame Mask", "hearthflame-mask", PokemonType.GRASS, PokemonType.FIRE, 1.2, 39.8, AbilityId.MOLD_BREAKER, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275), - new PokemonForm("Cornerstone Mask", "cornerstone-mask", PokemonType.GRASS, PokemonType.ROCK, 1.2, 39.8, AbilityId.STURDY, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275), - new PokemonForm("Teal Mask Terastallized", "teal-mask-tera", PokemonType.GRASS, null, 1.2, 39.8, AbilityId.EMBODY_ASPECT_TEAL, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275), - new PokemonForm("Wellspring Mask Terastallized", "wellspring-mask-tera", PokemonType.GRASS, PokemonType.WATER, 1.2, 39.8, AbilityId.EMBODY_ASPECT_WELLSPRING, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275), - new PokemonForm("Hearthflame Mask Terastallized", "hearthflame-mask-tera", PokemonType.GRASS, PokemonType.FIRE, 1.2, 39.8, AbilityId.EMBODY_ASPECT_HEARTHFLAME, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275), - new PokemonForm("Cornerstone Mask Terastallized", "cornerstone-mask-tera", PokemonType.GRASS, PokemonType.ROCK, 1.2, 39.8, AbilityId.EMBODY_ASPECT_CORNERSTONE, AbilityId.NONE, AbilityId.NONE, 550, 80, 120, 84, 60, 96, 110, 5, 50, 275), - ), - new PokemonSpecies(SpeciesId.ARCHALUDON, 9, false, false, false, "Alloy Pokémon", PokemonType.STEEL, PokemonType.DRAGON, 2, 60, AbilityId.STAMINA, AbilityId.STURDY, AbilityId.STALWART, 600, 90, 105, 130, 125, 65, 85, 10, 50, 300, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.HYDRAPPLE, 9, false, false, false, "Apple Hydra Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 1.8, 93, AbilityId.SUPERSWEET_SYRUP, AbilityId.REGENERATOR, AbilityId.STICKY_HOLD, 540, 106, 80, 110, 120, 80, 44, 10, 50, 270, GrowthRate.ERRATIC, 50, false), - new PokemonSpecies(SpeciesId.GOUGING_FIRE, 9, false, false, false, "Paradox Pokémon", PokemonType.FIRE, PokemonType.DRAGON, 3.5, 590, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 590, 105, 115, 121, 65, 93, 91, 10, 0, 295, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.RAGING_BOLT, 9, false, false, false, "Paradox Pokémon", PokemonType.ELECTRIC, PokemonType.DRAGON, 5.2, 480, AbilityId.PROTOSYNTHESIS, AbilityId.NONE, AbilityId.NONE, 590, 125, 73, 91, 137, 89, 75, 10, 0, 295, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.IRON_BOULDER, 9, false, false, false, "Paradox Pokémon", PokemonType.ROCK, PokemonType.PSYCHIC, 1.5, 162.5, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 590, 90, 120, 80, 68, 108, 124, 10, 0, 295, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.IRON_CROWN, 9, false, false, false, "Paradox Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 1.6, 156, AbilityId.QUARK_DRIVE, AbilityId.NONE, AbilityId.NONE, 590, 90, 72, 100, 122, 108, 98, 10, 0, 295, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.TERAPAGOS, 9, false, true, false, "Tera Pokémon", PokemonType.NORMAL, null, 0.2, 6.5, AbilityId.TERA_SHIFT, AbilityId.NONE, AbilityId.NONE, 450, 90, 65, 85, 65, 85, 60, 5, 50, 90, GrowthRate.SLOW, 50, false, false, - new PokemonForm("Normal Form", "", PokemonType.NORMAL, null, 0.2, 6.5, AbilityId.TERA_SHIFT, AbilityId.NONE, AbilityId.NONE, 450, 90, 65, 85, 65, 85, 60, 5, 50, 90, false, null, true), - new PokemonForm("Terastal Form", "terastal", PokemonType.NORMAL, null, 0.3, 16, AbilityId.TERA_SHELL, AbilityId.NONE, AbilityId.NONE, 600, 95, 95, 110, 105, 110, 85, 5, 50, 120), - new PokemonForm("Stellar Form", "stellar", PokemonType.NORMAL, null, 1.7, 77, AbilityId.TERAFORM_ZERO, AbilityId.NONE, AbilityId.NONE, 700, 160, 105, 110, 130, 110, 85, 5, 50, 140), - ), - new PokemonSpecies(SpeciesId.PECHARUNT, 9, false, false, true, "Subjugation Pokémon", PokemonType.POISON, PokemonType.GHOST, 0.3, 0.3, AbilityId.POISON_PUPPETEER, AbilityId.NONE, AbilityId.NONE, 600, 88, 88, 160, 88, 88, 88, 3, 0, 300, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.ALOLA_RATTATA, 7, false, false, false, "Mouse Pokémon", PokemonType.DARK, PokemonType.NORMAL, 0.3, 3.8, AbilityId.GLUTTONY, AbilityId.HUSTLE, AbilityId.THICK_FAT, 253, 30, 56, 35, 25, 35, 72, 255, 70, 51, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_RATICATE, 7, false, false, false, "Mouse Pokémon", PokemonType.DARK, PokemonType.NORMAL, 0.7, 25.5, AbilityId.GLUTTONY, AbilityId.HUSTLE, AbilityId.THICK_FAT, 413, 75, 71, 70, 40, 80, 77, 127, 70, 145, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_RAICHU, 7, false, false, false, "Mouse Pokémon", PokemonType.ELECTRIC, PokemonType.PSYCHIC, 0.7, 21, AbilityId.SURGE_SURFER, AbilityId.NONE, AbilityId.NONE, 485, 60, 85, 50, 95, 85, 110, 75, 50, 243, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_SANDSHREW, 7, false, false, false, "Mouse Pokémon", PokemonType.ICE, PokemonType.STEEL, 0.7, 40, AbilityId.SNOW_CLOAK, AbilityId.NONE, AbilityId.SLUSH_RUSH, 300, 50, 75, 90, 10, 35, 40, 255, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_SANDSLASH, 7, false, false, false, "Mouse Pokémon", PokemonType.ICE, PokemonType.STEEL, 1.2, 55, AbilityId.SNOW_CLOAK, AbilityId.NONE, AbilityId.SLUSH_RUSH, 450, 75, 100, 120, 25, 65, 65, 90, 50, 158, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_VULPIX, 7, false, false, false, "Fox Pokémon", PokemonType.ICE, null, 0.6, 9.9, AbilityId.SNOW_CLOAK, AbilityId.NONE, AbilityId.SNOW_WARNING, 299, 38, 41, 40, 50, 65, 65, 190, 50, 60, GrowthRate.MEDIUM_FAST, 25, false), - new PokemonSpecies(SpeciesId.ALOLA_NINETALES, 7, false, false, false, "Fox Pokémon", PokemonType.ICE, PokemonType.FAIRY, 1.1, 19.9, AbilityId.SNOW_CLOAK, AbilityId.NONE, AbilityId.SNOW_WARNING, 505, 73, 67, 75, 81, 100, 109, 75, 50, 177, GrowthRate.MEDIUM_FAST, 25, false), - new PokemonSpecies(SpeciesId.ALOLA_DIGLETT, 7, false, false, false, "Mole Pokémon", PokemonType.GROUND, PokemonType.STEEL, 0.2, 1, AbilityId.SAND_VEIL, AbilityId.TANGLING_HAIR, AbilityId.SAND_FORCE, 265, 10, 55, 30, 35, 45, 90, 255, 50, 53, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_DUGTRIO, 7, false, false, false, "Mole Pokémon", PokemonType.GROUND, PokemonType.STEEL, 0.7, 66.6, AbilityId.SAND_VEIL, AbilityId.TANGLING_HAIR, AbilityId.SAND_FORCE, 425, 35, 100, 60, 50, 70, 110, 50, 50, 149, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_MEOWTH, 7, false, false, false, "Scratch Cat Pokémon", PokemonType.DARK, null, 0.4, 4.2, AbilityId.PICKUP, AbilityId.TECHNICIAN, AbilityId.RATTLED, 290, 40, 35, 35, 50, 40, 90, 255, 50, 58, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_PERSIAN, 7, false, false, false, "Classy Cat Pokémon", PokemonType.DARK, null, 1.1, 33, AbilityId.FUR_COAT, AbilityId.TECHNICIAN, AbilityId.RATTLED, 440, 65, 60, 60, 75, 65, 115, 90, 50, 154, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_GEODUDE, 7, false, false, false, "Rock Pokémon", PokemonType.ROCK, PokemonType.ELECTRIC, 0.4, 20.3, AbilityId.MAGNET_PULL, AbilityId.STURDY, AbilityId.GALVANIZE, 300, 40, 80, 100, 30, 30, 20, 255, 70, 60, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_GRAVELER, 7, false, false, false, "Rock Pokémon", PokemonType.ROCK, PokemonType.ELECTRIC, 1, 110, AbilityId.MAGNET_PULL, AbilityId.STURDY, AbilityId.GALVANIZE, 390, 55, 95, 115, 45, 45, 35, 120, 70, 137, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_GOLEM, 7, false, false, false, "Megaton Pokémon", PokemonType.ROCK, PokemonType.ELECTRIC, 1.7, 316, AbilityId.MAGNET_PULL, AbilityId.STURDY, AbilityId.GALVANIZE, 495, 80, 120, 130, 55, 65, 45, 45, 70, 223, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_GRIMER, 7, false, false, false, "Sludge Pokémon", PokemonType.POISON, PokemonType.DARK, 0.7, 42, AbilityId.POISON_TOUCH, AbilityId.GLUTTONY, AbilityId.POWER_OF_ALCHEMY, 325, 80, 80, 50, 40, 50, 25, 190, 70, 65, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_MUK, 7, false, false, false, "Sludge Pokémon", PokemonType.POISON, PokemonType.DARK, 1, 52, AbilityId.POISON_TOUCH, AbilityId.GLUTTONY, AbilityId.POWER_OF_ALCHEMY, 500, 105, 105, 75, 65, 100, 50, 75, 70, 175, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_EXEGGUTOR, 7, false, false, false, "Coconut Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 10.9, 415.6, AbilityId.FRISK, AbilityId.NONE, AbilityId.HARVEST, 530, 95, 105, 85, 125, 75, 45, 45, 50, 186, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.ALOLA_MAROWAK, 7, false, false, false, "Bone Keeper Pokémon", PokemonType.FIRE, PokemonType.GHOST, 1, 34, AbilityId.CURSED_BODY, AbilityId.LIGHTNING_ROD, AbilityId.ROCK_HEAD, 425, 60, 80, 110, 50, 80, 45, 75, 50, 149, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.ETERNAL_FLOETTE, 6, true, false, false, "Single Bloom Pokémon", PokemonType.FAIRY, null, 0.2, 0.9, AbilityId.FLOWER_VEIL, AbilityId.NONE, AbilityId.SYMBIOSIS, 551, 74, 65, 67, 125, 128, 92, 120, 70, 243, GrowthRate.MEDIUM_FAST, 0, false), //Marked as Sub-Legend, for casing purposes - new PokemonSpecies(SpeciesId.GALAR_MEOWTH, 8, false, false, false, "Scratch Cat Pokémon", PokemonType.STEEL, null, 0.4, 7.5, AbilityId.PICKUP, AbilityId.TOUGH_CLAWS, AbilityId.UNNERVE, 290, 50, 65, 55, 40, 40, 40, 255, 50, 58, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GALAR_PONYTA, 8, false, false, false, "Fire Horse Pokémon", PokemonType.PSYCHIC, null, 0.8, 24, AbilityId.RUN_AWAY, AbilityId.PASTEL_VEIL, AbilityId.ANTICIPATION, 410, 50, 85, 55, 65, 65, 90, 190, 50, 82, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GALAR_RAPIDASH, 8, false, false, false, "Fire Horse Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.7, 80, AbilityId.RUN_AWAY, AbilityId.PASTEL_VEIL, AbilityId.ANTICIPATION, 500, 65, 100, 70, 80, 80, 105, 60, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GALAR_SLOWPOKE, 8, false, false, false, "Dopey Pokémon", PokemonType.PSYCHIC, null, 1.2, 36, AbilityId.GLUTTONY, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 315, 90, 65, 65, 40, 40, 15, 190, 50, 63, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GALAR_SLOWBRO, 8, false, false, false, "Hermit Crab Pokémon", PokemonType.POISON, PokemonType.PSYCHIC, 1.6, 70.5, AbilityId.QUICK_DRAW, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 490, 95, 100, 95, 100, 70, 30, 75, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GALAR_FARFETCHD, 8, false, false, false, "Wild Duck Pokémon", PokemonType.FIGHTING, null, 0.8, 42, AbilityId.STEADFAST, AbilityId.NONE, AbilityId.SCRAPPY, 377, 52, 95, 55, 58, 62, 55, 45, 50, 132, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GALAR_WEEZING, 8, false, false, false, "Poison Gas Pokémon", PokemonType.POISON, PokemonType.FAIRY, 3, 16, AbilityId.LEVITATE, AbilityId.NEUTRALIZING_GAS, AbilityId.MISTY_SURGE, 490, 65, 90, 120, 85, 70, 60, 60, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GALAR_MR_MIME, 8, false, false, false, "Barrier Pokémon", PokemonType.ICE, PokemonType.PSYCHIC, 1.4, 56.8, AbilityId.VITAL_SPIRIT, AbilityId.SCREEN_CLEANER, AbilityId.ICE_BODY, 460, 50, 65, 65, 90, 90, 100, 45, 50, 161, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GALAR_ARTICUNO, 8, true, false, false, "Freeze Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 1.7, 50.9, AbilityId.COMPETITIVE, AbilityId.NONE, AbilityId.NONE, 580, 90, 85, 85, 125, 100, 95, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.GALAR_ZAPDOS, 8, true, false, false, "Electric Pokémon", PokemonType.FIGHTING, PokemonType.FLYING, 1.6, 58.2, AbilityId.DEFIANT, AbilityId.NONE, AbilityId.NONE, 580, 90, 125, 90, 85, 90, 100, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.GALAR_MOLTRES, 8, true, false, false, "Flame Pokémon", PokemonType.DARK, PokemonType.FLYING, 2, 66, AbilityId.BERSERK, AbilityId.NONE, AbilityId.NONE, 580, 90, 85, 90, 100, 125, 90, 3, 35, 290, GrowthRate.SLOW, null, false), - new PokemonSpecies(SpeciesId.GALAR_SLOWKING, 8, false, false, false, "Royal Pokémon", PokemonType.POISON, PokemonType.PSYCHIC, 1.8, 79.5, AbilityId.CURIOUS_MEDICINE, AbilityId.OWN_TEMPO, AbilityId.REGENERATOR, 490, 95, 65, 80, 110, 110, 30, 70, 50, 172, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GALAR_CORSOLA, 8, false, false, false, "Coral Pokémon", PokemonType.GHOST, null, 0.6, 0.5, AbilityId.WEAK_ARMOR, AbilityId.NONE, AbilityId.CURSED_BODY, 410, 60, 55, 100, 65, 100, 30, 60, 50, 144, GrowthRate.FAST, 25, false), - new PokemonSpecies(SpeciesId.GALAR_ZIGZAGOON, 8, false, false, false, "Tiny Raccoon Pokémon", PokemonType.DARK, PokemonType.NORMAL, 0.4, 17.5, AbilityId.PICKUP, AbilityId.GLUTTONY, AbilityId.QUICK_FEET, 240, 38, 30, 41, 30, 41, 60, 255, 50, 56, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GALAR_LINOONE, 8, false, false, false, "Rushing Pokémon", PokemonType.DARK, PokemonType.NORMAL, 0.5, 32.5, AbilityId.PICKUP, AbilityId.GLUTTONY, AbilityId.QUICK_FEET, 420, 78, 70, 61, 50, 61, 100, 90, 50, 147, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GALAR_DARUMAKA, 8, false, false, false, "Zen Charm Pokémon", PokemonType.ICE, null, 0.7, 40, AbilityId.HUSTLE, AbilityId.NONE, AbilityId.INNER_FOCUS, 315, 70, 90, 45, 15, 45, 50, 120, 50, 63, GrowthRate.MEDIUM_SLOW, 50, false), - new PokemonSpecies(SpeciesId.GALAR_DARMANITAN, 8, false, false, false, "Blazing Pokémon", PokemonType.ICE, null, 1.7, 120, AbilityId.GORILLA_TACTICS, AbilityId.NONE, AbilityId.ZEN_MODE, 480, 105, 140, 55, 30, 55, 95, 60, 50, 168, GrowthRate.MEDIUM_SLOW, 50, false, true, - new PokemonForm("Standard Mode", "", PokemonType.ICE, null, 1.7, 120, AbilityId.GORILLA_TACTICS, AbilityId.NONE, AbilityId.ZEN_MODE, 480, 105, 140, 55, 30, 55, 95, 60, 50, 168, false, null, true), - new PokemonForm("Zen Mode", "zen", PokemonType.ICE, PokemonType.FIRE, 1.7, 120, AbilityId.GORILLA_TACTICS, AbilityId.NONE, AbilityId.ZEN_MODE, 540, 105, 160, 55, 30, 55, 135, 60, 50, 189), - ), - new PokemonSpecies(SpeciesId.GALAR_YAMASK, 8, false, false, false, "Spirit Pokémon", PokemonType.GROUND, PokemonType.GHOST, 0.5, 1.5, AbilityId.WANDERING_SPIRIT, AbilityId.NONE, AbilityId.NONE, 303, 38, 55, 85, 30, 65, 30, 190, 50, 61, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.GALAR_STUNFISK, 8, false, false, false, "Trap Pokémon", PokemonType.GROUND, PokemonType.STEEL, 0.7, 20.5, AbilityId.MIMICRY, AbilityId.NONE, AbilityId.NONE, 471, 109, 81, 99, 66, 84, 32, 75, 70, 165, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.HISUI_GROWLITHE, 8, false, false, false, "Puppy Pokémon", PokemonType.FIRE, PokemonType.ROCK, 0.8, 22.7, AbilityId.INTIMIDATE, AbilityId.FLASH_FIRE, AbilityId.ROCK_HEAD, 350, 60, 75, 45, 65, 50, 55, 190, 50, 70, GrowthRate.SLOW, 75, false), - new PokemonSpecies(SpeciesId.HISUI_ARCANINE, 8, false, false, false, "Legendary Pokémon", PokemonType.FIRE, PokemonType.ROCK, 2, 168, AbilityId.INTIMIDATE, AbilityId.FLASH_FIRE, AbilityId.ROCK_HEAD, 555, 95, 115, 80, 95, 80, 90, 85, 50, 194, GrowthRate.SLOW, 75, false), - new PokemonSpecies(SpeciesId.HISUI_VOLTORB, 8, false, false, false, "Ball Pokémon", PokemonType.ELECTRIC, PokemonType.GRASS, 0.5, 13, AbilityId.SOUNDPROOF, AbilityId.STATIC, AbilityId.AFTERMATH, 330, 40, 30, 50, 55, 55, 100, 190, 80, 66, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.HISUI_ELECTRODE, 8, false, false, false, "Ball Pokémon", PokemonType.ELECTRIC, PokemonType.GRASS, 1.2, 81, AbilityId.SOUNDPROOF, AbilityId.STATIC, AbilityId.AFTERMATH, 490, 60, 50, 70, 80, 80, 150, 60, 70, 172, GrowthRate.MEDIUM_FAST, null, false), - new PokemonSpecies(SpeciesId.HISUI_TYPHLOSION, 8, false, false, false, "Volcano Pokémon", PokemonType.FIRE, PokemonType.GHOST, 1.6, 69.8, AbilityId.BLAZE, AbilityId.NONE, AbilityId.FRISK, 534, 73, 84, 78, 119, 85, 95, 45, 70, 240, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.HISUI_QWILFISH, 8, false, false, false, "Balloon Pokémon", PokemonType.DARK, PokemonType.POISON, 0.5, 3.9, AbilityId.POISON_POINT, AbilityId.SWIFT_SWIM, AbilityId.INTIMIDATE, 440, 65, 95, 85, 55, 55, 85, 45, 50, 88, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.HISUI_SNEASEL, 8, false, false, false, "Sharp Claw Pokémon", PokemonType.FIGHTING, PokemonType.POISON, 0.9, 27, AbilityId.INNER_FOCUS, AbilityId.KEEN_EYE, AbilityId.PICKPOCKET, 430, 55, 95, 55, 35, 75, 115, 60, 35, 86, GrowthRate.MEDIUM_SLOW, 50, true), - new PokemonSpecies(SpeciesId.HISUI_SAMUROTT, 8, false, false, false, "Formidable Pokémon", PokemonType.WATER, PokemonType.DARK, 1.5, 58.2, AbilityId.TORRENT, AbilityId.NONE, AbilityId.SHARPNESS, 528, 90, 108, 80, 100, 65, 85, 45, 80, 238, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.HISUI_LILLIGANT, 8, false, false, false, "Flowering Pokémon", PokemonType.GRASS, PokemonType.FIGHTING, 1.2, 19.2, AbilityId.CHLOROPHYLL, AbilityId.HUSTLE, AbilityId.LEAF_GUARD, 480, 70, 105, 75, 50, 75, 105, 75, 50, 168, GrowthRate.MEDIUM_FAST, 0, false), - new PokemonSpecies(SpeciesId.HISUI_ZORUA, 8, false, false, false, "Tricky Fox Pokémon", PokemonType.NORMAL, PokemonType.GHOST, 0.7, 12.5, AbilityId.ILLUSION, AbilityId.NONE, AbilityId.NONE, 330, 35, 60, 40, 85, 40, 70, 75, 50, 66, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.HISUI_ZOROARK, 8, false, false, false, "Illusion Fox Pokémon", PokemonType.NORMAL, PokemonType.GHOST, 1.6, 83, AbilityId.ILLUSION, AbilityId.NONE, AbilityId.NONE, 510, 55, 100, 60, 125, 60, 110, 45, 50, 179, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.HISUI_BRAVIARY, 8, false, false, false, "Valiant Pokémon", PokemonType.PSYCHIC, PokemonType.FLYING, 1.7, 43.4, AbilityId.KEEN_EYE, AbilityId.SHEER_FORCE, AbilityId.TINTED_LENS, 510, 110, 83, 70, 112, 70, 65, 60, 50, 179, GrowthRate.SLOW, 100, false), - new PokemonSpecies(SpeciesId.HISUI_SLIGGOO, 8, false, false, false, "Soft Tissue Pokémon", PokemonType.STEEL, PokemonType.DRAGON, 0.7, 68.5, AbilityId.SAP_SIPPER, AbilityId.SHELL_ARMOR, AbilityId.GOOEY, 452, 58, 75, 83, 83, 113, 40, 45, 35, 158, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.HISUI_GOODRA, 8, false, false, false, "Dragon Pokémon", PokemonType.STEEL, PokemonType.DRAGON, 1.7, 334.1, AbilityId.SAP_SIPPER, AbilityId.SHELL_ARMOR, AbilityId.GOOEY, 600, 80, 100, 100, 110, 150, 60, 45, 35, 270, GrowthRate.SLOW, 50, false), - new PokemonSpecies(SpeciesId.HISUI_AVALUGG, 8, false, false, false, "Iceberg Pokémon", PokemonType.ICE, PokemonType.ROCK, 1.4, 262.4, AbilityId.STRONG_JAW, AbilityId.ICE_BODY, AbilityId.STURDY, 514, 95, 127, 184, 34, 36, 38, 55, 50, 180, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.HISUI_DECIDUEYE, 8, false, false, false, "Arrow Quill Pokémon", PokemonType.GRASS, PokemonType.FIGHTING, 1.6, 37, AbilityId.OVERGROW, AbilityId.NONE, AbilityId.SCRAPPY, 530, 88, 112, 80, 95, 95, 60, 45, 50, 239, GrowthRate.MEDIUM_SLOW, 87.5, false), - new PokemonSpecies(SpeciesId.PALDEA_TAUROS, 9, false, false, false, "Wild Bull Pokémon", PokemonType.FIGHTING, null, 1.4, 115, AbilityId.INTIMIDATE, AbilityId.ANGER_POINT, AbilityId.CUD_CHEW, 490, 75, 110, 105, 30, 70, 100, 45, 50, 172, GrowthRate.SLOW, 100, false, false, - new PokemonForm("Combat Breed", "combat", PokemonType.FIGHTING, null, 1.4, 115, AbilityId.INTIMIDATE, AbilityId.ANGER_POINT, AbilityId.CUD_CHEW, 490, 75, 110, 105, 30, 70, 100, 45, 50, 172, false, "", true), - new PokemonForm("Blaze Breed", "blaze", PokemonType.FIGHTING, PokemonType.FIRE, 1.4, 85, AbilityId.INTIMIDATE, AbilityId.ANGER_POINT, AbilityId.CUD_CHEW, 490, 75, 110, 105, 30, 70, 100, 45, 50, 172, false, null, true), - new PokemonForm("Aqua Breed", "aqua", PokemonType.FIGHTING, PokemonType.WATER, 1.4, 110, AbilityId.INTIMIDATE, AbilityId.ANGER_POINT, AbilityId.CUD_CHEW, 490, 75, 110, 105, 30, 70, 100, 45, 50, 172, false, null, true), - ), - new PokemonSpecies(SpeciesId.PALDEA_WOOPER, 9, false, false, false, "Water Fish Pokémon", PokemonType.POISON, PokemonType.GROUND, 0.4, 11, AbilityId.POISON_POINT, AbilityId.WATER_ABSORB, AbilityId.UNAWARE, 210, 55, 45, 45, 25, 25, 15, 255, 50, 42, GrowthRate.MEDIUM_FAST, 50, false), - new PokemonSpecies(SpeciesId.BLOODMOON_URSALUNA, 9, true, false, false, "Peat Pokémon", PokemonType.GROUND, PokemonType.NORMAL, 2.7, 333, AbilityId.MINDS_EYE, AbilityId.NONE, AbilityId.NONE, 555, 113, 70, 120, 135, 65, 52, 75, 50, 278, GrowthRate.MEDIUM_FAST, 50, false), //Marked as Sub-Legend, for casing purposes - ); -} diff --git a/src/data/pokemon/pokemon-data.ts b/src/data/pokemon/pokemon-data.ts index 6ae86bed5e7..0eabb0d354a 100644 --- a/src/data/pokemon/pokemon-data.ts +++ b/src/data/pokemon/pokemon-data.ts @@ -1,4 +1,6 @@ -import { type BattlerTag, loadBattlerTag } from "#data/battler-tags"; +import type { BattlerTag } from "#data/battler-tags"; +import { loadBattlerTag, SerializableBattlerTag } from "#data/battler-tags"; +import { allSpecies } from "#data/data-lists"; import type { Gender } from "#data/gender"; import { PokemonMove } from "#data/moves/pokemon-move"; import type { PokemonSpeciesForm } from "#data/pokemon-species"; @@ -8,10 +10,21 @@ import type { BerryType } from "#enums/berry-type"; import type { MoveId } from "#enums/move-id"; import type { Nature } from "#enums/nature"; import type { PokemonType } from "#enums/pokemon-type"; +import type { SpeciesId } from "#enums/species-id"; import type { AttackMoveResult } from "#types/attack-move-result"; import type { IllusionData } from "#types/illusion-data"; import type { TurnMove } from "#types/turn-move"; +import type { CoerceNullPropertiesToUndefined } from "#types/type-helpers"; import { isNullOrUndefined } from "#utils/common"; +import { getPokemonSpeciesForm } from "#utils/pokemon-utils"; + +/** + * The type that {@linkcode PokemonSpeciesForm} is converted to when an object containing it serializes it. + */ +type SerializedSpeciesForm = { + id: SpeciesId; + formIdx: number; +}; /** * Permanent data that can customize a Pokemon in non-standard ways from its Species. @@ -41,9 +54,59 @@ export class CustomPokemonData { } } +/** + * Deserialize a pokemon species form from an object containing `id` and `formIdx` properties. + * @param value - The value to deserialize + * @returns The `PokemonSpeciesForm` or `null` if the fields could not be properly discerned + */ +function deserializePokemonSpeciesForm(value: SerializedSpeciesForm | PokemonSpeciesForm): PokemonSpeciesForm | null { + // @ts-expect-error: We may be deserializing a PokemonSpeciesForm, but we catch later on + let { id, formIdx } = value; + + if (isNullOrUndefined(id) || isNullOrUndefined(formIdx)) { + // @ts-expect-error: Typescript doesn't know that in block, `value` must be a PokemonSpeciesForm + id = value.speciesId; + // @ts-expect-error: Same as above (plus we are accessing a protected property) + formIdx = value._formIndex; + } + // If for some reason either of these fields are null/undefined, we cannot reconstruct the species form + if (isNullOrUndefined(id) || isNullOrUndefined(formIdx)) { + return null; + } + return getPokemonSpeciesForm(id, formIdx); +} + +interface SerializedIllusionData extends Omit { + /** The id of the illusioned fusion species, or `undefined` if not a fusion */ + fusionSpecies?: SpeciesId; +} + +interface SerializedPokemonSummonData { + statStages: number[]; + moveQueue: TurnMove[]; + tags: BattlerTag[]; + abilitySuppressed: boolean; + speciesForm?: SerializedSpeciesForm; + fusionSpeciesForm?: SerializedSpeciesForm; + ability?: AbilityId; + passiveAbility?: AbilityId; + gender?: Gender; + fusionGender?: Gender; + stats: number[]; + moveset?: PokemonMove[]; + types: PokemonType[]; + addedType?: PokemonType; + illusion?: SerializedIllusionData; + illusionBroken: boolean; + berriesEatenLast: BerryType[]; + moveHistory: TurnMove[]; +} + /** * Persistent in-battle data for a {@linkcode Pokemon}. * Resets on switch or new battle. + * + * @sealed */ export class PokemonSummonData { /** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */ @@ -86,7 +149,7 @@ export class PokemonSummonData { */ public moveHistory: TurnMove[] = []; - constructor(source?: PokemonSummonData | Partial) { + constructor(source?: PokemonSummonData | SerializedPokemonSummonData) { if (isNullOrUndefined(source)) { return; } @@ -97,19 +160,88 @@ export class PokemonSummonData { continue; } + if (key === "speciesForm" || key === "fusionSpeciesForm") { + this[key] = deserializePokemonSpeciesForm(value); + } + + if (key === "illusion" && typeof value === "object") { + // Make a copy so as not to mutate provided value + const illusionData = { + ...value, + }; + if (!isNullOrUndefined(illusionData.fusionSpecies)) { + switch (typeof illusionData.fusionSpecies) { + case "object": + illusionData.fusionSpecies = allSpecies[illusionData.fusionSpecies.speciesId]; + break; + case "number": + illusionData.fusionSpecies = allSpecies[illusionData.fusionSpecies]; + break; + default: + illusionData.fusionSpecies = undefined; + } + } + this[key] = illusionData as IllusionData; + } + if (key === "moveset") { this.moveset = value?.map((m: any) => PokemonMove.loadMove(m)); continue; } - if (key === "tags") { - // load battler tags - this.tags = value.map((t: BattlerTag) => loadBattlerTag(t)); + if (key === "tags" && Array.isArray(value)) { + // load battler tags, discarding any that are not serializable + this.tags = value + .map((t: SerializableBattlerTag) => loadBattlerTag(t)) + .filter((t): t is SerializableBattlerTag => t instanceof SerializableBattlerTag); continue; } this[key] = value; } } + + /** + * Serialize this PokemonSummonData to JSON, converting {@linkcode PokemonSpeciesForm} and {@linkcode IllusionData.fusionSpecies} + * into simpler types instead of serializing all of their fields. + * + * @remarks + * - `IllusionData.fusionSpecies` is serialized as just the species ID + * - `PokemonSpeciesForm` and `PokemonSpeciesForm.fusionSpeciesForm` are converted into {@linkcode SerializedSpeciesForm} objects + */ + public toJSON(): SerializedPokemonSummonData { + // Pokemon species forms are never saved, only the species ID. + const illusion = this.illusion; + const speciesForm = this.speciesForm; + const fusionSpeciesForm = this.fusionSpeciesForm; + const illusionSpeciesForm = illusion?.fusionSpecies; + const t = { + // the "as omit" is required to avoid TS resolving the overwritten properties to "never" + // We coerce null to undefined in the type, as the for loop below replaces `null` with `undefined` + ...(this as Omit< + CoerceNullPropertiesToUndefined, + "speciesForm" | "fusionSpeciesForm" | "illusion" + >), + speciesForm: isNullOrUndefined(speciesForm) + ? undefined + : { id: speciesForm.speciesId, formIdx: speciesForm.formIndex }, + fusionSpeciesForm: isNullOrUndefined(fusionSpeciesForm) + ? undefined + : { id: fusionSpeciesForm.speciesId, formIdx: fusionSpeciesForm.formIndex }, + illusion: isNullOrUndefined(illusion) + ? undefined + : { + ...(this.illusion as Omit), + fusionSpecies: illusionSpeciesForm?.speciesId, + }, + }; + // Replace `null` with `undefined`, as `undefined` never gets serialized + for (const [key, value] of Object.entries(t)) { + if (value === null) { + t[key] = undefined; + } + } + return t; + } } // TODO: Merge this inside `summmonData` but exclude from save if/when a save data serializer is added diff --git a/src/data/positional-tags/load-positional-tag.ts b/src/data/positional-tags/load-positional-tag.ts new file mode 100644 index 00000000000..ef3609d93e7 --- /dev/null +++ b/src/data/positional-tags/load-positional-tag.ts @@ -0,0 +1,70 @@ +import { DelayedAttackTag, type PositionalTag, WishTag } from "#data/positional-tags/positional-tag"; +import { PositionalTagType } from "#enums/positional-tag-type"; +import type { ObjectValues } from "#types/type-helpers"; +import type { Constructor } from "#utils/common"; + +/** + * Load the attributes of a {@linkcode PositionalTag}. + * @param tagType - The {@linkcode PositionalTagType} to create + * @param args - The arguments needed to instantize the given tag + * @returns The newly created tag. + * @remarks + * This function does not perform any checking if the added tag is valid. + */ +export function loadPositionalTag({ + tagType, + ...args +}: serializedPosTagMap[T]): posTagInstanceMap[T]; +/** + * Load the attributes of a {@linkcode PositionalTag}. + * @param tag - The {@linkcode SerializedPositionalTag} to instantiate + * @returns The newly created tag. + * @remarks + * This function does not perform any checking if the added tag is valid. + */ +export function loadPositionalTag(tag: SerializedPositionalTag): PositionalTag; +export function loadPositionalTag({ + tagType, + ...rest +}: serializedPosTagMap[T]): posTagInstanceMap[T] { + // Note: We need 2 type assertions here: + // 1 because TS doesn't narrow the type of TagClass correctly based on `T`. + // It converts it into `new (DelayedAttackTag | WishTag) => DelayedAttackTag & WishTag` + const tagClass = posTagConstructorMap[tagType] as new (args: posTagParamMap[T]) => posTagInstanceMap[T]; + // 2 because TS doesn't narrow the type of `rest` correctly + // (from `Omit into `posTagParamMap[T]`) + return new tagClass(rest as unknown as posTagParamMap[T]); +} + +/** Const object mapping tag types to their constructors. */ +const posTagConstructorMap = Object.freeze({ + [PositionalTagType.DELAYED_ATTACK]: DelayedAttackTag, + [PositionalTagType.WISH]: WishTag, +}) satisfies { + // NB: This `satisfies` block ensures that all tag types have corresponding entries in the map. + [k in PositionalTagType]: Constructor; +}; + +/** Type mapping positional tag types to their constructors. */ +type posTagMap = typeof posTagConstructorMap; + +/** Type mapping all positional tag types to their instances. */ +type posTagInstanceMap = { + [k in PositionalTagType]: InstanceType; +}; + +/** Type mapping all positional tag types to their constructors' parameters. */ +type posTagParamMap = { + [k in PositionalTagType]: ConstructorParameters[0]; +}; + +/** + * Type mapping all positional tag types to their constructors' parameters, alongside the `tagType` selector. + * Equivalent to their serialized representations. + */ +export type serializedPosTagMap = { + [k in PositionalTagType]: posTagParamMap[k] & { tagType: k }; +}; + +/** Union type containing all serialized {@linkcode PositionalTag}s. */ +export type SerializedPositionalTag = ObjectValues; diff --git a/src/data/positional-tags/positional-tag-manager.ts b/src/data/positional-tags/positional-tag-manager.ts new file mode 100644 index 00000000000..7bf4d4995c6 --- /dev/null +++ b/src/data/positional-tags/positional-tag-manager.ts @@ -0,0 +1,55 @@ +import { loadPositionalTag } from "#data/positional-tags/load-positional-tag"; +import type { PositionalTag } from "#data/positional-tags/positional-tag"; +import type { BattlerIndex } from "#enums/battler-index"; +import type { PositionalTagType } from "#enums/positional-tag-type"; + +/** A manager for the {@linkcode PositionalTag}s in the arena. */ +export class PositionalTagManager { + /** + * Array containing all pending unactivated {@linkcode PositionalTag}s, + * sorted by order of creation (oldest first). + */ + public tags: PositionalTag[] = []; + + /** + * Add a new {@linkcode PositionalTag} to the arena. + * @remarks + * This function does not perform any checking if the added tag is valid. + */ + public addTag(tag: Parameters>[0]): void { + this.tags.push(loadPositionalTag(tag)); + } + + /** + * Check whether a new {@linkcode PositionalTag} can be added to the battlefield. + * @param tagType - The {@linkcode PositionalTagType} being created + * @param targetIndex - The {@linkcode BattlerIndex} being targeted + * @returns Whether the tag can be added. + */ + public canAddTag(tagType: PositionalTagType, targetIndex: BattlerIndex): boolean { + return !this.tags.some(t => t.tagType === tagType && t.targetIndex === targetIndex); + } + + /** + * Decrement turn counts of and trigger all pending {@linkcode PositionalTag}s on field. + * @remarks + * If multiple tags trigger simultaneously, they will activate in order of **initial creation**, regardless of current speed order. + * (Source: [Smogon]()) + */ + public activateAllTags(): void { + const leftoverTags: PositionalTag[] = []; + for (const tag of this.tags) { + // Check for silent removal, immediately removing invalid tags. + if (--tag.turnCount > 0) { + // tag still cooking + leftoverTags.push(tag); + continue; + } + + if (tag.shouldTrigger()) { + tag.trigger(); + } + } + this.tags = leftoverTags; + } +} diff --git a/src/data/positional-tags/positional-tag.ts b/src/data/positional-tags/positional-tag.ts new file mode 100644 index 00000000000..77ca6f0e9eb --- /dev/null +++ b/src/data/positional-tags/positional-tag.ts @@ -0,0 +1,174 @@ +import { globalScene } from "#app/global-scene"; +import { getPokemonNameWithAffix } from "#app/messages"; +// biome-ignore-start lint/correctness/noUnusedImports: TSDoc +import type { ArenaTag } from "#data/arena-tag"; +// biome-ignore-end lint/correctness/noUnusedImports: TSDoc +import { allMoves } from "#data/data-lists"; +import type { BattlerIndex } from "#enums/battler-index"; +import type { MoveId } from "#enums/move-id"; +import { MoveUseMode } from "#enums/move-use-mode"; +import { PositionalTagType } from "#enums/positional-tag-type"; +import type { Pokemon } from "#field/pokemon"; +import i18next from "i18next"; + +/** + * Baseline arguments used to construct all {@linkcode PositionalTag}s, + * the contents of which are serialized and used to construct new tags. \ + * Does not contain the `tagType` parameter (which is used to select the proper class constructor during tag loading). + * @privateRemarks + * All {@linkcode PositionalTag}s are intended to implement a sub-interface of this containing their respective parameters, + * and should refrain from adding extra serializable fields not contained in said interface. + * This ensures that all tags truly "become" their respective interfaces when converted to and from JSON. + */ +export interface PositionalTagBaseArgs { + /** + * The number of turns remaining until this tag's activation. \ + * Decremented by 1 at the end of each turn until reaching 0, at which point it will + * {@linkcode PositionalTag.trigger | trigger} the tag's effects and be removed. + */ + turnCount: number; + /** + * The {@linkcode BattlerIndex} targeted by this effect. + */ + targetIndex: BattlerIndex; +} + +/** + * A {@linkcode PositionalTag} is a variant of an {@linkcode ArenaTag} that targets a single *slot* of the battlefield. + * Each tag can last one or more turns, triggering various effects on removal. + * Multiple tags of the same kind can stack with one another, provided they are affecting different targets. + */ +export abstract class PositionalTag implements PositionalTagBaseArgs { + /** This tag's {@linkcode PositionalTagType | type} */ + public abstract readonly tagType: PositionalTagType; + // These arguments have to be public to implement the interface, but are functionally private + // outside this and the tag manager. + // Left undocumented to inherit doc comments from the interface + public turnCount: number; + public readonly targetIndex: BattlerIndex; + + constructor({ turnCount, targetIndex }: PositionalTagBaseArgs) { + this.turnCount = turnCount; + this.targetIndex = targetIndex; + } + + /** Trigger this tag's effects prior to removal. */ + public abstract trigger(): void; + + /** + * Check whether this tag should be allowed to {@linkcode trigger} and activate its effects + * upon its duration elapsing. + * @returns Whether this tag should be allowed to trigger prior to being removed. + */ + public abstract shouldTrigger(): boolean; + + /** + * Get the {@linkcode Pokemon} currently targeted by this tag. + * @returns The {@linkcode Pokemon} located in this tag's target position, or `undefined` if none exist in it. + */ + protected getTarget(): Pokemon | undefined { + return globalScene.getField()[this.targetIndex]; + } +} + +/** Interface containing additional properties used to construct a {@linkcode DelayedAttackTag}. */ +interface DelayedAttackArgs extends PositionalTagBaseArgs { + /** + * The {@linkcode Pokemon.id | PID} of the {@linkcode Pokemon} having created this effect. + */ + sourceId: number; + /** The {@linkcode MoveId} that created this attack. */ + sourceMove: MoveId; +} + +/** + * Tag to manage execution of delayed attacks, such as {@linkcode MoveId.FUTURE_SIGHT} or {@linkcode MoveId.DOOM_DESIRE}. \ + * Delayed attacks do nothing for the first several turns after use (including the turn the move is used), + * triggering against a certain slot after the turn count has elapsed. + */ +export class DelayedAttackTag extends PositionalTag implements DelayedAttackArgs { + public override readonly tagType = PositionalTagType.DELAYED_ATTACK; + public readonly sourceMove: MoveId; + public readonly sourceId: number; + + constructor({ sourceId, turnCount, targetIndex, sourceMove }: DelayedAttackArgs) { + super({ turnCount, targetIndex }); + this.sourceId = sourceId; + this.sourceMove = sourceMove; + } + + public override trigger(): void { + // Bangs are justified as the `shouldTrigger` method will queue the tag for removal + // if the source or target no longer exist + const source = globalScene.getPokemonById(this.sourceId)!; + const target = this.getTarget()!; + + source.turnData.extraTurns++; + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:tookMoveAttack", { + pokemonName: getPokemonNameWithAffix(target), + moveName: allMoves[this.sourceMove].name, + }), + ); + + globalScene.phaseManager.unshiftNew( + "MoveEffectPhase", + this.sourceId, // TODO: Find an alternate method of passing the source pokemon without a source ID + [this.targetIndex], + allMoves[this.sourceMove], + MoveUseMode.DELAYED_ATTACK, + ); + } + + public override shouldTrigger(): boolean { + const source = globalScene.getPokemonById(this.sourceId); + const target = this.getTarget(); + // Silently disappear if either source or target are missing or happen to be the same pokemon + // (i.e. targeting oneself) + // We also need to check for fainted targets as they don't technically leave the field until _after_ the turn ends + return !!source && !!target && source !== target && !target.isFainted(); + } +} + +/** Interface containing arguments used to construct a {@linkcode WishTag}. */ +interface WishArgs extends PositionalTagBaseArgs { + /** The amount of {@linkcode Stat.HP | HP} to heal; set to 50% of the user's max HP during move usage. */ + healHp: number; + /** The name of the {@linkcode Pokemon} having created the tag. */ + pokemonName: string; +} + +/** + * Tag to implement {@linkcode MoveId.WISH | Wish}. + */ +export class WishTag extends PositionalTag implements WishArgs { + public override readonly tagType = PositionalTagType.WISH; + + public readonly pokemonName: string; + public readonly healHp: number; + + constructor({ turnCount, targetIndex, healHp, pokemonName }: WishArgs) { + super({ turnCount, targetIndex }); + this.healHp = healHp; + this.pokemonName = pokemonName; + } + + public override trigger(): void { + // TODO: Rename this locales key - wish shows a message on REMOVAL, not addition + globalScene.phaseManager.queueMessage( + i18next.t("arenaTag:wishTagOnAdd", { + pokemonNameWithAffix: this.pokemonName, + }), + ); + + globalScene.phaseManager.unshiftNew("PokemonHealPhase", this.targetIndex, this.healHp, null, true, false); + } + + public override shouldTrigger(): boolean { + // Disappear if no target or target is fainted. + // The source need not exist at the time of activation (since all we need is a simple message) + // TODO: Verify whether Wish shows a message if the Pokemon it would affect is KO'd on the turn of activation + const target = this.getTarget(); + return !!target && !target.isFainted(); + } +} diff --git a/src/data/terrain.ts b/src/data/terrain.ts index f5382b1c3ec..7906450d0ea 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -3,6 +3,7 @@ import type { BattlerIndex } from "#enums/battler-index"; import { PokemonType } from "#enums/pokemon-type"; import type { Pokemon } from "#field/pokemon"; import type { Move } from "#moves/move"; +import { isFieldTargeted, isSpreadMove } from "#moves/move-utils"; import i18next from "i18next"; export enum TerrainType { @@ -60,13 +61,19 @@ export class Terrain { isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean { switch (this.terrainType) { case TerrainType.PSYCHIC: - if (!move.hasAttr("ProtectAttr")) { - // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain - return ( - move.getPriority(user) > 0 && - user.getOpponents(true).some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded()) - ); - } + // Cf https://bulbapedia.bulbagarden.net/wiki/Psychic_Terrain_(move)#Generation_VII + // Psychic terrain will only cancel a move if it: + return ( + // ... is neither spread nor field-targeted, + !isFieldTargeted(move) && + !isSpreadMove(move) && + // .. has positive final priority, + move.getPriority(user) > 0 && + // ...and is targeting at least 1 grounded opponent + user + .getOpponents(true) + .some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded()) + ); } return false; diff --git a/src/data/trainer-names.ts b/src/data/trainer-names.ts index 6b882d1ddc1..8eafd9f6404 100644 --- a/src/data/trainer-names.ts +++ b/src/data/trainer-names.ts @@ -1,12 +1,12 @@ import { TrainerType } from "#enums/trainer-type"; -import { toReadableString } from "#utils/common"; +import { toPascalSnakeCase } from "#utils/strings"; class TrainerNameConfig { public urls: string[]; public femaleUrls: string[] | null; constructor(type: TrainerType, ...urls: string[]) { - this.urls = urls.length ? urls : [toReadableString(TrainerType[type]).replace(/ /g, "_")]; + this.urls = urls.length ? urls : [toPascalSnakeCase(TrainerType[type])]; } hasGenderVariant(...femaleUrls: string[]): TrainerNameConfig { diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 2cc5125c626..3b30be5c354 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -41,15 +41,9 @@ import type { TrainerConfigs, TrainerTierPools, } from "#types/trainer-funcs"; -import { - coerceArray, - isNullOrUndefined, - randSeedInt, - randSeedIntRange, - randSeedItem, - toReadableString, -} from "#utils/common"; +import { coerceArray, isNullOrUndefined, randSeedInt, randSeedIntRange, randSeedItem } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; +import { toSnakeCase, toTitleCase } from "#utils/strings"; import i18next from "i18next"; /** Minimum BST for Pokemon generated onto the Elite Four's teams */ @@ -140,7 +134,7 @@ export class TrainerConfig { constructor(trainerType: TrainerType, allowLegendaries?: boolean) { this.trainerType = trainerType; this.trainerAI = new TrainerAI(); - this.name = toReadableString(TrainerType[this.getDerivedType()]); + this.name = toTitleCase(TrainerType[this.getDerivedType()]); this.battleBgm = "battle_trainer"; this.mixedBattleBgm = "battle_trainer"; this.victoryBgm = "victory_trainer"; @@ -722,7 +716,7 @@ export class TrainerConfig { } // Localize the trainer's name by converting it to lowercase and replacing spaces with underscores. - const nameForCall = this.name.toLowerCase().replace(/\s/g, "_"); + const nameForCall = toSnakeCase(this.name); this.name = i18next.t(`trainerNames:${nameForCall}`); // Set the title to "elite_four". (this is the key in the i18n file) @@ -1248,12 +1242,58 @@ export const trainerConfigs: TrainerConfigs = { .setHasDouble("Breeders") .setPartyTemplateFunc(() => getWavePartyTemplate( - trainerPartyTemplates.FOUR_WEAKER, - trainerPartyTemplates.FIVE_WEAKER, - trainerPartyTemplates.SIX_WEAKER, + trainerPartyTemplates.FOUR_WEAK, + trainerPartyTemplates.FIVE_WEAK, + trainerPartyTemplates.SIX_WEAK, ), ) - .setSpeciesFilter(s => s.baseTotal < 450), + .setSpeciesPools({ + [TrainerPoolTier.COMMON]: [ + SpeciesId.PICHU, + SpeciesId.CLEFFA, + SpeciesId.IGGLYBUFF, + SpeciesId.TOGEPI, + SpeciesId.TYROGUE, + SpeciesId.SMOOCHUM, + SpeciesId.AZURILL, + SpeciesId.BUDEW, + SpeciesId.CHINGLING, + SpeciesId.BONSLY, + SpeciesId.MIME_JR, + SpeciesId.HAPPINY, + SpeciesId.MANTYKE, + SpeciesId.TOXEL, + ], + [TrainerPoolTier.UNCOMMON]: [ + SpeciesId.DITTO, + SpeciesId.ELEKID, + SpeciesId.MAGBY, + SpeciesId.WYNAUT, + SpeciesId.MUNCHLAX, + SpeciesId.RIOLU, + SpeciesId.AUDINO, + ], + [TrainerPoolTier.RARE]: [ + SpeciesId.ALOLA_RATTATA, + SpeciesId.ALOLA_SANDSHREW, + SpeciesId.ALOLA_VULPIX, + SpeciesId.ALOLA_DIGLETT, + SpeciesId.ALOLA_MEOWTH, + SpeciesId.GALAR_PONYTA, + ], + [TrainerPoolTier.SUPER_RARE]: [ + SpeciesId.ALOLA_GEODUDE, + SpeciesId.ALOLA_GRIMER, + SpeciesId.GALAR_MEOWTH, + SpeciesId.GALAR_SLOWPOKE, + SpeciesId.GALAR_FARFETCHD, + SpeciesId.HISUI_GROWLITHE, + SpeciesId.HISUI_VOLTORB, + SpeciesId.HISUI_QWILFISH, + SpeciesId.HISUI_SNEASEL, + SpeciesId.HISUI_ZORUA, + ], + }), [TrainerType.CLERK]: new TrainerConfig(++t) .setHasGenders("Clerk Female") .setHasDouble("Colleagues") diff --git a/src/data/trainers/trainer-party-template.ts b/src/data/trainers/trainer-party-template.ts index d4e7fe7a261..0ad3d36dcfa 100644 --- a/src/data/trainers/trainer-party-template.ts +++ b/src/data/trainers/trainer-party-template.ts @@ -144,6 +144,7 @@ export const trainerPartyTemplates = { FIVE_WEAK_BALANCED: new TrainerPartyTemplate(5, PartyMemberStrength.WEAK, false, true), SIX_WEAKER: new TrainerPartyTemplate(6, PartyMemberStrength.WEAKER), SIX_WEAKER_SAME: new TrainerPartyTemplate(6, PartyMemberStrength.WEAKER, true), + SIX_WEAK: new TrainerPartyTemplate(6, PartyMemberStrength.WEAK), SIX_WEAK_SAME: new TrainerPartyTemplate(6, PartyMemberStrength.WEAK, true), SIX_WEAK_BALANCED: new TrainerPartyTemplate(6, PartyMemberStrength.WEAK, false, true), diff --git a/src/enums/ability-attr.ts b/src/enums/ability-attr.ts index 5f7d107f2d1..a3b9511ad02 100644 --- a/src/enums/ability-attr.ts +++ b/src/enums/ability-attr.ts @@ -1,3 +1,5 @@ +import type { ObjectValues } from "#types/type-helpers"; + /** * Not to be confused with an Ability Attribute. * This is an object literal storing the slot that an ability can occupy. @@ -8,4 +10,4 @@ export const AbilityAttr = Object.freeze({ ABILITY_HIDDEN: 4, }); -export type AbilityAttr = typeof AbilityAttr[keyof typeof AbilityAttr]; \ No newline at end of file +export type AbilityAttr = ObjectValues; \ No newline at end of file diff --git a/src/enums/arena-tag-type.ts b/src/enums/arena-tag-type.ts index 214826993b3..7f9364395c0 100644 --- a/src/enums/arena-tag-type.ts +++ b/src/enums/arena-tag-type.ts @@ -15,9 +15,6 @@ export enum ArenaTagType { SPIKES = "SPIKES", TOXIC_SPIKES = "TOXIC_SPIKES", MIST = "MIST", - FUTURE_SIGHT = "FUTURE_SIGHT", - DOOM_DESIRE = "DOOM_DESIRE", - WISH = "WISH", STEALTH_ROCK = "STEALTH_ROCK", STICKY_WEB = "STICKY_WEB", TRICK_ROOM = "TRICK_ROOM", diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts index 719b08c5b81..6d9d2dd4a92 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -1,5 +1,4 @@ export enum BattlerTagType { - NONE = "NONE", RECHARGING = "RECHARGING", FLINCHED = "FLINCHED", INTERRUPTED = "INTERRUPTED", diff --git a/src/enums/dex-attr.ts b/src/enums/dex-attr.ts index ee5ceb43ef2..1a98167b4a1 100644 --- a/src/enums/dex-attr.ts +++ b/src/enums/dex-attr.ts @@ -1,3 +1,5 @@ +import type { ObjectValues } from "#types/type-helpers"; + export const DexAttr = Object.freeze({ NON_SHINY: 1n, SHINY: 2n, @@ -8,4 +10,4 @@ export const DexAttr = Object.freeze({ VARIANT_3: 64n, DEFAULT_FORM: 128n, }); -export type DexAttr = typeof DexAttr[keyof typeof DexAttr]; +export type DexAttr = ObjectValues; diff --git a/src/enums/dynamic-phase-type.ts b/src/enums/dynamic-phase-type.ts index a34ac371668..b9ea6bf197d 100644 --- a/src/enums/dynamic-phase-type.ts +++ b/src/enums/dynamic-phase-type.ts @@ -1,6 +1,7 @@ /** - * Enum representation of the phase types held by implementations of {@linkcode PhasePriorityQueue} + * Enum representation of the phase types held by implementations of {@linkcode PhasePriorityQueue}. */ +// TODO: We currently assume these are in order export enum DynamicPhaseType { POST_SUMMON } diff --git a/src/enums/gacha-types.ts b/src/enums/gacha-types.ts index cd0bc67eae0..08f147b27b1 100644 --- a/src/enums/gacha-types.ts +++ b/src/enums/gacha-types.ts @@ -1,7 +1,9 @@ +import type { ObjectValues } from "#types/type-helpers"; + export const GachaType = Object.freeze({ MOVE: 0, LEGENDARY: 1, SHINY: 2 }); -export type GachaType = typeof GachaType[keyof typeof GachaType]; +export type GachaType = ObjectValues; diff --git a/src/enums/hit-check-result.ts b/src/enums/hit-check-result.ts index cf8a2b17194..0866050341e 100644 --- a/src/enums/hit-check-result.ts +++ b/src/enums/hit-check-result.ts @@ -1,3 +1,5 @@ +import type { ObjectValues } from "#types/type-helpers"; + /** The result of a hit check calculation */ export const HitCheckResult = { /** Hit checks haven't been evaluated yet in this pass */ @@ -20,4 +22,4 @@ export const HitCheckResult = { ERROR: 8, } as const; -export type HitCheckResult = typeof HitCheckResult[keyof typeof HitCheckResult]; +export type HitCheckResult = ObjectValues; diff --git a/src/enums/move-flags.ts b/src/enums/move-flags.ts index 06de265df09..6cdc1e5f8cc 100644 --- a/src/enums/move-flags.ts +++ b/src/enums/move-flags.ts @@ -4,15 +4,19 @@ */ export enum MoveFlags { NONE = 0, + /** + * Whether the move makes contact. + * Set by default on all contact moves, and unset by default on all special moves. + */ MAKES_CONTACT = 1 << 0, IGNORE_PROTECT = 1 << 1, /** * Sound-based moves have the following effects: - * - Pokemon with the {@linkcode AbilityId.SOUNDPROOF Soundproof Ability} are unaffected by other Pokemon's sound-based moves. - * - Pokemon affected by {@linkcode MoveId.THROAT_CHOP Throat Chop} cannot use sound-based moves for two turns. - * - Sound-based moves used by a Pokemon with {@linkcode AbilityId.LIQUID_VOICE Liquid Voice} become Water-type moves. - * - Sound-based moves used by a Pokemon with {@linkcode AbilityId.PUNK_ROCK Punk Rock} are boosted by 30%. Pokemon with Punk Rock also take half damage from sound-based moves. - * - All sound-based moves (except Howl) can hit Pokemon behind an active {@linkcode MoveId.SUBSTITUTE Substitute}. + * - Pokemon with the {@linkcode AbilityId.SOUNDPROOF | Soundproof} Ability are unaffected by other Pokemon's sound-based moves. + * - Pokemon affected by {@linkcode MoveId.THROAT_CHOP | Throat Chop} cannot use sound-based moves for two turns. + * - Sound-based moves used by a Pokemon with {@linkcode AbilityId.LIQUID_VOICE | Liquid Voice} become Water-type moves. + * - Sound-based moves used by a Pokemon with {@linkcode AbilityId.PUNK_ROCK | Punk Rock} are boosted by 30%. Pokemon with Punk Rock also take half damage from sound-based moves. + * - All sound-based moves (except Howl) can hit Pokemon behind an active {@linkcode MoveId.SUBSTITUTE | Substitute}. * * cf https://bulbapedia.bulbagarden.net/wiki/Sound-based_move */ diff --git a/src/enums/move-use-mode.ts b/src/enums/move-use-mode.ts index 1bb9d6374b7..13ea5248853 100644 --- a/src/enums/move-use-mode.ts +++ b/src/enums/move-use-mode.ts @@ -1,5 +1,7 @@ import type { PostDancingMoveAbAttr } from "#abilities/ability"; +import type { DelayedAttackAttr } from "#app/@types/move-types"; import type { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; +import type { ObjectValues } from "#types/type-helpers"; /** * Enum representing all the possible means through which a given move can be executed. @@ -59,11 +61,20 @@ export const MoveUseMode = { * and retain the same copy prevention as {@linkcode MoveUseMode.FOLLOW_UP}, but additionally * **cannot be reflected by other reflecting effects**. */ - REFLECTED: 5 - // TODO: Add use type TRANSPARENT for Future Sight and Doom Desire to prevent move history pushing + REFLECTED: 5, + /** + * This "move" was created by a transparent effect that **does not count as using a move**, + * such as {@linkcode DelayedAttackAttr | Future Sight/Doom Desire}. + * + * In addition to inheriting the cancellation ignores and copy prevention from {@linkcode MoveUseMode.REFLECTED}, + * transparent moves are ignored by **all forms of move usage checks** due to **not pushing to move history**. + * @todo Consider other means of implementing FS/DD than this - we currently only use it + * to prevent pushing to move history and avoid re-delaying the attack portion + */ + DELAYED_ATTACK: 6 } as const; -export type MoveUseMode = (typeof MoveUseMode)[keyof typeof MoveUseMode]; +export type MoveUseMode = ObjectValues; // # HELPER FUNCTIONS // Please update the markdown tables if any new `MoveUseMode`s get added. @@ -75,13 +86,14 @@ export type MoveUseMode = (typeof MoveUseMode)[keyof typeof MoveUseMode]; * @remarks * This function is equivalent to the following truth table: * - * | Use Type | Returns | - * |------------------------------------|---------| - * | {@linkcode MoveUseMode.NORMAL} | `false` | - * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | - * | {@linkcode MoveUseMode.INDIRECT} | `true` | - * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | - * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | Use Type | Returns | + * |----------------------------------------|---------| + * | {@linkcode MoveUseMode.NORMAL} | `false` | + * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | + * | {@linkcode MoveUseMode.INDIRECT} | `true` | + * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | + * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | {@linkcode MoveUseMode.DELAYED_ATTACK} | `true` | */ export function isVirtual(useMode: MoveUseMode): boolean { return useMode >= MoveUseMode.INDIRECT @@ -95,13 +107,14 @@ export function isVirtual(useMode: MoveUseMode): boolean { * @remarks * This function is equivalent to the following truth table: * - * | Use Type | Returns | - * |------------------------------------|---------| - * | {@linkcode MoveUseMode.NORMAL} | `false` | - * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | - * | {@linkcode MoveUseMode.INDIRECT} | `false` | - * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | - * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | Use Type | Returns | + * |----------------------------------------|---------| + * | {@linkcode MoveUseMode.NORMAL} | `false` | + * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | + * | {@linkcode MoveUseMode.INDIRECT} | `false` | + * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | + * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | {@linkcode MoveUseMode.DELAYED_ATTACK} | `true` | */ export function isIgnoreStatus(useMode: MoveUseMode): boolean { return useMode >= MoveUseMode.FOLLOW_UP; @@ -115,13 +128,14 @@ export function isIgnoreStatus(useMode: MoveUseMode): boolean { * @remarks * This function is equivalent to the following truth table: * - * | Use Type | Returns | - * |------------------------------------|---------| - * | {@linkcode MoveUseMode.NORMAL} | `false` | - * | {@linkcode MoveUseMode.IGNORE_PP} | `true` | - * | {@linkcode MoveUseMode.INDIRECT} | `true` | - * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | - * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | Use Type | Returns | + * |----------------------------------------|---------| + * | {@linkcode MoveUseMode.NORMAL} | `false` | + * | {@linkcode MoveUseMode.IGNORE_PP} | `true` | + * | {@linkcode MoveUseMode.INDIRECT} | `true` | + * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | + * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | {@linkcode MoveUseMode.DELAYED_ATTACK} | `true` | */ export function isIgnorePP(useMode: MoveUseMode): boolean { return useMode >= MoveUseMode.IGNORE_PP; @@ -136,14 +150,15 @@ export function isIgnorePP(useMode: MoveUseMode): boolean { * @remarks * This function is equivalent to the following truth table: * - * | Use Type | Returns | - * |------------------------------------|---------| - * | {@linkcode MoveUseMode.NORMAL} | `false` | - * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | - * | {@linkcode MoveUseMode.INDIRECT} | `false` | - * | {@linkcode MoveUseMode.FOLLOW_UP} | `false` | - * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | Use Type | Returns | + * |----------------------------------------|---------| + * | {@linkcode MoveUseMode.NORMAL} | `false` | + * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | + * | {@linkcode MoveUseMode.INDIRECT} | `false` | + * | {@linkcode MoveUseMode.FOLLOW_UP} | `false` | + * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | {@linkcode MoveUseMode.DELAYED_ATTACK} | `false` | */ export function isReflected(useMode: MoveUseMode): boolean { return useMode === MoveUseMode.REFLECTED; -} +} \ No newline at end of file diff --git a/src/enums/positional-tag-type.ts b/src/enums/positional-tag-type.ts new file mode 100644 index 00000000000..254503d0de6 --- /dev/null +++ b/src/enums/positional-tag-type.ts @@ -0,0 +1,10 @@ +/** + * Enum representing all positional tag types. + * @privateRemarks + * When adding new tag types, please update `positionalTagConstructorMap` in `src/data/positionalTags` + * with the new tag type. + */ +export enum PositionalTagType { + DELAYED_ATTACK = "DELAYED_ATTACK", + WISH = "WISH", +} diff --git a/src/enums/text-style.ts b/src/enums/text-style.ts new file mode 100644 index 00000000000..964a985cdd6 --- /dev/null +++ b/src/enums/text-style.ts @@ -0,0 +1,59 @@ +export const TextStyle = Object.freeze({ + MESSAGE: 1, + WINDOW: 2, + WINDOW_ALT: 3, + WINDOW_BATTLE_COMMAND: 4, + BATTLE_INFO: 5, + PARTY: 6, + PARTY_RED: 7, + PARTY_CANCEL_BUTTON: 8, + INSTRUCTIONS_TEXT: 9, + MOVE_LABEL: 10, + SUMMARY: 11, + SUMMARY_DEX_NUM: 12, + SUMMARY_DEX_NUM_GOLD: 13, + SUMMARY_ALT: 14, + SUMMARY_HEADER: 15, + SUMMARY_RED: 16, + SUMMARY_BLUE: 17, + SUMMARY_PINK: 18, + SUMMARY_GOLD: 19, + SUMMARY_GRAY: 20, + SUMMARY_GREEN: 21, + SUMMARY_STATS: 22, + SUMMARY_STATS_BLUE: 23, + SUMMARY_STATS_PINK: 24, + SUMMARY_STATS_GOLD: 25, + LUCK_VALUE: 26, + STATS_HEXAGON: 27, + GROWTH_RATE_TYPE: 28, + MONEY: 29, // Money default styling (pale yellow) + MONEY_WINDOW: 30, // Money displayed in Windows (needs different colors based on theme) + HEADER_LABEL: 31, + STATS_LABEL: 32, + STATS_VALUE: 33, + SETTINGS_VALUE: 34, + SETTINGS_LABEL: 35, + SETTINGS_LABEL_NAVBAR: 36, + SETTINGS_SELECTED: 37, + SETTINGS_LOCKED: 38, + EGG_LIST: 39, + EGG_SUMMARY_NAME: 40, + EGG_SUMMARY_DEX: 41, + STARTER_VALUE_LIMIT: 42, + TOOLTIP_TITLE: 43, + TOOLTIP_CONTENT: 44, + FILTER_BAR_MAIN: 45, + MOVE_INFO_CONTENT: 46, + MOVE_PP_FULL: 47, + MOVE_PP_HALF_FULL: 48, + MOVE_PP_NEAR_EMPTY: 49, + MOVE_PP_EMPTY: 50, + SMALLER_WINDOW_ALT: 51, + BGM_BAR: 52, + PERFECT_IV: 53, + ME_OPTION_DEFAULT: 54, // Default style for choices in ME + ME_OPTION_SPECIAL: 55, // Style for choices with special requirements in ME + SHADOW_TEXT: 56 // to obscure unavailable options +}) +export type TextStyle = typeof TextStyle[keyof typeof TextStyle]; \ No newline at end of file diff --git a/src/field/arena.ts b/src/field/arena.ts index 883e7958d42..adf62facc23 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -1,3 +1,7 @@ +// biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports +import type { PositionalTag } from "#data/positional-tags/positional-tag"; +// biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports + import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; @@ -7,6 +11,7 @@ import type { ArenaTag } from "#data/arena-tag"; import { ArenaTrapTag, getArenaTag } from "#data/arena-tag"; import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#data/form-change-triggers"; import type { PokemonSpecies } from "#data/pokemon-species"; +import { PositionalTagManager } from "#data/positional-tags/positional-tag-manager"; import { getTerrainClearMessage, getTerrainStartMessage, Terrain, TerrainType } from "#data/terrain"; import { getLegendaryWeatherContinuesMessage, @@ -39,7 +44,14 @@ export class Arena { public biomeType: BiomeId; public weather: Weather | null; public terrain: Terrain | null; - public tags: ArenaTag[]; + /** All currently-active {@linkcode ArenaTag}s on both sides of the field. */ + public tags: ArenaTag[] = []; + /** + * All currently-active {@linkcode PositionalTag}s on both sides of the field, + * sorted by tag type. + */ + public positionalTagManager: PositionalTagManager = new PositionalTagManager(); + public bgm: string; public ignoreAbilities: boolean; public ignoringEffectSource: BattlerIndex | null; @@ -59,7 +71,6 @@ export class Arena { constructor(biome: BiomeId, bgm: string, playerFaints = 0) { this.biomeType = biome; - this.tags = []; this.bgm = bgm; this.trainerPool = biomeTrainerPools[biome]; this.updatePoolsForTimeOfDay(); @@ -677,15 +688,15 @@ export class Arena { } /** - * Adds a new tag to the arena - * @param tagType {@linkcode ArenaTagType} the tag being added - * @param turnCount How many turns the tag lasts - * @param sourceMove {@linkcode MoveId} the move the tag came from, or `undefined` if not from a move - * @param sourceId The ID of the pokemon in play the tag came from (see {@linkcode BattleScene.getPokemonById}) - * @param side {@linkcode ArenaTagSide} which side(s) the tag applies to - * @param quiet If a message should be queued on screen to announce the tag being added - * @param targetIndex The {@linkcode BattlerIndex} of the target pokemon - * @returns `false` if there already exists a tag of this type in the Arena + * Add a new {@linkcode ArenaTag} to the arena, triggering overlap effects on existing tags as applicable. + * @param tagType - The {@linkcode ArenaTagType} of the tag to add. + * @param turnCount - The number of turns the newly-added tag should last. + * @param sourceId - The {@linkcode Pokemon.id | PID} of the Pokemon creating the tag. + * @param sourceMove - The {@linkcode MoveId} of the move creating the tag, or `undefined` if not from a move. + * @param side - The {@linkcode ArenaTagSide}(s) to which the tag should apply; default `ArenaTagSide.BOTH`. + * @param quiet - Whether to suppress messages produced by tag addition; default `false`. + * @returns `true` if the tag was successfully added without overlapping. + // TODO: Do we need the return value here? literally nothing uses it */ addTag( tagType: ArenaTagType, @@ -694,7 +705,6 @@ export class Arena { sourceId: number, side: ArenaTagSide = ArenaTagSide.BOTH, quiet = false, - targetIndex?: BattlerIndex, ): boolean { const existingTag = this.getTagOnSide(tagType, side); if (existingTag) { @@ -709,7 +719,7 @@ export class Arena { } // creates a new tag object - const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side); + const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, side); if (newTag) { newTag.onAdd(this, quiet); this.tags.push(newTag); @@ -725,10 +735,19 @@ export class Arena { } /** - * Attempts to get a tag from the Arena via {@linkcode getTagOnSide} that applies to both sides - * @param tagType The {@linkcode ArenaTagType} or {@linkcode ArenaTag} to get - * @returns either the {@linkcode ArenaTag}, or `undefined` if it isn't there + * Attempt to get a tag from the Arena via {@linkcode getTagOnSide} that applies to both sides + * @param tagType - The {@linkcode ArenaTagType} to retrieve + * @returns The existing {@linkcode ArenaTag}, or `undefined` if not present. + * @overload */ + getTag(tagType: ArenaTagType): ArenaTag | undefined; + /** + * Attempt to get a tag from the Arena via {@linkcode getTagOnSide} that applies to both sides + * @param tagType - The constructor of the {@linkcode ArenaTag} to retrieve + * @returns The existing {@linkcode ArenaTag}, or `undefined` if not present. + * @overload + */ + getTag(tagType: Constructor | AbstractConstructor): T | undefined; getTag(tagType: ArenaTagType | Constructor | AbstractConstructor): ArenaTag | undefined { return this.getTagOnSide(tagType, ArenaTagSide.BOTH); } diff --git a/src/field/damage-number-handler.ts b/src/field/damage-number-handler.ts index acb279a17a0..1bbacc19566 100644 --- a/src/field/damage-number-handler.ts +++ b/src/field/damage-number-handler.ts @@ -1,9 +1,10 @@ import { globalScene } from "#app/global-scene"; import type { BattlerIndex } from "#enums/battler-index"; import { HitResult } from "#enums/hit-result"; +import { TextStyle } from "#enums/text-style"; import type { Pokemon } from "#field/pokemon"; import type { DamageResult } from "#types/damage-result"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { fixedInt, formatStat } from "#utils/common"; type TextAndShadowArr = [string | null, string | null]; diff --git a/src/field/pokemon-sprite-sparkle-handler.ts b/src/field/pokemon-sprite-sparkle-handler.ts index 725229ce723..bd44dc03330 100644 --- a/src/field/pokemon-sprite-sparkle-handler.ts +++ b/src/field/pokemon-sprite-sparkle-handler.ts @@ -5,10 +5,11 @@ import { coerceArray, fixedInt, randInt } from "#utils/common"; export class PokemonSpriteSparkleHandler { private sprites: Set; + private counterTween?: Phaser.Tweens.Tween; + setup(): void { this.sprites = new Set(); - - globalScene.tweens.addCounter({ + this.counterTween = globalScene.tweens.addCounter({ duration: fixedInt(200), from: 0, to: 1, @@ -78,4 +79,12 @@ export class PokemonSpriteSparkleHandler { this.sprites.delete(s); } } + + destroy(): void { + this.removeAll(); + if (this.counterTween) { + this.counterTween.destroy(); + this.counterTween = undefined; + } + } } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e9b41cbaa52..e5d1ae9ad62 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -25,7 +25,6 @@ import { AutotomizedTag, BattlerTag, CritBoostTag, - DragonCheerTag, EncoreTag, ExposedTag, GroundedTag, @@ -61,7 +60,7 @@ import { } from "#data/pokemon-data"; import type { SpeciesFormChange } from "#data/pokemon-forms"; import type { PokemonSpeciesForm } from "#data/pokemon-species"; -import { getFusedSpeciesName, getPokemonSpeciesForm, PokemonSpecies } from "#data/pokemon-species"; +import { PokemonSpecies } from "#data/pokemon-species"; import { getRandomStatus, getStatusEffectOverlapText, Status } from "#data/status-effect"; import { getTerrainBlockMessage, TerrainType } from "#data/terrain"; import type { TypeDamageMultiplier } from "#data/type"; @@ -155,7 +154,7 @@ import { toDmgValue, } from "#utils/common"; import { getEnumValues } from "#utils/enums"; -import { getPokemonSpecies } from "#utils/pokemon-utils"; +import { getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; import { argbFromRgba, QuantizerCelebi, rgbaFromArgb } from "@material/material-color-utilities"; import i18next from "i18next"; import Phaser from "phaser"; @@ -199,8 +198,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * TODO: Stop treating this like a unique ID and stop treating 0 as no pokemon */ public id: number; - public name: string; - public nickname: string; + /** + * The Pokemon's current nickname, or `undefined` if it currently lacks one. + * If omitted, references to this should refer to the default name for this Pokemon's species. + */ + public nickname?: string; public species: PokemonSpecies; public formIndex: number; public abilityIndex: number; @@ -436,10 +438,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * @returns The name to render for this {@linkcode Pokemon}. */ getNameToRender(useIllusion = true) { - const name: string = - !useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.name : this.name; - const nickname: string = - !useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.nickname : this.nickname; + const illusion = this.summonData.illusion; + const name = useIllusion ? (illusion?.name ?? this.name) : this.name; + const nickname: string | undefined = useIllusion ? illusion?.nickname : this.nickname; try { if (nickname) { return decodeURIComponent(escape(atob(nickname))); // TODO: Remove `atob` and `escape`... eventually... @@ -457,7 +458,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * @returns The {@linkcode PokeballType} that will be shown when this Pokemon is sent out into battle. */ getPokeball(useIllusion = false): PokeballType { - return useIllusion && this.summonData.illusion ? this.summonData.illusion.pokeball : this.pokeball; + return useIllusion ? (this.summonData.illusion?.pokeball ?? this.pokeball) : this.pokeball; } init(): void { @@ -605,24 +606,33 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Generate an illusion of the last pokemon in the party, as other wild pokemon in the area. + * Set this pokemon's illusion to the data of the given pokemon. + * + * @remarks + * When setting the illusion of a wild pokemon, a {@linkcode PokemonSpecies} is generally passed. + * When setting the illusion of a pokemon in this way, the fields required by illusion data + * but missing from `PokemonSpecies` are set as follows + * - `pokeball` and `nickname` are both inherited from this pokemon + * - `shiny` will always be set if this pokemon OR its fusion is shiny + * - `variant` will always be 0 + * - Fields related to fusion will be set to `undefined` or `0` as appropriate + * - The gender is set to be the same as this pokemon, if it is compatible with the provided pokemon. + * - If the provided pokemon can only ever exist as one gender, it is always that gender + * - If this pokemon is genderless but the provided pokemon isn't, then a gender roll is done based on this + * pokemon's ID */ - setIllusion(pokemon: Pokemon): boolean { - if (this.summonData.illusion) { - this.breakIllusion(); - } - if (this.hasTrainer()) { + setIllusion(pokemon: Pokemon | PokemonSpecies): boolean { + this.breakIllusion(); + if (pokemon instanceof Pokemon) { const speciesId = pokemon.species.speciesId; this.summonData.illusion = { - basePokemon: { - name: this.name, - nickname: this.nickname, - shiny: this.shiny, - variant: this.variant, - fusionShiny: this.fusionShiny, - fusionVariant: this.fusionVariant, - }, + name: pokemon.name, + nickname: pokemon.nickname, + shiny: pokemon.shiny, + variant: pokemon.variant, + fusionShiny: pokemon.fusionShiny, + fusionVariant: pokemon.fusionVariant, species: speciesId, formIndex: pokemon.formIndex, gender: pokemon.gender, @@ -632,54 +642,61 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { fusionGender: pokemon.fusionGender, }; - this.name = pokemon.name; - this.nickname = pokemon.nickname; - this.shiny = pokemon.shiny; - this.variant = pokemon.variant; - this.fusionVariant = pokemon.fusionVariant; - this.fusionShiny = pokemon.fusionShiny; - if (this.shiny) { + if (pokemon.shiny || pokemon.fusionShiny) { this.initShinySparkle(); } - this.loadAssets(false, true).then(() => this.playAnim()); - this.updateInfo(); } else { - const randomIllusion: PokemonSpecies = globalScene.arena.randomSpecies( - globalScene.currentBattle.waveIndex, - this.level, - ); - + // Correct the gender in case the illusioned species has a gender incompatible with this pokemon + let gender = this.gender; + switch (pokemon.malePercent) { + case null: + gender = Gender.GENDERLESS; + break; + case 0: + gender = Gender.FEMALE; + break; + case 100: + gender = Gender.MALE; + break; + default: + gender = (this.id % 256) * 0.390625 < pokemon.malePercent ? Gender.MALE : Gender.FEMALE; + } + /* + TODO: Allow setting `variant` to something other than 0, which would require first loading the + assets for the provided species, as its entry would otherwise not + be guaranteed to exist in the `variantData` map. But this would prevent `summonData` from being populated + until the assets are loaded, which would cause issues as this method cannot be easily promisified. + */ this.summonData.illusion = { - basePokemon: { - name: this.name, - nickname: this.nickname, - shiny: this.shiny, - variant: this.variant, - fusionShiny: this.fusionShiny, - fusionVariant: this.fusionVariant, - }, - species: randomIllusion.speciesId, - formIndex: randomIllusion.formIndex, - gender: this.gender, + fusionShiny: false, + fusionVariant: 0, + shiny: this.shiny || this.fusionShiny, + variant: 0, + nickname: this.nickname, + name: pokemon.name, + species: pokemon.speciesId, + formIndex: pokemon.formIndex, + gender, pokeball: this.pokeball, }; - this.name = randomIllusion.name; - this.loadAssets(false, true).then(() => this.playAnim()); + if (this.shiny || this.fusionShiny) { + this.initShinySparkle(); + } } + this.loadAssets(false, true).then(() => this.playAnim()); + this.updateInfo(); return true; } + /** + * Break the illusion of this pokemon, if it has an active illusion. + * @returns Whether an illusion was broken. + */ breakIllusion(): boolean { if (!this.summonData.illusion) { return false; } - this.name = this.summonData.illusion.basePokemon.name; - this.nickname = this.summonData.illusion.basePokemon.nickname; - this.shiny = this.summonData.illusion.basePokemon.shiny; - this.variant = this.summonData.illusion.basePokemon.variant; - this.fusionVariant = this.summonData.illusion.basePokemon.fusionVariant; - this.fusionShiny = this.summonData.illusion.basePokemon.fusionShiny; this.summonData.illusion = null; if (this.isOnField()) { globalScene.playSound("PRSFX- Transform"); @@ -714,8 +731,12 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { // Assets for moves loadPromises.push(loadMoveAnimations(this.getMoveset().map(m => m.getMove().id))); + /** alias for `this.summonData.illusion`; bangs on this are safe when guarded with `useIllusion` being true */ + const illusion = this.summonData.illusion; + useIllusion = useIllusion && !!illusion; + // Load the assets for the species form - const formIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.formIndex : this.formIndex; + const formIndex = useIllusion ? illusion!.formIndex : this.formIndex; loadPromises.push( this.getSpeciesForm(false, useIllusion).loadAssets( this.getGender(useIllusion) === Gender.FEMALE, @@ -732,16 +753,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { ); } if (this.getFusionSpeciesForm()) { - const fusionFormIndex = - useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex : this.fusionFormIndex; - const fusionShiny = - !useIllusion && this.summonData.illusion?.basePokemon - ? this.summonData.illusion.basePokemon.fusionShiny - : this.fusionShiny; - const fusionVariant = - !useIllusion && this.summonData.illusion?.basePokemon - ? this.summonData.illusion.basePokemon.fusionVariant - : this.fusionVariant; + const { fusionFormIndex, fusionShiny, fusionVariant } = useIllusion ? illusion! : this; loadPromises.push( this.getFusionSpeciesForm(false, useIllusion).loadAssets( this.getFusionGender(false, useIllusion) === Gender.FEMALE, @@ -929,8 +941,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { return this.getSpeciesForm(ignoreOverride, false).getSpriteKey( this.getGender(ignoreOverride) === Gender.FEMALE, this.formIndex, - this.summonData.illusion?.basePokemon.shiny ?? this.shiny, - this.summonData.illusion?.basePokemon.variant ?? this.variant, + this.isShiny(false), + this.getVariant(false), ); } @@ -973,11 +985,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { } getIconAtlasKey(ignoreOverride = false, useIllusion = true): string { - // TODO: confirm the correct behavior here (is it intentional that the check fails if `illusion.formIndex` is `0`?) - const formIndex = - useIllusion && this.summonData.illusion?.formIndex ? this.summonData.illusion.formIndex : this.formIndex; - const variant = - !useIllusion && this.summonData.illusion ? this.summonData.illusion.basePokemon.variant : this.variant; + const illusion = this.summonData.illusion; + const { formIndex, variant } = useIllusion && illusion ? illusion : this; return this.getSpeciesForm(ignoreOverride, useIllusion).getIconAtlasKey( formIndex, this.isBaseShiny(useIllusion), @@ -986,15 +995,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { } getFusionIconAtlasKey(ignoreOverride = false, useIllusion = true): string { - // TODO: confirm the correct behavior here (is it intentional that the check fails if `illusion.fusionFormIndex` is `0`?) - const fusionFormIndex = - useIllusion && this.summonData.illusion?.fusionFormIndex - ? this.summonData.illusion.fusionFormIndex - : this.fusionFormIndex; - const fusionVariant = - !useIllusion && this.summonData.illusion - ? this.summonData.illusion.basePokemon.fusionVariant - : this.fusionVariant; + const illusion = this.summonData.illusion; + const { fusionFormIndex, fusionVariant } = useIllusion && illusion ? illusion : this; return this.getFusionSpeciesForm(ignoreOverride, useIllusion).getIconAtlasKey( fusionFormIndex, this.isFusionShiny(), @@ -1002,11 +1004,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { ); } - getIconId(ignoreOverride?: boolean, useIllusion = true): string { - const formIndex = - useIllusion && this.summonData.illusion?.formIndex ? this.summonData.illusion?.formIndex : this.formIndex; - const variant = - !useIllusion && !!this.summonData.illusion ? this.summonData.illusion?.basePokemon.variant : this.variant; + getIconId(ignoreOverride?: boolean, useIllusion = false): string { + const illusion = this.summonData.illusion; + const { formIndex, variant } = useIllusion && illusion ? illusion : this; return this.getSpeciesForm(ignoreOverride, useIllusion).getIconId( this.getGender(ignoreOverride, useIllusion) === Gender.FEMALE, formIndex, @@ -1016,14 +1016,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { } getFusionIconId(ignoreOverride?: boolean, useIllusion = true): string { - const fusionFormIndex = - useIllusion && this.summonData.illusion?.fusionFormIndex - ? this.summonData.illusion?.fusionFormIndex - : this.fusionFormIndex; - const fusionVariant = - !useIllusion && !!this.summonData.illusion - ? this.summonData.illusion?.basePokemon.fusionVariant - : this.fusionVariant; + const illusion = this.summonData.illusion; + const { fusionFormIndex, fusionVariant } = useIllusion && illusion ? illusion : this; return this.getFusionSpeciesForm(ignoreOverride, useIllusion).getIconId( this.getFusionGender(ignoreOverride, useIllusion) === Gender.FEMALE, fusionFormIndex, @@ -1385,8 +1379,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { const critBoostTag = source.getTag(CritBoostTag); if (critBoostTag) { // Dragon cheer only gives +1 crit stage to non-dragon types - critStage.value += - critBoostTag instanceof DragonCheerTag && !critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 1 : 2; + critStage.value += critBoostTag.critStages; } console.log(`crit stage: +${critStage.value}`); @@ -1692,29 +1685,18 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * @returns Whether this Pokemon is shiny */ isShiny(useIllusion = false): boolean { - if (!useIllusion && this.summonData.illusion) { - return ( - this.summonData.illusion.basePokemon?.shiny || - (this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny) || - false - ); - } - - return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny); + return this.isBaseShiny(useIllusion) || this.isFusionShiny(useIllusion); } isBaseShiny(useIllusion = false) { - if (!useIllusion && this.summonData.illusion) { - return !!this.summonData.illusion.basePokemon?.shiny; - } - return this.shiny; + return useIllusion ? (this.summonData.illusion?.shiny ?? this.shiny) : this.shiny; } isFusionShiny(useIllusion = false) { - if (!useIllusion && this.summonData.illusion) { - return !!this.summonData.illusion.basePokemon?.fusionShiny; + if (!this.isFusion(useIllusion)) { + return false; } - return this.isFusion(useIllusion) && this.fusionShiny; + return useIllusion ? (this.summonData.illusion?.fusionShiny ?? this.fusionShiny) : this.fusionShiny; } /** @@ -1723,39 +1705,48 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * @returns Whether this pokemon's base and fusion counterparts are both shiny. */ isDoubleShiny(useIllusion = false): boolean { - if (!useIllusion && this.summonData.illusion?.basePokemon) { - return ( - this.isFusion(false) && - this.summonData.illusion.basePokemon.shiny && - this.summonData.illusion.basePokemon.fusionShiny - ); - } - - return this.isFusion(useIllusion) && this.shiny && this.fusionShiny; + return this.isFusion(useIllusion) && this.isBaseShiny(useIllusion) && this.isFusionShiny(useIllusion); } /** * Return this Pokemon's {@linkcode Variant | shiny variant}. + * If a fusion, returns the maximum of the two variants. * Only meaningful if this pokemon is actually shiny. * @param useIllusion - Whether to consider this pokemon's illusion if present; default `false` * @returns The shiny variant of this Pokemon. */ getVariant(useIllusion = false): Variant { - if (!useIllusion && this.summonData.illusion) { - return !this.isFusion(false) - ? this.summonData.illusion.basePokemon!.variant - : (Math.max(this.variant, this.fusionVariant) as Variant); + const illusion = this.summonData.illusion; + const baseVariant = useIllusion ? (illusion?.variant ?? this.variant) : this.variant; + if (!this.isFusion(useIllusion)) { + return baseVariant; } - - return !this.isFusion(true) ? this.variant : (Math.max(this.variant, this.fusionVariant) as Variant); + const fusionVariant = useIllusion ? (illusion?.fusionVariant ?? this.fusionVariant) : this.fusionVariant; + return Math.max(baseVariant, fusionVariant) as Variant; } - // TODO: Clarify how this differs from `getVariant` - getBaseVariant(doubleShiny: boolean): Variant { - if (doubleShiny) { - return this.summonData.illusion?.basePokemon?.variant ?? this.variant; + /** + * Return the base pokemon's variant. Equivalent to {@linkcode getVariant} if this pokemon is not a fusion. + * @returns The shiny variant of this Pokemon's base species. + */ + getBaseVariant(useIllusion = false): Variant { + const illusion = this.summonData.illusion; + return useIllusion && illusion ? (illusion.variant ?? this.variant) : this.variant; + } + + /** + * Return the fused pokemon's variant. + * + * @remarks + * Always returns `0` if the pokemon is not a fusion. + * @returns The shiny variant of this pokemon's fusion species. + */ + getFusionVariant(useIllusion = false): Variant { + if (!this.isFusion(useIllusion)) { + return 0; } - return this.getVariant(); + const illusion = this.summonData.illusion; + return illusion ? (illusion.fusionVariant ?? this.fusionVariant) : this.fusionVariant; } /** @@ -1772,7 +1763,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * @returns Whether this Pokemon is currently fused with another species. */ isFusion(useIllusion = false): boolean { - return useIllusion && this.summonData.illusion ? !!this.summonData.illusion.fusionSpecies : !!this.fusionSpecies; + return useIllusion ? !!this.summonData.illusion?.fusionSpecies : !!this.fusionSpecies; } /** @@ -1782,9 +1773,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * @see {@linkcode getNameToRender} - gets this Pokemon's display name. */ getName(useIllusion = false): string { - return !useIllusion && this.summonData.illusion?.basePokemon - ? this.summonData.illusion.basePokemon.name - : this.name; + return useIllusion ? (this.summonData.illusion?.name ?? this.name) : this.name; } /** @@ -5655,7 +5644,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { } export class PlayerPokemon extends Pokemon { - protected battleInfo: PlayerBattleInfo; + protected declare battleInfo: PlayerBattleInfo; public compatibleTms: MoveId[]; constructor( @@ -6194,7 +6183,7 @@ export class PlayerPokemon extends Pokemon { } export class EnemyPokemon extends Pokemon { - protected battleInfo: EnemyBattleInfo; + protected declare battleInfo: EnemyBattleInfo; public trainerSlot: TrainerSlot; public aiType: AiType; public bossSegments: number; diff --git a/src/field/trainer.ts b/src/field/trainer.ts index dea610b4195..1a284f268cc 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -23,13 +23,13 @@ import { } from "#trainers/trainer-party-template"; import { randSeedInt, randSeedItem, randSeedWeightedItem } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; +import { toSnakeCase } from "#utils/strings"; import i18next from "i18next"; export class Trainer extends Phaser.GameObjects.Container { public config: TrainerConfig; public variant: TrainerVariant; public partyTemplateIndex: number; - public name: string; public partnerName: string; public nameKey: string; public partnerNameKey: string | undefined; @@ -170,7 +170,7 @@ export class Trainer extends Phaser.GameObjects.Container { const evilTeamTitles = ["grunt"]; if (this.name === "" && evilTeamTitles.some(t => name.toLocaleLowerCase().includes(t))) { // This is a evil team grunt so we localize it by only using the "name" as the title - title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`); + title = i18next.t(`trainerClasses:${toSnakeCase(name)}`); console.log("Localized grunt name: " + title); // Since grunts are not named we can just return the title return title; @@ -187,7 +187,7 @@ export class Trainer extends Phaser.GameObjects.Container { } // Get the localized trainer class name from the i18n file and set it as the title. // This is used for trainer class names, not titles like "Elite Four, Champion, etc." - title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`); + title = i18next.t(`trainerClasses:${toSnakeCase(name)}`); } // If no specific trainer slot is set. @@ -208,7 +208,7 @@ export class Trainer extends Phaser.GameObjects.Container { if (this.config.titleDouble && this.variant === TrainerVariant.DOUBLE && !this.config.doubleOnly) { title = this.config.titleDouble; - name = i18next.t(`trainerNames:${this.config.nameDouble.toLowerCase().replace(/\s/g, "_")}`); + name = i18next.t(`trainerNames:${toSnakeCase(this.config.nameDouble)}`); } console.log(title ? `${title} ${name}` : name); diff --git a/src/init/init.ts b/src/init/init.ts new file mode 100644 index 00000000000..35896005b98 --- /dev/null +++ b/src/init/init.ts @@ -0,0 +1,50 @@ +import { initAbilities } from "#abilities/ability"; +import { initBiomes } from "#balance/biomes"; +import { initEggMoves } from "#balance/egg-moves"; +import { initPokemonPrevolutions, initPokemonStarters } from "#balance/pokemon-evolutions"; +import { initSpecies } from "#balance/pokemon-species"; +import { initChallenges } from "#data/challenge"; +import { initTrainerTypeDialogue } from "#data/dialogue"; +import { initPokemonForms } from "#data/pokemon-forms"; +import { initHeldItems } from "#items/all-held-items"; +import { initTrainerItems } from "#items/all-trainer-items"; +import { initHeldItemPools } from "#items/init-held-item-pools"; +import { initRewardPools } from "#items/init-reward-pools"; +import { initTrainerItemPools } from "#items/init-trainer-item-pools"; +import { initRewards } from "#items/reward"; +import { initMoves } from "#moves/move"; +import { initMysteryEncounters } from "#mystery-encounters/mystery-encounters"; +import { initAchievements } from "#system/achv"; +import { initVouchers } from "#system/voucher"; +import { initStatsKeys } from "#ui/game-stats-ui-handler"; + +/** Initialize the game. */ +export function initializeGame() { + initItems(); + initVouchers(); + initAchievements(); + initStatsKeys(); + initPokemonPrevolutions(); + initPokemonStarters(); + initBiomes(); + initEggMoves(); + initPokemonForms(); + initTrainerTypeDialogue(); + initSpecies(); + initMoves(); + initAbilities(); + initChallenges(); + initMysteryEncounters(); +} + +/** + * Sub-method to initialize all the item-related code. + */ +function initItems() { + initHeldItems(); + initHeldItemPools(); + initTrainerItems(); + initTrainerItemPools(); + initRewards(); + initRewardPools(); +} diff --git a/src/loading-scene.ts b/src/loading-scene.ts index de5e8a6515b..c5b0263e785 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -1,33 +1,16 @@ -import { initAbilities } from "#abilities/ability"; import { timedEventManager } from "#app/global-event-manager"; +import { initializeGame } from "#app/init/init"; import { SceneBase } from "#app/scene-base"; import { isMobile } from "#app/touch-controls"; -import { initBiomes } from "#balance/biomes"; -import { initEggMoves } from "#balance/egg-moves"; -import { initPokemonPrevolutions, initPokemonStarters } from "#balance/pokemon-evolutions"; -import { initChallenges } from "#data/challenge"; -import { initTrainerTypeDialogue } from "#data/dialogue"; -import { initPokemonForms } from "#data/pokemon-forms"; -import { initSpecies } from "#data/pokemon-species"; import { BiomeId } from "#enums/biome-id"; import { GachaType } from "#enums/gacha-types"; import { getBiomeHasProps } from "#field/arena"; -import { initHeldItems } from "#items/all-held-items"; -import { initTrainerItems } from "#items/all-trainer-items"; -import { initHeldItemPools } from "#items/init-held-item-pools"; -import { initRewardPools } from "#items/init-reward-pools"; -import { initTrainerItemPools } from "#items/init-trainer-item-pools"; -import { initRewards } from "#items/reward"; -import { initMoves } from "#moves/move"; -import { initMysteryEncounters } from "#mystery-encounters/mystery-encounters"; import { CacheBustedLoaderPlugin } from "#plugins/cache-busted-loader-plugin"; -import { initAchievements } from "#system/achv"; -import { initVouchers } from "#system/voucher"; -import { initStatsKeys } from "#ui/game-stats-ui-handler"; import { getWindowVariantSuffix, WindowVariant } from "#ui/ui-theme"; import { hasAllLocalizedSprites, localPing } from "#utils/common"; import { getEnumValues } from "#utils/enums"; import i18next from "i18next"; +import type { GameObjects } from "phaser"; export class LoadingScene extends SceneBase { public static readonly KEY = "loading"; @@ -136,6 +119,7 @@ export class LoadingScene extends SceneBase { this.loadImage("party_bg", "ui"); this.loadImage("party_bg_double", "ui"); + this.loadImage("party_bg_double_manage", "ui"); this.loadAtlas("party_slot_main", "ui"); this.loadAtlas("party_slot", "ui"); this.loadImage("party_slot_overlay_lv", "ui"); @@ -143,6 +127,8 @@ export class LoadingScene extends SceneBase { this.loadAtlas("party_slot_hp_overlay", "ui"); this.loadAtlas("party_pb", "ui"); this.loadAtlas("party_cancel", "ui"); + this.loadAtlas("party_discard", "ui"); + this.loadAtlas("party_transfer", "ui"); this.loadImage("summary_bg", "ui"); this.loadImage("summary_overlay_shiny", "ui"); @@ -370,34 +356,12 @@ export class LoadingScene extends SceneBase { this.loadLoadingScreen(); - initRewards(); - initRewardPools(); - initHeldItemPools(); - initTrainerItemPools(); - - initAchievements(); - initVouchers(); - initStatsKeys(); - initPokemonPrevolutions(); - initPokemonStarters(); - initBiomes(); - initEggMoves(); - initPokemonForms(); - initTrainerTypeDialogue(); - initSpecies(); - initMoves(); - initAbilities(); - initHeldItems(); - initTrainerItems(); - initChallenges(); - initMysteryEncounters(); + initializeGame(); } loadLoadingScreen() { const mobile = isMobile(); - const loadingGraphics: any[] = []; - const bg = this.add.image(0, 0, ""); bg.setOrigin(0, 0); bg.setScale(6); @@ -468,6 +432,7 @@ export class LoadingScene extends SceneBase { }); disclaimerDescriptionText.setOrigin(0.5, 0.5); + const loadingGraphics: (GameObjects.Image | GameObjects.Graphics | GameObjects.Text)[] = []; loadingGraphics.push( bg, graphics, diff --git a/src/phase-manager.ts b/src/phase-manager.ts index db488e1583f..be2045cba18 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -9,6 +9,7 @@ import { AttemptCapturePhase } from "#phases/attempt-capture-phase"; import { AttemptRunPhase } from "#phases/attempt-run-phase"; import { BattleEndPhase } from "#phases/battle-end-phase"; import { BerryPhase } from "#phases/berry-phase"; +import { CheckInterludePhase } from "#phases/check-interlude-phase"; import { CheckStatusEffectPhase } from "#phases/check-status-effect-phase"; import { CheckSwitchPhase } from "#phases/check-switch-phase"; import { CommandPhase } from "#phases/command-phase"; @@ -59,6 +60,7 @@ import { PartyHealPhase } from "#phases/party-heal-phase"; import { PokemonAnimPhase } from "#phases/pokemon-anim-phase"; import { PokemonHealPhase } from "#phases/pokemon-heal-phase"; import { PokemonTransformPhase } from "#phases/pokemon-transform-phase"; +import { PositionalTagPhase } from "#phases/positional-tag-phase"; import { PostGameOverPhase } from "#phases/post-game-over-phase"; import { PostSummonPhase } from "#phases/post-summon-phase"; import { PostTurnStatusEffectPhase } from "#phases/post-turn-status-effect-phase"; @@ -121,6 +123,7 @@ const PHASES = Object.freeze({ AttemptRunPhase, BattleEndPhase, BerryPhase, + CheckInterludePhase, CheckStatusEffectPhase, CheckSwitchPhase, CommandPhase, @@ -170,6 +173,7 @@ const PHASES = Object.freeze({ PokemonAnimPhase, PokemonHealPhase, PokemonTransformPhase, + PositionalTagPhase, PostGameOverPhase, PostSummonPhase, PostTurnStatusEffectPhase, @@ -240,6 +244,21 @@ export class PhaseManager { this.dynamicPhaseTypes = [PostSummonPhase]; } + /** + * Clear all previously set phases, then add a new {@linkcode TitlePhase} to transition to the title screen. + * @param addLogin - Whether to add a new {@linkcode LoginPhase} before the {@linkcode TitlePhase} + * (but reset everything else). + * Default `false` + */ + public toTitleScreen(addLogin = false): void { + this.clearAllPhases(); + + if (addLogin) { + this.unshiftNew("LoginPhase"); + } + this.unshiftNew("TitlePhase"); + } + /* Phase Functions */ getCurrentPhase(): Phase | null { return this.currentPhase; @@ -303,7 +322,6 @@ export class PhaseManager { queue.splice(0, queue.length); } this.dynamicPhaseQueues.forEach(queue => queue.clear()); - this.currentPhase = null; this.standbyPhase = null; this.clearPhaseQueueSplice(); } @@ -665,4 +683,15 @@ export class PhaseManager { ): void { this.startDynamicPhase(this.create(phase, ...args)); } + + /** Prevents end of turn effects from triggering when transitioning to a new biome on a X0 wave */ + public onInterlude(): void { + const phasesToRemove = ["WeatherEffectPhase", "BerryPhase", "CheckStatusEffectPhase"]; + this.phaseQueue = this.phaseQueue.filter(p => !phasesToRemove.includes(p.phaseName)); + + const turnEndPhase = this.findPhase(p => p.phaseName === "TurnEndPhase"); + if (turnEndPhase) { + turnEndPhase.upcomingInterlude = true; + } + } } diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index f499907c5e2..49f6c7d41d5 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -276,6 +276,7 @@ export class AttemptCapturePhase extends PokemonPhase { globalScene.updateItems(true); removePokemon(); if (newPokemon) { + newPokemon.leaveField(true, true, false); newPokemon.loadAssets().then(end); } else { end(); diff --git a/src/phases/check-interlude-phase.ts b/src/phases/check-interlude-phase.ts new file mode 100644 index 00000000000..1589f74f058 --- /dev/null +++ b/src/phases/check-interlude-phase.ts @@ -0,0 +1,18 @@ +import { globalScene } from "#app/global-scene"; +import { Phase } from "#app/phase"; + +export class CheckInterludePhase extends Phase { + public override readonly phaseName = "CheckInterludePhase"; + + public override start(): void { + super.start(); + const { phaseManager } = globalScene; + const { waveIndex } = globalScene.currentBattle; + + if (waveIndex % 10 === 0 && globalScene.getEnemyParty().every(p => p.isFainted())) { + phaseManager.onInterlude(); + } + + this.end(); + } +} diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 14674037fbe..016d4ff5d3b 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -2,7 +2,6 @@ import type { TurnCommand } from "#app/battle"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { speciesStarterCosts } from "#balance/starters"; -import type { EncoreTag } from "#data/battler-tags"; import { TrappedTag } from "#data/battler-tags"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagSide } from "#enums/arena-tag-side"; @@ -22,59 +21,77 @@ import type { MoveTargetSet } from "#moves/move"; import { getMoveTargets } from "#moves/move-utils"; import { FieldPhase } from "#phases/field-phase"; import type { TurnMove } from "#types/turn-move"; -import { isNullOrUndefined } from "#utils/common"; import i18next from "i18next"; export class CommandPhase extends FieldPhase { public readonly phaseName = "CommandPhase"; protected fieldIndex: number; + /** + * Whether the command phase is handling a switch command + */ + private isSwitch = false; + constructor(fieldIndex: number) { super(); this.fieldIndex = fieldIndex; } - start() { - super.start(); - - globalScene.updateGameInfo(); - + /** + * Resets the cursor to the position of {@linkcode Command.FIGHT} if any of the following are true + * - The setting to remember the last action is not enabled + * - This is the first turn of a mystery encounter, trainer battle, or the END biome + * - The cursor is currently on the POKEMON command + */ + private resetCursorIfNeeded(): void { const commandUiHandler = globalScene.ui.handlers[UiMode.COMMAND]; + const { arena, commandCursorMemory, currentBattle } = globalScene; + const { battleType, turn } = currentBattle; + const { biomeType } = arena; // If one of these conditions is true, we always reset the cursor to Command.FIGHT const cursorResetEvent = - globalScene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER || - globalScene.currentBattle.battleType === BattleType.TRAINER || - globalScene.arena.biomeType === BiomeId.END; + battleType === BattleType.MYSTERY_ENCOUNTER || battleType === BattleType.TRAINER || biomeType === BiomeId.END; - if (commandUiHandler) { - if ( - (globalScene.currentBattle.turn === 1 && (!globalScene.commandCursorMemory || cursorResetEvent)) || - commandUiHandler.getCursor() === Command.POKEMON - ) { - commandUiHandler.setCursor(Command.FIGHT); - } else { - commandUiHandler.setCursor(commandUiHandler.getCursor()); - } + if (!commandUiHandler) { + return; + } + if ( + (turn === 1 && (!commandCursorMemory || cursorResetEvent)) || + commandUiHandler.getCursor() === Command.POKEMON + ) { + commandUiHandler.setCursor(Command.FIGHT); + } + } + + /** + * Submethod of {@linkcode start} that validates field index logic for nonzero field indices. + * Must only be called if the field index is nonzero. + */ + private handleFieldIndexLogic(): void { + // If we somehow are attempting to check the right pokemon but there's only one pokemon out + // Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching + // TODO: Prevent this from happening in the first place + if (globalScene.getPlayerField().filter(p => p.isActive()).length === 1) { + this.fieldIndex = FieldPosition.CENTER; + return; } - if (this.fieldIndex) { - // If we somehow are attempting to check the right pokemon but there's only one pokemon out - // Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching - if (globalScene.getPlayerField().filter(p => p.isActive()).length === 1) { - this.fieldIndex = FieldPosition.CENTER; - } else { - const allyCommand = globalScene.currentBattle.turnCommands[this.fieldIndex - 1]; - if (allyCommand?.command === Command.BALL || allyCommand?.command === Command.RUN) { - globalScene.currentBattle.turnCommands[this.fieldIndex] = { - command: allyCommand?.command, - skip: true, - }; - } - } + const allyCommand = globalScene.currentBattle.turnCommands[this.fieldIndex - 1]; + if (allyCommand?.command === Command.BALL || allyCommand?.command === Command.RUN) { + globalScene.currentBattle.turnCommands[this.fieldIndex] = { + command: allyCommand?.command, + skip: true, + }; } + } + /** + * Submethod of {@linkcode start} that sets the turn command to skip if this pokemon + * is commanding its ally via {@linkcode AbilityId.COMMANDER}. + */ + private checkCommander(): void { // If the Pokemon has applied Commander's effects to its ally, skip this command if ( globalScene.currentBattle?.double && @@ -86,377 +103,521 @@ export class CommandPhase extends FieldPhase { skip: true, }; } + } - // Checks if the Pokemon is under the effects of Encore. If so, Encore can end early if the encored move has no more PP. - const encoreTag = this.getPokemon().getTag(BattlerTagType.ENCORE) as EncoreTag | undefined; - if (encoreTag) { - this.getPokemon().lapseTag(BattlerTagType.ENCORE); - } - - if (globalScene.currentBattle.turnCommands[this.fieldIndex]?.skip) { - return this.end(); - } - - const playerPokemon = globalScene.getPlayerField()[this.fieldIndex]; - + /** + * Clear out all unusable moves in front of the currently acting pokemon's move queue. + */ + // TODO: Refactor move queue handling to ensure that this method is not necessary. + private clearUnusuableMoves(): void { + const playerPokemon = this.getPokemon(); const moveQueue = playerPokemon.getMoveQueue(); - - while ( - moveQueue.length && - moveQueue[0] && - moveQueue[0].move && - !isVirtual(moveQueue[0].useMode) && - (!playerPokemon.getMoveset().find(m => m.moveId === moveQueue[0].move) || - !playerPokemon - .getMoveset() - [playerPokemon.getMoveset().findIndex(m => m.moveId === moveQueue[0].move)].isUsable( - playerPokemon, - isIgnorePP(moveQueue[0].useMode), - )) - ) { - moveQueue.shift(); + if (moveQueue.length === 0) { + return; } - // TODO: Refactor this. I did a few simple find/replace matches but this is just ABHORRENTLY structured - if (moveQueue.length > 0) { - const queuedMove = moveQueue[0]; - if (!queuedMove.move) { - this.handleCommand(Command.FIGHT, -1, MoveUseMode.NORMAL); - } else { - const moveIndex = playerPokemon.getMoveset().findIndex(m => m.moveId === queuedMove.move); - if ( - (moveIndex > -1 && - playerPokemon.getMoveset()[moveIndex].isUsable(playerPokemon, isIgnorePP(queuedMove.useMode))) || - isVirtual(queuedMove.useMode) - ) { - this.handleCommand(Command.FIGHT, moveIndex, queuedMove.useMode, queuedMove); - } else { - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - } - } - } else { + let entriesToDelete = 0; + const moveset = playerPokemon.getMoveset(); + for (const queuedMove of moveQueue) { + const movesetQueuedMove = moveset.find(m => m.moveId === queuedMove.move); if ( - globalScene.currentBattle.isBattleMysteryEncounter() && - globalScene.currentBattle.mysteryEncounter?.skipToFightInput + queuedMove.move !== MoveId.NONE && + !isVirtual(queuedMove.useMode) && + !movesetQueuedMove?.isUsable(playerPokemon, isIgnorePP(queuedMove.useMode)) ) { - globalScene.ui.clearText(); - globalScene.ui.setMode(UiMode.FIGHT, this.fieldIndex); + entriesToDelete++; } else { - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); + break; } } + if (entriesToDelete) { + moveQueue.splice(0, entriesToDelete); + } } /** - * TODO: Remove `args` and clean this thing up - * Code will need to be copied over from pkty except replacing the `virtual` and `ignorePP` args with a corresponding `MoveUseMode`. + * Attempt to execute the first usable move in this Pokemon's move queue + * @returns Whether a queued move was successfully set to be executed. */ - handleCommand(command: Command, cursor: number, ...args: any[]): boolean { + private tryExecuteQueuedMove(): boolean { + this.clearUnusuableMoves(); const playerPokemon = globalScene.getPlayerField()[this.fieldIndex]; + const moveQueue = playerPokemon.getMoveQueue(); + + if (moveQueue.length === 0) { + return false; + } + + const queuedMove = moveQueue[0]; + if (queuedMove.move === MoveId.NONE) { + this.handleCommand(Command.FIGHT, -1); + return true; + } + const moveIndex = playerPokemon.getMoveset().findIndex(m => m.moveId === queuedMove.move); + if (!isVirtual(queuedMove.useMode) && moveIndex === -1) { + globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); + } else { + this.handleCommand(Command.FIGHT, moveIndex, queuedMove.useMode, queuedMove); + } + + return true; + } + + public override start(): void { + super.start(); + + globalScene.updateGameInfo(); + this.resetCursorIfNeeded(); + + if (this.fieldIndex) { + this.handleFieldIndexLogic(); + } + + this.checkCommander(); + + const playerPokemon = this.getPokemon(); + + // Note: It is OK to call this if the target is not under the effect of encore; it will simply do nothing. + playerPokemon.lapseTag(BattlerTagType.ENCORE); + + if (globalScene.currentBattle.turnCommands[this.fieldIndex]?.skip) { + this.end(); + return; + } + + if (this.tryExecuteQueuedMove()) { + return; + } + + if ( + globalScene.currentBattle.isBattleMysteryEncounter() && + globalScene.currentBattle.mysteryEncounter?.skipToFightInput + ) { + globalScene.ui.clearText(); + globalScene.ui.setMode(UiMode.FIGHT, this.fieldIndex); + } else { + globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); + } + } + + /** + * Submethod of {@linkcode handleFightCommand} responsible for queuing the appropriate + * error message when a move cannot be used. + * @param user - The pokemon using the move + * @param cursor - The index of the move in the moveset + */ + private queueFightErrorMessage(user: PlayerPokemon, cursor: number) { + const move = user.getMoveset()[cursor]; + globalScene.ui.setMode(UiMode.MESSAGE); + + // Decides between a Disabled, Not Implemented, or No PP translation message + const errorMessage = user.isMoveRestricted(move.moveId, user) + ? user.getRestrictingTag(move.moveId, user)!.selectionDeniedText(user, move.moveId) + : move.getName().endsWith(" (N)") + ? "battle:moveNotImplemented" + : "battle:moveNoPP"; + const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator + + globalScene.ui.showText( + i18next.t(errorMessage, { moveName: moveName }), + null, + () => { + globalScene.ui.clearText(); + globalScene.ui.setMode(UiMode.FIGHT, this.fieldIndex); + }, + null, + true, + ); + } + + /** + * Helper method for {@linkcode handleFightCommand} that returns the moveID for the phase + * based on the move passed in or the cursor. + * + * Does not check if the move is usable or not, that should be handled by the caller. + */ + private computeMoveId(playerPokemon: PlayerPokemon, cursor: number, move: TurnMove | undefined): MoveId { + return move?.move ?? (cursor > -1 ? playerPokemon.getMoveset()[cursor]?.moveId : MoveId.NONE); + } + + /** + * Process the logic for executing a fight-related command + * + * @remarks + * - Validates whether the move can be used, using struggle if not + * - Constructs the turn command and inserts it into the battle's turn commands + * + * @param command - The command to handle (FIGHT or TERA) + * @param cursor - The index that the cursor is placed on, or -1 if no move can be selected. + * @param ignorePP - Whether to ignore PP when checking if the move can be used. + * @param move - The move to force the command to use, if any. + */ + private handleFightCommand( + command: Command.FIGHT | Command.TERA, + cursor: number, + useMode: MoveUseMode = MoveUseMode.NORMAL, + move?: TurnMove, + ): boolean { + const playerPokemon = this.getPokemon(); + const ignorePP = isIgnorePP(useMode); + + let canUse = cursor === -1 || playerPokemon.trySelectMove(cursor, ignorePP); + + // Ternary here ensures we don't compute struggle conditions unless necessary + const useStruggle = canUse + ? false + : cursor > -1 && !playerPokemon.getMoveset().some(m => m.isUsable(playerPokemon)); + + canUse ||= useStruggle; + + if (!canUse) { + this.queueFightErrorMessage(playerPokemon, cursor); + return false; + } + + const moveId = useStruggle ? MoveId.STRUGGLE : this.computeMoveId(playerPokemon, cursor, move); + + const turnCommand: TurnCommand = { + command: Command.FIGHT, + cursor, + move: { move: moveId, targets: [], useMode }, + args: [useMode, move], + }; + const preTurnCommand: TurnCommand = { + command, + targets: [this.fieldIndex], + skip: command === Command.FIGHT, + }; + + const moveTargets: MoveTargetSet = + move === undefined + ? getMoveTargets(playerPokemon, moveId) + : { + targets: move.targets, + multiple: move.targets.length > 1, + }; + + if (moveId === MoveId.NONE) { + turnCommand.targets = [this.fieldIndex]; + } + + console.log( + "Move:", + MoveId[moveId], + "Move targets:", + moveTargets, + "\nPlayer Pokemon:", + getPokemonNameWithAffix(playerPokemon), + ); + + if (moveTargets.targets.length > 1 && moveTargets.multiple) { + globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex); + } + + if (turnCommand.move && (moveTargets.targets.length <= 1 || moveTargets.multiple)) { + turnCommand.move.targets = moveTargets.targets; + } else if ( + turnCommand.move && + playerPokemon.getTag(BattlerTagType.CHARGING) && + playerPokemon.getMoveQueue().length >= 1 + ) { + turnCommand.move.targets = playerPokemon.getMoveQueue()[0].targets; + } else { + globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex); + } + + globalScene.currentBattle.preTurnCommands[this.fieldIndex] = preTurnCommand; + globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; + + return true; + } + + /** + * Set the mode in preparation to show the text, and then show the text. + * Only works for parameterless i18next keys. + * @param key - The i18next key for the text to show + */ + private queueShowText(key: string): void { + globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(UiMode.MESSAGE); + + globalScene.ui.showText( + i18next.t(key), + null, + () => { + globalScene.ui.showText("", 0); + globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); + }, + null, + true, + ); + } + + /** + * Helper method for {@linkcode handleBallCommand} that checks if a pokeball can be thrown + * and displays the appropriate error message. + * + * @remarks + * The pokeball may not be thrown if any of the following are true: + * - It is a trainer battle + * - The player is in the {@linkcode BiomeId.END | End} biome and + * - it is not classic mode; or + * - the fresh start challenge is active; or + * - the player has not caught the target before and the player is still missing more than one starter + * - The player is in a mystery encounter that disallows catching the pokemon + * @returns Whether a pokeball can be thrown + */ + private checkCanUseBall(): boolean { + const { arena, currentBattle, gameData, gameMode } = globalScene; + const { battleType } = currentBattle; + const { biomeType } = arena; + const { isClassic } = gameMode; + const { dexData } = gameData; + + const someUncaughtSpeciesOnField = globalScene + .getEnemyField() + .some(p => p.isActive() && !dexData[p.species.speciesId].caughtAttr); + const missingMultipleStarters = + gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1; + if ( + biomeType === BiomeId.END && + (!isClassic || gameMode.isFreshStartChallenge() || (someUncaughtSpeciesOnField && missingMultipleStarters)) + ) { + this.queueShowText("battle:noPokeballForce"); + } else if (battleType === BattleType.TRAINER) { + this.queueShowText("battle:noPokeballTrainer"); + } else if (currentBattle.isBattleMysteryEncounter() && !currentBattle.mysteryEncounter!.catchAllowed) { + this.queueShowText("battle:noPokeballMysteryEncounter"); + } else { + return true; + } + + return false; + } + + /** + * Helper method for {@linkcode handleCommand} that handles the logic when the selected command is to use a pokeball. + * + * @param cursor - The index of the pokeball to use + * @returns Whether the command was successfully initiated + */ + private handleBallCommand(cursor: number): boolean { + const targets = globalScene + .getEnemyField() + .filter(p => p.isActive(true)) + .map(p => p.getBattlerIndex()); + if (targets.length > 1) { + this.queueShowText("battle:noPokeballMulti"); + return false; + } + + if (!this.checkCanUseBall()) { + return false; + } + + const numBallTypes = 5; + if (cursor < numBallTypes) { + const targetPokemon = globalScene.getEnemyPokemon(); + if ( + targetPokemon?.isBoss() && + targetPokemon?.bossSegmentIndex >= 1 && + // TODO: Decouple this hardcoded exception for wonder guard and just check the target... + !targetPokemon?.hasAbility(AbilityId.WONDER_GUARD, false, true) && + cursor < PokeballType.MASTER_BALL + ) { + this.queueShowText("battle:noPokeballStrong"); + return false; + } + + globalScene.currentBattle.turnCommands[this.fieldIndex] = { + command: Command.BALL, + cursor: cursor, + }; + globalScene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets; + if (this.fieldIndex) { + globalScene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; + } + return true; + } + + return false; + } + + /** + * Submethod of {@linkcode tryLeaveField} to handle the logic for effects that prevent the pokemon from leaving the field + * due to trapping abilities or effects. + * + * This method queues the proper messages in the case of trapping abilities or effects. + * + * @returns Whether the pokemon is currently trapped + */ + private handleTrap(): boolean { + const playerPokemon = this.getPokemon(); + const trappedAbMessages: string[] = []; + const isSwitch = this.isSwitch; + if (!playerPokemon.isTrapped(trappedAbMessages)) { + return false; + } + if (trappedAbMessages.length > 0) { + if (isSwitch) { + globalScene.ui.setMode(UiMode.MESSAGE); + } + globalScene.ui.showText( + trappedAbMessages[0], + null, + () => { + globalScene.ui.showText("", 0); + if (isSwitch) { + globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); + } + }, + null, + true, + ); + } else { + const trapTag = playerPokemon.getTag(TrappedTag); + const fairyLockTag = globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.PLAYER); + + if (!isSwitch) { + globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(UiMode.MESSAGE); + } + if (trapTag) { + this.showNoEscapeText(trapTag, false); + } else if (fairyLockTag) { + this.showNoEscapeText(fairyLockTag, false); + } + } + + return true; + } + + /** + * Common helper method that attempts to have the pokemon leave the field. + * Checks for trapping abilities and effects. + * + * @param cursor - The index of the option that the cursor is on + * @returns Whether the pokemon is able to leave the field, indicating the command phase should end + */ + private tryLeaveField(cursor?: number, isBatonSwitch = false): boolean { + const currentBattle = globalScene.currentBattle; + + if (isBatonSwitch || !this.handleTrap()) { + currentBattle.turnCommands[this.fieldIndex] = this.isSwitch + ? { + command: Command.POKEMON, + cursor, + args: [isBatonSwitch], + } + : { + command: Command.RUN, + }; + if (!this.isSwitch && this.fieldIndex) { + currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; + } + return true; + } + + return false; + } + + /** + * Helper method for {@linkcode handleCommand} that handles the logic when the selected command is RUN. + * + * @remarks + * Checks if the player is allowed to flee, and if not, queues the appropriate message. + * + * The player cannot flee if: + * - The player is in the {@linkcode BiomeId.END | End} biome + * - The player is in a trainer battle + * - The player is in a mystery encounter that disallows fleeing + * - The player's pokemon is trapped by an ability or effect + * @returns Whether the pokemon is able to leave the field, indicating the command phase should end + */ + private handleRunCommand(): boolean { + const { currentBattle, arena } = globalScene; + const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed ?? true; + if (arena.biomeType === BiomeId.END || !mysteryEncounterFleeAllowed) { + this.queueShowText("battle:noEscapeForce"); + return false; + } + if ( + currentBattle.battleType === BattleType.TRAINER || + currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE + ) { + this.queueShowText("battle:noEscapeTrainer"); + return false; + } + + const success = this.tryLeaveField(); + + return success; + } + + /** + * Show a message indicating that the pokemon cannot escape, and then return to the command phase. + */ + private showNoEscapeText(tag: any, isSwitch: boolean): void { + globalScene.ui.showText( + i18next.t("battle:noEscapePokemon", { + pokemonName: + tag.sourceId && globalScene.getPokemonById(tag.sourceId) + ? getPokemonNameWithAffix(globalScene.getPokemonById(tag.sourceId)!) + : "", + moveName: tag.getMoveName(), + escapeVerb: i18next.t(isSwitch ? "battle:escapeVerbSwitch" : "battle:escapeVerbFlee"), + }), + null, + () => { + globalScene.ui.showText("", 0); + if (!isSwitch) { + globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); + } + }, + null, + true, + ); + } + + // Overloads for handleCommand to provide a more specific signature for the different options + /** + * Process the command phase logic based on the selected command + * + * @param command - The kind of command to handle + * @param cursor - The index of option that the cursor is on, or -1 if no option is selected + * @param useMode - The mode to use for the move, if applicable. For switches, a boolean that specifies whether the switch is a Baton switch. + * @param move - For {@linkcode Command.FIGHT}, the move to use + * @returns Whether the command was successful + */ + handleCommand(command: Command.FIGHT | Command.TERA, cursor: number, useMode?: MoveUseMode, move?: TurnMove): boolean; + handleCommand(command: Command.BALL, cursor: number): boolean; + handleCommand(command: Command.POKEMON, cursor: number, useBaton: boolean): boolean; + handleCommand(command: Command.RUN, cursor: number): boolean; + handleCommand(command: Command, cursor: number, useMode?: boolean | MoveUseMode, move?: TurnMove): boolean; + + public handleCommand( + command: Command, + cursor: number, + useMode: boolean | MoveUseMode = false, + move?: TurnMove, + ): boolean { let success = false; switch (command) { - // TODO: We don't need 2 args for this - moveUseMode is carried over from queuedMove case Command.TERA: - case Command.FIGHT: { - let useStruggle = false; - const turnMove: TurnMove | undefined = args.length === 2 ? (args[1] as TurnMove) : undefined; - if ( - cursor === -1 || - playerPokemon.trySelectMove(cursor, isIgnorePP(args[0] as MoveUseMode)) || - (useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m.isUsable(playerPokemon)).length) - ) { - let moveId: MoveId; - if (useStruggle) { - moveId = MoveId.STRUGGLE; - } else if (turnMove !== undefined) { - moveId = turnMove.move; - } else if (cursor > -1) { - moveId = playerPokemon.getMoveset()[cursor].moveId; - } else { - moveId = MoveId.NONE; - } - - const turnCommand: TurnCommand = { - command: Command.FIGHT, - cursor: cursor, - move: { move: moveId, targets: [], useMode: args[0] }, - args: args, - }; - const preTurnCommand: TurnCommand = { - command: command, - targets: [this.fieldIndex], - skip: command === Command.FIGHT, - }; - const moveTargets: MoveTargetSet = - turnMove === undefined - ? getMoveTargets(playerPokemon, moveId) - : { - targets: turnMove.targets, - multiple: turnMove.targets.length > 1, - }; - if (!moveId) { - turnCommand.targets = [this.fieldIndex]; - } - console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); - if (moveTargets.targets.length > 1 && moveTargets.multiple) { - globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex); - } - if (turnCommand.move && (moveTargets.targets.length <= 1 || moveTargets.multiple)) { - turnCommand.move.targets = moveTargets.targets; - } else if ( - turnCommand.move && - playerPokemon.getTag(BattlerTagType.CHARGING) && - playerPokemon.getMoveQueue().length >= 1 - ) { - turnCommand.move.targets = playerPokemon.getMoveQueue()[0].targets; - } else { - globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex); - } - globalScene.currentBattle.preTurnCommands[this.fieldIndex] = preTurnCommand; - globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; - success = true; - } else if (cursor < playerPokemon.getMoveset().length) { - const move = playerPokemon.getMoveset()[cursor]; - globalScene.ui.setMode(UiMode.MESSAGE); - - // Decides between a Disabled, Not Implemented, or No PP translation message - const errorMessage = playerPokemon.isMoveRestricted(move.moveId, playerPokemon) - ? playerPokemon - .getRestrictingTag(move.moveId, playerPokemon)! - .selectionDeniedText(playerPokemon, move.moveId) - : move.getName().endsWith(" (N)") - ? "battle:moveNotImplemented" - : "battle:moveNoPP"; - const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator - - globalScene.ui.showText( - i18next.t(errorMessage, { moveName: moveName }), - null, - () => { - globalScene.ui.clearText(); - globalScene.ui.setMode(UiMode.FIGHT, this.fieldIndex); - }, - null, - true, - ); - } + case Command.FIGHT: + success = this.handleFightCommand(command, cursor, typeof useMode === "boolean" ? undefined : useMode, move); break; - } - case Command.BALL: { - const notInDex = - globalScene - .getEnemyField() - .filter(p => p.isActive(true)) - .some(p => !globalScene.gameData.dexData[p.species.speciesId].caughtAttr) && - globalScene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1; - if ( - globalScene.arena.biomeType === BiomeId.END && - (!globalScene.gameMode.isClassic || globalScene.gameMode.isFreshStartChallenge() || notInDex) - ) { - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - globalScene.ui.setMode(UiMode.MESSAGE); - globalScene.ui.showText( - i18next.t("battle:noPokeballForce"), - null, - () => { - globalScene.ui.showText("", 0); - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - }, - null, - true, - ); - } else if (globalScene.currentBattle.battleType === BattleType.TRAINER) { - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - globalScene.ui.setMode(UiMode.MESSAGE); - globalScene.ui.showText( - i18next.t("battle:noPokeballTrainer"), - null, - () => { - globalScene.ui.showText("", 0); - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - }, - null, - true, - ); - } else if ( - globalScene.currentBattle.isBattleMysteryEncounter() && - !globalScene.currentBattle.mysteryEncounter!.catchAllowed - ) { - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - globalScene.ui.setMode(UiMode.MESSAGE); - globalScene.ui.showText( - i18next.t("battle:noPokeballMysteryEncounter"), - null, - () => { - globalScene.ui.showText("", 0); - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - }, - null, - true, - ); - } else { - const targets = globalScene - .getEnemyField() - .filter(p => p.isActive(true)) - .map(p => p.getBattlerIndex()); - if (targets.length > 1) { - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - globalScene.ui.setMode(UiMode.MESSAGE); - globalScene.ui.showText( - i18next.t("battle:noPokeballMulti"), - null, - () => { - globalScene.ui.showText("", 0); - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - }, - null, - true, - ); - } else if (cursor < 5) { - const targetPokemon = globalScene.getEnemyField().find(p => p.isActive(true)); - if ( - targetPokemon?.isBoss() && - targetPokemon?.bossSegmentIndex >= 1 && - !targetPokemon?.hasAbility(AbilityId.WONDER_GUARD, false, true) && - cursor < PokeballType.MASTER_BALL - ) { - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - globalScene.ui.setMode(UiMode.MESSAGE); - globalScene.ui.showText( - i18next.t("battle:noPokeballStrong"), - null, - () => { - globalScene.ui.showText("", 0); - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - }, - null, - true, - ); - } else { - globalScene.currentBattle.turnCommands[this.fieldIndex] = { - command: Command.BALL, - cursor: cursor, - }; - globalScene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets; - if (this.fieldIndex) { - globalScene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; - } - success = true; - } - } - } + case Command.BALL: + success = this.handleBallCommand(cursor); break; - } case Command.POKEMON: - case Command.RUN: { - const isSwitch = command === Command.POKEMON; - const { currentBattle, arena } = globalScene; - const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed; - if ( - !isSwitch && - (arena.biomeType === BiomeId.END || - (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed)) - ) { - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - globalScene.ui.setMode(UiMode.MESSAGE); - globalScene.ui.showText( - i18next.t("battle:noEscapeForce"), - null, - () => { - globalScene.ui.showText("", 0); - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - }, - null, - true, - ); - } else if ( - !isSwitch && - (currentBattle.battleType === BattleType.TRAINER || - currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) - ) { - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - globalScene.ui.setMode(UiMode.MESSAGE); - globalScene.ui.showText( - i18next.t("battle:noEscapeTrainer"), - null, - () => { - globalScene.ui.showText("", 0); - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - }, - null, - true, - ); - } else { - const batonPass = isSwitch && (args[0] as boolean); - const trappedAbMessages: string[] = []; - if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) { - currentBattle.turnCommands[this.fieldIndex] = isSwitch - ? { command: Command.POKEMON, cursor: cursor, args: args } - : { command: Command.RUN }; - success = true; - if (!isSwitch && this.fieldIndex) { - currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; - } - } else if (trappedAbMessages.length > 0) { - if (!isSwitch) { - globalScene.ui.setMode(UiMode.MESSAGE); - } - globalScene.ui.showText( - trappedAbMessages[0], - null, - () => { - globalScene.ui.showText("", 0); - if (!isSwitch) { - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - } - }, - null, - true, - ); - } else { - const trapTag = playerPokemon.getTag(TrappedTag); - const fairyLockTag = globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.PLAYER); - - if (!trapTag && !fairyLockTag) { - i18next.t(`battle:noEscape${isSwitch ? "Switch" : "Flee"}`); - break; - } - if (!isSwitch) { - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - globalScene.ui.setMode(UiMode.MESSAGE); - } - const showNoEscapeText = (tag: any) => { - globalScene.ui.showText( - i18next.t("battle:noEscapePokemon", { - pokemonName: - tag.sourceId && globalScene.getPokemonById(tag.sourceId) - ? getPokemonNameWithAffix(globalScene.getPokemonById(tag.sourceId)!) - : "", - moveName: tag.getMoveName(), - escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee"), - }), - null, - () => { - globalScene.ui.showText("", 0); - if (!isSwitch) { - globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex); - } - }, - null, - true, - ); - }; - - if (trapTag) { - showNoEscapeText(trapTag); - } else if (fairyLockTag) { - showNoEscapeText(fairyLockTag); - } - } - } + this.isSwitch = true; + success = this.tryLeaveField(cursor, typeof useMode === "boolean" ? useMode : undefined); + this.isSwitch = false; break; - } + case Command.RUN: + success = this.handleRunCommand(); } if (success) { diff --git a/src/phases/end-card-phase.ts b/src/phases/end-card-phase.ts index 5c3f6e1bf9b..b9b383db13d 100644 --- a/src/phases/end-card-phase.ts +++ b/src/phases/end-card-phase.ts @@ -1,7 +1,8 @@ import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; import { PlayerGender } from "#enums/player-gender"; -import { addTextObject, TextStyle } from "#ui/text"; +import { TextStyle } from "#enums/text-style"; +import { addTextObject } from "#ui/text"; import i18next from "i18next"; export class EndCardPhase extends Phase { diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index f8bee8371f2..cad79455af3 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -135,7 +135,7 @@ export class EvolutionPhase extends Phase { sprite .setPipelineData("ignoreTimeTint", true) - .setPipelineData("spriteKey", pokemon.getSpriteKey()) + .setPipelineData("spriteKey", spriteKey) .setPipelineData("shiny", pokemon.shiny) .setPipelineData("variant", pokemon.variant); diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 804b99122b9..aa3ba1b68be 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -20,7 +20,7 @@ import { MoveFlags } from "#enums/move-flags"; import { MoveId } from "#enums/move-id"; import { MoveResult } from "#enums/move-result"; import { MoveTarget } from "#enums/move-target"; -import { isReflected, isVirtual, MoveUseMode } from "#enums/move-use-mode"; +import { isReflected, MoveUseMode } from "#enums/move-use-mode"; import { PokemonType } from "#enums/pokemon-type"; import type { Pokemon } from "#field/pokemon"; import { applyHeldItems } from "#items/all-held-items"; @@ -238,43 +238,19 @@ export class MoveEffectPhase extends PokemonPhase { globalScene.currentBattle.lastPlayerInvolved = this.fieldIndex; } - const isDelayedAttack = this.move.hasAttr("DelayedAttackAttr"); - /** If the user was somehow removed from the field and it's not a delayed attack, end this phase */ - if (!user.isOnField()) { - if (!isDelayedAttack) { - super.end(); - return; - } - if (!user.scene) { - /* - * This happens if the Pokemon that used the delayed attack gets caught and released - * on the turn the attack would have triggered. Having access to the global scene - * in the future may solve this entirely, so for now we just cancel the hit - */ - super.end(); - return; - } - } + const move = this.move; /** * Does an effect from this move override other effects on this turn? * e.g. Charging moves (Fly, etc.) on their first turn of use. */ const overridden = new BooleanHolder(false); - const move = this.move; // Apply effects to override a move effect. // Assuming single target here works as this is (currently) // only used for Future Sight, calling and Pledge moves. // TODO: change if any other move effect overrides are introduced - applyMoveAttrs( - "OverrideMoveEffectAttr", - user, - this.getFirstTarget() ?? null, - move, - overridden, - isVirtual(this.useMode), - ); + applyMoveAttrs("OverrideMoveEffectAttr", user, this.getFirstTarget() ?? null, move, overridden, this.useMode); // If other effects were overriden, stop this phase before they can be applied if (overridden.value) { @@ -349,7 +325,7 @@ export class MoveEffectPhase extends PokemonPhase { */ private postAnimCallback(user: Pokemon, targets: Pokemon[]) { // Add to the move history entry - if (this.firstHit) { + if (this.firstHit && this.useMode !== MoveUseMode.DELAYED_ATTACK) { user.pushMoveHistory(this.moveHistoryEntry); applyAbAttrs("ExecutedMoveAbAttr", { pokemon: user }); } @@ -657,6 +633,7 @@ export class MoveEffectPhase extends PokemonPhase { /** @returns The {@linkcode Pokemon} using this phase's invoked move */ public getUserPokemon(): Pokemon | null { + // TODO: Make this purely a battler index if (this.battlerIndex > BattlerIndex.ENEMY_2) { return globalScene.getPokemonById(this.battlerIndex); } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 82bb6b153ef..cd7c7a8f48f 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -2,14 +2,12 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; -import type { DelayedAttackTag } from "#data/arena-tag"; import { CenterOfAttentionTag } from "#data/battler-tags"; import { SpeciesFormChangePreMoveTrigger } from "#data/form-change-triggers"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#data/status-effect"; import { getTerrainBlockMessage } from "#data/terrain"; import { getWeatherBlockMessage } from "#data/weather"; import { AbilityId } from "#enums/ability-id"; -import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -297,21 +295,6 @@ export class MovePhase extends BattlePhase { // form changes happen even before we know that the move wll execute. globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger); - // Check the player side arena if another delayed attack is active and hitting the same slot. - if (move.hasAttr("DelayedAttackAttr")) { - const currentTargetIndex = targets[0].getBattlerIndex(); - const delayedAttackHittingSameSlot = globalScene.arena.tags.some( - tag => - (tag.tagType === ArenaTagType.FUTURE_SIGHT || tag.tagType === ArenaTagType.DOOM_DESIRE) && - (tag as DelayedAttackTag).targetIndex === currentTargetIndex, - ); - - if (delayedAttackHittingSameSlot) { - this.failMove(true); - return; - } - } - // Check if the move has any attributes that can interrupt its own use **before** displaying text. // TODO: This should not rely on direct return values let failed = move.getAttrs("PreUseInterruptAttr").some(attr => attr.apply(this.pokemon, targets[0], move)); diff --git a/src/phases/positional-tag-phase.ts b/src/phases/positional-tag-phase.ts new file mode 100644 index 00000000000..dec572273c5 --- /dev/null +++ b/src/phases/positional-tag-phase.ts @@ -0,0 +1,21 @@ +// biome-ignore-start lint/correctness/noUnusedImports: TSDocs +import type { PositionalTag } from "#data/positional-tags/positional-tag"; +import type { TurnEndPhase } from "#phases/turn-end-phase"; +// biome-ignore-end lint/correctness/noUnusedImports: TSDocs + +import { globalScene } from "#app/global-scene"; +import { Phase } from "#app/phase"; + +/** + * Phase to trigger all pending post-turn {@linkcode PositionalTag}s. + * Occurs before {@linkcode TurnEndPhase} to allow for proper electrify timing. + */ +export class PositionalTagPhase extends Phase { + public readonly phaseName = "PositionalTagPhase"; + + public override start(): void { + globalScene.arena.positionalTagManager.activateAllTags(); + super.end(); + return; + } +} diff --git a/src/phases/scan-ivs-phase.ts b/src/phases/scan-ivs-phase.ts index e0865feb7ca..eebee28bfbb 100644 --- a/src/phases/scan-ivs-phase.ts +++ b/src/phases/scan-ivs-phase.ts @@ -2,9 +2,10 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import type { BattlerIndex } from "#enums/battler-index"; import { PERMANENT_STATS, Stat } from "#enums/stat"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import { PokemonPhase } from "#phases/pokemon-phase"; -import { getTextColor, TextStyle } from "#ui/text"; +import { getTextColor } from "#ui/text"; import i18next from "i18next"; export class ScanIvsPhase extends PokemonPhase { diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index ff97a9e33c0..61f81f1e468 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -24,10 +24,11 @@ export class SelectStarterPhase extends Phase { globalScene.ui.setMode(UiMode.STARTER_SELECT, (starters: Starter[]) => { globalScene.ui.clearText(); globalScene.ui.setMode(UiMode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: number) => { + // If clicking cancel, back out to title screen if (slotId === -1) { - globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.pushNew("TitlePhase"); - return this.end(); + globalScene.phaseManager.toTitleScreen(); + this.end(); + return; } globalScene.sessionSlotId = slotId; this.initBattle(starters); @@ -98,8 +99,12 @@ export class SelectStarterPhase extends Phase { starterPokemon.generateFusionSpecies(true); } starterPokemon.setVisible(false); - applyChallenges(ChallengeType.STARTER_MODIFY, starterPokemon); + const chalApplied = applyChallenges(ChallengeType.STARTER_MODIFY, starterPokemon); party.push(starterPokemon); + if (chalApplied) { + // If any challenges modified the starter, it should update + loadPokemonAssets.push(starterPokemon.updateInfo()); + } loadPokemonAssets.push(starterPokemon.loadAssets()); }); overrideTrainerItems(); diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index e042be5fcd6..1e56965dc0e 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -112,11 +112,11 @@ export class TitlePhase extends Phase { }); } } + // Cancel button = back to title options.push({ label: i18next.t("menu:cancel"), handler: () => { - globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.pushNew("TitlePhase"); + globalScene.phaseManager.toTitleScreen(); super.end(); return true; }, @@ -189,11 +189,12 @@ export class TitlePhase extends Phase { initDailyRun(): void { globalScene.ui.clearText(); globalScene.ui.setMode(UiMode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: number) => { - globalScene.phaseManager.clearPhaseQueue(); if (slotId === -1) { - globalScene.phaseManager.pushNew("TitlePhase"); - return super.end(); + globalScene.phaseManager.toTitleScreen(); + super.end(); + return; } + globalScene.phaseManager.clearPhaseQueue(); globalScene.sessionSlotId = slotId; const generateDaily = (seed: string) => { diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index a9394791a1e..2a42c560433 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -14,6 +14,8 @@ import i18next from "i18next"; export class TurnEndPhase extends FieldPhase { public readonly phaseName = "TurnEndPhase"; + public upcomingInterlude = false; + start() { super.start(); @@ -56,9 +58,11 @@ export class TurnEndPhase extends FieldPhase { pokemon.tempSummonData.waveTurnCount++; }; - this.executeForAll(handlePokemon); + if (!this.upcomingInterlude) { + this.executeForAll(handlePokemon); - globalScene.arena.lapseTags(); + globalScene.arena.lapseTags(); + } if (globalScene.arena.weather && !globalScene.arena.weather.lapse()) { globalScene.arena.trySetWeather(WeatherType.NONE); diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index bf674baa46d..70743b8b6ae 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -219,6 +219,10 @@ export class TurnStartPhase extends FieldPhase { break; } } + phaseManager.pushNew("CheckInterludePhase"); + + // TODO: Re-order these phases to be consistent with mainline turn order: + // https://www.smogon.com/forums/threads/sword-shield-battle-mechanics-research.3655528/page-64#post-9244179 phaseManager.pushNew("WeatherEffectPhase"); phaseManager.pushNew("BerryPhase"); @@ -226,12 +230,13 @@ export class TurnStartPhase extends FieldPhase { /** Add a new phase to check who should be taking status damage */ phaseManager.pushNew("CheckStatusEffectPhase", moveOrder); + phaseManager.pushNew("PositionalTagPhase"); phaseManager.pushNew("TurnEndPhase"); - /** - * this.end() will call shiftPhase(), which dumps everything from PrependQueue (aka everything that is unshifted()) to the front - * of the queue and dequeues to start the next phase - * this is important since stuff like SwitchSummon, AttemptRun, AttemptCapture Phases break the "flow" and should take precedence + /* + * `this.end()` will call `PhaseManager#shiftPhase()`, which dumps everything from `phaseQueuePrepend` + * (aka everything that is queued via `unshift()`) to the front of the queue and dequeues to start the next phase. + * This is important since stuff like `SwitchSummonPhase`, `AttemptRunPhase`, and `AttemptCapturePhase` break the "flow" and should take precedence */ this.end(); } diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 4ee4aa730fb..62fc73a10a3 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -1,5 +1,5 @@ import pkg from "#package.json"; -import { camelCaseToKebabCase } from "#utils/common"; +import { toKebabCase } from "#utils/strings"; import i18next from "i18next"; import LanguageDetector from "i18next-browser-languagedetector"; import HttpBackend from "i18next-http-backend"; @@ -79,13 +79,13 @@ const fonts: Array = [ face: new FontFace("emerald", "url(./fonts/pokemon-bw.ttf)", { unicodeRange: rangesByLanguage.japanese, }), - only: ["en", "es", "fr", "it", "de", "pt", "ko", "ja", "ca", "da", "tr", "ro", "ru"], + only: ["en", "es", "fr", "it", "de", "pt", "ko", "ja", "ca", "da", "tr", "ro", "ru", "tl"], }, { face: new FontFace("pkmnems", "url(./fonts/pokemon-bw.ttf)", { unicodeRange: rangesByLanguage.japanese, }), - only: ["en", "es", "fr", "it", "de", "pt", "ko", "ja", "ca", "da", "tr", "ro", "ru"], + only: ["en", "es", "fr", "it", "de", "pt", "ko", "ja", "ca", "da", "tr", "ro", "ru", "tl"], }, ]; @@ -191,17 +191,20 @@ export async function initI18n(): Promise { "tr", "ro", "ru", + "tl", ], backend: { loadPath(lng: string, [ns]: string[]) { + // Use namespace maps where required let fileName: string; if (namespaceMap[ns]) { fileName = namespaceMap[ns]; } else if (ns.startsWith("mysteryEncounters/")) { - fileName = camelCaseToKebabCase(ns + "Dialogue"); + fileName = toKebabCase(ns + "-dialogue"); // mystery-encounters/a-trainers-test-dialogue } else { - fileName = camelCaseToKebabCase(ns); + fileName = toKebabCase(ns); } + // ex: "./locales/en/move-anims" return `./locales/${lng}/${fileName}.json?v=${pkg.version}`; }, }, diff --git a/src/system/achv.ts b/src/system/achv.ts index 9158d827dc6..8c6210ae326 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -890,7 +890,7 @@ export const achvs = { 100, c => c instanceof FreshStartChallenge && - c.value > 0 && + c.value === 1 && !globalScene.gameMode.challenges.some( c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, ), diff --git a/src/system/arena-data.ts b/src/system/arena-data.ts index c0ad4a25024..b2a04f96a55 100644 --- a/src/system/arena-data.ts +++ b/src/system/arena-data.ts @@ -1,5 +1,6 @@ import type { ArenaTag } from "#data/arena-tag"; import { loadArenaTag, SerializableArenaTag } from "#data/arena-tag"; +import type { SerializedPositionalTag } from "#data/positional-tags/load-positional-tag"; import { Terrain } from "#data/terrain"; import { Weather } from "#data/weather"; import type { BiomeId } from "#enums/biome-id"; @@ -12,6 +13,7 @@ export interface SerializedArenaData { weather: NonFunctionProperties | null; terrain: NonFunctionProperties | null; tags?: ArenaTagTypeData[]; + positionalTags: SerializedPositionalTag[]; playerTerasUsed?: number; } @@ -20,6 +22,7 @@ export class ArenaData { public weather: Weather | null; public terrain: Terrain | null; public tags: ArenaTag[]; + public positionalTags: SerializedPositionalTag[] = []; public playerTerasUsed: number; constructor(source: Arena | SerializedArenaData) { @@ -37,11 +40,15 @@ export class ArenaData { this.biome = source.biomeType; this.weather = source.weather; this.terrain = source.terrain; + // The assertion here is ok - we ensure that all tags are inside the `posTagConstructorMap` map, + // and that all `PositionalTags` will become their respective interfaces when serialized and de-serialized. + this.positionalTags = (source.positionalTagManager.tags as unknown as SerializedPositionalTag[]) ?? []; return; } this.biome = source.biome; this.weather = source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null; this.terrain = source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null; + this.positionalTags = source.positionalTags ?? []; } } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index e6109090de3..4f6ed1257a8 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -16,6 +16,7 @@ import { allMoves, allSpecies } from "#data/data-lists"; import type { Egg } from "#data/egg"; import { pokemonFormChanges } from "#data/pokemon-forms"; import type { PokemonSpecies } from "#data/pokemon-species"; +import { loadPositionalTag } from "#data/positional-tags/load-positional-tag"; import { TerrainType } from "#data/terrain"; import { AbilityAttr } from "#enums/ability-attr"; import { BattleType } from "#enums/battle-type"; @@ -1097,6 +1098,10 @@ export class GameData { globalScene.trainerItems.clearItems(); globalScene.assignTrainerItemsFromSaveData(sessionData.trainerItems, true); + globalScene.arena.positionalTagManager.tags = sessionData.arena.positionalTags.map(tag => + loadPositionalTag(tag), + ); + globalScene.enemyTrainerItems.clearItems(); globalScene.assignTrainerItemsFromSaveData(sessionData.enemyTrainerItems, false); @@ -1439,11 +1444,10 @@ export class GameData { reader.onload = (_ => { return e => { - let dataName: string; + let dataName = GameDataType[dataType].toLowerCase(); let dataStr = AES.decrypt(e.target?.result?.toString()!, saveKey).toString(enc.Utf8); // TODO: is this bang correct? let valid = false; try { - dataName = GameDataType[dataType].toLowerCase(); switch (dataType) { case GameDataType.SYSTEM: { dataStr = this.convertSystemDataStr(dataStr); @@ -1478,7 +1482,6 @@ export class GameData { const displayError = (error: string) => globalScene.ui.showText(error, null, () => globalScene.ui.showText("", 0), fixedInt(1500)); - dataName = dataName!; // tell TS compiler that dataName is defined! if (!valid) { return globalScene.ui.showText( diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index d5706c34dc7..055c0eb8172 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -1,7 +1,6 @@ import { globalScene } from "#app/global-scene"; import type { Gender } from "#data/gender"; import { CustomPokemonData, PokemonBattleData, PokemonSummonData } from "#data/pokemon-data"; -import { getPokemonSpeciesForm } from "#data/pokemon-species"; import { Status } from "#data/status-effect"; import { BattleType } from "#enums/battle-type"; import type { BiomeId } from "#enums/biome-id"; @@ -16,7 +15,7 @@ import type { HeldItemSaveData } from "#items/held-item-data-types"; import { saveDataToConfig } from "#items/held-item-pool"; import { PokemonMove } from "#moves/pokemon-move"; import type { Variant } from "#sprites/variant"; -import { getPokemonSpecies } from "#utils/pokemon-utils"; +import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; export class PokemonData { public id: number; @@ -91,12 +90,12 @@ export class PokemonData { this.id = source.id; this.player = sourcePokemon?.isPlayer() ?? source.player; this.species = sourcePokemon?.species.speciesId ?? source.species; - this.nickname = sourcePokemon?.summonData.illusion?.basePokemon.nickname ?? source.nickname; + this.nickname = source.nickname; this.formIndex = Math.max(Math.min(source.formIndex, getPokemonSpecies(this.species).forms.length - 1), 0); this.abilityIndex = source.abilityIndex; this.passive = source.passive; - this.shiny = sourcePokemon?.summonData.illusion?.basePokemon.shiny ?? source.shiny; - this.variant = sourcePokemon?.summonData.illusion?.basePokemon.variant ?? source.variant; + this.shiny = source.shiny; + this.variant = source.variant; this.pokeball = source.pokeball ?? PokeballType.POKEBALL; this.level = source.level; this.exp = source.exp; @@ -140,8 +139,8 @@ export class PokemonData { this.fusionSpecies = sourcePokemon?.fusionSpecies?.speciesId ?? source.fusionSpecies; this.fusionFormIndex = source.fusionFormIndex; this.fusionAbilityIndex = source.fusionAbilityIndex; - this.fusionShiny = sourcePokemon?.summonData.illusion?.basePokemon.fusionShiny ?? source.fusionShiny; - this.fusionVariant = sourcePokemon?.summonData.illusion?.basePokemon.fusionVariant ?? source.fusionVariant; + this.fusionShiny = source.fusionShiny; + this.fusionVariant = source.fusionVariant; this.fusionGender = source.fusionGender; this.fusionLuck = source.fusionLuck ?? (source.fusionShiny ? source.fusionVariant + 1 : 0); this.fusionTeraType = (source.fusionTeraType ?? 0) as PokemonType; diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 106e7855fc5..c5b13ad576d 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -171,6 +171,7 @@ export const SettingKeys = { UI_Volume: "UI_SOUND_EFFECTS", Battle_Music: "BATTLE_MUSIC", Show_BGM_Bar: "SHOW_BGM_BAR", + Hide_Username: "HIDE_USERNAME", Move_Touch_Controls: "MOVE_TOUCH_CONTROLS", Shop_Overlay_Opacity: "SHOP_OVERLAY_OPACITY", }; @@ -625,6 +626,13 @@ export const Setting: Array = [ default: 1, type: SettingType.DISPLAY, }, + { + key: SettingKeys.Hide_Username, + label: i18next.t("settings:hideUsername"), + options: OFF_ON, + default: 0, + type: SettingType.DISPLAY, + }, { key: SettingKeys.Master_Volume, label: i18next.t("settings:masterVolume"), @@ -792,6 +800,9 @@ export function setSetting(setting: string, value: number): boolean { case SettingKeys.Show_BGM_Bar: globalScene.showBgmBar = Setting[index].options[value].value === "On"; break; + case SettingKeys.Hide_Username: + globalScene.hideUsername = Setting[index].options[value].value === "On"; + break; case SettingKeys.Candy_Upgrade_Notification: if (globalScene.candyUpgradeNotification === value) { break; @@ -970,6 +981,10 @@ export function setSetting(setting: string, value: number): boolean { label: "Română (Needs Help)", handler: () => changeLocaleHandler("ro"), }, + { + label: "Tagalog (Needs Help)", + handler: () => changeLocaleHandler("tl"), + }, { label: i18next.t("settings:back"), handler: () => cancelHandler(), diff --git a/src/system/version-migration/versions/v1_7_0.ts b/src/system/version-migration/versions/v1_7_0.ts index 9ba25bcbaac..69c640756ea 100644 --- a/src/system/version-migration/versions/v1_7_0.ts +++ b/src/system/version-migration/versions/v1_7_0.ts @@ -1,11 +1,10 @@ import { globalScene } from "#app/global-scene"; -import { getPokemonSpeciesForm } from "#data/pokemon-species"; import { DexAttr } from "#enums/dex-attr"; import type { SessionSaveData, SystemSaveData } from "#system/game-data"; import type { SessionSaveMigrator } from "#types/session-save-migrator"; import type { SystemSaveMigrator } from "#types/system-save-migrator"; import { isNullOrUndefined } from "#utils/common"; -import { getPokemonSpecies } from "#utils/pokemon-utils"; +import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; /** * If a starter is caught, but the only forms registered as caught are not starterSelectable, diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index 4c476292fa6..34223f5d6a1 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -6,8 +6,9 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { SpeciesId } from "#enums/species-id"; import { TrainerItemId } from "#enums/trainer-item-id"; +import { TextStyle } from "#enums/text-style"; import { WeatherType } from "#enums/weather-type"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import type { nil } from "#utils/common"; import { isNullOrUndefined } from "#utils/common"; import i18next from "i18next"; diff --git a/src/typings/i18next.d.ts b/src/typings/i18next.d.ts index 0eaa1e6ff0f..1e1695f6b9a 100644 --- a/src/typings/i18next.d.ts +++ b/src/typings/i18next.d.ts @@ -3,6 +3,7 @@ import type { TOptions } from "i18next"; // Module declared to make referencing keys in the localization files type-safe. declare module "i18next" { interface TFunction { + // biome-ignore lint/style/useShorthandFunctionType: This needs to be an interface due to interface merging (key: string | string[], options?: TOptions & Record): string; } } diff --git a/src/typings/phaser/index.d.ts b/src/typings/phaser/index.d.ts index 26fbcff75bd..caddaedfc59 100644 --- a/src/typings/phaser/index.d.ts +++ b/src/typings/phaser/index.d.ts @@ -1,7 +1,7 @@ import "phaser"; declare module "phaser" { - namespace GameObjects { + namespace GameObjects { interface GameObject { width: number; @@ -16,45 +16,45 @@ declare module "phaser" { y: number; } - interface Container { + interface Container { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): this; - } - interface Sprite { + setPositionRelative(guideObject: any, x: number, y: number): this; + } + interface Sprite { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): this; - } - interface Image { + setPositionRelative(guideObject: any, x: number, y: number): this; + } + interface Image { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): this; - } - interface NineSlice { + setPositionRelative(guideObject: any, x: number, y: number): this; + } + interface NineSlice { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): this; - } - interface Text { + setPositionRelative(guideObject: any, x: number, y: number): this; + } + interface Text { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): this; - } - interface Rectangle { + setPositionRelative(guideObject: any, x: number, y: number): this; + } + interface Rectangle { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): this; - } - } + setPositionRelative(guideObject: any, x: number, y: number): this; + } + } - namespace Input { + namespace Input { namespace Gamepad { interface GamepadPlugin { /** diff --git a/src/ui/ability-bar.ts b/src/ui/ability-bar.ts index 79a68e9dce7..4b868d4e66c 100644 --- a/src/ui/ability-bar.ts +++ b/src/ui/ability-bar.ts @@ -1,5 +1,6 @@ import { globalScene } from "#app/global-scene"; -import { addTextObject, TextStyle } from "#ui/text"; +import { TextStyle } from "#enums/text-style"; +import { addTextObject } from "#ui/text"; import i18next from "i18next"; const barWidth = 118; diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index d93ad8b7665..2fb0159b6ef 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -1,7 +1,8 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; -import { addBBCodeTextObject, getTextColor, getTextStyleOptions, TextStyle } from "#ui/text"; +import { addBBCodeTextObject, getTextColor, getTextStyleOptions } from "#ui/text"; import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import { fixedInt, rgbHexToRgba } from "#utils/common"; diff --git a/src/ui/achv-bar.ts b/src/ui/achv-bar.ts index 8e0f2a9404b..bb1ef95c9de 100644 --- a/src/ui/achv-bar.ts +++ b/src/ui/achv-bar.ts @@ -1,8 +1,9 @@ import { globalScene } from "#app/global-scene"; import type { PlayerGender } from "#enums/player-gender"; +import { TextStyle } from "#enums/text-style"; import { Achv, getAchievementDescription } from "#system/achv"; import { Voucher } from "#system/voucher"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; export class AchvBar extends Phaser.GameObjects.Container { private defaultWidth: number; diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts index 6b247f6da96..01fd1d45a61 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { PlayerGender } from "#enums/player-gender"; +import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; import type { Achv } from "#system/achv"; import { achvs, getAchievementDescription } from "#system/achv"; @@ -9,7 +10,7 @@ import type { Voucher } from "#system/voucher"; import { getVoucherTypeIcon, getVoucherTypeName, vouchers } from "#system/voucher"; import { MessageUiHandler } from "#ui/message-ui-handler"; import { ScrollBar } from "#ui/scroll-bar"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/admin-ui-handler.ts b/src/ui/admin-ui-handler.ts index 3d0a1153127..e577368363d 100644 --- a/src/ui/admin-ui-handler.ts +++ b/src/ui/admin-ui-handler.ts @@ -1,12 +1,12 @@ import { pokerogueApi } from "#api/pokerogue-api"; import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; import type { ModalConfig } from "#ui/modal-ui-handler"; -import { TextStyle } from "#ui/text"; -import { formatText } from "#utils/common"; +import { toTitleCase } from "#utils/strings"; type AdminUiHandlerService = "discord" | "google"; type AdminUiHandlerServiceMode = "Link" | "Unlink"; @@ -21,9 +21,9 @@ export class AdminUiHandler extends FormModalUiHandler { private readonly httpUserNotFoundErrorCode: number = 404; private readonly ERR_REQUIRED_FIELD = (field: string) => { if (field === "username") { - return `${formatText(field)} is required`; + return `${toTitleCase(field)} is required`; } - return `${formatText(field)} Id is required`; + return `${toTitleCase(field)} Id is required`; }; // returns a string saying whether a username has been successfully linked/unlinked to discord/google private readonly SUCCESS_SERVICE_MODE = (service: string, mode: string) => { diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index 43cc553d936..d2a45646690 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -3,6 +3,7 @@ import { ArenaTrapTag } from "#data/arena-tag"; import { TerrainType } from "#data/terrain"; import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagType } from "#enums/arena-tag-type"; +import { TextStyle } from "#enums/text-style"; import { WeatherType } from "#enums/weather-type"; import type { ArenaEvent } from "#events/arena"; import { @@ -14,10 +15,11 @@ import { } from "#events/arena"; import type { TurnEndEvent } from "#events/battle-scene"; import { BattleSceneEventType } from "#events/battle-scene"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { TimeOfDayWidget } from "#ui/time-of-day-widget"; import { addWindow, WindowVariant } from "#ui/ui-theme"; -import { fixedInt, formatText, toCamelCaseString } from "#utils/common"; +import { fixedInt } from "#utils/common"; +import { toCamelCase, toTitleCase } from "#utils/strings"; import type { ParseKeys } from "i18next"; import i18next from "i18next"; @@ -48,10 +50,10 @@ export function getFieldEffectText(arenaTagType: string): string { if (!arenaTagType || arenaTagType === ArenaTagType.NONE) { return arenaTagType; } - const effectName = toCamelCaseString(arenaTagType); + const effectName = toCamelCase(arenaTagType); const i18nKey = `arenaFlyout:${effectName}` as ParseKeys; const resultName = i18next.t(i18nKey); - return !resultName || resultName === i18nKey ? formatText(arenaTagType) : resultName; + return !resultName || resultName === i18nKey ? toTitleCase(arenaTagType) : resultName; } export class ArenaFlyout extends Phaser.GameObjects.Container { @@ -86,14 +88,14 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { private flyoutTextHeaderPlayer: Phaser.GameObjects.Text; /** The {@linkcode Phaser.GameObjects.Text} header used to indicate the enemy's effects */ private flyoutTextHeaderEnemy: Phaser.GameObjects.Text; - /** The {@linkcode Phaser.GameObjects.Text} header used to indicate neutral effects */ + /** The {@linkcode Phaser.GameObjects.Text} header used to indicate field effects */ private flyoutTextHeaderField: Phaser.GameObjects.Text; /** The {@linkcode Phaser.GameObjects.Text} used to indicate the player's effects */ private flyoutTextPlayer: Phaser.GameObjects.Text; /** The {@linkcode Phaser.GameObjects.Text} used to indicate the enemy's effects */ private flyoutTextEnemy: Phaser.GameObjects.Text; - /** The {@linkcode Phaser.GameObjects.Text} used to indicate neutral effects */ + /** The {@linkcode Phaser.GameObjects.Text} used to indicate field effects */ private flyoutTextField: Phaser.GameObjects.Text; /** Container for all field effects observed by this object */ @@ -163,7 +165,7 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { this.flyoutTextHeaderField = addTextObject( this.flyoutWidth / 2, 5, - i18next.t("arenaFlyout:neutral"), + i18next.t("arenaFlyout:field"), TextStyle.SUMMARY_GREEN, ); this.flyoutTextHeaderField.setFontSize(54); diff --git a/src/ui/ball-ui-handler.ts b/src/ui/ball-ui-handler.ts index bde340e3cf7..67beb0eba84 100644 --- a/src/ui/ball-ui-handler.ts +++ b/src/ui/ball-ui-handler.ts @@ -2,9 +2,10 @@ import { globalScene } from "#app/global-scene"; import { getPokeballName } from "#data/pokeball"; import { Button } from "#enums/buttons"; import { Command } from "#enums/command"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { CommandPhase } from "#phases/command-phase"; -import { addTextObject, getTextStyleOptions, TextStyle } from "#ui/text"; +import { addTextObject, getTextStyleOptions } from "#ui/text"; import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/base-stats-overlay.ts b/src/ui/base-stats-overlay.ts index 888b87a8d11..e3ba472475a 100644 --- a/src/ui/base-stats-overlay.ts +++ b/src/ui/base-stats-overlay.ts @@ -1,6 +1,7 @@ import type { InfoToggle } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; -import { addTextObject, TextStyle } from "#ui/text"; +import { TextStyle } from "#enums/text-style"; +import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt } from "#utils/common"; import i18next from "i18next"; diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts index dd83c8c9a4b..c0193825d10 100644 --- a/src/ui/battle-flyout.ts +++ b/src/ui/battle-flyout.ts @@ -2,12 +2,13 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { BerryType } from "#enums/berry-type"; import { MoveId } from "#enums/move-id"; +import { TextStyle } from "#enums/text-style"; import { UiTheme } from "#enums/ui-theme"; import type { BerryUsedEvent, MoveUsedEvent } from "#events/battle-scene"; import { BattleSceneEventType } from "#events/battle-scene"; import type { EnemyPokemon, Pokemon } from "#field/pokemon"; import type { Move } from "#moves/move"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { fixedInt } from "#utils/common"; /** Container for info about a {@linkcode Move} */ diff --git a/src/ui/battle-info/battle-info.ts b/src/ui/battle-info/battle-info.ts index 4a2a6d1804d..0aedfbdf5e7 100644 --- a/src/ui/battle-info/battle-info.ts +++ b/src/ui/battle-info/battle-info.ts @@ -4,9 +4,10 @@ import { getTypeRgb } from "#data/type"; import { PokemonType } from "#enums/pokemon-type"; import { Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; +import { TextStyle } from "#enums/text-style"; import type { Pokemon } from "#field/pokemon"; import { getVariantTint } from "#sprites/variant"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { fixedInt, getLocalizedSpriteKey, getShinyDescriptor } from "#utils/common"; import i18next from "i18next"; diff --git a/src/ui/battle-info/enemy-battle-info.ts b/src/ui/battle-info/enemy-battle-info.ts index 5799fb476ef..d426a49df5c 100644 --- a/src/ui/battle-info/enemy-battle-info.ts +++ b/src/ui/battle-info/enemy-battle-info.ts @@ -1,10 +1,11 @@ import { globalScene } from "#app/global-scene"; import { Stat } from "#enums/stat"; +import { TextStyle } from "#enums/text-style"; import type { EnemyPokemon } from "#field/pokemon"; import { BattleFlyout } from "#ui/battle-flyout"; import type { BattleInfoParamList } from "#ui/battle-info"; import { BattleInfo } from "#ui/battle-info"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { addWindow, WindowVariant } from "#ui/ui-theme"; import i18next from "i18next"; import type { GameObjects } from "phaser"; diff --git a/src/ui/battle-message-ui-handler.ts b/src/ui/battle-message-ui-handler.ts index bd524f0bb43..b58897b9022 100644 --- a/src/ui/battle-message-ui-handler.ts +++ b/src/ui/battle-message-ui-handler.ts @@ -1,9 +1,10 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { getStatKey, PERMANENT_STATS } from "#enums/stat"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import { MessageUiHandler } from "#ui/message-ui-handler"; -import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#ui/text"; +import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import i18next from "i18next"; import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; diff --git a/src/ui/bgm-bar.ts b/src/ui/bgm-bar.ts index d8b6bbe8b8a..e2c6925ec30 100644 --- a/src/ui/bgm-bar.ts +++ b/src/ui/bgm-bar.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; -import { addTextObject, TextStyle } from "#ui/text"; -import { formatText } from "#utils/common"; +import { TextStyle } from "#enums/text-style"; +import { addTextObject } from "#ui/text"; +import { toTitleCase } from "#utils/strings"; import i18next from "i18next"; const hiddenX = -150; @@ -100,7 +101,7 @@ export class BgmBar extends Phaser.GameObjects.Container { getRealBgmName(bgmName: string): string { return i18next.t([`bgmName:${bgmName}`, "bgmName:missing_entries"], { - name: formatText(bgmName), + name: toTitleCase(bgmName), }); } } diff --git a/src/ui/candy-bar.ts b/src/ui/candy-bar.ts index ea3500d6c4c..239b963227b 100644 --- a/src/ui/candy-bar.ts +++ b/src/ui/candy-bar.ts @@ -1,7 +1,8 @@ import { globalScene } from "#app/global-scene"; import { starterColors } from "#app/global-vars/starter-colors"; import type { SpeciesId } from "#enums/species-id"; -import { addTextObject, TextStyle } from "#ui/text"; +import { TextStyle } from "#enums/text-style"; +import { addTextObject } from "#ui/text"; import { rgbHexToRgba } from "#utils/common"; import { argbFromRgba } from "@material/material-color-utilities"; diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts index a827cddc9a7..4a7ab7641a3 100644 --- a/src/ui/challenges-select-ui-handler.ts +++ b/src/ui/challenges-select-ui-handler.ts @@ -3,8 +3,9 @@ import type { Challenge } from "#data/challenge"; import { Button } from "#enums/buttons"; import { Challenges } from "#enums/challenges"; import { Color, ShadowColor } from "#enums/color"; +import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import { getLocalizedSpriteKey } from "#utils/common"; @@ -381,8 +382,7 @@ export class GameChallengesUiHandler extends UiHandler { this.cursorObj?.setVisible(true); this.updateChallengeArrows(this.startCursor.visible); } else { - globalScene.phaseManager.clearPhaseQueue(); - globalScene.phaseManager.pushNew("TitlePhase"); + globalScene.phaseManager.toTitleScreen(); globalScene.phaseManager.getCurrentPhase()?.end(); } success = true; diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index 23cc25ab73f..9adf6b7f9de 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -6,10 +6,11 @@ import { Command } from "#enums/command"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; import { TrainerItemId } from "#enums/trainer-item-id"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { CommandPhase } from "#phases/command-phase"; import { PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { UiHandler } from "#ui/ui-handler"; import i18next from "i18next"; diff --git a/src/ui/daily-run-scoreboard.ts b/src/ui/daily-run-scoreboard.ts index dcd45b40390..9391d02859c 100644 --- a/src/ui/daily-run-scoreboard.ts +++ b/src/ui/daily-run-scoreboard.ts @@ -1,6 +1,7 @@ import { pokerogueApi } from "#api/pokerogue-api"; import { globalScene } from "#app/global-scene"; -import { addTextObject, TextStyle } from "#ui/text"; +import { TextStyle } from "#enums/text-style"; +import { addTextObject } from "#ui/text"; import { addWindow, WindowVariant } from "#ui/ui-theme"; import { executeIf } from "#utils/common"; import { getEnumKeys } from "#utils/enums"; diff --git a/src/ui/dropdown.ts b/src/ui/dropdown.ts index 2a100ddbe59..c13d1ab6482 100644 --- a/src/ui/dropdown.ts +++ b/src/ui/dropdown.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; +import { TextStyle } from "#enums/text-style"; import { ScrollBar } from "#ui/scroll-bar"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { addWindow, WindowVariant } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/egg-counter-container.ts b/src/ui/egg-counter-container.ts index ff536228fde..da394e73b28 100644 --- a/src/ui/egg-counter-container.ts +++ b/src/ui/egg-counter-container.ts @@ -1,8 +1,9 @@ import { globalScene } from "#app/global-scene"; +import { TextStyle } from "#enums/text-style"; import type { EggCountChangedEvent } from "#events/egg"; import { EggEventType } from "#events/egg"; import type { EggHatchSceneHandler } from "#ui/egg-hatch-scene-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; /** diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 19d1efa75dd..5dcf05e2606 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -6,10 +6,11 @@ import { Egg, getLegendaryGachaSpeciesForTimestamp } from "#data/egg"; import { Button } from "#enums/buttons"; import { EggTier } from "#enums/egg-type"; import { GachaType } from "#enums/gacha-types"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import { getVoucherTypeIcon, VoucherType } from "#system/voucher"; import { MessageUiHandler } from "#ui/message-ui-handler"; -import { addTextObject, getEggTierTextTint, getTextStyleOptions, TextStyle } from "#ui/text"; +import { addTextObject, getEggTierTextTint, getTextStyleOptions } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt, randSeedShuffle } from "#utils/common"; import { getEnumValues } from "#utils/enums"; @@ -74,7 +75,7 @@ export class EggGachaUiHandler extends MessageUiHandler { const gachaInfoContainer = globalScene.add.container(160, 46); const currentLanguage = i18next.resolvedLanguage ?? "en"; - let gachaTextStyle = TextStyle.WINDOW_ALT; + let gachaTextStyle: TextStyle = TextStyle.WINDOW_ALT; let gachaX = 4; let gachaY = 0; let pokemonIconX = -20; diff --git a/src/ui/egg-list-ui-handler.ts b/src/ui/egg-list-ui-handler.ts index 94d6889ed48..42f969b9d38 100644 --- a/src/ui/egg-list-ui-handler.ts +++ b/src/ui/egg-list-ui-handler.ts @@ -1,11 +1,12 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import { MessageUiHandler } from "#ui/message-ui-handler"; import { PokemonIconAnimHandler, PokemonIconAnimMode } from "#ui/pokemon-icon-anim-handler"; import { ScrollBar } from "#ui/scroll-bar"; import { ScrollableGridUiHandler } from "#ui/scrollable-grid-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/evolution-scene-handler.ts b/src/ui/evolution-scene-handler.ts index 5ad4fc6fdf5..c22cf31faaa 100644 --- a/src/ui/evolution-scene-handler.ts +++ b/src/ui/evolution-scene-handler.ts @@ -1,8 +1,9 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import { MessageUiHandler } from "#ui/message-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; export class EvolutionSceneHandler extends MessageUiHandler { public evolutionContainer: Phaser.GameObjects.Container; diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 286199b99e2..42f8cba5df4 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -7,12 +7,13 @@ import { Command } from "#enums/command"; import { MoveCategory } from "#enums/move-category"; import { MoveUseMode } from "#enums/move-use-mode"; import { PokemonType } from "#enums/pokemon-type"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { EnemyPokemon, Pokemon } from "#field/pokemon"; import type { PokemonMove } from "#moves/pokemon-move"; import type { CommandPhase } from "#phases/command-phase"; import { MoveInfoOverlay } from "#ui/move-info-overlay"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { UiHandler } from "#ui/ui-handler"; import { fixedInt, getLocalizedSpriteKey, padInt } from "#utils/common"; import i18next from "i18next"; @@ -284,7 +285,7 @@ export class FightUiHandler extends UiHandler implements InfoToggle { const ppColorStyle = FightUiHandler.ppRatioToColor(pp / maxPP); - //** Changes the text color and shadow according to the determined TextStyle */ + // Changes the text color and shadow according to the determined TextStyle this.ppText.setColor(this.getTextColor(ppColorStyle, false)).setShadowColor(this.getTextColor(ppColorStyle, true)); this.moveInfoOverlay.show(pokemonMove.getMove()); diff --git a/src/ui/filter-bar.ts b/src/ui/filter-bar.ts index 3961ae3415c..ea227655a97 100644 --- a/src/ui/filter-bar.ts +++ b/src/ui/filter-bar.ts @@ -1,10 +1,11 @@ import { globalScene } from "#app/global-scene"; import type { DropDownColumn } from "#enums/drop-down-column"; +import { TextStyle } from "#enums/text-style"; import type { UiTheme } from "#enums/ui-theme"; import type { DropDown } from "#ui/dropdown"; import { DropDownType } from "#ui/dropdown"; import type { StarterContainer } from "#ui/starter-container"; -import { addTextObject, getTextColor, TextStyle } from "#ui/text"; +import { addTextObject, getTextColor } from "#ui/text"; import { addWindow, WindowVariant } from "#ui/ui-theme"; export class FilterBar extends Phaser.GameObjects.Container { diff --git a/src/ui/filter-text.ts b/src/ui/filter-text.ts index 4a9012e44fc..ff7119dd778 100644 --- a/src/ui/filter-text.ts +++ b/src/ui/filter-text.ts @@ -1,9 +1,10 @@ import { globalScene } from "#app/global-scene"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { UiTheme } from "#enums/ui-theme"; import type { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; import type { StarterContainer } from "#ui/starter-container"; -import { addTextObject, getTextColor, TextStyle } from "#ui/text"; +import { addTextObject, getTextColor } from "#ui/text"; import type { UI } from "#ui/ui"; import { addWindow, WindowVariant } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/form-modal-ui-handler.ts b/src/ui/form-modal-ui-handler.ts index 35965b09b80..203d98a86c7 100644 --- a/src/ui/form-modal-ui-handler.ts +++ b/src/ui/form-modal-ui-handler.ts @@ -1,9 +1,10 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; +import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; import type { ModalConfig } from "#ui/modal-ui-handler"; import { ModalUiHandler } from "#ui/modal-ui-handler"; -import { addTextInputObject, addTextObject, TextStyle } from "#ui/text"; +import { addTextInputObject, addTextObject } from "#ui/text"; import { addWindow, WindowVariant } from "#ui/ui-theme"; import { fixedInt } from "#utils/common"; import type InputText from "phaser3-rex-plugins/plugins/inputtext"; @@ -71,6 +72,10 @@ export abstract class FormModalUiHandler extends ModalUiHandler { (hasTitle ? 31 : 5) + 20 * (config.length - 1) + 16 + this.getButtonTopMargin(), "", TextStyle.TOOLTIP_CONTENT, + { + fontSize: "42px", + wordWrap: { width: 850 }, + }, ); this.errorMessage.setColor(this.getTextColor(TextStyle.SUMMARY_PINK)); this.errorMessage.setShadowColor(this.getTextColor(TextStyle.SUMMARY_PINK, true)); @@ -83,20 +88,28 @@ export abstract class FormModalUiHandler extends ModalUiHandler { this.inputs = []; this.formLabels = []; fieldsConfig.forEach((config, f) => { - const label = addTextObject(10, (hasTitle ? 31 : 5) + 20 * f, config.label, TextStyle.TOOLTIP_CONTENT); + // The Pokédex Scan Window uses width `300` instead of `160` like the other forms + // Therefore, the label does not need to be shortened + const label = addTextObject( + 10, + (hasTitle ? 31 : 5) + 20 * f, + config.label.length > 25 && this.getWidth() < 200 ? config.label.slice(0, 20) + "..." : config.label, + TextStyle.TOOLTIP_CONTENT, + ); label.name = "formLabel" + f; this.formLabels.push(label); this.modalContainer.add(this.formLabels[this.formLabels.length - 1]); - const inputContainer = globalScene.add.container(70, (hasTitle ? 28 : 2) + 20 * f); + const inputWidth = label.width < 320 ? 80 : 80 - (label.width - 320) / 5.5; + const inputContainer = globalScene.add.container(70 + (80 - inputWidth), (hasTitle ? 28 : 2) + 20 * f); inputContainer.setVisible(false); - const inputBg = addWindow(0, 0, 80, 16, false, false, 0, 0, WindowVariant.XTHIN); + const inputBg = addWindow(0, 0, inputWidth, 16, false, false, 0, 0, WindowVariant.XTHIN); const isPassword = config?.isPassword; const isReadOnly = config?.isReadOnly; - const input = addTextInputObject(4, -2, 440, 116, TextStyle.TOOLTIP_CONTENT, { + const input = addTextInputObject(4, -2, inputWidth * 5.5, 116, TextStyle.TOOLTIP_CONTENT, { type: isPassword ? "password" : "text", maxLength: isPassword ? 64 : 20, readOnly: isReadOnly, diff --git a/src/ui/game-stats-ui-handler.ts b/src/ui/game-stats-ui-handler.ts index 759792b122f..ed66230bed7 100644 --- a/src/ui/game-stats-ui-handler.ts +++ b/src/ui/game-stats-ui-handler.ts @@ -2,12 +2,14 @@ import { globalScene } from "#app/global-scene"; import { speciesStarterCosts } from "#balance/starters"; import { Button } from "#enums/buttons"; import { DexAttr } from "#enums/dex-attr"; +import { TextStyle } from "#enums/text-style"; import { UiTheme } from "#enums/ui-theme"; import type { GameData } from "#system/game-data"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; -import { formatFancyLargeNumber, getPlayTimeString, toReadableString } from "#utils/common"; +import { formatFancyLargeNumber, getPlayTimeString } from "#utils/common"; +import { toTitleCase } from "#utils/strings"; import i18next from "i18next"; import Phaser from "phaser"; @@ -501,11 +503,9 @@ export function initStatsKeys() { sourceFunc: gameData => gameData.gameStats[key].toString(), }; } - if (!(displayStats[key] as DisplayStat).label_key) { + if (!displayStats[key].label_key) { const splittableKey = key.replace(/([a-z]{2,})([A-Z]{1}(?:[^A-Z]|$))/g, "$1_$2"); - (displayStats[key] as DisplayStat).label_key = toReadableString( - `${splittableKey[0].toUpperCase()}${splittableKey.slice(1)}`, - ); + displayStats[key].label_key = toTitleCase(splittableKey); } } } diff --git a/src/ui/loading-modal-ui-handler.ts b/src/ui/loading-modal-ui-handler.ts index 585d70d51db..de00d911c47 100644 --- a/src/ui/loading-modal-ui-handler.ts +++ b/src/ui/loading-modal-ui-handler.ts @@ -1,6 +1,7 @@ +import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; import { ModalUiHandler } from "#ui/modal-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import i18next from "i18next"; export class LoadingModalUiHandler extends ModalUiHandler { diff --git a/src/ui/login-form-ui-handler.ts b/src/ui/login-form-ui-handler.ts index 417a9031bf7..524eaeece86 100644 --- a/src/ui/login-form-ui-handler.ts +++ b/src/ui/login-form-ui-handler.ts @@ -1,11 +1,12 @@ import { pokerogueApi } from "#api/pokerogue-api"; import { globalScene } from "#app/global-scene"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; import type { ModalConfig } from "#ui/modal-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt } from "#utils/common"; import i18next from "i18next"; diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index de47e21b0e9..a18b60ac6f9 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -5,13 +5,14 @@ import { bypassLogin } from "#app/global-vars/bypass-login"; import { handleTutorial, Tutorial } from "#app/tutorial"; import { Button } from "#enums/buttons"; import { GameDataType } from "#enums/game-data-type"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; import { AdminMode, getAdminModeName } from "#ui/admin-ui-handler"; import type { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; import { BgmBar } from "#ui/bgm-bar"; import { MessageUiHandler } from "#ui/message-ui-handler"; -import { addTextObject, getTextStyleOptions, TextStyle } from "#ui/text"; +import { addTextObject, getTextStyleOptions } from "#ui/text"; import { addWindow, WindowVariant } from "#ui/ui-theme"; import { fixedInt, isLocal, sessionIdKey } from "#utils/common"; import { getCookie } from "#utils/cookies"; diff --git a/src/ui/modal-ui-handler.ts b/src/ui/modal-ui-handler.ts index 844f7f43930..228d80968b9 100644 --- a/src/ui/modal-ui-handler.ts +++ b/src/ui/modal-ui-handler.ts @@ -1,7 +1,8 @@ import { globalScene } from "#app/global-scene"; import type { Button } from "#enums/buttons"; +import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { UiHandler } from "#ui/ui-handler"; import { addWindow, WindowVariant } from "#ui/ui-theme"; @@ -151,7 +152,12 @@ export abstract class ModalUiHandler extends UiHandler { updateContainer(config?: ModalConfig): void { const [marginTop, marginRight, marginBottom, marginLeft] = this.getMargin(config); - const [width, height] = [this.getWidth(config), this.getHeight(config)]; + /** + * If the total amount of characters for the 2 buttons exceeds ~30 characters, + * the width in `registration-form-ui-handler.ts` and `login-form-ui-handler.ts` needs to be increased. + */ + const width = this.getWidth(config); + const height = this.getHeight(config); this.modalContainer.setPosition( (globalScene.game.canvas.width / 6 - (width + (marginRight - marginLeft))) / 2, (-globalScene.game.canvas.height / 6 - (height + (marginBottom - marginTop))) / 2, @@ -165,10 +171,14 @@ export abstract class ModalUiHandler extends UiHandler { this.titleText.setX(width / 2); this.titleText.setVisible(!!title); - for (let b = 0; b < this.buttonContainers.length; b++) { - const sliceWidth = width / (this.buttonContainers.length + 1); - - this.buttonContainers[b].setPosition(sliceWidth * (b + 1), this.modalBg.height - (this.buttonBgs[b].height + 8)); + if (this.buttonContainers.length > 0) { + const spacing = 12; + const totalWidth = this.buttonBgs.reduce((sum, bg) => sum + bg.width, 0) + spacing * (this.buttonBgs.length - 1); + let x = (this.modalBg.width - totalWidth) / 2; + this.buttonContainers.forEach((container, i) => { + container.setPosition(x + this.buttonBgs[i].width / 2, this.modalBg.height - (this.buttonBgs[i].height + 8)); + x += this.buttonBgs[i].width + spacing; + }); } } diff --git a/src/ui/move-info-overlay.ts b/src/ui/move-info-overlay.ts index 7720354a5b3..f8632eb244e 100644 --- a/src/ui/move-info-overlay.ts +++ b/src/ui/move-info-overlay.ts @@ -2,8 +2,9 @@ import type { InfoToggle } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import { MoveCategory } from "#enums/move-category"; import { PokemonType } from "#enums/pokemon-type"; +import { TextStyle } from "#enums/text-style"; import type { Move } from "#moves/move"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt, getLocalizedSpriteKey } from "#utils/common"; import i18next from "i18next"; diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index 37f0efb50e4..b6bc464855c 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -3,13 +3,14 @@ import { getPokeballAtlasKey } from "#data/pokeball"; import { Button } from "#enums/buttons"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import { getEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { OptionSelectSettings } from "#mystery-encounters/encounter-phase-utils"; import type { MysteryEncounterOption } from "#mystery-encounters/mystery-encounter-option"; import type { MysteryEncounterPhase } from "#phases/mystery-encounter-phases"; import { PartyUiMode } from "#ui/party-ui-handler"; -import { addBBCodeTextObject, getBBCodeFrag, TextStyle } from "#ui/text"; +import { addBBCodeTextObject, getBBCodeFrag } from "#ui/text"; import { UiHandler } from "#ui/ui-handler"; import { addWindow, WindowVariant } from "#ui/ui-theme"; import { fixedInt, isNullOrUndefined } from "#utils/common"; diff --git a/src/ui/party-exp-bar.ts b/src/ui/party-exp-bar.ts index 0d6ec936a92..952a1f8227a 100644 --- a/src/ui/party-exp-bar.ts +++ b/src/ui/party-exp-bar.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; +import { TextStyle } from "#enums/text-style"; import type { Pokemon } from "#field/pokemon"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import i18next from "i18next"; export class PartyExpBar extends Phaser.GameObjects.Container { diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 8e975ffe7e6..70aed5d0519 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -14,6 +14,7 @@ import { MoveId } from "#enums/move-id"; import { MoveResult } from "#enums/move-result"; import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; import { formChangeItemName } from "#items/item-utility"; @@ -24,9 +25,10 @@ import type { TurnMove } from "#types/turn-move"; import { MessageUiHandler } from "#ui/message-ui-handler"; import { MoveInfoOverlay } from "#ui/move-info-overlay"; import { PokemonIconAnimHandler, PokemonIconAnimMode } from "#ui/pokemon-icon-anim-handler"; -import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#ui/text"; +import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; -import { BooleanHolder, getLocalizedSpriteKey, randInt, toReadableString } from "#utils/common"; +import { BooleanHolder, getLocalizedSpriteKey, randInt } from "#utils/common"; +import { toTitleCase } from "#utils/strings"; import i18next from "i18next"; import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; @@ -102,6 +104,11 @@ export enum PartyUiMode { * This is generally used in for Mystery Encounter or special effects that require the player to select a Pokemon */ SELECT, + /** + * Indicates that the party UI is open to select a party member from which items will be discarded. + * This type of selection can be cancelled. + */ + DISCARD, } export enum PartyOption { @@ -120,6 +127,7 @@ export enum PartyOption { RELEASE, RENAME, SELECT, + DISCARD, SCROLL_UP = 1000, SCROLL_DOWN = 1001, FORM_CHANGE_ITEM = 2000, @@ -151,6 +159,7 @@ export class PartyUiHandler extends MessageUiHandler { private partySlotsContainer: Phaser.GameObjects.Container; private partySlots: PartySlot[]; private partyCancelButton: PartyCancelButton; + private partyDiscardModeButton: PartyDiscardModeButton; private partyMessageBox: Phaser.GameObjects.NineSlice; private moveInfoOverlay: MoveInfoOverlay; @@ -176,7 +185,9 @@ export class PartyUiHandler extends MessageUiHandler { private transferAll: boolean; private lastCursor = 0; - private selectCallback: PartySelectCallback | PartyItemTransferSelectCallback | null; + private lastLeftPokemonCursor = 0; + private lastRightPokemonCursor = 0; + private selectCallback: PartySelectCallback | PokemonItemTransferSelectFilter | null; private selectFilter: PokemonSelectFilter | PokemonItemTransferSelectFilter; private moveSelectFilter: PokemonMoveSelectFilter; private tmMoveId: MoveId; @@ -301,6 +312,12 @@ export class PartyUiHandler extends MessageUiHandler { this.iconAnimHandler = new PokemonIconAnimHandler(); this.iconAnimHandler.setup(); + const partyDiscardModeButton = new PartyDiscardModeButton(60, -globalScene.game.canvas.height / 15 - 1, this); + + partyContainer.add(partyDiscardModeButton); + + this.partyDiscardModeButton = partyDiscardModeButton; + // prepare move overlay. in case it appears to be too big, set the overlayScale to .5 const overlayScale = 1; this.moveInfoOverlay = new MoveInfoOverlay({ @@ -342,8 +359,18 @@ export class PartyUiHandler extends MessageUiHandler { this.showMovePp = args.length > 6 && args[6]; this.partyContainer.setVisible(true); - this.partyBg.setTexture(`party_bg${globalScene.currentBattle.double ? "_double" : ""}`); + if (this.isItemManageMode()) { + this.partyBg.setTexture(`party_bg${globalScene.currentBattle.double ? "_double_manage" : ""}`); + } else { + this.partyBg.setTexture(`party_bg${globalScene.currentBattle.double ? "_double" : ""}`); + } + this.populatePartySlots(); + // If we are currently transferring items, set the icon to its proper state and reveal the button. + if (this.isItemManageMode()) { + this.partyDiscardModeButton.toggleIcon(this.partyUiMode as PartyUiMode.MODIFIER_TRANSFER | PartyUiMode.DISCARD); + } + this.showPartyText(); this.setCursor(0); return true; @@ -586,7 +613,7 @@ export class PartyUiHandler extends MessageUiHandler { const option = this.options[this.optionsCursor]; if (button === Button.LEFT) { /** Decrease quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.ITEM_TRANSFER) { + if (this.isItemManageMode()) { this.transferQuantities[option] = this.transferQuantities[option] === 1 ? this.transferQuantitiesMax[option] @@ -600,7 +627,7 @@ export class PartyUiHandler extends MessageUiHandler { if (button === Button.RIGHT) { /** Increase quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.ITEM_TRANSFER) { + if (this.isItemManageMode()) { this.transferQuantities[option] = this.transferQuantities[option] === this.transferQuantitiesMax[option] ? 1 @@ -630,6 +657,45 @@ export class PartyUiHandler extends MessageUiHandler { return success; } + private processDiscardMenuInput(pokemon: PlayerPokemon) { + const ui = this.getUi(); + const option = this.options[this.optionsCursor]; + this.clearOptions(); + + this.blockInput = true; + this.showText(i18next.t("partyUiHandler:discardConfirmation"), null, () => { + this.blockInput = false; + ui.setModeWithoutClear( + UiMode.CONFIRM, + () => { + ui.setMode(UiMode.PARTY); + this.doDiscard(option, pokemon); + }, + () => { + ui.setMode(UiMode.PARTY); + this.showPartyText(); + }, + ); + }); + + return true; + } + + private doDiscard(option: PartyOption, pokemon: PlayerPokemon) { + const itemModifiers = this.getTransferrableItemsFromPokemon(pokemon); + this.clearOptions(); + + if (option === PartyOption.ALL) { + // Discard all currently held items + for (let i = 0; i < itemModifiers.length; i++) { + globalScene.tryDiscardHeldItemModifier(itemModifiers[i], this.transferQuantities[i]); + } + } else { + // Discard the currently selected item + globalScene.tryDiscardHeldItemModifier(itemModifiers[option], this.transferQuantities[option]); + } + } + private moveOptionCursor(button: Button.UP | Button.DOWN): boolean { if (button === Button.UP) { return this.setCursor(this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1); @@ -710,6 +776,10 @@ export class PartyUiHandler extends MessageUiHandler { return this.processItemTransferModeInput(pokemon); } + if (this.partyUiMode === PartyUiMode.DISCARD) { + return this.processDiscardMenuInput(pokemon); + } + // options specific to the mode (moves) if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_REWARD) { return this.processRememberMoveModeInput(pokemon); @@ -849,7 +919,7 @@ export class PartyUiHandler extends MessageUiHandler { } if (button === Button.LEFT || button === Button.RIGHT) { - if (this.partyUiMode === PartyUiMode.ITEM_TRANSFER) { + if (this.isItemManageMode()) { return this.processItemTransferModeLeftRightInput(button); } } @@ -904,10 +974,22 @@ export class PartyUiHandler extends MessageUiHandler { return !(this.partyUiMode === PartyUiMode.FAINT_SWITCH || this.partyUiMode === PartyUiMode.REVIVAL_BLESSING); } + /** + * Return whether this UI handler is responsible for managing items. + * Used to ensure proper placement of mode toggle buttons in the UI, etc. + * @returns Whether the current handler is responsible for managing items. + */ + private isItemManageMode(): boolean { + return this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER || this.partyUiMode === PartyUiMode.DISCARD; + } + private processPartyActionInput(): boolean { const ui = this.getUi(); if (this.cursor < 6) { - if (this.partyUiMode === PartyUiMode.ITEM_TRANSFER && !this.transferMode) { + if ( + (this.partyUiMode === PartyUiMode.ITEM_TRANSFER && !this.transferMode) || + this.partyUiMode === PartyUiMode.DISCARD + ) { /** Initialize item quantities for the selected Pokemon */ const pokemon = globalScene.getPlayerParty()[this.cursor]; const items = pokemon.heldItemManager.getTransferableHeldItems(); @@ -917,6 +999,25 @@ export class PartyUiHandler extends MessageUiHandler { this.showOptions(); ui.playSelect(); } + + // Toggle item transfer mode to discard items or vice versa + if (this.cursor === 7) { + switch (this.partyUiMode) { + case PartyUiMode.DISCARD: + this.partyUiMode = PartyUiMode.MODIFIER_TRANSFER; + break; + case PartyUiMode.MODIFIER_TRANSFER: + this.partyUiMode = PartyUiMode.DISCARD; + break; + default: + ui.playError(); + return false; + } + this.partyDiscardModeButton.toggleIcon(this.partyUiMode); + ui.playSelect(); + return true; + } + // Pressing return button if (this.cursor === 6) { if (!this.allowCancel()) { @@ -937,6 +1038,7 @@ export class PartyUiHandler extends MessageUiHandler { this.clearTransfer(); ui.playSelect(); } else if (this.allowCancel()) { + this.partyDiscardModeButton.clear(); if (this.selectCallback) { const selectCallback = this.selectCallback; this.selectCallback = null; @@ -955,30 +1057,74 @@ export class PartyUiHandler extends MessageUiHandler { const slotCount = this.partySlots.length; const battlerCount = globalScene.currentBattle.getBattlerCount(); + if (this.lastCursor < battlerCount) { + this.lastLeftPokemonCursor = this.lastCursor; + } + if (this.lastCursor >= battlerCount && this.lastCursor < 6) { + this.lastRightPokemonCursor = this.lastCursor; + } + let success = false; switch (button) { + // Item manage mode adds an extra 8th "toggle mode" button to the UI, located *below* both active party members. + // The following logic serves to ensure its menu behaviour matches its in-game position, + // being selected when scrolling up from the first inactive party member or down from the last active one. case Button.UP: + if (this.isItemManageMode()) { + if (this.cursor === 1) { + success = this.setCursor(globalScene.currentBattle.double ? 0 : 7); + break; + } + if (this.cursor === 2) { + success = this.setCursor(globalScene.currentBattle.double ? 7 : 1); + break; + } + if (this.cursor === 6) { + success = this.setCursor(slotCount <= globalScene.currentBattle.getBattlerCount() ? 7 : slotCount - 1); + break; + } + if (this.cursor === 7) { + success = this.setCursor(globalScene.currentBattle.double && slotCount > 1 ? 1 : 0); + break; + } + } success = this.setCursor(this.cursor ? (this.cursor < 6 ? this.cursor - 1 : slotCount - 1) : 6); break; case Button.DOWN: + if (this.isItemManageMode()) { + if (this.cursor === 0) { + success = this.setCursor(globalScene.currentBattle.double && slotCount > 1 ? 1 : 7); + break; + } + if (this.cursor === 1) { + success = this.setCursor(globalScene.currentBattle.double ? 7 : slotCount > 2 ? 2 : 6); + break; + } + if (this.cursor === 7) { + success = this.setCursor( + slotCount > globalScene.currentBattle.getBattlerCount() ? globalScene.currentBattle.getBattlerCount() : 6, + ); + break; + } + } success = this.setCursor(this.cursor < 6 ? (this.cursor < slotCount - 1 ? this.cursor + 1 : 6) : 0); break; case Button.LEFT: - if (this.cursor >= battlerCount && this.cursor <= 6) { - success = this.setCursor(0); + if (this.cursor === 6) { + success = this.setCursor(this.isItemManageMode() ? 7 : this.lastLeftPokemonCursor); + } + if (this.cursor >= battlerCount && this.cursor < 6) { + success = this.setCursor(this.lastLeftPokemonCursor); } break; case Button.RIGHT: - if (slotCount === battlerCount) { + // Scrolling right from item transfer button or with no backup party members goes to cancel + if (this.cursor === 7 || slotCount <= battlerCount) { success = this.setCursor(6); break; } - if (battlerCount >= 2 && slotCount > battlerCount && this.getCursor() === 0 && this.lastCursor === 1) { - success = this.setCursor(2); - break; - } - if (slotCount > battlerCount && this.cursor < battlerCount) { - success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || battlerCount : battlerCount); + if (this.cursor < battlerCount) { + success = this.setCursor(this.lastRightPokemonCursor || battlerCount); break; } } @@ -1025,11 +1171,15 @@ export class PartyUiHandler extends MessageUiHandler { this.partySlots[this.lastCursor].deselect(); } else if (this.lastCursor === 6) { this.partyCancelButton.deselect(); + } else if (this.lastCursor === 7) { + this.partyDiscardModeButton.deselect(); } if (cursor < 6) { this.partySlots[cursor].select(); } else if (cursor === 6) { this.partyCancelButton.select(); + } else if (cursor === 7) { + this.partyDiscardModeButton.select(); } } return changed; @@ -1124,14 +1274,16 @@ export class PartyUiHandler extends MessageUiHandler { optionsMessage = i18next.t("partyUiHandler:selectAnotherPokemonToSplice"); } break; + case PartyUiMode.DISCARD: + optionsMessage = i18next.t("partyUiHandler:changeQuantityDiscard"); } this.showText(optionsMessage, 0); this.updateOptions(); - /** When an item is being selected for transfer, the message box is taller as the message occupies two lines */ - if (this.partyUiMode === PartyUiMode.ITEM_TRANSFER) { + /** When an item is being selected for transfer or discard, the message box is taller as the message occupies two lines */ + if (this.isItemManageMode()) { this.partyMessageBox.setSize(262 - Math.max(this.optionsBg.displayWidth - 56, 0), 42); } else { this.partyMessageBox.setSize(262 - Math.max(this.optionsBg.displayWidth - 56, 0), 30); @@ -1140,6 +1292,20 @@ export class PartyUiHandler extends MessageUiHandler { this.setCursor(0); } + showPartyText() { + switch (this.partyUiMode) { + case PartyUiMode.ITEM_TRANSFER: + this.showText(i18next.t("partyUiHandler:partyTransfer")); + break; + case PartyUiMode.DISCARD: + this.showText(i18next.t("partyUiHandler:partyDiscard")); + break; + default: + this.showText("", 0); + break; + } + } + private allowBatonSwitch(): boolean { return !!( this.partyUiMode !== PartyUiMode.FAINT_SWITCH && @@ -1247,6 +1413,9 @@ export class PartyUiHandler extends MessageUiHandler { this.addCommonOptions(pokemon); } break; + case PartyUiMode.DISCARD: + this.updateOptionsWithModifierTransferMode(pokemon); + break; // TODO: This still needs to be broken up. // It could use a rework differentiating different kind of switches // to treat baton passing separately from switching on faint. @@ -1350,7 +1519,8 @@ export class PartyUiHandler extends MessageUiHandler { optionName = "↓"; } else if ( (this.partyUiMode !== PartyUiMode.REMEMBER_MOVE_REWARD && - (this.partyUiMode !== PartyUiMode.ITEM_TRANSFER || this.transferMode)) || + (this.partyUiMode !== PartyUiMode.ITEM_TRANSFER || this.transferMode) && + this.partyUiMode !== PartyUiMode.DISCARD) || option === PartyOption.CANCEL ) { switch (option) { @@ -1379,7 +1549,7 @@ export class PartyUiHandler extends MessageUiHandler { if (this.localizedOptions.includes(option)) { optionName = i18next.t(`partyUiHandler:${PartyOption[option]}`); } else { - optionName = toReadableString(PartyOption[option]); + optionName = toTitleCase(PartyOption[option]); } } break; @@ -1413,7 +1583,7 @@ export class PartyUiHandler extends MessageUiHandler { const items = pokemon.getHeldItems(); const item = items[option]; if ( - this.partyUiMode === PartyUiMode.ITEM_TRANSFER && + this.isItemManageMode() && this.transferQuantitiesMax[option] > 1 && !this.transferMode && item !== undefined && @@ -1443,7 +1613,6 @@ export class PartyUiHandler extends MessageUiHandler { optionText.x = 15 - this.optionsBg.width; } } - startTransfer(): void { this.transferMode = true; this.transferCursor = this.cursor; @@ -1571,7 +1740,7 @@ export class PartyUiHandler extends MessageUiHandler { this.eraseOptionsCursor(); this.partyMessageBox.setSize(262, 30); - this.showText("", 0); + this.showPartyText(); } eraseOptionsCursor() { @@ -1626,7 +1795,9 @@ class PartySlot extends Phaser.GameObjects.Container { ? -184 + (globalScene.currentBattle.double ? -40 : 0) + (28 + (globalScene.currentBattle.double ? 8 : 0)) * slotIndex - : -124 + (globalScene.currentBattle.double ? -8 : 0) + slotIndex * 64, + : partyUiMode === PartyUiMode.MODIFIER_TRANSFER + ? -124 + (globalScene.currentBattle.double ? -20 : 0) + slotIndex * 55 + : -124 + (globalScene.currentBattle.double ? -8 : 0) + slotIndex * 64, ); this.slotIndex = slotIndex; @@ -1756,17 +1927,16 @@ class PartySlot extends Phaser.GameObjects.Container { const shinyStar = globalScene.add.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`); shinyStar.setOrigin(0, 0); shinyStar.setPositionRelative(this.slotName, -9, 3); - shinyStar.setTint(getVariantTint(this.pokemon.getBaseVariant(doubleShiny))); + shinyStar.setTint(getVariantTint(this.pokemon.getBaseVariant())); slotInfoContainer.add(shinyStar); if (doubleShiny) { - const fusionShinyStar = globalScene.add.image(0, 0, "shiny_star_small_2"); - fusionShinyStar.setOrigin(0, 0); - fusionShinyStar.setPosition(shinyStar.x, shinyStar.y); - fusionShinyStar.setTint( - getVariantTint(this.pokemon.summonData.illusion?.basePokemon.fusionVariant ?? this.pokemon.fusionVariant), - ); + const fusionShinyStar = globalScene.add + .image(0, 0, "shiny_star_small_2") + .setOrigin(0) + .setPosition(shinyStar.x, shinyStar.y) + .setTint(getVariantTint(this.pokemon.fusionVariant)); slotInfoContainer.add(fusionShinyStar); } @@ -1882,7 +2052,6 @@ class PartySlot extends Phaser.GameObjects.Container { class PartyCancelButton extends Phaser.GameObjects.Container { private selected: boolean; - private partyCancelBg: Phaser.GameObjects.Sprite; private partyCancelPb: Phaser.GameObjects.Sprite; @@ -1929,3 +2098,96 @@ class PartyCancelButton extends Phaser.GameObjects.Container { this.partyCancelPb.setFrame("party_pb"); } } + +class PartyDiscardModeButton extends Phaser.GameObjects.Container { + private selected: boolean; + private transferIcon: Phaser.GameObjects.Sprite; + private discardIcon: Phaser.GameObjects.Sprite; + private textBox: Phaser.GameObjects.Text; + private party: PartyUiHandler; + + constructor(x: number, y: number, party: PartyUiHandler) { + super(globalScene, x, y); + + this.setup(party); + } + + setup(party: PartyUiHandler) { + this.transferIcon = globalScene.add.sprite(0, 0, "party_transfer"); + this.discardIcon = globalScene.add.sprite(0, 0, "party_discard"); + this.textBox = addTextObject(-8, -7, i18next.t("partyUiHandler:TRANSFER"), TextStyle.PARTY); + this.party = party; + + this.add(this.transferIcon); + this.add(this.discardIcon); + this.add(this.textBox); + + this.clear(); + } + + select() { + if (this.selected) { + return; + } + + this.selected = true; + + this.party.showText(i18next.t("partyUiHandler:changeMode")); + + this.transferIcon.setFrame("selected"); + this.discardIcon.setFrame("selected"); + } + + deselect() { + if (!this.selected) { + return; + } + + this.selected = false; + this.party.showPartyText(); + + this.transferIcon.setFrame("normal"); + this.discardIcon.setFrame("normal"); + } + + /** + * If the current mode deals with transferring items, toggle the discard items button's name and assets. + * @param partyMode - The current {@linkcode PartyUiMode} + * @remarks + * This will also reveal the button if it is currently hidden. + */ + public toggleIcon(partyMode: PartyUiMode.MODIFIER_TRANSFER | PartyUiMode.DISCARD): void { + this.setActive(true).setVisible(true); + switch (partyMode) { + case PartyUiMode.MODIFIER_TRANSFER: + this.transferIcon.setVisible(true); + this.discardIcon.setVisible(false); + this.textBox.setVisible(true); + this.textBox.setText(i18next.t("partyUiHandler:TRANSFER")); + this.setPosition( + globalScene.currentBattle.double ? 64 : 60, + globalScene.currentBattle.double ? -48 : -globalScene.game.canvas.height / 15 - 1, + ); + this.transferIcon.displayWidth = this.textBox.text.length * 9 + 3; + break; + case PartyUiMode.DISCARD: + this.transferIcon.setVisible(false); + this.discardIcon.setVisible(true); + this.textBox.setVisible(true); + this.textBox.setText(i18next.t("partyUiHandler:DISCARD")); + this.setPosition( + globalScene.currentBattle.double ? 64 : 60, + globalScene.currentBattle.double ? -48 : -globalScene.game.canvas.height / 15 - 1, + ); + this.discardIcon.displayWidth = this.textBox.text.length * 9 + 3; + break; + } + } + + clear() { + this.setActive(false).setVisible(false); + this.transferIcon.setVisible(false); + this.discardIcon.setVisible(false); + this.textBox.setVisible(false); + } +} diff --git a/src/ui/pokedex-info-overlay.ts b/src/ui/pokedex-info-overlay.ts index 6d3b8f1009f..0f2f5fa3dde 100644 --- a/src/ui/pokedex-info-overlay.ts +++ b/src/ui/pokedex-info-overlay.ts @@ -1,6 +1,7 @@ import type { InfoToggle } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; -import { addTextObject, TextStyle } from "#ui/text"; +import { TextStyle } from "#enums/text-style"; +import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt } from "#utils/common"; diff --git a/src/ui/pokedex-mon-container.ts b/src/ui/pokedex-mon-container.ts index 73799870e6b..cfb8555e6c9 100644 --- a/src/ui/pokedex-mon-container.ts +++ b/src/ui/pokedex-mon-container.ts @@ -1,7 +1,8 @@ import { globalScene } from "#app/global-scene"; import type { PokemonSpecies } from "#data/pokemon-species"; +import { TextStyle } from "#enums/text-style"; import type { Variant } from "#sprites/variant"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { isNullOrUndefined } from "#utils/common"; interface SpeciesDetails { diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index ff96aa55772..227b86c4d4d 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -25,7 +25,7 @@ import { getNatureName } from "#data/nature"; import type { SpeciesFormChange } from "#data/pokemon-forms"; import { pokemonFormChanges } from "#data/pokemon-forms"; import type { PokemonSpecies } from "#data/pokemon-species"; -import { getPokemonSpeciesForm, normalForm } from "#data/pokemon-species"; +import { normalForm } from "#data/pokemon-species"; import { AbilityAttr } from "#enums/ability-attr"; import type { AbilityId } from "#enums/ability-id"; import { BiomeId } from "#enums/biome-id"; @@ -38,6 +38,7 @@ import type { Nature } from "#enums/nature"; import { Passive as PassiveAttr } from "#enums/passive"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; +import { TextStyle } from "#enums/text-style"; import { TimeOfDay } from "#enums/time-of-day"; import { UiMode } from "#enums/ui-mode"; import type { Variant } from "#sprites/variant"; @@ -51,18 +52,12 @@ import { MessageUiHandler } from "#ui/message-ui-handler"; import { MoveInfoOverlay } from "#ui/move-info-overlay"; import { PokedexInfoOverlay } from "#ui/pokedex-info-overlay"; import { StatsContainer } from "#ui/stats-container"; -import { addBBCodeTextObject, addTextObject, getTextColor, getTextStyleOptions, TextStyle } from "#ui/text"; +import { addBBCodeTextObject, addTextObject, getTextColor, getTextStyleOptions } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; -import { - BooleanHolder, - getLocalizedSpriteKey, - isNullOrUndefined, - padInt, - rgbHexToRgba, - toReadableString, -} from "#utils/common"; +import { BooleanHolder, getLocalizedSpriteKey, isNullOrUndefined, padInt, rgbHexToRgba } from "#utils/common"; import { getEnumValues } from "#utils/enums"; -import { getPokemonSpecies } from "#utils/pokemon-utils"; +import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; +import { toTitleCase } from "#utils/strings"; import { argbFromRgba } from "@material/material-color-utilities"; import i18next from "i18next"; import type BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; @@ -2619,7 +2614,7 @@ export class PokedexPageUiHandler extends MessageUiHandler { // Setting growth rate text if (isFormCaught) { - let growthReadable = toReadableString(GrowthRate[species.growthRate]); + let growthReadable = toTitleCase(GrowthRate[species.growthRate]); const growthAux = growthReadable.replace(" ", "_"); if (i18next.exists("growth:" + growthAux)) { growthReadable = i18next.t(("growth:" + growthAux) as any); diff --git a/src/ui/pokedex-scan-ui-handler.ts b/src/ui/pokedex-scan-ui-handler.ts index bcf869f6f39..ab3258a03de 100644 --- a/src/ui/pokedex-scan-ui-handler.ts +++ b/src/ui/pokedex-scan-ui-handler.ts @@ -158,8 +158,11 @@ export class PokedexScanUiHandler extends FormModalUiHandler { if (super.show(args)) { const config = args[0] as ModalConfig; - this.inputs[0].resize(1150, 116); - this.inputContainers[0].list[0].width = 200; + const label = this.formLabels[0]; + + const inputWidth = label.width < 420 ? 200 : 200 - (label.width - 420) / 5.75; + this.inputs[0].resize(inputWidth * 5.75, 116); + this.inputContainers[0].list[0].width = inputWidth; if (args[1] && typeof (args[1] as PlayerPokemon).getNameToRender === "function") { this.inputs[0].text = (args[1] as PlayerPokemon).getNameToRender(); } else { diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index c2f595cb190..cd1dc312f4d 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -15,7 +15,7 @@ import { import { speciesTmMoves } from "#balance/tms"; import { allAbilities, allMoves, allSpecies } from "#data/data-lists"; import type { PokemonForm, PokemonSpecies } from "#data/pokemon-species"; -import { getPokemonSpeciesForm, getPokerusStarters, normalForm } from "#data/pokemon-species"; +import { normalForm } from "#data/pokemon-species"; import { AbilityAttr } from "#enums/ability-attr"; import { AbilityId } from "#enums/ability-id"; import { BiomeId } from "#enums/biome-id"; @@ -26,6 +26,7 @@ import type { Nature } from "#enums/nature"; import { Passive as PassiveAttr } from "#enums/passive"; import { PokemonType } from "#enums/pokemon-type"; import type { SpeciesId } from "#enums/species-id"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { Variant } from "#sprites/variant"; import { getVariantIcon, getVariantTint } from "#sprites/variant"; @@ -40,11 +41,12 @@ import { MessageUiHandler } from "#ui/message-ui-handler"; import { PokedexMonContainer } from "#ui/pokedex-mon-container"; import { PokemonIconAnimHandler, PokemonIconAnimMode } from "#ui/pokemon-icon-anim-handler"; import { ScrollBar } from "#ui/scroll-bar"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { BooleanHolder, fixedInt, getLocalizedSpriteKey, padInt, randIntRange, rgbHexToRgba } from "#utils/common"; import type { StarterPreferences } from "#utils/data"; import { loadStarterPreferences } from "#utils/data"; +import { getPokemonSpeciesForm, getPokerusStarters } from "#utils/pokemon-utils"; import { argbFromRgba } from "@material/material-color-utilities"; import i18next from "i18next"; diff --git a/src/ui/pokemon-hatch-info-container.ts b/src/ui/pokemon-hatch-info-container.ts index 8bcd62316cd..bb1cc22e9fd 100644 --- a/src/ui/pokemon-hatch-info-container.ts +++ b/src/ui/pokemon-hatch-info-container.ts @@ -5,13 +5,14 @@ import { allMoves } from "#data/data-lists"; import { getEggTierForSpecies } from "#data/egg"; import type { EggHatchData } from "#data/egg-hatch-data"; import { Gender } from "#data/gender"; -import { getPokemonSpeciesForm } from "#data/pokemon-species"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; +import { TextStyle } from "#enums/text-style"; import type { PlayerPokemon } from "#field/pokemon"; import { PokemonInfoContainer } from "#ui/pokemon-info-container"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { padInt, rgbHexToRgba } from "#utils/common"; +import { getPokemonSpeciesForm } from "#utils/pokemon-utils"; import { argbFromRgba } from "@material/material-color-utilities"; /** diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 707d476da5f..15498c68c9d 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -3,13 +3,14 @@ import { Gender, getGenderColor, getGenderSymbol } from "#data/gender"; import { getNatureName } from "#data/nature"; import { DexAttr } from "#enums/dex-attr"; import { PokemonType } from "#enums/pokemon-type"; +import { TextStyle } from "#enums/text-style"; import type { Pokemon } from "#field/pokemon"; import { getVariantTint } from "#sprites/variant"; import type { StarterDataEntry } from "#system/game-data"; import type { DexEntry } from "#types/dex-data"; import { ConfirmUiHandler } from "#ui/confirm-ui-handler"; import { StatsContainer } from "#ui/stats-container"; -import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#ui/text"; +import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt, getShinyDescriptor } from "#utils/common"; import i18next from "i18next"; diff --git a/src/ui/registration-form-ui-handler.ts b/src/ui/registration-form-ui-handler.ts index 2466603af71..2c8080d534d 100644 --- a/src/ui/registration-form-ui-handler.ts +++ b/src/ui/registration-form-ui-handler.ts @@ -1,25 +1,13 @@ import { pokerogueApi } from "#api/pokerogue-api"; import { globalScene } from "#app/global-scene"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; import type { ModalConfig } from "#ui/modal-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import i18next from "i18next"; -interface LanguageSetting { - inputFieldFontSize?: string; - warningMessageFontSize?: string; - errorMessageFontSize?: string; -} - -const languageSettings: { [key: string]: LanguageSetting } = { - "es-ES": { - inputFieldFontSize: "50px", - errorMessageFontSize: "40px", - }, -}; - export class RegistrationFormUiHandler extends FormModalUiHandler { getModalTitle(_config?: ModalConfig): string { return i18next.t("menu:register"); @@ -34,7 +22,7 @@ export class RegistrationFormUiHandler extends FormModalUiHandler { } getButtonTopMargin(): number { - return 8; + return 12; } getButtonLabels(_config?: ModalConfig): string[] { @@ -75,18 +63,9 @@ export class RegistrationFormUiHandler extends FormModalUiHandler { setup(): void { super.setup(); - this.modalContainer.list.forEach((child: Phaser.GameObjects.GameObject) => { - if (child instanceof Phaser.GameObjects.Text && child !== this.titleText) { - const inputFieldFontSize = languageSettings[i18next.resolvedLanguage!]?.inputFieldFontSize; - if (inputFieldFontSize) { - child.setFontSize(inputFieldFontSize); - } - } - }); - - const warningMessageFontSize = languageSettings[i18next.resolvedLanguage!]?.warningMessageFontSize ?? "42px"; const label = addTextObject(10, 87, i18next.t("menu:registrationAgeWarning"), TextStyle.TOOLTIP_CONTENT, { - fontSize: warningMessageFontSize, + fontSize: "42px", + wordWrap: { width: 850 }, }); this.modalContainer.add(label); @@ -106,10 +85,6 @@ export class RegistrationFormUiHandler extends FormModalUiHandler { const onFail = error => { globalScene.ui.setMode(UiMode.REGISTRATION_FORM, Object.assign(config, { errorMessage: error?.trim() })); globalScene.ui.playError(); - const errorMessageFontSize = languageSettings[i18next.resolvedLanguage!]?.errorMessageFontSize; - if (errorMessageFontSize) { - this.errorMessage.setFontSize(errorMessageFontSize); - } }; if (!this.inputs[0].text) { return onFail(i18next.t("menu:emptyUsername")); diff --git a/src/ui/reward-select-ui-handler.ts b/src/ui/reward-select-ui-handler.ts index 2e4776adc0b..7b222460b97 100644 --- a/src/ui/reward-select-ui-handler.ts +++ b/src/ui/reward-select-ui-handler.ts @@ -6,6 +6,7 @@ import { getPokeballAtlasKey } from "#data/pokeball"; import { Button } from "#enums/buttons"; import type { PokeballType } from "#enums/pokeball"; import { ShopCursorTarget } from "#enums/shop-cursor-target"; +import { TextStyle } from "#enums/text-style"; import { TrainerItemId } from "#enums/trainer-item-id"; import { UiMode } from "#enums/ui-mode"; import type { RewardOption } from "#items/reward"; @@ -13,7 +14,7 @@ import { getPlayerShopRewardOptionsForWave, isTmReward } from "#items/reward-uti import { TrainerItemEffect } from "#items/trainer-item"; import { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; import { MoveInfoOverlay } from "#ui/move-info-overlay"; -import { addTextObject, getRarityTierTextTint, getTextColor, getTextStyleOptions, TextStyle } from "#ui/text"; +import { addTextObject, getRarityTierTextTint, getTextColor, getTextStyleOptions } from "#ui/text"; import { formatMoney, NumberHolder } from "#utils/common"; import i18next from "i18next"; import Phaser from "phaser"; @@ -69,7 +70,8 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { if (context) { context.font = styleOptions.fontSize + "px " + styleOptions.fontFamily; - this.transferButtonWidth = context.measureText(i18next.t("rewardSelectUiHandler:transfer")).width; + // TODO @Wlowscha: Rename these locales + this.transferButtonWidth = context.measureText(i18next.t("rewardSelectUiHandler:manageItems")).width; this.checkButtonWidth = context.measureText(i18next.t("rewardSelectUiHandler:checkTeam")).width; } @@ -81,7 +83,8 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { this.transferButtonContainer.setVisible(false); ui.add(this.transferButtonContainer); - const transferButtonText = addTextObject(-4, -2, i18next.t("rewardSelectUiHandler:transfer"), TextStyle.PARTY); + // TODO @Wlowscha: Remember to rename these locales + const transferButtonText = addTextObject(-4, -2, i18next.t("rewardSelectUiHandler:manageItems"), TextStyle.PARTY); transferButtonText.setName("text-transfer-btn"); transferButtonText.setOrigin(1, 0); this.transferButtonContainer.add(transferButtonText); @@ -274,12 +277,23 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { // causing errors if reroll is selected this.awaitingActionInput = false; - // TODO: Replace with `Promise.withResolvers` when possible. - let tweenResolve: () => void; - const tweenPromise = new Promise(resolve => (tweenResolve = resolve)); + const { promise: tweenPromise, resolve: tweenResolve } = Promise.withResolvers(); let i = 0; - // TODO: Rework this bespoke logic for animating the modifier options. + // #region: animation + /** Holds promises that resolve once each reward's *upgrade animation* has finished playing */ + const rewardAnimPromises: Promise[] = []; + /** Holds promises that resolves once *all* animations for a reward have finished playing */ + const rewardAnimAllSettledPromises: Promise[] = []; + + /* + * A counter here is used instead of a loop to "stagger" the apperance of each reward, + * using `sine.easeIn` to speed up the appearance of the rewards as each animation progresses. + * + * The `onComplete` callback for this tween is set to resolve once the upgrade animations + * for each reward has finished playing, allowing for the next set of animations to + * start to appear. + */ globalScene.tweens.addCounter({ ease: "Sine.easeIn", duration: 1250, @@ -289,30 +303,35 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { const index = Math.floor(value * typeOptions.length); if (index > i && index <= typeOptions.length) { const option = this.options[i]; - option?.show( - Math.floor((1 - value) * 1250) * 0.325 + 2000 * maxUpgradeCount, - -(maxUpgradeCount - typeOptions[i].upgradeCount), - ); + if (option) { + rewardAnimPromises.push( + option.show( + Math.floor((1 - value) * 1250) * 0.325 + 2000 * maxUpgradeCount, + -(maxUpgradeCount - typeOptions[i].upgradeCount), + rewardAnimAllSettledPromises, + ), + ); + } i++; } }, onComplete: () => { - tweenResolve(); + Promise.allSettled(rewardAnimPromises).then(() => tweenResolve()); }, }); - let shopResolve: () => void; - const shopPromise = new Promise(resolve => (shopResolve = resolve)); - tweenPromise.then(() => { - globalScene.time.delayedCall(1000, () => { - for (const shopOption of this.shopOptionsRows.flat()) { - shopOption.show(0, 0); - } - shopResolve(); - }); + /** Holds promises that resolve once each shop item has finished animating */ + const shopAnimPromises: Promise[] = []; + globalScene.time.delayedCall(1000 + maxUpgradeCount * 2000, () => { + for (const shopOption of this.shopOptionsRows.flat()) { + // It is safe to skip awaiting the `show` method here, + // as the promise it returns is also part of the promise appended to `shopAnimPromises`, + // which is awaited later on. + shopOption.show(0, 0, shopAnimPromises, false); + } }); - shopPromise.then(() => { + tweenPromise.then(() => { globalScene.time.delayedCall(500, () => { if (partyHasHeldItem) { this.transferButtonContainer.setAlpha(0); @@ -345,31 +364,39 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { duration: 250, }); - const updateCursorTarget = () => { - if (globalScene.shopCursorTarget === ShopCursorTarget.CHECK_TEAM) { - this.setRowCursor(0); - this.setCursor(2); - } else if (globalScene.shopCursorTarget === ShopCursorTarget.SHOP && globalScene.gameMode.hasNoShop) { - this.setRowCursor(ShopCursorTarget.REWARDS); - this.setCursor(0); - } else { - this.setRowCursor(globalScene.shopCursorTarget); - this.setCursor(0); - } - }; + // Ensure that the reward animations have completed before allowing input to proceed. + // Required to ensure that the user cannot interact with the UI before the animations + // have completed, (which, among other things, would allow the GameObjects to be destroyed + // before the animations have completed, causing errors). + Promise.allSettled([...shopAnimPromises, ...rewardAnimAllSettledPromises]).then(() => { + const updateCursorTarget = () => { + if (globalScene.shopCursorTarget === ShopCursorTarget.CHECK_TEAM) { + this.setRowCursor(0); + this.setCursor(2); + } else if (globalScene.shopCursorTarget === ShopCursorTarget.SHOP && globalScene.gameMode.hasNoShop) { + this.setRowCursor(ShopCursorTarget.REWARDS); + this.setCursor(0); + } else { + this.setRowCursor(globalScene.shopCursorTarget); + this.setCursor(0); + } + }; - updateCursorTarget(); + updateCursorTarget(); - handleTutorial(Tutorial.Select_Item).then(res => { - if (res) { - updateCursorTarget(); - } - this.awaitingActionInput = true; - this.onActionInput = args[2]; + handleTutorial(Tutorial.Select_Item).then(res => { + if (res) { + updateCursorTarget(); + } + this.awaitingActionInput = true; + this.onActionInput = args[2]; + }); }); }); }); + // #endregion: animation + return true; } @@ -577,7 +604,8 @@ export class RewardSelectUiHandler extends AwaitableUiHandler { (globalScene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, OPTION_BUTTON_YPOSITION + 4, ); - ui.showText(i18next.t("rewardSelectUiHandler:transferDesc")); + // TODO @Wlowscha: Remember to rename these locales + ui.showText(i18next.t("rewardSelectUiHandler:manageItemsDesc")); } else if (cursor === 2) { this.cursorObj.setPosition( (globalScene.game.canvas.width - this.checkButtonWidth) / 6 - 10, @@ -821,14 +849,47 @@ class ModifierOption extends Phaser.GameObjects.Container { } } - show(remainingDuration: number, upgradeCountOffset: number) { - if (!this.rewardOption.cost) { + // TODO @SirzBenjie: Review to make sure I didn't bork the method + + /** + * Start the tweens responsible for animating the option's appearance + * + * @privateRemarks + * This method is unusual. It "returns" (one via the actual return, one by via appending to the `promiseHolder` + * parameter) two promises. The promise returned by the method resolves once the option's appearance animations have + * completed, and is meant to allow callers to synchronize with the completion of the option's appearance animations. + * The promise appended to `promiseHolder` resolves once *all* animations started by this method have completed, + * and should be used by callers to ensure that all animations have completed before proceeding. + * + * @param remainingDuration - The duration in milliseconds that the animation can play for + * @param upgradeCountOffset - The offset to apply to the upgrade count for options whose rarity is being upgraded + * @param promiseHolder - A promise that resolves once all tweens started by this method have completed will be pushed to this array. + * @param isReward - Whether the option being shown is a reward, meaning it should show pokeball and upgrade animations. + * @returns A promise that resolves once the *option's apperance animations* have completed. This promise will resolve _before_ all + * promises that are initiated in this method complete. Instead, the `promiseHolder` array will contain a new promise + * that will resolve once all animations have completed. + * + */ + async show( + remainingDuration: number, + upgradeCountOffset: number, + promiseHolder: Promise[], + isReward = true, + ): Promise { + /** Promises for the pokeball and upgrade animations */ + const animPromises: Promise[] = []; + if (isReward) { + const { promise: bouncePromise, resolve: resolveBounce } = Promise.withResolvers(); globalScene.tweens.add({ targets: this.pb, y: 0, duration: 1250, ease: "Bounce.Out", + onComplete: () => { + resolveBounce(); + }, }); + animPromises.push(bouncePromise); let lastValue = 1; let bounceCount = 0; @@ -858,7 +919,9 @@ class ModifierOption extends Phaser.GameObjects.Container { // TODO: Figure out proper delay between chains and then convert this into a single tween chain // rather than starting multiple tween chains. + for (let u = 0; u < this.rewardOption.upgradeCount; u++) { + const { resolve, promise } = Promise.withResolvers(); globalScene.tweens.chain({ tweens: [ { @@ -884,65 +947,99 @@ class ModifierOption extends Phaser.GameObjects.Container { ease: "Sine.easeOut", onComplete: () => { this.pbTint.setVisible(false); + resolve(); }, }, ], }); + animPromises.push(promise); } } + const finalPromises: Promise[] = []; globalScene.time.delayedCall(remainingDuration + 2000, () => { - if (!globalScene) { - return; - } - - if (!this.rewardOption.cost) { + if (isReward) { this.pb.setTexture("pb", `${this.getPbAtlasKey(0)}_open`); globalScene.playSound("se/pb_rel"); + const { resolve: pbResolve, promise: pbPromise } = Promise.withResolvers(); + globalScene.tweens.add({ targets: this.pb, duration: 500, - delay: 250, ease: "Sine.easeIn", alpha: 0, - onComplete: () => this.pb.destroy(), + onComplete: () => { + Promise.allSettled(animPromises).then(() => this.pb.destroy()); + pbResolve(); + }, }); + finalPromises.push(pbPromise); } + /** Delay for the rest of the tweens to ensure they show after the pokeball animation begins to appear */ + const delay = isReward ? 250 : 0; + + const { resolve: itemResolve, promise: itemPromise } = Promise.withResolvers(); globalScene.tweens.add({ targets: this.itemContainer, + delay, duration: 500, ease: "Elastic.Out", scale: 2, alpha: 1, + onComplete: () => { + itemResolve(); + }, }); - if (!this.rewardOption.cost) { + finalPromises.push(itemPromise); + + if (isReward) { + const { resolve: itemTintResolve, promise: itemTintPromise } = Promise.withResolvers(); globalScene.tweens.add({ targets: this.itemTint, alpha: 0, + delay, duration: 500, ease: "Sine.easeIn", - onComplete: () => this.itemTint.destroy(), + onComplete: () => { + this.itemTint.destroy(); + itemTintResolve(); + }, }); + finalPromises.push(itemTintPromise); } + + const { resolve: itemTextResolve, promise: itemTextPromise } = Promise.withResolvers(); globalScene.tweens.add({ targets: this.itemText, + delay, duration: 500, alpha: 1, y: 25, ease: "Cubic.easeInOut", + onComplete: () => itemTextResolve(), }); + finalPromises.push(itemTextPromise); + if (this.itemCostText) { + const { resolve: itemCostResolve, promise: itemCostPromise } = Promise.withResolvers(); globalScene.tweens.add({ targets: this.itemCostText, + delay, duration: 500, alpha: 1, y: 35, ease: "Cubic.easeInOut", + onComplete: () => itemCostResolve(), }); + finalPromises.push(itemCostPromise); } }); + // The `.then` suppresses the return type for the Promise.allSettled so that it returns void. + promiseHolder.push(Promise.allSettled([...animPromises, ...finalPromises]).then()); + + await Promise.allSettled(animPromises); } getPbAtlasKey(tierOffset = 0) { diff --git a/src/ui/run-history-ui-handler.ts b/src/ui/run-history-ui-handler.ts index f810468aea1..00aa47ae65d 100644 --- a/src/ui/run-history-ui-handler.ts +++ b/src/ui/run-history-ui-handler.ts @@ -3,13 +3,14 @@ import { BattleType } from "#enums/battle-type"; import { Button } from "#enums/buttons"; import { GameModes } from "#enums/game-modes"; import { PlayerGender } from "#enums/player-gender"; +import { TextStyle } from "#enums/text-style"; import { TrainerVariant } from "#enums/trainer-variant"; import { UiMode } from "#enums/ui-mode"; import type { RunEntry } from "#system/game-data"; import type { PokemonData } from "#system/pokemon-data"; import { MessageUiHandler } from "#ui/message-ui-handler"; import { RunDisplayMode } from "#ui/run-info-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt, formatLargeNumber } from "#utils/common"; import i18next from "i18next"; diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 8bd0b276b6f..dbc7585f6e6 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -13,6 +13,7 @@ import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PlayerGender } from "#enums/player-gender"; import { PokemonType } from "#enums/pokemon-type"; import type { SpeciesId } from "#enums/species-id"; +import { TextStyle } from "#enums/text-style"; import { TrainerVariant } from "#enums/trainer-variant"; import { UiMode } from "#enums/ui-mode"; import { heldItemSortFunc } from "#items/item-utility"; @@ -20,7 +21,7 @@ import { getVariantTint } from "#sprites/variant"; import type { SessionSaveData } from "#system/game-data"; import type { PokemonData } from "#system/pokemon-data"; import { SettingKeyboard } from "#system/settings-keyboard"; -import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#ui/text"; +import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text"; import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import { formatFancyLargeNumber, formatLargeNumber, formatMoney, getPlayTimeString } from "#utils/common"; diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index b54624f52b1..2f53411c142 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -2,12 +2,13 @@ import { GameMode } from "#app/game-mode"; import { globalScene } from "#app/global-scene"; import { allTrainerItems } from "#data/data-lists"; import { Button } from "#enums/buttons"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { SessionSaveData } from "#system/game-data"; import type { PokemonData } from "#system/pokemon-data"; import { MessageUiHandler } from "#ui/message-ui-handler"; import { RunDisplayMode } from "#ui/run-info-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt, formatLargeNumber, getPlayTimeString, isNullOrUndefined } from "#utils/common"; import i18next from "i18next"; diff --git a/src/ui/session-reload-modal-ui-handler.ts b/src/ui/session-reload-modal-ui-handler.ts index ab1197324a6..1f5a205f990 100644 --- a/src/ui/session-reload-modal-ui-handler.ts +++ b/src/ui/session-reload-modal-ui-handler.ts @@ -1,7 +1,8 @@ +import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; import type { ModalConfig } from "#ui/modal-ui-handler"; import { ModalUiHandler } from "#ui/modal-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; export class SessionReloadModalUiHandler extends ModalUiHandler { constructor(mode: UiMode | null = null) { diff --git a/src/ui/settings/abstract-binding-ui-handler.ts b/src/ui/settings/abstract-binding-ui-handler.ts index 7004af8c4ed..eb68456a69d 100644 --- a/src/ui/settings/abstract-binding-ui-handler.ts +++ b/src/ui/settings/abstract-binding-ui-handler.ts @@ -1,8 +1,9 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; +import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; import { NavigationManager } from "#ui/navigation-menu"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/settings/abstract-control-settings-ui-handler.ts b/src/ui/settings/abstract-control-settings-ui-handler.ts index 64786849abc..ee9e990ee2a 100644 --- a/src/ui/settings/abstract-control-settings-ui-handler.ts +++ b/src/ui/settings/abstract-control-settings-ui-handler.ts @@ -2,13 +2,15 @@ import { globalScene } from "#app/global-scene"; import type { InterfaceConfig } from "#app/inputs-controller"; import { Button } from "#enums/buttons"; import type { Device } from "#enums/devices"; +import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; import { getIconWithSettingName } from "#inputs/config-handler"; import { NavigationManager, NavigationMenu } from "#ui/navigation-menu"; import { ScrollBar } from "#ui/scroll-bar"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; +import { toCamelCase } from "#utils/strings"; import i18next from "i18next"; export interface InputsIcons { @@ -87,12 +89,6 @@ export abstract class AbstractControlSettingsUiHandler extends UiHandler { return settings; } - private camelize(string: string): string { - return string - .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => (index === 0 ? word.toLowerCase() : word.toUpperCase())) - .replace(/\s+/g, ""); - } - /** * Setup UI elements. */ @@ -209,14 +205,15 @@ export abstract class AbstractControlSettingsUiHandler extends UiHandler { settingFiltered.forEach((setting, s) => { // Convert the setting key from format 'Key_Name' to 'Key name' for display. - const settingName = setting.replace(/_/g, " "); + // TODO: IDK if this can be followed by both an underscore and a space, so leaving it as a regex matching both for now + const i18nKey = toCamelCase(setting.replace(/Alt(_| )/, "")); // Create and add a text object for the setting name to the scene. const isLock = this.settingBlacklisted.includes(this.setting[setting]); const labelStyle = isLock ? TextStyle.SETTINGS_LOCKED : TextStyle.SETTINGS_LABEL; + const isAlt = setting.includes("Alt"); let labelText: string; - const i18nKey = this.camelize(settingName.replace("Alt ", "")); - if (settingName.toLowerCase().includes("alt")) { + if (isAlt) { labelText = `${i18next.t(`settings:${i18nKey}`)}${i18next.t("settings:alt")}`; } else { labelText = i18next.t(`settings:${i18nKey}`); diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index 9e56ae80b14..81d733220fc 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -1,5 +1,6 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { SettingType } from "#system/settings"; import { Setting, SettingKeys } from "#system/settings"; @@ -7,7 +8,7 @@ import type { InputsIcons } from "#ui/abstract-control-settings-ui-handler"; import { MessageUiHandler } from "#ui/message-ui-handler"; import { NavigationManager, NavigationMenu } from "#ui/navigation-menu"; import { ScrollBar } from "#ui/scroll-bar"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/settings/gamepad-binding-ui-handler.ts b/src/ui/settings/gamepad-binding-ui-handler.ts index e97fc56d7c0..53d606b6f84 100644 --- a/src/ui/settings/gamepad-binding-ui-handler.ts +++ b/src/ui/settings/gamepad-binding-ui-handler.ts @@ -1,9 +1,10 @@ import { globalScene } from "#app/global-scene"; import { Device } from "#enums/devices"; +import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; import { getIconWithSettingName, getKeyWithKeycode } from "#inputs/config-handler"; import { AbstractBindingUiHandler } from "#ui/abstract-binding-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import i18next from "i18next"; export class GamepadBindingUiHandler extends AbstractBindingUiHandler { diff --git a/src/ui/settings/keyboard-binding-ui-handler.ts b/src/ui/settings/keyboard-binding-ui-handler.ts index e43184795d1..b339ac16188 100644 --- a/src/ui/settings/keyboard-binding-ui-handler.ts +++ b/src/ui/settings/keyboard-binding-ui-handler.ts @@ -1,9 +1,10 @@ import { globalScene } from "#app/global-scene"; import { Device } from "#enums/devices"; +import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; import { getKeyWithKeycode } from "#inputs/config-handler"; import { AbstractBindingUiHandler } from "#ui/abstract-binding-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import i18next from "i18next"; export class KeyboardBindingUiHandler extends AbstractBindingUiHandler { diff --git a/src/ui/settings/navigation-menu.ts b/src/ui/settings/navigation-menu.ts index 1303c32d3a5..2f3aa50f7f3 100644 --- a/src/ui/settings/navigation-menu.ts +++ b/src/ui/settings/navigation-menu.ts @@ -1,8 +1,9 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { InputsIcons } from "#ui/abstract-control-settings-ui-handler"; -import { addTextObject, setTextStyle, TextStyle } from "#ui/text"; +import { addTextObject, setTextStyle } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import i18next from "i18next"; diff --git a/src/ui/settings/settings-display-ui-handler.ts b/src/ui/settings/settings-display-ui-handler.ts index 3c261d6ddab..1a0481b8e8d 100644 --- a/src/ui/settings/settings-display-ui-handler.ts +++ b/src/ui/settings/settings-display-ui-handler.ts @@ -117,6 +117,12 @@ export class SettingsDisplayUiHandler extends AbstractSettingsUiHandler { label: "Română (Needs Help)", }; break; + case "tl": + this.settings[languageIndex].options[0] = { + value: "Tagalog", + label: "Tagalog (Needs Help)", + }; + break; default: this.settings[languageIndex].options[0] = { value: "English", diff --git a/src/ui/settings/settings-gamepad-ui-handler.ts b/src/ui/settings/settings-gamepad-ui-handler.ts index ea2e18a2ce6..57a70411f4c 100644 --- a/src/ui/settings/settings-gamepad-ui-handler.ts +++ b/src/ui/settings/settings-gamepad-ui-handler.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; import type { InterfaceConfig } from "#app/inputs-controller"; import { Device } from "#enums/devices"; +import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; import pad_dualshock from "#inputs/pad-dualshock"; import pad_unlicensedSNES from "#inputs/pad-unlicensed-snes"; @@ -13,7 +14,7 @@ import { settingGamepadOptions, } from "#system/settings-gamepad"; import { AbstractControlSettingsUiHandler } from "#ui/abstract-control-settings-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { truncateString } from "#utils/common"; import i18next from "i18next"; diff --git a/src/ui/settings/settings-keyboard-ui-handler.ts b/src/ui/settings/settings-keyboard-ui-handler.ts index 2c2e0dbd7cd..295a71abe36 100644 --- a/src/ui/settings/settings-keyboard-ui-handler.ts +++ b/src/ui/settings/settings-keyboard-ui-handler.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; import type { InterfaceConfig } from "#app/inputs-controller"; import { Device } from "#enums/devices"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import cfg_keyboard_qwerty from "#inputs/cfg-keyboard-qwerty"; import { deleteBind } from "#inputs/config-handler"; @@ -13,8 +14,9 @@ import { } from "#system/settings-keyboard"; import { AbstractControlSettingsUiHandler } from "#ui/abstract-control-settings-ui-handler"; import { NavigationManager } from "#ui/navigation-menu"; -import { addTextObject, TextStyle } from "#ui/text"; -import { reverseValueToKeySetting, truncateString } from "#utils/common"; +import { addTextObject } from "#ui/text"; +import { truncateString } from "#utils/common"; +import { toPascalSnakeCase } from "#utils/strings"; import i18next from "i18next"; /** @@ -100,7 +102,7 @@ export class SettingsKeyboardUiHandler extends AbstractControlSettingsUiHandler } const cursor = this.cursor + this.scrollCursor; // Calculate the absolute cursor position. const selection = this.settingLabels[cursor].text; - const key = reverseValueToKeySetting(selection); + const key = toPascalSnakeCase(selection); const settingName = SettingKeyboard[key]; const activeConfig = this.getActiveConfig(); const success = deleteBind(this.getActiveConfig(), settingName); diff --git a/src/ui/starter-container.ts b/src/ui/starter-container.ts index 4c174dc5955..f81ac8e5bfb 100644 --- a/src/ui/starter-container.ts +++ b/src/ui/starter-container.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; import type { PokemonSpecies } from "#data/pokemon-species"; -import { addTextObject, TextStyle } from "#ui/text"; +import { TextStyle } from "#enums/text-style"; +import { addTextObject } from "#ui/text"; export class StarterContainer extends Phaser.GameObjects.Container { public species: PokemonSpecies; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 18a3fbc30a3..dac6bc677a2 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -24,7 +24,6 @@ import { Gender, getGenderColor, getGenderSymbol } from "#data/gender"; import { getNatureName } from "#data/nature"; import { pokemonFormChanges } from "#data/pokemon-forms"; import type { PokemonSpecies } from "#data/pokemon-species"; -import { getPokemonSpeciesForm, getPokerusStarters } from "#data/pokemon-species"; import { AbilityAttr } from "#enums/ability-attr"; import { AbilityId } from "#enums/ability-id"; import { Button } from "#enums/buttons"; @@ -39,6 +38,7 @@ import type { Nature } from "#enums/nature"; import { Passive as PassiveAttr } from "#enums/passive"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { CandyUpgradeNotificationChangedEvent } from "#events/battle-scene"; import { BattleSceneEventType } from "#events/battle-scene"; @@ -57,7 +57,7 @@ import { PokemonIconAnimHandler, PokemonIconAnimMode } from "#ui/pokemon-icon-an import { ScrollBar } from "#ui/scroll-bar"; import { StarterContainer } from "#ui/starter-container"; import { StatsContainer } from "#ui/stats-container"; -import { addBBCodeTextObject, addTextObject, TextStyle } from "#ui/text"; +import { addBBCodeTextObject, addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { BooleanHolder, @@ -68,10 +68,11 @@ import { padInt, randIntRange, rgbHexToRgba, - toReadableString, } from "#utils/common"; import type { StarterPreferences } from "#utils/data"; import { loadStarterPreferences, saveStarterPreferences } from "#utils/data"; +import { getPokemonSpeciesForm, getPokerusStarters } from "#utils/pokemon-utils"; +import { toTitleCase } from "#utils/strings"; import { argbFromRgba } from "@material/material-color-utilities"; import i18next from "i18next"; import type { GameObjects } from "phaser"; @@ -175,6 +176,10 @@ const languageSettings: { [key: string]: LanguageSetting } = { starterInfoYOffset: 0.5, starterInfoXPos: 26, }, + tl: { + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, }; const valueReductionMax = 2; @@ -1476,7 +1481,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { loop: -1, // Make the initial bounce a little randomly delayed delay: randIntRange(0, 50) * 5, - loopDelay: 1000, + loopDelay: fixedInt(1000), tweens: [ { targets: icon, @@ -3526,7 +3531,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.pokemonLuckLabelText.setVisible(this.pokemonLuckText.visible); //Growth translate - let growthReadable = toReadableString(GrowthRate[species.growthRate]); + let growthReadable = toTitleCase(GrowthRate[species.growthRate]); const growthAux = growthReadable.replace(" ", "_"); if (i18next.exists("growth:" + growthAux)) { growthReadable = i18next.t(("growth:" + growthAux) as any); @@ -4302,7 +4307,10 @@ export class StarterSelectUiHandler extends MessageUiHandler { return true; } - tryExit(): boolean { + /** + * Attempt to back out of the starter selection screen into the appropriate parent modal + */ + tryExit(): void { this.blockInput = true; const ui = this.getUi(); @@ -4316,12 +4324,13 @@ export class StarterSelectUiHandler extends MessageUiHandler { UiMode.CONFIRM, () => { ui.setMode(UiMode.STARTER_SELECT); - globalScene.phaseManager.clearPhaseQueue(); - if (globalScene.gameMode.isChallenge) { + // Non-challenge modes go directly back to title, while challenge modes go to the selection screen. + if (!globalScene.gameMode.isChallenge) { + globalScene.phaseManager.toTitleScreen(); + } else { + globalScene.phaseManager.clearPhaseQueue(); globalScene.phaseManager.pushNew("SelectChallengePhase"); globalScene.phaseManager.pushNew("EncounterPhase"); - } else { - globalScene.phaseManager.pushNew("TitlePhase"); } this.clearText(); globalScene.phaseManager.getCurrentPhase()?.end(); @@ -4332,8 +4341,6 @@ export class StarterSelectUiHandler extends MessageUiHandler { 19, ); }); - - return true; } tryStart(manualTrigger = false): boolean { diff --git a/src/ui/stats-container.ts b/src/ui/stats-container.ts index 6b89e80b80a..e9af5eed3e3 100644 --- a/src/ui/stats-container.ts +++ b/src/ui/stats-container.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; import { getStatKey, PERMANENT_STATS } from "#enums/stat"; -import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#ui/text"; +import { TextStyle } from "#enums/text-style"; +import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text"; import i18next from "i18next"; import type BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index e832462f5be..ddfcb6ba5d5 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -17,6 +17,7 @@ import { PlayerGender } from "#enums/player-gender"; import { PokemonType } from "#enums/pokemon-type"; import { getStatKey, PERMANENT_STATS, Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon } from "#field/pokemon"; import { heldItemSortFunc } from "#items/item-utility"; @@ -25,7 +26,7 @@ import type { PokemonMove } from "#moves/pokemon-move"; import type { Variant } from "#sprites/variant"; import { getVariantTint } from "#sprites/variant"; import { achvs } from "#system/achv"; -import { addBBCodeTextObject, addTextObject, getBBCodeFrag, TextStyle } from "#ui/text"; +import { addBBCodeTextObject, addTextObject, getBBCodeFrag } from "#ui/text"; import { UiHandler } from "#ui/ui-handler"; import { fixedInt, @@ -35,9 +36,9 @@ import { isNullOrUndefined, padInt, rgbHexToRgba, - toReadableString, } from "#utils/common"; import { getEnumValues } from "#utils/enums"; +import { toTitleCase } from "#utils/strings"; import { argbFromRgba } from "@material/material-color-utilities"; import i18next from "i18next"; @@ -355,18 +356,13 @@ export class SummaryUiHandler extends UiHandler { } catch (err: unknown) { console.error(`Failed to play animation for ${spriteKey}`, err); } - this.pokemonSprite.setPipelineData("teraColor", getTypeRgb(this.pokemon.getTeraType())); - this.pokemonSprite.setPipelineData("isTerastallized", this.pokemon.isTerastallized); - this.pokemonSprite.setPipelineData("ignoreTimeTint", true); - this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); - this.pokemonSprite.setPipelineData( - "shiny", - this.pokemon.summonData.illusion?.basePokemon.shiny ?? this.pokemon.shiny, - ); - this.pokemonSprite.setPipelineData( - "variant", - this.pokemon.summonData.illusion?.basePokemon.variant ?? this.pokemon.variant, - ); + this.pokemonSprite + .setPipelineData("teraColor", getTypeRgb(this.pokemon.getTeraType())) + .setPipelineData("isTerastallized", this.pokemon.isTerastallized) + .setPipelineData("ignoreTimeTint", true) + .setPipelineData("spriteKey", this.pokemon.getSpriteKey()) + .setPipelineData("shiny", this.pokemon.shiny) + .setPipelineData("variant", this.pokemon.variant); ["spriteColors", "fusionSpriteColors"].map(k => { delete this.pokemonSprite.pipelineData[`${k}Base`]; if (this.pokemon?.summonData.speciesForm) { @@ -464,9 +460,7 @@ export class SummaryUiHandler extends UiHandler { this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); this.fusionShinyIcon.setVisible(doubleShiny); if (isFusion) { - this.fusionShinyIcon.setTint( - getVariantTint(this.pokemon.summonData.illusion?.basePokemon.fusionVariant ?? this.pokemon.fusionVariant), - ); + this.fusionShinyIcon.setTint(getVariantTint(this.pokemon.fusionVariant)); } this.pokeball.setFrame(getPokeballAtlasKey(this.pokemon.pokeball)); @@ -811,24 +805,34 @@ export class SummaryUiHandler extends UiHandler { case Page.PROFILE: { const profileContainer = globalScene.add.container(0, -pageBg.height); pageContainer.add(profileContainer); + const otColor = + globalScene.gameData.gender === PlayerGender.FEMALE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE; + const usernameReplacement = + globalScene.gameData.gender === PlayerGender.FEMALE + ? i18next.t("trainerNames:player_f") + : i18next.t("trainerNames:player_m"); // TODO: should add field for original trainer name to Pokemon object, to support gift/traded Pokemon from MEs const trainerText = addBBCodeTextObject( 7, 12, - `${i18next.t("pokemonSummary:ot")}/${getBBCodeFrag(loggedInUser?.username || i18next.t("pokemonSummary:unknown"), globalScene.gameData.gender === PlayerGender.FEMALE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE)}`, + `${i18next.t("pokemonSummary:ot")}/${getBBCodeFrag( + !globalScene.hideUsername + ? loggedInUser?.username || i18next.t("pokemonSummary:unknown") + : usernameReplacement, + otColor, + )}`, TextStyle.SUMMARY_ALT, - ); - trainerText.setOrigin(0, 0); + ).setOrigin(0); profileContainer.add(trainerText); + const idToDisplay = globalScene.hideUsername ? "*****" : globalScene.gameData.trainerId.toString(); const trainerIdText = addTextObject( 141, 12, - `${i18next.t("pokemonSummary:idNo")}${globalScene.gameData.trainerId.toString()}`, + `${i18next.t("pokemonSummary:idNo")}${idToDisplay}`, TextStyle.SUMMARY_ALT, - ); - trainerIdText.setOrigin(0, 0); + ).setOrigin(0); profileContainer.add(trainerIdText); const typeLabel = addTextObject(7, 28, `${i18next.t("pokemonSummary:type")}/`, TextStyle.WINDOW_ALT); @@ -959,8 +963,8 @@ export class SummaryUiHandler extends UiHandler { this.passiveContainer?.descriptionText?.setVisible(false); const closeFragment = getBBCodeFrag("", TextStyle.WINDOW_ALT); - const rawNature = toReadableString(Nature[this.pokemon?.getNature()!]); // TODO: is this bang correct? - const nature = `${getBBCodeFrag(toReadableString(getNatureName(this.pokemon?.getNature()!)), TextStyle.SUMMARY_RED)}${closeFragment}`; // TODO: is this bang correct? + const rawNature = toTitleCase(Nature[this.pokemon?.getNature()!]); // TODO: is this bang correct? + const nature = `${getBBCodeFrag(toTitleCase(getNatureName(this.pokemon?.getNature()!)), TextStyle.SUMMARY_RED)}${closeFragment}`; // TODO: is this bang correct? const memoString = i18next.t("pokemonSummary:memoString", { metFragment: i18next.t( diff --git a/src/ui/text.ts b/src/ui/text.ts index 73ddc235593..f44fd79b6ff 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -1,79 +1,14 @@ import { globalScene } from "#app/global-scene"; import { EggTier } from "#enums/egg-type"; import { RarityTier } from "#enums/reward-tier"; +import { TextStyle } from "#enums/text-style"; import { UiTheme } from "#enums/ui-theme"; import i18next from "#plugins/i18n"; +import type { TextStyleOptions } from "#types/ui"; import type Phaser from "phaser"; import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; import type InputText from "phaser3-rex-plugins/plugins/inputtext"; -export enum TextStyle { - MESSAGE, - WINDOW, - WINDOW_ALT, - WINDOW_BATTLE_COMMAND, - BATTLE_INFO, - PARTY, - PARTY_RED, - PARTY_CANCEL_BUTTON, - INSTRUCTIONS_TEXT, - MOVE_LABEL, - SUMMARY, - SUMMARY_DEX_NUM, - SUMMARY_DEX_NUM_GOLD, - SUMMARY_ALT, - SUMMARY_HEADER, - SUMMARY_RED, - SUMMARY_BLUE, - SUMMARY_PINK, - SUMMARY_GOLD, - SUMMARY_GRAY, - SUMMARY_GREEN, - SUMMARY_STATS, - SUMMARY_STATS_BLUE, - SUMMARY_STATS_PINK, - SUMMARY_STATS_GOLD, - LUCK_VALUE, - STATS_HEXAGON, - GROWTH_RATE_TYPE, - MONEY, // Money default styling (pale yellow) - MONEY_WINDOW, // Money displayed in Windows (needs different colors based on theme) - HEADER_LABEL, - STATS_LABEL, - STATS_VALUE, - SETTINGS_VALUE, - SETTINGS_LABEL, - SETTINGS_LABEL_NAVBAR, - SETTINGS_SELECTED, - SETTINGS_LOCKED, - EGG_LIST, - EGG_SUMMARY_NAME, - EGG_SUMMARY_DEX, - STARTER_VALUE_LIMIT, - TOOLTIP_TITLE, - TOOLTIP_CONTENT, - FILTER_BAR_MAIN, - MOVE_INFO_CONTENT, - MOVE_PP_FULL, - MOVE_PP_HALF_FULL, - MOVE_PP_NEAR_EMPTY, - MOVE_PP_EMPTY, - SMALLER_WINDOW_ALT, - BGM_BAR, - PERFECT_IV, - ME_OPTION_DEFAULT, // Default style for choices in ME - ME_OPTION_SPECIAL, // Style for choices with special requirements in ME - SHADOW_TEXT, // To obscure unavailable options -} - -export interface TextStyleOptions { - scale: number; - styleOptions: Phaser.Types.GameObjects.Text.TextStyle | InputText.IConfig; - shadowColor: string; - shadowXpos: number; - shadowYpos: number; -} - export function addTextObject( x: number, y: number, @@ -87,9 +22,10 @@ export function addTextObject( extraStyleOptions, ); - const ret = globalScene.add.text(x, y, content, styleOptions); - ret.setScale(scale); - ret.setShadow(shadowXpos, shadowYpos, shadowColor); + const ret = globalScene.add + .text(x, y, content, styleOptions) + .setScale(scale) + .setShadow(shadowXpos, shadowYpos, shadowColor); if (!(styleOptions as Phaser.Types.GameObjects.Text.TextStyle).lineSpacing) { ret.setLineSpacing(scale * 30); } @@ -107,8 +43,7 @@ export function setTextStyle( globalScene.uiTheme, extraStyleOptions, ); - obj.setScale(scale); - obj.setShadow(shadowXpos, shadowYpos, shadowColor); + obj.setScale(scale).setShadow(shadowXpos, shadowYpos, shadowColor); if (!(styleOptions as Phaser.Types.GameObjects.Text.TextStyle).lineSpacing) { obj.setLineSpacing(scale * 30); } @@ -133,8 +68,7 @@ export function addBBCodeTextObject( const ret = new BBCodeText(globalScene, x, y, content, styleOptions as BBCodeText.TextStyle); globalScene.add.existing(ret); - ret.setScale(scale); - ret.setShadow(shadowXpos, shadowYpos, shadowColor); + ret.setScale(scale).setShadow(shadowXpos, shadowYpos, shadowColor); if (!(styleOptions as BBCodeText.TextStyle).lineSpacing) { ret.setLineSpacing(scale * 60); } diff --git a/src/ui/title-ui-handler.ts b/src/ui/title-ui-handler.ts index b7c37538a3e..66cb69f6a26 100644 --- a/src/ui/title-ui-handler.ts +++ b/src/ui/title-ui-handler.ts @@ -5,10 +5,11 @@ import { TimedEventDisplay } from "#app/timed-event-manager"; import { getSplashMessages } from "#data/splash-messages"; import { PlayerGender } from "#enums/player-gender"; import type { SpeciesId } from "#enums/species-id"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import { version } from "#package.json"; import { OptionSelectUiHandler } from "#ui/option-select-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { fixedInt, randInt, randItem } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; diff --git a/src/ui/ui-handler.ts b/src/ui/ui-handler.ts index c7b25c90205..7dde6b22dcd 100644 --- a/src/ui/ui-handler.ts +++ b/src/ui/ui-handler.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; import type { Button } from "#enums/buttons"; +import type { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; -import type { TextStyle } from "#ui/text"; import { getTextColor } from "#ui/text"; /** diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 53423c1c281..c3a0ff365ea 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene"; import type { Button } from "#enums/buttons"; import { Device } from "#enums/devices"; import { PlayerGender } from "#enums/player-gender"; +import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import { AchvBar } from "#ui/achv-bar"; import { AchvsUiHandler } from "#ui/achvs-ui-handler"; @@ -51,7 +52,7 @@ import { StarterSelectUiHandler } from "#ui/starter-select-ui-handler"; import { SummaryUiHandler } from "#ui/summary-ui-handler"; import { TargetSelectUiHandler } from "#ui/target-select-ui-handler"; import { TestDialogueUiHandler } from "#ui/test-dialogue-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { TitleUiHandler } from "#ui/title-ui-handler"; import type { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; diff --git a/src/ui/unavailable-modal-ui-handler.ts b/src/ui/unavailable-modal-ui-handler.ts index 420a47664a7..5c3dc513473 100644 --- a/src/ui/unavailable-modal-ui-handler.ts +++ b/src/ui/unavailable-modal-ui-handler.ts @@ -1,9 +1,10 @@ import { updateUserInfo } from "#app/account"; import { globalScene } from "#app/global-scene"; +import { TextStyle } from "#enums/text-style"; import type { UiMode } from "#enums/ui-mode"; import type { ModalConfig } from "#ui/modal-ui-handler"; import { ModalUiHandler } from "#ui/modal-ui-handler"; -import { addTextObject, TextStyle } from "#ui/text"; +import { addTextObject } from "#ui/text"; import { sessionIdKey } from "#utils/common"; import { removeCookie } from "#utils/cookies"; import i18next from "i18next"; diff --git a/src/utils/common.ts b/src/utils/common.ts index 26d522c5912..1730a1ce82b 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,6 +1,5 @@ import { pokerogueApi } from "#api/pokerogue-api"; import { MoneyFormat } from "#enums/money-format"; -import { MoveId } from "#enums/move-id"; import type { Variant } from "#sprites/variant"; import i18next from "i18next"; @@ -10,19 +9,6 @@ export const MissingTextureKey = "__MISSING"; // TODO: Draft tests for these utility functions // TODO: Break up this file -/** - * Convert a `snake_case` string in any capitalization (such as one from an enum reverse mapping) - * into a readable `Title Case` version. - * @param str - The snake case string to be converted. - * @returns The result of converting `str` into title case. - */ -export function toReadableString(str: string): string { - return str - .replace(/_/g, " ") - .split(" ") - .map(s => capitalizeFirstLetter(s.toLowerCase())) - .join(" "); -} export function randomString(length: number, seeded = false) { const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -278,7 +264,7 @@ export function formatMoney(format: MoneyFormat, amount: number) { } export function formatStat(stat: number, forHp = false): string { - return formatLargeNumber(stat, forHp ? 100000 : 1000000); + return formatLargeNumber(stat, forHp ? 100_000 : 1_000_000); } export function getTypedKeys, K extends number = Extract>(obj: T): K[] { @@ -367,31 +353,6 @@ export function fixedInt(value: number): number { return new FixedInt(value) as unknown as number; } -/** - * Formats a string to title case - * @param unformattedText Text to be formatted - * @returns the formatted string - */ -export function formatText(unformattedText: string): string { - const text = unformattedText.split("_"); - for (let i = 0; i < text.length; i++) { - text[i] = text[i].charAt(0).toUpperCase() + text[i].substring(1).toLowerCase(); - } - - return text.join(" "); -} - -export function toCamelCaseString(unformattedText: string): string { - if (!unformattedText) { - return ""; - } - return unformattedText - .split(/[_ ]/) - .filter(f => f) - .map((f, i) => (i ? `${f[0].toUpperCase()}${f.slice(1).toLowerCase()}` : f.toLowerCase())) - .join(""); -} - export function rgbToHsv(r: number, g: number, b: number) { const v = Math.max(r, g, b); const c = v - Math.min(r, g, b); @@ -483,6 +444,7 @@ export function hasAllLocalizedSprites(lang?: string): boolean { case "ja": case "ca": case "ru": + case "tl": return true; default: return false; @@ -518,41 +480,6 @@ export function truncateString(str: string, maxLength = 10) { return str; } -/** - * Convert a space-separated string into a capitalized and underscored string. - * @param input - The string to be converted. - * @returns The converted string with words capitalized and separated by underscores. - */ -export function reverseValueToKeySetting(input: string) { - // Split the input string into an array of words - const words = input.split(" "); - // Capitalize the first letter of each word and convert the rest to lowercase - const capitalizedWords = words.map((word: string) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()); - // Join the capitalized words with underscores and return the result - return capitalizedWords.join("_"); -} - -/** - * Capitalize a string. - * @param str - The string to be capitalized. - * @param sep - The separator between the words of the string. - * @param lowerFirstChar - Whether the first character of the string should be lowercase or not. - * @param returnWithSpaces - Whether the returned string should have spaces between the words or not. - * @returns The capitalized string. - */ -export function capitalizeString(str: string, sep: string, lowerFirstChar = true, returnWithSpaces = false) { - if (str) { - const splitedStr = str.toLowerCase().split(sep); - - for (let i = +lowerFirstChar; i < splitedStr?.length; i++) { - splitedStr[i] = splitedStr[i].charAt(0).toUpperCase() + splitedStr[i].substring(1); - } - - return returnWithSpaces ? splitedStr.join(" ") : splitedStr.join(""); - } - return null; -} - /** * Report whether a given value is nullish (`null`/`undefined`). * @param val - The value whose nullishness is being checked @@ -562,15 +489,6 @@ export function isNullOrUndefined(val: any): val is null | undefined { return val === null || val === undefined; } -/** - * Capitalize the first letter of a string. - * @param str - The string whose first letter is being capitalized - * @return The original string with its first letter capitalized - */ -export function capitalizeFirstLetter(str: string) { - return str.charAt(0).toUpperCase() + str.slice(1); -} - /** * This function is used in the context of a Pokémon battle game to calculate the actual integer damage value from a float result. * Many damage calculation formulas involve various parameters and result in float values. @@ -605,26 +523,6 @@ export function isBetween(num: number, min: number, max: number): boolean { return min <= num && num <= max; } -/** - * Helper method to return the animation filename for a given move - * - * @param move the move for which the animation filename is needed - */ -export function animationFileName(move: MoveId): string { - return MoveId[move].toLowerCase().replace(/_/g, "-"); -} - -/** - * Transforms a camelCase string into a kebab-case string - * @param str The camelCase string - * @returns A kebab-case string - * - * @source {@link https://stackoverflow.com/a/67243723/} - */ -export function camelCaseToKebabCase(str: string): string { - return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (s, o) => (o ? "-" : "") + s.toLowerCase()); -} - /** Get the localized shiny descriptor for the provided variant * @param variant - The variant to get the shiny descriptor for * @returns The localized shiny descriptor diff --git a/src/utils/enums.ts b/src/utils/enums.ts index 98cb4272ee9..25ee864794c 100644 --- a/src/utils/enums.ts +++ b/src/utils/enums.ts @@ -1,5 +1,5 @@ -import type { EnumOrObject, EnumValues, NormalEnum, TSNumericEnum } from "#app/@types/enum-types"; -import type { InferKeys } from "#app/@types/type-helpers"; +import type { EnumOrObject, NormalEnum, TSNumericEnum } from "#types/enum-types"; +import type { InferKeys, ObjectValues } from "#types/type-helpers"; /** * Return the string keys of an Enum object, excluding reverse-mapped numbers. @@ -61,7 +61,7 @@ export function getEnumValues(enumType: TSNumericEnum * If multiple keys map to the same value, the first one (in insertion order) will be retrieved, * but the return type will be the union of ALL their corresponding keys. */ -export function enumValueToKey>( +export function enumValueToKey>( object: NormalEnum, val: V, ): InferKeys { diff --git a/src/utils/pokemon-utils.ts b/src/utils/pokemon-utils.ts index da39cfe11ad..8de0a3bfcf1 100644 --- a/src/utils/pokemon-utils.ts +++ b/src/utils/pokemon-utils.ts @@ -1,6 +1,9 @@ +import { globalScene } from "#app/global-scene"; +import { POKERUS_STARTER_COUNT, speciesStarterCosts } from "#balance/starters"; import { allSpecies } from "#data/data-lists"; -import type { PokemonSpecies } from "#data/pokemon-species"; +import type { PokemonSpecies, PokemonSpeciesForm } from "#data/pokemon-species"; import type { SpeciesId } from "#enums/species-id"; +import { randSeedItem } from "./common"; /** * Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given @@ -19,3 +22,104 @@ export function getPokemonSpecies(species: SpeciesId | SpeciesId[]): PokemonSpec } return allSpecies[species - 1]; } + +/** + * Method to get the daily list of starters with Pokerus. + * @returns A list of starters with Pokerus + */ +export function getPokerusStarters(): PokemonSpecies[] { + const pokerusStarters: PokemonSpecies[] = []; + const date = new Date(); + date.setUTCHours(0, 0, 0, 0); + globalScene.executeWithSeedOffset( + () => { + while (pokerusStarters.length < POKERUS_STARTER_COUNT) { + const randomSpeciesId = Number.parseInt(randSeedItem(Object.keys(speciesStarterCosts)), 10); + const species = getPokemonSpecies(randomSpeciesId); + if (!pokerusStarters.includes(species)) { + pokerusStarters.push(species); + } + } + }, + 0, + date.getTime().toString(), + ); + return pokerusStarters; +} + +export function getFusedSpeciesName(speciesAName: string, speciesBName: string): string { + const fragAPattern = /([a-z]{2}.*?[aeiou(?:y$)\-']+)(.*?)$/i; + const fragBPattern = /([a-z]{2}.*?[aeiou(?:y$)\-'])(.*?)$/i; + + const [speciesAPrefixMatch, speciesBPrefixMatch] = [speciesAName, speciesBName].map(n => /^(?:[^ ]+) /.exec(n)); + const [speciesAPrefix, speciesBPrefix] = [speciesAPrefixMatch, speciesBPrefixMatch].map(m => (m ? m[0] : "")); + + if (speciesAPrefix) { + speciesAName = speciesAName.slice(speciesAPrefix.length); + } + if (speciesBPrefix) { + speciesBName = speciesBName.slice(speciesBPrefix.length); + } + + const [speciesASuffixMatch, speciesBSuffixMatch] = [speciesAName, speciesBName].map(n => / (?:[^ ]+)$/.exec(n)); + const [speciesASuffix, speciesBSuffix] = [speciesASuffixMatch, speciesBSuffixMatch].map(m => (m ? m[0] : "")); + + if (speciesASuffix) { + speciesAName = speciesAName.slice(0, -speciesASuffix.length); + } + if (speciesBSuffix) { + speciesBName = speciesBName.slice(0, -speciesBSuffix.length); + } + + const splitNameA = speciesAName.split(/ /g); + const splitNameB = speciesBName.split(/ /g); + + const fragAMatch = fragAPattern.exec(speciesAName); + const fragBMatch = fragBPattern.exec(speciesBName); + + let fragA: string; + let fragB: string; + + fragA = splitNameA.length === 1 ? (fragAMatch ? fragAMatch[1] : speciesAName) : splitNameA[splitNameA.length - 1]; + + if (splitNameB.length === 1) { + if (fragBMatch) { + const lastCharA = fragA.slice(fragA.length - 1); + const prevCharB = fragBMatch[1].slice(fragBMatch.length - 1); + fragB = (/[-']/.test(prevCharB) ? prevCharB : "") + fragBMatch[2] || prevCharB; + if (lastCharA === fragB[0]) { + if (/[aiu]/.test(lastCharA)) { + fragB = fragB.slice(1); + } else { + const newCharMatch = new RegExp(`[^${lastCharA}]`).exec(fragB); + if (newCharMatch?.index !== undefined && newCharMatch.index > 0) { + fragB = fragB.slice(newCharMatch.index); + } + } + } + } else { + fragB = speciesBName; + } + } else { + fragB = splitNameB[splitNameB.length - 1]; + } + + if (splitNameA.length > 1) { + fragA = `${splitNameA.slice(0, splitNameA.length - 1).join(" ")} ${fragA}`; + } + + fragB = `${fragB.slice(0, 1).toLowerCase()}${fragB.slice(1)}`; + + return `${speciesAPrefix || speciesBPrefix}${fragA}${fragB}${speciesBSuffix || speciesASuffix}`; +} + +export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): PokemonSpeciesForm { + const retSpecies: PokemonSpecies = + species >= 2000 + ? allSpecies.find(s => s.speciesId === species)! // TODO: is the bang correct? + : allSpecies[species - 1]; + if (formIndex < retSpecies.forms?.length) { + return retSpecies.forms[formIndex]; + } + return retSpecies; +} diff --git a/src/utils/strings.ts b/src/utils/strings.ts new file mode 100644 index 00000000000..bf5e5c6473f --- /dev/null +++ b/src/utils/strings.ts @@ -0,0 +1,181 @@ +// TODO: Standardize file and path casing to remove the need for all these different casing methods + +// #region Split string code + +// Regexps involved with splitting words in various case formats. +// Sourced from https://www.npmjs.com/package/change-case (with slight tweaking here and there) + +/** Regex to split at word boundaries.*/ +const SPLIT_LOWER_UPPER_RE = /([\p{Ll}\d])(\p{Lu})/gu; +/** Regex to split around single-letter uppercase words.*/ +const SPLIT_UPPER_UPPER_RE = /(\p{Lu})([\p{Lu}][\p{Ll}])/gu; +/** Regexp involved with stripping non-word delimiters from the result. */ +const DELIM_STRIP_REGEXP = /[-_ ]+/giu; +// The replacement value for splits. +const SPLIT_REPLACE_VALUE = "$1\0$2"; + +/** + * Split any cased string into an array of its constituent words. + * @param string - The string to be split + * @returns The new string, delimited at each instance of one or more spaces, underscores, hyphens + * or lower-to-upper boundaries. + * @remarks + * **DO NOT USE THIS FUNCTION!** + * Exported only to allow for testing. + * @todo Consider tests into [in-source testing](https://vitest.dev/guide/in-source.html) and converting this to unexported + */ +export function splitWords(value: string): string[] { + let result = value.trim(); + result = result.replace(SPLIT_LOWER_UPPER_RE, SPLIT_REPLACE_VALUE).replace(SPLIT_UPPER_UPPER_RE, SPLIT_REPLACE_VALUE); + result = result.replace(DELIM_STRIP_REGEXP, "\0"); + + // Trim the delimiter from around the output string + return trimFromStartAndEnd(result, "\0").split(/\0/g); +} + +/** + * Helper function to remove one or more sequences of characters from either end of a string. + * @param str - The string to replace + * @param charToTrim - The string to remove + * @returns The result of removing all instances of {@linkcode charsToTrim} from either end of {@linkcode str}. + */ +function trimFromStartAndEnd(str: string, charToTrim: string): string { + let start = 0; + let end = str.length; + const blockLength = charToTrim.length; + + while (str.startsWith(charToTrim, start)) { + start += blockLength; + } + if (start - end === blockLength) { + // Occurs if the ENTIRE string is made up of charToTrim (at which point we return nothing) + return ""; + } + while (str.endsWith(charToTrim, end)) { + end -= blockLength; + } + return str.slice(start, end); +} + +// #endregion Split String code + +/** + * Capitalize the first letter of a string. + * @param str - The string whose first letter is to be capitalized + * @return The original string with its first letter capitalized. + * @example + * ```ts + * console.log(capitalizeFirstLetter("consectetur adipiscing elit")); // returns "Consectetur adipiscing elit" + * ``` + */ +export function capitalizeFirstLetter(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +/** + * Helper method to convert a string into `Title Case` (such as one used for console logs). + * @param str - The string being converted + * @returns The result of converting `str` into title case. + * @example + * ```ts + * console.log(toTitleCase("lorem ipsum dolor sit amet")); // returns "Lorem Ipsum Dolor Sit Amet" + * ``` + */ +export function toTitleCase(str: string): string { + return splitWords(str) + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(" "); +} + +/** + * Helper method to convert a string into `camelCase` (such as one used for i18n keys). + * @param str - The string being converted + * @returns The result of converting `str` into camel case. + * @example + * ```ts + * console.log(toCamelCase("BIG_ANGRY_TRAINER")); // returns "bigAngryTrainer" + * ``` + */ +export function toCamelCase(str: string) { + return splitWords(str) + .map((word, index) => + index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(), + ) + .join(""); +} + +/** + * Helper method to convert a string into `PascalCase`. + * @param str - The string being converted + * @returns The result of converting `str` into pascal case. + * @example + * ```ts + * console.log(toPascalCase("hi how was your day")); // returns "HiHowWasYourDay" + * ``` + * @remarks + */ +export function toPascalCase(str: string) { + return splitWords(str) + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(""); +} + +/** + * Helper method to convert a string into `kebab-case` (such as one used for filenames). + * @param str - The string being converted + * @returns The result of converting `str` into kebab case. + * @example + * ```ts + * console.log(toKebabCase("not_kebab-caSe String")); // returns "not-kebab-case-string" + * ``` + */ +export function toKebabCase(str: string): string { + return splitWords(str) + .map(word => word.toLowerCase()) + .join("-"); +} + +/** + * Helper method to convert a string into `snake_case` (such as one used for filenames). + * @param str - The string being converted + * @returns The result of converting `str` into snake case. + * @example + * ```ts + * console.log(toSnakeCase("not-in snake_CaSe")); // returns "not_in_snake_case" + * ``` + */ +export function toSnakeCase(str: string) { + return splitWords(str) + .map(word => word.toLowerCase()) + .join("_"); +} + +/** + * Helper method to convert a string into `UPPER_SNAKE_CASE`. + * @param str - The string being converted + * @returns The result of converting `str` into upper snake case. + * @example + * ```ts + * console.log(toUpperSnakeCase("apples bananas_oranGes-PearS")); // returns "APPLES_BANANAS_ORANGES_PEARS" + * ``` + */ +export function toUpperSnakeCase(str: string) { + return splitWords(str) + .map(word => word.toUpperCase()) + .join("_"); +} + +/** + * Helper method to convert a string into `Pascal_Snake_Case`. + * @param str - The string being converted + * @returns The result of converting `str` into pascal snake case. + * @example + * ```ts + * console.log(toPascalSnakeCase("apples-bananas_oranGes Pears")); // returns "Apples_Bananas_Oranges_Pears" + * ``` + */ +export function toPascalSnakeCase(str: string) { + return splitWords(str) + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join("_"); +} diff --git a/src/vite.env.d.ts b/src/vite.env.d.ts index a2acc658a10..68159908730 100644 --- a/src/vite.env.d.ts +++ b/src/vite.env.d.ts @@ -1,16 +1,16 @@ /// interface ImportMetaEnv { - readonly VITE_PORT?: string; - readonly VITE_BYPASS_LOGIN?: string; - readonly VITE_BYPASS_TUTORIAL?: string; - readonly VITE_API_BASE_URL?: string; - readonly VITE_SERVER_URL?: string; - readonly VITE_DISCORD_CLIENT_ID?: string; - readonly VITE_GOOGLE_CLIENT_ID?: string; - readonly VITE_I18N_DEBUG?: string; + readonly VITE_PORT?: string; + readonly VITE_BYPASS_LOGIN?: string; + readonly VITE_BYPASS_TUTORIAL?: string; + readonly VITE_API_BASE_URL?: string; + readonly VITE_SERVER_URL?: string; + readonly VITE_DISCORD_CLIENT_ID?: string; + readonly VITE_GOOGLE_CLIENT_ID?: string; + readonly VITE_I18N_DEBUG?: string; } interface ImportMeta { - readonly env: ImportMetaEnv + readonly env: ImportMetaEnv; } diff --git a/test/@types/vitest.d.ts b/test/@types/vitest.d.ts new file mode 100644 index 00000000000..7b756c45a57 --- /dev/null +++ b/test/@types/vitest.d.ts @@ -0,0 +1,139 @@ +import type { TerrainType } from "#app/data/terrain"; +import type { AbilityId } from "#enums/ability-id"; +import type { BattlerTagType } from "#enums/battler-tag-type"; +import type { MoveId } from "#enums/move-id"; +import type { PokemonType } from "#enums/pokemon-type"; +import type { BattleStat, EffectiveStat, Stat } from "#enums/stat"; +import type { StatusEffect } from "#enums/status-effect"; +import type { WeatherType } from "#enums/weather-type"; +import type { Pokemon } from "#field/pokemon"; +import type { ToHaveEffectiveStatMatcherOptions } from "#test/test-utils/matchers/to-have-effective-stat"; +import type { expectedStatusType } from "#test/test-utils/matchers/to-have-status-effect"; +import type { toHaveTypesOptions } from "#test/test-utils/matchers/to-have-types"; +import type { TurnMove } from "#types/turn-move"; +import type { AtLeastOne } from "#types/type-helpers"; +import type { expect } from "vitest"; +import type Overrides from "#app/overrides"; +import type { PokemonMove } from "#moves/pokemon-move"; + +declare module "vitest" { + interface Assertion { + /** + * Check whether an array contains EXACTLY the given items (in any order). + * + * Different from {@linkcode expect.arrayContaining} as the latter only checks for subset equality + * (as opposed to full equality). + * + * @param expected - The expected contents of the array, in any order + * @see {@linkcode expect.arrayContaining} + */ + toEqualArrayUnsorted(expected: E[]): void; + + /** + * Check whether a {@linkcode Pokemon}'s current typing includes the given types. + * + * @param expected - The expected types (in any order) + * @param options - The options passed to the matcher + */ + toHaveTypes(expected: [PokemonType, ...PokemonType[]], options?: toHaveTypesOptions): void; + + /** + * Matcher to check the contents of a {@linkcode Pokemon}'s move history. + * + * @param expectedValue - The expected value; can be a {@linkcode MoveId} or a partially filled {@linkcode TurnMove} + * containing the desired properties to check + * @param index - The index of the move history entry to check, in order from most recent to least recent. + * Default `0` (last used move) + * @see {@linkcode Pokemon.getLastXMoves} + */ + toHaveUsedMove(expected: MoveId | AtLeastOne, index?: number): void; + + /** + * Check whether a {@linkcode Pokemon}'s effective stat is as expected + * (checked after all stat value modifications). + * + * @param stat - The {@linkcode EffectiveStat} to check + * @param expectedValue - The expected value of {@linkcode stat} + * @param options - (Optional) The {@linkcode ToHaveEffectiveStatMatcherOptions} + * @remarks + * If you want to check the stat **before** modifiers are applied, use {@linkcode Pokemon.getStat} instead. + */ + toHaveEffectiveStat(stat: EffectiveStat, expectedValue: number, options?: ToHaveEffectiveStatMatcherOptions): void; + + /** + * Check whether a {@linkcode Pokemon} has taken a specific amount of damage. + * @param expectedDamageTaken - The expected amount of damage taken + * @param roundDown - Whether to round down {@linkcode expectedDamageTaken} with {@linkcode toDmgValue}; default `true` + */ + toHaveTakenDamage(expectedDamageTaken: number, roundDown?: boolean): void; + + /** + * Check whether the current {@linkcode WeatherType} is as expected. + * @param expectedWeatherType - The expected {@linkcode WeatherType} + */ + toHaveWeather(expectedWeatherType: WeatherType): void; + + /** + * Check whether the current {@linkcode TerrainType} is as expected. + * @param expectedTerrainType - The expected {@linkcode TerrainType} + */ + toHaveTerrain(expectedTerrainType: TerrainType): void; + + /** + * Check whether a {@linkcode Pokemon} is at full HP. + */ + toHaveFullHp(): void; + + /** + * Check whether a {@linkcode Pokemon} has a specific {@linkcode StatusEffect | non-volatile status effect}. + * @param expectedStatusEffect - The {@linkcode StatusEffect} the Pokemon is expected to have, + * or a partially filled {@linkcode Status} containing the desired properties + */ + toHaveStatusEffect(expectedStatusEffect: expectedStatusType): void; + + /** + * Check whether a {@linkcode Pokemon} has a specific {@linkcode Stat} stage. + * @param stat - The {@linkcode BattleStat} to check + * @param expectedStage - The expected stat stage value of {@linkcode stat} + */ + toHaveStatStage(stat: BattleStat, expectedStage: number): void; + + /** + * Check whether a {@linkcode Pokemon} has a specific {@linkcode BattlerTagType}. + * @param expectedBattlerTagType - The expected {@linkcode BattlerTagType} + */ + toHaveBattlerTag(expectedBattlerTagType: BattlerTagType): void; + + /** + * Check whether a {@linkcode Pokemon} has applied a specific {@linkcode AbilityId}. + * @param expectedAbilityId - The expected {@linkcode AbilityId} + */ + toHaveAbilityApplied(expectedAbilityId: AbilityId): void; + + /** + * Check whether a {@linkcode Pokemon} has a specific amount of {@linkcode Stat.HP | HP}. + * @param expectedHp - The expected amount of {@linkcode Stat.HP | HP} to have + */ + toHaveHp(expectedHp: number): void; + + /** + * Check whether a {@linkcode Pokemon} is currently fainted (as determined by {@linkcode Pokemon.isFainted}). + * @remarks + * When checking whether an enemy wild Pokemon is fainted, one must reference it in a variable _before_ the fainting effect occurs + * as otherwise the Pokemon will be GC'ed and rendered `undefined`. + */ + toHaveFainted(): void; + + /** + * Check whether a {@linkcode Pokemon} has consumed the given amount of PP for one of its moves. + * @param expectedValue - The {@linkcode MoveId} of the {@linkcode PokemonMove} that should have consumed PP + * @param ppUsed - The numerical amount of PP that should have been consumed, + * or `all` to indicate the move should be _out_ of PP + * @remarks + * If the Pokemon's moveset has been set via {@linkcode Overrides.MOVESET_OVERRIDE}/{@linkcode Overrides.OPP_MOVESET_OVERRIDE}, + * does not contain {@linkcode expectedMove} + * or contains the desired move more than once, this will fail the test. + */ + toHaveUsedPP(expectedMove: MoveId, ppUsed: number | "all"): void; + } +} diff --git a/test/abilities/illusion.test.ts b/test/abilities/illusion.test.ts index 9063c8dd5ae..33e2734c637 100644 --- a/test/abilities/illusion.test.ts +++ b/test/abilities/illusion.test.ts @@ -146,8 +146,8 @@ describe("Abilities - Illusion", () => { const zoroark = game.scene.getPlayerPokemon()!; - expect(zoroark.name).equals("Axew"); - expect(zoroark.getNameToRender()).equals("axew nickname"); + expect(zoroark.summonData.illusion?.name).equals("Axew"); + expect(zoroark.getNameToRender(true)).equals("axew nickname"); expect(zoroark.getGender(false, true)).equals(Gender.FEMALE); expect(zoroark.isShiny(true)).equals(true); expect(zoroark.getPokeball(true)).equals(PokeballType.GREAT_BALL); diff --git a/test/abilities/normal-move-type-change.test.ts b/test/abilities/normal-move-type-change.test.ts index e771cbef75d..e538186e58c 100644 --- a/test/abilities/normal-move-type-change.test.ts +++ b/test/abilities/normal-move-type-change.test.ts @@ -50,7 +50,7 @@ describe.each([ .startingLevel(100) .starterSpecies(SpeciesId.MAGIKARP) .ability(ab) - .moveset([MoveId.TACKLE, MoveId.REVELATION_DANCE, MoveId.FURY_SWIPES]) + .moveset([MoveId.TACKLE, MoveId.REVELATION_DANCE, MoveId.FURY_SWIPES, MoveId.CRUSH_GRIP]) .enemySpecies(SpeciesId.DUSCLOPS) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) @@ -77,6 +77,27 @@ describe.each([ expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); }); + // Regression test to ensure proper ordering of effects + it("should still boost variable-power moves", async () => { + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + + const playerPokemon = game.field.getPlayerPokemon(); + const typeSpy = vi.spyOn(playerPokemon, "getMoveType"); + + const enemyPokemon = game.field.getEnemyPokemon(); + const enemySpy = vi.spyOn(enemyPokemon, "getMoveEffectiveness"); + const powerSpy = vi.spyOn(allMoves[MoveId.CRUSH_GRIP], "calculateBattlePower"); + + game.move.select(MoveId.CRUSH_GRIP); + + await game.toEndOfTurn(); + + expect(typeSpy).toHaveLastReturnedWith(ty); + expect(enemySpy).toHaveReturnedWith(1); + expect(powerSpy).toHaveReturnedWith(144); // 120 * 1.2 + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + }); + // Galvanize specifically would like to check for volt absorb's activation if (ab === AbilityId.GALVANIZE) { it("should cause Normal-type attacks to activate Volt Absorb", async () => { diff --git a/test/abilities/normalize.test.ts b/test/abilities/normalize.test.ts index 898cfad0a8a..610e125387d 100644 --- a/test/abilities/normalize.test.ts +++ b/test/abilities/normalize.test.ts @@ -45,6 +45,18 @@ describe("Abilities - Normalize", () => { expect(powerSpy).toHaveLastReturnedWith(toDmgValue(allMoves[MoveId.TACKLE].power * 1.2)); }); + it("should boost variable power moves", async () => { + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + const magikarp = game.field.getPlayerPokemon(); + magikarp.friendship = 255; + + const powerSpy = vi.spyOn(allMoves[MoveId.RETURN], "calculateBattlePower"); + + game.move.use(MoveId.RETURN); + await game.toEndOfTurn(); + expect(powerSpy).toHaveLastReturnedWith(102 * 1.2); + }); + it("should not apply the old type boost item after changing a move's type", async () => { game.override.startingHeldItems([{ entry: HeldItemId.MIRACLE_SEED }]).moveset([MoveId.LEAFAGE]); diff --git a/test/abilities/truant.test.ts b/test/abilities/truant.test.ts new file mode 100644 index 00000000000..0d71cd393b0 --- /dev/null +++ b/test/abilities/truant.test.ts @@ -0,0 +1,72 @@ +import { getPokemonNameWithAffix } from "#app/messages"; +import { AbilityId } from "#enums/ability-id"; +import { MoveId } from "#enums/move-id"; +import { MoveResult } from "#enums/move-result"; +import { SpeciesId } from "#enums/species-id"; +import { GameManager } from "#test/test-utils/game-manager"; +import i18next from "i18next"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Ability - Truant", () => { + 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 + .battleStyle("single") + .criticalHits(false) + .moveset([MoveId.SPLASH, MoveId.TACKLE]) + .ability(AbilityId.TRUANT) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH); + }); + + it("should loaf around and prevent using moves every other turn", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const player = game.field.getPlayerPokemon(); + const enemy = game.field.getEnemyPokemon(); + + // Turn 1: Splash succeeds + game.move.select(MoveId.SPLASH); + await game.toNextTurn(); + + expect(player.getLastXMoves(1)[0]).toEqual( + expect.objectContaining({ move: MoveId.SPLASH, result: MoveResult.SUCCESS }), + ); + + // Turn 2: Truant activates, cancelling tackle and displaying message + game.move.select(MoveId.TACKLE); + await game.toNextTurn(); + + expect(player.getLastXMoves(1)[0]).toEqual(expect.objectContaining({ move: MoveId.NONE, result: MoveResult.FAIL })); + expect(enemy.hp).toBe(enemy.getMaxHp()); + expect(game.textInterceptor.logs).toContain( + i18next.t("battlerTags:truantLapse", { + pokemonNameWithAffix: getPokemonNameWithAffix(player), + }), + ); + + // Turn 3: Truant didn't activate, tackle worked + game.move.select(MoveId.TACKLE); + await game.toNextTurn(); + + expect(player.getLastXMoves(1)[0]).toEqual( + expect.objectContaining({ move: MoveId.TACKLE, result: MoveResult.SUCCESS }), + ); + expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); + }); +}); diff --git a/test/arena/psychic-terrain.test.ts b/test/arena/psychic-terrain.test.ts new file mode 100644 index 00000000000..82232cd8d05 --- /dev/null +++ b/test/arena/psychic-terrain.test.ts @@ -0,0 +1,59 @@ +import { AbilityId } from "#enums/ability-id"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import { StatusEffect } from "#enums/status-effect"; +import { WeatherType } from "#enums/weather-type"; +import { GameManager } from "#test/test-utils/game-manager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Arena - Psychic Terrain", () => { + 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 + .battleStyle("single") + .criticalHits(false) + .enemyLevel(1) + .enemySpecies(SpeciesId.SHUCKLE) + .enemyAbility(AbilityId.STURDY) + .enemyMoveset(MoveId.SPLASH) + .moveset([MoveId.PSYCHIC_TERRAIN, MoveId.RAIN_DANCE, MoveId.DARK_VOID]) + .ability(AbilityId.NO_GUARD); + }); + + it("Dark Void with Prankster is not blocked", async () => { + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + + game.move.select(MoveId.PSYCHIC_TERRAIN); + await game.toNextTurn(); + + game.move.select(MoveId.DARK_VOID); + await game.toEndOfTurn(); + + expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.SLEEP); + }); + + it("Rain Dance with Prankster is not blocked", async () => { + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + + game.move.select(MoveId.PSYCHIC_TERRAIN); + await game.toNextTurn(); + + game.move.select(MoveId.RAIN_DANCE); + await game.toEndOfTurn(); + + expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.RAIN); + }); +}); diff --git a/test/matchers.setup.ts b/test/matchers.setup.ts new file mode 100644 index 00000000000..03b29302916 --- /dev/null +++ b/test/matchers.setup.ts @@ -0,0 +1,39 @@ +import { toEqualArrayUnsorted } from "#test/test-utils/matchers/to-equal-array-unsorted"; +import { toHaveAbilityApplied } from "#test/test-utils/matchers/to-have-ability-applied"; +import { toHaveBattlerTag } from "#test/test-utils/matchers/to-have-battler-tag"; +import { toHaveEffectiveStat } from "#test/test-utils/matchers/to-have-effective-stat"; +import { toHaveFainted } from "#test/test-utils/matchers/to-have-fainted"; +import { toHaveFullHp } from "#test/test-utils/matchers/to-have-full-hp"; +import { toHaveHp } from "#test/test-utils/matchers/to-have-hp"; +import { toHaveStatStage } from "#test/test-utils/matchers/to-have-stat-stage"; +import { toHaveStatusEffect } from "#test/test-utils/matchers/to-have-status-effect"; +import { toHaveTakenDamage } from "#test/test-utils/matchers/to-have-taken-damage"; +import { toHaveTerrain } from "#test/test-utils/matchers/to-have-terrain"; +import { toHaveTypes } from "#test/test-utils/matchers/to-have-types"; +import { toHaveUsedMove } from "#test/test-utils/matchers/to-have-used-move"; +import { toHaveUsedPP } from "#test/test-utils/matchers/to-have-used-pp"; +import { toHaveWeather } from "#test/test-utils/matchers/to-have-weather"; +import { expect } from "vitest"; + +/* + * Setup file for custom matchers. + * Make sure to define the call signatures in `test/@types/vitest.d.ts` too! + */ + +expect.extend({ + toEqualArrayUnsorted, + toHaveTypes, + toHaveUsedMove, + toHaveEffectiveStat, + toHaveTakenDamage, + toHaveWeather, + toHaveTerrain, + toHaveFullHp, + toHaveStatusEffect, + toHaveStatStage, + toHaveBattlerTag, + toHaveAbilityApplied, + toHaveHp, + toHaveFainted, + toHaveUsedPP, +}); diff --git a/test/misc.test.ts b/test/misc.test.ts index 96407b78470..a77ac1f5c91 100644 --- a/test/misc.test.ts +++ b/test/misc.test.ts @@ -1,5 +1,4 @@ import { GameManager } from "#test/test-utils/game-manager"; -import { waitUntil } from "#test/test-utils/game-manager-utils"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; @@ -36,19 +35,6 @@ describe("Test misc", () => { expect(spy).toHaveBeenCalled(); }); - // it.skip("test apifetch mock async", async () => { - // const spy = vi.fn(); - // await apiFetch("https://localhost:8080/account/info").then(response => { - // expect(response.status).toBe(200); - // expect(response.ok).toBe(true); - // return response.json(); - // }).then(data => { - // spy(); // Call the spy function - // expect(data).toEqual({ "username": "greenlamp", "lastSessionSlot": 0 }); - // }); - // expect(spy).toHaveBeenCalled(); - // }); - it("test fetch mock sync", async () => { const response = await fetch("https://localhost:8080/account/info"); const data = await response.json(); @@ -62,19 +48,4 @@ describe("Test misc", () => { const data = await game.scene.cachedFetch("./battle-anims/splishy-splash.json"); expect(data).toBeDefined(); }); - - it("testing wait phase queue", async () => { - const fakeScene = { - phaseQueue: [1, 2, 3], // Initially not empty - }; - setTimeout(() => { - fakeScene.phaseQueue = []; - }, 500); - const spy = vi.fn(); - await waitUntil(() => fakeScene.phaseQueue.length === 0).then(result => { - expect(result).toBe(true); - spy(); // Call the spy function - }); - expect(spy).toHaveBeenCalled(); - }); }); diff --git a/test/moves/assist.test.ts b/test/moves/assist.test.ts index 08112d911b1..52467c2ba98 100644 --- a/test/moves/assist.test.ts +++ b/test/moves/assist.test.ts @@ -86,12 +86,11 @@ describe("Moves - Assist", () => { }); it("should apply secondary effects of a move", async () => { - game.override.moveset([MoveId.ASSIST, MoveId.WOOD_HAMMER, MoveId.WOOD_HAMMER, MoveId.WOOD_HAMMER]); await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.SHUCKLE]); const [feebas, shuckle] = game.scene.getPlayerField(); - game.move.changeMoveset(feebas, [MoveId.ASSIST, MoveId.SKETCH, MoveId.PROTECT, MoveId.DRAGON_TAIL]); - game.move.changeMoveset(shuckle, [MoveId.ASSIST, MoveId.SKETCH, MoveId.PROTECT, MoveId.DRAGON_TAIL]); + game.move.changeMoveset(feebas, [MoveId.ASSIST, MoveId.WOOD_HAMMER]); + game.move.changeMoveset(shuckle, [MoveId.ASSIST, MoveId.WOOD_HAMMER]); game.move.select(MoveId.ASSIST, 0); game.move.select(MoveId.ASSIST, 1); diff --git a/test/moves/delayed-attack.test.ts b/test/moves/delayed-attack.test.ts new file mode 100644 index 00000000000..e8cf2871626 --- /dev/null +++ b/test/moves/delayed-attack.test.ts @@ -0,0 +1,389 @@ +import { getPokemonNameWithAffix } from "#app/messages"; +import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; +import { allMoves } from "#data/data-lists"; +import { AbilityId } from "#enums/ability-id"; +import { BattleType } from "#enums/battle-type"; +import { BattlerIndex } from "#enums/battler-index"; +import { MoveId } from "#enums/move-id"; +import { MoveResult } from "#enums/move-result"; +import { PokemonType } from "#enums/pokemon-type"; +import { PositionalTagType } from "#enums/positional-tag-type"; +import { SpeciesId } from "#enums/species-id"; +import { Stat } from "#enums/stat"; +import { GameManager } from "#test/test-utils/game-manager"; +import i18next from "i18next"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - Delayed Attacks", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(AbilityId.NO_GUARD) + .battleStyle("single") + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.STURDY) + .enemyMoveset(MoveId.SPLASH); + }); + + /** + * Wait until a number of turns have passed. + * @param numTurns - Number of turns to pass. + * @param toEndOfTurn - Whether to advance to the `TurnEndPhase` (`true`) or the `PositionalTagPhase` (`false`); + * default `true` + * @returns A Promise that resolves once the specified number of turns has elapsed + * and the specified phase has been reached. + */ + async function passTurns(numTurns: number, toEndOfTurn = true): Promise { + for (let i = 0; i < numTurns; i++) { + game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER); + if (game.scene.getPlayerField()[1]?.isActive()) { + game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2); + } + await game.move.forceEnemyMove(MoveId.SPLASH); + if (game.scene.getEnemyField()[1]?.isActive()) { + await game.move.forceEnemyMove(MoveId.SPLASH); + } + await game.phaseInterceptor.to("PositionalTagPhase"); + } + if (toEndOfTurn) { + await game.toEndOfTurn(); + } + } + + /** + * Expect that future sight is active with the specified number of attacks. + * @param numAttacks - The number of delayed attacks that should be queued; default `1` + */ + function expectFutureSightActive(numAttacks = 1) { + const delayedAttacks = game.scene.arena.positionalTagManager["tags"].filter( + t => t.tagType === PositionalTagType.DELAYED_ATTACK, + ); + expect(delayedAttacks).toHaveLength(numAttacks); + } + + it.each<{ name: string; move: MoveId }>([ + { name: "Future Sight", move: MoveId.FUTURE_SIGHT }, + { name: "Doom Desire", move: MoveId.DOOM_DESIRE }, + ])("$name should show message and strike 2 turns after use, ignoring player/enemy switches", async ({ move }) => { + game.override.battleType(BattleType.TRAINER); + await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MILOTIC]); + + game.move.use(move); + await game.toNextTurn(); + + expectFutureSightActive(); + + game.doSwitchPokemon(1); + game.forceEnemyToSwitch(); + await game.toNextTurn(); + + await passTurns(1); + + expectFutureSightActive(0); + const enemy = game.field.getEnemyPokemon(); + expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); + expect(game.textInterceptor.logs).toContain( + i18next.t("moveTriggers:tookMoveAttack", { + pokemonName: getPokemonNameWithAffix(enemy), + moveName: allMoves[move].name, + }), + ); + }); + + it("should fail (preserving prior instances) when used against the same target", async () => { + await game.classicMode.startBattle([SpeciesId.BRONZONG]); + + game.move.use(MoveId.FUTURE_SIGHT); + await game.toNextTurn(); + + expectFutureSightActive(); + const bronzong = game.field.getPlayerPokemon(); + expect(bronzong.getLastXMoves()[0].result).toBe(MoveResult.OTHER); + + game.move.use(MoveId.FUTURE_SIGHT); + await game.toNextTurn(); + + expectFutureSightActive(); + expect(bronzong.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should still be delayed when called by other moves", async () => { + await game.classicMode.startBattle([SpeciesId.BRONZONG]); + + game.move.use(MoveId.METRONOME); + game.move.forceMetronomeMove(MoveId.FUTURE_SIGHT); + await game.toNextTurn(); + + expectFutureSightActive(); + const enemy = game.field.getEnemyPokemon(); + expect(enemy.hp).toBe(enemy.getMaxHp()); + + await passTurns(2); + + expectFutureSightActive(0); + expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); + }); + + it("should work when used against different targets in doubles", async () => { + game.override.battleStyle("double"); + await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]); + + const [karp, feebas, enemy1, enemy2] = game.scene.getField(); + + game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2); + await game.toEndOfTurn(); + + expectFutureSightActive(2); + expect(enemy1.hp).toBe(enemy1.getMaxHp()); + expect(enemy2.hp).toBe(enemy2.getMaxHp()); + expect(karp.getLastXMoves()[0].result).toBe(MoveResult.OTHER); + expect(feebas.getLastXMoves()[0].result).toBe(MoveResult.OTHER); + + await passTurns(2); + + expect(enemy1.hp).toBeLessThan(enemy1.getMaxHp()); + expect(enemy2.hp).toBeLessThan(enemy2.getMaxHp()); + }); + + it("should trigger multiple pending attacks in order of creation, even if that order changes later on", async () => { + game.override.battleStyle("double"); + await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]); + + const [alomomola, blissey] = game.scene.getField(); + + const oldOrder = game.field.getSpeedOrder(); + + game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2); + await game.move.forceEnemyMove(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER); + await game.move.forceEnemyMove(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER_2); + // Ensure that the moves are used deterministically in speed order (for speed ties) + await game.setTurnOrder(oldOrder.map(p => p.getBattlerIndex())); + await game.toNextTurn(); + + expectFutureSightActive(4); + + // Lower speed to change turn order + alomomola.setStatStage(Stat.SPD, 6); + blissey.setStatStage(Stat.SPD, -6); + + const newOrder = game.field.getSpeedOrder(); + expect(newOrder).not.toEqual(oldOrder); + + await passTurns(2, false); + + // All attacks have concluded at this point, unshifting new `MoveEffectPhase`s to the queue. + expectFutureSightActive(0); + + const MEPs = game.scene.phaseManager.phaseQueue.filter(p => p.is("MoveEffectPhase")); + expect(MEPs).toHaveLength(4); + expect(MEPs.map(mep => mep.getPokemon())).toEqual(oldOrder); + }); + + it("should vanish silently if it would otherwise hit the user", async () => { + game.override.battleStyle("double"); + await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS, SpeciesId.MILOTIC]); + + const [karp, feebas, milotic] = game.scene.getPlayerParty(); + + game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2); + game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2); + await game.toNextTurn(); + + expectFutureSightActive(1); + + // Milotic / Feebas // Karp + game.doSwitchPokemon(2); + game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2); + await game.toNextTurn(); + + expect(game.scene.getPlayerParty()).toEqual([milotic, feebas, karp]); + + // Milotic / Karp // Feebas + game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER); + game.doSwitchPokemon(2); + + await passTurns(1); + + expect(game.scene.getPlayerParty()).toEqual([milotic, karp, feebas]); + + expect(karp.hp).toBe(karp.getMaxHp()); + expect(feebas.hp).toBe(feebas.getMaxHp()); + expect(game.textInterceptor.logs).not.toContain( + i18next.t("moveTriggers:tookMoveAttack", { + pokemonName: getPokemonNameWithAffix(karp), + moveName: allMoves[MoveId.FUTURE_SIGHT].name, + }), + ); + }); + + it("should redirect normally if target is fainted when move is used", async () => { + game.override.battleStyle("double"); + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + + const [enemy1, enemy2] = game.scene.getEnemyField(); + + game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2); + await game.killPokemon(enemy2); + await game.toNextTurn(); + + expect(enemy2.isFainted()).toBe(true); + expectFutureSightActive(); + + const attack = game.scene.arena.positionalTagManager.tags.find( + t => t.tagType === PositionalTagType.DELAYED_ATTACK, + )!; + expect(attack).toBeDefined(); + expect(attack.targetIndex).toBe(enemy1.getBattlerIndex()); + + await passTurns(2); + + expect(enemy1.hp).toBeLessThan(enemy1.getMaxHp()); + expect(game.textInterceptor.logs).toContain( + i18next.t("moveTriggers:tookMoveAttack", { + pokemonName: getPokemonNameWithAffix(enemy1), + moveName: allMoves[MoveId.FUTURE_SIGHT].name, + }), + ); + }); + + it("should vanish silently if slot is vacant when attack lands", async () => { + game.override.battleStyle("double"); + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + + const [enemy1, enemy2] = game.scene.getEnemyField(); + + game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2); + await game.toNextTurn(); + + expectFutureSightActive(1); + + game.move.use(MoveId.SPLASH); + await game.killPokemon(enemy2); + await game.toNextTurn(); + + game.move.use(MoveId.SPLASH); + await game.toNextTurn(); + + expectFutureSightActive(0); + expect(enemy1.hp).toBe(enemy1.getMaxHp()); + expect(game.textInterceptor.logs).not.toContain( + i18next.t("moveTriggers:tookMoveAttack", { + pokemonName: getPokemonNameWithAffix(enemy1), + moveName: allMoves[MoveId.FUTURE_SIGHT].name, + }), + ); + }); + + it("should consider type changes at moment of execution while ignoring redirection", async () => { + game.override.battleStyle("double"); + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + + // fake left enemy having lightning rod + const [enemy1, enemy2] = game.scene.getEnemyField(); + game.field.mockAbility(enemy1, AbilityId.LIGHTNING_ROD); + + game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2); + await game.toNextTurn(); + + expectFutureSightActive(1); + + game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER); + await game.toNextTurn(); + + game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER); + await game.move.forceEnemyMove(MoveId.ELECTRIFY, BattlerIndex.PLAYER); + await game.phaseInterceptor.to("PositionalTagPhase"); + await game.phaseInterceptor.to("MoveEffectPhase", false); + + // Wait until all normal attacks have triggered, then check pending MEP + const karp = game.field.getPlayerPokemon(); + const typeMock = vi.spyOn(karp, "getMoveType"); + + await game.toEndOfTurn(); + + expect(enemy1.hp).toBe(enemy1.getMaxHp()); + expect(enemy2.hp).toBeLessThan(enemy2.getMaxHp()); + expect(game.textInterceptor.logs).toContain( + i18next.t("moveTriggers:tookMoveAttack", { + pokemonName: getPokemonNameWithAffix(enemy2), + moveName: allMoves[MoveId.FUTURE_SIGHT].name, + }), + ); + expect(typeMock).toHaveLastReturnedWith(PokemonType.ELECTRIC); + }); + + // TODO: this is not implemented + it.todo("should not apply Shell Bell recovery, even if user is on field"); + + // TODO: Enable once code is added to MEP to do this + it.todo("should not apply the user's abilities when dealing damage if the user is inactive", async () => { + game.override.ability(AbilityId.NORMALIZE).enemySpecies(SpeciesId.LUNALA); + await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MILOTIC]); + + game.move.use(MoveId.DOOM_DESIRE); + await game.toNextTurn(); + + expectFutureSightActive(); + + await passTurns(1); + + game.doSwitchPokemon(1); + const typeMock = vi.spyOn(game.field.getPlayerPokemon(), "getMoveType"); + const powerMock = vi.spyOn(allMoves[MoveId.DOOM_DESIRE], "calculateBattlePower"); + + await game.toNextTurn(); + + // Player Normalize was not applied due to being off field + const enemy = game.field.getEnemyPokemon(); + expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); + expect(game.textInterceptor.logs).toContain( + i18next.t("moveTriggers:tookMoveAttack", { + pokemonName: getPokemonNameWithAffix(enemy), + moveName: allMoves[MoveId.DOOM_DESIRE].name, + }), + ); + expect(typeMock).toHaveLastReturnedWith(PokemonType.STEEL); + expect(powerMock).toHaveLastReturnedWith(150); + }); + + it.todo("should not apply the user's held items when dealing damage if the user is inactive", async () => { + game.override.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 99, type: PokemonType.PSYCHIC }]); + await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MILOTIC]); + + game.move.use(MoveId.FUTURE_SIGHT); + await game.toNextTurn(); + + expectFutureSightActive(); + + await passTurns(1); + + game.doSwitchPokemon(1); + + const powerMock = vi.spyOn(allMoves[MoveId.FUTURE_SIGHT], "calculateBattlePower"); + const typeBoostSpy = vi.spyOn(AttackTypeBoosterModifier.prototype, "apply"); + + await game.toNextTurn(); + + expect(powerMock).toHaveLastReturnedWith(120); + expect(typeBoostSpy).not.toHaveBeenCalled(); + }); + + // TODO: Implement and move to a power spot's test file + it.todo("Should activate ally's power spot when switched in during single battles"); +}); diff --git a/test/moves/future-sight.test.ts b/test/moves/future-sight.test.ts deleted file mode 100644 index 53e93412570..00000000000 --- a/test/moves/future-sight.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { AbilityId } from "#enums/ability-id"; -import { MoveId } from "#enums/move-id"; -import { SpeciesId } from "#enums/species-id"; -import { GameManager } from "#test/test-utils/game-manager"; -import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; - -describe("Moves - Future Sight", () => { - 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 - .startingLevel(50) - .moveset([MoveId.FUTURE_SIGHT, MoveId.SPLASH]) - .battleStyle("single") - .enemySpecies(SpeciesId.MAGIKARP) - .enemyAbility(AbilityId.STURDY) - .enemyMoveset(MoveId.SPLASH); - }); - - it("hits 2 turns after use, ignores user switch out", async () => { - await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MILOTIC]); - - game.move.select(MoveId.FUTURE_SIGHT); - await game.toNextTurn(); - game.doSwitchPokemon(1); - await game.toNextTurn(); - game.move.select(MoveId.SPLASH); - await game.toNextTurn(); - - expect(game.scene.getEnemyPokemon()!.isFullHp()).toBe(false); - }); -}); diff --git a/test/moves/grudge.test.ts b/test/moves/grudge.test.ts index 6f5df077d9f..d9e2f4f8320 100644 --- a/test/moves/grudge.test.ts +++ b/test/moves/grudge.test.ts @@ -2,6 +2,7 @@ import { AbilityId } from "#enums/ability-id"; import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; +import { WeatherType } from "#enums/weather-type"; import { GameManager } from "#test/test-utils/game-manager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -23,68 +24,64 @@ describe("Moves - Grudge", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .moveset([MoveId.EMBER, MoveId.SPLASH]) .ability(AbilityId.BALL_FETCH) .battleStyle("single") .criticalHits(false) - .enemySpecies(SpeciesId.SHEDINJA) - .enemyAbility(AbilityId.WONDER_GUARD) - .enemyMoveset([MoveId.GRUDGE, MoveId.SPLASH]); + .enemySpecies(SpeciesId.RATTATA) + .startingLevel(100) + .enemyAbility(AbilityId.NO_GUARD); }); - it("should reduce the PP of the Pokemon's move to 0 when the user has fainted", async () => { + it("should reduce the PP of an attack that faints the user to 0", async () => { await game.classicMode.startBattle([SpeciesId.FEEBAS]); - const playerPokemon = game.scene.getPlayerPokemon(); - game.move.select(MoveId.EMBER); - await game.move.selectEnemyMove(MoveId.GRUDGE); + const feebas = game.field.getPlayerPokemon(); + const ratatta = game.field.getEnemyPokemon(); + + game.move.use(MoveId.GUILLOTINE); + await game.move.forceEnemyMove(MoveId.GRUDGE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to("BerryPhase"); + await game.phaseInterceptor.to("FaintPhase"); - const playerMove = playerPokemon?.getMoveset().find(m => m.moveId === MoveId.EMBER); - - expect(playerMove?.getPpRatio()).toBe(0); + // Ratatta should have fainted and consumed all of Guillotine's PP + expect(ratatta).toHaveFainted(); + expect(feebas).toHaveUsedPP(MoveId.GUILLOTINE, "all"); }); it("should remain in effect until the user's next move", async () => { await game.classicMode.startBattle([SpeciesId.FEEBAS]); - const playerPokemon = game.scene.getPlayerPokemon(); - game.move.select(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.GRUDGE); + const feebas = game.field.getPlayerPokemon(); + const ratatta = game.field.getEnemyPokemon(); + + game.move.use(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.GRUDGE); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); - game.move.select(MoveId.EMBER); - await game.move.selectEnemyMove(MoveId.SPLASH); + game.move.use(MoveId.GUILLOTINE); + await game.move.forceEnemyMove(MoveId.SPLASH); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("BerryPhase"); + await game.toEndOfTurn(); - const playerMove = playerPokemon?.getMoveset().find(m => m.moveId === MoveId.EMBER); - - expect(playerMove?.getPpRatio()).toBe(0); + expect(ratatta).toHaveFainted(); + expect(feebas).toHaveUsedPP(MoveId.GUILLOTINE, "all"); }); - it("should not reduce the opponent's PP if the user dies to weather/indirect damage", async () => { + it("should not reduce PP if the user dies to weather/indirect damage", async () => { // Opponent will be reduced to 1 HP by False Swipe, then faint to Sandstorm - game.override - .moveset([MoveId.FALSE_SWIPE]) - .startingLevel(100) - .ability(AbilityId.SAND_STREAM) - .enemySpecies(SpeciesId.RATTATA); - await game.classicMode.startBattle([SpeciesId.GEODUDE]); + game.override.weather(WeatherType.SANDSTORM); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); - const enemyPokemon = game.scene.getEnemyPokemon(); - const playerPokemon = game.scene.getPlayerPokemon(); + const feebas = game.field.getPlayerPokemon(); + const ratatta = game.field.getEnemyPokemon(); - game.move.select(MoveId.FALSE_SWIPE); - await game.move.selectEnemyMove(MoveId.GRUDGE); + game.move.use(MoveId.FALSE_SWIPE); + await game.move.forceEnemyMove(MoveId.GRUDGE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to("BerryPhase"); + await game.toEndOfTurn(); - expect(enemyPokemon?.isFainted()).toBe(true); - - const playerMove = playerPokemon?.getMoveset().find(m => m.moveId === MoveId.FALSE_SWIPE); - expect(playerMove?.getPpRatio()).toBeGreaterThan(0); + expect(ratatta).toHaveFainted(); + expect(feebas).toHaveUsedPP(MoveId.FALSE_SWIPE, 1); }); }); diff --git a/test/moves/heal-block.test.ts b/test/moves/heal-block.test.ts index 0948581a34a..ad950fc0fba 100644 --- a/test/moves/heal-block.test.ts +++ b/test/moves/heal-block.test.ts @@ -1,10 +1,9 @@ import { AbilityId } from "#enums/ability-id"; -import { ArenaTagSide } from "#enums/arena-tag-side"; -import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; +import { PositionalTagType } from "#enums/positional-tag-type"; import { SpeciesId } from "#enums/species-id"; import { WeatherType } from "#enums/weather-type"; import { GameManager } from "#test/test-utils/game-manager"; @@ -69,22 +68,25 @@ describe("Moves - Heal Block", () => { expect(enemy.isFullHp()).toBe(false); }); - it("should stop delayed heals, such as from Wish", async () => { + it("should prevent Wish from restoring HP", async () => { await game.classicMode.startBattle([SpeciesId.CHARIZARD]); - const player = game.scene.getPlayerPokemon()!; + const player = game.field.getPlayerPokemon()!; - player.damageAndUpdate(player.getMaxHp() - 1); + player.hp = 1; - game.move.select(MoveId.WISH); - await game.phaseInterceptor.to("TurnEndPhase"); + game.move.use(MoveId.WISH); + await game.toNextTurn(); - expect(game.scene.arena.getTagOnSide(ArenaTagType.WISH, ArenaTagSide.PLAYER)).toBeDefined(); - while (game.scene.arena.getTagOnSide(ArenaTagType.WISH, ArenaTagSide.PLAYER)) { - game.move.select(MoveId.SPLASH); - await game.phaseInterceptor.to("TurnEndPhase"); - } + expect(game.scene.arena.positionalTagManager.tags.filter(t => t.tagType === PositionalTagType.WISH)) // + .toHaveLength(1); + game.move.use(MoveId.SPLASH); + await game.toNextTurn(); + + // wish triggered, but did NOT heal the player + expect(game.scene.arena.positionalTagManager.tags.filter(t => t.tagType === PositionalTagType.WISH)) // + .toHaveLength(0); expect(player.hp).toBe(1); }); diff --git a/test/moves/order-up.test.ts b/test/moves/order-up.test.ts index 2e77d4b2fa7..2da7cc5daf8 100644 --- a/test/moves/order-up.test.ts +++ b/test/moves/order-up.test.ts @@ -65,23 +65,4 @@ describe("Moves - Order Up", () => { affectedStats.forEach(st => expect(dondozo.getStatStage(st)).toBe(st === stat ? 3 : 2)); }, ); - - it("should be boosted by Sheer Force while still applying a stat boost", async () => { - game.override.passiveAbility(AbilityId.SHEER_FORCE).starterForms({ [SpeciesId.TATSUGIRI]: 0 }); - - await game.classicMode.startBattle([SpeciesId.TATSUGIRI, SpeciesId.DONDOZO]); - - const [tatsugiri, dondozo] = game.scene.getPlayerField(); - - expect(game.scene.triggerPokemonBattleAnim).toHaveBeenLastCalledWith(tatsugiri, PokemonAnimType.COMMANDER_APPLY); - expect(dondozo.getTag(BattlerTagType.COMMANDED)).toBeDefined(); - - game.move.select(MoveId.ORDER_UP, 1, BattlerIndex.ENEMY); - expect(game.scene.currentBattle.turnCommands[0]?.skip).toBeTruthy(); - - await game.phaseInterceptor.to("BerryPhase", false); - - expect(dondozo.waveData.abilitiesApplied.has(AbilityId.SHEER_FORCE)).toBeTruthy(); - expect(dondozo.getStatStage(Stat.ATK)).toBe(3); - }); }); diff --git a/test/moves/spite.test.ts b/test/moves/spite.test.ts new file mode 100644 index 00000000000..56c1be76198 --- /dev/null +++ b/test/moves/spite.test.ts @@ -0,0 +1,126 @@ +import { AbilityId } from "#enums/ability-id"; +import { BattlerIndex } from "#enums/battler-index"; +import { MoveId } from "#enums/move-id"; +import { MoveResult } from "#enums/move-result"; +import { MoveUseMode } from "#enums/move-use-mode"; +import { SpeciesId } from "#enums/species-id"; +import { GameManager } from "#test/test-utils/game-manager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Spite", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(AbilityId.BALL_FETCH) + .battleStyle("single") + .criticalHits(false) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .startingLevel(100) + .enemyLevel(100); + }); + + it("should reduce the PP of the target's last used move by 4", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const karp = game.field.getEnemyPokemon(); + game.move.changeMoveset(karp, [MoveId.SPLASH, MoveId.TACKLE]); + + game.move.use(MoveId.SPITE); + await game.move.selectEnemyMove(MoveId.TACKLE); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.toNextTurn(); + + expect(karp).toHaveUsedPP(MoveId.TACKLE, 1); + + game.move.use(MoveId.SPITE); + await game.move.selectEnemyMove(MoveId.SPLASH); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.toEndOfTurn(); + + expect(karp).toHaveUsedPP(MoveId.TACKLE, 4 + 1); + }); + + it("should fail if the target has not used a move", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const karp = game.field.getEnemyPokemon(); + game.move.changeMoveset(karp, [MoveId.SPLASH, MoveId.TACKLE]); + + game.move.use(MoveId.SPITE); + await game.move.selectEnemyMove(MoveId.TACKLE); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.toEndOfTurn(); + + const feebas = game.field.getPlayerPokemon(); + expect(feebas).toHaveUsedMove({ move: MoveId.SPITE, result: MoveResult.FAIL }); + }); + + it("should fail if the target's last used move is out of PP", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const karp = game.field.getEnemyPokemon(); + game.move.changeMoveset(karp, [MoveId.TACKLE]); + karp.moveset[0].ppUsed = 0; + + game.move.use(MoveId.SPITE); + await game.move.selectEnemyMove(MoveId.TACKLE); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.toEndOfTurn(); + + const feebas = game.field.getPlayerPokemon(); + expect(feebas).toHaveUsedMove({ move: MoveId.SPITE, result: MoveResult.FAIL }); + }); + + it("should fail if the target's last used move is not in their moveset", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const karp = game.field.getEnemyPokemon(); + game.move.changeMoveset(karp, [MoveId.TACKLE]); + // Fake magikarp having used Splash the turn prior + karp.pushMoveHistory({ move: MoveId.SPLASH, targets: [BattlerIndex.ENEMY], useMode: MoveUseMode.NORMAL }); + + game.move.use(MoveId.SPITE); + await game.move.selectEnemyMove(MoveId.TACKLE); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.toEndOfTurn(); + + const feebas = game.field.getPlayerPokemon(); + expect(feebas).toHaveUsedMove({ move: MoveId.SPITE, result: MoveResult.FAIL }); + }); + + it("should ignore virtual and Dancer-induced moves", async () => { + game.override.battleStyle("double").enemyAbility(AbilityId.DANCER); + game.move.forceMetronomeMove(MoveId.SPLASH); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const [karp1, karp2] = game.scene.getEnemyField(); + game.move.changeMoveset(karp1, [MoveId.SPLASH, MoveId.METRONOME, MoveId.SWORDS_DANCE]); + game.move.changeMoveset(karp2, [MoveId.SWORDS_DANCE, MoveId.TACKLE]); + + game.move.use(MoveId.SPITE); + await game.move.selectEnemyMove(MoveId.METRONOME); + await game.move.selectEnemyMove(MoveId.SWORDS_DANCE); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER]); + await game.toEndOfTurn(); + + // Spite ignored virtual splash and swords dance, instead only docking from metronome + expect(karp1).toHaveUsedPP(MoveId.SPLASH, 0); + expect(karp1).toHaveUsedPP(MoveId.SWORDS_DANCE, 0); + expect(karp1).toHaveUsedPP(MoveId.METRONOME, 5); + }); +}); diff --git a/test/moves/whirlwind.test.ts b/test/moves/whirlwind.test.ts index 90bbf722224..2aadb76b019 100644 --- a/test/moves/whirlwind.test.ts +++ b/test/moves/whirlwind.test.ts @@ -206,7 +206,7 @@ describe("Moves - Whirlwind", () => { await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.TOTODILE]); // expect the enemy to have at least 4 pokemon, necessary for this check to even work - expect(game.scene.getEnemyParty().length, "enemy must have exactly 4 pokemon").toBe(4); + expect(game.scene.getEnemyParty().length, "enemy must have exactly 4 pokemon").toBeGreaterThanOrEqual(4); const user = game.scene.getPlayerPokemon()!; diff --git a/test/moves/wish.test.ts b/test/moves/wish.test.ts new file mode 100644 index 00000000000..147c598106b --- /dev/null +++ b/test/moves/wish.test.ts @@ -0,0 +1,183 @@ +import { getPokemonNameWithAffix } from "#app/messages"; +import { AbilityId } from "#enums/ability-id"; +import { BattlerIndex } from "#enums/battler-index"; +import { MoveId } from "#enums/move-id"; +import { MoveResult } from "#enums/move-result"; +import { PositionalTagType } from "#enums/positional-tag-type"; +import { SpeciesId } from "#enums/species-id"; +import { Stat } from "#enums/stat"; +import { GameManager } from "#test/test-utils/game-manager"; +import { toDmgValue } from "#utils/common"; +import i18next from "i18next"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Move - Wish", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(AbilityId.BALL_FETCH) + .battleStyle("single") + .criticalHits(false) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH) + .startingLevel(100) + .enemyLevel(100); + }); + + /** + * Expect that wish is active with the specified number of attacks. + * @param numAttacks - The number of wish instances that should be queued; default `1` + */ + function expectWishActive(numAttacks = 1) { + const wishes = game.scene.arena.positionalTagManager["tags"].filter(t => t.tagType === PositionalTagType.WISH); + expect(wishes).toHaveLength(numAttacks); + } + + it("should heal the Pokemon in the current slot for 50% of the user's maximum HP", async () => { + await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]); + + const [alomomola, blissey] = game.scene.getPlayerParty(); + alomomola.hp = 1; + blissey.hp = 1; + + game.move.use(MoveId.WISH); + await game.toNextTurn(); + + expectWishActive(); + + game.doSwitchPokemon(1); + await game.toEndOfTurn(); + + expectWishActive(0); + expect(game.textInterceptor.logs).toContain( + i18next.t("arenaTag:wishTagOnAdd", { + pokemonNameWithAffix: getPokemonNameWithAffix(alomomola), + }), + ); + expect(alomomola.hp).toBe(1); + expect(blissey.hp).toBe(toDmgValue(alomomola.getMaxHp() / 2) + 1); + }); + + it("should work if the user has full HP, but not if it already has an active Wish", async () => { + await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]); + + const alomomola = game.field.getPlayerPokemon(); + alomomola.hp = 1; + + game.move.use(MoveId.WISH); + await game.toNextTurn(); + + expectWishActive(); + + game.move.use(MoveId.WISH); + await game.toEndOfTurn(); + + expect(alomomola.hp).toBe(toDmgValue(alomomola.getMaxHp() / 2) + 1); + expect(alomomola.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should function independently of Future Sight", async () => { + await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]); + + const [alomomola, blissey] = game.scene.getPlayerParty(); + alomomola.hp = 1; + blissey.hp = 1; + + game.move.use(MoveId.WISH); + await game.move.forceEnemyMove(MoveId.FUTURE_SIGHT); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.toNextTurn(); + + expectWishActive(1); + }); + + it("should work in double battles and trigger in order of creation", async () => { + game.override.battleStyle("double"); + await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]); + + const [alomomola, blissey, karp1, karp2] = game.scene.getField(); + alomomola.hp = 1; + blissey.hp = 1; + + vi.spyOn(karp1, "getNameToRender").mockReturnValue("Karp 1"); + vi.spyOn(karp2, "getNameToRender").mockReturnValue("Karp 2"); + + const oldOrder = game.field.getSpeedOrder(); + + game.move.use(MoveId.WISH, BattlerIndex.PLAYER); + game.move.use(MoveId.WISH, BattlerIndex.PLAYER_2); + await game.move.forceEnemyMove(MoveId.WISH); + await game.move.forceEnemyMove(MoveId.WISH); + // Ensure that the wishes are used deterministically in speed order (for speed ties) + await game.setTurnOrder(oldOrder.map(p => p.getBattlerIndex())); + await game.toNextTurn(); + + expectWishActive(4); + + // Lower speed to change turn order + alomomola.setStatStage(Stat.SPD, 6); + blissey.setStatStage(Stat.SPD, -6); + + const newOrder = game.field.getSpeedOrder(); + expect(newOrder).not.toEqual(oldOrder); + + game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER); + game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("PositionalTagPhase"); + + // all wishes have activated and added healing phases + expectWishActive(0); + + const healPhases = game.scene.phaseManager.phaseQueue.filter(p => p.is("PokemonHealPhase")); + expect(healPhases).toHaveLength(4); + expect.soft(healPhases.map(php => php.getPokemon())).toEqual(oldOrder); + + await game.toEndOfTurn(); + + expect(alomomola.hp).toBe(toDmgValue(alomomola.getMaxHp() / 2) + 1); + expect(blissey.hp).toBe(toDmgValue(blissey.getMaxHp() / 2) + 1); + }); + + it("should vanish and not play message if slot is empty", async () => { + game.override.battleStyle("double"); + await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]); + + const [alomomola, blissey] = game.scene.getPlayerParty(); + alomomola.hp = 1; + blissey.hp = 1; + + game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER); + game.move.use(MoveId.WISH, BattlerIndex.PLAYER_2); + await game.toNextTurn(); + + expectWishActive(); + + game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER); + game.move.use(MoveId.MEMENTO, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2); + await game.toEndOfTurn(); + + // Wish went away without doing anything + expectWishActive(0); + expect(game.textInterceptor.logs).not.toContain( + i18next.t("arenaTag:wishTagOnAdd", { + pokemonNameWithAffix: getPokemonNameWithAffix(blissey), + }), + ); + expect(alomomola.hp).toBe(1); + }); +}); diff --git a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts index a12508a04a0..26fb0a71946 100644 --- a/test/mystery-encounter/encounters/berries-abound-encounter.test.ts +++ b/test/mystery-encounter/encounters/berries-abound-encounter.test.ts @@ -172,7 +172,7 @@ describe("Berries Abound - Mystery Encounter", () => { }); it("should start battle if fastest pokemon is slower than boss below wave 50", async () => { - game.override.startingWave(41); + game.override.startingWave(42); const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText"); await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty); diff --git a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index 2a653f903b3..589d9a7b3ad 100644 --- a/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -253,7 +253,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { }); it("should start battle against the Bug-Type Superfan with wave 70 party template", async () => { - game.override.startingWave(61); + game.override.startingWave(63); await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); @@ -268,7 +268,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { }); it("should start battle against the Bug-Type Superfan with wave 100 party template", async () => { - game.override.startingWave(81); + game.override.startingWave(83); await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); @@ -284,7 +284,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { }); it("should start battle against the Bug-Type Superfan with wave 120 party template", async () => { - game.override.startingWave(111); + game.override.startingWave(113); await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); @@ -302,7 +302,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { }); it("should start battle against the Bug-Type Superfan with wave 140 party template", async () => { - game.override.startingWave(131); + game.override.startingWave(133); await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); @@ -320,7 +320,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { }); it("should start battle against the Bug-Type Superfan with wave 160 party template", async () => { - game.override.startingWave(151); + game.override.startingWave(153); await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); @@ -338,7 +338,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { }); it("should start battle against the Bug-Type Superfan with wave 180 party template", async () => { - game.override.startingWave(171); + game.override.startingWave(173); await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); diff --git a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index a58bb187ad2..81a1f9fc3ec 100644 --- a/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -91,7 +91,7 @@ describe("Global Trade System - Mystery Encounter", () => { describe("Option 1 - Check Trade Offers", () => { it("should have the correct properties", () => { const option = GlobalTradeSystemEncounter.options[0]; - expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); + expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT); expect(option.dialogue).toBeDefined(); expect(option.dialogue).toStrictEqual({ buttonLabel: `${namespace}:option.1.label`, @@ -152,7 +152,7 @@ describe("Global Trade System - Mystery Encounter", () => { describe("Option 2 - Wonder Trade", () => { it("should have the correct properties", () => { const option = GlobalTradeSystemEncounter.options[1]; - expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); + expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT); expect(option.dialogue).toBeDefined(); expect(option.dialogue).toStrictEqual({ buttonLabel: `${namespace}:option.2.label`, diff --git a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index 3e9c8e2e5ad..1b1990c2f93 100644 --- a/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -79,14 +79,6 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { expect(TeleportingHijinksEncounter.options.length).toBe(3); }); - it("should run in waves that are X1", async () => { - game.override.startingWave(11).mysteryEncounterTier(MysteryEncounterTier.COMMON); - - await game.runToMysteryEncounter(); - - expect(scene.currentBattle?.mysteryEncounter?.encounterType).toBe(MysteryEncounterType.TELEPORTING_HIJINKS); - }); - it("should run in waves that are X2", async () => { game.override.startingWave(32).mysteryEncounterTier(MysteryEncounterTier.COMMON); @@ -103,8 +95,16 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { expect(scene.currentBattle?.mysteryEncounter?.encounterType).toBe(MysteryEncounterType.TELEPORTING_HIJINKS); }); - it("should NOT run in waves that are not X1, X2, or X3", async () => { - game.override.startingWave(54); + it("should run in waves that are X4", async () => { + game.override.startingWave(54).mysteryEncounterTier(MysteryEncounterTier.COMMON); + + await game.runToMysteryEncounter(); + + expect(scene.currentBattle?.mysteryEncounter?.encounterType).toBe(MysteryEncounterType.TELEPORTING_HIJINKS); + }); + + it("should NOT run in waves that are not X2, X3, or X4", async () => { + game.override.startingWave(67); await game.runToMysteryEncounter(); diff --git a/test/mystery-encounter/mystery-encounter.test.ts b/test/mystery-encounter/mystery-encounter.test.ts index 71bc9aec008..ec27f7c6a48 100644 --- a/test/mystery-encounter/mystery-encounter.test.ts +++ b/test/mystery-encounter/mystery-encounter.test.ts @@ -24,7 +24,7 @@ describe("Mystery Encounters", () => { beforeEach(() => { game = new GameManager(phaserGame); scene = game.scene; - game.override.startingWave(11).mysteryEncounterChance(100); + game.override.startingWave(12).mysteryEncounterChance(100); }); it("Spawns a mystery encounter", async () => { @@ -37,12 +37,20 @@ describe("Mystery Encounters", () => { expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(MysteryEncounterPhase.name); }); + it("Encounters should not run on X1 waves", async () => { + game.override.startingWave(11); + + await game.runToMysteryEncounter(); + + expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); + }); + it("Encounters should not run below wave 10", async () => { game.override.startingWave(9); await game.runToMysteryEncounter(); - expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.MYSTERIOUS_CHALLENGERS); + expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); }); it("Encounters should not run above wave 180", async () => { diff --git a/test/phases/capture-phase.test.ts b/test/phases/capture-phase.test.ts new file mode 100644 index 00000000000..45a915ebb55 --- /dev/null +++ b/test/phases/capture-phase.test.ts @@ -0,0 +1,37 @@ +import { AbilityId } from "#enums/ability-id"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import { GameManager } from "#test/test-utils/game-manager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it } from "vitest"; + +describe("Capture Phase", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(AbilityId.BALL_FETCH) + .battleStyle("single") + .criticalHits(false) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH) + .startingLevel(100) + .enemyLevel(100); + }); + + // TODO: write test and enable once the phase's logic has been refactored + it.todo("should reset the captured Pokemon's temporary data"); +}); diff --git a/test/phases/check-interlude-phase.test.ts b/test/phases/check-interlude-phase.test.ts new file mode 100644 index 00000000000..d5413d1db35 --- /dev/null +++ b/test/phases/check-interlude-phase.test.ts @@ -0,0 +1,63 @@ +import { AbilityId } from "#enums/ability-id"; +import { BerryType } from "#enums/berry-type"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import { WeatherType } from "#enums/weather-type"; +import { GameManager } from "#test/test-utils/game-manager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Check Biome End Phase", () => { + 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(SpeciesId.MAGIKARP) + .enemyMoveset(MoveId.SPLASH) + .enemyAbility(AbilityId.BALL_FETCH) + .ability(AbilityId.BALL_FETCH) + .startingLevel(100) + .battleStyle("single"); + }); + + it("should not trigger end of turn effects when defeating the final pokemon of a biome in classic", async () => { + game.override + .startingWave(10) + .weather(WeatherType.SANDSTORM) + .startingHeldItems([{ name: "BERRY", type: BerryType.SITRUS }]); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const player = game.field.getPlayerPokemon(); + + player.hp = 1; + + game.move.use(MoveId.EXTREME_SPEED); + await game.toEndOfTurn(); + + expect(player.hp).toBe(1); + }); + + it("should not prevent end of turn effects when transitioning waves within a biome", async () => { + game.override.weather(WeatherType.SANDSTORM); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const player = game.field.getPlayerPokemon(); + + game.move.use(MoveId.EXTREME_SPEED); + await game.toEndOfTurn(); + + expect(player.hp).toBeLessThan(player.getMaxHp()); + }); +}); diff --git a/test/phases/mystery-encounter-phase.test.ts b/test/phases/mystery-encounter-phase.test.ts index ad71a4151ac..2b6105c7034 100644 --- a/test/phases/mystery-encounter-phase.test.ts +++ b/test/phases/mystery-encounter-phase.test.ts @@ -27,7 +27,7 @@ describe("Mystery Encounter Phases", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.startingWave(11).mysteryEncounterChance(100).seed("test"); // Seed guarantees wild encounter to be replaced by ME + game.override.startingWave(12).mysteryEncounterChance(100).seed("test"); // Seed guarantees wild encounter to be replaced by ME }); describe("MysteryEncounterPhase", () => { diff --git a/test/setting-menu/helpers/in-game-manip.ts b/test/setting-menu/helpers/in-game-manip.ts index acc119b2cc2..2f4350bab5c 100644 --- a/test/setting-menu/helpers/in-game-manip.ts +++ b/test/setting-menu/helpers/in-game-manip.ts @@ -1,5 +1,6 @@ import { getIconForLatestInput, getSettingNameWithKeycode } from "#inputs/config-handler"; import { SettingKeyboard } from "#system/settings-keyboard"; +import { toPascalSnakeCase } from "#utils/strings"; import { expect } from "vitest"; export class InGameManip { @@ -56,22 +57,11 @@ export class InGameManip { return this; } - normalizeSettingNameString(input) { - // Convert the input string to lower case - const lowerCasedInput = input.toLowerCase(); - - // Replace underscores with spaces, capitalize the first letter of each word, and join them back with underscores - const words = lowerCasedInput.split("_").map(word => word.charAt(0).toUpperCase() + word.slice(1)); - const result = words.join("_"); - - return result; - } - weShouldTriggerTheButton(settingName) { if (!settingName.includes("Button_")) { settingName = "Button_" + settingName; } - this.settingName = SettingKeyboard[this.normalizeSettingNameString(settingName)]; + this.settingName = SettingKeyboard[toPascalSnakeCase(settingName)]; expect(getSettingNameWithKeycode(this.config, this.keycode)).toEqual(this.settingName); return this; } diff --git a/test/setting-menu/helpers/menu-manip.ts b/test/setting-menu/helpers/menu-manip.ts index 29e096608f1..276fef2f973 100644 --- a/test/setting-menu/helpers/menu-manip.ts +++ b/test/setting-menu/helpers/menu-manip.ts @@ -29,6 +29,7 @@ export class MenuManip { this.specialCaseIcon = null; } + // TODO: Review this convertNameToButtonString(input) { // Check if the input starts with "Alt_Button" if (input.startsWith("Alt_Button")) { diff --git a/test/test-utils/game-manager-utils.ts b/test/test-utils/game-manager-utils.ts index db758cfe64d..89e352cdbdc 100644 --- a/test/test-utils/game-manager-utils.ts +++ b/test/test-utils/game-manager-utils.ts @@ -3,7 +3,6 @@ import type { BattleScene } from "#app/battle-scene"; import { getGameMode } from "#app/game-mode"; import { getDailyRunStarters } from "#data/daily-run"; import { Gender } from "#data/gender"; -import { getPokemonSpeciesForm } from "#data/pokemon-species"; import { BattleType } from "#enums/battle-type"; import { GameModes } from "#enums/game-modes"; import type { MoveId } from "#enums/move-id"; @@ -11,7 +10,7 @@ import type { SpeciesId } from "#enums/species-id"; import { PlayerPokemon } from "#field/pokemon"; import type { StarterMoveset } from "#system/game-data"; import type { Starter } from "#ui/starter-select-ui-handler"; -import { getPokemonSpecies } from "#utils/pokemon-utils"; +import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; /** Function to convert Blob to string */ export function blobToString(blob) { @@ -87,17 +86,6 @@ function getTestRunStarters(seed: string, species?: SpeciesId[]): Starter[] { return starters; } -export function waitUntil(truth): Promise { - return new Promise(resolve => { - const interval = setInterval(() => { - if (truth()) { - clearInterval(interval); - resolve(true); - } - }, 1000); - }); -} - /** * Useful for populating party, wave index, etc. without having to spin up and run through an entire EncounterPhase */ diff --git a/test/test-utils/game-manager.ts b/test/test-utils/game-manager.ts index 01e11d1f69f..17a93d759d5 100644 --- a/test/test-utils/game-manager.ts +++ b/test/test-utils/game-manager.ts @@ -30,7 +30,7 @@ import { TurnEndPhase } from "#phases/turn-end-phase"; import { TurnInitPhase } from "#phases/turn-init-phase"; import { TurnStartPhase } from "#phases/turn-start-phase"; import { ErrorInterceptor } from "#test/test-utils/error-interceptor"; -import { generateStarter, waitUntil } from "#test/test-utils/game-manager-utils"; +import { generateStarter } from "#test/test-utils/game-manager-utils"; import { GameWrapper } from "#test/test-utils/game-wrapper"; import { ChallengeModeHelper } from "#test/test-utils/helpers/challenge-mode-helper"; import { ClassicModeHelper } from "#test/test-utils/helpers/classic-mode-helper"; @@ -84,33 +84,22 @@ export class GameManager { constructor(phaserGame: Phaser.Game, bypassLogin = true) { localStorage.clear(); ErrorInterceptor.getInstance().clear(); - BattleScene.prototype.randBattleSeedInt = (range, min = 0) => min + range - 1; // This simulates a max roll + // Simulate max rolls on RNG functions + // TODO: Create helpers for disabling/enabling battle RNG + BattleScene.prototype.randBattleSeedInt = (range, min = 0) => min + range - 1; this.gameWrapper = new GameWrapper(phaserGame, bypassLogin); - let firstTimeScene = false; + // TODO: Figure out a way to optimize and re-use the same game manager for each test + // Re-use an existing `globalScene` if present, or else create a new scene from scratch. if (globalScene) { this.scene = globalScene; + this.phaseInterceptor = new PhaseInterceptor(this.scene); + this.resetScene(); } else { this.scene = new BattleScene(); + this.phaseInterceptor = new PhaseInterceptor(this.scene); this.gameWrapper.setScene(this.scene); - firstTimeScene = true; - } - - this.phaseInterceptor = new PhaseInterceptor(this.scene); - - if (!firstTimeScene) { - this.scene.reset(false, true); - (this.scene.ui.handlers[UiMode.STARTER_SELECT] as StarterSelectUiHandler).clearStarterPreferences(); - this.scene.phaseManager.clearAllPhases(); - - // Must be run after phase interceptor has been initialized. - - this.scene.phaseManager.pushNew("LoginPhase"); - this.scene.phaseManager.pushNew("TitlePhase"); - this.scene.phaseManager.shiftPhase(); - - this.gameWrapper.scene = this.scene; } this.textInterceptor = new TextInterceptor(this.scene); @@ -123,12 +112,31 @@ export class GameManager { this.reload = new ReloadHelper(this); this.modifiers = new ModifierHelper(this); this.field = new FieldHelper(this); - this.override.sanitizeOverrides(); + this.initDefaultOverrides(); + + // TODO: remove `any` assertion + global.fetch = vi.fn(MockFetch) as any; + } + + /** Reset a prior `BattleScene` instance to the proper initial state. */ + private resetScene(): void { + this.scene.reset(false, true); + (this.scene.ui.handlers[UiMode.STARTER_SELECT] as StarterSelectUiHandler).clearStarterPreferences(); + + this.gameWrapper.scene = this.scene; + + this.scene.phaseManager.toTitleScreen(true); + this.scene.phaseManager.shiftPhase(); + } + + /** + * Initialize various default overrides for starting tests, typically to alleviate randomness. + */ + // TODO: This should not be here + private initDefaultOverrides(): void { // Disables Mystery Encounters on all tests (can be overridden at test level) this.override.mysteryEncounterChance(0); - - global.fetch = vi.fn(MockFetch) as any; } /** @@ -144,15 +152,13 @@ export class GameManager { * @param mode - The mode to wait for. * @returns A promise that resolves when the mode is set. */ - waitMode(mode: UiMode): Promise { - return new Promise(async resolve => { - await waitUntil(() => this.scene.ui?.getMode() === mode); - return resolve(); - }); + // TODO: This is unused + async waitMode(mode: UiMode): Promise { + await vi.waitUntil(() => this.scene.ui?.getMode() === mode); } /** - * Ends the current phase. + * End the currently running phase immediately. */ endPhase() { this.scene.phaseManager.getCurrentPhase()?.end(); @@ -286,11 +292,14 @@ export class GameManager { .getPokemon() .getMoveset() [movePosition].getMove(); - if (!move.isMultiTarget()) { - handler.setCursor(targetIndex !== undefined ? targetIndex : BattlerIndex.ENEMY); - } - if (move.isMultiTarget() && targetIndex !== undefined) { - expect.fail(`targetIndex was passed to selectMove() but move ("${move.name}") is not targetted`); + + // Multi target attacks do not select a target + if (move.isMultiTarget()) { + if (targetIndex !== undefined) { + expect.fail(`targetIndex was passed to selectMove() but move ("${move.name}") is not targeted`); + } + } else { + handler.setCursor(targetIndex ?? BattlerIndex.ENEMY); } handler.processInput(Button.ACTION); }, diff --git a/test/test-utils/helpers/field-helper.ts b/test/test-utils/helpers/field-helper.ts index 35ca853d049..2d8fd8ee701 100644 --- a/test/test-utils/helpers/field-helper.ts +++ b/test/test-utils/helpers/field-helper.ts @@ -5,7 +5,6 @@ import type { globalScene } from "#app/global-scene"; import type { Ability } from "#abilities/ability"; import { allAbilities } from "#data/data-lists"; import type { AbilityId } from "#enums/ability-id"; -import type { BattlerIndex } from "#enums/battler-index"; import type { PokemonType } from "#enums/pokemon-type"; import { Stat } from "#enums/stat"; import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; @@ -45,18 +44,21 @@ export class FieldHelper extends GameManagerHelper { } /** - * @returns The {@linkcode BattlerIndex | indexes} of Pokemon on the field in order of decreasing Speed. + * Helper function to return all on-field {@linkcode Pokemon} in speed order (fastest first). + * @returns An array containing all {@linkcode Pokemon} on the field in order of descending Speed. * Speed ties are returned in increasing order of index. * * @remarks * This does not account for Trick Room as it does not modify the _speed_ of Pokemon on the field, * only their turn order. */ - public getSpeedOrder(): BattlerIndex[] { + public getSpeedOrder(): Pokemon[] { return this.game.scene .getField(true) - .sort((pA, pB) => pB.getEffectiveStat(Stat.SPD) - pA.getEffectiveStat(Stat.SPD)) - .map(p => p.getBattlerIndex()); + .sort( + (pA, pB) => + pB.getEffectiveStat(Stat.SPD) - pA.getEffectiveStat(Stat.SPD) || pA.getBattlerIndex() - pB.getBattlerIndex(), + ); } /** diff --git a/test/test-utils/helpers/move-helper.ts b/test/test-utils/helpers/move-helper.ts index fd6a123a5bb..6a01e4110da 100644 --- a/test/test-utils/helpers/move-helper.ts +++ b/test/test-utils/helpers/move-helper.ts @@ -12,7 +12,8 @@ import type { CommandPhase } from "#phases/command-phase"; import type { EnemyCommandPhase } from "#phases/enemy-command-phase"; import { MoveEffectPhase } from "#phases/move-effect-phase"; import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper"; -import { coerceArray, toReadableString } from "#utils/common"; +import { coerceArray } from "#utils/common"; +import { toTitleCase } from "#utils/strings"; import type { MockInstance } from "vitest"; import { expect, vi } from "vitest"; @@ -66,12 +67,12 @@ export class MoveHelper extends GameManagerHelper { const movePosition = this.getMovePosition(pkmIndex, move); if (movePosition === -1) { expect.fail( - `MoveHelper.select called with move '${toReadableString(MoveId[move])}' not in moveset!` + - `\nBattler Index: ${toReadableString(BattlerIndex[pkmIndex])}` + + `MoveHelper.select called with move '${toTitleCase(MoveId[move])}' not in moveset!` + + `\nBattler Index: ${toTitleCase(BattlerIndex[pkmIndex])}` + `\nMoveset: [${this.game.scene .getPlayerParty() [pkmIndex].getMoveset() - .map(pm => toReadableString(MoveId[pm.moveId])) + .map(pm => toTitleCase(MoveId[pm.moveId])) .join(", ")}]`, ); } @@ -110,12 +111,12 @@ export class MoveHelper extends GameManagerHelper { const movePosition = this.getMovePosition(pkmIndex, move); if (movePosition === -1) { expect.fail( - `MoveHelper.selectWithTera called with move '${toReadableString(MoveId[move])}' not in moveset!` + - `\nBattler Index: ${toReadableString(BattlerIndex[pkmIndex])}` + + `MoveHelper.selectWithTera called with move '${toTitleCase(MoveId[move])}' not in moveset!` + + `\nBattler Index: ${toTitleCase(BattlerIndex[pkmIndex])}` + `\nMoveset: [${this.game.scene .getPlayerParty() [pkmIndex].getMoveset() - .map(pm => toReadableString(MoveId[pm.moveId])) + .map(pm => toTitleCase(MoveId[pm.moveId])) .join(", ")}]`, ); } @@ -142,7 +143,7 @@ export class MoveHelper extends GameManagerHelper { } } - /** Helper function to get the index of the selected move in the selected part member's moveset. */ + /** Helper function to get the index of the selected move in the selected party member's moveset. */ private getMovePosition(pokemonIndex: BattlerIndex.PLAYER | BattlerIndex.PLAYER_2, move: MoveId): number { const playerPokemon = this.game.scene.getPlayerField()[pokemonIndex]; const moveset = playerPokemon.getMoveset(); @@ -152,17 +153,18 @@ export class MoveHelper extends GameManagerHelper { } /** - * Modifies a player pokemon's moveset to contain only the selected move and then + * Modifies a player pokemon's moveset to contain only the selected move, and then * selects it to be used during the next {@linkcode CommandPhase}. * - * Warning: Will disable the player moveset override if it is enabled! + * **Warning**: Will disable the player moveset override if it is enabled, as well as any mid-battle moveset changes! * - * Note: If you need to check for changes in the player's moveset as part of the test, it may be - * best to use {@linkcode changeMoveset} and {@linkcode select} instead. - * @param moveId - the move to use - * @param pkmIndex - The {@linkcode BattlerIndex} of the player Pokemon using the move. Relevant for double battles only and defaults to {@linkcode BattlerIndex.PLAYER} if not specified. - * @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves; should be omitted for multi-target moves. - * @param useTera - If `true`, the Pokemon will attempt to Terastallize even without a Tera Orb; default `false`. + * @param moveId - The {@linkcode MoveId} to use + * @param pkmIndex - The {@linkcode BattlerIndex} of the player Pokemon using the move. Relevant for double battles only and defaults to {@linkcode BattlerIndex.PLAYER} if not specified + * @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves; should be omitted for multi-target moves + * @param useTera - If `true`, the Pokemon will attempt to Terastallize even without a Tera Orb; default `false` + * @remarks + * If you need to check for changes in the player's moveset as part of the test, it may be + * better to use {@linkcode changeMoveset} and {@linkcode select} instead. */ public use( moveId: MoveId, @@ -175,8 +177,11 @@ export class MoveHelper extends GameManagerHelper { console.warn("Warning: `MoveHelper.use` overwriting player pokemon moveset and disabling moveset override!"); } + // Clear out both the normal and temporary movesets before setting the move. const pokemon = this.game.scene.getPlayerField()[pkmIndex]; - pokemon.moveset = [new PokemonMove(moveId)]; + pokemon.moveset.splice(0); + pokemon.summonData.moveset?.splice(0); + pokemon.setMove(0, moveId); if (useTera) { this.selectWithTera(moveId, pkmIndex, targetIndex); @@ -209,15 +214,30 @@ export class MoveHelper extends GameManagerHelper { /** * Changes a pokemon's moveset to the given move(s). - * Used when the normal moveset override can't be used (such as when it's necessary to check or update properties of the moveset). + * + * Useful when normal moveset overrides can't be used (such as when it's necessary to check or update properties of the moveset). + * + * **Note**: Will disable the moveset override matching the pokemon's party. * @param pokemon - The {@linkcode Pokemon} being modified * @param moveset - The {@linkcode MoveId} (single or array) to change the Pokemon's moveset to. */ public changeMoveset(pokemon: Pokemon, moveset: MoveId | MoveId[]): void { + if (pokemon.isPlayer()) { + if (coerceArray(Overrides.MOVESET_OVERRIDE).length > 0) { + vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([]); + console.warn("Player moveset override disabled due to use of `game.move.changeMoveset`!"); + } + } else { + if (coerceArray(Overrides.OPP_MOVESET_OVERRIDE).length > 0) { + vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([]); + console.warn("Enemy moveset override disabled due to use of `game.move.changeMoveset`!"); + } + } moveset = coerceArray(moveset); + expect(moveset.length, "Cannot assign more than 4 moves to a moveset!").toBeLessThanOrEqual(4); pokemon.moveset = []; - moveset.forEach(move => { - pokemon.moveset.push(new PokemonMove(move)); + moveset.forEach((move, i) => { + pokemon.setMove(i, move); }); const movesetStr = moveset.map(moveId => MoveId[moveId]).join(", "); console.log(`Pokemon ${pokemon.species.name}'s moveset manually set to ${movesetStr} (=[${moveset.join(", ")}])!`); @@ -309,10 +329,16 @@ export class MoveHelper extends GameManagerHelper { } /** - * Force the move used by Metronome to be a specific move. - * @param move - The move to force metronome to use - * @param once - If `true`, uses {@linkcode MockInstance#mockReturnValueOnce} when mocking, else uses {@linkcode MockInstance#mockReturnValue}. + * Force the next move(s) used by Metronome to be a specific move. \ + * Triggers during the next upcoming {@linkcode MoveEffectPhase} that Metronome is used. + * @param move - The move to force Metronome to call + * @param once - If `true`, mocks the return value exactly once; default `false` * @returns The spy that for Metronome that was mocked (Usually unneeded). + * @example + * ```ts + * game.move.use(MoveId.METRONOME); + * game.move.forceMetronomeMove(MoveId.FUTURE_SIGHT); // Can be in any order + * ``` */ public forceMetronomeMove(move: MoveId, once = false): MockInstance { const spy = vi.spyOn(allMoves[MoveId.METRONOME].getAttrs("RandomMoveAttr")[0], "getMoveOverride"); diff --git a/test/test-utils/helpers/overrides-helper.ts b/test/test-utils/helpers/overrides-helper.ts index 0d2d39a7b49..ec4519753fc 100644 --- a/test/test-utils/helpers/overrides-helper.ts +++ b/test/test-utils/helpers/overrides-helper.ts @@ -5,7 +5,7 @@ import type { NewArenaEvent } from "#events/battle-scene"; import type { PokeballCounts } from "#app/battle-scene"; import type { BattleStyle, RandomTrainerOverride } from "#app/overrides"; -import Overrides, { defaultOverrides } from "#app/overrides"; +import Overrides from "#app/overrides"; import { AbilityId } from "#enums/ability-id"; import type { BattleType } from "#enums/battle-type"; import { BiomeId } from "#enums/biome-id"; @@ -23,7 +23,7 @@ import type { TrainerItemConfiguration } from "#items/trainer-item-data-types"; import type { Variant } from "#sprites/variant"; import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper"; import { coerceArray, shiftCharCodes } from "#utils/common"; -import { expect, vi } from "vitest"; +import { vi } from "vitest"; /** * Helper to handle overrides in tests @@ -693,14 +693,4 @@ export class OverridesHelper extends GameManagerHelper { private log(...params: any[]) { console.log("Overrides:", ...params); } - - public sanitizeOverrides(): void { - for (const key of Object.keys(defaultOverrides)) { - if (Overrides[key] !== defaultOverrides[key]) { - vi.spyOn(Overrides, key as any, "get").mockReturnValue(defaultOverrides[key]); - } - } - expect(Overrides).toEqual(defaultOverrides); - this.log("Sanitizing all overrides!"); - } } diff --git a/test/test-utils/matchers/to-equal-array-unsorted.ts b/test/test-utils/matchers/to-equal-array-unsorted.ts new file mode 100644 index 00000000000..846ea9e7779 --- /dev/null +++ b/test/test-utils/matchers/to-equal-array-unsorted.ts @@ -0,0 +1,47 @@ +import { getOnelineDiffStr } from "#test/test-utils/string-utils"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +/** + * Matcher that checks if an array contains exactly the given items, disregarding order. + * @param received - The received value. Should be an array of elements + * @param expected - The array to check equality with + * @returns Whether the matcher passed + */ +export function toEqualArrayUnsorted( + this: MatcherState, + received: unknown, + expected: unknown[], +): SyncExpectationResult { + if (!Array.isArray(received)) { + return { + pass: false, + message: () => `Expected an array, but got ${this.utils.stringify(received)}!`, + }; + } + + if (received.length !== expected.length) { + return { + pass: false, + message: () => `Expected to receive array of length ${received.length}, but got ${expected.length} instead!`, + actual: received, + expected, + }; + } + + const actualSorted = received.slice().sort(); + const expectedSorted = expected.slice().sort(); + const pass = this.equals(actualSorted, expectedSorted, [...this.customTesters, this.utils.iterableEquality]); + + const actualStr = getOnelineDiffStr.call(this, actualSorted); + const expectedStr = getOnelineDiffStr.call(this, expectedSorted); + + return { + pass, + message: () => + pass + ? `Expected ${actualStr} to NOT exactly equal ${expectedStr} without order, but it did!` + : `Expected ${actualStr} to exactly equal ${expectedStr} without order, but it didn't!`, + expected: expectedSorted, + actual: actualSorted, + }; +} diff --git a/test/test-utils/matchers/to-have-ability-applied.ts b/test/test-utils/matchers/to-have-ability-applied.ts new file mode 100644 index 00000000000..a3921e6371c --- /dev/null +++ b/test/test-utils/matchers/to-have-ability-applied.ts @@ -0,0 +1,43 @@ +/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ +import type { Pokemon } from "#field/pokemon"; +/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ + +import { getPokemonNameWithAffix } from "#app/messages"; +import { AbilityId } from "#enums/ability-id"; +import { getEnumStr } from "#test/test-utils/string-utils"; +import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +/** + * Matcher that checks if a {@linkcode Pokemon} has applied a specific {@linkcode AbilityId}. + * @param received - The object to check. Should be a {@linkcode Pokemon} + * @param expectedAbility - The {@linkcode AbilityId} to check for + * @returns Whether the matcher passed + */ +export function toHaveAbilityApplied( + this: MatcherState, + received: unknown, + expectedAbilityId: AbilityId, +): SyncExpectationResult { + if (!isPokemonInstance(received)) { + return { + pass: false, + message: () => `Expected to recieve a Pokemon, but got ${receivedStr(received)}!`, + }; + } + + const pass = received.waveData.abilitiesApplied.has(expectedAbilityId); + + const pkmName = getPokemonNameWithAffix(received); + const expectedAbilityStr = getEnumStr(AbilityId, expectedAbilityId); + + return { + pass, + message: () => + pass + ? `Expected ${pkmName} to NOT have applied ${expectedAbilityStr}, but it did!` + : `Expected ${pkmName} to have applied ${expectedAbilityStr}, but it didn't!`, + expected: expectedAbilityId, + actual: received.waveData.abilitiesApplied, + }; +} diff --git a/test/test-utils/matchers/to-have-battler-tag.ts b/test/test-utils/matchers/to-have-battler-tag.ts new file mode 100644 index 00000000000..af405d7da39 --- /dev/null +++ b/test/test-utils/matchers/to-have-battler-tag.ts @@ -0,0 +1,43 @@ +/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ +import type { Pokemon } from "#field/pokemon"; +/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ + +import { getPokemonNameWithAffix } from "#app/messages"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { getEnumStr } from "#test/test-utils/string-utils"; +import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +/** + * Matcher that checks if a {@linkcode Pokemon} has a specific {@linkcode BattlerTagType}. + * @param received - The object to check. Should be a {@linkcode Pokemon} + * @param expectedBattlerTagType - The {@linkcode BattlerTagType} to check for + * @returns Whether the matcher passed + */ +export function toHaveBattlerTag( + this: MatcherState, + received: unknown, + expectedBattlerTagType: BattlerTagType, +): SyncExpectationResult { + if (!isPokemonInstance(received)) { + return { + pass: this.isNot, + message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, + }; + } + + const pass = !!received.getTag(expectedBattlerTagType); + const pkmName = getPokemonNameWithAffix(received); + // "BattlerTagType.SEEDED (=1)" + const expectedTagStr = getEnumStr(BattlerTagType, expectedBattlerTagType, { prefix: "BattlerTagType." }); + + return { + pass, + message: () => + pass + ? `Expected ${pkmName} to NOT have ${expectedTagStr}, but it did!` + : `Expected ${pkmName} to have ${expectedTagStr}, but it didn't!`, + expected: expectedBattlerTagType, + actual: received.summonData.tags.map(t => t.tagType), + }; +} diff --git a/test/test-utils/matchers/to-have-effective-stat.ts b/test/test-utils/matchers/to-have-effective-stat.ts new file mode 100644 index 00000000000..bc10a646c02 --- /dev/null +++ b/test/test-utils/matchers/to-have-effective-stat.ts @@ -0,0 +1,66 @@ +import { getPokemonNameWithAffix } from "#app/messages"; +import type { EffectiveStat } from "#enums/stat"; +import type { Pokemon } from "#field/pokemon"; +import type { Move } from "#moves/move"; +import { getStatName } from "#test/test-utils/string-utils"; +import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +export interface ToHaveEffectiveStatMatcherOptions { + /** + * The target {@linkcode Pokemon} + * @see {@linkcode Pokemon.getEffectiveStat} + */ + enemy?: Pokemon; + /** + * The {@linkcode Move} being used + * @see {@linkcode Pokemon.getEffectiveStat} + */ + move?: Move; + /** + * Whether a critical hit occurred or not + * @see {@linkcode Pokemon.getEffectiveStat} + * @defaultValue `false` + */ + isCritical?: boolean; +} + +/** + * Matcher that checks if a {@linkcode Pokemon}'s effective stat equals a certain value. + * @param received - The object to check. Should be a {@linkcode Pokemon} + * @param stat - The {@linkcode EffectiveStat} to check + * @param expectedValue - The expected value of the {@linkcode stat} + * @param options - The {@linkcode ToHaveEffectiveStatMatcherOptions} + * @returns Whether the matcher passed + */ +export function toHaveEffectiveStat( + this: MatcherState, + received: unknown, + stat: EffectiveStat, + expectedValue: number, + { enemy, move, isCritical = false }: ToHaveEffectiveStatMatcherOptions = {}, +): SyncExpectationResult { + if (!isPokemonInstance(received)) { + return { + pass: false, + message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, + }; + } + + // TODO: Change once getEffectiveStat is refactored to take an object literal + const actualValue = received.getEffectiveStat(stat, enemy, move, undefined, undefined, undefined, isCritical); + const pass = actualValue === expectedValue; + + const pkmName = getPokemonNameWithAffix(received); + const statName = getStatName(stat); + + return { + pass, + message: () => + pass + ? `Expected ${pkmName} to NOT have ${expectedValue} ${statName}, but it did!` + : `Expected ${pkmName} to have ${expectedValue} ${statName}, but got ${actualValue} instead!`, + expected: expectedValue, + actual: actualValue, + }; +} diff --git a/test/test-utils/matchers/to-have-fainted.ts b/test/test-utils/matchers/to-have-fainted.ts new file mode 100644 index 00000000000..73ca96a31b5 --- /dev/null +++ b/test/test-utils/matchers/to-have-fainted.ts @@ -0,0 +1,35 @@ +import { getPokemonNameWithAffix } from "#app/messages"; +// biome-ignore lint/correctness/noUnusedImports: TSDoc +import type { Pokemon } from "#field/pokemon"; +import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +/** + * Matcher that checks if a {@linkcode Pokemon} has fainted. + * @param received - The object to check. Should be a {@linkcode Pokemon} + * @returns Whether the matcher passed + */ +export function toHaveFainted(this: MatcherState, received: unknown): SyncExpectationResult { + if (!isPokemonInstance(received)) { + return { + pass: false, + message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, + }; + } + + const pass = received.isFainted(); + + const hp = received.hp; + const maxHp = received.getMaxHp(); + const pkmName = getPokemonNameWithAffix(received); + + return { + pass, + message: () => + pass + ? `Expected ${pkmName} to NOT have fainted, but it did!` + : `Expected ${pkmName} to have fainted, but it didn't! (${hp}/${maxHp} HP)`, + expected: 0, + actual: hp, + }; +} diff --git a/test/test-utils/matchers/to-have-full-hp.ts b/test/test-utils/matchers/to-have-full-hp.ts new file mode 100644 index 00000000000..3d7c8f9458d --- /dev/null +++ b/test/test-utils/matchers/to-have-full-hp.ts @@ -0,0 +1,35 @@ +import { getPokemonNameWithAffix } from "#app/messages"; +// biome-ignore lint/correctness/noUnusedImports: TSDoc +import type { Pokemon } from "#field/pokemon"; +import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +/** + * Matcher that checks if a {@linkcode Pokemon} is at full hp. + * @param received - The object to check. Should be a {@linkcode Pokemon}. + * @returns Whether the matcher passed + */ +export function toHaveFullHp(this: MatcherState, received: unknown): SyncExpectationResult { + if (!isPokemonInstance(received)) { + return { + pass: false, + message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, + }; + } + + const pass = received.isFullHp(); + + const hp = received.hp; + const maxHp = received.getMaxHp(); + const pkmName = getPokemonNameWithAffix(received); + + return { + pass, + message: () => + pass + ? `Expected ${pkmName} to NOT have full hp, but it did!` + : `Expected ${pkmName} to have full hp, but it didn't! (${hp}/${maxHp} HP)`, + expected: maxHp, + actual: hp, + }; +} diff --git a/test/test-utils/matchers/to-have-hp.ts b/test/test-utils/matchers/to-have-hp.ts new file mode 100644 index 00000000000..20d171b23ce --- /dev/null +++ b/test/test-utils/matchers/to-have-hp.ts @@ -0,0 +1,35 @@ +import { getPokemonNameWithAffix } from "#app/messages"; +// biome-ignore lint/correctness/noUnusedImports: TSDoc +import type { Pokemon } from "#field/pokemon"; +import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +/** + * Matcher that checks if a Pokemon has a specific amount of HP. + * @param received - The object to check. Should be a {@linkcode Pokemon}. + * @param expectedHp - The expected amount of HP the {@linkcode Pokemon} has + * @returns Whether the matcher passed + */ +export function toHaveHp(this: MatcherState, received: unknown, expectedHp: number): SyncExpectationResult { + if (!isPokemonInstance(received)) { + return { + pass: false, + message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, + }; + } + + const actualHp = received.hp; + const pass = actualHp === expectedHp; + + const pkmName = getPokemonNameWithAffix(received); + + return { + pass, + message: () => + pass + ? `Expected ${pkmName} to NOT have ${expectedHp} HP, but it did!` + : `Expected ${pkmName} to have ${expectedHp} HP, but got ${actualHp} HP instead!`, + expected: expectedHp, + actual: actualHp, + }; +} diff --git a/test/test-utils/matchers/to-have-stat-stage.ts b/test/test-utils/matchers/to-have-stat-stage.ts new file mode 100644 index 00000000000..feecd650bef --- /dev/null +++ b/test/test-utils/matchers/to-have-stat-stage.ts @@ -0,0 +1,53 @@ +/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ +import type { Pokemon } from "#field/pokemon"; +/** biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports */ + +import { getPokemonNameWithAffix } from "#app/messages"; +import type { BattleStat } from "#enums/stat"; +import { getStatName } from "#test/test-utils/string-utils"; +import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +/** + * Matcher that checks if a Pokemon has a specific {@linkcode BattleStat | Stat} stage. + * @param received - The object to check. Should be a {@linkcode Pokemon}. + * @param stat - The {@linkcode BattleStat | Stat} to check + * @param expectedStage - The expected numerical value of {@linkcode stat}; should be within the range `[-6, 6]` + * @returns Whether the matcher passed + */ +export function toHaveStatStage( + this: MatcherState, + received: unknown, + stat: BattleStat, + expectedStage: number, +): SyncExpectationResult { + if (!isPokemonInstance(received)) { + return { + pass: false, + message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, + }; + } + + if (expectedStage < -6 || expectedStage > 6) { + return { + pass: false, + message: () => `Expected ${expectedStage} to be within the range [-6, 6]!`, + }; + } + + const actualStage = received.getStatStage(stat); + const pass = actualStage === expectedStage; + + const pkmName = getPokemonNameWithAffix(received); + const statName = getStatName(stat); + + return { + pass, + message: () => + pass + ? `Expected ${pkmName}'s ${statName} stat stage to NOT be ${expectedStage}, but it was!` + : `Expected ${pkmName}'s ${statName} stat stage to be ${expectedStage}, but got ${actualStage} instead!`, + expected: expectedStage, + actual: actualStage, + }; +} diff --git a/test/test-utils/matchers/to-have-status-effect.ts b/test/test-utils/matchers/to-have-status-effect.ts new file mode 100644 index 00000000000..a46800632f3 --- /dev/null +++ b/test/test-utils/matchers/to-have-status-effect.ts @@ -0,0 +1,83 @@ +/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ +import type { Status } from "#data/status-effect"; +import type { Pokemon } from "#field/pokemon"; +/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ + +import { getPokemonNameWithAffix } from "#app/messages"; +import { StatusEffect } from "#enums/status-effect"; +import { getEnumStr, getOnelineDiffStr } from "#test/test-utils/string-utils"; +import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +export type expectedStatusType = + | StatusEffect + | { effect: StatusEffect.TOXIC; toxicTurnCount: number } + | { effect: StatusEffect.SLEEP; sleepTurnsRemaining: number }; + +/** + * Matcher that checks if a Pokemon's {@linkcode StatusEffect} is as expected + * @param received - The actual value received. Should be a {@linkcode Pokemon} + * @param expectedStatus - The {@linkcode StatusEffect} the Pokemon is expected to have, + * or a partially filled {@linkcode Status} containing the desired properties + * @returns Whether the matcher passed + */ +export function toHaveStatusEffect( + this: MatcherState, + received: unknown, + expectedStatus: expectedStatusType, +): SyncExpectationResult { + if (!isPokemonInstance(received)) { + return { + pass: false, + message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, + }; + } + + const pkmName = getPokemonNameWithAffix(received); + const actualEffect = received.status?.effect ?? StatusEffect.NONE; + + // Check exclusively effect equality first, coercing non-matching status effects to numbers. + if (actualEffect !== (expectedStatus as Exclude)?.effect) { + // This is actually 100% safe as `expectedStatus?.effect` will evaluate to `undefined` if a StatusEffect was passed, + // which will never match actualEffect by definition + expectedStatus = (expectedStatus as Exclude).effect; + } + + if (typeof expectedStatus === "number") { + const pass = this.equals(actualEffect, expectedStatus, [...this.customTesters, this.utils.iterableEquality]); + + const actualStr = getEnumStr(StatusEffect, actualEffect, { prefix: "StatusEffect." }); + const expectedStr = getEnumStr(StatusEffect, expectedStatus, { prefix: "StatusEffect." }); + + return { + pass, + message: () => + pass + ? `Expected ${pkmName} to NOT have ${expectedStr}, but it did!` + : `Expected ${pkmName} to have status effect ${expectedStr}, but got ${actualStr} instead!`, + expected: expectedStatus, + actual: actualEffect, + }; + } + + // Check for equality of all fields (for toxic turn count/etc) + const actualStatus = received.status; + const pass = this.equals(received, expectedStatus, [ + ...this.customTesters, + this.utils.subsetEquality, + this.utils.iterableEquality, + ]); + + const expectedStr = getOnelineDiffStr.call(this, expectedStatus); + const actualStr = getOnelineDiffStr.call(this, actualStatus); + + return { + pass, + message: () => + pass + ? `Expected ${pkmName}'s status to NOT match ${expectedStr}, but it did!` + : `Expected ${pkmName}'s status to match ${expectedStr}, but got ${actualStr} instead!`, + expected: expectedStatus, + actual: actualStatus, + }; +} diff --git a/test/test-utils/matchers/to-have-taken-damage.ts b/test/test-utils/matchers/to-have-taken-damage.ts new file mode 100644 index 00000000000..77c60ae836a --- /dev/null +++ b/test/test-utils/matchers/to-have-taken-damage.ts @@ -0,0 +1,46 @@ +/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ +import type { Pokemon } from "#field/pokemon"; +/** biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports */ + +import { getPokemonNameWithAffix } from "#app/messages"; +import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; +import { toDmgValue } from "#utils/common"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +/** + * Matcher that checks if a Pokemon has taken a specific amount of damage. + * Unless specified, will run the expected damage value through {@linkcode toDmgValue} + * to round it down and make it a minimum of 1. + * @param received - The object to check. Should be a {@linkcode Pokemon}. + * @param expectedDamageTaken - The expected amount of damage the {@linkcode Pokemon} has taken + * @param roundDown - Whether to round down {@linkcode expectedDamageTaken} with {@linkcode toDmgValue}; default `true` + * @returns Whether the matcher passed + */ +export function toHaveTakenDamage( + this: MatcherState, + received: unknown, + expectedDamageTaken: number, + roundDown = true, +): SyncExpectationResult { + if (!isPokemonInstance(received)) { + return { + pass: false, + message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, + }; + } + + const expectedDmgValue = roundDown ? toDmgValue(expectedDamageTaken) : expectedDamageTaken; + const actualDmgValue = received.getInverseHp(); + const pass = actualDmgValue === expectedDmgValue; + const pkmName = getPokemonNameWithAffix(received); + + return { + pass, + message: () => + pass + ? `Expected ${pkmName} to NOT have taken ${expectedDmgValue} damage, but it did!` + : `Expected ${pkmName} to have taken ${expectedDmgValue} damage, but got ${actualDmgValue} instead!`, + expected: expectedDmgValue, + actual: actualDmgValue, + }; +} diff --git a/test/test-utils/matchers/to-have-terrain.ts b/test/test-utils/matchers/to-have-terrain.ts new file mode 100644 index 00000000000..292c32abafc --- /dev/null +++ b/test/test-utils/matchers/to-have-terrain.ts @@ -0,0 +1,62 @@ +/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ +import type { GameManager } from "#test/test-utils/game-manager"; +/** biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports */ + +import { TerrainType } from "#app/data/terrain"; +import { getEnumStr } from "#test/test-utils/string-utils"; +import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +/** + * Matcher that checks if the {@linkcode TerrainType} is as expected + * @param received - The object to check. Should be an instance of {@linkcode GameManager}. + * @param expectedTerrainType - The expected {@linkcode TerrainType}, or {@linkcode TerrainType.NONE} if no terrain should be active + * @returns Whether the matcher passed + */ +export function toHaveTerrain( + this: MatcherState, + received: unknown, + expectedTerrainType: TerrainType, +): SyncExpectationResult { + if (!isGameManagerInstance(received)) { + return { + pass: false, + message: () => `Expected GameManager, but got ${receivedStr(received)}!`, + }; + } + + if (!received.scene?.arena) { + return { + pass: false, + message: () => `Expected GameManager.${received.scene ? "scene" : "scene.arena"} to be defined!`, + }; + } + + const actual = received.scene.arena.getTerrainType(); + const pass = actual === expectedTerrainType; + const actualStr = toTerrainStr(actual); + const expectedStr = toTerrainStr(expectedTerrainType); + + return { + pass, + message: () => + pass + ? `Expected Arena to NOT have ${expectedStr} active, but it did!` + : `Expected Arena to have ${expectedStr} active, but got ${actualStr} instead!`, + expected: expectedTerrainType, + actual, + }; +} + +/** + * Get a human readable string of the current {@linkcode TerrainType}. + * @param terrainType - The {@linkcode TerrainType} to transform + * @returns A human readable string + */ +function toTerrainStr(terrainType: TerrainType) { + if (terrainType === TerrainType.NONE) { + return "no terrain"; + } + // "Electric Terrain (=2)" + return getEnumStr(TerrainType, terrainType, { casing: "Title", suffix: " Terrain" }); +} diff --git a/test/test-utils/matchers/to-have-types.ts b/test/test-utils/matchers/to-have-types.ts new file mode 100644 index 00000000000..3f16f740583 --- /dev/null +++ b/test/test-utils/matchers/to-have-types.ts @@ -0,0 +1,61 @@ +import { getPokemonNameWithAffix } from "#app/messages"; +import { PokemonType } from "#enums/pokemon-type"; +import type { Pokemon } from "#field/pokemon"; +import { stringifyEnumArray } from "#test/test-utils/string-utils"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; +import { isPokemonInstance, receivedStr } from "../test-utils"; + +export interface toHaveTypesOptions { + /** + * Whether to enforce exact matches (`true`) or superset matches (`false`). + * @defaultValue `true` + */ + exact?: boolean; + /** + * Optional arguments to pass to {@linkcode Pokemon.getTypes}. + */ + args?: Parameters<(typeof Pokemon.prototype)["getTypes"]>; +} + +/** + * Matcher that checks if an array contains exactly the given items, disregarding order. + * @param received - The object to check. Should be an array of one or more {@linkcode PokemonType}s. + * @param options - The {@linkcode toHaveTypesOptions | options} for this matcher + * @returns The result of the matching + */ +export function toHaveTypes( + this: MatcherState, + received: unknown, + expected: [PokemonType, ...PokemonType[]], + options: toHaveTypesOptions = {}, +): SyncExpectationResult { + if (!isPokemonInstance(received)) { + return { + pass: false, + message: () => `Expected to recieve a Pokémon, but got ${receivedStr(received)}!`, + }; + } + + const actualTypes = received.getTypes(...(options.args ?? [])).sort(); + const expectedTypes = expected.slice().sort(); + + // Exact matches do not care about subset equality + const matchers = options.exact + ? [...this.customTesters, this.utils.iterableEquality] + : [...this.customTesters, this.utils.subsetEquality, this.utils.iterableEquality]; + const pass = this.equals(actualTypes, expectedTypes, matchers); + + const actualStr = stringifyEnumArray(PokemonType, actualTypes); + const expectedStr = stringifyEnumArray(PokemonType, expectedTypes); + const pkmName = getPokemonNameWithAffix(received); + + return { + pass, + message: () => + pass + ? `Expected ${pkmName} to NOT have types ${expectedStr}, but it did!` + : `Expected ${pkmName} to have types ${expectedStr}, but got ${actualStr} instead!`, + expected: expectedTypes, + actual: actualTypes, + }; +} diff --git a/test/test-utils/matchers/to-have-used-move.ts b/test/test-utils/matchers/to-have-used-move.ts new file mode 100644 index 00000000000..ef90e4dbad9 --- /dev/null +++ b/test/test-utils/matchers/to-have-used-move.ts @@ -0,0 +1,70 @@ +/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ +import type { Pokemon } from "#field/pokemon"; +/** biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports */ + +import { getPokemonNameWithAffix } from "#app/messages"; +import type { MoveId } from "#enums/move-id"; +import { getOnelineDiffStr, getOrdinal } from "#test/test-utils/string-utils"; +import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; +import type { TurnMove } from "#types/turn-move"; +import type { AtLeastOne } from "#types/type-helpers"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +/** + * Matcher to check the contents of a {@linkcode Pokemon}'s move history. + * @param received - The actual value received. Should be a {@linkcode Pokemon} + * @param expectedValue - The {@linkcode MoveId} the Pokemon is expected to have used, + * or a partially filled {@linkcode TurnMove} containing the desired properties to check + * @param index - The index of the move history entry to check, in order from most recent to least recent. + * Default `0` (last used move) + * @returns Whether the matcher passed + */ +export function toHaveUsedMove( + this: MatcherState, + received: unknown, + expectedResult: MoveId | AtLeastOne, + index = 0, +): SyncExpectationResult { + if (!isPokemonInstance(received)) { + return { + pass: false, + message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, + }; + } + + const move: TurnMove | undefined = received.getLastXMoves(-1)[index]; + const pkmName = getPokemonNameWithAffix(received); + + if (move === undefined) { + return { + pass: false, + message: () => `Expected ${pkmName} to have used ${index + 1} moves, but it didn't!`, + actual: received.getLastXMoves(-1), + }; + } + + // Coerce to a `TurnMove` + if (typeof expectedResult === "number") { + expectedResult = { move: expectedResult }; + } + + const moveIndexStr = index === 0 ? "last move" : `${getOrdinal(index)} most recent move`; + + const pass = this.equals(move, expectedResult, [ + ...this.customTesters, + this.utils.subsetEquality, + this.utils.iterableEquality, + ]); + + const expectedStr = getOnelineDiffStr.call(this, expectedResult); + return { + pass, + message: () => + pass + ? `Expected ${pkmName}'s ${moveIndexStr} to NOT match ${expectedStr}, but it did!` + : // Replace newlines with spaces to preserve one-line ness + `Expected ${pkmName}'s ${moveIndexStr} to match ${expectedStr}, but it didn't!`, + expected: expectedResult, + actual: move, + }; +} diff --git a/test/test-utils/matchers/to-have-used-pp.ts b/test/test-utils/matchers/to-have-used-pp.ts new file mode 100644 index 00000000000..3b606a535bc --- /dev/null +++ b/test/test-utils/matchers/to-have-used-pp.ts @@ -0,0 +1,77 @@ +// biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports +import type { Pokemon } from "#field/pokemon"; +// biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports + +import { getPokemonNameWithAffix } from "#app/messages"; +import Overrides from "#app/overrides"; +import { MoveId } from "#enums/move-id"; +import { getEnumStr } from "#test/test-utils/string-utils"; +import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; +import { coerceArray } from "#utils/common"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +/** + * Matcher to check the amount of PP consumed by a {@linkcode Pokemon}. + * @param received - The actual value received. Should be a {@linkcode Pokemon} + * @param expectedValue - The {@linkcode MoveId} that should have consumed PP + * @param ppUsed - The numerical amount of PP that should have been consumed, + * or `all` to indicate the move should be _out_ of PP + * @returns Whether the matcher passed + * @remarks + * If the same move appears in the Pokemon's moveset multiple times, this will fail the test! + */ +export function toHaveUsedPP( + this: MatcherState, + received: unknown, + expectedMove: MoveId, + ppUsed: number | "all", +): SyncExpectationResult { + if (!isPokemonInstance(received)) { + return { + pass: false, + message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, + }; + } + + const override = received.isPlayer() ? Overrides.MOVESET_OVERRIDE : Overrides.OPP_MOVESET_OVERRIDE; + if (coerceArray(override).length > 0) { + return { + pass: false, + message: () => + `Cannot test for PP consumption with ${received.isPlayer() ? "player" : "enemy"} moveset overrides active!`, + }; + } + + const pkmName = getPokemonNameWithAffix(received); + const moveStr = getEnumStr(MoveId, expectedMove); + + const movesetMoves = received.getMoveset().filter(pm => pm.moveId === expectedMove); + if (movesetMoves.length !== 1) { + return { + pass: false, + message: () => + `Expected MoveId.${moveStr} to appear in ${pkmName}'s moveset exactly once, but got ${movesetMoves.length} times!`, + expected: expectedMove, + actual: received.getMoveset(), + }; + } + + const move = movesetMoves[0]; // will be the only move in the array + + let ppStr: string = ppUsed.toString(); + if (typeof ppUsed === "string") { + ppStr = "all its"; + ppUsed = move.getMovePp(); + } + const pass = move.ppUsed === ppUsed; + + return { + pass, + message: () => + pass + ? `Expected ${pkmName}'s ${moveStr} to NOT have used ${ppStr} PP, but it did!` + : `Expected ${pkmName}'s ${moveStr} to have used ${ppStr} PP, but got ${move.ppUsed} instead!`, + expected: ppUsed, + actual: move.ppUsed, + }; +} diff --git a/test/test-utils/matchers/to-have-weather.ts b/test/test-utils/matchers/to-have-weather.ts new file mode 100644 index 00000000000..49433b2137b --- /dev/null +++ b/test/test-utils/matchers/to-have-weather.ts @@ -0,0 +1,62 @@ +/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ +import type { GameManager } from "#test/test-utils/game-manager"; +/** biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports */ + +import { WeatherType } from "#enums/weather-type"; +import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils"; +import { toTitleCase } from "#utils/strings"; +import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; + +/** + * Matcher that checks if the {@linkcode WeatherType} is as expected + * @param received - The object to check. Expects an instance of {@linkcode GameManager}. + * @param expectedWeatherType - The expected {@linkcode WeatherType} + * @returns Whether the matcher passed + */ +export function toHaveWeather( + this: MatcherState, + received: unknown, + expectedWeatherType: WeatherType, +): SyncExpectationResult { + if (!isGameManagerInstance(received)) { + return { + pass: false, + message: () => `Expected GameManager, but got ${receivedStr(received)}!`, + }; + } + + if (!received.scene?.arena) { + return { + pass: false, + message: () => `Expected GameManager.${received.scene ? "scene" : "scene.arena"} to be defined!`, + }; + } + + const actual = received.scene.arena.getWeatherType(); + const pass = actual === expectedWeatherType; + const actualStr = toWeatherStr(actual); + const expectedStr = toWeatherStr(expectedWeatherType); + + return { + pass, + message: () => + pass + ? `Expected Arena to NOT have ${expectedStr} weather active, but it did!` + : `Expected Arena to have ${expectedStr} weather active, but got ${actualStr} instead!`, + expected: expectedWeatherType, + actual, + }; +} + +/** + * Get a human readable representation of the current {@linkcode WeatherType}. + * @param weatherType - The {@linkcode WeatherType} to transform + * @returns A human readable string + */ +function toWeatherStr(weatherType: WeatherType) { + if (weatherType === WeatherType.NONE) { + return "no weather"; + } + + return toTitleCase(WeatherType[weatherType]); +} diff --git a/test/test-utils/phase-interceptor.ts b/test/test-utils/phase-interceptor.ts index d2aff60b67d..2944b4d37c8 100644 --- a/test/test-utils/phase-interceptor.ts +++ b/test/test-utils/phase-interceptor.ts @@ -36,6 +36,7 @@ import { NextEncounterPhase } from "#phases/next-encounter-phase"; import { PartyExpPhase } from "#phases/party-exp-phase"; import { PartyHealPhase } from "#phases/party-heal-phase"; import { PokemonTransformPhase } from "#phases/pokemon-transform-phase"; +import { PositionalTagPhase } from "#phases/positional-tag-phase"; import { PostGameOverPhase } from "#phases/post-game-over-phase"; import { PostSummonPhase } from "#phases/post-summon-phase"; import { QuietFormChangePhase } from "#phases/quiet-form-change-phase"; @@ -142,6 +143,7 @@ export class PhaseInterceptor { [LevelCapPhase, this.startPhase], [AttemptRunPhase, this.startPhase], [SelectBiomePhase, this.startPhase], + [PositionalTagPhase, this.startPhase], [PokemonTransformPhase, this.startPhase], [MysteryEncounterPhase, this.startPhase], [MysteryEncounterOptionSelectedPhase, this.startPhase], diff --git a/test/test-utils/string-utils.ts b/test/test-utils/string-utils.ts new file mode 100644 index 00000000000..bd3dd7c2fa9 --- /dev/null +++ b/test/test-utils/string-utils.ts @@ -0,0 +1,183 @@ +import { getStatKey, type Stat } from "#enums/stat"; +import type { EnumOrObject, NormalEnum, TSNumericEnum } from "#types/enum-types"; +import type { ObjectValues } from "#types/type-helpers"; +import { enumValueToKey } from "#utils/enums"; +import { toTitleCase } from "#utils/strings"; +import type { MatcherState } from "@vitest/expect"; +import i18next from "i18next"; + +type Casing = "Preserve" | "Title"; + +interface getEnumStrOptions { + /** + * A string denoting the casing method to use. + * @defaultValue "Preserve" + */ + casing?: Casing; + /** + * If present, will be prepended to the beginning of the enum string. + */ + prefix?: string; + /** + * If present, will be added to the end of the enum string. + */ + suffix?: string; +} + +/** + * Return the name of an enum member or const object value, alongside its corresponding value. + * @param obj - The {@linkcode EnumOrObject} to source reverse mappings from + * @param enums - One of {@linkcode obj}'s values + * @param casing - A string denoting the casing method to use; default `Preserve` + * @param prefix - An optional string to be prepended to the enum's string representation + * @param suffix - An optional string to be appended to the enum's string representation + * @returns The stringified representation of `val` as dictated by the options. + * @example + * ```ts + * enum fakeEnum { + * ONE: 1, + * TWO: 2, + * THREE: 3, + * } + * getEnumStr(fakeEnum, fakeEnum.ONE); // Output: "ONE (=1)" + * getEnumStr(fakeEnum, fakeEnum.TWO, {casing: "Title", prefix: "fakeEnum.", suffix: "!!!"}); // Output: "fakeEnum.TWO!!! (=2)" + * ``` + */ +export function getEnumStr( + obj: E, + val: ObjectValues, + { casing = "Preserve", prefix = "", suffix = "" }: getEnumStrOptions = {}, +): string { + let casingFunc: ((s: string) => string) | undefined; + switch (casing) { + case "Preserve": + break; + case "Title": + casingFunc = toTitleCase; + break; + } + + let stringPart = + obj[val] !== undefined + ? // TS reverse mapped enum + (obj[val] as string) + : // Normal enum/`const object` + (enumValueToKey(obj as NormalEnum, val) as string); + + if (casingFunc) { + stringPart = casingFunc(stringPart); + } + + return `${prefix}${stringPart}${suffix} (=${val})`; +} + +/** + * Convert an array of enums or `const object`s into a readable string version. + * @param obj - The {@linkcode EnumOrObject} to source reverse mappings from + * @param enums - An array of {@linkcode obj}'s values + * @returns The stringified representation of `enums`. + * @example + * ```ts + * enum fakeEnum { + * ONE: 1, + * TWO: 2, + * THREE: 3, + * } + * console.log(stringifyEnumArray(fakeEnum, [fakeEnum.ONE, fakeEnum.TWO, fakeEnum.THREE])); // Output: "[ONE, TWO, THREE] (=[1, 2, 3])" + * ``` + */ +export function stringifyEnumArray(obj: E, enums: E[keyof E][]): string { + if (obj.length === 0) { + return "[]"; + } + + const vals = enums.slice(); + /** An array of string names */ + let names: string[]; + + if (obj[enums[0]] !== undefined) { + // Reverse mapping exists - `obj` is a `TSNumericEnum` and its reverse mapped counterparts are strings + names = enums.map(e => (obj as TSNumericEnum)[e] as string); + } else { + // No reverse mapping exists means `obj` is a `NormalEnum`. + // NB: This (while ugly) should be more ergonomic than doing a repeated lookup for large `const object`s + // as the `enums` array should be significantly shorter than the corresponding enum type. + names = []; + for (const [k, v] of Object.entries(obj as NormalEnum)) { + if (names.length === enums.length) { + // No more names to get + break; + } + // Find all matches for the given enum, assigning their keys to the names array + findIndices(enums, v).forEach(matchIndex => { + names[matchIndex] = k; + }); + } + } + return `[${names.join(", ")}] (=[${vals.join(", ")}])`; +} + +/** + * Return the indices of all occurrences of a value in an array. + * @param arr - The array to search + * @param searchElement - The value to locate in the array + * @param fromIndex - The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0 + */ +function findIndices(arr: T[], searchElement: T, fromIndex = 0): number[] { + const indices: number[] = []; + const arrSliced = arr.slice(fromIndex); + for (const [index, value] of arrSliced.entries()) { + if (value === searchElement) { + indices.push(index); + } + } + return indices; +} + +/** + * Convert a number into an English ordinal + * @param num - The number to convert into an ordinal + * @returns The ordinal representation of {@linkcode num}. + * @example + * ```ts + * console.log(getOrdinal(1)); // Output: "1st" + * console.log(getOrdinal(12)); // Output: "12th" + * console.log(getOrdinal(24)); // Output: "24th" + * ``` + */ +export function getOrdinal(num: number): string { + const tens = num % 10; + const hundreds = num % 100; + if (tens === 1 && hundreds !== 11) { + return num + "st"; + } + if (tens === 2 && hundreds !== 12) { + return num + "nd"; + } + if (tens === 3 && hundreds !== 13) { + return num + "rd"; + } + return num + "th"; +} + +/** + * Get the localized name of a {@linkcode Stat}. + * @param s - The {@linkcode Stat} to check + * @returns - The proper name for s, retrieved from the translations. + */ +export function getStatName(s: Stat): string { + return i18next.t(getStatKey(s)); +} + +/** + * Convert an object into a oneline diff to be shown in an error message. + * @param obj - The object to return the oneline diff of + * @returns The updated diff + */ +export function getOnelineDiffStr(this: MatcherState, obj: unknown): string { + return this.utils + .stringify(obj, undefined, { maxLength: 35, indent: 0, printBasicPrototype: false }) + .replace(/\n/g, " ") // Replace newlines with spaces + .replace(/,(\s*)}$/g, "$1}"); +} diff --git a/test/test-utils/test-file-initialization.ts b/test/test-utils/test-file-initialization.ts index 3dc107c653a..631d3f9146b 100644 --- a/test/test-utils/test-file-initialization.ts +++ b/test/test-utils/test-file-initialization.ts @@ -1,42 +1,46 @@ -import { initAbilities } from "#abilities/ability"; -import { initLoggedInUser } from "#app/account"; import { SESSION_ID_COOKIE_NAME } from "#app/constants"; -import { initBiomes } from "#balance/biomes"; -import { initEggMoves } from "#balance/egg-moves"; -import { initPokemonPrevolutions, initPokemonStarters } from "#balance/pokemon-evolutions"; -import { initPokemonForms } from "#data/pokemon-forms"; -import { initSpecies } from "#data/pokemon-species"; -import { initHeldItems } from "#items/all-held-items"; -import { initTrainerItems } from "#items/all-trainer-items"; -import { initHeldItemPools } from "#items/init-held-item-pools"; -import { initRewardPools } from "#items/init-reward-pools"; -import { initTrainerItemPools } from "#items/init-trainer-item-pools"; -import { initRewards } from "#items/reward"; -import { initMoves } from "#moves/move"; -import { initMysteryEncounters } from "#mystery-encounters/mystery-encounters"; +import { initializeGame } from "#app/init/init"; import { initI18n } from "#plugins/i18n"; -import { initAchievements } from "#system/achv"; -import { initVouchers } from "#system/voucher"; import { blobToString } from "#test/test-utils/game-manager-utils"; import { manageListeners } from "#test/test-utils/listeners-manager"; import { MockConsoleLog } from "#test/test-utils/mocks/mock-console-log"; import { mockContext } from "#test/test-utils/mocks/mock-context-canvas"; import { mockLocalStorage } from "#test/test-utils/mocks/mock-local-storage"; import { MockImage } from "#test/test-utils/mocks/mocks-container/mock-image"; -import { initStatsKeys } from "#ui/game-stats-ui-handler"; import { setCookie } from "#utils/cookies"; import Phaser from "phaser"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import InputText from "phaser3-rex-plugins/plugins/inputtext"; let wasInitialized = false; -/** - * An initialization function that is run at the beginning of every test file (via `beforeAll()`). - */ -export function initTestFile() { - // Set the timezone to UTC for tests. - process.env.TZ = "UTC"; +/** + * Run initialization code upon starting a new file, both per-suite and per-instance oncess. + */ +export function initTests(): void { + setupStubs(); + if (!wasInitialized) { + initTestFile(); + wasInitialized = true; + } + + manageListeners(); +} + +/** + * Initialize various values at the beginning of each testing instance. + */ +function initTestFile(): void { + initI18n(); + initializeGame(); +} + +/** + * Setup various stubs for testing. + * @todo Move this into a dedicated stub file instead of running it once per test instance + * @todo Investigate why this resets on new test suite start + */ +function setupStubs(): void { Object.defineProperty(window, "localStorage", { value: mockLocalStorage(), }); @@ -72,9 +76,9 @@ export function initTestFile() { /** * Sets this object's position relative to another object with a given offset - * @param guideObject {@linkcode Phaser.GameObjects.GameObject} to base the position off of - * @param x The relative x position - * @param y The relative y position + * @param guideObject - The {@linkcode Phaser.GameObjects.GameObject} to base the position off of + * @param x - The relative x position + * @param y - The relative y position */ const setPositionRelative = function (guideObject: any, x: number, y: number): any { const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX)); @@ -89,34 +93,6 @@ export function initTestFile() { Phaser.GameObjects.Text.prototype.setPositionRelative = setPositionRelative; Phaser.GameObjects.Rectangle.prototype.setPositionRelative = setPositionRelative; HTMLCanvasElement.prototype.getContext = () => mockContext; - - // Initialize all of these things if and only if they have not been initialized yet - if (!wasInitialized) { - wasInitialized = true; - initI18n(); - initHeldItems(); - initHeldItemPools(); - initTrainerItems(); - initTrainerItemPools(); - initRewards(); - initRewardPools(); - initVouchers(); - initAchievements(); - initStatsKeys(); - initPokemonPrevolutions(); - initBiomes(); - initEggMoves(); - initPokemonForms(); - initSpecies(); - initMoves(); - initAbilities(); - initLoggedInUser(); - initMysteryEncounters(); - // init the pokemon starters for the pokedex - initPokemonStarters(); - } - - manageListeners(); } /** diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 40e4bbe8775..b9e73c3e9da 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -1,3 +1,5 @@ +import { Pokemon } from "#field/pokemon"; +import type { GameManager } from "#test/test-utils/game-manager"; import i18next, { type ParseKeys } from "i18next"; import { vi } from "vitest"; @@ -29,3 +31,54 @@ export function arrayOfRange(start: number, end: number) { export function getApiBaseUrl() { return import.meta.env.VITE_SERVER_URL ?? "http://localhost:8001"; } + +type TypeOfResult = "undefined" | "object" | "boolean" | "number" | "bigint" | "string" | "symbol" | "function"; + +/** + * Helper to determine the actual type of the received object as human readable string + * @param received - The received object + * @returns A human readable string of the received object (type) + */ +export function receivedStr(received: unknown, expectedType: TypeOfResult = "object"): string { + if (received === null) { + return "null"; + } + if (received === undefined) { + return "undefined"; + } + if (typeof received !== expectedType) { + return typeof received; + } + if (expectedType === "object") { + return received.constructor.name; + } + + return "unknown"; +} + +/** + * Helper to check if the received object is an {@linkcode object} + * @param received - The object to check + * @returns Whether the object is an {@linkcode object}. + */ +function isObject(received: unknown): received is object { + return received !== null && typeof received === "object"; +} + +/** + * Helper function to check if a given object is a {@linkcode Pokemon}. + * @param received - The object to check + * @return Whether `received` is a {@linkcode Pokemon} instance. + */ +export function isPokemonInstance(received: unknown): received is Pokemon { + return isObject(received) && received instanceof Pokemon; +} + +/** + * Checks if an object is a {@linkcode GameManager} instance + * @param received - The object to check + * @returns Whether the object is a {@linkcode GameManager} instance. + */ +export function isGameManagerInstance(received: unknown): received is GameManager { + return isObject(received) && (received as GameManager).constructor.name === "GameManager"; +} diff --git a/test/types/enum-types.test-d.ts b/test/types/enum-types.test-d.ts index 396c479e85a..3d03098c2ad 100644 --- a/test/types/enum-types.test-d.ts +++ b/test/types/enum-types.test-d.ts @@ -1,5 +1,6 @@ -import type { EnumOrObject, EnumValues, NormalEnum, TSNumericEnum } from "#app/@types/enum-types"; import type { enumValueToKey, getEnumKeys, getEnumValues } from "#app/utils/enums"; +import type { EnumOrObject, NormalEnum, TSNumericEnum } from "#types/enum-types"; +import type { ObjectValues } from "#types/type-helpers"; import { describe, expectTypeOf, it } from "vitest"; enum testEnumNum { @@ -16,21 +17,33 @@ const testObjNum = { testON1: 1, testON2: 2 } as const; const testObjString = { testOS1: "apple", testOS2: "banana" } as const; -describe("Enum Type Helpers", () => { - describe("EnumValues", () => { - it("should go from enum object type to value type", () => { - expectTypeOf>().toEqualTypeOf(); - expectTypeOf>().branded.toEqualTypeOf<1 | 2>(); +interface testObject { + key_1: "1"; + key_2: "2"; + key_3: "3"; +} - expectTypeOf>().toEqualTypeOf(); - expectTypeOf>().toEqualTypeOf(); - expectTypeOf>().toMatchTypeOf<"apple" | "banana">(); +describe("Enum Type Helpers", () => { + describe("ObjectValues", () => { + it("should produce a union of an object's values", () => { + expectTypeOf>().toEqualTypeOf<"1" | "2" | "3">(); + }); + + it("should go from enum object type to value type", () => { + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().branded.toEqualTypeOf<1 | 2>(); + + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf< + testEnumString.testS1 | testEnumString.testS2 + >(); + + expectTypeOf>().toExtend<"apple" | "banana">(); }); it("should produce union of const object values as type", () => { - expectTypeOf>().toEqualTypeOf<1 | 2>(); - - expectTypeOf>().toEqualTypeOf<"apple" | "banana">(); + expectTypeOf>().toEqualTypeOf<1 | 2>(); + expectTypeOf>().toEqualTypeOf<"apple" | "banana">(); }); }); @@ -38,7 +51,6 @@ describe("Enum Type Helpers", () => { it("should match numeric enums", () => { expectTypeOf>().toEqualTypeOf(); }); - it("should not match string enums or const objects", () => { expectTypeOf>().toBeNever(); expectTypeOf>().toBeNever(); @@ -59,19 +71,19 @@ describe("Enum Type Helpers", () => { describe("EnumOrObject", () => { it("should match any enum or const object", () => { - expectTypeOf().toMatchTypeOf(); - expectTypeOf().toMatchTypeOf(); - expectTypeOf().toMatchTypeOf(); - expectTypeOf().toMatchTypeOf(); + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); }); it("should not match an enum value union w/o typeof", () => { - expectTypeOf().not.toMatchTypeOf(); - expectTypeOf().not.toMatchTypeOf(); + expectTypeOf().not.toExtend(); + expectTypeOf().not.toExtend(); }); it("should be equivalent to `TSNumericEnum | NormalEnum`", () => { - expectTypeOf().branded.toEqualTypeOf | NormalEnum>(); + expectTypeOf().toEqualTypeOf | NormalEnum>(); }); }); }); @@ -80,6 +92,7 @@ describe("Enum Functions", () => { describe("getEnumKeys", () => { it("should retrieve keys of numeric enum", () => { expectTypeOf>().returns.toEqualTypeOf<("testN1" | "testN2")[]>(); + expectTypeOf>().returns.toEqualTypeOf<("testON1" | "testON2")[]>(); }); }); diff --git a/test/types/positional-tags.test-d.ts b/test/types/positional-tags.test-d.ts new file mode 100644 index 00000000000..a75cc291764 --- /dev/null +++ b/test/types/positional-tags.test-d.ts @@ -0,0 +1,29 @@ +import type { SerializedPositionalTag, serializedPosTagMap } from "#data/positional-tags/load-positional-tag"; +import type { DelayedAttackTag, WishTag } from "#data/positional-tags/positional-tag"; +import type { PositionalTagType } from "#enums/positional-tag-type"; +import type { Mutable, NonFunctionPropertiesRecursive } from "#types/type-helpers"; +import { describe, expectTypeOf, it } from "vitest"; + +// Needed to get around properties being readonly in certain classes +type NonFunctionMutable = Mutable>; + +describe("serializedPositionalTagMap", () => { + it("should contain representations of each tag's serialized form", () => { + expectTypeOf().branded.toEqualTypeOf< + NonFunctionMutable + >(); + expectTypeOf().branded.toEqualTypeOf>(); + }); +}); + +describe("SerializedPositionalTag", () => { + it("should accept a union of all serialized tag forms", () => { + expectTypeOf().branded.toEqualTypeOf< + NonFunctionMutable | NonFunctionMutable + >(); + }); + it("should accept a union of all unserialized tag forms", () => { + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); + }); +}); diff --git a/test/types/type-helpers.test-d.ts b/test/types/type-helpers.test-d.ts new file mode 100644 index 00000000000..29f957890fc --- /dev/null +++ b/test/types/type-helpers.test-d.ts @@ -0,0 +1,38 @@ +import type { AtLeastOne } from "#types/type-helpers"; +import { describe, it } from "node:test"; +import { expectTypeOf } from "vitest"; + +type fakeObj = { + foo: number; + bar: string; + baz: number | string; +}; + +type optionalObj = { + foo: number; + bar: string; + baz?: number | string; +}; + +describe("AtLeastOne", () => { + it("should accept an object with at least 1 of its defined parameters", () => { + expectTypeOf<{ foo: number }>().toExtend>(); + expectTypeOf<{ bar: string }>().toExtend>(); + expectTypeOf<{ baz: number | string }>().toExtend>(); + }); + + it("should convert to a partial intersection with the union of all individual single properties", () => { + expectTypeOf>().branded.toEqualTypeOf< + Partial & ({ foo: number } | { bar: string } | { baz: number | string }) + >(); + }); + + it("should treat optional properties as required", () => { + expectTypeOf>().branded.toEqualTypeOf>(); + }); + + it("should not accept empty objects, even if optional properties are present", () => { + expectTypeOf>().not.toExtend>(); + expectTypeOf>().not.toExtend>(); + }); +}); diff --git a/test/ui/item-manage-button.test.ts b/test/ui/item-manage-button.test.ts new file mode 100644 index 00000000000..a7ea76918a5 --- /dev/null +++ b/test/ui/item-manage-button.test.ts @@ -0,0 +1,172 @@ +import { BerryType } from "#enums/berry-type"; +import { Button } from "#enums/buttons"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import { UiMode } from "#enums/ui-mode"; +import type { Pokemon } from "#field/pokemon"; +import { GameManager } from "#test/test-utils/game-manager"; +import type { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; +import type { PartyUiHandler } from "#ui/party-ui-handler"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("UI - Transfer Items", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(async () => { + game = new GameManager(phaserGame); + game.override + .battleStyle("single") + .startingLevel(100) + .startingHeldItems([ + { name: "BERRY", count: 1, type: BerryType.SITRUS }, + { name: "BERRY", count: 2, type: BerryType.APICOT }, + { name: "BERRY", count: 2, type: BerryType.LUM }, + ]) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyMoveset(MoveId.SPLASH); + + await game.classicMode.startBattle([SpeciesId.RAYQUAZA, SpeciesId.RAYQUAZA, SpeciesId.RAYQUAZA]); + + game.move.use(MoveId.DRAGON_CLAW); + + await game.phaseInterceptor.to("SelectModifierPhase"); + }); + + it("manage button exists in the proper screen", async () => { + let handlerLength: Phaser.GameObjects.GameObject[] | undefined; + + await new Promise(resolve => { + //select manage items menu + game.onNextPrompt("SelectModifierPhase", UiMode.MODIFIER_SELECT, async () => { + await new Promise(r => setTimeout(r, 100)); + const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler; + + handler.processInput(Button.DOWN); + handler.setCursor(1); + handler.processInput(Button.ACTION); + }); + + game.onNextPrompt("SelectModifierPhase", UiMode.PARTY, async () => { + await new Promise(r => setTimeout(r, 100)); + const handler = game.scene.ui.getHandler() as PartyUiHandler; + + handler.processInput(Button.DOWN); + handler.processInput(Button.ACTION); + handlerLength = handler.optionsContainer.list; + + handler.processInput(Button.CANCEL); + + resolve(); + }); + }); + + expect(handlerLength).toHaveLength(0); // should select manage button, which has no menu + }); + + it("manage button doesn't exist in the other screens", async () => { + let handlerLength: Phaser.GameObjects.GameObject[] | undefined; + + await new Promise(resolve => { + game.onNextPrompt("SelectModifierPhase", UiMode.MODIFIER_SELECT, async () => { + await new Promise(r => setTimeout(r, 100)); + const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler; + + handler.processInput(Button.DOWN); + handler.setCursor(2); + handler.processInput(Button.ACTION); + }); + + game.onNextPrompt("SelectModifierPhase", UiMode.PARTY, async () => { + await new Promise(r => setTimeout(r, 100)); + const handler = game.scene.ui.getHandler() as PartyUiHandler; + + handler.processInput(Button.DOWN); + handler.processInput(Button.ACTION); + handlerLength = handler.optionsContainer.list; + + handler.processInput(Button.CANCEL); + handler.processInput(Button.CANCEL); + + resolve(); + }); + }); + + expect(handlerLength).toHaveLength(6); // should select 2nd pokemon (length is 5 options + image) + }); + + // Test that the manage button actually discards items, needs proofreading + it("should discard items when button is selected", async () => { + let pokemon: Pokemon | undefined; + + await new Promise(resolve => { + game.onNextPrompt("SelectModifierPhase", UiMode.MODIFIER_SELECT, async () => { + await new Promise(r => setTimeout(r, 100)); + const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler; + + handler.processInput(Button.DOWN); + handler.setCursor(1); + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectModifierPhase", UiMode.PARTY, async () => { + await new Promise(r => setTimeout(r, 100)); + const handler = game.scene.ui.getHandler() as PartyUiHandler; + + // Enter discard mode and select first party member + handler.setCursor(7); + handler.processInput(Button.ACTION); + handler.setCursor(0); + handler.processInput(Button.ACTION); + pokemon = game.field.getPlayerPokemon(); + + resolve(); + }); + }); + + expect(pokemon).toBeDefined(); + if (pokemon) { + expect(pokemon.getHeldItems()).toHaveLength(3); + expect(pokemon.getHeldItems().map(h => h.stackCount)).toEqual([1, 2, 2]); + } + + await new Promise(resolve => { + game.onNextPrompt("SelectModifierPhase", UiMode.PARTY, async () => { + await new Promise(r => setTimeout(r, 100)); + const handler = game.scene.ui.getHandler() as PartyUiHandler; + handler.processInput(Button.ACTION); + resolve(); + }); + }); + + await new Promise(resolve => { + game.onNextPrompt("SelectModifierPhase", UiMode.PARTY, async () => { + await new Promise(r => setTimeout(r, 100)); + const handler = game.scene.ui.getHandler() as PartyUiHandler; + handler.processInput(Button.ACTION); + + pokemon = game.field.getPlayerPokemon(); + + handler.processInput(Button.CANCEL); + resolve(); + }); + }); + + expect(pokemon).toBeDefined(); + if (pokemon) { + // Sitrus berry was discarded, leaving 2 stacks of 2 berries behind + expect(pokemon.getHeldItems()).toHaveLength(2); + expect(pokemon.getHeldItems().map(h => h.stackCount)).toEqual([2, 2]); + } + }); +}); diff --git a/test/utils/strings.test.ts b/test/utils/strings.test.ts new file mode 100644 index 00000000000..3d6eb235ba8 --- /dev/null +++ b/test/utils/strings.test.ts @@ -0,0 +1,47 @@ +import { splitWords } from "#utils/strings"; +import { describe, expect, it } from "vitest"; + +interface testCase { + input: string; + words: string[]; +} + +const testCases: testCase[] = [ + { + input: "Lorem ipsum dolor sit amet", + words: ["Lorem", "ipsum", "dolor", "sit", "amet"], + }, + { + input: "consectetur-adipiscing-elit", + words: ["consectetur", "adipiscing", "elit"], + }, + { + input: "sed_do_eiusmod_tempor_incididunt_ut_labore", + words: ["sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore"], + }, + { + input: "Et Dolore Magna Aliqua", + words: ["Et", "Dolore", "Magna", "Aliqua"], + }, + { + input: "BIG_ANGRY_TRAINER", + words: ["BIG", "ANGRY", "TRAINER"], + }, + { + input: "ApplesBananasOrangesAndAPear", + words: ["Apples", "Bananas", "Oranges", "And", "A", "Pear"], + }, + { + input: "mysteryEncounters/anOfferYouCantRefuse", + words: ["mystery", "Encounters/an", "Offer", "You", "Cant", "Refuse"], + }, +]; + +describe("Utils - Casing -", () => { + describe("splitWords", () => { + it.each(testCases)("should split a string into its constituent words - $input", ({ input, words }) => { + const ret = splitWords(input); + expect(ret).toEqual(words); + }); + }); +}); diff --git a/test/vitest.setup.ts b/test/vitest.setup.ts index 70293f20469..be35e18e2e9 100644 --- a/test/vitest.setup.ts +++ b/test/vitest.setup.ts @@ -1,5 +1,5 @@ import "vitest-canvas-mock"; -import { initTestFile } from "#test/test-utils/test-file-initialization"; +import { initTests } from "#test/test-utils/test-file-initialization"; import { afterAll, beforeAll, vi } from "vitest"; /** Set the timezone to UTC for tests. */ @@ -51,7 +51,7 @@ vi.mock("i18next", async importOriginal => { global.testFailed = false; beforeAll(() => { - initTestFile(); + initTests(); }); afterAll(() => { diff --git a/tsconfig.json b/tsconfig.json index 6e714ceaa79..471c1034996 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,11 @@ { "compilerOptions": { - "target": "ES2020", - "module": "ES2020", + "target": "ES2023", + "module": "ES2022", // Modifying this option requires all values to be set manually because the defaults get overridden - // Values other than "ES2024.Promise" taken from https://github.com/microsoft/TypeScript/blob/main/src/lib/es2020.full.d.ts + // Values other than "ES2024.Promise" taken from https://github.com/microsoft/TypeScript/blob/main/src/lib/es2023.full.d.ts "lib": [ - "ES2020", + "ES2023", "ES2024.Promise", "DOM", "DOM.AsyncIterable", @@ -18,6 +18,7 @@ "esModuleInterop": true, "strictNullChecks": true, "sourceMap": false, + "checkJs": true, "strict": false, // TODO: Enable this eventually "rootDir": ".", "baseUrl": "./src", @@ -48,7 +49,7 @@ "./system/*.ts" ], "#trainers/*": ["./data/trainers/*.ts"], - "#types/*": ["./@types/*.ts", "./typings/phaser/*.ts"], + "#types/*": ["./@types/helpers/*.ts", "./@types/*.ts", "./typings/phaser/*.ts"], "#ui/*": ["./ui/battle-info/*.ts", "./ui/settings/*.ts", "./ui/*.ts"], "#utils/*": ["./utils/*.ts"], "#data/*": ["./data/pokemon-forms/*.ts", "./data/pokemon/*.ts", "./data/*.ts"], diff --git a/tsdoc.json b/tsdoc.json index b4cbc9a62a5..689f7a96c5c 100644 --- a/tsdoc.json +++ b/tsdoc.json @@ -9,6 +9,10 @@ { "tagName": "@linkcode", "syntaxKind": "inline" + }, + { + "tagName": "@module", + "syntaxKind": "modifier" } ] } diff --git a/vitest.config.ts b/vitest.config.ts index e9f7a2a438c..65c5427e591 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -5,8 +5,11 @@ import { defaultConfig } from "./vite.config"; export default defineProject(({ mode }) => ({ ...defaultConfig, test: { + env: { + TZ: "UTC", + }, testTimeout: 20000, - setupFiles: ["./test/font-face.setup.ts", "./test/vitest.setup.ts"], + setupFiles: ["./test/font-face.setup.ts", "./test/vitest.setup.ts", "./test/matchers.setup.ts"], sequence: { sequencer: MySequencer, }, From ad26adf426d0de240d369df86c55c5fea4d04e26 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Sun, 3 Aug 2025 19:07:47 -0400 Subject: [PATCH 38/39] Fixed `objectValues` issues --- src/enums/held-item-effect.ts | 4 ++-- src/enums/held-item-id.ts | 6 +++--- src/enums/reward-id.ts | 6 +++--- src/items/held-items/berry.ts | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/enums/held-item-effect.ts b/src/enums/held-item-effect.ts index 209a74b921e..52aeb13faec 100644 --- a/src/enums/held-item-effect.ts +++ b/src/enums/held-item-effect.ts @@ -1,4 +1,4 @@ -import type { EnumValues } from "#types/enum-types"; +import type { ObjectValues } from "#types/type-helpers"; /** * Enum representing the various "classes" of item effects that can be applied. @@ -34,4 +34,4 @@ export const HeldItemEffect = { INCREMENTING_STAT: 52, } as const; -export type HeldItemEffect = EnumValues; +export type HeldItemEffect = ObjectValues; diff --git a/src/enums/held-item-id.ts b/src/enums/held-item-id.ts index c112ab6f37a..91d1901cb4c 100644 --- a/src/enums/held-item-id.ts +++ b/src/enums/held-item-id.ts @@ -1,4 +1,4 @@ -import type { EnumValues } from "#types/enum-types"; +import type { ObjectValues } from "#types/type-helpers"; // TODO: make category the lower 2 bytes export const HeldItemId = { @@ -94,7 +94,7 @@ export const HeldItemId = { GIMMIGHOUL_EVO_TRACKER: 0x0A01, } as const; -export type HeldItemId = EnumValues; +export type HeldItemId = ObjectValues; type HeldItemNameMap = { [k in HeldItemName as (typeof HeldItemId)[k]]: k @@ -127,7 +127,7 @@ export const HeldItemCategoryId = { EVO_TRACKER: 0x0A00, } as const; -export type HeldItemCategoryId = EnumValues; +export type HeldItemCategoryId = ObjectValues; const ITEM_CATEGORY_MASK = 0xFF00 diff --git a/src/enums/reward-id.ts b/src/enums/reward-id.ts index 97a6a1f5747..145bad98914 100644 --- a/src/enums/reward-id.ts +++ b/src/enums/reward-id.ts @@ -1,4 +1,4 @@ -import type { EnumValues } from "#types/enum-types"; +import type { ObjectValues } from "#types/type-helpers"; export const RewardId = { NONE: 0x0000, @@ -69,7 +69,7 @@ export const RewardId = { RARE_FORM_CHANGE_ITEM: 0x2E02, } as const; -export type RewardId = EnumValues; +export type RewardId = ObjectValues; export const RewardCategoryId = { NONE: 0x0000, @@ -90,7 +90,7 @@ export const RewardCategoryId = { FORM_CHANGE_ITEM: 0x0F00, } as const; -export type RewardCategoryId = EnumValues; +export type RewardCategoryId = ObjectValues; const ITEM_CATEGORY_MASK = 0xFF00 diff --git a/src/items/held-items/berry.ts b/src/items/held-items/berry.ts index 0ce886c41fe..e0903778965 100644 --- a/src/items/held-items/berry.ts +++ b/src/items/held-items/berry.ts @@ -7,11 +7,11 @@ import { BerryUsedEvent } from "#events/battle-scene"; import type { Pokemon } from "#field/pokemon"; import { ConsumableHeldItem } from "#items/held-item"; import { TrainerItemEffect } from "#items/trainer-item"; -import type { EnumValues } from "#types/enum-types"; +import type { ObjectValues } from "#types/type-helpers"; import { BooleanHolder } from "#utils/common"; type BerryTypeToHeldItemMap = { - [key in EnumValues]: HeldItemId; + [key in ObjectValues]: HeldItemId; }; // TODO: Rework this to use a bitwise XOR From dac9e202a00d123445da8b83dfd664fec02f02af Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 4 Aug 2025 09:04:48 +0200 Subject: [PATCH 39/39] [Modifier Refactor] Modernize reward pool (#6208) * Added missing `export`s to classes * Created `allRewards` object * RewardPool now uses ids; function to generate appropriate reward from id (including held item or trainer item) * Added generateRewardOptionFromId function * Using `RewardSpecs` for predetermined reward generation * Removed RewardOverride * Removed rewardInitObj * Removed WeightedReward * Proper initialization of allRewards --- src/@types/rewards.ts | 9 + src/data/data-lists.ts | 7 +- .../encounters/a-trainers-test-encounter.ts | 4 +- .../encounters/berries-abound-encounter.ts | 8 +- .../encounters/bug-type-superfan-encounter.ts | 35 +- .../encounters/dancing-lessons-encounter.ts | 4 +- .../department-store-sale-encounter.ts | 42 +- .../encounters/field-trip-encounter.ts | 32 +- .../encounters/fun-and-games-encounter.ts | 10 +- .../mysterious-challengers-encounter.ts | 4 +- .../slumbering-snorlax-encounter.ts | 5 +- .../teleporting-hijinks-encounter.ts | 7 +- .../the-expert-pokemon-breeder-encounter.ts | 8 +- .../encounters/the-strong-stuff-encounter.ts | 3 +- .../the-winstrate-challenge-encounter.ts | 14 +- .../encounters/weird-dream-encounter.ts | 19 +- src/enums/reward-id.ts | 7 +- src/init/init.ts | 2 +- src/items/all-held-items.ts | 2 +- src/items/all-rewards.ts | 182 +++++++ src/items/init-reward-pools.ts | 492 +++++++++--------- src/items/reward-pool-utils.ts | 73 +-- src/items/reward-utils.ts | 88 ++-- src/items/reward.ts | 339 +----------- src/overrides.ts | 11 +- src/phases/select-reward-phase.ts | 2 +- test/phases/select-reward-phase.test.ts | 6 +- test/test-utils/helpers/overrides-helper.ts | 4 +- 28 files changed, 640 insertions(+), 779 deletions(-) create mode 100644 src/items/all-rewards.ts diff --git a/src/@types/rewards.ts b/src/@types/rewards.ts index ad938a60164..acf4749a3da 100644 --- a/src/@types/rewards.ts +++ b/src/@types/rewards.ts @@ -9,9 +9,18 @@ export type WeightedRewardWeightFunc = (party: Pokemon[], rerollCount?: number) export type RewardPoolId = RewardId | HeldItemId | TrainerItemId; +export type RewardGeneratorSpecs = { + id: RewardId; + args: RewardGeneratorArgs; +}; +// TODO: fix this with correctly typed args for different RewardIds + +export type RewardSpecs = RewardPoolId | RewardGeneratorSpecs; + export type RewardPoolEntry = { id: RewardPoolId; weight: number | WeightedRewardWeightFunc; + maxWeight?: number; }; export type RewardPool = { diff --git a/src/data/data-lists.ts b/src/data/data-lists.ts index 85f5a0e6b20..a8db163fa69 100644 --- a/src/data/data-lists.ts +++ b/src/data/data-lists.ts @@ -1,11 +1,12 @@ import type { Ability } from "#abilities/ability"; import type { PokemonSpecies } from "#data/pokemon-species"; import type { HeldItemId } from "#enums/held-item-id"; +import type { RewardId } from "#enums/reward-id"; import type { TrainerItemId } from "#enums/trainer-item-id"; import type { HeldItem } from "#items/held-item"; -import type { Rewards } from "#items/reward"; import type { TrainerItem } from "#items/trainer-item"; import type { Move } from "#moves/move"; +import type { RewardFunc } from "#types/rewards"; export const allAbilities: Ability[] = []; export const allMoves: Move[] = []; @@ -13,6 +14,4 @@ export const allSpecies: PokemonSpecies[] = []; export const allHeldItems: Record = {}; export const allTrainerItems: Record = {}; - -// TODO: Figure out what this is used for and provide an appropriate tsdoc comment -export const allRewards = {} as Rewards; +export const allRewards: Record = {}; diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index 57a78f5f9d0..4bbabe73ee4 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -1,11 +1,11 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { allRewards } from "#data/data-lists"; import type { IEggOptions } from "#data/egg"; import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { RewardId } from "#enums/reward-id"; import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; @@ -164,7 +164,7 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder. encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`)); setEncounterRewards( { - guaranteedRewardFuncs: [allRewards.SACRED_ASH], + guaranteedRewardSpecs: [RewardId.SACRED_ASH], guaranteedRarityTiers: [RarityTier.ROGUE, RarityTier.ULTRA], fillRemaining: true, }, diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index ed581c8f544..2abfc81b1ea 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -1,19 +1,19 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { allRewards } from "#data/data-lists"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { RewardId } from "#enums/reward-id"; import { RewardPoolType } from "#enums/reward-pool-type"; import { PERMANENT_STATS, Stat } from "#enums/stat"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; import { berryTypeToHeldItem } from "#items/berry"; import type { RewardOption } from "#items/reward"; import { generateRewardPoolWeights, getRewardPoolForType } from "#items/reward-pool-utils"; -import { generateRewardOption } from "#items/reward-utils"; +import { generateRewardOptionFromId } from "#items/reward-utils"; import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; import { @@ -162,7 +162,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder. const shopOptions: RewardOption[] = []; for (let i = 0; i < 5; i++) { // Generate shop berries - const mod = generateRewardOption(allRewards.BERRY); + const mod = generateRewardOptionFromId(RewardId.BERRY); if (mod) { shopOptions.push(mod); } @@ -189,7 +189,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder. const shopOptions: RewardOption[] = []; for (let i = 0; i < 5; i++) { // Generate shop berries - const mod = generateRewardOption(allRewards.BERRY); + const mod = generateRewardOptionFromId(RewardId.BERRY); if (mod) { shopOptions.push(mod); } diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index b526be72ea0..8b9edd9f51e 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -1,6 +1,6 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { allHeldItems, allMoves, allRewards } from "#data/data-lists"; +import { allHeldItems, allMoves } from "#data/data-lists"; import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -8,6 +8,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { PokemonType } from "#enums/pokemon-type"; +import { RewardId } from "#enums/reward-id"; import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerItemId } from "#enums/trainer-item-id"; @@ -15,7 +16,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerType } from "#enums/trainer-type"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; import type { RewardOption } from "#items/reward"; -import { generateRewardOption } from "#items/reward-utils"; +import { generateRewardOptionFromId } from "#items/reward-utils"; import { PokemonMove } from "#moves/pokemon-move"; import { getEncounterText, showEncounterDialogue } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; @@ -285,7 +286,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde moveTutorOptions, }; - // Assigns callback that teaches move before continuing to allRewards + // Assigns callback that teaches move before continuing to RewardId encounter.onRewards = doBugTypeMoveTutor; setEncounterRewards({ fillRemaining: true }); @@ -305,7 +306,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde // Player shows off their bug types const encounter = globalScene.currentBattle.mysteryEncounter!; - // Player gets different allRewards depending on the number of bug types they have + // Player gets different RewardId depending on the number of bug types they have const numBugTypes = globalScene.getPlayerParty().filter(p => p.isOfType(PokemonType.BUG, true)).length; const numBugTypesText = i18next.t(`${namespace}:numBugTypes`, { count: numBugTypes, @@ -314,7 +315,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde if (numBugTypes < 2) { setEncounterRewards({ - guaranteedRewardFuncs: [allRewards.SUPER_LURE, allRewards.GREAT_BALL], + guaranteedRewardSpecs: [RewardId.SUPER_LURE, RewardId.GREAT_BALL], fillRemaining: false, }); encounter.selectedOption!.dialogue!.selected = [ @@ -325,7 +326,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde ]; } else if (numBugTypes < 4) { setEncounterRewards({ - guaranteedRewardFuncs: [allRewards.QUICK_CLAW, allRewards.MAX_LURE, allRewards.ULTRA_BALL], + guaranteedRewardSpecs: [HeldItemId.QUICK_CLAW, RewardId.MAX_LURE, RewardId.ULTRA_BALL], fillRemaining: false, }); encounter.selectedOption!.dialogue!.selected = [ @@ -336,7 +337,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde ]; } else if (numBugTypes < 6) { setEncounterRewards({ - guaranteedRewardFuncs: [allRewards.GRIP_CLAW, allRewards.MAX_LURE, allRewards.ROGUE_BALL], + guaranteedRewardSpecs: [HeldItemId.GRIP_CLAW, RewardId.MAX_LURE, RewardId.ROGUE_BALL], fillRemaining: false, }); encounter.selectedOption!.dialogue!.selected = [ @@ -348,28 +349,28 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde } else { // If the player has any evolution/form change items that are valid for their party, // spawn one of those items in addition to Dynamax Band, Mega Band, and Master Ball - const rewardOptions: RewardOption[] = [generateRewardOption(allRewards.MASTER_BALL)!]; + const rewardOptions: RewardOption[] = [generateRewardOptionFromId(RewardId.MASTER_BALL)!]; const specialOptions: RewardOption[] = []; if (!globalScene.trainerItems.hasItem(TrainerItemId.MEGA_BRACELET)) { - rewardOptions.push(generateRewardOption(allRewards.MEGA_BRACELET)!); + rewardOptions.push(generateRewardOptionFromId(TrainerItemId.MEGA_BRACELET)!); } if (!globalScene.trainerItems.hasItem(TrainerItemId.DYNAMAX_BAND)) { - rewardOptions.push(generateRewardOption(allRewards.DYNAMAX_BAND)!); + rewardOptions.push(generateRewardOptionFromId(TrainerItemId.DYNAMAX_BAND)!); } - const nonRareEvolutionReward = generateRewardOption(allRewards.EVOLUTION_ITEM); + const nonRareEvolutionReward = generateRewardOptionFromId(RewardId.EVOLUTION_ITEM); if (nonRareEvolutionReward) { specialOptions.push(nonRareEvolutionReward); } - const rareEvolutionReward = generateRewardOption(allRewards.RARE_EVOLUTION_ITEM); + const rareEvolutionReward = generateRewardOptionFromId(RewardId.RARE_EVOLUTION_ITEM); if (rareEvolutionReward) { specialOptions.push(rareEvolutionReward); } - const formChangeReward = generateRewardOption(allRewards.FORM_CHANGE_ITEM); + const formChangeReward = generateRewardOptionFromId(RewardId.FORM_CHANGE_ITEM); if (formChangeReward) { specialOptions.push(formChangeReward); } - const rareFormChangeReward = generateRewardOption(allRewards.RARE_FORM_CHANGE_ITEM); + const rareFormChangeReward = generateRewardOptionFromId(RewardId.RARE_FORM_CHANGE_ITEM); if (rareFormChangeReward) { specialOptions.push(rareFormChangeReward); } @@ -465,12 +466,12 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde chosenPokemon.loseHeldItem(lostItem, false); globalScene.updateItems(true); - const bugNet = generateRewardOption(allRewards.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!; + const bugNet = generateRewardOptionFromId(TrainerItemId.GOLDEN_BUG_NET)!; bugNet.type.tier = RarityTier.ROGUE; setEncounterRewards({ guaranteedRewardOptions: [bugNet], - guaranteedRewardFuncs: [allRewards.REVIVER_SEED], + guaranteedRewardSpecs: [HeldItemId.REVIVER_SEED], fillRemaining: false, }); leaveEncounterWithoutBattle(true); @@ -744,7 +745,7 @@ function doBugTypeMoveTutor(): Promise { ); } - // Complete battle and go to allRewards + // Complete battle and go to RewardId resolve(); }); } diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 56b21490f29..2db0580fccc 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -1,11 +1,11 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; import { EncounterBattleAnim } from "#data/battle-anims"; -import { allRewards } from "#data/data-lists"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BiomeId } from "#enums/biome-id"; import { EncounterAnim } from "#enums/encounter-anims"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { MoveUseMode } from "#enums/move-use-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -219,7 +219,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder await hideOricorioPokemon(); setEncounterRewards({ - guaranteedRewardFuncs: [allRewards.BATON], + guaranteedRewardSpecs: [HeldItemId.BATON], fillRemaining: true, }); await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); diff --git a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts index 28de326cf46..bb645c5d004 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -1,12 +1,12 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; -import { allRewards } from "#data/data-lists"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { RewardId } from "#enums/reward-id"; import { SpeciesId } from "#enums/species-id"; import { leaveEncounterWithoutBattle, setEncounterRewards } from "#mystery-encounters/encounter-phase-utils"; import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; -import type { RewardFunc } from "#types/rewards"; +import type { RewardSpecs } from "#types/rewards"; import { randSeedInt } from "#utils/common"; /** i18n namespace for encounter */ @@ -59,23 +59,23 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu }, async () => { // Choose TMs - const rewards: RewardFunc[] = []; + const rewards: RewardSpecs[] = []; let i = 0; while (i < 5) { // 2/2/1 weight on TM rarity const roll = randSeedInt(5); if (roll < 2) { - rewards.push(allRewards.TM_COMMON); + rewards.push(RewardId.TM_COMMON); } else if (roll < 4) { - rewards.push(allRewards.TM_GREAT); + rewards.push(RewardId.TM_GREAT); } else { - rewards.push(allRewards.TM_ULTRA); + rewards.push(RewardId.TM_ULTRA); } i++; } setEncounterRewards({ - guaranteedRewardFuncs: rewards, + guaranteedRewardSpecs: rewards, fillRemaining: false, }); leaveEncounterWithoutBattle(); @@ -88,21 +88,21 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu }, async () => { // Choose Vitamins - const rewards: RewardFunc[] = []; + const rewards: RewardSpecs[] = []; let i = 0; while (i < 3) { // 2/1 weight on base stat booster vs PP Up const roll = randSeedInt(3); if (roll === 0) { - rewards.push(allRewards.PP_UP); + rewards.push(RewardId.PP_UP); } else { - rewards.push(allRewards.BASE_STAT_BOOSTER); + rewards.push(RewardId.BASE_STAT_BOOSTER); } i++; } setEncounterRewards({ - guaranteedRewardFuncs: rewards, + guaranteedRewardSpecs: rewards, fillRemaining: false, }); leaveEncounterWithoutBattle(); @@ -115,21 +115,21 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu }, async () => { // Choose X Items - const rewards: RewardFunc[] = []; + const rewards: RewardSpecs[] = []; let i = 0; while (i < 5) { // 4/1 weight on base stat booster vs Dire Hit const roll = randSeedInt(5); if (roll === 0) { - rewards.push(allRewards.DIRE_HIT); + rewards.push(RewardId.DIRE_HIT); } else { - rewards.push(allRewards.TEMP_STAT_STAGE_BOOSTER); + rewards.push(RewardId.TEMP_STAT_STAGE_BOOSTER); } i++; } setEncounterRewards({ - guaranteedRewardFuncs: rewards, + guaranteedRewardSpecs: rewards, fillRemaining: false, }); leaveEncounterWithoutBattle(); @@ -142,25 +142,25 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu }, async () => { // Choose Pokeballs - const rewards: RewardFunc[] = []; + const rewards: RewardSpecs[] = []; let i = 0; while (i < 4) { // 10/30/20/5 weight on pokeballs const roll = randSeedInt(65); if (roll < 10) { - rewards.push(allRewards.POKEBALL); + rewards.push(RewardId.POKEBALL); } else if (roll < 40) { - rewards.push(allRewards.GREAT_BALL); + rewards.push(RewardId.GREAT_BALL); } else if (roll < 60) { - rewards.push(allRewards.ULTRA_BALL); + rewards.push(RewardId.ULTRA_BALL); } else { - rewards.push(allRewards.ROGUE_BALL); + rewards.push(RewardId.ROGUE_BALL); } i++; } setEncounterRewards({ - guaranteedRewardFuncs: rewards, + guaranteedRewardSpecs: rewards, fillRemaining: false, }); leaveEncounterWithoutBattle(); diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index 35485fba083..34b13527cfd 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -1,13 +1,11 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { allRewards } from "#data/data-lists"; import { MoveCategory } from "#enums/move-category"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Stat } from "#enums/stat"; import type { PlayerPokemon } from "#field/pokemon"; -import { generateRewardOption } from "#items/reward-utils"; import type { PokemonMove } from "#moves/pokemon-move"; import { leaveEncounterWithoutBattle, @@ -96,11 +94,11 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with const encounter = globalScene.currentBattle.mysteryEncounter!; if (encounter.misc.correctMove) { const modifiers = [ - generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.ATK])!, - generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.DEF])!, - generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!, - generateRewardOption(allRewards.DIRE_HIT)!, - generateRewardOption(allRewards.RARER_CANDY)!, + generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.ATK])!, + generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.DEF])!, + generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!, + generateRewardOptionFromId(RewardId.DIRE_HIT)!, + generateRewardOptionFromId(RewardId.RARER_CANDY)!, ]; setEncounterRewards({ @@ -144,11 +142,11 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with const encounter = globalScene.currentBattle.mysteryEncounter!; if (encounter.misc.correctMove) { const modifiers = [ - generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.SPATK])!, - generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.SPDEF])!, - generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!, - generateRewardOption(allRewards.DIRE_HIT)!, - generateRewardOption(allRewards.RARER_CANDY)!, + generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.SPATK])!, + generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.SPDEF])!, + generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!, + generateRewardOptionFromId(RewardId.DIRE_HIT)!, + generateRewardOptionFromId(RewardId.RARER_CANDY)!, ]; setEncounterRewards({ @@ -192,11 +190,11 @@ export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.with const encounter = globalScene.currentBattle.mysteryEncounter!; if (encounter.misc.correctMove) { const modifiers = [ - generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.ACC])!, - generateRewardOption(allRewards.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!, - generateRewardOption(allRewards.GREAT_BALL)!, - generateRewardOption(allRewards.IV_SCANNER)!, - generateRewardOption(allRewards.RARER_CANDY)!, + generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.ACC])!, + generateRewardOptionFromId(RewardId.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!, + generateRewardOptionFromId(RewardId.GREAT_BALL)!, + generateRewardOptionFromId(RewardId.IV_SCANNER)!, + generateRewardOptionFromId(RewardId.RARER_CANDY)!, ]; setEncounterRewards({ diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 8fb63b16085..51bfeea397c 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -1,10 +1,10 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { allRewards } from "#data/data-lists"; import { SpeciesFormChangeActiveTrigger } from "#data/form-change-triggers"; import { getPokeballAtlasKey, getPokeballTintColor } from "#data/pokeball"; import { FieldPosition } from "#enums/field-position"; +import { HeldItemId } from "#enums/held-item-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; @@ -160,7 +160,7 @@ export const FunAndGamesEncounter: MysteryEncounter = MysteryEncounterBuilder.wi ], }, async () => { - // Leave encounter with no allRewards or exp + // Leave encounter with no RewardId or exp await transitionMysteryEncounterIntroVisuals(true, true); leaveEncounterWithoutBattle(true); return true; @@ -281,21 +281,21 @@ function handleNextTurn() { if (healthRatio < 0.03) { // Grand prize setEncounterRewards({ - guaranteedRewardFuncs: [allRewards.MULTI_LENS], + guaranteedRewardSpecs: [HeldItemId.MULTI_LENS], fillRemaining: false, }); resultMessageKey = `${namespace}:best_result`; } else if (healthRatio < 0.15) { // 2nd prize setEncounterRewards({ - guaranteedRewardFuncs: [allRewards.SCOPE_LENS], + guaranteedRewardSpecs: [HeldItemId.SCOPE_LENS], fillRemaining: false, }); resultMessageKey = `${namespace}:great_result`; } else if (healthRatio < 0.33) { // 3rd prize setEncounterRewards({ - guaranteedRewardFuncs: [allRewards.WIDE_LENS], + guaranteedRewardSpecs: [HeldItemId.WIDE_LENS], fillRemaining: false, }); resultMessageKey = `${namespace}:good_result`; diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index ec26298bf93..4d7a03485c7 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -1,9 +1,9 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { allRewards } from "#data/data-lists"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; +import { RewardId } from "#enums/reward-id"; import { RarityTier } from "#enums/reward-tier"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; import { initBattleWithEnemyConfig, setEncounterRewards } from "#mystery-encounters/encounter-phase-utils"; @@ -147,7 +147,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; setEncounterRewards({ - guaranteedRewardFuncs: [allRewards.TM_COMMON, allRewards.TM_GREAT, allRewards.MEMORY_MUSHROOM], + guaranteedRewardSpecs: [RewardId.TM_COMMON, RewardId.TM_GREAT, RewardId.MEMORY_MUSHROOM], fillRemaining: true, }); diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index e3d55b6724b..62f864bb464 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -1,5 +1,4 @@ import { globalScene } from "#app/global-scene"; -import { allRewards } from "#data/data-lists"; import { CustomPokemonData } from "#data/pokemon-data"; import { AiType } from "#enums/ai-type"; import { BattlerIndex } from "#enums/battler-index"; @@ -116,7 +115,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil // Pick battle const encounter = globalScene.currentBattle.mysteryEncounter!; setEncounterRewards({ - guaranteedRewardFuncs: [allRewards.LEFTOVERS], + guaranteedRewardSpecs: [HeldItemId.LEFTOVERS], fillRemaining: true, }); encounter.startOfBattleEffects.push({ @@ -163,7 +162,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil // Steal the Snorlax's Leftovers const instance = globalScene.currentBattle.mysteryEncounter!; setEncounterRewards({ - guaranteedRewardFuncs: [allRewards.LEFTOVERS], + guaranteedRewardSpecs: [HeldItemId.LEFTOVERS], fillRemaining: false, }); // Snorlax exp to Pokemon that did the stealing diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index 5239da1512b..4579ad1246b 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -1,9 +1,9 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { allRewards } from "#data/data-lists"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BiomeId } from "#enums/biome-id"; +import { HeldItemId } from "#enums/held-item-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; @@ -13,7 +13,6 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { getBiomeKey } from "#field/arena"; import type { Pokemon } from "#field/pokemon"; import { EnemyPokemon } from "#field/pokemon"; -import { generateRewardOption } from "#items/reward-utils"; import { queueEncounterMessage, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; import { @@ -173,10 +172,8 @@ export const TeleportingHijinksEncounter: MysteryEncounter = MysteryEncounterBui ], }; - const magnet = generateRewardOption(allRewards.ATTACK_TYPE_BOOSTER, [PokemonType.STEEL])!; - const metalCoat = generateRewardOption(allRewards.ATTACK_TYPE_BOOSTER, [PokemonType.ELECTRIC])!; setEncounterRewards({ - guaranteedRewardOptions: [magnet, metalCoat], + guaranteedRewardSpecs: [HeldItemId.MAGNET, HeldItemId.METAL_COAT], fillRemaining: true, }); await transitionMysteryEncounterIntroVisuals(true, true); 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 4eb63d94af0..5e1e50e2c41 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 @@ -1,11 +1,11 @@ import { globalScene } from "#app/global-scene"; import { speciesStarterCosts } from "#balance/starters"; -import { allRewards } from "#data/data-lists"; import type { IEggOptions } from "#data/egg"; import { getPokeballTintColor } from "#data/pokeball"; import { BiomeId } from "#enums/biome-id"; import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; +import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -294,7 +294,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount const eggOptions = getEggOptions(pokemon1CommonEggs, pokemon1RareEggs); setEncounterRewards( { - guaranteedRewardFuncs: [allRewards.SOOTHE_BELL], + guaranteedRewardSpecs: [HeldItemId.SOOTHE_BELL], fillRemaining: true, }, eggOptions, @@ -353,7 +353,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount const eggOptions = getEggOptions(pokemon2CommonEggs, pokemon2RareEggs); setEncounterRewards( { - guaranteedRewardFuncs: [allRewards.SOOTHE_BELL], + guaranteedRewardSpecs: [HeldItemId.SOOTHE_BELL], fillRemaining: true, }, eggOptions, @@ -412,7 +412,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount const eggOptions = getEggOptions(pokemon3CommonEggs, pokemon3RareEggs); setEncounterRewards( { - guaranteedRewardFuncs: [allRewards.SOOTHE_BELL], + guaranteedRewardSpecs: [HeldItemId.SOOTHE_BELL], fillRemaining: true, }, eggOptions, diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index fd1e6268132..3ce4aeb6412 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -1,6 +1,5 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; -import { allRewards } from "#data/data-lists"; import { CustomPokemonData } from "#data/pokemon-data"; import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -193,7 +192,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder // Pick battle const encounter = globalScene.currentBattle.mysteryEncounter!; setEncounterRewards({ - guaranteedRewardFuncs: [allRewards.SOUL_DEW], + guaranteedRewardSpecs: [HeldItemId.SOUL_DEW], fillRemaining: true, }); encounter.startOfBattleEffects.push( diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 8c2e422aa35..e6754a5b4bb 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -11,10 +11,12 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Nature } from "#enums/nature"; +import { RewardId } from "#enums/reward-id"; import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; -import { generateRewardOption } from "#items/reward-utils"; +import type { Reward } from "#items/reward"; +import { generateRewardOptionFromId } from "#items/reward-utils"; import { showEncounterDialogue, showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; import type { EnemyPartyConfig } from "#mystery-encounters/encounter-phase-utils"; import { @@ -140,7 +142,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounter // Refuse the challenge, they full heal the party and give the player a Rarer Candy globalScene.phaseManager.unshiftNew("PartyHealPhase", true); setEncounterRewards({ - guaranteedRewardFuncs: [allRewards.RARER_CANDY], + guaranteedRewardSpecs: [RewardId.RARER_CANDY], fillRemaining: false, }); leaveEncounterWithoutBattle(); @@ -156,14 +158,14 @@ async function spawnNextTrainerOrEndEncounter() { await showEncounterDialogue(`${namespace}:victory`, `${namespace}:speaker`); // Give 10x Voucher - const reward = allRewards.VOUCHER_PREMIUM(); - globalScene.applyReward(reward, {}); + const reward = allRewards[RewardId.VOUCHER_PREMIUM](); + globalScene.applyReward(reward as Reward, {}); globalScene.playSound("item_fanfare"); - await showEncounterText(i18next.t("battle:rewardGain", { modifierName: reward.name })); + await showEncounterText(i18next.t("battle:rewardGain", { modifierName: (reward as Reward).name })); await showEncounterDialogue(`${namespace}:victory_2`, `${namespace}:speaker`); globalScene.ui.clearText(); // Clears "Winstrate" title from screen as allRewards get animated in - const machoBrace = generateRewardOption(allRewards.MYSTERY_ENCOUNTER_MACHO_BRACE)!; + const machoBrace = generateRewardOptionFromId(HeldItemId.MACHO_BRACE)!; machoBrace.type.tier = RarityTier.MASTER; setEncounterRewards({ guaranteedRewardOptions: [machoBrace], diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 9876b47696c..b172748f1ef 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { allRewards, allSpecies } from "#data/data-lists"; +import { allSpecies } from "#data/data-lists"; import { getLevelTotalExp } from "#data/exp"; import type { PokemonSpecies } from "#data/pokemon-species"; import { Challenges } from "#enums/challenges"; @@ -11,6 +11,7 @@ import { Nature } from "#enums/nature"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { PlayerGender } from "#enums/player-gender"; import { PokemonType } from "#enums/pokemon-type"; +import { RewardId } from "#enums/reward-id"; import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; @@ -218,12 +219,12 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit await doNewTeamPostProcess(transformations); setEncounterRewards({ - guaranteedRewardFuncs: [ - allRewards.MEMORY_MUSHROOM, - allRewards.ROGUE_BALL, - allRewards.MINT, - allRewards.MINT, - allRewards.MINT, + guaranteedRewardSpecs: [ + RewardId.MEMORY_MUSHROOM, + RewardId.ROGUE_BALL, + RewardId.MINT, + RewardId.MINT, + RewardId.MINT, ], fillRemaining: false, }); @@ -242,7 +243,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit ], }, async () => { - // Battle your "future" team for some item allRewards + // Battle your "future" team for some item RewardId const transformations: PokemonTransformation[] = globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations; @@ -293,7 +294,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit }; const onBeforeRewards = () => { - // Before battle allRewards, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently) + // Before battle RewardId, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently) // One random pokemon will get its passive unlocked const passiveDisabledPokemon = globalScene.getPlayerParty().filter(p => !p.passive); if (passiveDisabledPokemon?.length > 0) { diff --git a/src/enums/reward-id.ts b/src/enums/reward-id.ts index 145bad98914..f8179828665 100644 --- a/src/enums/reward-id.ts +++ b/src/enums/reward-id.ts @@ -61,9 +61,10 @@ export const RewardId = { TRAINER_ITEM: 0x2D01, TEMP_STAT_STAGE_BOOSTER: 0x2D02, - LURE: 0x2D03, - SUPER_LURE: 0x2D04, - MAX_LURE: 0x2D05, + DIRE_HIT: 0x2D03, + LURE: 0x2D04, + SUPER_LURE: 0x2D05, + MAX_LURE: 0x2D06, FORM_CHANGE_ITEM: 0x2E01, RARE_FORM_CHANGE_ITEM: 0x2E02, diff --git a/src/init/init.ts b/src/init/init.ts index 35896005b98..4f6731213da 100644 --- a/src/init/init.ts +++ b/src/init/init.ts @@ -7,11 +7,11 @@ import { initChallenges } from "#data/challenge"; import { initTrainerTypeDialogue } from "#data/dialogue"; import { initPokemonForms } from "#data/pokemon-forms"; import { initHeldItems } from "#items/all-held-items"; +import { initRewards } from "#items/all-rewards"; import { initTrainerItems } from "#items/all-trainer-items"; import { initHeldItemPools } from "#items/init-held-item-pools"; import { initRewardPools } from "#items/init-reward-pools"; import { initTrainerItemPools } from "#items/init-trainer-item-pools"; -import { initRewards } from "#items/reward"; import { initMoves } from "#moves/move"; import { initMysteryEncounters } from "#mystery-encounters/mystery-encounters"; import { initAchievements } from "#system/achv"; diff --git a/src/items/all-held-items.ts b/src/items/all-held-items.ts index fa3c8d8cebe..d505a28bb52 100644 --- a/src/items/all-held-items.ts +++ b/src/items/all-held-items.ts @@ -1,5 +1,6 @@ import { allHeldItems } from "#data/data-lists"; import { BerryType } from "#enums/berry-type"; +import { HeldItemEffect } from "#enums/held-item-effect"; import { HeldItemId } from "#enums/held-item-id"; import type { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; @@ -36,7 +37,6 @@ import { SurviveChanceHeldItem, type SurviveChanceParams } from "#items/survive- import { TurnEndHealHeldItem, type TurnEndHealParams } from "#items/turn-end-heal"; import { TurnEndStatusHeldItem, type TurnEndStatusParams } from "#items/turn-end-status"; import { getEnumValues } from "#utils/enums"; -import { HeldItemEffect } from "./HeldItemEffect"; export function initHeldItems() { for (const berry of getEnumValues(BerryType)) { diff --git a/src/items/all-rewards.ts b/src/items/all-rewards.ts new file mode 100644 index 00000000000..4ab8def39a1 --- /dev/null +++ b/src/items/all-rewards.ts @@ -0,0 +1,182 @@ +import { allRewards } from "#data/data-lists"; +import { PokeballType } from "#enums/pokeball"; +import { RewardId } from "#enums/reward-id"; +import { RarityTier } from "#enums/reward-tier"; +import { TrainerItemId } from "#enums/trainer-item-id"; +import { VoucherType } from "#system/voucher"; +import { + AddMoneyReward, + AddPokeballReward, + AddVoucherReward, + AllPokemonFullReviveReward, + AllPokemonLevelIncrementReward, + AttackTypeBoosterRewardGenerator, + BaseStatBoosterRewardGenerator, + BerryRewardGenerator, + EvolutionItemRewardGenerator, + FormChangeItemRewardGenerator, + FusePokemonReward, + LapsingTrainerItemReward, + MintRewardGenerator, + PokemonAllMovePpRestoreReward, + PokemonHpRestoreReward, + PokemonLevelIncrementReward, + PokemonPpRestoreReward, + PokemonPpUpReward, + PokemonReviveReward, + PokemonStatusHealReward, + RememberMoveReward, + SpeciesStatBoosterRewardGenerator, + TempStatStageBoosterRewardGenerator, + TeraTypeRewardGenerator, + TmRewardGenerator, +} from "./reward"; + +export function initRewards() { + // Pokeball rewards + allRewards[RewardId.POKEBALL] = () => new AddPokeballReward("pb", PokeballType.POKEBALL, 5, RewardId.POKEBALL); + allRewards[RewardId.GREAT_BALL] = () => new AddPokeballReward("gb", PokeballType.GREAT_BALL, 5, RewardId.GREAT_BALL); + allRewards[RewardId.ULTRA_BALL] = () => new AddPokeballReward("ub", PokeballType.ULTRA_BALL, 5, RewardId.ULTRA_BALL); + allRewards[RewardId.ROGUE_BALL] = () => new AddPokeballReward("rb", PokeballType.ROGUE_BALL, 5, RewardId.ROGUE_BALL); + allRewards[RewardId.MASTER_BALL] = () => + new AddPokeballReward("mb", PokeballType.MASTER_BALL, 1, RewardId.MASTER_BALL); + + // Voucher rewards + allRewards[RewardId.VOUCHER] = () => new AddVoucherReward(VoucherType.REGULAR, 1, RewardId.VOUCHER); + allRewards[RewardId.VOUCHER_PLUS] = () => new AddVoucherReward(VoucherType.PLUS, 1, RewardId.VOUCHER_PLUS); + allRewards[RewardId.VOUCHER_PREMIUM] = () => new AddVoucherReward(VoucherType.PREMIUM, 1, RewardId.VOUCHER_PREMIUM); + + // Money rewards + allRewards[RewardId.NUGGET] = () => + new AddMoneyReward( + "modifierType:ModifierType.NUGGET", + "nugget", + 1, + "modifierType:ModifierType.MoneyRewardModifierType.extra.small", + RewardId.NUGGET, + ); + allRewards[RewardId.BIG_NUGGET] = () => + new AddMoneyReward( + "modifierType:ModifierType.BIG_NUGGET", + "big_nugget", + 2.5, + "modifierType:ModifierType.MoneyRewardModifierType.extra.moderate", + RewardId.BIG_NUGGET, + ); + allRewards[RewardId.RELIC_GOLD] = () => + new AddMoneyReward( + "modifierType:ModifierType.RELIC_GOLD", + "relic_gold", + 10, + "modifierType:ModifierType.MoneyRewardModifierType.extra.large", + RewardId.RELIC_GOLD, + ); + + // Party-wide consumables + allRewards[RewardId.RARER_CANDY] = () => + new AllPokemonLevelIncrementReward("modifierType:ModifierType.RARER_CANDY", "rarer_candy"); + allRewards[RewardId.SACRED_ASH] = () => + new AllPokemonFullReviveReward("modifierType:ModifierType.SACRED_ASH", "sacred_ash"); + + // Pokemon consumables + allRewards[RewardId.RARE_CANDY] = () => + new PokemonLevelIncrementReward("modifierType:ModifierType.RARE_CANDY", "rare_candy"); + + allRewards[RewardId.EVOLUTION_ITEM] = () => new EvolutionItemRewardGenerator(false, RewardId.EVOLUTION_ITEM); + allRewards[RewardId.RARE_EVOLUTION_ITEM] = () => new EvolutionItemRewardGenerator(true, RewardId.RARE_EVOLUTION_ITEM); + + allRewards[RewardId.POTION] = () => + new PokemonHpRestoreReward("modifierType:ModifierType.POTION", "potion", RewardId.POTION, 20, 10); + allRewards[RewardId.SUPER_POTION] = () => + new PokemonHpRestoreReward("modifierType:ModifierType.SUPER_POTION", "super_potion", RewardId.SUPER_POTION, 50, 25); + allRewards[RewardId.HYPER_POTION] = () => + new PokemonHpRestoreReward( + "modifierType:ModifierType.HYPER_POTION", + "hyper_potion", + RewardId.HYPER_POTION, + 200, + 50, + ); + allRewards[RewardId.MAX_POTION] = () => + new PokemonHpRestoreReward("modifierType:ModifierType.MAX_POTION", "max_potion", RewardId.MAX_POTION, 0, 100); + allRewards[RewardId.FULL_RESTORE] = () => + new PokemonHpRestoreReward( + "modifierType:ModifierType.FULL_RESTORE", + "full_restore", + RewardId.FULL_RESTORE, + 0, + 100, + true, + ); + + allRewards[RewardId.REVIVE] = () => + new PokemonReviveReward("modifierType:ModifierType.REVIVE", "revive", RewardId.REVIVE, 50); + allRewards[RewardId.MAX_REVIVE] = () => + new PokemonReviveReward("modifierType:ModifierType.MAX_REVIVE", "max_revive", RewardId.MAX_REVIVE, 100); + + allRewards[RewardId.FULL_HEAL] = () => + new PokemonStatusHealReward("modifierType:ModifierType.FULL_HEAL", "full_heal"); + + allRewards[RewardId.ETHER] = () => + new PokemonPpRestoreReward("modifierType:ModifierType.ETHER", "ether", RewardId.ETHER, 10); + allRewards[RewardId.MAX_ETHER] = () => + new PokemonPpRestoreReward("modifierType:ModifierType.MAX_ETHER", "max_ether", RewardId.MAX_ETHER, -1); + + allRewards[RewardId.ELIXIR] = () => + new PokemonAllMovePpRestoreReward("modifierType:ModifierType.ELIXIR", "elixir", RewardId.ELIXIR, 10); + allRewards[RewardId.MAX_ELIXIR] = () => + new PokemonAllMovePpRestoreReward("modifierType:ModifierType.MAX_ELIXIR", "max_elixir", RewardId.MAX_ELIXIR, -1); + + allRewards[RewardId.PP_UP] = () => + new PokemonPpUpReward("modifierType:ModifierType.PP_UP", "pp_up", RewardId.PP_UP, 1); + allRewards[RewardId.PP_MAX] = () => + new PokemonPpUpReward("modifierType:ModifierType.PP_MAX", "pp_max", RewardId.PP_MAX, 3); + + /*REPEL] = () => new DoubleBattleChanceBoosterReward('Repel', 5), + SUPER_REPEL] = () => new DoubleBattleChanceBoosterReward('Super Repel', 10), + MAX_REPEL] = () => new DoubleBattleChanceBoosterReward('Max Repel', 25),*/ + + allRewards[RewardId.MINT] = () => new MintRewardGenerator(); + + allRewards[RewardId.TERA_SHARD] = () => new TeraTypeRewardGenerator(); + + allRewards[RewardId.TM_COMMON] = () => new TmRewardGenerator(RarityTier.COMMON); + allRewards[RewardId.TM_GREAT] = () => new TmRewardGenerator(RarityTier.GREAT); + allRewards[RewardId.TM_ULTRA] = () => new TmRewardGenerator(RarityTier.ULTRA); + + allRewards[RewardId.MEMORY_MUSHROOM] = () => + new RememberMoveReward("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom"); + + allRewards[RewardId.DNA_SPLICERS] = () => + new FusePokemonReward("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers"); + + // Form change items + allRewards[RewardId.FORM_CHANGE_ITEM] = () => new FormChangeItemRewardGenerator(false, RewardId.FORM_CHANGE_ITEM); + allRewards[RewardId.RARE_FORM_CHANGE_ITEM] = () => + new FormChangeItemRewardGenerator(true, RewardId.RARE_FORM_CHANGE_ITEM); + + // Held items + + allRewards[RewardId.SPECIES_STAT_BOOSTER] = () => new SpeciesStatBoosterRewardGenerator(false); + allRewards[RewardId.RARE_SPECIES_STAT_BOOSTER] = () => new SpeciesStatBoosterRewardGenerator(true); + + allRewards[RewardId.BASE_STAT_BOOSTER] = () => new BaseStatBoosterRewardGenerator(); + + allRewards[RewardId.ATTACK_TYPE_BOOSTER] = () => new AttackTypeBoosterRewardGenerator(); + + allRewards[RewardId.BERRY] = () => new BerryRewardGenerator(); + + // MINI_BLACK_HOLE] = () => new HeldItemReward(HeldItemId.MINI_BLACK_HOLE), + + // Trainer items + + allRewards[RewardId.LURE] = () => new LapsingTrainerItemReward(TrainerItemId.LURE, RewardId.LURE); + allRewards[RewardId.SUPER_LURE] = () => new LapsingTrainerItemReward(TrainerItemId.SUPER_LURE, RewardId.SUPER_LURE); + allRewards[RewardId.MAX_LURE] = () => new LapsingTrainerItemReward(TrainerItemId.MAX_LURE, RewardId.MAX_LURE); + + allRewards[RewardId.TEMP_STAT_STAGE_BOOSTER] = () => new TempStatStageBoosterRewardGenerator(); + + allRewards[RewardId.DIRE_HIT] = () => + new LapsingTrainerItemReward(TrainerItemId.DIRE_HIT, RewardId.TEMP_STAT_STAGE_BOOSTER); + // GOLDEN_POKEBALL] = () => new TrainerItemReward(TrainerItemId.GOLDEN_POKEBALL), +} diff --git a/src/items/init-reward-pools.ts b/src/items/init-reward-pools.ts index ec1f8ee50ff..0517e4421cd 100644 --- a/src/items/init-reward-pools.ts +++ b/src/items/init-reward-pools.ts @@ -1,23 +1,23 @@ /* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ -import type { initRewards, Reward } from "#items/reward"; +import { initRewards, Reward } from "#items/reward"; /* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ import { timedEventManager } from "#app/global-event-manager"; import { globalScene } from "#app/global-scene"; import { pokemonEvolutions } from "#balance/pokemon-evolutions"; -import { allHeldItems, allRewards, allTrainerItems } from "#data/data-lists"; +import { allHeldItems, allTrainerItems } from "#data/data-lists"; import { MAX_PER_TYPE_POKEBALLS } from "#data/pokeball"; import { AbilityId } from "#enums/ability-id"; import { HeldItemId } from "#enums/held-item-id"; import { MoveId } from "#enums/move-id"; import { PokeballType } from "#enums/pokeball"; +import { RewardId } from "#enums/reward-id"; import { RarityTier } from "#enums/reward-tier"; import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; import { TrainerItemId } from "#enums/trainer-item-id"; import { Unlockables } from "#enums/unlockables"; import type { Pokemon } from "#field/pokemon"; -import { WeightedReward } from "#items/reward"; import { rewardPool } from "#items/reward-pools"; import type { TurnEndStatusHeldItem } from "#items/turn-end-status"; import type { WeightedRewardWeightFunc } from "#types/rewards"; @@ -28,33 +28,33 @@ import { isNullOrUndefined } from "#utils/common"; */ function initCommonRewardPool() { rewardPool[RarityTier.COMMON] = [ - new WeightedReward(allRewards.POKEBALL, () => (hasMaximumBalls(PokeballType.POKEBALL) ? 0 : 6), 6), - new WeightedReward(allRewards.RARE_CANDY, 2), - new WeightedReward( - allRewards.POTION, - (party: Pokemon[]) => { + { id: RewardId.POKEBALL, weight: () => (hasMaximumBalls(PokeballType.POKEBALL) ? 0 : 6), maxWeight: 6 }, + { id: RewardId.RARE_CANDY, weight: 2 }, + { + id: RewardId.POTION, + weight: (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter(p => p.getInverseHp() >= 10 && p.getHpRatio() <= 0.875 && !p.isFainted()).length, 3, ); return thresholdPartyMemberCount * 3; }, - 9, - ), - new WeightedReward( - allRewards.SUPER_POTION, - (party: Pokemon[]) => { + maxWeight: 9, + }, + { + id: RewardId.SUPER_POTION, + weight: (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter(p => p.getInverseHp() >= 25 && p.getHpRatio() <= 0.75 && !p.isFainted()).length, 3, ); return thresholdPartyMemberCount; }, - 3, - ), - new WeightedReward( - allRewards.ETHER, - (party: Pokemon[]) => { + maxWeight: 3, + }, + { + id: RewardId.ETHER, + weight: (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter( p => @@ -69,11 +69,11 @@ function initCommonRewardPool() { ); return thresholdPartyMemberCount * 3; }, - 9, - ), - new WeightedReward( - allRewards.MAX_ETHER, - (party: Pokemon[]) => { + maxWeight: 9, + }, + { + id: RewardId.MAX_ETHER, + weight: (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter( p => @@ -88,12 +88,12 @@ function initCommonRewardPool() { ); return thresholdPartyMemberCount; }, - 3, - ), - new WeightedReward(allRewards.LURE, lureWeightFunc(TrainerItemId.LURE, 2)), - new WeightedReward(allRewards.TEMP_STAT_STAGE_BOOSTER, 4), - new WeightedReward(allRewards.BERRY, 2), - new WeightedReward(allRewards.TM_COMMON, 2), + maxWeight: 3, + }, + { id: RewardId.LURE, weight: lureWeightFunc(TrainerItemId.LURE, 2) }, + { id: RewardId.TEMP_STAT_STAGE_BOOSTER, weight: 4 }, + { id: RewardId.BERRY, weight: 2 }, + { id: RewardId.TM_COMMON, weight: 2 }, ]; } @@ -102,11 +102,11 @@ function initCommonRewardPool() { */ function initGreatRewardPool() { rewardPool[RarityTier.GREAT] = [ - new WeightedReward(allRewards.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6), - new WeightedReward(allRewards.PP_UP, 2), - new WeightedReward( - allRewards.FULL_HEAL, - (party: Pokemon[]) => { + { id: RewardId.GREAT_BALL, weight: () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), maxWeight: 6 }, + { id: RewardId.PP_UP, weight: 2 }, + { + id: RewardId.FULL_HEAL, + weight: (party: Pokemon[]) => { const statusEffectPartyMemberCount = Math.min( party.filter( p => @@ -121,56 +121,56 @@ function initGreatRewardPool() { ); return statusEffectPartyMemberCount * 6; }, - 18, - ), - new WeightedReward( - allRewards.REVIVE, - (party: Pokemon[]) => { + maxWeight: 18, + }, + { + id: RewardId.REVIVE, + weight: (party: Pokemon[]) => { const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); return faintedPartyMemberCount * 9; }, - 27, - ), - new WeightedReward( - allRewards.MAX_REVIVE, - (party: Pokemon[]) => { + maxWeight: 27, + }, + { + id: RewardId.MAX_REVIVE, + weight: (party: Pokemon[]) => { const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3); return faintedPartyMemberCount * 3; }, - 9, - ), - new WeightedReward( - allRewards.SACRED_ASH, - (party: Pokemon[]) => { + maxWeight: 9, + }, + { + id: RewardId.SACRED_ASH, + weight: (party: Pokemon[]) => { return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0; }, - 1, - ), - new WeightedReward( - allRewards.HYPER_POTION, - (party: Pokemon[]) => { + maxWeight: 1, + }, + { + id: RewardId.HYPER_POTION, + weight: (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.625 && !p.isFainted()).length, 3, ); return thresholdPartyMemberCount * 3; }, - 9, - ), - new WeightedReward( - allRewards.MAX_POTION, - (party: Pokemon[]) => { + maxWeight: 9, + }, + { + id: RewardId.MAX_POTION, + weight: (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length, 3, ); return thresholdPartyMemberCount; }, - 3, - ), - new WeightedReward( - allRewards.FULL_RESTORE, - (party: Pokemon[]) => { + maxWeight: 3, + }, + { + id: RewardId.FULL_RESTORE, + weight: (party: Pokemon[]) => { const statusEffectPartyMemberCount = Math.min( party.filter( p => @@ -190,11 +190,11 @@ function initGreatRewardPool() { ); return thresholdPartyMemberCount; }, - 3, - ), - new WeightedReward( - allRewards.ELIXIR, - (party: Pokemon[]) => { + maxWeight: 3, + }, + { + id: RewardId.ELIXIR, + weight: (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter( p => @@ -209,11 +209,11 @@ function initGreatRewardPool() { ); return thresholdPartyMemberCount * 3; }, - 9, - ), - new WeightedReward( - allRewards.MAX_ELIXIR, - (party: Pokemon[]) => { + maxWeight: 9, + }, + { + id: RewardId.MAX_ELIXIR, + weight: (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min( party.filter( p => @@ -228,29 +228,29 @@ function initGreatRewardPool() { ); return thresholdPartyMemberCount; }, - 3, - ), - new WeightedReward(allRewards.DIRE_HIT, 4), - new WeightedReward(allRewards.SUPER_LURE, lureWeightFunc(TrainerItemId.SUPER_LURE, 4)), - new WeightedReward(allRewards.NUGGET, skipInLastClassicWaveOrDefault(5)), - new WeightedReward(allRewards.SPECIES_STAT_BOOSTER, 4), - new WeightedReward( - allRewards.EVOLUTION_ITEM, - () => { + maxWeight: 3, + }, + { id: RewardId.DIRE_HIT, weight: 4 }, + { id: RewardId.SUPER_LURE, weight: lureWeightFunc(TrainerItemId.SUPER_LURE, 4) }, + { id: RewardId.NUGGET, weight: skipInLastClassicWaveOrDefault(5) }, + { id: RewardId.SPECIES_STAT_BOOSTER, weight: 4 }, + { + id: RewardId.EVOLUTION_ITEM, + weight: () => { return Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15), 8); }, - 8, - ), - new WeightedReward( - allRewards.MAP, - () => (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex < 180 ? 2 : 0), - 2, - ), - new WeightedReward(allRewards.SOOTHE_BELL, 2), - new WeightedReward(allRewards.TM_GREAT, 3), - new WeightedReward( - allRewards.MEMORY_MUSHROOM, - (party: Pokemon[]) => { + maxWeight: 8, + }, + { + id: TrainerItemId.MAP, + weight: () => (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex < 180 ? 2 : 0), + maxWeight: 2, + }, + { id: HeldItemId.SOOTHE_BELL, weight: 2 }, + { id: RewardId.TM_GREAT, weight: 3 }, + { + id: RewardId.MEMORY_MUSHROOM, + weight: (party: Pokemon[]) => { if (!party.find(p => p.getLearnableLevelMoves().length)) { return 0; } @@ -259,20 +259,22 @@ function initGreatRewardPool() { .reduce((highestLevel: number, level: number) => Math.max(highestLevel, level), 1); return Math.min(Math.ceil(highestPartyLevel / 20), 4); }, - 4, - ), - new WeightedReward(allRewards.BASE_STAT_BOOSTER, 3), - new WeightedReward(allRewards.TERA_SHARD, (party: Pokemon[]) => - party.filter( - p => - !(p.hasSpecies(SpeciesId.TERAPAGOS) || p.hasSpecies(SpeciesId.OGERPON) || p.hasSpecies(SpeciesId.SHEDINJA)), - ).length > 0 - ? 1 - : 0, - ), - new WeightedReward( - allRewards.DNA_SPLICERS, - (party: Pokemon[]) => { + maxWeight: 4, + }, + { id: RewardId.BASE_STAT_BOOSTER, weight: 3 }, + { + id: RewardId.TERA_SHARD, + weight: (party: Pokemon[]) => + party.filter( + p => + !(p.hasSpecies(SpeciesId.TERAPAGOS) || p.hasSpecies(SpeciesId.OGERPON) || p.hasSpecies(SpeciesId.SHEDINJA)), + ).length > 0 + ? 1 + : 0, + }, + { + id: RewardId.DNA_SPLICERS, + weight: (party: Pokemon[]) => { if (party.filter(p => !p.fusionSpecies).length > 1) { if (globalScene.gameMode.isSplicedOnly) { return 4; @@ -283,13 +285,14 @@ function initGreatRewardPool() { } return 0; }, - 4, - ), - new WeightedReward( - allRewards.VOUCHER, - (_party: Pokemon[], rerollCount: number) => (!globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0), - 1, - ), + maxWeight: 4, + }, + { + id: RewardId.VOUCHER, + weight: (_party: Pokemon[], rerollCount: number) => + !globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0, + maxWeight: 1, + }, ]; } @@ -298,46 +301,49 @@ function initGreatRewardPool() { */ function initUltraRewardPool() { rewardPool[RarityTier.ULTRA] = [ - new WeightedReward(allRewards.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15), - new WeightedReward(allRewards.MAX_LURE, lureWeightFunc(TrainerItemId.MAX_LURE, 4)), - new WeightedReward(allRewards.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), - new WeightedReward(allRewards.PP_MAX, 3), - new WeightedReward(allRewards.MINT, 4), - new WeightedReward( - allRewards.RARE_EVOLUTION_ITEM, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15) * 4, 32), - 32, - ), - new WeightedReward( - allRewards.FORM_CHANGE_ITEM, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, - 24, - ), - new WeightedReward(allRewards.AMULET_COIN, skipInLastClassicWaveOrDefault(3)), - new WeightedReward(allRewards.EVIOLITE, (party: Pokemon[]) => { - const { gameMode, gameData } = globalScene; - if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) { - return party.some(p => { - // Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd - if ( - !p.isMax() && - (p.getSpeciesForm(true).speciesId in pokemonEvolutions || - (p.isFusion() && p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions)) - ) { - // Check if Pokemon is already holding an Eviolite - return !p.heldItemManager.hasItem(HeldItemId.EVIOLITE); - } - return false; - }) - ? 10 - : 0; - } - return 0; - }), - new WeightedReward(allRewards.RARE_SPECIES_STAT_BOOSTER, 12), - new WeightedReward( - allRewards.LEEK, - (party: Pokemon[]) => { + { id: RewardId.ULTRA_BALL, weight: () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), maxWeight: 15 }, + { id: RewardId.MAX_LURE, weight: lureWeightFunc(TrainerItemId.MAX_LURE, 4) }, + { id: RewardId.BIG_NUGGET, weight: skipInLastClassicWaveOrDefault(12) }, + { id: RewardId.PP_MAX, weight: 3 }, + { id: RewardId.MINT, weight: 4 }, + { + id: RewardId.RARE_EVOLUTION_ITEM, + weight: () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15) * 4, 32), + maxWeight: 32, + }, + { + id: RewardId.FORM_CHANGE_ITEM, + weight: () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, + maxWeight: 24, + }, + { id: TrainerItemId.AMULET_COIN, weight: skipInLastClassicWaveOrDefault(3) }, + { + id: HeldItemId.EVIOLITE, + weight: (party: Pokemon[]) => { + const { gameMode, gameData } = globalScene; + if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) { + return party.some(p => { + // Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd + if ( + !p.isMax() && + (p.getSpeciesForm(true).speciesId in pokemonEvolutions || + (p.isFusion() && p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions)) + ) { + // Check if Pokemon is already holding an Eviolite + return !p.heldItemManager.hasItem(HeldItemId.EVIOLITE); + } + return false; + }) + ? 10 + : 0; + } + return 0; + }, + }, + { id: RewardId.RARE_SPECIES_STAT_BOOSTER, weight: 12 }, + { + id: HeldItemId.LEEK, + weight: (party: Pokemon[]) => { const checkedSpecies = [SpeciesId.FARFETCHD, SpeciesId.GALAR_FARFETCHD, SpeciesId.SIRFETCHD]; // If a party member doesn't already have a Leek and is one of the relevant species, Leek can appear return party.some( @@ -349,11 +355,11 @@ function initUltraRewardPool() { ? 12 : 0; }, - 12, - ), - new WeightedReward( - allRewards.TOXIC_ORB, - (party: Pokemon[]) => { + maxWeight: 12, + }, + { + id: HeldItemId.TOXIC_ORB, + weight: (party: Pokemon[]) => { return party.some(p => { const isHoldingOrb = p.getHeldItems().some(i => i in [HeldItemId.FLAME_ORB, HeldItemId.TOXIC_ORB]); @@ -395,11 +401,11 @@ function initUltraRewardPool() { ? 10 : 0; }, - 10, - ), - new WeightedReward( - allRewards.FLAME_ORB, - (party: Pokemon[]) => { + maxWeight: 10, + }, + { + id: HeldItemId.FLAME_ORB, + weight: (party: Pokemon[]) => { return party.some(p => { const isHoldingOrb = p.getHeldItems().some(i => i in [HeldItemId.FLAME_ORB, HeldItemId.TOXIC_ORB]); @@ -441,11 +447,11 @@ function initUltraRewardPool() { ? 10 : 0; }, - 10, - ), - new WeightedReward( - allRewards.MYSTICAL_ROCK, - (party: Pokemon[]) => { + maxWeight: 10, + }, + { + id: HeldItemId.MYSTICAL_ROCK, + weight: (party: Pokemon[]) => { return party.some(p => { const stack = p.heldItemManager.getStack(HeldItemId.MYSTICAL_ROCK); const isHoldingMax = stack === allHeldItems[HeldItemId.MYSTICAL_ROCK].maxStackCount; @@ -488,68 +494,68 @@ function initUltraRewardPool() { ? 10 : 0; }, - 10, - ), - new WeightedReward(allRewards.REVIVER_SEED, 4), - new WeightedReward(allRewards.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), - new WeightedReward(allRewards.ATTACK_TYPE_BOOSTER, 9), - new WeightedReward(allRewards.TM_ULTRA, 11), - new WeightedReward(allRewards.RARER_CANDY, 4), - new WeightedReward(allRewards.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), - new WeightedReward(allRewards.IV_SCANNER, skipInLastClassicWaveOrDefault(4)), - new WeightedReward(allRewards.EXP_CHARM, skipInLastClassicWaveOrDefault(8)), - new WeightedReward(allRewards.EXP_SHARE, skipInLastClassicWaveOrDefault(10)), - new WeightedReward( - allRewards.TERA_ORB, - () => + maxWeight: 10, + }, + { id: HeldItemId.REVIVER_SEED, weight: 4 }, + { id: TrainerItemId.CANDY_JAR, weight: skipInLastClassicWaveOrDefault(5) }, + { id: RewardId.ATTACK_TYPE_BOOSTER, weight: 9 }, + { id: RewardId.TM_ULTRA, weight: 11 }, + { id: RewardId.RARER_CANDY, weight: 4 }, + { id: HeldItemId.GOLDEN_PUNCH, weight: skipInLastClassicWaveOrDefault(2) }, + { id: TrainerItemId.IV_SCANNER, weight: skipInLastClassicWaveOrDefault(4) }, + { id: TrainerItemId.EXP_CHARM, weight: skipInLastClassicWaveOrDefault(8) }, + { id: TrainerItemId.EXP_SHARE, weight: skipInLastClassicWaveOrDefault(10) }, + { + id: TrainerItemId.TERA_ORB, + weight: () => !globalScene.gameMode.isClassic ? Math.min(Math.max(Math.floor(globalScene.currentBattle.waveIndex / 50) * 2, 1), 4) : 0, - 4, - ), - new WeightedReward(allRewards.QUICK_CLAW, 3), - new WeightedReward(allRewards.WIDE_LENS, 7), + maxWeight: 4, + }, + { id: HeldItemId.QUICK_CLAW, weight: 3 }, + { id: HeldItemId.WIDE_LENS, weight: 7 }, ]; } function initRogueRewardPool() { rewardPool[RarityTier.ROGUE] = [ - new WeightedReward(allRewards.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16), - new WeightedReward(allRewards.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), - new WeightedReward(allRewards.LEFTOVERS, 3), - new WeightedReward(allRewards.SHELL_BELL, 3), - new WeightedReward(allRewards.BERRY_POUCH, 4), - new WeightedReward(allRewards.GRIP_CLAW, 5), - new WeightedReward(allRewards.SCOPE_LENS, 4), - new WeightedReward(allRewards.BATON, 2), - new WeightedReward(allRewards.SOUL_DEW, 7), - new WeightedReward(allRewards.CATCHING_CHARM, () => (!globalScene.gameMode.isClassic ? 4 : 0), 4), - new WeightedReward(allRewards.ABILITY_CHARM, skipInClassicAfterWave(189, 6)), - new WeightedReward(allRewards.FOCUS_BAND, 5), - new WeightedReward(allRewards.KINGS_ROCK, 3), - new WeightedReward(allRewards.LOCK_CAPSULE, () => (globalScene.gameMode.isClassic ? 0 : 3)), - new WeightedReward(allRewards.SUPER_EXP_CHARM, skipInLastClassicWaveOrDefault(8)), - new WeightedReward( - allRewards.RARE_FORM_CHANGE_ITEM, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, - 24, - ), - new WeightedReward( - allRewards.MEGA_BRACELET, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, - 36, - ), - new WeightedReward( - allRewards.DYNAMAX_BAND, - () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, - 36, - ), - new WeightedReward( - allRewards.VOUCHER_PLUS, - (_party: Pokemon[], rerollCount: number) => + { id: RewardId.ROGUE_BALL, weight: () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), maxWeight: 16 }, + { id: RewardId.RELIC_GOLD, weight: skipInLastClassicWaveOrDefault(2) }, + { id: HeldItemId.LEFTOVERS, weight: 3 }, + { id: HeldItemId.SHELL_BELL, weight: 3 }, + { id: TrainerItemId.BERRY_POUCH, weight: 4 }, + { id: HeldItemId.GRIP_CLAW, weight: 5 }, + { id: HeldItemId.SCOPE_LENS, weight: 4 }, + { id: HeldItemId.BATON, weight: 2 }, + { id: HeldItemId.SOUL_DEW, weight: 7 }, + { id: TrainerItemId.CATCHING_CHARM, weight: () => (!globalScene.gameMode.isClassic ? 4 : 0), maxWeight: 4 }, + { id: TrainerItemId.ABILITY_CHARM, weight: skipInClassicAfterWave(189, 6) }, + { id: HeldItemId.FOCUS_BAND, weight: 5 }, + { id: HeldItemId.KINGS_ROCK, weight: 3 }, + { id: TrainerItemId.LOCK_CAPSULE, weight: () => (globalScene.gameMode.isClassic ? 0 : 3) }, + { id: TrainerItemId.SUPER_EXP_CHARM, weight: skipInLastClassicWaveOrDefault(8) }, + { + id: RewardId.RARE_FORM_CHANGE_ITEM, + weight: () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, + maxWeight: 24, + }, + { + id: TrainerItemId.MEGA_BRACELET, + weight: () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, + maxWeight: 36, + }, + { + id: TrainerItemId.DYNAMAX_BAND, + weight: () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, + maxWeight: 36, + }, + { + id: RewardId.VOUCHER_PLUS, + weight: (_party: Pokemon[], rerollCount: number) => !globalScene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0, - 3, - ), + maxWeight: 3, + }, ]; } @@ -558,37 +564,37 @@ function initRogueRewardPool() { */ function initMasterRewardPool() { rewardPool[RarityTier.MASTER] = [ - new WeightedReward(allRewards.MASTER_BALL, () => (hasMaximumBalls(PokeballType.MASTER_BALL) ? 0 : 24), 24), - new WeightedReward(allRewards.SHINY_CHARM, 14), - new WeightedReward(allRewards.HEALING_CHARM, 18), - new WeightedReward(allRewards.MULTI_LENS, 18), - new WeightedReward( - allRewards.VOUCHER_PREMIUM, - (_party: Pokemon[], rerollCount: number) => + { id: RewardId.MASTER_BALL, weight: () => (hasMaximumBalls(PokeballType.MASTER_BALL) ? 0 : 24), maxWeight: 24 }, + { id: TrainerItemId.SHINY_CHARM, weight: 14 }, + { id: TrainerItemId.HEALING_CHARM, weight: 18 }, + { id: HeldItemId.MULTI_LENS, weight: 18 }, + { + id: RewardId.VOUCHER_PREMIUM, + weight: (_party: Pokemon[], rerollCount: number) => !globalScene.gameMode.isDaily && !globalScene.gameMode.isEndless && !globalScene.gameMode.isSplicedOnly ? Math.max(5 - rerollCount * 2, 0) : 0, - 5, - ), - new WeightedReward( - allRewards.DNA_SPLICERS, - (party: Pokemon[]) => + maxWeight: 5, + }, + { + id: RewardId.DNA_SPLICERS, + weight: (party: Pokemon[]) => !(globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) && !globalScene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 24 : 0, - 24, - ), - new WeightedReward( - allRewards.MINI_BLACK_HOLE, - () => + maxWeight: 24, + }, + { + id: HeldItemId.MINI_BLACK_HOLE, + weight: () => globalScene.gameMode.isDaily || (!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE)) ? 1 : 0, - 1, - ), + maxWeight: 1, + }, ]; } diff --git a/src/items/reward-pool-utils.ts b/src/items/reward-pool-utils.ts index 7a9886a76f2..4ad5df55b9a 100644 --- a/src/items/reward-pool-utils.ts +++ b/src/items/reward-pool-utils.ts @@ -1,15 +1,14 @@ import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; -import { allRewards } from "#data/data-lists"; import { RewardPoolType } from "#enums/reward-pool-type"; import { RarityTier } from "#enums/reward-tier"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; -import type { RewardFunc, RewardPool, RewardPoolWeights } from "#types/rewards"; +import type { RewardPool, RewardPoolWeights, RewardSpecs } from "#types/rewards"; import { isNullOrUndefined, pickWeightedIndex, randSeedInt } from "#utils/common"; import { getPartyLuckValue } from "#utils/party"; -import { type Reward, RewardGenerator, RewardOption, type RewardOverride, TrainerItemReward } from "./reward"; +import type { RewardOption } from "./reward"; import { rewardPool, rewardPoolWeights } from "./reward-pools"; -import { getRewardDefaultTier } from "./reward-utils"; +import { generateRewardOptionFromId, generateRewardOptionFromSpecs, isTrainerItemId } from "./reward-utils"; /* This file still contains several functions to generate rewards from pools. The hierarchy of these functions is explained here. @@ -26,9 +25,6 @@ cases to assign modifiers. This usage is now deprecated, as we have separate poo However, `getNewRewardOption` is not called directly by `generatePlayerRewardOptions`. Instead, it is filtered by `getRewardOptionWithRetry`, which also checks existing rewards to minimize the chance of duplicates. -Note that the pool contains `WeightedReward` instances, which contain either a `Reward` or a `RewardGenerator`. -Once a pool entry is chosen, a specific `Reward` is generated accordingly and put in the returned `RewardOption`. - This will allow more customization in creating pools for challenges, MEs etc. */ @@ -36,7 +32,7 @@ export interface CustomRewardSettings { guaranteedRarityTiers?: RarityTier[]; guaranteedRewardOptions?: RewardOption[]; /** If specified, will override the next X items to be auto-generated from specific reward functions (these don't have to be pre-genned). */ - guaranteedRewardFuncs?: RewardFunc[]; + guaranteedRewardSpecs?: RewardSpecs[]; /** * If set to `true`, will fill the remainder of shop items that were not overridden by the 3 options above, up to the `count` param value. * @example @@ -68,9 +64,8 @@ export interface CustomRewardSettings { export function generateRewardPoolWeights(pool: RewardPool, party: Pokemon[], rerollCount = 0) { for (const tier of Object.keys(pool)) { const poolWeights = pool[tier].map(w => { - if (w.reward instanceof TrainerItemReward) { - const id = w.reward.itemId; - if (globalScene.trainerItems.isMaxStack(id)) { + if (isTrainerItemId(w.id)) { + if (globalScene.trainerItems.isMaxStack(w.id)) { return 0; } } @@ -158,26 +153,18 @@ export function generatePlayerRewardOptions( options.push(getRewardOptionWithRetry(pool, weights, options, retryCount, party, tier)); } } else { - // Guaranteed mod options first + // Guaranteed reward options first if (customRewardSettings?.guaranteedRewardOptions && customRewardSettings.guaranteedRewardOptions.length > 0) { options.push(...customRewardSettings.guaranteedRewardOptions!); } - // Guaranteed mod functions second - if (customRewardSettings.guaranteedRewardFuncs && customRewardSettings.guaranteedRewardFuncs.length > 0) { - customRewardSettings.guaranteedRewardFuncs!.forEach((mod, _i) => { - const rewardId = Object.keys(allRewards).find(k => allRewards[k] === mod) as string; - const guaranteedMod: Reward = allRewards[rewardId]?.(); - - // Populates item id and tier - const guaranteedModTier = getRewardDefaultTier(guaranteedMod); - - const modType = guaranteedMod instanceof RewardGenerator ? guaranteedMod.generateReward(party) : guaranteedMod; - if (modType) { - const option = new RewardOption(modType, 0, guaranteedModTier); - options.push(option); + if (customRewardSettings?.guaranteedRewardSpecs && customRewardSettings.guaranteedRewardSpecs.length > 0) { + for (const specs of customRewardSettings.guaranteedRewardSpecs) { + const rewardOption = generateRewardOptionFromSpecs(specs); + if (rewardOption) { + options.push(rewardOption); } - }); + } } // Guaranteed tiers third @@ -285,42 +272,30 @@ function getNewRewardOption( return null; } - let reward: Reward | RewardGenerator | null = pool[tier][index].reward; - if (reward instanceof RewardGenerator) { - reward = (reward as RewardGenerator).generateReward(party); - if (reward === null) { - console.log(RarityTier[tier], upgradeCount); - return getNewRewardOption(pool, weights, party, tier, upgradeCount, ++retryCount); - } + const rewardOption = generateRewardOptionFromId(pool[tier][index].id, 0, tier, upgradeCount); + if (rewardOption === null) { + console.log(RarityTier[tier], upgradeCount); + return getNewRewardOption(pool, weights, party, tier, upgradeCount, ++retryCount); } - console.log(reward); + console.log(rewardOption); - return new RewardOption(reward as Reward, upgradeCount!, tier); // TODO: is this bang correct? + return rewardOption; } /** * Replaces the {@linkcode Reward} of the entries within {@linkcode options} with any - * {@linkcode RewardOverride} entries listed in {@linkcode Overrides.REWARD_OVERRIDE} * up to the smallest amount of entries between {@linkcode options} and the override array. * @param options Array of naturally rolled {@linkcode RewardOption}s * @param party Array of the player's current party */ -export function overridePlayerRewardOptions(options: RewardOption[], party: PlayerPokemon[]) { +export function overridePlayerRewardOptions(options: RewardOption[]) { const minLength = Math.min(options.length, Overrides.REWARD_OVERRIDE.length); for (let i = 0; i < minLength; i++) { - const override: RewardOverride = Overrides.REWARD_OVERRIDE[i]; - const rewardFunc = allRewards[override.name]; - let reward: Reward | RewardGenerator | null = rewardFunc(); - - if (reward instanceof RewardGenerator) { - const pregenArgs = "type" in override && override.type !== null ? [override.type] : undefined; - reward = reward.generateReward(party, pregenArgs); - } - - if (reward) { - options[i].type = reward; - options[i].tier = getRewardDefaultTier(reward); + const specs: RewardSpecs = Overrides.REWARD_OVERRIDE[i]; + const rewardOption = generateRewardOptionFromSpecs(specs); + if (rewardOption) { + options[i] = rewardOption; } } } diff --git a/src/items/reward-utils.ts b/src/items/reward-utils.ts index f9872738fe9..7f229b65c94 100644 --- a/src/items/reward-utils.ts +++ b/src/items/reward-utils.ts @@ -4,20 +4,20 @@ import type { HeldItemId } from "#enums/held-item-id"; import { getRewardCategory, RewardCategoryId, RewardId } from "#enums/reward-id"; import type { RarityTier } from "#enums/reward-tier"; import type { TrainerItemId } from "#enums/trainer-item-id"; -import type { RewardFunc, RewardPoolId } from "#types/rewards"; -import { getHeldItemTier } from "./held-item-default-tiers"; +import type { RewardFunc, RewardPoolId, RewardSpecs } from "#types/rewards"; +import { heldItemRarities } from "./held-item-default-tiers"; import { - type HeldItemReward, + HeldItemReward, type PokemonMoveReward, type RememberMoveReward, type Reward, RewardGenerator, RewardOption, type TmReward, - type TrainerItemReward, + TrainerItemReward, } from "./reward"; -import { getRewardTier } from "./reward-defaults-tiers"; -import { getTrainerItemTier } from "./trainer-item-default-tiers"; +import { rewardRarities } from "./reward-defaults-tiers"; +import { trainerItemRarities } from "./trainer-item-default-tiers"; export function isTmReward(reward: Reward): reward is TmReward { return getRewardCategory(reward.id) === RewardCategoryId.TM; @@ -42,33 +42,43 @@ export function generateReward(rewardFunc: RewardFunc, pregenArgs?: any[]): Rewa return reward instanceof RewardGenerator ? reward.generateReward(globalScene.getPlayerParty(), pregenArgs) : reward; } -/** - * Generates a Reward Option from a given function - * @param rewardFunc - * @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. - */ -export function generateRewardOption(rewardFunc: RewardFunc, pregenArgs?: any[]): RewardOption | null { +export function generateRewardOptionFromId( + id: RewardPoolId, + cost = 0, + tierOverride?: RarityTier, + upgradeCount = 0, + pregenArgs?: any[], +): RewardOption | null { + if (isHeldItemId(id)) { + const reward = new HeldItemReward(id); + const tier = tierOverride ?? heldItemRarities[id]; + return new RewardOption(reward, upgradeCount, tier, cost); + } + + if (isTrainerItemId(id)) { + const reward = new TrainerItemReward(id); + const tier = tierOverride ?? trainerItemRarities[id]; + return new RewardOption(reward, upgradeCount, tier, cost); + } + + const rewardFunc = allRewards[id]; const reward = generateReward(rewardFunc, pregenArgs); if (reward) { - const tier = getRewardDefaultTier(reward); - return new RewardOption(reward, 0, tier); + const tier = tierOverride ?? rewardRarities[id]; + return new RewardOption(reward, upgradeCount, tier, cost); } return null; } -/** - * Finds the default rarity tier for a given reward. For unique held item or trainer item rewards, - * falls back to the default rarity tier for the item. - * @param reward The {@linkcode Reward} to determine the tier for. - */ -export function getRewardDefaultTier(reward: Reward): RarityTier { - if (reward.id === RewardId.HELD_ITEM) { - return getHeldItemTier((reward as HeldItemReward).itemId); +export function generateRewardOptionFromSpecs( + specs: RewardSpecs, + cost = 0, + overrideTier?: RarityTier, +): RewardOption | null { + if (typeof specs === "number") { + return generateRewardOptionFromId(specs, cost, overrideTier); } - if (reward.id === RewardId.TRAINER_ITEM) { - return getTrainerItemTier((reward as TrainerItemReward).itemId); - } - return getRewardTier(reward.id); + return generateRewardOptionFromId(specs.id, cost, overrideTier, 0, specs.args); } export function getPlayerShopRewardOptionsForWave(waveIndex: number, baseCost: number): RewardOption[] { @@ -78,26 +88,26 @@ export function getPlayerShopRewardOptionsForWave(waveIndex: number, baseCost: n const options = [ [ - new RewardOption(allRewards.POTION(), 0, baseCost * 0.2), - new RewardOption(allRewards.ETHER(), 0, baseCost * 0.4), - new RewardOption(allRewards.REVIVE(), 0, baseCost * 2), + generateRewardOptionFromId(RewardId.POTION, baseCost * 0.2), + generateRewardOptionFromId(RewardId.ETHER, baseCost * 0.4), + generateRewardOptionFromId(RewardId.REVIVE, baseCost * 2), ], [ - new RewardOption(allRewards.SUPER_POTION(), 0, baseCost * 0.45), - new RewardOption(allRewards.FULL_HEAL(), 0, baseCost), + generateRewardOptionFromId(RewardId.SUPER_POTION, baseCost * 0.45), + generateRewardOptionFromId(RewardId.FULL_HEAL, baseCost), ], - [new RewardOption(allRewards.ELIXIR(), 0, baseCost), new RewardOption(allRewards.MAX_ETHER(), 0, baseCost)], + [generateRewardOptionFromId(RewardId.ELIXIR, baseCost), generateRewardOptionFromId(RewardId.MAX_ETHER, baseCost)], [ - new RewardOption(allRewards.HYPER_POTION(), 0, baseCost * 0.8), - new RewardOption(allRewards.MAX_REVIVE(), 0, baseCost * 2.75), - new RewardOption(allRewards.MEMORY_MUSHROOM(), 0, baseCost * 4), + generateRewardOptionFromId(RewardId.HYPER_POTION, baseCost * 0.8), + generateRewardOptionFromId(RewardId.MAX_REVIVE, baseCost * 2.75), + generateRewardOptionFromId(RewardId.MEMORY_MUSHROOM, baseCost * 4), ], [ - new RewardOption(allRewards.MAX_POTION(), 0, baseCost * 1.5), - new RewardOption(allRewards.MAX_ELIXIR(), 0, baseCost * 2.5), + generateRewardOptionFromId(RewardId.MAX_POTION, baseCost * 1.5), + generateRewardOptionFromId(RewardId.MAX_ELIXIR, baseCost * 2.5), ], - [new RewardOption(allRewards.FULL_RESTORE(), 0, baseCost * 2.25)], - [new RewardOption(allRewards.SACRED_ASH(), 0, baseCost * 10)], + [generateRewardOptionFromId(RewardId.FULL_RESTORE, baseCost * 2.25)], + [generateRewardOptionFromId(RewardId.SACRED_ASH, baseCost * 10)], ]; return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat(); } diff --git a/src/items/reward.ts b/src/items/reward.ts index 244b80e1314..b4fcc9a1735 100644 --- a/src/items/reward.ts +++ b/src/items/reward.ts @@ -4,7 +4,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { EvolutionItem, FusionSpeciesFormEvolution, pokemonEvolutions } from "#balance/pokemon-evolutions"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#balance/starters"; import { tmPoolTiers, tmSpecies } from "#balance/tms"; -import { allHeldItems, allMoves, allRewards, allTrainerItems } from "#data/data-lists"; +import { allHeldItems, allMoves, allTrainerItems } from "#data/data-lists"; import { getLevelTotalExp } from "#data/exp"; import { SpeciesFormChangeItemTrigger } from "#data/form-change-triggers"; import { getNatureName, getNatureStatMultiplier } from "#data/nature"; @@ -17,7 +17,7 @@ import { HeldItemId } from "#enums/held-item-id"; import { LearnMoveType } from "#enums/learn-move-type"; import { MoveId } from "#enums/move-id"; import { Nature } from "#enums/nature"; -import { PokeballType } from "#enums/pokeball"; +import type { PokeballType } from "#enums/pokeball"; import { PokemonType } from "#enums/pokemon-type"; import { RewardId } from "#enums/reward-id"; import { RarityTier } from "#enums/reward-tier"; @@ -32,15 +32,10 @@ import { permanentStatToHeldItem, statBoostItems } from "#items/base-stat-booste import { berryTypeToHeldItem } from "#items/berry"; import { getNewAttackTypeBoosterHeldItem, getNewBerryHeldItem, getNewVitaminHeldItem } from "#items/held-item-pool"; import { formChangeItemName } from "#items/item-utility"; -import { - SPECIES_STAT_BOOSTER_ITEMS, - type SpeciesStatBoosterItemId, - type SpeciesStatBoostHeldItem, -} from "#items/stat-booster"; +import { SPECIES_STAT_BOOSTER_ITEMS, type SpeciesStatBoostHeldItem } from "#items/stat-booster"; import { TrainerItemEffect, tempStatToTrainerItem } from "#items/trainer-item"; import type { PokemonMove } from "#moves/pokemon-move"; -import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#system/voucher"; -import type { RewardFunc, WeightedRewardWeightFunc } from "#types/rewards"; +import { getVoucherTypeIcon, getVoucherTypeName, type VoucherType } from "#system/voucher"; import type { Exact } from "#types/type-helpers"; import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#ui/party-ui-handler"; import { PartyUiHandler } from "#ui/party-ui-handler"; @@ -90,7 +85,6 @@ for example. The entries of rewardInitObj are used in the RewardPool. There are some more derived classes, in particular: RewardGenerator, which creates Reward instances from a certain group (e.g. TMs, nature mints, or berries); -WeightedReward, which is a Reward with an attached weight or weight function to be used in pools; and RewardOption, which is displayed during the select reward phase at the end of each encounter. */ @@ -589,7 +583,7 @@ export class PokemonReviveReward extends PokemonHpRestoreReward { } } -class AllPokemonFullReviveReward extends Reward { +export class AllPokemonFullReviveReward extends Reward { constructor(localeKey: string, iconImage: string) { super(localeKey, iconImage, "modifierType:ModifierType.AllPokemonFullReviveModifierType"); this.id = RewardId.SACRED_ASH; @@ -863,7 +857,7 @@ export class RememberMoveReward extends PokemonReward { } } -class BerryRewardGenerator extends RewardGenerator { +export class BerryRewardGenerator extends RewardGenerator { constructor() { super((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { @@ -877,7 +871,7 @@ class BerryRewardGenerator extends RewardGenerator { } } -class MintRewardGenerator extends RewardGenerator { +export class MintRewardGenerator extends RewardGenerator { constructor() { super((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in Nature) { @@ -889,7 +883,7 @@ class MintRewardGenerator extends RewardGenerator { } } -class TeraTypeRewardGenerator extends RewardGenerator { +export class TeraTypeRewardGenerator extends RewardGenerator { constructor() { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { @@ -1225,7 +1219,7 @@ export class FusePokemonReward extends PokemonReward { } } -class AttackTypeBoosterRewardGenerator extends RewardGenerator { +export class AttackTypeBoosterRewardGenerator extends RewardGenerator { constructor() { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { @@ -1240,7 +1234,7 @@ class AttackTypeBoosterRewardGenerator extends RewardGenerator { } } -class BaseStatBoosterRewardGenerator extends RewardGenerator { +export class BaseStatBoosterRewardGenerator extends RewardGenerator { constructor() { super((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs) { @@ -1252,7 +1246,7 @@ class BaseStatBoosterRewardGenerator extends RewardGenerator { } } -class TempStatStageBoosterRewardGenerator extends RewardGenerator { +export class TempStatStageBoosterRewardGenerator extends RewardGenerator { public static readonly items: Record = { [Stat.ATK]: "x_attack", [Stat.DEF]: "x_defense", @@ -1280,7 +1274,7 @@ class TempStatStageBoosterRewardGenerator extends RewardGenerator { * the current list of {@linkcode items}. * @extends RewardGenerator */ -class SpeciesStatBoosterRewardGenerator extends RewardGenerator { +export class SpeciesStatBoosterRewardGenerator extends RewardGenerator { /** Object comprised of the currently available species-based stat boosting held items */ constructor(rare: boolean) { @@ -1347,7 +1341,7 @@ class SpeciesStatBoosterRewardGenerator extends RewardGenerator { } } -class TmRewardGenerator extends RewardGenerator { +export class TmRewardGenerator extends RewardGenerator { constructor(tier: RarityTier) { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in MoveId) { @@ -1380,7 +1374,7 @@ class TmRewardGenerator extends RewardGenerator { } } -class EvolutionItemRewardGenerator extends RewardGenerator { +export class EvolutionItemRewardGenerator extends RewardGenerator { constructor(rare: boolean, id: RewardId) { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in EvolutionItem) { @@ -1512,305 +1506,6 @@ export class FormChangeItemRewardGenerator extends RewardGenerator { } } -export class WeightedReward { - public reward: Reward | RewardGenerator; - public weight: number | WeightedRewardWeightFunc; - public maxWeight: number | WeightedRewardWeightFunc; - - constructor( - rewardFunc: RewardFunc, - weight: number | WeightedRewardWeightFunc, - maxWeight?: number | WeightedRewardWeightFunc, - ) { - this.reward = rewardFunc(); - this.weight = weight; - this.maxWeight = maxWeight || (!(weight instanceof Function) ? weight : 0); - } -} - -type BaseRewardOverride = { - name: Exclude; - count?: number; -}; - -/** Type for modifiers and held items that are constructed via {@linkcode RewardGenerator}. */ -export type GeneratorRewardOverride = { - count?: number; -} & ( - | { - name: keyof Pick; - type?: SpeciesStatBoosterItemId; - } - | { - name: keyof Pick; - type?: TempBattleStat; - } - | { - name: keyof Pick; - type?: Stat; - } - | { - name: keyof Pick; - type?: Nature; - } - | { - name: keyof Pick; - type?: PokemonType; - } - | { - name: keyof Pick; - type?: BerryType; - } - | { - name: keyof Pick; - type?: EvolutionItem; - } - | { - name: keyof Pick; - type?: FormChangeItem; - } - | { - name: keyof Pick; - type?: MoveId; - } -); - -/** Type used to construct modifiers and held items for overriding purposes. */ -export type RewardOverride = GeneratorRewardOverride | BaseRewardOverride; - -const rewardInitObj = Object.freeze({ - // Pokeball rewards - POKEBALL: () => new AddPokeballReward("pb", PokeballType.POKEBALL, 5, RewardId.POKEBALL), - GREAT_BALL: () => new AddPokeballReward("gb", PokeballType.GREAT_BALL, 5, RewardId.GREAT_BALL), - ULTRA_BALL: () => new AddPokeballReward("ub", PokeballType.ULTRA_BALL, 5, RewardId.ULTRA_BALL), - ROGUE_BALL: () => new AddPokeballReward("rb", PokeballType.ROGUE_BALL, 5, RewardId.ROGUE_BALL), - MASTER_BALL: () => new AddPokeballReward("mb", PokeballType.MASTER_BALL, 1, RewardId.MASTER_BALL), - - // Voucher rewards - VOUCHER: () => new AddVoucherReward(VoucherType.REGULAR, 1, RewardId.VOUCHER), - VOUCHER_PLUS: () => new AddVoucherReward(VoucherType.PLUS, 1, RewardId.VOUCHER_PLUS), - VOUCHER_PREMIUM: () => new AddVoucherReward(VoucherType.PREMIUM, 1, RewardId.VOUCHER_PREMIUM), - - // Money rewards - NUGGET: () => - new AddMoneyReward( - "modifierType:ModifierType.NUGGET", - "nugget", - 1, - "modifierType:ModifierType.MoneyRewardModifierType.extra.small", - RewardId.NUGGET, - ), - BIG_NUGGET: () => - new AddMoneyReward( - "modifierType:ModifierType.BIG_NUGGET", - "big_nugget", - 2.5, - "modifierType:ModifierType.MoneyRewardModifierType.extra.moderate", - RewardId.BIG_NUGGET, - ), - RELIC_GOLD: () => - new AddMoneyReward( - "modifierType:ModifierType.RELIC_GOLD", - "relic_gold", - 10, - "modifierType:ModifierType.MoneyRewardModifierType.extra.large", - RewardId.RELIC_GOLD, - ), - - // Party-wide consumables - RARER_CANDY: () => new AllPokemonLevelIncrementReward("modifierType:ModifierType.RARER_CANDY", "rarer_candy"), - SACRED_ASH: () => new AllPokemonFullReviveReward("modifierType:ModifierType.SACRED_ASH", "sacred_ash"), - - // Pokemon consumables - RARE_CANDY: () => new PokemonLevelIncrementReward("modifierType:ModifierType.RARE_CANDY", "rare_candy"), - - EVOLUTION_ITEM: () => new EvolutionItemRewardGenerator(false, RewardId.EVOLUTION_ITEM), - RARE_EVOLUTION_ITEM: () => new EvolutionItemRewardGenerator(true, RewardId.RARE_EVOLUTION_ITEM), - - POTION: () => new PokemonHpRestoreReward("modifierType:ModifierType.POTION", "potion", RewardId.POTION, 20, 10), - SUPER_POTION: () => - new PokemonHpRestoreReward("modifierType:ModifierType.SUPER_POTION", "super_potion", RewardId.SUPER_POTION, 50, 25), - HYPER_POTION: () => - new PokemonHpRestoreReward( - "modifierType:ModifierType.HYPER_POTION", - "hyper_potion", - RewardId.HYPER_POTION, - 200, - 50, - ), - MAX_POTION: () => - new PokemonHpRestoreReward("modifierType:ModifierType.MAX_POTION", "max_potion", RewardId.MAX_POTION, 0, 100), - FULL_RESTORE: () => - new PokemonHpRestoreReward( - "modifierType:ModifierType.FULL_RESTORE", - "full_restore", - RewardId.FULL_RESTORE, - 0, - 100, - true, - ), - - REVIVE: () => new PokemonReviveReward("modifierType:ModifierType.REVIVE", "revive", RewardId.REVIVE, 50), - MAX_REVIVE: () => - new PokemonReviveReward("modifierType:ModifierType.MAX_REVIVE", "max_revive", RewardId.MAX_REVIVE, 100), - - FULL_HEAL: () => new PokemonStatusHealReward("modifierType:ModifierType.FULL_HEAL", "full_heal"), - - ETHER: () => new PokemonPpRestoreReward("modifierType:ModifierType.ETHER", "ether", RewardId.ETHER, 10), - MAX_ETHER: () => - new PokemonPpRestoreReward("modifierType:ModifierType.MAX_ETHER", "max_ether", RewardId.MAX_ETHER, -1), - - ELIXIR: () => new PokemonAllMovePpRestoreReward("modifierType:ModifierType.ELIXIR", "elixir", RewardId.ELIXIR, 10), - MAX_ELIXIR: () => - new PokemonAllMovePpRestoreReward("modifierType:ModifierType.MAX_ELIXIR", "max_elixir", RewardId.MAX_ELIXIR, -1), - - PP_UP: () => new PokemonPpUpReward("modifierType:ModifierType.PP_UP", "pp_up", RewardId.PP_UP, 1), - PP_MAX: () => new PokemonPpUpReward("modifierType:ModifierType.PP_MAX", "pp_max", RewardId.PP_MAX, 3), - - /*REPEL: () => new DoubleBattleChanceBoosterReward('Repel', 5), - SUPER_REPEL: () => new DoubleBattleChanceBoosterReward('Super Repel', 10), - MAX_REPEL: () => new DoubleBattleChanceBoosterReward('Max Repel', 25),*/ - - MINT: () => new MintRewardGenerator(), - - TERA_SHARD: () => new TeraTypeRewardGenerator(), - - TM_COMMON: () => new TmRewardGenerator(RarityTier.COMMON), - TM_GREAT: () => new TmRewardGenerator(RarityTier.GREAT), - TM_ULTRA: () => new TmRewardGenerator(RarityTier.ULTRA), - - MEMORY_MUSHROOM: () => new RememberMoveReward("modifierType:ModifierType.MEMORY_MUSHROOM", "big_mushroom"), - - DNA_SPLICERS: () => new FusePokemonReward("modifierType:ModifierType.DNA_SPLICERS", "dna_splicers"), - - // Form change items - FORM_CHANGE_ITEM: () => new FormChangeItemRewardGenerator(false, RewardId.FORM_CHANGE_ITEM), - RARE_FORM_CHANGE_ITEM: () => new FormChangeItemRewardGenerator(true, RewardId.RARE_FORM_CHANGE_ITEM), - - // Held items - REVIVER_SEED: () => new HeldItemReward(HeldItemId.REVIVER_SEED), - - WHITE_HERB: () => new HeldItemReward(HeldItemId.WHITE_HERB), - - SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterRewardGenerator(false), - RARE_SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterRewardGenerator(true), - - BASE_STAT_BOOSTER: () => new BaseStatBoosterRewardGenerator(), - - ATTACK_TYPE_BOOSTER: () => new AttackTypeBoosterRewardGenerator(), - - MYSTICAL_ROCK: () => new HeldItemReward(HeldItemId.MYSTICAL_ROCK), - - BERRY: () => new BerryRewardGenerator(), - - LUCKY_EGG: () => new HeldItemReward(HeldItemId.LUCKY_EGG), - GOLDEN_EGG: () => new HeldItemReward(HeldItemId.GOLDEN_EGG), - - SOOTHE_BELL: () => new HeldItemReward(HeldItemId.SOOTHE_BELL), - - SCOPE_LENS: () => new HeldItemReward(HeldItemId.SCOPE_LENS), - LEEK: () => new HeldItemReward(HeldItemId.LEEK), - - EVIOLITE: () => new HeldItemReward(HeldItemId.EVIOLITE), - - SOUL_DEW: () => new HeldItemReward(HeldItemId.SOUL_DEW), - - GOLDEN_PUNCH: () => new HeldItemReward(HeldItemId.GOLDEN_PUNCH), - - GRIP_CLAW: () => new HeldItemReward(HeldItemId.GRIP_CLAW), - WIDE_LENS: () => new HeldItemReward(HeldItemId.WIDE_LENS), - - MULTI_LENS: () => new HeldItemReward(HeldItemId.MULTI_LENS), - - FOCUS_BAND: () => new HeldItemReward(HeldItemId.FOCUS_BAND), - - QUICK_CLAW: () => new HeldItemReward(HeldItemId.QUICK_CLAW), - - KINGS_ROCK: () => new HeldItemReward(HeldItemId.KINGS_ROCK), - - LEFTOVERS: () => new HeldItemReward(HeldItemId.LEFTOVERS), - - SHELL_BELL: () => new HeldItemReward(HeldItemId.SHELL_BELL), - - TOXIC_ORB: () => new HeldItemReward(HeldItemId.TOXIC_ORB), - - FLAME_ORB: () => new HeldItemReward(HeldItemId.FLAME_ORB), - - BATON: () => new HeldItemReward(HeldItemId.BATON), - - MINI_BLACK_HOLE: () => new HeldItemReward(HeldItemId.MINI_BLACK_HOLE), - - // Trainer items - MEGA_BRACELET: () => new TrainerItemReward(TrainerItemId.MEGA_BRACELET), - DYNAMAX_BAND: () => new TrainerItemReward(TrainerItemId.DYNAMAX_BAND), - TERA_ORB: () => new TrainerItemReward(TrainerItemId.TERA_ORB), - - MAP: () => new TrainerItemReward(TrainerItemId.MAP), - - LURE: () => new LapsingTrainerItemReward(TrainerItemId.LURE, RewardId.LURE), - SUPER_LURE: () => new LapsingTrainerItemReward(TrainerItemId.SUPER_LURE, RewardId.SUPER_LURE), - MAX_LURE: () => new LapsingTrainerItemReward(TrainerItemId.MAX_LURE, RewardId.MAX_LURE), - - TEMP_STAT_STAGE_BOOSTER: () => new TempStatStageBoosterRewardGenerator(), - - DIRE_HIT: () => new LapsingTrainerItemReward(TrainerItemId.DIRE_HIT, RewardId.TEMP_STAT_STAGE_BOOSTER), - - EXP_SHARE: () => new TrainerItemReward(TrainerItemId.EXP_SHARE), - EXP_BALANCE: () => new TrainerItemReward(TrainerItemId.EXP_BALANCE), - - OVAL_CHARM: () => new TrainerItemReward(TrainerItemId.OVAL_CHARM), - - EXP_CHARM: () => new TrainerItemReward(TrainerItemId.EXP_CHARM), - SUPER_EXP_CHARM: () => new TrainerItemReward(TrainerItemId.SUPER_EXP_CHARM), - - AMULET_COIN: () => new TrainerItemReward(TrainerItemId.AMULET_COIN), - - LOCK_CAPSULE: () => new TrainerItemReward(TrainerItemId.LOCK_CAPSULE), - - HEALING_CHARM: () => new TrainerItemReward(TrainerItemId.HEALING_CHARM), - CANDY_JAR: () => new TrainerItemReward(TrainerItemId.CANDY_JAR), - - BERRY_POUCH: () => new TrainerItemReward(TrainerItemId.BERRY_POUCH), - - SHINY_CHARM: () => new TrainerItemReward(TrainerItemId.SHINY_CHARM), - ABILITY_CHARM: () => new TrainerItemReward(TrainerItemId.ABILITY_CHARM), - CATCHING_CHARM: () => new TrainerItemReward(TrainerItemId.CATCHING_CHARM), - - IV_SCANNER: () => new TrainerItemReward(TrainerItemId.IV_SCANNER), - - GOLDEN_POKEBALL: () => new TrainerItemReward(TrainerItemId.GOLDEN_POKEBALL), - - // Tokens //TODO: do we even need them here? - ENEMY_DAMAGE_BOOSTER: () => new TrainerItemReward(TrainerItemId.ENEMY_DAMAGE_BOOSTER), - ENEMY_DAMAGE_REDUCTION: () => new TrainerItemReward(TrainerItemId.ENEMY_DAMAGE_REDUCTION), - //ENEMY_SUPER_EFFECT_BOOSTER: () => new Reward('Type Advantage Token', 'Increases damage of super effective attacks by 30%', (type, _args) => new EnemySuperEffectiveDamageBoosterModifier(type, 30), 'wl_custom_super_effective'), - ENEMY_HEAL: () => new TrainerItemReward(TrainerItemId.ENEMY_HEAL), - ENEMY_ATTACK_POISON_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ATTACK_POISON_CHANCE), - ENEMY_ATTACK_PARALYZE_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ATTACK_PARALYZE_CHANCE), - ENEMY_ATTACK_BURN_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ATTACK_BURN_CHANCE), - ENEMY_STATUS_EFFECT_HEAL_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_STATUS_EFFECT_HEAL_CHANCE), - ENEMY_ENDURE_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_ENDURE_CHANCE), - ENEMY_FUSED_CHANCE: () => new TrainerItemReward(TrainerItemId.ENEMY_FUSED_CHANCE), - - // Items from mystery encounters - MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD: () => new HeldItemReward(HeldItemId.SHUCKLE_JUICE_GOOD), - MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD: () => new HeldItemReward(HeldItemId.SHUCKLE_JUICE_BAD), - - MYSTERY_ENCOUNTER_OLD_GATEAU: () => new HeldItemReward(HeldItemId.OLD_GATEAU), - - MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new TrainerItemReward(TrainerItemId.BLACK_SLUDGE), - - MYSTERY_ENCOUNTER_MACHO_BRACE: () => new HeldItemReward(HeldItemId.MACHO_BRACE), - - MYSTERY_ENCOUNTER_GOLDEN_BUG_NET: () => new TrainerItemReward(TrainerItemId.GOLDEN_BUG_NET), -}); - -/** - * The initial set of modifier types, used to generate the modifier pool. - */ -export type Rewards = typeof rewardInitObj; -export type RewardKeys = keyof typeof rewardInitObj; - export class RewardOption { public type: Reward; public upgradeCount: number; @@ -1825,12 +1520,6 @@ export class RewardOption { } } -export function initRewards() { - for (const [key, value] of Object.entries(rewardInitObj)) { - allRewards[key] = value; - } -} - // TODO: If necessary, add the rest of the modifier types here. // For now, doing the minimal work until the modifier rework lands. const RewardConstructorMap = Object.freeze({ diff --git a/src/overrides.ts b/src/overrides.ts index 3a96a1c7bea..a21ae28b3a2 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -22,9 +22,9 @@ import { Unlockables } from "#enums/unlockables"; import { VariantTier } from "#enums/variant-tier"; import { WeatherType } from "#enums/weather-type"; import { HeldItemConfiguration } from "#items/held-item-data-types"; -import { type RewardOverride } from "#items/reward"; import { TrainerItemConfiguration } from "#items/trainer-item-data-types"; import { Variant } from "#sprites/variant"; +import { RewardSpecs } from "#types/rewards"; /** * This comment block exists to prevent IDEs from automatically removing unused imports @@ -273,25 +273,18 @@ class DefaultOverrides { * STARTING_HELD_ITEM_OVERRIDE = [{name: "BERRY"}] * ``` */ - /** Override array of {@linkcode RewardOverride}s used to provide held items to first party member when starting a new game. */ readonly STARTING_TRAINER_ITEMS_OVERRIDE: TrainerItemConfiguration = []; - /** Override array of {@linkcode RewardOverride}s used to provide held items to enemies on spawn. */ readonly OPP_TRAINER_ITEMS_OVERRIDE: TrainerItemConfiguration = []; - - /** Override array of {@linkcode RewardOverride}s used to provide held items to first party member when starting a new game. */ readonly STARTING_HELD_ITEMS_OVERRIDE: HeldItemConfiguration = []; - /** Override array of {@linkcode RewardOverride}s used to provide held items to enemies on spawn. */ readonly OPP_HELD_ITEMS_OVERRIDE: HeldItemConfiguration = []; /** - * Override array of {@linkcode RewardOverride}s used to replace the generated reward rolls after a wave. - * * If less entries are listed than rolled, only those entries will be used to replace the corresponding items while the rest randomly generated. * If more entries are listed than rolled, only the first X entries will be used, where X is the number of items rolled. * * Note that, for all items in the array, `count` is not used. */ - readonly REWARD_OVERRIDE: RewardOverride[] = []; + readonly REWARD_OVERRIDE: RewardSpecs[] = []; /** * If `true`, disable all non-scripted opponent trainer encounters. diff --git a/src/phases/select-reward-phase.ts b/src/phases/select-reward-phase.ts index 4c5e080733c..e552b209666 100644 --- a/src/phases/select-reward-phase.ts +++ b/src/phases/select-reward-phase.ts @@ -373,7 +373,7 @@ export class SelectRewardPhase extends BattlePhase { const newItemCount = (this.customRewardSettings.guaranteedRarityTiers?.length ?? 0) + (this.customRewardSettings.guaranteedRewardOptions?.length ?? 0) + - (this.customRewardSettings.guaranteedRewardFuncs?.length ?? 0); + (this.customRewardSettings.guaranteedRewardSpecs?.length ?? 0); if (this.customRewardSettings.fillRemaining) { const originalCount = rewardCountHolder.value; rewardCountHolder.value = originalCount > newItemCount ? originalCount : newItemCount; diff --git a/test/phases/select-reward-phase.test.ts b/test/phases/select-reward-phase.test.ts index ed664cf1aff..9bd17f434db 100644 --- a/test/phases/select-reward-phase.test.ts +++ b/test/phases/select-reward-phase.test.ts @@ -155,7 +155,7 @@ describe("SelectRewardPhase", () => { await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); scene.money = 1000000; const customRewards: CustomRewardSettings = { - guaranteedRewardFuncs: [ + guaranteedRewardSpecs: [ allRewards.MEMORY_MUSHROOM, allRewards.TM_ULTRA, allRewards.LEFTOVERS, @@ -238,7 +238,7 @@ describe("SelectRewardPhase", () => { await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); scene.money = 1000000; const customRewards: CustomRewardSettings = { - guaranteedRewardFuncs: [allRewards.MEMORY_MUSHROOM, allRewards.TM_COMMON], + guaranteedRewardSpecs: [allRewards.MEMORY_MUSHROOM, allRewards.TM_COMMON], guaranteedRarityTiers: [RarityTier.MASTER, RarityTier.MASTER], }; const selectRewardPhase = new SelectRewardPhase(0, undefined, customRewards); @@ -261,7 +261,7 @@ describe("SelectRewardPhase", () => { await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.VOLCARONA]); scene.money = 1000000; const customRewards: CustomRewardSettings = { - guaranteedRewardFuncs: [allRewards.MEMORY_MUSHROOM], + guaranteedRewardSpecs: [allRewards.MEMORY_MUSHROOM], guaranteedRarityTiers: [RarityTier.MASTER], fillRemaining: true, }; diff --git a/test/test-utils/helpers/overrides-helper.ts b/test/test-utils/helpers/overrides-helper.ts index ec4519753fc..e02d63c1804 100644 --- a/test/test-utils/helpers/overrides-helper.ts +++ b/test/test-utils/helpers/overrides-helper.ts @@ -18,10 +18,10 @@ import { StatusEffect } from "#enums/status-effect"; import type { Unlockables } from "#enums/unlockables"; import { WeatherType } from "#enums/weather-type"; import type { HeldItemConfiguration } from "#items/held-item-data-types"; -import type { RewardOverride } from "#items/reward"; import type { TrainerItemConfiguration } from "#items/trainer-item-data-types"; import type { Variant } from "#sprites/variant"; import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper"; +import type { RewardSpecs } from "#types/rewards"; import { coerceArray, shiftCharCodes } from "#utils/common"; import { vi } from "vitest"; @@ -557,7 +557,7 @@ export class OverridesHelper extends GameManagerHelper { * @param items - The items to be rolled * @returns `this` */ - public itemRewards(items: RewardOverride[]): this { + public rewards(items: RewardSpecs[]): this { vi.spyOn(Overrides, "REWARD_OVERRIDE", "get").mockReturnValue(items); this.log("Item allRewards set to:", items); return this;