From 39abac65be26c2004ea1ca9a686cc8d7b35efb65 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 19 Oct 2024 18:44:36 -0700 Subject: [PATCH 01/24] Add eslint rule to enforce indenting of `case` statements (#4692) --- eslint.config.js | 16 +- src/battle-scene.ts | 586 ++++----- src/data/ability.ts | 60 +- src/data/arena-tag.ts | 174 +-- src/data/balance/biomes.ts | 16 +- src/data/balance/starters.ts | 38 +- src/data/battle-anims.ts | 322 ++--- src/data/battler-tags.ts | 392 +++--- src/data/berry.ts | 198 +-- src/data/challenge.ts | 132 +- src/data/egg.ts | 136 +- src/data/exp.ts | 60 +- src/data/gender.ts | 16 +- src/data/move.ts | 1114 ++++++++--------- .../encounters/a-trainers-test-encounter.ts | 52 +- .../utils/encounter-phase-utils.ts | 26 +- src/data/nature.ts | 140 +-- src/data/pokeball.ts | 108 +- src/data/pokemon-species.ts | 200 +-- src/data/status-effect.ts | 44 +- src/data/terrain.ts | 76 +- src/data/trainer-config.ts | 314 ++--- src/data/type.ts | 628 +++++----- src/data/variant.ts | 24 +- src/data/weather.ts | 466 +++---- src/field/anims.ts | 30 +- src/field/arena.ts | 560 ++++----- src/field/damage-number-handler.ts | 30 +- src/field/pokemon.ts | 424 +++---- src/field/trainer.ts | 76 +- src/game-mode.ts | 148 +-- src/loading-scene.ts | 24 +- src/messages.ts | 32 +- src/modifier/modifier-type.ts | 226 ++-- src/modifier/modifier.ts | 30 +- src/phases/command-phase.ts | 250 ++-- src/phases/damage-phase.ts | 20 +- src/phases/encounter-phase.ts | 46 +- src/phases/faint-phase.ts | 22 +- src/phases/move-phase.ts | 34 +- src/phases/pokemon-anim-phase.ts | 28 +- src/phases/post-turn-status-effect-phase.ts | 20 +- src/phases/select-modifier-phase.ts | 136 +- src/phases/turn-start-phase.ts | 90 +- src/system/achv.ts | 254 ++-- src/system/game-data.ts | 82 +- src/system/settings/settings-gamepad.ts | 110 +- src/system/settings/settings-keyboard.ts | 92 +- src/system/settings/settings.ts | 450 +++---- src/system/unlockables.ts | 16 +- .../version_migration/versions/v1_0_4.ts | 18 +- src/system/voucher.ts | 48 +- .../mystery-encounter/encounter-test-utils.ts | 26 +- src/touch-controls.ts | 28 +- src/ui-inputs.ts | 38 +- src/ui/abstact-option-select-ui-handler.ts | 28 +- src/ui/achvs-ui-handler.ts | 130 +- src/ui/arena-flyout.ts | 144 +-- src/ui/ball-ui-handler.ts | 12 +- src/ui/challenges-select-ui-handler.ts | 98 +- src/ui/command-ui-handler.ts | 78 +- src/ui/daily-run-scoreboard.ts | 14 +- src/ui/dropdown.ts | 42 +- src/ui/egg-gacha-ui-handler.ts | 282 ++--- src/ui/fight-ui-handler.ts | 40 +- src/ui/game-stats-ui-handler.ts | 20 +- src/ui/login-form-ui-handler.ts | 24 +- src/ui/menu-ui-handler.ts | 274 ++-- src/ui/message-ui-handler.ts | 24 +- src/ui/modifier-select-ui-handler.ts | 154 +-- src/ui/mystery-encounter-ui-handler.ts | 192 +-- src/ui/party-ui-handler.ts | 272 ++-- src/ui/pokemon-icon-anim-handler.ts | 12 +- src/ui/registration-form-ui-handler.ts | 12 +- src/ui/run-history-ui-handler.ts | 66 +- src/ui/run-info-ui-handler.ts | 216 ++-- src/ui/save-slot-select-ui-handler.ts | 102 +- src/ui/scrollable-grid-handler.ts | 74 +- .../settings/abstract-binding-ui-handler.ts | 28 +- .../abstract-control-settings-ui-handler.ts | 132 +- .../settings/abstract-settings-ui-handler.ts | 98 +- src/ui/settings/navigationMenu.ts | 12 +- .../settings/settings-display-ui-handler.ts | 146 +-- src/ui/starter-select-ui-handler.ts | 754 +++++------ src/ui/summary-ui-handler.ts | 622 ++++----- src/ui/target-select-ui-handler.ts | 40 +- src/ui/text.ts | 328 ++--- src/ui/ui-theme.ts | 12 +- src/utils.ts | 58 +- 89 files changed, 6633 insertions(+), 6633 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index c371f073f76..2f2b466c66f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,5 +1,5 @@ import tseslint from '@typescript-eslint/eslint-plugin'; -import stylisticTs from '@stylistic/eslint-plugin-ts' +import stylisticTs from '@stylistic/eslint-plugin-ts'; import parser from '@typescript-eslint/parser'; import importX from 'eslint-plugin-import-x'; @@ -16,15 +16,15 @@ export default [ '@typescript-eslint': tseslint }, rules: { - "eqeqeq": ["error", "always"], // Enforces the use of === and !== instead of == and != - "indent": ["error", 2], // Enforces a 2-space indentation + "eqeqeq": ["error", "always"], // Enforces the use of `===` and `!==` instead of `==` and `!=` + "indent": ["error", 2, { "SwitchCase": 1 }], // Enforces a 2-space indentation, enforces indentation of `case ...:` statements "quotes": ["error", "double"], // Enforces the use of double quotes for strings - "no-var": "error", // Disallows the use of var, enforcing let or const instead - "prefer-const": "error", // Prefers the use of const for variables that are never reassigned + "no-var": "error", // Disallows the use of `var`, enforcing `let` or `const` instead + "prefer-const": "error", // Enforces the use of `const` for variables that are never reassigned "no-undef": "off", // Disables the rule that disallows the use of undeclared variables (TypeScript handles this) "@typescript-eslint/no-unused-vars": [ "error", { "args": "none", // Allows unused function parameters. Useful for functions with specific signatures where not all parameters are always used. - "ignoreRestSiblings": true // Allows unused variables that are part of a rest property in object destructuring. Useful for excluding certain properties from an object while using the rest. + "ignoreRestSiblings": true // Allows unused variables that are part of a rest property in object destructuring. Useful for excluding certain properties from an object while using the others. }], "eol-last": ["error", "always"], // Enforces at least one newline at the end of files "@stylistic/ts/semi": ["error", "always"], // Requires semicolons for TypeScript-specific syntax @@ -32,14 +32,14 @@ export default [ "no-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax "brace-style": "off", // Note: you must disable the base rule as it can report incorrect errors "curly": ["error", "all"], // Enforces the use of curly braces for all control statements - "@stylistic/ts/brace-style": ["error", "1tbs"], + "@stylistic/ts/brace-style": ["error", "1tbs"], // Enforces the following brace style: https://eslint.style/rules/js/brace-style#_1tbs "no-trailing-spaces": ["error", { // Disallows trailing whitespace at the end of lines "skipBlankLines": false, // Enforces the rule even on blank lines "ignoreComments": false // Enforces the rule on lines containing comments }], "space-before-blocks": ["error", "always"], // Enforces a space before blocks "keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords - "comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after comma + "comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after commas "import-x/extensions": ["error", "never", { "json": "always" }], // Enforces no extension for imports unless json "array-bracket-spacing": ["error", "always", { "objectsInArrays": false, "arraysInArrays": false }], // Enforces consistent spacing inside array brackets "object-curly-spacing": ["error", "always", { "arraysInObjects": false, "objectsInObjects": false }], // Enforces consistent spacing inside braces of object literals, destructuring assignments, and import/export specifiers diff --git a/src/battle-scene.ts b/src/battle-scene.ts index c6fff1e209a..0d482a5f1a5 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1359,69 +1359,69 @@ export default class BattleScene extends SceneBase { } switch (species.speciesId) { - case Species.UNOWN: - case Species.SHELLOS: - case Species.GASTRODON: - case Species.BASCULIN: - case Species.DEERLING: - case Species.SAWSBUCK: - case Species.FROAKIE: - case Species.FROGADIER: - case Species.SCATTERBUG: - case Species.SPEWPA: - case Species.VIVILLON: - case Species.FLABEBE: - case Species.FLOETTE: - case Species.FLORGES: - case Species.FURFROU: - case Species.PUMPKABOO: - case Species.GOURGEIST: - case Species.ORICORIO: - case Species.MAGEARNA: - case Species.ZARUDE: - case Species.SQUAWKABILLY: - case Species.TATSUGIRI: - case Species.PALDEA_TAUROS: - return Utils.randSeedInt(species.forms.length); - case Species.PIKACHU: - return Utils.randSeedInt(8); - case Species.EEVEE: - return Utils.randSeedInt(2); - case Species.GRENINJA: - return Utils.randSeedInt(2); - case Species.ZYGARDE: - return Utils.randSeedInt(4); - case Species.MINIOR: - return Utils.randSeedInt(6); - case Species.ALCREMIE: - return Utils.randSeedInt(9); - case Species.MEOWSTIC: - case Species.INDEEDEE: - case Species.BASCULEGION: - case Species.OINKOLOGNE: - return gender === Gender.FEMALE ? 1 : 0; - case Species.TOXTRICITY: - const lowkeyNatures = [ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ]; - if (nature !== undefined && lowkeyNatures.indexOf(nature) > -1) { - return 1; - } - return 0; - case Species.GIMMIGHOUL: - // Chest form can only be found in Mysterious Chest Encounter, if this is a game mode with MEs - if (this.gameMode.hasMysteryEncounters) { - return 1; // Wandering form - } else { + case Species.UNOWN: + case Species.SHELLOS: + case Species.GASTRODON: + case Species.BASCULIN: + case Species.DEERLING: + case Species.SAWSBUCK: + case Species.FROAKIE: + case Species.FROGADIER: + case Species.SCATTERBUG: + case Species.SPEWPA: + case Species.VIVILLON: + case Species.FLABEBE: + case Species.FLOETTE: + case Species.FLORGES: + case Species.FURFROU: + case Species.PUMPKABOO: + case Species.GOURGEIST: + case Species.ORICORIO: + case Species.MAGEARNA: + case Species.ZARUDE: + case Species.SQUAWKABILLY: + case Species.TATSUGIRI: + case Species.PALDEA_TAUROS: return Utils.randSeedInt(species.forms.length); - } + case Species.PIKACHU: + return Utils.randSeedInt(8); + case Species.EEVEE: + return Utils.randSeedInt(2); + case Species.GRENINJA: + return Utils.randSeedInt(2); + case Species.ZYGARDE: + return Utils.randSeedInt(4); + case Species.MINIOR: + return Utils.randSeedInt(6); + case Species.ALCREMIE: + return Utils.randSeedInt(9); + case Species.MEOWSTIC: + case Species.INDEEDEE: + case Species.BASCULEGION: + case Species.OINKOLOGNE: + return gender === Gender.FEMALE ? 1 : 0; + case Species.TOXTRICITY: + const lowkeyNatures = [ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ]; + if (nature !== undefined && lowkeyNatures.indexOf(nature) > -1) { + return 1; + } + return 0; + case Species.GIMMIGHOUL: + // Chest form can only be found in Mysterious Chest Encounter, if this is a game mode with MEs + if (this.gameMode.hasMysteryEncounters) { + return 1; // Wandering form + } else { + return Utils.randSeedInt(species.forms.length); + } } if (ignoreArena) { switch (species.speciesId) { - case Species.BURMY: - case Species.WORMADAM: - case Species.ROTOM: - case Species.LYCANROC: - return Utils.randSeedInt(species.forms.length); + case Species.BURMY: + case Species.WORMADAM: + case Species.ROTOM: + case Species.LYCANROC: + return Utils.randSeedInt(species.forms.length); } return 0; } @@ -1886,17 +1886,17 @@ export default class BattleScene extends SceneBase { const soundDetails = sound.key.split("/"); switch (soundDetails[0]) { - case "battle_anims": - case "cry": - if (soundDetails[1].startsWith("PRSFX- ")) { - sound.setVolume(this.masterVolume * this.fieldVolume * 0.5); - } else { - sound.setVolume(this.masterVolume * this.fieldVolume); - } - break; - case "se": - case "ui": - sound.setVolume(this.masterVolume * this.seVolume); + case "battle_anims": + case "cry": + if (soundDetails[1].startsWith("PRSFX- ")) { + sound.setVolume(this.masterVolume * this.fieldVolume * 0.5); + } else { + sound.setVolume(this.masterVolume * this.fieldVolume); + } + break; + case "se": + case "ui": + sound.setVolume(this.masterVolume * this.seVolume); } } } @@ -1936,31 +1936,31 @@ export default class BattleScene extends SceneBase { const keyDetails = key.split("/"); config["volume"] = config["volume"] ?? 1; switch (keyDetails[0]) { - case "level_up_fanfare": - case "item_fanfare": - case "minor_fanfare": - case "heal": - case "evolution": - case "evolution_fanfare": + case "level_up_fanfare": + case "item_fanfare": + case "minor_fanfare": + case "heal": + case "evolution": + case "evolution_fanfare": // These sounds are loaded in as BGM, but played as sound effects // When these sounds are updated in updateVolume(), they are treated as BGM however because they are placed in the BGM Cache through being called by playSoundWithoutBGM() - config["volume"] *= (this.masterVolume * this.bgmVolume); - break; - case "battle_anims": - case "cry": - config["volume"] *= (this.masterVolume * this.fieldVolume); - //PRSFX sound files are unusually loud - if (keyDetails[1].startsWith("PRSFX- ")) { - config["volume"] *= 0.5; - } - break; - case "ui": + config["volume"] *= (this.masterVolume * this.bgmVolume); + break; + case "battle_anims": + case "cry": + config["volume"] *= (this.masterVolume * this.fieldVolume); + //PRSFX sound files are unusually loud + if (keyDetails[1].startsWith("PRSFX- ")) { + config["volume"] *= 0.5; + } + break; + case "ui": //As of, right now this applies to the "select", "menu_open", "error" sound effects - config["volume"] *= (this.masterVolume * this.uiVolume); - break; - case "se": - config["volume"] *= (this.masterVolume * this.seVolume); - break; + config["volume"] *= (this.masterVolume * this.uiVolume); + break; + case "se": + config["volume"] *= (this.masterVolume * this.seVolume); + break; } this.sound.play(key, config); return this.sound.get(key) as AnySound; @@ -1989,208 +1989,208 @@ export default class BattleScene extends SceneBase { getBgmLoopPoint(bgmName: string): number { switch (bgmName) { - case "battle_kanto_champion": //B2W2 Kanto Champion Battle - return 13.950; - case "battle_johto_champion": //B2W2 Johto Champion Battle - return 23.498; - case "battle_hoenn_champion_g5": //B2W2 Hoenn Champion Battle - return 11.328; - case "battle_hoenn_champion_g6": //ORAS Hoenn Champion Battle - return 11.762; - case "battle_sinnoh_champion": //B2W2 Sinnoh Champion Battle - return 12.235; - case "battle_champion_alder": //BW Unova Champion Battle - return 27.653; - case "battle_champion_iris": //B2W2 Unova Champion Battle - return 10.145; - case "battle_kalos_champion": //XY Kalos Champion Battle - return 10.380; - case "battle_alola_champion": //USUM Alola Champion Battle - return 13.025; - case "battle_galar_champion": //SWSH Galar Champion Battle - return 61.635; - case "battle_champion_geeta": //SV Champion Geeta Battle - return 37.447; - case "battle_champion_nemona": //SV Champion Nemona Battle - return 14.914; - case "battle_champion_kieran": //SV Champion Kieran Battle - return 7.206; - case "battle_hoenn_elite": //ORAS Elite Four Battle - return 11.350; - case "battle_unova_elite": //BW Elite Four Battle - return 17.730; - case "battle_kalos_elite": //XY Elite Four Battle - return 12.340; - case "battle_alola_elite": //SM Elite Four Battle - return 19.212; - case "battle_galar_elite": //SWSH League Tournament Battle - return 164.069; - case "battle_paldea_elite": //SV Elite Four Battle - return 12.770; - case "battle_bb_elite": //SV BB League Elite Four Battle - return 19.434; - case "battle_final_encounter": //PMD RTDX Rayquaza's Domain - return 19.159; - case "battle_final": //BW Ghetsis Battle - return 16.453; - case "battle_kanto_gym": //B2W2 Kanto Gym Battle - return 13.857; - case "battle_johto_gym": //B2W2 Johto Gym Battle - return 12.911; - case "battle_hoenn_gym": //B2W2 Hoenn Gym Battle - return 12.379; - case "battle_sinnoh_gym": //B2W2 Sinnoh Gym Battle - return 13.122; - case "battle_unova_gym": //BW Unova Gym Battle - return 19.145; - case "battle_kalos_gym": //XY Kalos Gym Battle - return 44.810; - case "battle_galar_gym": //SWSH Galar Gym Battle - return 171.262; - case "battle_paldea_gym": //SV Paldea Gym Battle - return 127.489; - case "battle_legendary_kanto": //XY Kanto Legendary Battle - return 32.966; - case "battle_legendary_raikou": //HGSS Raikou Battle - return 12.632; - case "battle_legendary_entei": //HGSS Entei Battle - return 2.905; - case "battle_legendary_suicune": //HGSS Suicune Battle - return 12.636; - case "battle_legendary_lugia": //HGSS Lugia Battle - return 19.770; - case "battle_legendary_ho_oh": //HGSS Ho-oh Battle - return 17.668; - case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle - return 49.500; - case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle - return 21.130; - case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle - return 10.547; - case "battle_legendary_rayquaza": //ORAS Rayquaza Battle - return 10.495; - case "battle_legendary_deoxys": //ORAS Deoxys Battle - return 13.333; - case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle - return 16.887; - case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle - return 22.770; - case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle - return 16.009; - case "battle_legendary_origin_forme": //LA Origin Dialga & Palkia Battle - return 18.961; - case "battle_legendary_giratina": //ORAS Giratina Battle - return 10.451; - case "battle_legendary_arceus": //HGSS Arceus Battle - return 9.595; - case "battle_legendary_unova": //BW Unova Legendary Battle - return 13.855; - case "battle_legendary_kyurem": //BW Kyurem Battle - return 18.314; - case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle - return 18.329; - case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle - return 26.468; - case "battle_legendary_tapu": //SM Tapu Battle - return 0.000; - case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle - return 6.525; - case "battle_legendary_ub": //SM Ultra Beast Battle - return 9.818; - case "battle_legendary_dusk_dawn": //USUM Dusk Mane & Dawn Wings Necrozma Battle - return 5.211; - case "battle_legendary_ultra_nec": //USUM Ultra Necrozma Battle - return 10.344; - case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle - return 11.424; - case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle - return 12.503; - case "battle_legendary_calyrex": //SWSH Calyrex Battle - return 50.641; - case "battle_legendary_riders": //SWSH Ice & Shadow Rider Calyrex Battle - return 18.155; - case "battle_legendary_birds_galar": //SWSH Galarian Legendary Birds Battle - return 0.175; - case "battle_legendary_ruinous": //SV Treasures of Ruin Battle - return 6.333; - case "battle_legendary_kor_mir": //SV Depths of Area Zero Battle - return 6.442; - case "battle_legendary_loyal_three": //SV Loyal Three Battle - return 6.500; - case "battle_legendary_ogerpon": //SV Ogerpon Battle - return 14.335; - case "battle_legendary_terapagos": //SV Terapagos Battle - return 24.377; - case "battle_legendary_pecharunt": //SV Pecharunt Battle - return 6.508; - case "battle_rival": //BW Rival Battle - return 14.110; - case "battle_rival_2": //BW N Battle - return 17.714; - case "battle_rival_3": //BW Final N Battle - return 17.586; - case "battle_trainer": //BW Trainer Battle - return 13.686; - case "battle_wild": //BW Wild Battle - return 12.703; - case "battle_wild_strong": //BW Strong Wild Battle - return 13.940; - case "end_summit": //PMD RTDX Sky Tower Summit - return 30.025; - case "battle_rocket_grunt": //HGSS Team Rocket Battle - return 12.707; - case "battle_aqua_magma_grunt": //ORAS Team Aqua & Magma Battle - return 12.062; - case "battle_galactic_grunt": //BDSP Team Galactic Battle - return 13.043; - case "battle_plasma_grunt": //BW Team Plasma Battle - return 12.974; - case "battle_flare_grunt": //XY Team Flare Battle - return 4.228; - case "battle_aether_grunt": // SM Aether Foundation Battle - return 16.00; - case "battle_skull_grunt": // SM Team Skull Battle - return 20.87; - case "battle_macro_grunt": // SWSH Trainer Battle - return 11.56; - case "battle_star_grunt": //SV Team Star Battle - return 133.362; - case "battle_galactic_admin": //BDSP Team Galactic Admin Battle - return 11.997; - case "battle_skull_admin": //SM Team Skull Admin Battle - return 15.463; - case "battle_oleana": //SWSH Oleana Battle - return 14.110; - case "battle_star_admin": //SV Team Star Boss Battle - return 9.493; - case "battle_rocket_boss": //USUM Giovanni Battle - return 9.115; - case "battle_aqua_magma_boss": //ORAS Archie & Maxie Battle - return 14.847; - case "battle_galactic_boss": //BDSP Cyrus Battle - return 106.962; - case "battle_plasma_boss": //B2W2 Ghetsis Battle - return 25.624; - case "battle_flare_boss": //XY Lysandre Battle - return 8.085; - case "battle_aether_boss": //SM Lusamine Battle - return 11.33; - case "battle_skull_boss": //SM Guzma Battle - return 13.13; - case "battle_macro_boss": //SWSH Rose Battle - return 11.42; - case "battle_star_boss": //SV Cassiopeia Battle - return 25.764; - case "mystery_encounter_gen_5_gts": // BW GTS - return 8.52; - case "mystery_encounter_gen_6_gts": // XY GTS - return 9.24; - case "mystery_encounter_fun_and_games": // EoS Guildmaster Wigglytuff - return 4.78; - case "mystery_encounter_weird_dream": // EoS Temporal Spire - return 41.42; - case "mystery_encounter_delibirdy": // Firel Delibirdy - return 82.28; + case "battle_kanto_champion": //B2W2 Kanto Champion Battle + return 13.950; + case "battle_johto_champion": //B2W2 Johto Champion Battle + return 23.498; + case "battle_hoenn_champion_g5": //B2W2 Hoenn Champion Battle + return 11.328; + case "battle_hoenn_champion_g6": //ORAS Hoenn Champion Battle + return 11.762; + case "battle_sinnoh_champion": //B2W2 Sinnoh Champion Battle + return 12.235; + case "battle_champion_alder": //BW Unova Champion Battle + return 27.653; + case "battle_champion_iris": //B2W2 Unova Champion Battle + return 10.145; + case "battle_kalos_champion": //XY Kalos Champion Battle + return 10.380; + case "battle_alola_champion": //USUM Alola Champion Battle + return 13.025; + case "battle_galar_champion": //SWSH Galar Champion Battle + return 61.635; + case "battle_champion_geeta": //SV Champion Geeta Battle + return 37.447; + case "battle_champion_nemona": //SV Champion Nemona Battle + return 14.914; + case "battle_champion_kieran": //SV Champion Kieran Battle + return 7.206; + case "battle_hoenn_elite": //ORAS Elite Four Battle + return 11.350; + case "battle_unova_elite": //BW Elite Four Battle + return 17.730; + case "battle_kalos_elite": //XY Elite Four Battle + return 12.340; + case "battle_alola_elite": //SM Elite Four Battle + return 19.212; + case "battle_galar_elite": //SWSH League Tournament Battle + return 164.069; + case "battle_paldea_elite": //SV Elite Four Battle + return 12.770; + case "battle_bb_elite": //SV BB League Elite Four Battle + return 19.434; + case "battle_final_encounter": //PMD RTDX Rayquaza's Domain + return 19.159; + case "battle_final": //BW Ghetsis Battle + return 16.453; + case "battle_kanto_gym": //B2W2 Kanto Gym Battle + return 13.857; + case "battle_johto_gym": //B2W2 Johto Gym Battle + return 12.911; + case "battle_hoenn_gym": //B2W2 Hoenn Gym Battle + return 12.379; + case "battle_sinnoh_gym": //B2W2 Sinnoh Gym Battle + return 13.122; + case "battle_unova_gym": //BW Unova Gym Battle + return 19.145; + case "battle_kalos_gym": //XY Kalos Gym Battle + return 44.810; + case "battle_galar_gym": //SWSH Galar Gym Battle + return 171.262; + case "battle_paldea_gym": //SV Paldea Gym Battle + return 127.489; + case "battle_legendary_kanto": //XY Kanto Legendary Battle + return 32.966; + case "battle_legendary_raikou": //HGSS Raikou Battle + return 12.632; + case "battle_legendary_entei": //HGSS Entei Battle + return 2.905; + case "battle_legendary_suicune": //HGSS Suicune Battle + return 12.636; + case "battle_legendary_lugia": //HGSS Lugia Battle + return 19.770; + case "battle_legendary_ho_oh": //HGSS Ho-oh Battle + return 17.668; + case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle + return 49.500; + case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle + return 21.130; + case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle + return 10.547; + case "battle_legendary_rayquaza": //ORAS Rayquaza Battle + return 10.495; + case "battle_legendary_deoxys": //ORAS Deoxys Battle + return 13.333; + case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle + return 16.887; + case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle + return 22.770; + case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle + return 16.009; + case "battle_legendary_origin_forme": //LA Origin Dialga & Palkia Battle + return 18.961; + case "battle_legendary_giratina": //ORAS Giratina Battle + return 10.451; + case "battle_legendary_arceus": //HGSS Arceus Battle + return 9.595; + case "battle_legendary_unova": //BW Unova Legendary Battle + return 13.855; + case "battle_legendary_kyurem": //BW Kyurem Battle + return 18.314; + case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle + return 18.329; + case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle + return 26.468; + case "battle_legendary_tapu": //SM Tapu Battle + return 0.000; + case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle + return 6.525; + case "battle_legendary_ub": //SM Ultra Beast Battle + return 9.818; + case "battle_legendary_dusk_dawn": //USUM Dusk Mane & Dawn Wings Necrozma Battle + return 5.211; + case "battle_legendary_ultra_nec": //USUM Ultra Necrozma Battle + return 10.344; + case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle + return 11.424; + case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle + return 12.503; + case "battle_legendary_calyrex": //SWSH Calyrex Battle + return 50.641; + case "battle_legendary_riders": //SWSH Ice & Shadow Rider Calyrex Battle + return 18.155; + case "battle_legendary_birds_galar": //SWSH Galarian Legendary Birds Battle + return 0.175; + case "battle_legendary_ruinous": //SV Treasures of Ruin Battle + return 6.333; + case "battle_legendary_kor_mir": //SV Depths of Area Zero Battle + return 6.442; + case "battle_legendary_loyal_three": //SV Loyal Three Battle + return 6.500; + case "battle_legendary_ogerpon": //SV Ogerpon Battle + return 14.335; + case "battle_legendary_terapagos": //SV Terapagos Battle + return 24.377; + case "battle_legendary_pecharunt": //SV Pecharunt Battle + return 6.508; + case "battle_rival": //BW Rival Battle + return 14.110; + case "battle_rival_2": //BW N Battle + return 17.714; + case "battle_rival_3": //BW Final N Battle + return 17.586; + case "battle_trainer": //BW Trainer Battle + return 13.686; + case "battle_wild": //BW Wild Battle + return 12.703; + case "battle_wild_strong": //BW Strong Wild Battle + return 13.940; + case "end_summit": //PMD RTDX Sky Tower Summit + return 30.025; + case "battle_rocket_grunt": //HGSS Team Rocket Battle + return 12.707; + case "battle_aqua_magma_grunt": //ORAS Team Aqua & Magma Battle + return 12.062; + case "battle_galactic_grunt": //BDSP Team Galactic Battle + return 13.043; + case "battle_plasma_grunt": //BW Team Plasma Battle + return 12.974; + case "battle_flare_grunt": //XY Team Flare Battle + return 4.228; + case "battle_aether_grunt": // SM Aether Foundation Battle + return 16.00; + case "battle_skull_grunt": // SM Team Skull Battle + return 20.87; + case "battle_macro_grunt": // SWSH Trainer Battle + return 11.56; + case "battle_star_grunt": //SV Team Star Battle + return 133.362; + case "battle_galactic_admin": //BDSP Team Galactic Admin Battle + return 11.997; + case "battle_skull_admin": //SM Team Skull Admin Battle + return 15.463; + case "battle_oleana": //SWSH Oleana Battle + return 14.110; + case "battle_star_admin": //SV Team Star Boss Battle + return 9.493; + case "battle_rocket_boss": //USUM Giovanni Battle + return 9.115; + case "battle_aqua_magma_boss": //ORAS Archie & Maxie Battle + return 14.847; + case "battle_galactic_boss": //BDSP Cyrus Battle + return 106.962; + case "battle_plasma_boss": //B2W2 Ghetsis Battle + return 25.624; + case "battle_flare_boss": //XY Lysandre Battle + return 8.085; + case "battle_aether_boss": //SM Lusamine Battle + return 11.33; + case "battle_skull_boss": //SM Guzma Battle + return 13.13; + case "battle_macro_boss": //SWSH Rose Battle + return 11.42; + case "battle_star_boss": //SV Cassiopeia Battle + return 25.764; + case "mystery_encounter_gen_5_gts": // BW GTS + return 8.52; + case "mystery_encounter_gen_6_gts": // XY GTS + return 9.24; + case "mystery_encounter_fun_and_games": // EoS Guildmaster Wigglytuff + return 4.78; + case "mystery_encounter_weird_dream": // EoS Temporal Spire + return 41.42; + case "mystery_encounter_delibirdy": // Firel Delibirdy + return 82.28; } return 0; diff --git a/src/data/ability.ts b/src/data/ability.ts index 5d2ccfc9d36..33f6e0522f7 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2579,24 +2579,24 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { // Clear weather only if user's ability matches the weather and no other pokemon has the ability. switch (weatherType) { - case (WeatherType.HARSH_SUN): - if (pokemon.hasAbility(Abilities.DESOLATE_LAND) + case (WeatherType.HARSH_SUN): + if (pokemon.hasAbility(Abilities.DESOLATE_LAND) && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.HEAVY_RAIN): - if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) + turnOffWeather = true; + } + break; + case (WeatherType.HEAVY_RAIN): + if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.STRONG_WINDS): - if (pokemon.hasAbility(Abilities.DELTA_STREAM) + turnOffWeather = true; + } + break; + case (WeatherType.STRONG_WINDS): + if (pokemon.hasAbility(Abilities.DELTA_STREAM) && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { - turnOffWeather = true; - } - break; + turnOffWeather = true; + } + break; } if (simulated) { @@ -4079,24 +4079,24 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr { // Clear weather only if user's ability matches the weather and no other pokemon has the ability. switch (weatherType) { - case (WeatherType.HARSH_SUN): - if (pokemon.hasAbility(Abilities.DESOLATE_LAND) + case (WeatherType.HARSH_SUN): + if (pokemon.hasAbility(Abilities.DESOLATE_LAND) && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.HEAVY_RAIN): - if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) + turnOffWeather = true; + } + break; + case (WeatherType.HEAVY_RAIN): + if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.STRONG_WINDS): - if (pokemon.hasAbility(Abilities.DELTA_STREAM) + turnOffWeather = true; + } + break; + case (WeatherType.STRONG_WINDS): + if (pokemon.hasAbility(Abilities.DELTA_STREAM) && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { - turnOffWeather = true; - } - break; + turnOffWeather = true; + } + break; } if (simulated) { diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 2bd6ae09877..6507d34dcfd 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -88,13 +88,13 @@ export abstract class ArenaTag { */ public getAffectedPokemon(scene: BattleScene): Pokemon[] { switch (this.side) { - case ArenaTagSide.PLAYER: - return scene.getPlayerField() ?? []; - case ArenaTagSide.ENEMY: - return scene.getEnemyField() ?? []; - case ArenaTagSide.BOTH: - default: - return scene.getField(true) ?? []; + case ArenaTagSide.PLAYER: + return scene.getPlayerField() ?? []; + case ArenaTagSide.ENEMY: + return scene.getEnemyField() ?? []; + case ArenaTagSide.BOTH: + default: + return scene.getField(true) ?? []; } } } @@ -332,11 +332,11 @@ const WideGuardConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean = const move = allMoves[moveId]; switch (move.moveTarget) { - case MoveTarget.ALL_ENEMIES: - case MoveTarget.ALL_NEAR_ENEMIES: - case MoveTarget.ALL_OTHERS: - case MoveTarget.ALL_NEAR_OTHERS: - return true; + case MoveTarget.ALL_ENEMIES: + case MoveTarget.ALL_NEAR_ENEMIES: + case MoveTarget.ALL_OTHERS: + case MoveTarget.ALL_NEAR_OTHERS: + return true; } return false; }; @@ -807,24 +807,24 @@ class StealthRockTag extends ArenaTrapTag { let damageHpRatio: number = 0; switch (effectiveness) { - case 0: - damageHpRatio = 0; - break; - case 0.25: - damageHpRatio = 0.03125; - break; - case 0.5: - damageHpRatio = 0.0625; - break; - case 1: - damageHpRatio = 0.125; - break; - case 2: - damageHpRatio = 0.25; - break; - case 4: - damageHpRatio = 0.5; - break; + case 0: + damageHpRatio = 0; + break; + case 0.25: + damageHpRatio = 0.03125; + break; + case 0.5: + damageHpRatio = 0.0625; + break; + case 1: + damageHpRatio = 0.125; + break; + case 2: + damageHpRatio = 0.25; + break; + case 4: + damageHpRatio = 0.5; + break; } return damageHpRatio; @@ -1185,63 +1185,63 @@ class GrassWaterPledgeTag extends ArenaTag { // TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null { switch (tagType) { - case ArenaTagType.MIST: - return new MistTag(turnCount, sourceId, side); - case ArenaTagType.QUICK_GUARD: - return new QuickGuardTag(sourceId, side); - case ArenaTagType.WIDE_GUARD: - return new WideGuardTag(sourceId, side); - case ArenaTagType.MAT_BLOCK: - return new MatBlockTag(sourceId, side); - case ArenaTagType.CRAFTY_SHIELD: - return new CraftyShieldTag(sourceId, side); - case ArenaTagType.NO_CRIT: - return new NoCritTag(turnCount, sourceMove!, sourceId, side); // TODO: is this bang correct? - case ArenaTagType.MUD_SPORT: - return new MudSportTag(turnCount, sourceId); - case ArenaTagType.WATER_SPORT: - return new WaterSportTag(turnCount, sourceId); - case ArenaTagType.ION_DELUGE: - return new IonDelugeTag(sourceMove); - case ArenaTagType.SPIKES: - return new SpikesTag(sourceId, side); - case ArenaTagType.TOXIC_SPIKES: - return new ToxicSpikesTag(sourceId, side); - case ArenaTagType.FUTURE_SIGHT: - case ArenaTagType.DOOM_DESIRE: - return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang - case ArenaTagType.WISH: - return new WishTag(turnCount, sourceId, side); - case ArenaTagType.STEALTH_ROCK: - return new StealthRockTag(sourceId, side); - case ArenaTagType.STICKY_WEB: - return new StickyWebTag(sourceId, side); - case ArenaTagType.TRICK_ROOM: - return new TrickRoomTag(turnCount, sourceId); - case ArenaTagType.GRAVITY: - return new GravityTag(turnCount); - case ArenaTagType.REFLECT: - return new ReflectTag(turnCount, sourceId, side); - case ArenaTagType.LIGHT_SCREEN: - return new LightScreenTag(turnCount, sourceId, side); - case ArenaTagType.AURORA_VEIL: - return new AuroraVeilTag(turnCount, sourceId, side); - case ArenaTagType.TAILWIND: - return new TailwindTag(turnCount, sourceId, side); - case ArenaTagType.HAPPY_HOUR: - return new HappyHourTag(turnCount, sourceId, side); - case ArenaTagType.SAFEGUARD: - return new SafeguardTag(turnCount, sourceId, side); - case ArenaTagType.IMPRISON: - return new ImprisonTag(sourceId, side); - case ArenaTagType.FIRE_GRASS_PLEDGE: - return new FireGrassPledgeTag(sourceId, side); - case ArenaTagType.WATER_FIRE_PLEDGE: - return new WaterFirePledgeTag(sourceId, side); - case ArenaTagType.GRASS_WATER_PLEDGE: - return new GrassWaterPledgeTag(sourceId, side); - default: - return null; + case ArenaTagType.MIST: + return new MistTag(turnCount, sourceId, side); + case ArenaTagType.QUICK_GUARD: + return new QuickGuardTag(sourceId, side); + case ArenaTagType.WIDE_GUARD: + return new WideGuardTag(sourceId, side); + case ArenaTagType.MAT_BLOCK: + return new MatBlockTag(sourceId, side); + case ArenaTagType.CRAFTY_SHIELD: + return new CraftyShieldTag(sourceId, side); + case ArenaTagType.NO_CRIT: + return new NoCritTag(turnCount, sourceMove!, sourceId, side); // TODO: is this bang correct? + case ArenaTagType.MUD_SPORT: + return new MudSportTag(turnCount, sourceId); + case ArenaTagType.WATER_SPORT: + return new WaterSportTag(turnCount, sourceId); + case ArenaTagType.ION_DELUGE: + return new IonDelugeTag(sourceMove); + case ArenaTagType.SPIKES: + return new SpikesTag(sourceId, side); + case ArenaTagType.TOXIC_SPIKES: + return new ToxicSpikesTag(sourceId, side); + case ArenaTagType.FUTURE_SIGHT: + case ArenaTagType.DOOM_DESIRE: + return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang + case ArenaTagType.WISH: + return new WishTag(turnCount, sourceId, side); + case ArenaTagType.STEALTH_ROCK: + return new StealthRockTag(sourceId, side); + case ArenaTagType.STICKY_WEB: + return new StickyWebTag(sourceId, side); + case ArenaTagType.TRICK_ROOM: + return new TrickRoomTag(turnCount, sourceId); + case ArenaTagType.GRAVITY: + return new GravityTag(turnCount); + case ArenaTagType.REFLECT: + return new ReflectTag(turnCount, sourceId, side); + case ArenaTagType.LIGHT_SCREEN: + return new LightScreenTag(turnCount, sourceId, side); + case ArenaTagType.AURORA_VEIL: + return new AuroraVeilTag(turnCount, sourceId, side); + case ArenaTagType.TAILWIND: + return new TailwindTag(turnCount, sourceId, side); + case ArenaTagType.HAPPY_HOUR: + return new HappyHourTag(turnCount, sourceId, side); + case ArenaTagType.SAFEGUARD: + return new SafeguardTag(turnCount, sourceId, side); + case ArenaTagType.IMPRISON: + return new ImprisonTag(sourceId, side); + case ArenaTagType.FIRE_GRASS_PLEDGE: + return new FireGrassPledgeTag(sourceId, side); + case ArenaTagType.WATER_FIRE_PLEDGE: + return new WaterFirePledgeTag(sourceId, side); + case ArenaTagType.GRASS_WATER_PLEDGE: + return new GrassWaterPledgeTag(sourceId, side); + default: + return null; } } diff --git a/src/data/balance/biomes.ts b/src/data/balance/biomes.ts index 7ef83b654db..2ce693c360b 100644 --- a/src/data/balance/biomes.ts +++ b/src/data/balance/biomes.ts @@ -13,14 +13,14 @@ export function getBiomeName(biome: Biome | -1) { return i18next.t("biome:unknownLocation"); } switch (biome) { - case Biome.GRASS: - return i18next.t("biome:GRASS"); - case Biome.RUINS: - return i18next.t("biome:RUINS"); - case Biome.END: - return i18next.t("biome:END"); - default: - return i18next.t(`biome:${Biome[biome].toUpperCase()}`); + case Biome.GRASS: + return i18next.t("biome:GRASS"); + case Biome.RUINS: + return i18next.t("biome:RUINS"); + case Biome.END: + return i18next.t("biome:END"); + default: + return i18next.t(`biome:${Biome[biome].toUpperCase()}`); } } diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts index bf3a1f7ad56..d6a1f0c3eaf 100644 --- a/src/data/balance/starters.ts +++ b/src/data/balance/starters.ts @@ -15,25 +15,25 @@ export const FRIENDSHIP_LOSS_FROM_FAINT = 10; */ export function getStarterValueFriendshipCap(starterCost: number): number { switch (starterCost) { - case 1: - return 20; - case 2: - return 40; - case 3: - return 60; - case 4: - return 100; - case 5: - return 140; - case 6: - return 200; - case 7: - return 280; - case 8: - case 9: - return 450; - default: - return 600; + case 1: + return 20; + case 2: + return 40; + case 3: + return 60; + case 4: + return 100; + case 5: + return 140; + case 6: + return 200; + case 7: + return 280; + case 8: + case 9: + return 450; + default: + return 600; } } diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 939f834cccc..03bf0809fa6 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -134,15 +134,15 @@ export class AnimConfig { for (const te of frameTimedEvents[fte]) { let timedEvent: AnimTimedEvent | undefined; switch (te.eventType) { - case "AnimTimedSoundEvent": - timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te); - break; - case "AnimTimedAddBgEvent": - timedEvent = new AnimTimedAddBgEvent(te.frameIndex, te.resourceName, te); - break; - case "AnimTimedUpdateBgEvent": - timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te); - break; + case "AnimTimedSoundEvent": + timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te); + break; + case "AnimTimedAddBgEvent": + timedEvent = new AnimTimedAddBgEvent(te.frameIndex, te.resourceName, te); + break; + case "AnimTimedUpdateBgEvent": + timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te); + break; } timedEvent && timedEvents.push(timedEvent); @@ -243,12 +243,12 @@ class AnimFrame { if (!init) { let target = AnimFrameTarget.GRAPHIC; switch (pattern) { - case -2: - target = AnimFrameTarget.TARGET; - break; - case -1: - target = AnimFrameTarget.USER; - break; + case -2: + target = AnimFrameTarget.TARGET; + break; + case -1: + target = AnimFrameTarget.USER; + break; } this.target = target; this.graphicFrame = pattern >= 0 ? pattern : 0; @@ -803,23 +803,23 @@ export abstract class BattleAnim { let scaleX = (frame.zoomX / 100) * (!frame.mirror ? 1 : -1); const scaleY = (frame.zoomY / 100); switch (frame.focus) { - case AnimFocus.TARGET: - x += targetInitialX - targetFocusX; - y += (targetInitialY - targetHalfHeight) - targetFocusY; - break; - case AnimFocus.USER: - x += userInitialX - userFocusX; - y += (userInitialY - userHalfHeight) - userFocusY; - break; - case AnimFocus.USER_TARGET: - const point = transformPoint(this.srcLine[0], this.srcLine[1], this.srcLine[2], this.srcLine[3], - this.dstLine[0], this.dstLine[1] - userHalfHeight, this.dstLine[2], this.dstLine[3] - targetHalfHeight, x, y); - x = point[0]; - y = point[1]; - if (frame.target === AnimFrameTarget.GRAPHIC && isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2])) { - scaleX = scaleX * -1; - } - break; + case AnimFocus.TARGET: + x += targetInitialX - targetFocusX; + y += (targetInitialY - targetHalfHeight) - targetFocusY; + break; + case AnimFocus.USER: + x += userInitialX - userFocusX; + y += (userInitialY - userHalfHeight) - userFocusY; + break; + case AnimFocus.USER_TARGET: + const point = transformPoint(this.srcLine[0], this.srcLine[1], this.srcLine[2], this.srcLine[3], + this.dstLine[0], this.dstLine[1] - userHalfHeight, this.dstLine[2], this.dstLine[3] - targetHalfHeight, x, y); + x = point[0]; + y = point[1]; + if (frame.target === AnimFrameTarget.GRAPHIC && isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2])) { + scaleX = scaleX * -1; + } + break; } const angle = -frame.angle; const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++; @@ -993,44 +993,44 @@ export abstract class BattleAnim { spritePriorities[graphicIndex] = frame.priority; const setSpritePriority = (priority: integer) => { switch (priority) { - case 0: - scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon()!); // This bang assumes that if (the EnemyPokemon is undefined, then the PlayerPokemon function must return an object), correct assumption? - break; - case 1: - scene.field.moveTo(moveSprite, scene.field.getAll().length - 1); - break; - case 2: - switch (frame.focus) { - case AnimFocus.USER: - if (this.bgSprite) { - scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite); - } else { - scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? + case 0: + scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon()!); // This bang assumes that if (the EnemyPokemon is undefined, then the PlayerPokemon function must return an object), correct assumption? + break; + case 1: + scene.field.moveTo(moveSprite, scene.field.getAll().length - 1); + break; + case 2: + switch (frame.focus) { + case AnimFocus.USER: + if (this.bgSprite) { + scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite); + } else { + scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? + } + break; + case AnimFocus.TARGET: + scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? + break; + default: + setSpritePriority(1); + break; } break; - case AnimFocus.TARGET: - scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? + case 3: + switch (frame.focus) { + case AnimFocus.USER: + scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? + break; + case AnimFocus.TARGET: + scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? + break; + default: + setSpritePriority(1); + break; + } break; default: setSpritePriority(1); - break; - } - break; - case 3: - switch (frame.focus) { - case AnimFocus.USER: - scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? - break; - case AnimFocus.TARGET: - scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? - break; - default: - setSpritePriority(1); - break; - } - break; - default: - setSpritePriority(1); } }; setSpritePriority(frame.priority); @@ -1396,108 +1396,108 @@ export async function populateAnims() { const fieldName = field.slice(0, field.indexOf(":")); const fieldData = field.slice(fieldName.length + 1, field.lastIndexOf("\n")).trim(); switch (fieldName) { - case "array": - const framesData = fieldData.split(" - - - ").slice(1); - for (let fd = 0; fd < framesData.length; fd++) { - anim.frames.push([]); - const frameData = framesData[fd]; - const focusFramesData = frameData.split(" - - "); - for (let tf = 0; tf < focusFramesData.length; tf++) { - const values = focusFramesData[tf].replace(/ \- /g, "").split("\n"); - const targetFrame = new AnimFrame(parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[11]), parseFloat(values[3]), - parseInt(values[4]) === 1, parseInt(values[6]) === 1, parseInt(values[5]), parseInt(values[7]), parseInt(values[8]), parseInt(values[12]), parseInt(values[13]), - parseInt(values[14]), parseInt(values[15]), parseInt(values[16]), parseInt(values[17]), parseInt(values[18]), parseInt(values[19]), - parseInt(values[21]), parseInt(values[22]), parseInt(values[23]), parseInt(values[24]), parseInt(values[20]) === 1, parseInt(values[25]), parseInt(values[26]) as AnimFocus); - anim.frames[fd].push(targetFrame); + case "array": + const framesData = fieldData.split(" - - - ").slice(1); + for (let fd = 0; fd < framesData.length; fd++) { + anim.frames.push([]); + const frameData = framesData[fd]; + const focusFramesData = frameData.split(" - - "); + for (let tf = 0; tf < focusFramesData.length; tf++) { + const values = focusFramesData[tf].replace(/ \- /g, "").split("\n"); + const targetFrame = new AnimFrame(parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[11]), parseFloat(values[3]), + parseInt(values[4]) === 1, parseInt(values[6]) === 1, parseInt(values[5]), parseInt(values[7]), parseInt(values[8]), parseInt(values[12]), parseInt(values[13]), + parseInt(values[14]), parseInt(values[15]), parseInt(values[16]), parseInt(values[17]), parseInt(values[18]), parseInt(values[19]), + parseInt(values[21]), parseInt(values[22]), parseInt(values[23]), parseInt(values[24]), parseInt(values[20]) === 1, parseInt(values[25]), parseInt(values[26]) as AnimFocus); + anim.frames[fd].push(targetFrame); + } } - } - break; - case "graphic": - const graphic = fieldData !== "''" ? fieldData : ""; - anim.graphic = graphic.indexOf(".") > -1 - ? graphic.slice(0, fieldData.indexOf(".")) - : graphic; - break; - case "timing": - const timingEntries = fieldData.split("- !ruby/object:PBAnimTiming ").slice(1); - for (let t = 0; t < timingEntries.length; t++) { - const timingData = timingEntries[t].replace(/\n/g, " ").replace(/[ ]{2,}/g, " ").replace(/[a-z]+: ! '', /ig, "").replace(/name: (.*?),/, "name: \"$1\",") - .replace(/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/, "flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1"); - const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct? - let resourceName = /name: "(.*?)"/.exec(timingData)![1].replace("''", ""); // TODO: is the bang correct? - const timingType = parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct? - let timedEvent: AnimTimedEvent | undefined; - switch (timingType) { - case 0: - if (resourceName && resourceName.indexOf(".") === -1) { - let ext: string | undefined; - [ "wav", "mp3", "m4a" ].every(e => { - if (seNames.indexOf(`${resourceName}.${e}`) > -1) { - ext = e; - return false; + break; + case "graphic": + const graphic = fieldData !== "''" ? fieldData : ""; + anim.graphic = graphic.indexOf(".") > -1 + ? graphic.slice(0, fieldData.indexOf(".")) + : graphic; + break; + case "timing": + const timingEntries = fieldData.split("- !ruby/object:PBAnimTiming ").slice(1); + for (let t = 0; t < timingEntries.length; t++) { + const timingData = timingEntries[t].replace(/\n/g, " ").replace(/[ ]{2,}/g, " ").replace(/[a-z]+: ! '', /ig, "").replace(/name: (.*?),/, "name: \"$1\",") + .replace(/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/, "flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1"); + const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct? + let resourceName = /name: "(.*?)"/.exec(timingData)![1].replace("''", ""); // TODO: is the bang correct? + const timingType = parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct? + let timedEvent: AnimTimedEvent | undefined; + switch (timingType) { + case 0: + if (resourceName && resourceName.indexOf(".") === -1) { + let ext: string | undefined; + [ "wav", "mp3", "m4a" ].every(e => { + if (seNames.indexOf(`${resourceName}.${e}`) > -1) { + ext = e; + return false; + } + return true; + }); + if (!ext) { + ext = ".wav"; + } + resourceName += `.${ext}`; } - return true; - }); - if (!ext) { - ext = ".wav"; + timedEvent = new AnimTimedSoundEvent(frameIndex, resourceName); + break; + case 1: + timedEvent = new AnimTimedAddBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf("."))); + break; + case 2: + timedEvent = new AnimTimedUpdateBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf("."))); + break; + } + if (!timedEvent) { + continue; + } + const propPattern = /([a-z]+): (.*?)(?:,|\})/ig; + let propMatch: RegExpExecArray; + while ((propMatch = propPattern.exec(timingData)!)) { // TODO: is this bang correct? + const prop = propMatch[1]; + let value: any = propMatch[2]; + switch (prop) { + case "bgX": + case "bgY": + value = parseFloat(value); + break; + case "volume": + case "pitch": + case "opacity": + case "colorRed": + case "colorGreen": + case "colorBlue": + case "colorAlpha": + case "duration": + case "flashScope": + case "flashRed": + case "flashGreen": + case "flashBlue": + case "flashAlpha": + case "flashDuration": + value = parseInt(value); + break; + } + if (timedEvent.hasOwnProperty(prop)) { + timedEvent[prop] = value; } - resourceName += `.${ext}`; } - timedEvent = new AnimTimedSoundEvent(frameIndex, resourceName); - break; - case 1: - timedEvent = new AnimTimedAddBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf("."))); - break; - case 2: - timedEvent = new AnimTimedUpdateBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf("."))); - break; - } - if (!timedEvent) { - continue; - } - const propPattern = /([a-z]+): (.*?)(?:,|\})/ig; - let propMatch: RegExpExecArray; - while ((propMatch = propPattern.exec(timingData)!)) { // TODO: is this bang correct? - const prop = propMatch[1]; - let value: any = propMatch[2]; - switch (prop) { - case "bgX": - case "bgY": - value = parseFloat(value); - break; - case "volume": - case "pitch": - case "opacity": - case "colorRed": - case "colorGreen": - case "colorBlue": - case "colorAlpha": - case "duration": - case "flashScope": - case "flashRed": - case "flashGreen": - case "flashBlue": - case "flashAlpha": - case "flashDuration": - value = parseInt(value); - break; + if (!anim.frameTimedEvents.has(frameIndex)) { + anim.frameTimedEvents.set(frameIndex, []); } - if (timedEvent.hasOwnProperty(prop)) { - timedEvent[prop] = value; - } - } - if (!anim.frameTimedEvents.has(frameIndex)) { - anim.frameTimedEvents.set(frameIndex, []); - } anim.frameTimedEvents.get(frameIndex)!.push(timedEvent); // TODO: is this bang correct? - } - break; - case "position": - anim.position = parseInt(fieldData); - break; - case "hue": - anim.hue = parseInt(fieldData); - break; + } + break; + case "position": + anim.position = parseInt(fieldData); + break; + case "hue": + anim.hue = parseInt(fieldData); + break; } } } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 8491307fc76..4977a8da5a9 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -919,14 +919,14 @@ export class EncoreTag extends BattlerTag { } switch (repeatableMove.move) { - case Moves.MIMIC: - case Moves.MIRROR_MOVE: - case Moves.TRANSFORM: - case Moves.STRUGGLE: - case Moves.SKETCH: - case Moves.SLEEP_TALK: - case Moves.ENCORE: - return false; + case Moves.MIMIC: + case Moves.MIRROR_MOVE: + case Moves.TRANSFORM: + case Moves.STRUGGLE: + case Moves.SKETCH: + case Moves.SLEEP_TALK: + case Moves.ENCORE: + return false; } if (allMoves[repeatableMove.move].hasAttr(ChargeAttr) && repeatableMove.result === MoveResult.OTHER) { @@ -1641,12 +1641,12 @@ export class HighestStatBoostTag extends AbilityBattlerTag { this.stat = highestStat; switch (this.stat) { - case Stat.SPD: - this.multiplier = 1.5; - break; - default: - this.multiplier = 1.3; - break; + case Stat.SPD: + this.multiplier = 1.5; + break; + default: + this.multiplier = 1.3; + break; } pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: i18next.t(getStatKey(highestStat)) }), null, false, null, true); @@ -2436,15 +2436,15 @@ export class SubstituteTag extends BattlerTag { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { switch (lapseType) { - case BattlerTagLapseType.PRE_MOVE: - this.onPreMove(pokemon); - break; - case BattlerTagLapseType.AFTER_MOVE: - this.onAfterMove(pokemon); - break; - case BattlerTagLapseType.HIT: - this.onHit(pokemon); - break; + case BattlerTagLapseType.PRE_MOVE: + this.onPreMove(pokemon); + break; + case BattlerTagLapseType.AFTER_MOVE: + this.onAfterMove(pokemon); + break; + case BattlerTagLapseType.HIT: + this.onHit(pokemon); + break; } return lapseType !== BattlerTagLapseType.CUSTOM; // only remove this tag on custom lapse } @@ -2769,179 +2769,179 @@ export class PowerTrickTag extends BattlerTag { */ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag { switch (tagType) { - case BattlerTagType.RECHARGING: - return new RechargingTag(sourceMove); - case BattlerTagType.BEAK_BLAST_CHARGING: - return new BeakBlastChargingTag(); - case BattlerTagType.SHELL_TRAP: - return new ShellTrapTag(); - case BattlerTagType.FLINCHED: - return new FlinchedTag(sourceMove); - case BattlerTagType.INTERRUPTED: - return new InterruptedTag(sourceMove); - case BattlerTagType.CONFUSED: - return new ConfusedTag(turnCount, sourceMove); - case BattlerTagType.INFATUATED: - return new InfatuatedTag(sourceMove, sourceId); - case BattlerTagType.SEEDED: - return new SeedTag(sourceId); - case BattlerTagType.NIGHTMARE: - return new NightmareTag(); - case BattlerTagType.FRENZY: - return new FrenzyTag(turnCount, sourceMove, sourceId); - case BattlerTagType.CHARGING: - return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove, sourceId); - case BattlerTagType.ENCORE: - return new EncoreTag(sourceId); - case BattlerTagType.HELPING_HAND: - return new HelpingHandTag(sourceId); - case BattlerTagType.INGRAIN: - return new IngrainTag(sourceId); - case BattlerTagType.AQUA_RING: - return new AquaRingTag(); - case BattlerTagType.DROWSY: - return new DrowsyTag(); - case BattlerTagType.TRAPPED: - return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); - case BattlerTagType.NO_RETREAT: - return new NoRetreatTag(sourceId); - case BattlerTagType.BIND: - return new BindTag(turnCount, sourceId); - case BattlerTagType.WRAP: - return new WrapTag(turnCount, sourceId); - case BattlerTagType.FIRE_SPIN: - return new FireSpinTag(turnCount, sourceId); - case BattlerTagType.WHIRLPOOL: - return new WhirlpoolTag(turnCount, sourceId); - case BattlerTagType.CLAMP: - return new ClampTag(turnCount, sourceId); - case BattlerTagType.SAND_TOMB: - return new SandTombTag(turnCount, sourceId); - case BattlerTagType.MAGMA_STORM: - return new MagmaStormTag(turnCount, sourceId); - case BattlerTagType.SNAP_TRAP: - return new SnapTrapTag(turnCount, sourceId); - case BattlerTagType.THUNDER_CAGE: - return new ThunderCageTag(turnCount, sourceId); - case BattlerTagType.INFESTATION: - return new InfestationTag(turnCount, sourceId); - case BattlerTagType.PROTECTED: - return new ProtectedTag(sourceMove); - case BattlerTagType.SPIKY_SHIELD: - return new ContactDamageProtectedTag(sourceMove, 8); - case BattlerTagType.KINGS_SHIELD: - return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1); - case BattlerTagType.OBSTRUCT: - return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2); - case BattlerTagType.SILK_TRAP: - return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1); - case BattlerTagType.BANEFUL_BUNKER: - return new ContactPoisonProtectedTag(sourceMove); - case BattlerTagType.BURNING_BULWARK: - return new ContactBurnProtectedTag(sourceMove); - case BattlerTagType.ENDURING: - return new EnduringTag(sourceMove); - case BattlerTagType.STURDY: - return new SturdyTag(sourceMove); - case BattlerTagType.PERISH_SONG: - return new PerishSongTag(turnCount); - case BattlerTagType.CENTER_OF_ATTENTION: - return new CenterOfAttentionTag(sourceMove); - case BattlerTagType.TRUANT: - return new TruantTag(); - case BattlerTagType.SLOW_START: - return new SlowStartTag(); - case BattlerTagType.PROTOSYNTHESIS: - return new WeatherHighestStatBoostTag(tagType, Abilities.PROTOSYNTHESIS, WeatherType.SUNNY, WeatherType.HARSH_SUN); - case BattlerTagType.QUARK_DRIVE: - return new TerrainHighestStatBoostTag(tagType, Abilities.QUARK_DRIVE, TerrainType.ELECTRIC); - case BattlerTagType.FLYING: - case BattlerTagType.UNDERGROUND: - case BattlerTagType.UNDERWATER: - case BattlerTagType.HIDDEN: - return new SemiInvulnerableTag(tagType, turnCount, sourceMove); - case BattlerTagType.FIRE_BOOST: - return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false); - case BattlerTagType.CRIT_BOOST: - return new CritBoostTag(tagType, sourceMove); - case BattlerTagType.DRAGON_CHEER: - return new DragonCheerTag(); - case BattlerTagType.ALWAYS_CRIT: - case BattlerTagType.IGNORE_ACCURACY: - return new BattlerTag(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); - case BattlerTagType.BYPASS_SLEEP: - return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove); - case BattlerTagType.IGNORE_FLYING: - return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); - case BattlerTagType.ROOSTED: - return new RoostedTag(); - case BattlerTagType.BURNED_UP: - return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); - case BattlerTagType.DOUBLE_SHOCKED: - return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); - case BattlerTagType.SALT_CURED: - return new SaltCuredTag(sourceId); - case BattlerTagType.CURSED: - return new CursedTag(sourceId); - case BattlerTagType.CHARGED: - return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true); - case BattlerTagType.FLOATING: - return new FloatingTag(tagType, sourceMove); - case BattlerTagType.MINIMIZED: - return new MinimizeTag(); - case BattlerTagType.DESTINY_BOND: - return new DestinyBondTag(sourceMove, sourceId); - case BattlerTagType.ICE_FACE: - return new IceFaceBlockDamageTag(tagType); - case BattlerTagType.DISGUISE: - return new FormBlockDamageTag(tagType); - case BattlerTagType.STOCKPILING: - return new StockpilingTag(sourceMove); - case BattlerTagType.OCTOLOCK: - return new OctolockTag(sourceId); - case BattlerTagType.DISABLED: - return new DisabledTag(sourceId); - case BattlerTagType.IGNORE_GHOST: - return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]); - case BattlerTagType.IGNORE_DARK: - return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ]); - case BattlerTagType.GULP_MISSILE_ARROKUDA: - case BattlerTagType.GULP_MISSILE_PIKACHU: - return new GulpMissileTag(tagType, sourceMove); - case BattlerTagType.TAR_SHOT: - return new TarShotTag(); - case BattlerTagType.ELECTRIFIED: - return new ElectrifiedTag(); - case BattlerTagType.THROAT_CHOPPED: - return new ThroatChoppedTag(); - case BattlerTagType.GORILLA_TACTICS: - return new GorillaTacticsTag(); - case BattlerTagType.SUBSTITUTE: - return new SubstituteTag(sourceMove, sourceId); - case BattlerTagType.AUTOTOMIZED: - return new AutotomizedTag(); - case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON: - return new MysteryEncounterPostSummonTag(); - case BattlerTagType.HEAL_BLOCK: - return new HealBlockTag(turnCount, sourceMove); - case BattlerTagType.TORMENT: - return new TormentTag(sourceId); - case BattlerTagType.TAUNT: - return new TauntTag(); - case BattlerTagType.IMPRISON: - return new ImprisonTag(sourceId); - case BattlerTagType.SYRUP_BOMB: - return new SyrupBombTag(sourceId); - case BattlerTagType.TELEKINESIS: - return new TelekinesisTag(sourceMove); - case BattlerTagType.POWER_TRICK: - return new PowerTrickTag(sourceMove, sourceId); - case BattlerTagType.NONE: - default: - return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); + case BattlerTagType.RECHARGING: + return new RechargingTag(sourceMove); + case BattlerTagType.BEAK_BLAST_CHARGING: + return new BeakBlastChargingTag(); + case BattlerTagType.SHELL_TRAP: + return new ShellTrapTag(); + case BattlerTagType.FLINCHED: + return new FlinchedTag(sourceMove); + case BattlerTagType.INTERRUPTED: + return new InterruptedTag(sourceMove); + case BattlerTagType.CONFUSED: + return new ConfusedTag(turnCount, sourceMove); + case BattlerTagType.INFATUATED: + return new InfatuatedTag(sourceMove, sourceId); + case BattlerTagType.SEEDED: + return new SeedTag(sourceId); + case BattlerTagType.NIGHTMARE: + return new NightmareTag(); + case BattlerTagType.FRENZY: + return new FrenzyTag(turnCount, sourceMove, sourceId); + case BattlerTagType.CHARGING: + return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove, sourceId); + case BattlerTagType.ENCORE: + return new EncoreTag(sourceId); + case BattlerTagType.HELPING_HAND: + return new HelpingHandTag(sourceId); + case BattlerTagType.INGRAIN: + return new IngrainTag(sourceId); + case BattlerTagType.AQUA_RING: + return new AquaRingTag(); + case BattlerTagType.DROWSY: + return new DrowsyTag(); + case BattlerTagType.TRAPPED: + return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); + case BattlerTagType.NO_RETREAT: + return new NoRetreatTag(sourceId); + case BattlerTagType.BIND: + return new BindTag(turnCount, sourceId); + case BattlerTagType.WRAP: + return new WrapTag(turnCount, sourceId); + case BattlerTagType.FIRE_SPIN: + return new FireSpinTag(turnCount, sourceId); + case BattlerTagType.WHIRLPOOL: + return new WhirlpoolTag(turnCount, sourceId); + case BattlerTagType.CLAMP: + return new ClampTag(turnCount, sourceId); + case BattlerTagType.SAND_TOMB: + return new SandTombTag(turnCount, sourceId); + case BattlerTagType.MAGMA_STORM: + return new MagmaStormTag(turnCount, sourceId); + case BattlerTagType.SNAP_TRAP: + return new SnapTrapTag(turnCount, sourceId); + case BattlerTagType.THUNDER_CAGE: + return new ThunderCageTag(turnCount, sourceId); + case BattlerTagType.INFESTATION: + return new InfestationTag(turnCount, sourceId); + case BattlerTagType.PROTECTED: + return new ProtectedTag(sourceMove); + case BattlerTagType.SPIKY_SHIELD: + return new ContactDamageProtectedTag(sourceMove, 8); + case BattlerTagType.KINGS_SHIELD: + return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1); + case BattlerTagType.OBSTRUCT: + return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2); + case BattlerTagType.SILK_TRAP: + return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1); + case BattlerTagType.BANEFUL_BUNKER: + return new ContactPoisonProtectedTag(sourceMove); + case BattlerTagType.BURNING_BULWARK: + return new ContactBurnProtectedTag(sourceMove); + case BattlerTagType.ENDURING: + return new EnduringTag(sourceMove); + case BattlerTagType.STURDY: + return new SturdyTag(sourceMove); + case BattlerTagType.PERISH_SONG: + return new PerishSongTag(turnCount); + case BattlerTagType.CENTER_OF_ATTENTION: + return new CenterOfAttentionTag(sourceMove); + case BattlerTagType.TRUANT: + return new TruantTag(); + case BattlerTagType.SLOW_START: + return new SlowStartTag(); + case BattlerTagType.PROTOSYNTHESIS: + return new WeatherHighestStatBoostTag(tagType, Abilities.PROTOSYNTHESIS, WeatherType.SUNNY, WeatherType.HARSH_SUN); + case BattlerTagType.QUARK_DRIVE: + return new TerrainHighestStatBoostTag(tagType, Abilities.QUARK_DRIVE, TerrainType.ELECTRIC); + case BattlerTagType.FLYING: + case BattlerTagType.UNDERGROUND: + case BattlerTagType.UNDERWATER: + case BattlerTagType.HIDDEN: + return new SemiInvulnerableTag(tagType, turnCount, sourceMove); + case BattlerTagType.FIRE_BOOST: + return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false); + case BattlerTagType.CRIT_BOOST: + return new CritBoostTag(tagType, sourceMove); + case BattlerTagType.DRAGON_CHEER: + return new DragonCheerTag(); + case BattlerTagType.ALWAYS_CRIT: + case BattlerTagType.IGNORE_ACCURACY: + return new BattlerTag(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); + case BattlerTagType.BYPASS_SLEEP: + return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove); + case BattlerTagType.IGNORE_FLYING: + return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); + case BattlerTagType.ROOSTED: + return new RoostedTag(); + case BattlerTagType.BURNED_UP: + return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); + case BattlerTagType.DOUBLE_SHOCKED: + return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); + case BattlerTagType.SALT_CURED: + return new SaltCuredTag(sourceId); + case BattlerTagType.CURSED: + return new CursedTag(sourceId); + case BattlerTagType.CHARGED: + return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true); + case BattlerTagType.FLOATING: + return new FloatingTag(tagType, sourceMove); + case BattlerTagType.MINIMIZED: + return new MinimizeTag(); + case BattlerTagType.DESTINY_BOND: + return new DestinyBondTag(sourceMove, sourceId); + case BattlerTagType.ICE_FACE: + return new IceFaceBlockDamageTag(tagType); + case BattlerTagType.DISGUISE: + return new FormBlockDamageTag(tagType); + case BattlerTagType.STOCKPILING: + return new StockpilingTag(sourceMove); + case BattlerTagType.OCTOLOCK: + return new OctolockTag(sourceId); + case BattlerTagType.DISABLED: + return new DisabledTag(sourceId); + case BattlerTagType.IGNORE_GHOST: + return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]); + case BattlerTagType.IGNORE_DARK: + return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ]); + case BattlerTagType.GULP_MISSILE_ARROKUDA: + case BattlerTagType.GULP_MISSILE_PIKACHU: + return new GulpMissileTag(tagType, sourceMove); + case BattlerTagType.TAR_SHOT: + return new TarShotTag(); + case BattlerTagType.ELECTRIFIED: + return new ElectrifiedTag(); + case BattlerTagType.THROAT_CHOPPED: + return new ThroatChoppedTag(); + case BattlerTagType.GORILLA_TACTICS: + return new GorillaTacticsTag(); + case BattlerTagType.SUBSTITUTE: + return new SubstituteTag(sourceMove, sourceId); + case BattlerTagType.AUTOTOMIZED: + return new AutotomizedTag(); + case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON: + return new MysteryEncounterPostSummonTag(); + case BattlerTagType.HEAL_BLOCK: + return new HealBlockTag(turnCount, sourceMove); + case BattlerTagType.TORMENT: + return new TormentTag(sourceId); + case BattlerTagType.TAUNT: + return new TauntTag(); + case BattlerTagType.IMPRISON: + return new ImprisonTag(sourceId); + case BattlerTagType.SYRUP_BOMB: + return new SyrupBombTag(sourceId); + case BattlerTagType.TELEKINESIS: + return new TelekinesisTag(sourceMove); + case BattlerTagType.POWER_TRICK: + return new PowerTrickTag(sourceMove, sourceId); + case BattlerTagType.NONE: + default: + return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); } } diff --git a/src/data/berry.ts b/src/data/berry.ts index 01325ee39dd..7243c4c1b2e 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -22,42 +22,42 @@ export type BerryPredicate = (pokemon: Pokemon) => boolean; export function getBerryPredicate(berryType: BerryType): BerryPredicate { switch (berryType) { - case BerryType.SITRUS: - return (pokemon: Pokemon) => pokemon.getHpRatio() < 0.5; - case BerryType.LUM: - return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED); - case BerryType.ENIGMA: - return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length; - case BerryType.LIECHI: - case BerryType.GANLON: - case BerryType.PETAYA: - case BerryType.APICOT: - case BerryType.SALAC: - return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); - // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth - const stat: BattleStat = berryType - BerryType.ENIGMA; - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); - return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6; - }; - case BerryType.LANSAT: - return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); - return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); - }; - case BerryType.STARF: - return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); - return pokemon.getHpRatio() < 0.25; - }; - case BerryType.LEPPA: - return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); - return !!pokemon.getMoveset().find(m => !m?.getPpRatio()); - }; + case BerryType.SITRUS: + return (pokemon: Pokemon) => pokemon.getHpRatio() < 0.5; + case BerryType.LUM: + return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED); + case BerryType.ENIGMA: + return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length; + case BerryType.LIECHI: + case BerryType.GANLON: + case BerryType.PETAYA: + case BerryType.APICOT: + case BerryType.SALAC: + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth + const stat: BattleStat = berryType - BerryType.ENIGMA; + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6; + }; + case BerryType.LANSAT: + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); + }; + case BerryType.STARF: + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + return pokemon.getHpRatio() < 0.25; + }; + case BerryType.LEPPA: + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + return !!pokemon.getMoveset().find(m => !m?.getPpRatio()); + }; } } @@ -65,70 +65,70 @@ export type BerryEffectFunc = (pokemon: Pokemon) => void; export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { switch (berryType) { - case BerryType.SITRUS: - case BerryType.ENIGMA: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4)); - applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed); - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), - hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true)); - }; - case BerryType.LUM: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - if (pokemon.status) { - pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); - } - pokemon.resetStatus(true, true); - pokemon.updateInfo(); - }; - case BerryType.LIECHI: - case BerryType.GANLON: - case BerryType.PETAYA: - case BerryType.APICOT: - case BerryType.SALAC: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth - const stat: BattleStat = berryType - BerryType.ENIGMA; - const statStages = new Utils.NumberHolder(1); - applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value)); - }; - case BerryType.LANSAT: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - pokemon.addTag(BattlerTagType.CRIT_BOOST); - }; - case BerryType.STARF: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK); - const stages = new Utils.NumberHolder(2); - applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value)); - }; - case BerryType.LEPPA: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct? - if (ppRestoreMove !== undefined) { + case BerryType.SITRUS: + case BerryType.ENIGMA: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4)); + applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed); + pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), + hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true)); + }; + case BerryType.LUM: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + if (pokemon.status) { + pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + } + pokemon.resetStatus(true, true); + pokemon.updateInfo(); + }; + case BerryType.LIECHI: + case BerryType.GANLON: + case BerryType.PETAYA: + case BerryType.APICOT: + case BerryType.SALAC: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth + const stat: BattleStat = berryType - BerryType.ENIGMA; + const statStages = new Utils.NumberHolder(1); + applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value)); + }; + case BerryType.LANSAT: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + pokemon.addTag(BattlerTagType.CRIT_BOOST); + }; + case BerryType.STARF: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK); + const stages = new Utils.NumberHolder(2); + applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value)); + }; + case BerryType.LEPPA: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct? + if (ppRestoreMove !== undefined) { ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0); pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) })); - } - }; + } + }; } } diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 7ecdfbf36e1..a64a90e5d14 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -448,21 +448,21 @@ export class SingleGenerationChallenge extends Challenge { applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean { let trainerTypes: TrainerType[] = []; switch (waveIndex) { - case 182: - trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ]; - break; - case 184: - trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ]; - break; - case 186: - trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ]), TrainerType.LARRY_ELITE ]; - break; - case 188: - trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ]; - break; - case 190: - trainerTypes = [ TrainerType.BLUE, Utils.randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), Utils.randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, Utils.randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ]; - break; + case 182: + trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ]; + break; + case 184: + trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ]; + break; + case 186: + trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ]), TrainerType.LARRY_ELITE ]; + break; + case 188: + trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ]; + break; + case 190: + trainerTypes = [ TrainerType.BLUE, Utils.randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), Utils.randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, Utils.randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ]; + break; } if (trainerTypes.length === 0) { return false; @@ -891,45 +891,45 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType gameMode.challenges.forEach(c => { if (c.value !== 0) { switch (challengeType) { - case ChallengeType.STARTER_CHOICE: - ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]); - break; - case ChallengeType.STARTER_POINTS: - ret ||= c.applyStarterPoints(args[0]); - break; - case ChallengeType.STARTER_COST: - ret ||= c.applyStarterCost(args[0], args[1]); - break; - case ChallengeType.STARTER_MODIFY: - ret ||= c.applyStarterModify(args[0]); - break; - case ChallengeType.POKEMON_IN_BATTLE: - ret ||= c.applyPokemonInBattle(args[0], args[1]); - break; - case ChallengeType.FIXED_BATTLES: - ret ||= c.applyFixedBattle(args[0], args[1]); - break; - case ChallengeType.TYPE_EFFECTIVENESS: - ret ||= c.applyTypeEffectiveness(args[0]); - break; - case ChallengeType.AI_LEVEL: - ret ||= c.applyLevelChange(args[0], args[1], args[2], args[3]); - break; - case ChallengeType.AI_MOVE_SLOTS: - ret ||= c.applyMoveSlot(args[0], args[1]); - break; - case ChallengeType.PASSIVE_ACCESS: - ret ||= c.applyPassiveAccess(args[0], args[1]); - break; - case ChallengeType.GAME_MODE_MODIFY: - ret ||= c.applyGameModeModify(gameMode); - break; - case ChallengeType.MOVE_ACCESS: - ret ||= c.applyMoveAccessLevel(args[0], args[1], args[2], args[3]); - break; - case ChallengeType.MOVE_WEIGHT: - ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]); - break; + case ChallengeType.STARTER_CHOICE: + ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]); + break; + case ChallengeType.STARTER_POINTS: + ret ||= c.applyStarterPoints(args[0]); + break; + case ChallengeType.STARTER_COST: + ret ||= c.applyStarterCost(args[0], args[1]); + break; + case ChallengeType.STARTER_MODIFY: + ret ||= c.applyStarterModify(args[0]); + break; + case ChallengeType.POKEMON_IN_BATTLE: + ret ||= c.applyPokemonInBattle(args[0], args[1]); + break; + case ChallengeType.FIXED_BATTLES: + ret ||= c.applyFixedBattle(args[0], args[1]); + break; + case ChallengeType.TYPE_EFFECTIVENESS: + ret ||= c.applyTypeEffectiveness(args[0]); + break; + case ChallengeType.AI_LEVEL: + ret ||= c.applyLevelChange(args[0], args[1], args[2], args[3]); + break; + case ChallengeType.AI_MOVE_SLOTS: + ret ||= c.applyMoveSlot(args[0], args[1]); + break; + case ChallengeType.PASSIVE_ACCESS: + ret ||= c.applyPassiveAccess(args[0], args[1]); + break; + case ChallengeType.GAME_MODE_MODIFY: + ret ||= c.applyGameModeModify(gameMode); + break; + case ChallengeType.MOVE_ACCESS: + ret ||= c.applyMoveAccessLevel(args[0], args[1], args[2], args[3]); + break; + case ChallengeType.MOVE_WEIGHT: + ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]); + break; } } }); @@ -943,18 +943,18 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType */ export function copyChallenge(source: Challenge | any): Challenge { switch (source.id) { - case Challenges.SINGLE_GENERATION: - return SingleGenerationChallenge.loadChallenge(source); - case Challenges.SINGLE_TYPE: - return SingleTypeChallenge.loadChallenge(source); - case Challenges.LOWER_MAX_STARTER_COST: - return LowerStarterMaxCostChallenge.loadChallenge(source); - case Challenges.LOWER_STARTER_POINTS: - return LowerStarterPointsChallenge.loadChallenge(source); - case Challenges.FRESH_START: - return FreshStartChallenge.loadChallenge(source); - case Challenges.INVERSE_BATTLE: - return InverseBattleChallenge.loadChallenge(source); + case Challenges.SINGLE_GENERATION: + return SingleGenerationChallenge.loadChallenge(source); + case Challenges.SINGLE_TYPE: + return SingleTypeChallenge.loadChallenge(source); + case Challenges.LOWER_MAX_STARTER_COST: + return LowerStarterMaxCostChallenge.loadChallenge(source); + case Challenges.LOWER_STARTER_POINTS: + return LowerStarterPointsChallenge.loadChallenge(source); + case Challenges.FRESH_START: + return FreshStartChallenge.loadChallenge(source); + case Challenges.INVERSE_BATTLE: + return InverseBattleChallenge.loadChallenge(source); } throw new Error("Unknown challenge copied"); } diff --git a/src/data/egg.ts b/src/data/egg.ts index c475fc729e6..0f88c53b748 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -262,14 +262,14 @@ export class Egg { return "Manaphy"; } switch (this.tier) { - case EggTier.RARE: - return i18next.t("egg:greatTier"); - case EggTier.EPIC: - return i18next.t("egg:ultraTier"); - case EggTier.LEGENDARY: - return i18next.t("egg:masterTier"); - default: - return i18next.t("egg:defaultTier"); + case EggTier.RARE: + return i18next.t("egg:greatTier"); + case EggTier.EPIC: + return i18next.t("egg:ultraTier"); + case EggTier.LEGENDARY: + return i18next.t("egg:masterTier"); + default: + return i18next.t("egg:defaultTier"); } } @@ -288,19 +288,19 @@ export class Egg { public getEggTypeDescriptor(scene: BattleScene): string { switch (this.sourceType) { - case EggSourceType.SAME_SPECIES_EGG: - return this._eggDescriptor ?? i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName() }); - case EggSourceType.GACHA_LEGENDARY: - return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp)).getName()})`; - case EggSourceType.GACHA_SHINY: - return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny"); - case EggSourceType.GACHA_MOVE: - return this._eggDescriptor ?? i18next.t("egg:gachaTypeMove"); - case EggSourceType.EVENT: - return this._eggDescriptor ?? i18next.t("egg:eventType"); - default: - console.warn("getEggTypeDescriptor case not defined. Returning default empty string"); - return ""; + case EggSourceType.SAME_SPECIES_EGG: + return this._eggDescriptor ?? i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName() }); + case EggSourceType.GACHA_LEGENDARY: + return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp)).getName()})`; + case EggSourceType.GACHA_SHINY: + return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny"); + case EggSourceType.GACHA_MOVE: + return this._eggDescriptor ?? i18next.t("egg:gachaTypeMove"); + case EggSourceType.EVENT: + return this._eggDescriptor ?? i18next.t("egg:eventType"); + default: + console.warn("getEggTypeDescriptor case not defined. Returning default empty string"); + return ""; } } @@ -315,14 +315,14 @@ export class Egg { private rollEggMoveIndex() { let baseChance = GACHA_DEFAULT_RARE_EGGMOVE_RATE; switch (this._sourceType) { - case EggSourceType.SAME_SPECIES_EGG: - baseChance = SAME_SPECIES_EGG_RARE_EGGMOVE_RATE; - break; - case EggSourceType.GACHA_MOVE: - baseChance = GACHA_MOVE_UP_RARE_EGGMOVE_RATE; - break; - default: - break; + case EggSourceType.SAME_SPECIES_EGG: + baseChance = SAME_SPECIES_EGG_RARE_EGGMOVE_RATE; + break; + case EggSourceType.GACHA_MOVE: + baseChance = GACHA_MOVE_UP_RARE_EGGMOVE_RATE; + break; + default: + break; } const tierMultiplier = this.isManaphyEgg() ? 2 : Math.pow(2, 3 - this.tier); @@ -335,12 +335,12 @@ export class Egg { } switch (eggTier ?? this._tier) { - case EggTier.COMMON: - return HATCH_WAVES_COMMON_EGG; - case EggTier.RARE: - return HATCH_WAVES_RARE_EGG; - case EggTier.EPIC: - return HATCH_WAVES_EPIC_EGG; + case EggTier.COMMON: + return HATCH_WAVES_COMMON_EGG; + case EggTier.RARE: + return HATCH_WAVES_RARE_EGG; + case EggTier.EPIC: + return HATCH_WAVES_EPIC_EGG; } return HATCH_WAVES_LEGENDARY_EGG; } @@ -379,22 +379,22 @@ export class Egg { let maxStarterValue: integer; switch (this.tier) { - case EggTier.RARE: - minStarterValue = 4; - maxStarterValue = 5; - break; - case EggTier.EPIC: - minStarterValue = 6; - maxStarterValue = 7; - break; - case EggTier.LEGENDARY: - minStarterValue = 8; - maxStarterValue = 9; - break; - default: - minStarterValue = 1; - maxStarterValue = 3; - break; + case EggTier.RARE: + minStarterValue = 4; + maxStarterValue = 5; + break; + case EggTier.EPIC: + minStarterValue = 6; + maxStarterValue = 7; + break; + case EggTier.LEGENDARY: + minStarterValue = 8; + maxStarterValue = 9; + break; + default: + minStarterValue = 1; + maxStarterValue = 3; + break; } const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ]; @@ -469,14 +469,14 @@ export class Egg { private rollShiny(): boolean { let shinyChance = GACHA_DEFAULT_SHINY_RATE; switch (this._sourceType) { - case EggSourceType.GACHA_SHINY: - shinyChance = GACHA_SHINY_UP_SHINY_RATE; - break; - case EggSourceType.SAME_SPECIES_EGG: - shinyChance = SAME_SPECIES_EGG_SHINY_RATE; - break; - default: - break; + case EggSourceType.GACHA_SHINY: + shinyChance = GACHA_SHINY_UP_SHINY_RATE; + break; + case EggSourceType.SAME_SPECIES_EGG: + shinyChance = SAME_SPECIES_EGG_SHINY_RATE; + break; + default: + break; } return !Utils.randSeedInt(shinyChance); @@ -523,15 +523,15 @@ export class Egg { return; } switch (this.tier) { - case EggTier.RARE: - scene.gameData.gameStats.rareEggsPulled++; - break; - case EggTier.EPIC: - scene.gameData.gameStats.epicEggsPulled++; - break; - case EggTier.LEGENDARY: - scene.gameData.gameStats.legendaryEggsPulled++; - break; + case EggTier.RARE: + scene.gameData.gameStats.rareEggsPulled++; + break; + case EggTier.EPIC: + scene.gameData.gameStats.epicEggsPulled++; + break; + case EggTier.LEGENDARY: + scene.gameData.gameStats.legendaryEggsPulled++; + break; } } diff --git a/src/data/exp.ts b/src/data/exp.ts index 3b332eb7cf2..c03abddadfc 100644 --- a/src/data/exp.ts +++ b/src/data/exp.ts @@ -28,24 +28,24 @@ export function getLevelTotalExp(level: integer, growthRate: GrowthRate): intege let ret: integer; switch (growthRate) { - case GrowthRate.ERRATIC: - ret = (Math.pow(level, 4) + (Math.pow(level, 3) * 2000)) / 3500; - break; - case GrowthRate.FAST: - ret = Math.pow(level, 3) * 4 / 5; - break; - case GrowthRate.MEDIUM_FAST: - ret = Math.pow(level, 3); - break; - case GrowthRate.MEDIUM_SLOW: - ret = (Math.pow(level, 3) * 6 / 5) - (15 * Math.pow(level, 2)) + (100 * level) - 140; - break; - case GrowthRate.SLOW: - ret = Math.pow(level, 3) * 5 / 4; - break; - case GrowthRate.FLUCTUATING: - ret = (Math.pow(level, 3) * ((level / 2) + 8)) * 4 / (100 + level); - break; + case GrowthRate.ERRATIC: + ret = (Math.pow(level, 4) + (Math.pow(level, 3) * 2000)) / 3500; + break; + case GrowthRate.FAST: + ret = Math.pow(level, 3) * 4 / 5; + break; + case GrowthRate.MEDIUM_FAST: + ret = Math.pow(level, 3); + break; + case GrowthRate.MEDIUM_SLOW: + ret = (Math.pow(level, 3) * 6 / 5) - (15 * Math.pow(level, 2)) + (100 * level) - 140; + break; + case GrowthRate.SLOW: + ret = Math.pow(level, 3) * 5 / 4; + break; + case GrowthRate.FLUCTUATING: + ret = (Math.pow(level, 3) * ((level / 2) + 8)) * 4 / (100 + level); + break; } if (growthRate !== GrowthRate.MEDIUM_FAST) { @@ -61,17 +61,17 @@ export function getLevelRelExp(level: integer, growthRate: GrowthRate): number { export function getGrowthRateColor(growthRate: GrowthRate, shadow?: boolean) { switch (growthRate) { - case GrowthRate.ERRATIC: - return !shadow ? "#f85888" : "#906060"; - case GrowthRate.FAST: - return !shadow ? "#f8d030" : "#b8a038"; - case GrowthRate.MEDIUM_FAST: - return !shadow ? "#78c850" : "#588040"; - case GrowthRate.MEDIUM_SLOW: - return !shadow ? "#6890f0" : "#807870"; - case GrowthRate.SLOW: - return !shadow ? "#f08030" : "#c03028"; - case GrowthRate.FLUCTUATING: - return !shadow ? "#a040a0" : "#483850"; + case GrowthRate.ERRATIC: + return !shadow ? "#f85888" : "#906060"; + case GrowthRate.FAST: + return !shadow ? "#f8d030" : "#b8a038"; + case GrowthRate.MEDIUM_FAST: + return !shadow ? "#78c850" : "#588040"; + case GrowthRate.MEDIUM_SLOW: + return !shadow ? "#6890f0" : "#807870"; + case GrowthRate.SLOW: + return !shadow ? "#f08030" : "#c03028"; + case GrowthRate.FLUCTUATING: + return !shadow ? "#a040a0" : "#483850"; } } diff --git a/src/data/gender.ts b/src/data/gender.ts index 0d4b76d8bd1..dae7723dd85 100644 --- a/src/data/gender.ts +++ b/src/data/gender.ts @@ -6,20 +6,20 @@ export enum Gender { export function getGenderSymbol(gender: Gender) { switch (gender) { - case Gender.MALE: - return "♂"; - case Gender.FEMALE: - return "♀"; + case Gender.MALE: + return "♂"; + case Gender.FEMALE: + return "♀"; } return ""; } export function getGenderColor(gender: Gender, shadow?: boolean) { switch (gender) { - case Gender.MALE: - return shadow ? "#006090" : "#40c8f8"; - case Gender.FEMALE: - return shadow ? "#984038" : "#f89890"; + case Gender.MALE: + return shadow ? "#006090" : "#40c8f8"; + case Gender.FEMALE: + return shadow ? "#984038" : "#f89890"; } return "#ffffff"; } diff --git a/src/data/move.ts b/src/data/move.ts index 57307b49061..309a2d3a7eb 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -274,16 +274,16 @@ export default class Move implements Localizable { */ isMultiTarget(): boolean { switch (this.moveTarget) { - case MoveTarget.ALL_OTHERS: - case MoveTarget.ALL_NEAR_OTHERS: - case MoveTarget.ALL_NEAR_ENEMIES: - case MoveTarget.ALL_ENEMIES: - case MoveTarget.USER_AND_ALLIES: - case MoveTarget.ALL: - case MoveTarget.USER_SIDE: - case MoveTarget.ENEMY_SIDE: - case MoveTarget.BOTH_SIDES: - return true; + case MoveTarget.ALL_OTHERS: + case MoveTarget.ALL_NEAR_OTHERS: + case MoveTarget.ALL_NEAR_ENEMIES: + case MoveTarget.ALL_ENEMIES: + case MoveTarget.USER_AND_ALLIES: + case MoveTarget.ALL: + case MoveTarget.USER_SIDE: + case MoveTarget.ENEMY_SIDE: + case MoveTarget.BOTH_SIDES: + return true; } return false; } @@ -295,13 +295,13 @@ export default class Move implements Localizable { isAllyTarget(): boolean { switch (this.moveTarget) { - case MoveTarget.USER: - case MoveTarget.NEAR_ALLY: - case MoveTarget.ALLY: - case MoveTarget.USER_OR_NEAR_ALLY: - case MoveTarget.USER_AND_ALLIES: - case MoveTarget.USER_SIDE: - return true; + case MoveTarget.USER: + case MoveTarget.NEAR_ALLY: + case MoveTarget.ALLY: + case MoveTarget.USER_OR_NEAR_ALLY: + case MoveTarget.USER_AND_ALLIES: + case MoveTarget.USER_SIDE: + return true; } return false; } @@ -320,16 +320,16 @@ export default class Move implements Localizable { } switch (type) { - case Type.GRASS: - if (this.hasFlag(MoveFlags.POWDER_MOVE)) { - return true; - } - break; - case Type.DARK: - if (user.hasAbility(Abilities.PRANKSTER) && this.category === MoveCategory.STATUS && (user.isPlayer() !== target.isPlayer())) { - return true; - } - break; + case Type.GRASS: + if (this.hasFlag(MoveFlags.POWDER_MOVE)) { + return true; + } + break; + case Type.DARK: + if (user.hasAbility(Abilities.PRANKSTER) && this.category === MoveCategory.STATUS && (user.isPlayer() !== target.isPlayer())) { + return true; + } + break; } return false; } @@ -616,26 +616,26 @@ export default class Move implements Localizable { checkFlag(flag: MoveFlags, user: Pokemon, target: Pokemon | null): boolean { // special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact switch (flag) { - case MoveFlags.MAKES_CONTACT: - if (user.hasAbilityWithAttr(IgnoreContactAbAttr) || this.hitsSubstitute(user, target)) { - return false; - } - break; - case MoveFlags.IGNORE_ABILITIES: - if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { - const abilityEffectsIgnored = new Utils.BooleanHolder(false); - applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); - if (abilityEffectsIgnored.value) { + case MoveFlags.MAKES_CONTACT: + if (user.hasAbilityWithAttr(IgnoreContactAbAttr) || this.hitsSubstitute(user, target)) { + return false; + } + break; + case MoveFlags.IGNORE_ABILITIES: + if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { + const abilityEffectsIgnored = new Utils.BooleanHolder(false); + applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); + if (abilityEffectsIgnored.value) { + return true; + } + } + break; + case MoveFlags.IGNORE_PROTECT: + if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) + && this.checkFlag(MoveFlags.MAKES_CONTACT, user, null)) { return true; } - } - break; - case MoveFlags.IGNORE_PROTECT: - if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) - && this.checkFlag(MoveFlags.MAKES_CONTACT, user, null)) { - return true; - } - break; + break; } return !!(this.flags & flag); @@ -1745,17 +1745,17 @@ export abstract class WeatherHealAttr extends HealAttr { export class PlantHealAttr extends WeatherHealAttr { getWeatherHealRatio(weatherType: WeatherType): number { switch (weatherType) { - case WeatherType.SUNNY: - case WeatherType.HARSH_SUN: - return 2 / 3; - case WeatherType.RAIN: - case WeatherType.SANDSTORM: - case WeatherType.HAIL: - case WeatherType.SNOW: - case WeatherType.HEAVY_RAIN: - return 0.25; - default: - return 0.5; + case WeatherType.SUNNY: + case WeatherType.HARSH_SUN: + return 2 / 3; + case WeatherType.RAIN: + case WeatherType.SANDSTORM: + case WeatherType.HAIL: + case WeatherType.SNOW: + case WeatherType.HEAVY_RAIN: + return 0.25; + default: + return 0.5; } } } @@ -1763,10 +1763,10 @@ export class PlantHealAttr extends WeatherHealAttr { export class SandHealAttr extends WeatherHealAttr { getWeatherHealRatio(weatherType: WeatherType): number { switch (weatherType) { - case WeatherType.SANDSTORM: - return 2 / 3; - default: - return 0.5; + case WeatherType.SANDSTORM: + return 2 / 3; + default: + return 0.5; } } } @@ -1996,33 +1996,33 @@ export class MultiHitAttr extends MoveAttr { */ getHitCount(user: Pokemon, target: Pokemon): integer { switch (this.multiHitType) { - case MultiHitType._2_TO_5: - { - const rand = user.randSeedInt(16); - const hitValue = new Utils.IntegerHolder(rand); - applyAbAttrs(MaxMultiHitAbAttr, user, null, false, hitValue); - if (hitValue.value >= 10) { - return 2; - } else if (hitValue.value >= 4) { - return 3; - } else if (hitValue.value >= 2) { - return 4; - } else { - return 5; + case MultiHitType._2_TO_5: + { + const rand = user.randSeedInt(16); + const hitValue = new Utils.IntegerHolder(rand); + applyAbAttrs(MaxMultiHitAbAttr, user, null, false, hitValue); + if (hitValue.value >= 10) { + return 2; + } else if (hitValue.value >= 4) { + return 3; + } else if (hitValue.value >= 2) { + return 4; + } else { + return 5; + } } - } - case MultiHitType._2: - return 2; - case MultiHitType._3: - return 3; - case MultiHitType._10: - return 10; - case MultiHitType.BEAT_UP: - const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty(); - // No status means the ally pokemon can contribute to Beat Up - return party.reduce((total, pokemon) => { - return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1); - }, 0); + case MultiHitType._2: + return 2; + case MultiHitType._3: + return 3; + case MultiHitType._10: + return 10; + case MultiHitType.BEAT_UP: + const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty(); + // No status means the ally pokemon can contribute to Beat Up + return party.reduce((total, pokemon) => { + return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1); + }, 0); } } } @@ -2845,26 +2845,26 @@ export class StatStageChangeAttr extends MoveEffectAttr { } let noEffect = false; switch (stat) { - case Stat.ATK: - if (this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); - } - break; - case Stat.DEF: - if (!this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); - } - break; - case Stat.SPATK: - if (this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); - } - break; - case Stat.SPDEF: - if (!this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); - } - break; + case Stat.ATK: + if (this.selfTarget) { + noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); + } + break; + case Stat.DEF: + if (!this.selfTarget) { + noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); + } + break; + case Stat.SPATK: + if (this.selfTarget) { + noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); + } + break; + case Stat.SPDEF: + if (!this.selfTarget) { + noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); + } + break; } if (noEffect) { continue; @@ -2933,19 +2933,19 @@ export class SecretPowerAttr extends MoveEffectAttr { private determineTerrainEffect(terrain: TerrainType): MoveEffectAttr { let secondaryEffect: MoveEffectAttr; switch (terrain) { - case TerrainType.ELECTRIC: - default: - secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false); - break; - case TerrainType.MISTY: - secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false); - break; - case TerrainType.GRASSY: - secondaryEffect = new StatusEffectAttr(StatusEffect.SLEEP, false); - break; - case TerrainType.PSYCHIC: - secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false); - break; + case TerrainType.ELECTRIC: + default: + secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false); + break; + case TerrainType.MISTY: + secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false); + break; + case TerrainType.GRASSY: + secondaryEffect = new StatusEffectAttr(StatusEffect.SLEEP, false); + break; + case TerrainType.PSYCHIC: + secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false); + break; } return secondaryEffect; } @@ -2970,62 +2970,62 @@ export class SecretPowerAttr extends MoveEffectAttr { private determineBiomeEffect(biome: Biome): MoveEffectAttr { let secondaryEffect: MoveEffectAttr; switch (biome) { - case Biome.PLAINS: - case Biome.GRASS: - case Biome.TALL_GRASS: - case Biome.FOREST: - case Biome.JUNGLE: - case Biome.MEADOW: - secondaryEffect = new StatusEffectAttr(StatusEffect.SLEEP, false); - break; - case Biome.SWAMP: - case Biome.MOUNTAIN: - case Biome.TEMPLE: - case Biome.RUINS: - secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false); - break; - case Biome.ICE_CAVE: - case Biome.SNOWY_FOREST: - secondaryEffect = new StatusEffectAttr(StatusEffect.FREEZE, false); - break; - case Biome.VOLCANO: - secondaryEffect = new StatusEffectAttr(StatusEffect.BURN, false); - break; - case Biome.FAIRY_CAVE: - secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false); - break; - case Biome.DESERT: - case Biome.CONSTRUCTION_SITE: - case Biome.BEACH: - case Biome.ISLAND: - case Biome.BADLANDS: - secondaryEffect = new StatStageChangeAttr([ Stat.ACC ], -1, false); - break; - case Biome.SEA: - case Biome.LAKE: - case Biome.SEABED: - secondaryEffect = new StatStageChangeAttr([ Stat.ATK ], -1, false); - break; - case Biome.CAVE: - case Biome.WASTELAND: - case Biome.GRAVEYARD: - case Biome.ABYSS: - case Biome.SPACE: - secondaryEffect = new AddBattlerTagAttr(BattlerTagType.FLINCHED, false, true); - break; - case Biome.END: - secondaryEffect = new StatStageChangeAttr([ Stat.DEF ], -1, false); - break; - case Biome.TOWN: - case Biome.METROPOLIS: - case Biome.SLUM: - case Biome.DOJO: - case Biome.FACTORY: - case Biome.LABORATORY: - case Biome.POWER_PLANT: - default: - secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false); - break; + case Biome.PLAINS: + case Biome.GRASS: + case Biome.TALL_GRASS: + case Biome.FOREST: + case Biome.JUNGLE: + case Biome.MEADOW: + secondaryEffect = new StatusEffectAttr(StatusEffect.SLEEP, false); + break; + case Biome.SWAMP: + case Biome.MOUNTAIN: + case Biome.TEMPLE: + case Biome.RUINS: + secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false); + break; + case Biome.ICE_CAVE: + case Biome.SNOWY_FOREST: + secondaryEffect = new StatusEffectAttr(StatusEffect.FREEZE, false); + break; + case Biome.VOLCANO: + secondaryEffect = new StatusEffectAttr(StatusEffect.BURN, false); + break; + case Biome.FAIRY_CAVE: + secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false); + break; + case Biome.DESERT: + case Biome.CONSTRUCTION_SITE: + case Biome.BEACH: + case Biome.ISLAND: + case Biome.BADLANDS: + secondaryEffect = new StatStageChangeAttr([ Stat.ACC ], -1, false); + break; + case Biome.SEA: + case Biome.LAKE: + case Biome.SEABED: + secondaryEffect = new StatStageChangeAttr([ Stat.ATK ], -1, false); + break; + case Biome.CAVE: + case Biome.WASTELAND: + case Biome.GRAVEYARD: + case Biome.ABYSS: + case Biome.SPACE: + secondaryEffect = new AddBattlerTagAttr(BattlerTagType.FLINCHED, false, true); + break; + case Biome.END: + secondaryEffect = new StatStageChangeAttr([ Stat.DEF ], -1, false); + break; + case Biome.TOWN: + case Biome.METROPOLIS: + case Biome.SLUM: + case Biome.DOJO: + case Biome.FACTORY: + case Biome.LABORATORY: + case Biome.POWER_PLANT: + default: + secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false); + break; } return secondaryEffect; } @@ -3309,21 +3309,21 @@ export class LessPPMorePowerAttr extends VariablePowerAttr { const power = args[0] as Utils.NumberHolder; switch (ppRemains) { - case 0: - power.value = 200; - break; - case 1: - power.value = 80; - break; - case 2: - power.value = 60; - break; - case 3: - power.value = 50; - break; - default: - power.value = 40; - break; + case 0: + power.value = 200; + break; + case 1: + power.value = 80; + break; + case 2: + power.value = 60; + break; + case 3: + power.value = 50; + break; + default: + power.value = 40; + break; } return true; } @@ -3543,24 +3543,24 @@ export class LowHpPowerAttr extends VariablePowerAttr { const hpRatio = user.getHpRatio(); switch (true) { - case (hpRatio < 0.0417): - power.value = 200; - break; - case (hpRatio < 0.1042): - power.value = 150; - break; - case (hpRatio < 0.2083): - power.value = 100; - break; - case (hpRatio < 0.3542): - power.value = 80; - break; - case (hpRatio < 0.6875): - power.value = 40; - break; - default: - power.value = 20; - break; + case (hpRatio < 0.0417): + power.value = 200; + break; + case (hpRatio < 0.1042): + power.value = 150; + break; + case (hpRatio < 0.2083): + power.value = 100; + break; + case (hpRatio < 0.3542): + power.value = 80; + break; + case (hpRatio < 0.6875): + power.value = 40; + break; + default: + power.value = 20; + break; } return true; @@ -3580,21 +3580,21 @@ export class CompareWeightPowerAttr extends VariablePowerAttr { const relativeWeight = (targetWeight / userWeight) * 100; switch (true) { - case (relativeWeight < 20.01): - power.value = 120; - break; - case (relativeWeight < 25.01): - power.value = 100; - break; - case (relativeWeight < 33.35): - power.value = 80; - break; - case (relativeWeight < 50.01): - power.value = 60; - break; - default: - power.value = 40; - break; + case (relativeWeight < 20.01): + power.value = 120; + break; + case (relativeWeight < 25.01): + power.value = 100; + break; + case (relativeWeight < 33.35): + power.value = 80; + break; + case (relativeWeight < 50.01): + power.value = 60; + break; + default: + power.value = 40; + break; } return true; @@ -3710,13 +3710,13 @@ export class AntiSunlightPowerDecreaseAttr extends VariablePowerAttr { const power = args[0] as Utils.NumberHolder; const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { - case WeatherType.RAIN: - case WeatherType.SANDSTORM: - case WeatherType.HAIL: - case WeatherType.SNOW: - case WeatherType.HEAVY_RAIN: - power.value *= 0.5; - return true; + case WeatherType.RAIN: + case WeatherType.SANDSTORM: + case WeatherType.HAIL: + case WeatherType.SNOW: + case WeatherType.HEAVY_RAIN: + power.value *= 0.5; + return true; } } @@ -4113,14 +4113,14 @@ export class ThunderAccuracyAttr extends VariableAccuracyAttr { const accuracy = args[0] as Utils.NumberHolder; const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { - case WeatherType.SUNNY: - case WeatherType.HARSH_SUN: - accuracy.value = 50; - return true; - case WeatherType.RAIN: - case WeatherType.HEAVY_RAIN: - accuracy.value = -1; - return true; + case WeatherType.SUNNY: + case WeatherType.HARSH_SUN: + accuracy.value = 50; + return true; + case WeatherType.RAIN: + case WeatherType.HEAVY_RAIN: + accuracy.value = -1; + return true; } } @@ -4139,10 +4139,10 @@ export class StormAccuracyAttr extends VariableAccuracyAttr { const accuracy = args[0] as Utils.NumberHolder; const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { - case WeatherType.RAIN: - case WeatherType.HEAVY_RAIN: - accuracy.value = -1; - return true; + case WeatherType.RAIN: + case WeatherType.HEAVY_RAIN: + accuracy.value = -1; + return true; } } @@ -4372,21 +4372,21 @@ export class TechnoBlastTypeAttr extends VariableMoveTypeAttr { const form = user.species.speciesId === Species.GENESECT ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { - case 1: // Shock Drive - moveType.value = Type.ELECTRIC; - break; - case 2: // Burn Drive - moveType.value = Type.FIRE; - break; - case 3: // Chill Drive - moveType.value = Type.ICE; - break; - case 4: // Douse Drive - moveType.value = Type.WATER; - break; - default: - moveType.value = Type.NORMAL; - break; + case 1: // Shock Drive + moveType.value = Type.ELECTRIC; + break; + case 2: // Burn Drive + moveType.value = Type.FIRE; + break; + case 3: // Chill Drive + moveType.value = Type.ICE; + break; + case 4: // Douse Drive + moveType.value = Type.WATER; + break; + default: + moveType.value = Type.NORMAL; + break; } return true; } @@ -4406,12 +4406,12 @@ export class AuraWheelTypeAttr extends VariableMoveTypeAttr { const form = user.species.speciesId === Species.MORPEKO ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { - case 1: // Hangry Mode - moveType.value = Type.DARK; - break; - default: // Full Belly Mode - moveType.value = Type.ELECTRIC; - break; + case 1: // Hangry Mode + moveType.value = Type.DARK; + break; + default: // Full Belly Mode + moveType.value = Type.ELECTRIC; + break; } return true; } @@ -4431,15 +4431,15 @@ export class RagingBullTypeAttr extends VariableMoveTypeAttr { const form = user.species.speciesId === Species.PALDEA_TAUROS ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { - case 1: // Blaze breed - moveType.value = Type.FIRE; - break; - case 2: // Aqua breed - moveType.value = Type.WATER; - break; - default: - moveType.value = Type.FIGHTING; - break; + case 1: // Blaze breed + moveType.value = Type.FIRE; + break; + case 2: // Aqua breed + moveType.value = Type.WATER; + break; + default: + moveType.value = Type.FIGHTING; + break; } return true; } @@ -4459,22 +4459,22 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr { const form = user.species.speciesId === Species.OGERPON ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { - case 1: // Wellspring Mask - case 5: // Wellspring Mask Tera - moveType.value = Type.WATER; - break; - case 2: // Hearthflame Mask - case 6: // Hearthflame Mask Tera - moveType.value = Type.FIRE; - break; - case 3: // Cornerstone Mask - case 7: // Cornerstone Mask Tera - moveType.value = Type.ROCK; - break; - case 4: // Teal Mask Tera - default: - moveType.value = Type.GRASS; - break; + case 1: // Wellspring Mask + case 5: // Wellspring Mask Tera + moveType.value = Type.WATER; + break; + case 2: // Hearthflame Mask + case 6: // Hearthflame Mask Tera + moveType.value = Type.FIRE; + break; + case 3: // Cornerstone Mask + case 7: // Cornerstone Mask Tera + moveType.value = Type.ROCK; + break; + case 4: // Teal Mask Tera + default: + moveType.value = Type.GRASS; + break; } return true; } @@ -4492,23 +4492,23 @@ export class WeatherBallTypeAttr extends VariableMoveTypeAttr { if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { switch (user.scene.arena.weather?.weatherType) { - case WeatherType.SUNNY: - case WeatherType.HARSH_SUN: - moveType.value = Type.FIRE; - break; - case WeatherType.RAIN: - case WeatherType.HEAVY_RAIN: - moveType.value = Type.WATER; - break; - case WeatherType.SANDSTORM: - moveType.value = Type.ROCK; - break; - case WeatherType.HAIL: - case WeatherType.SNOW: - moveType.value = Type.ICE; - break; - default: - return false; + case WeatherType.SUNNY: + case WeatherType.HARSH_SUN: + moveType.value = Type.FIRE; + break; + case WeatherType.RAIN: + case WeatherType.HEAVY_RAIN: + moveType.value = Type.WATER; + break; + case WeatherType.SANDSTORM: + moveType.value = Type.ROCK; + break; + case WeatherType.HAIL: + case WeatherType.SNOW: + moveType.value = Type.ICE; + break; + default: + return false; } return true; } @@ -4543,20 +4543,20 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr { const currentTerrain = user.scene.arena.getTerrainType(); switch (currentTerrain) { - case TerrainType.MISTY: - moveType.value = Type.FAIRY; - break; - case TerrainType.ELECTRIC: - moveType.value = Type.ELECTRIC; - break; - case TerrainType.GRASSY: - moveType.value = Type.GRASS; - break; - case TerrainType.PSYCHIC: - moveType.value = Type.PSYCHIC; - break; - default: - return false; + case TerrainType.MISTY: + moveType.value = Type.FAIRY; + break; + case TerrainType.ELECTRIC: + moveType.value = Type.ELECTRIC; + break; + case TerrainType.GRASSY: + moveType.value = Type.GRASS; + break; + case TerrainType.PSYCHIC: + moveType.value = Type.PSYCHIC; + break; + default: + return false; } return true; } @@ -4656,26 +4656,26 @@ export class CombinedPledgeTypeAttr extends VariableMoveTypeAttr { } switch (move.id) { - case Moves.FIRE_PLEDGE: - if (combinedPledgeMove === Moves.WATER_PLEDGE) { - moveType.value = Type.WATER; - return true; - } - return false; - case Moves.WATER_PLEDGE: - if (combinedPledgeMove === Moves.GRASS_PLEDGE) { - moveType.value = Type.GRASS; - return true; - } - return false; - case Moves.GRASS_PLEDGE: - if (combinedPledgeMove === Moves.FIRE_PLEDGE) { - moveType.value = Type.FIRE; - return true; - } - return false; - default: - return false; + case Moves.FIRE_PLEDGE: + if (combinedPledgeMove === Moves.WATER_PLEDGE) { + moveType.value = Type.WATER; + return true; + } + return false; + case Moves.WATER_PLEDGE: + if (combinedPledgeMove === Moves.GRASS_PLEDGE) { + moveType.value = Type.GRASS; + return true; + } + return false; + case Moves.GRASS_PLEDGE: + if (combinedPledgeMove === Moves.FIRE_PLEDGE) { + moveType.value = Type.FIRE; + return true; + } + return false; + default: + return false; } } } @@ -4920,48 +4920,48 @@ export class AddBattlerTagAttr extends MoveEffectAttr { getTagTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer | void { switch (this.tagType) { - case BattlerTagType.RECHARGING: - case BattlerTagType.PERISH_SONG: - return -16; - case BattlerTagType.FLINCHED: - case BattlerTagType.CONFUSED: - case BattlerTagType.INFATUATED: - case BattlerTagType.NIGHTMARE: - case BattlerTagType.DROWSY: - case BattlerTagType.DISABLED: - case BattlerTagType.HEAL_BLOCK: - case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: - return -5; - case BattlerTagType.SEEDED: - case BattlerTagType.SALT_CURED: - case BattlerTagType.CURSED: - case BattlerTagType.FRENZY: - case BattlerTagType.TRAPPED: - case BattlerTagType.BIND: - case BattlerTagType.WRAP: - case BattlerTagType.FIRE_SPIN: - case BattlerTagType.WHIRLPOOL: - case BattlerTagType.CLAMP: - case BattlerTagType.SAND_TOMB: - case BattlerTagType.MAGMA_STORM: - case BattlerTagType.SNAP_TRAP: - case BattlerTagType.THUNDER_CAGE: - case BattlerTagType.INFESTATION: - return -3; - case BattlerTagType.ENCORE: - return -2; - case BattlerTagType.MINIMIZED: - case BattlerTagType.ALWAYS_GET_HIT: - return 0; - case BattlerTagType.INGRAIN: - case BattlerTagType.IGNORE_ACCURACY: - case BattlerTagType.AQUA_RING: - return 3; - case BattlerTagType.PROTECTED: - case BattlerTagType.FLYING: - case BattlerTagType.CRIT_BOOST: - case BattlerTagType.ALWAYS_CRIT: - return 5; + case BattlerTagType.RECHARGING: + case BattlerTagType.PERISH_SONG: + return -16; + case BattlerTagType.FLINCHED: + case BattlerTagType.CONFUSED: + case BattlerTagType.INFATUATED: + case BattlerTagType.NIGHTMARE: + case BattlerTagType.DROWSY: + case BattlerTagType.DISABLED: + case BattlerTagType.HEAL_BLOCK: + case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: + return -5; + case BattlerTagType.SEEDED: + case BattlerTagType.SALT_CURED: + case BattlerTagType.CURSED: + case BattlerTagType.FRENZY: + case BattlerTagType.TRAPPED: + case BattlerTagType.BIND: + case BattlerTagType.WRAP: + case BattlerTagType.FIRE_SPIN: + case BattlerTagType.WHIRLPOOL: + case BattlerTagType.CLAMP: + case BattlerTagType.SAND_TOMB: + case BattlerTagType.MAGMA_STORM: + case BattlerTagType.SNAP_TRAP: + case BattlerTagType.THUNDER_CAGE: + case BattlerTagType.INFESTATION: + return -3; + case BattlerTagType.ENCORE: + return -2; + case BattlerTagType.MINIMIZED: + case BattlerTagType.ALWAYS_GET_HIT: + return 0; + case BattlerTagType.INGRAIN: + case BattlerTagType.IGNORE_ACCURACY: + case BattlerTagType.AQUA_RING: + return 3; + case BattlerTagType.PROTECTED: + case BattlerTagType.FLYING: + case BattlerTagType.CRIT_BOOST: + case BattlerTagType.ALWAYS_CRIT: + return 5; } } @@ -5934,19 +5934,19 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { } let selectTargets: BattlerIndex[]; switch (true) { - case (moveTargets.multiple || moveTargets.targets.length === 1): { - selectTargets = moveTargets.targets; - break; - } - case (moveTargets.targets.indexOf(target.getBattlerIndex()) > -1): { - selectTargets = [ target.getBattlerIndex() ]; - break; - } - default: { - moveTargets.targets.splice(moveTargets.targets.indexOf(user.getAlly().getBattlerIndex())); - selectTargets = [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; - break; - } + case (moveTargets.multiple || moveTargets.targets.length === 1): { + selectTargets = moveTargets.targets; + break; + } + case (moveTargets.targets.indexOf(target.getBattlerIndex()) > -1): { + selectTargets = [ target.getBattlerIndex() ]; + break; + } + default: { + moveTargets.targets.splice(moveTargets.targets.indexOf(user.getAlly().getBattlerIndex())); + selectTargets = [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; + break; + } } const targets = selectTargets; user.getMoveQueue().push({ move: move?.moveId!, targets: targets, ignorePP: true }); // TODO: is this bang correct? @@ -5990,131 +5990,131 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { let moveId; switch (user.scene.arena.getTerrainType()) { // this allows terrains to 'override' the biome move - case TerrainType.NONE: - switch (user.scene.arena.biomeType) { - case Biome.TOWN: - moveId = Moves.ROUND; + case TerrainType.NONE: + switch (user.scene.arena.biomeType) { + case Biome.TOWN: + moveId = Moves.ROUND; + break; + case Biome.METROPOLIS: + moveId = Moves.TRI_ATTACK; + break; + case Biome.SLUM: + moveId = Moves.SLUDGE_BOMB; + break; + case Biome.PLAINS: + moveId = Moves.SILVER_WIND; + break; + case Biome.GRASS: + moveId = Moves.GRASS_KNOT; + break; + case Biome.TALL_GRASS: + moveId = Moves.POLLEN_PUFF; + break; + case Biome.MEADOW: + moveId = Moves.GIGA_DRAIN; + break; + case Biome.FOREST: + moveId = Moves.BUG_BUZZ; + break; + case Biome.JUNGLE: + moveId = Moves.LEAF_STORM; + break; + case Biome.SEA: + moveId = Moves.HYDRO_PUMP; + break; + case Biome.SWAMP: + moveId = Moves.MUD_BOMB; + break; + case Biome.BEACH: + moveId = Moves.SCALD; + break; + case Biome.LAKE: + moveId = Moves.BUBBLE_BEAM; + break; + case Biome.SEABED: + moveId = Moves.BRINE; + break; + case Biome.ISLAND: + moveId = Moves.LEAF_TORNADO; + break; + case Biome.MOUNTAIN: + moveId = Moves.AIR_SLASH; + break; + case Biome.BADLANDS: + moveId = Moves.EARTH_POWER; + break; + case Biome.DESERT: + moveId = Moves.SCORCHING_SANDS; + break; + case Biome.WASTELAND: + moveId = Moves.DRAGON_PULSE; + break; + case Biome.CONSTRUCTION_SITE: + moveId = Moves.STEEL_BEAM; + break; + case Biome.CAVE: + moveId = Moves.POWER_GEM; + break; + case Biome.ICE_CAVE: + moveId = Moves.ICE_BEAM; + break; + case Biome.SNOWY_FOREST: + moveId = Moves.FROST_BREATH; + break; + case Biome.VOLCANO: + moveId = Moves.LAVA_PLUME; + break; + case Biome.GRAVEYARD: + moveId = Moves.SHADOW_BALL; + break; + case Biome.RUINS: + moveId = Moves.ANCIENT_POWER; + break; + case Biome.TEMPLE: + moveId = Moves.EXTRASENSORY; + break; + case Biome.DOJO: + moveId = Moves.FOCUS_BLAST; + break; + case Biome.FAIRY_CAVE: + moveId = Moves.ALLURING_VOICE; + break; + case Biome.ABYSS: + moveId = Moves.OMINOUS_WIND; + break; + case Biome.SPACE: + moveId = Moves.DRACO_METEOR; + break; + case Biome.FACTORY: + moveId = Moves.FLASH_CANNON; + break; + case Biome.LABORATORY: + moveId = Moves.ZAP_CANNON; + break; + case Biome.POWER_PLANT: + moveId = Moves.CHARGE_BEAM; + break; + case Biome.END: + moveId = Moves.ETERNABEAM; + break; + } break; - case Biome.METROPOLIS: + case TerrainType.MISTY: + moveId = Moves.MOONBLAST; + break; + case TerrainType.ELECTRIC: + moveId = Moves.THUNDERBOLT; + break; + case TerrainType.GRASSY: + moveId = Moves.ENERGY_BALL; + break; + case TerrainType.PSYCHIC: + moveId = Moves.PSYCHIC; + break; + default: + // Just in case there's no match moveId = Moves.TRI_ATTACK; break; - case Biome.SLUM: - moveId = Moves.SLUDGE_BOMB; - break; - case Biome.PLAINS: - moveId = Moves.SILVER_WIND; - break; - case Biome.GRASS: - moveId = Moves.GRASS_KNOT; - break; - case Biome.TALL_GRASS: - moveId = Moves.POLLEN_PUFF; - break; - case Biome.MEADOW: - moveId = Moves.GIGA_DRAIN; - break; - case Biome.FOREST: - moveId = Moves.BUG_BUZZ; - break; - case Biome.JUNGLE: - moveId = Moves.LEAF_STORM; - break; - case Biome.SEA: - moveId = Moves.HYDRO_PUMP; - break; - case Biome.SWAMP: - moveId = Moves.MUD_BOMB; - break; - case Biome.BEACH: - moveId = Moves.SCALD; - break; - case Biome.LAKE: - moveId = Moves.BUBBLE_BEAM; - break; - case Biome.SEABED: - moveId = Moves.BRINE; - break; - case Biome.ISLAND: - moveId = Moves.LEAF_TORNADO; - break; - case Biome.MOUNTAIN: - moveId = Moves.AIR_SLASH; - break; - case Biome.BADLANDS: - moveId = Moves.EARTH_POWER; - break; - case Biome.DESERT: - moveId = Moves.SCORCHING_SANDS; - break; - case Biome.WASTELAND: - moveId = Moves.DRAGON_PULSE; - break; - case Biome.CONSTRUCTION_SITE: - moveId = Moves.STEEL_BEAM; - break; - case Biome.CAVE: - moveId = Moves.POWER_GEM; - break; - case Biome.ICE_CAVE: - moveId = Moves.ICE_BEAM; - break; - case Biome.SNOWY_FOREST: - moveId = Moves.FROST_BREATH; - break; - case Biome.VOLCANO: - moveId = Moves.LAVA_PLUME; - break; - case Biome.GRAVEYARD: - moveId = Moves.SHADOW_BALL; - break; - case Biome.RUINS: - moveId = Moves.ANCIENT_POWER; - break; - case Biome.TEMPLE: - moveId = Moves.EXTRASENSORY; - break; - case Biome.DOJO: - moveId = Moves.FOCUS_BLAST; - break; - case Biome.FAIRY_CAVE: - moveId = Moves.ALLURING_VOICE; - break; - case Biome.ABYSS: - moveId = Moves.OMINOUS_WIND; - break; - case Biome.SPACE: - moveId = Moves.DRACO_METEOR; - break; - case Biome.FACTORY: - moveId = Moves.FLASH_CANNON; - break; - case Biome.LABORATORY: - moveId = Moves.ZAP_CANNON; - break; - case Biome.POWER_PLANT: - moveId = Moves.CHARGE_BEAM; - break; - case Biome.END: - moveId = Moves.ETERNABEAM; - break; - } - break; - case TerrainType.MISTY: - moveId = Moves.MOONBLAST; - break; - case TerrainType.ELECTRIC: - moveId = Moves.THUNDERBOLT; - break; - case TerrainType.GRASSY: - moveId = Moves.ENERGY_BALL; - break; - case TerrainType.PSYCHIC: - moveId = Moves.PSYCHIC; - break; - default: - // Just in case there's no match - moveId = Moves.TRI_ATTACK; - break; } user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true }); @@ -7152,47 +7152,47 @@ export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet { let multiple = false; switch (moveTarget) { - case MoveTarget.USER: - case MoveTarget.PARTY: - set = [ user ]; - break; - case MoveTarget.NEAR_OTHER: - case MoveTarget.OTHER: - case MoveTarget.ALL_NEAR_OTHERS: - case MoveTarget.ALL_OTHERS: - set = (opponents.concat([ user.getAlly() ])); - multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS; - break; - case MoveTarget.NEAR_ENEMY: - case MoveTarget.ALL_NEAR_ENEMIES: - case MoveTarget.ALL_ENEMIES: - case MoveTarget.ENEMY_SIDE: - set = opponents; - multiple = moveTarget !== MoveTarget.NEAR_ENEMY; - break; - case MoveTarget.RANDOM_NEAR_ENEMY: - set = [ opponents[user.randSeedInt(opponents.length)] ]; - break; - case MoveTarget.ATTACKER: - return { targets: [ -1 as BattlerIndex ], multiple: false }; - case MoveTarget.NEAR_ALLY: - case MoveTarget.ALLY: - set = [ user.getAlly() ]; - break; - case MoveTarget.USER_OR_NEAR_ALLY: - case MoveTarget.USER_AND_ALLIES: - case MoveTarget.USER_SIDE: - set = [ user, user.getAlly() ]; - multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY; - break; - case MoveTarget.ALL: - case MoveTarget.BOTH_SIDES: - set = [ user, user.getAlly() ].concat(opponents); - multiple = true; - break; - case MoveTarget.CURSE: - set = user.getTypes(true).includes(Type.GHOST) ? (opponents.concat([ user.getAlly() ])) : [ user ]; - break; + case MoveTarget.USER: + case MoveTarget.PARTY: + set = [ user ]; + break; + case MoveTarget.NEAR_OTHER: + case MoveTarget.OTHER: + case MoveTarget.ALL_NEAR_OTHERS: + case MoveTarget.ALL_OTHERS: + set = (opponents.concat([ user.getAlly() ])); + multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS; + break; + case MoveTarget.NEAR_ENEMY: + case MoveTarget.ALL_NEAR_ENEMIES: + case MoveTarget.ALL_ENEMIES: + case MoveTarget.ENEMY_SIDE: + set = opponents; + multiple = moveTarget !== MoveTarget.NEAR_ENEMY; + break; + case MoveTarget.RANDOM_NEAR_ENEMY: + set = [ opponents[user.randSeedInt(opponents.length)] ]; + break; + case MoveTarget.ATTACKER: + return { targets: [ -1 as BattlerIndex ], multiple: false }; + case MoveTarget.NEAR_ALLY: + case MoveTarget.ALLY: + set = [ user.getAlly() ]; + break; + case MoveTarget.USER_OR_NEAR_ALLY: + case MoveTarget.USER_AND_ALLIES: + case MoveTarget.USER_SIDE: + set = [ user, user.getAlly() ]; + multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY; + break; + case MoveTarget.ALL: + case MoveTarget.BOTH_SIDES: + set = [ user, user.getAlly() ].concat(opponents); + multiple = true; + break; + case MoveTarget.CURSE: + set = user.getTypes(true).includes(Type.GHOST) ? (opponents.concat([ user.getAlly() ])) : [ user ]; + break; } return { targets: set.filter(p => p?.isActive(true)).map(p => p.getBattlerIndex()).filter(t => t !== undefined), multiple }; 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 56d80c9598c..f0155b4f2a4 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -44,32 +44,32 @@ export const ATrainersTestEncounter: MysteryEncounter = let spriteKeys; let trainerNameKey: string; switch (randSeedInt(5)) { - default: - case 0: - trainerType = TrainerType.BUCK; - spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL); - trainerNameKey = "buck"; - break; - case 1: - trainerType = TrainerType.CHERYL; - spriteKeys = getSpriteKeysFromSpecies(Species.BLISSEY); - trainerNameKey = "cheryl"; - break; - case 2: - trainerType = TrainerType.MARLEY; - spriteKeys = getSpriteKeysFromSpecies(Species.ARCANINE); - trainerNameKey = "marley"; - break; - case 3: - trainerType = TrainerType.MIRA; - spriteKeys = getSpriteKeysFromSpecies(Species.ALAKAZAM, false, 1); - trainerNameKey = "mira"; - break; - case 4: - trainerType = TrainerType.RILEY; - spriteKeys = getSpriteKeysFromSpecies(Species.LUCARIO, false, 1); - trainerNameKey = "riley"; - break; + default: + case 0: + trainerType = TrainerType.BUCK; + spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL); + trainerNameKey = "buck"; + break; + case 1: + trainerType = TrainerType.CHERYL; + spriteKeys = getSpriteKeysFromSpecies(Species.BLISSEY); + trainerNameKey = "cheryl"; + break; + case 2: + trainerType = TrainerType.MARLEY; + spriteKeys = getSpriteKeysFromSpecies(Species.ARCANINE); + trainerNameKey = "marley"; + break; + case 3: + trainerType = TrainerType.MIRA; + spriteKeys = getSpriteKeysFromSpecies(Species.ALAKAZAM, false, 1); + trainerNameKey = "mira"; + break; + case 4: + trainerType = TrainerType.RILEY; + spriteKeys = getSpriteKeysFromSpecies(Species.LUCARIO, false, 1); + trainerNameKey = "riley"; + break; } // Dialogue and tokens for trainer diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index f76dff1fec7..485b955f998 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -1069,19 +1069,19 @@ export function calculateRareSpawnAggregateStats(scene: BattleScene, luckValue: const tier = tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE; switch (tier) { - default: - case BiomePoolTier.BOSS: - ++bossEncountersByRarity[0]; - break; - case BiomePoolTier.BOSS_RARE: - ++bossEncountersByRarity[1]; - break; - case BiomePoolTier.BOSS_SUPER_RARE: - ++bossEncountersByRarity[2]; - break; - case BiomePoolTier.BOSS_ULTRA_RARE: - ++bossEncountersByRarity[3]; - break; + default: + case BiomePoolTier.BOSS: + ++bossEncountersByRarity[0]; + break; + case BiomePoolTier.BOSS_RARE: + ++bossEncountersByRarity[1]; + break; + case BiomePoolTier.BOSS_SUPER_RARE: + ++bossEncountersByRarity[2]; + break; + case BiomePoolTier.BOSS_ULTRA_RARE: + ++bossEncountersByRarity[3]; + break; } } diff --git a/src/data/nature.ts b/src/data/nature.ts index c614be465c3..edac06f1a4f 100644 --- a/src/data/nature.ts +++ b/src/data/nature.ts @@ -37,76 +37,76 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals export function getNatureStatMultiplier(nature: Nature, stat: Stat): number { switch (stat) { - case Stat.ATK: - switch (nature) { - case Nature.LONELY: - case Nature.BRAVE: - case Nature.ADAMANT: - case Nature.NAUGHTY: - return 1.1; - case Nature.BOLD: - case Nature.TIMID: - case Nature.MODEST: - case Nature.CALM: - return 0.9; - } - break; - case Stat.DEF: - switch (nature) { - case Nature.BOLD: - case Nature.RELAXED: - case Nature.IMPISH: - case Nature.LAX: - return 1.1; - case Nature.LONELY: - case Nature.HASTY: - case Nature.MILD: - case Nature.GENTLE: - return 0.9; - } - break; - case Stat.SPATK: - switch (nature) { - case Nature.MODEST: - case Nature.MILD: - case Nature.QUIET: - case Nature.RASH: - return 1.1; - case Nature.ADAMANT: - case Nature.IMPISH: - case Nature.JOLLY: - case Nature.CAREFUL: - return 0.9; - } - break; - case Stat.SPDEF: - switch (nature) { - case Nature.CALM: - case Nature.GENTLE: - case Nature.SASSY: - case Nature.CAREFUL: - return 1.1; - case Nature.NAUGHTY: - case Nature.LAX: - case Nature.NAIVE: - case Nature.RASH: - return 0.9; - } - break; - case Stat.SPD: - switch (nature) { - case Nature.TIMID: - case Nature.HASTY: - case Nature.JOLLY: - case Nature.NAIVE: - return 1.1; - case Nature.BRAVE: - case Nature.RELAXED: - case Nature.QUIET: - case Nature.SASSY: - return 0.9; - } - break; + case Stat.ATK: + switch (nature) { + case Nature.LONELY: + case Nature.BRAVE: + case Nature.ADAMANT: + case Nature.NAUGHTY: + return 1.1; + case Nature.BOLD: + case Nature.TIMID: + case Nature.MODEST: + case Nature.CALM: + return 0.9; + } + break; + case Stat.DEF: + switch (nature) { + case Nature.BOLD: + case Nature.RELAXED: + case Nature.IMPISH: + case Nature.LAX: + return 1.1; + case Nature.LONELY: + case Nature.HASTY: + case Nature.MILD: + case Nature.GENTLE: + return 0.9; + } + break; + case Stat.SPATK: + switch (nature) { + case Nature.MODEST: + case Nature.MILD: + case Nature.QUIET: + case Nature.RASH: + return 1.1; + case Nature.ADAMANT: + case Nature.IMPISH: + case Nature.JOLLY: + case Nature.CAREFUL: + return 0.9; + } + break; + case Stat.SPDEF: + switch (nature) { + case Nature.CALM: + case Nature.GENTLE: + case Nature.SASSY: + case Nature.CAREFUL: + return 1.1; + case Nature.NAUGHTY: + case Nature.LAX: + case Nature.NAIVE: + case Nature.RASH: + return 0.9; + } + break; + case Stat.SPD: + switch (nature) { + case Nature.TIMID: + case Nature.HASTY: + case Nature.JOLLY: + case Nature.NAIVE: + return 1.1; + case Nature.BRAVE: + case Nature.RELAXED: + case Nature.QUIET: + case Nature.SASSY: + return 0.9; + } + break; } return 1; diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index 59ff4ed86ce..57a78e2cd61 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -8,77 +8,77 @@ export const MAX_PER_TYPE_POKEBALLS: integer = 99; export function getPokeballAtlasKey(type: PokeballType): string { switch (type) { - case PokeballType.POKEBALL: - return "pb"; - case PokeballType.GREAT_BALL: - return "gb"; - case PokeballType.ULTRA_BALL: - return "ub"; - case PokeballType.ROGUE_BALL: - return "rb"; - case PokeballType.MASTER_BALL: - return "mb"; - case PokeballType.LUXURY_BALL: - return "lb"; + case PokeballType.POKEBALL: + return "pb"; + case PokeballType.GREAT_BALL: + return "gb"; + case PokeballType.ULTRA_BALL: + return "ub"; + case PokeballType.ROGUE_BALL: + return "rb"; + case PokeballType.MASTER_BALL: + return "mb"; + case PokeballType.LUXURY_BALL: + return "lb"; } } export function getPokeballName(type: PokeballType): string { let ret: string; switch (type) { - case PokeballType.POKEBALL: - ret = i18next.t("pokeball:pokeBall"); - break; - case PokeballType.GREAT_BALL: - ret = i18next.t("pokeball:greatBall"); - break; - case PokeballType.ULTRA_BALL: - ret = i18next.t("pokeball:ultraBall"); - break; - case PokeballType.ROGUE_BALL: - ret = i18next.t("pokeball:rogueBall"); - break; - case PokeballType.MASTER_BALL: - ret = i18next.t("pokeball:masterBall"); - break; - case PokeballType.LUXURY_BALL: - ret = i18next.t("pokeball:luxuryBall"); - break; + case PokeballType.POKEBALL: + ret = i18next.t("pokeball:pokeBall"); + break; + case PokeballType.GREAT_BALL: + ret = i18next.t("pokeball:greatBall"); + break; + case PokeballType.ULTRA_BALL: + ret = i18next.t("pokeball:ultraBall"); + break; + case PokeballType.ROGUE_BALL: + ret = i18next.t("pokeball:rogueBall"); + break; + case PokeballType.MASTER_BALL: + ret = i18next.t("pokeball:masterBall"); + break; + case PokeballType.LUXURY_BALL: + ret = i18next.t("pokeball:luxuryBall"); + break; } return ret; } export function getPokeballCatchMultiplier(type: PokeballType): number { switch (type) { - case PokeballType.POKEBALL: - return 1; - case PokeballType.GREAT_BALL: - return 1.5; - case PokeballType.ULTRA_BALL: - return 2; - case PokeballType.ROGUE_BALL: - return 3; - case PokeballType.MASTER_BALL: - return -1; - case PokeballType.LUXURY_BALL: - return 1; + case PokeballType.POKEBALL: + return 1; + case PokeballType.GREAT_BALL: + return 1.5; + case PokeballType.ULTRA_BALL: + return 2; + case PokeballType.ROGUE_BALL: + return 3; + case PokeballType.MASTER_BALL: + return -1; + case PokeballType.LUXURY_BALL: + return 1; } } export function getPokeballTintColor(type: PokeballType): number { switch (type) { - case PokeballType.POKEBALL: - return 0xd52929; - case PokeballType.GREAT_BALL: - return 0x94b4de; - case PokeballType.ULTRA_BALL: - return 0xe6cd31; - case PokeballType.ROGUE_BALL: - return 0xd52929; - case PokeballType.MASTER_BALL: - return 0xa441bd; - case PokeballType.LUXURY_BALL: - return 0xffde6a; + case PokeballType.POKEBALL: + return 0xd52929; + case PokeballType.GREAT_BALL: + return 0x94b4de; + case PokeballType.ULTRA_BALL: + return 0xe6cd31; + case PokeballType.ROGUE_BALL: + return 0xd52929; + case PokeballType.MASTER_BALL: + return 0xa441bd; + case PokeballType.LUXURY_BALL: + return 0xffde6a; } } diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index eb1b761e306..947ac939989 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -238,8 +238,8 @@ export abstract class PokemonSpeciesForm { isRareRegional(): boolean { switch (this.getRegion()) { - case Region.HISUI: - return true; + case Region.HISUI: + return true; } return false; @@ -265,14 +265,14 @@ export abstract class PokemonSpeciesForm { getBaseExp(): number { let ret = this.baseExp; switch (this.getFormSpriteKey()) { - case SpeciesFormKey.MEGA: - case SpeciesFormKey.MEGA_X: - case SpeciesFormKey.MEGA_Y: - case SpeciesFormKey.PRIMAL: - case SpeciesFormKey.GIGANTAMAX: - case SpeciesFormKey.ETERNAMAX: - ret *= 1.5; - break; + case SpeciesFormKey.MEGA: + case SpeciesFormKey.MEGA_X: + case SpeciesFormKey.MEGA_Y: + case SpeciesFormKey.PRIMAL: + case SpeciesFormKey.GIGANTAMAX: + case SpeciesFormKey.ETERNAMAX: + ret *= 1.5; + break; } return ret; } @@ -346,29 +346,29 @@ export abstract class PokemonSpeciesForm { } switch (this.speciesId) { - case Species.HIPPOPOTAS: - case Species.HIPPOWDON: - case Species.UNFEZANT: - case Species.FRILLISH: - case Species.JELLICENT: - case Species.PYROAR: - ret += female ? "-f" : ""; - break; + case Species.HIPPOPOTAS: + case Species.HIPPOWDON: + case Species.UNFEZANT: + case Species.FRILLISH: + case Species.JELLICENT: + case Species.PYROAR: + ret += female ? "-f" : ""; + break; } let formSpriteKey = this.getFormSpriteKey(formIndex); if (formSpriteKey) { switch (this.speciesId) { - case Species.DUDUNSPARCE: - break; - case Species.ZACIAN: - case Species.ZAMAZENTA: - if (formSpriteKey.startsWith("behemoth")) { - formSpriteKey = "crowned"; - } - default: - ret += `-${formSpriteKey}`; - break; + case Species.DUDUNSPARCE: + break; + case Species.ZACIAN: + case Species.ZAMAZENTA: + if (formSpriteKey.startsWith("behemoth")) { + formSpriteKey = "crowned"; + } + default: + ret += `-${formSpriteKey}`; + break; } } @@ -383,15 +383,15 @@ export abstract class PokemonSpeciesForm { let speciesId = this.speciesId; if (this.speciesId > 2000) { switch (this.speciesId) { - case Species.GALAR_SLOWPOKE: - break; - case Species.ETERNAL_FLOETTE: - break; - case Species.BLOODMOON_URSALUNA: - break; - default: - speciesId = speciesId % 2000; - break; + case Species.GALAR_SLOWPOKE: + break; + case Species.ETERNAL_FLOETTE: + break; + case Species.BLOODMOON_URSALUNA: + break; + default: + speciesId = speciesId % 2000; + break; } } let ret = speciesId.toString(); @@ -403,44 +403,44 @@ export abstract class PokemonSpeciesForm { } const formKey = forms[formIndex || 0].formKey; switch (formKey) { - case SpeciesFormKey.MEGA: - case SpeciesFormKey.MEGA_X: - case SpeciesFormKey.MEGA_Y: - case SpeciesFormKey.GIGANTAMAX: - case SpeciesFormKey.GIGANTAMAX_SINGLE: - case SpeciesFormKey.GIGANTAMAX_RAPID: - case "white": - case "black": - case "therian": - case "sky": - case "gorging": - case "gulping": - case "no-ice": - case "hangry": - case "crowned": - case "eternamax": - case "four": - case "droopy": - case "stretchy": - case "hero": - case "roaming": - case "complete": - case "10-complete": - case "10": - case "10-pc": - case "super": - case "unbound": - case "pau": - case "pompom": - case "sensu": - case "dusk": - case "midnight": - case "school": - case "dawn-wings": - case "dusk-mane": - case "ultra": - ret += `-${formKey}`; - break; + case SpeciesFormKey.MEGA: + case SpeciesFormKey.MEGA_X: + case SpeciesFormKey.MEGA_Y: + case SpeciesFormKey.GIGANTAMAX: + case SpeciesFormKey.GIGANTAMAX_SINGLE: + case SpeciesFormKey.GIGANTAMAX_RAPID: + case "white": + case "black": + case "therian": + case "sky": + case "gorging": + case "gulping": + case "no-ice": + case "hangry": + case "crowned": + case "eternamax": + case "four": + case "droopy": + case "stretchy": + case "hero": + case "roaming": + case "complete": + case "10-complete": + case "10": + case "10-pc": + case "super": + case "unbound": + case "pau": + case "pompom": + case "sensu": + case "dusk": + case "midnight": + case "school": + case "dawn-wings": + case "dusk-mane": + case "ultra": + ret += `-${formKey}`; + break; } } return ret; @@ -636,19 +636,19 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali const form = this.forms[formIndex]; let key: string | null; switch (form.formKey) { - case SpeciesFormKey.MEGA: - case SpeciesFormKey.PRIMAL: - case SpeciesFormKey.ETERNAMAX: - case SpeciesFormKey.MEGA_X: - case SpeciesFormKey.MEGA_Y: - key = form.formKey; - break; - default: - if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) { - key = "gigantamax"; - } else { - key = null; - } + case SpeciesFormKey.MEGA: + case SpeciesFormKey.PRIMAL: + case SpeciesFormKey.ETERNAMAX: + case SpeciesFormKey.MEGA_X: + case SpeciesFormKey.MEGA_Y: + key = form.formKey; + break; + default: + if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) { + key = "gigantamax"; + } else { + key = null; + } } if (key) { @@ -690,18 +690,18 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali */ private getStrengthLevelDiff(strength: PartyMemberStrength): integer { switch (Math.min(strength, PartyMemberStrength.STRONGER)) { - case PartyMemberStrength.WEAKEST: - return 60; - case PartyMemberStrength.WEAKER: - return 40; - case PartyMemberStrength.WEAK: - return 20; - case PartyMemberStrength.AVERAGE: - return 8; - case PartyMemberStrength.STRONG: - return 4; - default: - return 0; + case PartyMemberStrength.WEAKEST: + return 60; + case PartyMemberStrength.WEAKER: + return 40; + case PartyMemberStrength.WEAK: + return 20; + case PartyMemberStrength.AVERAGE: + return 8; + case PartyMemberStrength.STRONG: + return 4; + default: + return 0; } } diff --git a/src/data/status-effect.ts b/src/data/status-effect.ts index ffe32a02aeb..4319985f43a 100644 --- a/src/data/status-effect.ts +++ b/src/data/status-effect.ts @@ -26,20 +26,20 @@ export class Status { function getStatusEffectMessageKey(statusEffect: StatusEffect | undefined): string { switch (statusEffect) { - case StatusEffect.POISON: - return "statusEffect:poison"; - case StatusEffect.TOXIC: - return "statusEffect:toxic"; - case StatusEffect.PARALYSIS: - return "statusEffect:paralysis"; - case StatusEffect.SLEEP: - return "statusEffect:sleep"; - case StatusEffect.FREEZE: - return "statusEffect:freeze"; - case StatusEffect.BURN: - return "statusEffect:burn"; - default: - return "statusEffect:none"; + case StatusEffect.POISON: + return "statusEffect:poison"; + case StatusEffect.TOXIC: + return "statusEffect:toxic"; + case StatusEffect.PARALYSIS: + return "statusEffect:paralysis"; + case StatusEffect.SLEEP: + return "statusEffect:sleep"; + case StatusEffect.FREEZE: + return "statusEffect:freeze"; + case StatusEffect.BURN: + return "statusEffect:burn"; + default: + return "statusEffect:none"; } } @@ -90,14 +90,14 @@ export function getStatusEffectDescriptor(statusEffect: StatusEffect): string { export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect): number { switch (statusEffect) { - case StatusEffect.POISON: - case StatusEffect.TOXIC: - case StatusEffect.PARALYSIS: - case StatusEffect.BURN: - return 1.5; - case StatusEffect.SLEEP: - case StatusEffect.FREEZE: - return 2.5; + case StatusEffect.POISON: + case StatusEffect.TOXIC: + case StatusEffect.PARALYSIS: + case StatusEffect.BURN: + return 1.5; + case StatusEffect.SLEEP: + case StatusEffect.FREEZE: + return 2.5; } return 1; diff --git a/src/data/terrain.ts b/src/data/terrain.ts index 6db68b92239..d8ee8d67925 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -34,21 +34,21 @@ export class Terrain { getAttackTypeMultiplier(attackType: Type): number { switch (this.terrainType) { - case TerrainType.ELECTRIC: - if (attackType === Type.ELECTRIC) { - return 1.3; - } - break; - case TerrainType.GRASSY: - if (attackType === Type.GRASS) { - return 1.3; - } - break; - case TerrainType.PSYCHIC: - if (attackType === Type.PSYCHIC) { - return 1.3; - } - break; + case TerrainType.ELECTRIC: + if (attackType === Type.ELECTRIC) { + return 1.3; + } + break; + case TerrainType.GRASSY: + if (attackType === Type.GRASS) { + return 1.3; + } + break; + case TerrainType.PSYCHIC: + if (attackType === Type.PSYCHIC) { + return 1.3; + } + break; } return 1; @@ -56,13 +56,13 @@ export class Terrain { isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean { switch (this.terrainType) { - case TerrainType.PSYCHIC: - if (!move.hasAttr(ProtectAttr)) { - const priority = new Utils.IntegerHolder(move.priority); - applyAbAttrs(ChangeMovePriorityAbAttr, user, null, false, move, priority); - // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain - return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded()); - } + case TerrainType.PSYCHIC: + if (!move.hasAttr(ProtectAttr)) { + const priority = new Utils.IntegerHolder(move.priority); + applyAbAttrs(ChangeMovePriorityAbAttr, user, null, false, move, priority); + // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain + return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded()); + } } return false; @@ -71,14 +71,14 @@ export class Terrain { export function getTerrainName(terrainType: TerrainType): string { switch (terrainType) { - case TerrainType.MISTY: - return i18next.t("terrain:misty"); - case TerrainType.ELECTRIC: - return i18next.t("terrain:electric"); - case TerrainType.GRASSY: - return i18next.t("terrain:grassy"); - case TerrainType.PSYCHIC: - return i18next.t("terrain:psychic"); + case TerrainType.MISTY: + return i18next.t("terrain:misty"); + case TerrainType.ELECTRIC: + return i18next.t("terrain:electric"); + case TerrainType.GRASSY: + return i18next.t("terrain:grassy"); + case TerrainType.PSYCHIC: + return i18next.t("terrain:psychic"); } return ""; @@ -87,14 +87,14 @@ export function getTerrainName(terrainType: TerrainType): string { export function getTerrainColor(terrainType: TerrainType): [ integer, integer, integer ] { switch (terrainType) { - case TerrainType.MISTY: - return [ 232, 136, 200 ]; - case TerrainType.ELECTRIC: - return [ 248, 248, 120 ]; - case TerrainType.GRASSY: - return [ 120, 200, 80 ]; - case TerrainType.PSYCHIC: - return [ 160, 64, 160 ]; + case TerrainType.MISTY: + return [ 232, 136, 200 ]; + case TerrainType.ELECTRIC: + return [ 248, 248, 120 ]; + case TerrainType.GRASSY: + return [ 120, 200, 80 ]; + case TerrainType.PSYCHIC: + return [ 160, 64, 160 ]; } return [ 0, 0, 0 ]; diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index bbeecba84e6..fcc13975270 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -299,65 +299,65 @@ export class TrainerConfig { getDerivedType(trainerTypeToDeriveFrom: TrainerType | null = null): TrainerType { let trainerType = trainerTypeToDeriveFrom ? trainerTypeToDeriveFrom : this.trainerType; switch (trainerType) { - case TrainerType.RIVAL_2: - case TrainerType.RIVAL_3: - case TrainerType.RIVAL_4: - case TrainerType.RIVAL_5: - case TrainerType.RIVAL_6: - trainerType = TrainerType.RIVAL; - break; - case TrainerType.LANCE_CHAMPION: - trainerType = TrainerType.LANCE; - break; - case TrainerType.LARRY_ELITE: - trainerType = TrainerType.LARRY; - break; - case TrainerType.ROCKET_BOSS_GIOVANNI_1: - case TrainerType.ROCKET_BOSS_GIOVANNI_2: - trainerType = TrainerType.GIOVANNI; - break; - case TrainerType.MAXIE_2: - trainerType = TrainerType.MAXIE; - break; - case TrainerType.ARCHIE_2: - trainerType = TrainerType.ARCHIE; - break; - case TrainerType.CYRUS_2: - trainerType = TrainerType.CYRUS; - break; - case TrainerType.GHETSIS_2: - trainerType = TrainerType.GHETSIS; - break; - case TrainerType.LYSANDRE_2: - trainerType = TrainerType.LYSANDRE; - break; - case TrainerType.LUSAMINE_2: - trainerType = TrainerType.LUSAMINE; - break; - case TrainerType.GUZMA_2: - trainerType = TrainerType.GUZMA; - break; - case TrainerType.ROSE_2: - trainerType = TrainerType.ROSE; - break; - case TrainerType.PENNY_2: - trainerType = TrainerType.PENNY; - break; - case TrainerType.MARNIE_ELITE: - trainerType = TrainerType.MARNIE; - break; - case TrainerType.NESSA_ELITE: - trainerType = TrainerType.NESSA; - break; - case TrainerType.BEA_ELITE: - trainerType = TrainerType.BEA; - break; - case TrainerType.ALLISTER_ELITE: - trainerType = TrainerType.ALLISTER; - break; - case TrainerType.RAIHAN_ELITE: - trainerType = TrainerType.RAIHAN; - break; + case TrainerType.RIVAL_2: + case TrainerType.RIVAL_3: + case TrainerType.RIVAL_4: + case TrainerType.RIVAL_5: + case TrainerType.RIVAL_6: + trainerType = TrainerType.RIVAL; + break; + case TrainerType.LANCE_CHAMPION: + trainerType = TrainerType.LANCE; + break; + case TrainerType.LARRY_ELITE: + trainerType = TrainerType.LARRY; + break; + case TrainerType.ROCKET_BOSS_GIOVANNI_1: + case TrainerType.ROCKET_BOSS_GIOVANNI_2: + trainerType = TrainerType.GIOVANNI; + break; + case TrainerType.MAXIE_2: + trainerType = TrainerType.MAXIE; + break; + case TrainerType.ARCHIE_2: + trainerType = TrainerType.ARCHIE; + break; + case TrainerType.CYRUS_2: + trainerType = TrainerType.CYRUS; + break; + case TrainerType.GHETSIS_2: + trainerType = TrainerType.GHETSIS; + break; + case TrainerType.LYSANDRE_2: + trainerType = TrainerType.LYSANDRE; + break; + case TrainerType.LUSAMINE_2: + trainerType = TrainerType.LUSAMINE; + break; + case TrainerType.GUZMA_2: + trainerType = TrainerType.GUZMA; + break; + case TrainerType.ROSE_2: + trainerType = TrainerType.ROSE; + break; + case TrainerType.PENNY_2: + trainerType = TrainerType.PENNY; + break; + case TrainerType.MARNIE_ELITE: + trainerType = TrainerType.MARNIE; + break; + case TrainerType.NESSA_ELITE: + trainerType = TrainerType.NESSA; + break; + case TrainerType.BEA_ELITE: + trainerType = TrainerType.BEA; + break; + case TrainerType.ALLISTER_ELITE: + trainerType = TrainerType.ALLISTER; + break; + case TrainerType.RAIHAN_ELITE: + trainerType = TrainerType.RAIHAN; + break; } return trainerType; @@ -564,104 +564,104 @@ export class TrainerConfig { speciesPoolPerEvilTeamAdmin(team): TrainerTierPools { team = team.toLowerCase(); switch (team) { - case "rocket": { - return { - [TrainerPoolTier.COMMON]: [ Species.RATTATA, Species.KOFFING, Species.EKANS, Species.ZUBAT, Species.MAGIKARP, Species.HOUNDOUR, Species.ONIX, Species.CUBONE, Species.GROWLITHE, Species.MURKROW, Species.GASTLY, Species.EXEGGCUTE, Species.VOLTORB, Species.DROWZEE, Species.VILEPLUME ], - [TrainerPoolTier.UNCOMMON]: [ Species.PORYGON, Species.MANKEY, Species.MAGNEMITE, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE, Species.PALDEA_TAUROS, Species.OMANYTE, Species.KABUTO, Species.MAGBY, Species.ELEKID ], - [TrainerPoolTier.RARE]: [ Species.DRATINI, Species.LARVITAR ] - }; - } - case "magma": { - return { - [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.SLUGMA, Species.SOLROCK, Species.HIPPOPOTAS, Species.BALTOY, Species.ROLYCOLY, Species.GLIGAR, Species.TORKOAL, Species.HOUNDOUR, Species.MAGBY ], - [TrainerPoolTier.UNCOMMON]: [ Species.TRAPINCH, Species.SILICOBRA, Species.RHYHORN, Species.ANORITH, Species.LILEEP, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.TOEDSCOOL ], - [TrainerPoolTier.RARE]: [ Species.CAPSAKID, Species.CHARCADET ] - }; - } - case "aqua": { - return { - [TrainerPoolTier.COMMON]: [ Species.CORPHISH, Species.SPHEAL, Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.LOTAD, Species.WAILMER, Species.REMORAID, Species.BARBOACH ], - [TrainerPoolTier.UNCOMMON]: [ Species.MANTYKE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.DHELMISE, Species.CLOBBOPUS, Species.FEEBAS, Species.PALDEA_WOOPER, Species.HORSEA, Species.SKRELP ], - [TrainerPoolTier.RARE]: [ Species.DONDOZO, Species.BASCULEGION ] - }; - } - case "galactic": { - return { - [TrainerPoolTier.COMMON]: [ Species.BRONZOR, Species.SWINUB, Species.YANMA, Species.LICKITUNG, Species.TANGELA, Species.MAGBY, Species.ELEKID, Species.SKORUPI, Species.ZUBAT, Species.MURKROW, Species.MAGIKARP, Species.VOLTORB ], - [TrainerPoolTier.UNCOMMON]: [ Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.SNEASEL, Species.DUSKULL, Species.ROTOM, Species.HISUI_VOLTORB, Species.GLIGAR, Species.ABRA ], - [TrainerPoolTier.RARE]: [ Species.URSALUNA, Species.HISUI_LILLIGANT, Species.SPIRITOMB, Species.HISUI_SNEASEL ] - }; - } - case "plasma": { - return { - [TrainerPoolTier.COMMON]: [ Species.YAMASK, Species.ROGGENROLA, Species.JOLTIK, Species.TYMPOLE, Species.FRILLISH, Species.FERROSEED, Species.SANDILE, Species.TIMBURR, Species.DARUMAKA, Species.FOONGUS, Species.CUBCHOO, Species.VANILLITE ], - [TrainerPoolTier.UNCOMMON]: [ Species.PAWNIARD, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK, Species.TYNAMO, Species.GALAR_DARUMAKA, Species.GOLETT, Species.MIENFOO, Species.DURANT, Species.SIGILYPH ], - [TrainerPoolTier.RARE]: [ Species.HISUI_ZORUA, Species.AXEW, Species.DEINO, Species.HISUI_BRAVIARY ] - }; - } - case "flare": { - return { - [TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.FOONGUS, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKORUPI, Species.PURRLOIN, Species.CLAWITZER, Species.PANCHAM, Species.ESPURR, Species.BUNNELBY ], - [TrainerPoolTier.UNCOMMON]: [ Species.LITWICK, Species.SNEASEL, Species.PUMPKABOO, Species.PHANTUMP, Species.HONEDGE, Species.BINACLE, Species.HOUNDOUR, Species.SKRELP, Species.SLIGGOO ], - [TrainerPoolTier.RARE]: [ Species.NOIBAT, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ] - }; - } - case "aether": { - return { - [TrainerPoolTier.COMMON]: [ Species.BRUXISH, Species.SLOWPOKE, Species.BALTOY, Species.EXEGGCUTE, Species.ABRA, Species.ALOLA_RAICHU, Species.ELGYEM, Species.NATU, Species.BLIPBUG, Species.GIRAFARIG, Species.ORANGURU ], - [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.MEDITITE, Species.BELDUM, Species.HATENNA, Species.INKAY, Species.RALTS, Species.GALAR_MR_MIME ], - [TrainerPoolTier.RARE]: [ Species.ARMAROUGE, Species.HISUI_BRAVIARY, Species.PORYGON ] - }; - } - case "skull": { - return { - [TrainerPoolTier.COMMON]: [ Species.MAREANIE, Species.ALOLA_GRIMER, Species.GASTLY, Species.ZUBAT, Species.FOMANTIS, Species.VENIPEDE, Species.BUDEW, Species.KOFFING, Species.STUNKY, Species.CROAGUNK, Species.NIDORAN_F ], - [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.SKORUPI, Species.PALDEA_WOOPER, Species.VULLABY, Species.HISUI_QWILFISH, Species.GLIMMET ], - [TrainerPoolTier.RARE]: [ Species.SKRELP, Species.HISUI_SNEASEL ] - }; - } - case "macro": { - return { - [TrainerPoolTier.COMMON]: [ Species.HATENNA, Species.FEEBAS, Species.BOUNSWEET, Species.SALANDIT, Species.GALAR_PONYTA, Species.GOTHITA, Species.FROSLASS, Species.VULPIX, Species.FRILLISH, Species.ODDISH, Species.SINISTEA ], - [TrainerPoolTier.UNCOMMON]: [ Species.VULLABY, Species.MAREANIE, Species.ALOLA_VULPIX, Species.TOGEPI, Species.GALAR_CORSOLA, Species.APPLIN ], - [TrainerPoolTier.RARE]: [ Species.TINKATINK, Species.HISUI_LILLIGANT ] - }; - } - case "star_1": { - return { - [TrainerPoolTier.COMMON]: [ Species.MURKROW, Species.SEEDOT, Species.CACNEA, Species.STUNKY, Species.SANDILE, Species.NYMBLE, Species.MASCHIFF, Species.GALAR_ZIGZAGOON ], - [TrainerPoolTier.UNCOMMON]: [ Species.UMBREON, Species.SNEASEL, Species.CORPHISH, Species.ZORUA, Species.INKAY, Species.BOMBIRDIER ], - [TrainerPoolTier.RARE]: [ Species.DEINO, Species.SPRIGATITO ] - }; - } - case "star_2": { - return { - [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.HOUNDOUR, Species.NUMEL, Species.LITWICK, Species.FLETCHLING, Species.LITLEO, Species.ROLYCOLY, Species.CAPSAKID ], - [TrainerPoolTier.UNCOMMON]: [ Species.PONYTA, Species.FLAREON, Species.MAGBY, Species.TORKOAL, Species.SALANDIT, Species.TURTONATOR ], - [TrainerPoolTier.RARE]: [ Species.LARVESTA, Species.FUECOCO ] - }; - } - case "star_3": { - return { - [TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ], - [TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ], - [TrainerPoolTier.RARE]: [ Species.GLIMMET, Species.BULBASAUR ] - }; - } - case "star_4": { - return { - [TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ], - [TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ], - [TrainerPoolTier.RARE]: [ Species.GALAR_PONYTA, Species.POPPLIO ] - }; - } - case "star_5": { - return { - [TrainerPoolTier.COMMON]: [ Species.SHROOMISH, Species.MAKUHITA, Species.MEDITITE, Species.CROAGUNK, Species.SCRAGGY, Species.MIENFOO, Species.PAWMI, Species.PALDEA_TAUROS ], - [TrainerPoolTier.UNCOMMON]: [ Species.RIOLU, Species.TIMBURR, Species.HAWLUCHA, Species.PASSIMIAN, Species.FALINKS, Species.FLAMIGO ], - [TrainerPoolTier.RARE]: [ Species.JANGMO_O, Species.QUAXLY ] - }; - } + case "rocket": { + return { + [TrainerPoolTier.COMMON]: [ Species.RATTATA, Species.KOFFING, Species.EKANS, Species.ZUBAT, Species.MAGIKARP, Species.HOUNDOUR, Species.ONIX, Species.CUBONE, Species.GROWLITHE, Species.MURKROW, Species.GASTLY, Species.EXEGGCUTE, Species.VOLTORB, Species.DROWZEE, Species.VILEPLUME ], + [TrainerPoolTier.UNCOMMON]: [ Species.PORYGON, Species.MANKEY, Species.MAGNEMITE, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE, Species.PALDEA_TAUROS, Species.OMANYTE, Species.KABUTO, Species.MAGBY, Species.ELEKID ], + [TrainerPoolTier.RARE]: [ Species.DRATINI, Species.LARVITAR ] + }; + } + case "magma": { + return { + [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.SLUGMA, Species.SOLROCK, Species.HIPPOPOTAS, Species.BALTOY, Species.ROLYCOLY, Species.GLIGAR, Species.TORKOAL, Species.HOUNDOUR, Species.MAGBY ], + [TrainerPoolTier.UNCOMMON]: [ Species.TRAPINCH, Species.SILICOBRA, Species.RHYHORN, Species.ANORITH, Species.LILEEP, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.TOEDSCOOL ], + [TrainerPoolTier.RARE]: [ Species.CAPSAKID, Species.CHARCADET ] + }; + } + case "aqua": { + return { + [TrainerPoolTier.COMMON]: [ Species.CORPHISH, Species.SPHEAL, Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.LOTAD, Species.WAILMER, Species.REMORAID, Species.BARBOACH ], + [TrainerPoolTier.UNCOMMON]: [ Species.MANTYKE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.DHELMISE, Species.CLOBBOPUS, Species.FEEBAS, Species.PALDEA_WOOPER, Species.HORSEA, Species.SKRELP ], + [TrainerPoolTier.RARE]: [ Species.DONDOZO, Species.BASCULEGION ] + }; + } + case "galactic": { + return { + [TrainerPoolTier.COMMON]: [ Species.BRONZOR, Species.SWINUB, Species.YANMA, Species.LICKITUNG, Species.TANGELA, Species.MAGBY, Species.ELEKID, Species.SKORUPI, Species.ZUBAT, Species.MURKROW, Species.MAGIKARP, Species.VOLTORB ], + [TrainerPoolTier.UNCOMMON]: [ Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.SNEASEL, Species.DUSKULL, Species.ROTOM, Species.HISUI_VOLTORB, Species.GLIGAR, Species.ABRA ], + [TrainerPoolTier.RARE]: [ Species.URSALUNA, Species.HISUI_LILLIGANT, Species.SPIRITOMB, Species.HISUI_SNEASEL ] + }; + } + case "plasma": { + return { + [TrainerPoolTier.COMMON]: [ Species.YAMASK, Species.ROGGENROLA, Species.JOLTIK, Species.TYMPOLE, Species.FRILLISH, Species.FERROSEED, Species.SANDILE, Species.TIMBURR, Species.DARUMAKA, Species.FOONGUS, Species.CUBCHOO, Species.VANILLITE ], + [TrainerPoolTier.UNCOMMON]: [ Species.PAWNIARD, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK, Species.TYNAMO, Species.GALAR_DARUMAKA, Species.GOLETT, Species.MIENFOO, Species.DURANT, Species.SIGILYPH ], + [TrainerPoolTier.RARE]: [ Species.HISUI_ZORUA, Species.AXEW, Species.DEINO, Species.HISUI_BRAVIARY ] + }; + } + case "flare": { + return { + [TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.FOONGUS, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKORUPI, Species.PURRLOIN, Species.CLAWITZER, Species.PANCHAM, Species.ESPURR, Species.BUNNELBY ], + [TrainerPoolTier.UNCOMMON]: [ Species.LITWICK, Species.SNEASEL, Species.PUMPKABOO, Species.PHANTUMP, Species.HONEDGE, Species.BINACLE, Species.HOUNDOUR, Species.SKRELP, Species.SLIGGOO ], + [TrainerPoolTier.RARE]: [ Species.NOIBAT, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ] + }; + } + case "aether": { + return { + [TrainerPoolTier.COMMON]: [ Species.BRUXISH, Species.SLOWPOKE, Species.BALTOY, Species.EXEGGCUTE, Species.ABRA, Species.ALOLA_RAICHU, Species.ELGYEM, Species.NATU, Species.BLIPBUG, Species.GIRAFARIG, Species.ORANGURU ], + [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.MEDITITE, Species.BELDUM, Species.HATENNA, Species.INKAY, Species.RALTS, Species.GALAR_MR_MIME ], + [TrainerPoolTier.RARE]: [ Species.ARMAROUGE, Species.HISUI_BRAVIARY, Species.PORYGON ] + }; + } + case "skull": { + return { + [TrainerPoolTier.COMMON]: [ Species.MAREANIE, Species.ALOLA_GRIMER, Species.GASTLY, Species.ZUBAT, Species.FOMANTIS, Species.VENIPEDE, Species.BUDEW, Species.KOFFING, Species.STUNKY, Species.CROAGUNK, Species.NIDORAN_F ], + [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.SKORUPI, Species.PALDEA_WOOPER, Species.VULLABY, Species.HISUI_QWILFISH, Species.GLIMMET ], + [TrainerPoolTier.RARE]: [ Species.SKRELP, Species.HISUI_SNEASEL ] + }; + } + case "macro": { + return { + [TrainerPoolTier.COMMON]: [ Species.HATENNA, Species.FEEBAS, Species.BOUNSWEET, Species.SALANDIT, Species.GALAR_PONYTA, Species.GOTHITA, Species.FROSLASS, Species.VULPIX, Species.FRILLISH, Species.ODDISH, Species.SINISTEA ], + [TrainerPoolTier.UNCOMMON]: [ Species.VULLABY, Species.MAREANIE, Species.ALOLA_VULPIX, Species.TOGEPI, Species.GALAR_CORSOLA, Species.APPLIN ], + [TrainerPoolTier.RARE]: [ Species.TINKATINK, Species.HISUI_LILLIGANT ] + }; + } + case "star_1": { + return { + [TrainerPoolTier.COMMON]: [ Species.MURKROW, Species.SEEDOT, Species.CACNEA, Species.STUNKY, Species.SANDILE, Species.NYMBLE, Species.MASCHIFF, Species.GALAR_ZIGZAGOON ], + [TrainerPoolTier.UNCOMMON]: [ Species.UMBREON, Species.SNEASEL, Species.CORPHISH, Species.ZORUA, Species.INKAY, Species.BOMBIRDIER ], + [TrainerPoolTier.RARE]: [ Species.DEINO, Species.SPRIGATITO ] + }; + } + case "star_2": { + return { + [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.HOUNDOUR, Species.NUMEL, Species.LITWICK, Species.FLETCHLING, Species.LITLEO, Species.ROLYCOLY, Species.CAPSAKID ], + [TrainerPoolTier.UNCOMMON]: [ Species.PONYTA, Species.FLAREON, Species.MAGBY, Species.TORKOAL, Species.SALANDIT, Species.TURTONATOR ], + [TrainerPoolTier.RARE]: [ Species.LARVESTA, Species.FUECOCO ] + }; + } + case "star_3": { + return { + [TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ], + [TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ], + [TrainerPoolTier.RARE]: [ Species.GLIMMET, Species.BULBASAUR ] + }; + } + case "star_4": { + return { + [TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ], + [TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ], + [TrainerPoolTier.RARE]: [ Species.GALAR_PONYTA, Species.POPPLIO ] + }; + } + case "star_5": { + return { + [TrainerPoolTier.COMMON]: [ Species.SHROOMISH, Species.MAKUHITA, Species.MEDITITE, Species.CROAGUNK, Species.SCRAGGY, Species.MIENFOO, Species.PAWMI, Species.PALDEA_TAUROS ], + [TrainerPoolTier.UNCOMMON]: [ Species.RIOLU, Species.TIMBURR, Species.HAWLUCHA, Species.PASSIMIAN, Species.FALINKS, Species.FLAMIGO ], + [TrainerPoolTier.RARE]: [ Species.JANGMO_O, Species.QUAXLY ] + }; + } } console.warn(`Evil team admin for ${team} not found. Returning empty species pools.`); diff --git a/src/data/type.ts b/src/data/type.ts index 47bea8dd72b..483ec068d3c 100644 --- a/src/data/type.ts +++ b/src/data/type.ts @@ -29,260 +29,260 @@ export function getTypeDamageMultiplier(attackType: Type, defType: Type): TypeDa } switch (defType) { - case Type.NORMAL: - switch (attackType) { - case Type.FIGHTING: - return 2; - case Type.GHOST: - return 0; - default: - return 1; - } - case Type.FIGHTING: - switch (attackType) { - case Type.FLYING: - case Type.PSYCHIC: - case Type.FAIRY: - return 2; - case Type.ROCK: - case Type.BUG: - case Type.DARK: - return 0.5; - default: - return 1; - } - case Type.FLYING: - switch (attackType) { - case Type.ROCK: - case Type.ELECTRIC: - case Type.ICE: - return 2; - case Type.FIGHTING: - case Type.BUG: - case Type.GRASS: - return 0.5; - case Type.GROUND: - return 0; - default: - return 1; - } - case Type.POISON: - switch (attackType) { - case Type.GROUND: - case Type.PSYCHIC: - return 2; - case Type.FIGHTING: - case Type.POISON: - case Type.BUG: - case Type.GRASS: - case Type.FAIRY: - return 0.5; - default: - return 1; - } - case Type.GROUND: - switch (attackType) { - case Type.WATER: - case Type.GRASS: - case Type.ICE: - return 2; - case Type.POISON: - case Type.ROCK: - return 0.5; - case Type.ELECTRIC: - return 0; - default: - return 1; - } - case Type.ROCK: - switch (attackType) { - case Type.FIGHTING: - case Type.GROUND: - case Type.STEEL: - case Type.WATER: - case Type.GRASS: - return 2; case Type.NORMAL: - case Type.FLYING: - case Type.POISON: - case Type.FIRE: - return 0.5; - default: - return 1; - } - case Type.BUG: - switch (attackType) { - case Type.FLYING: - case Type.ROCK: - case Type.FIRE: - return 2; + switch (attackType) { + case Type.FIGHTING: + return 2; + case Type.GHOST: + return 0; + default: + return 1; + } case Type.FIGHTING: + switch (attackType) { + case Type.FLYING: + case Type.PSYCHIC: + case Type.FAIRY: + return 2; + case Type.ROCK: + case Type.BUG: + case Type.DARK: + return 0.5; + default: + return 1; + } + case Type.FLYING: + switch (attackType) { + case Type.ROCK: + case Type.ELECTRIC: + case Type.ICE: + return 2; + case Type.FIGHTING: + case Type.BUG: + case Type.GRASS: + return 0.5; + case Type.GROUND: + return 0; + default: + return 1; + } + case Type.POISON: + switch (attackType) { + case Type.GROUND: + case Type.PSYCHIC: + return 2; + case Type.FIGHTING: + case Type.POISON: + case Type.BUG: + case Type.GRASS: + case Type.FAIRY: + return 0.5; + default: + return 1; + } case Type.GROUND: - case Type.GRASS: - return 0.5; - default: - return 1; - } - case Type.GHOST: - switch (attackType) { + switch (attackType) { + case Type.WATER: + case Type.GRASS: + case Type.ICE: + return 2; + case Type.POISON: + case Type.ROCK: + return 0.5; + case Type.ELECTRIC: + return 0; + default: + return 1; + } + case Type.ROCK: + switch (attackType) { + case Type.FIGHTING: + case Type.GROUND: + case Type.STEEL: + case Type.WATER: + case Type.GRASS: + return 2; + case Type.NORMAL: + case Type.FLYING: + case Type.POISON: + case Type.FIRE: + return 0.5; + default: + return 1; + } + case Type.BUG: + switch (attackType) { + case Type.FLYING: + case Type.ROCK: + case Type.FIRE: + return 2; + case Type.FIGHTING: + case Type.GROUND: + case Type.GRASS: + return 0.5; + default: + return 1; + } case Type.GHOST: - case Type.DARK: - return 2; - case Type.POISON: - case Type.BUG: - return 0.5; - case Type.NORMAL: - case Type.FIGHTING: - return 0; - default: - return 1; - } - case Type.STEEL: - switch (attackType) { - case Type.FIGHTING: - case Type.GROUND: - case Type.FIRE: - return 2; - case Type.NORMAL: - case Type.FLYING: - case Type.ROCK: - case Type.BUG: + switch (attackType) { + case Type.GHOST: + case Type.DARK: + return 2; + case Type.POISON: + case Type.BUG: + return 0.5; + case Type.NORMAL: + case Type.FIGHTING: + return 0; + default: + return 1; + } case Type.STEEL: + switch (attackType) { + case Type.FIGHTING: + case Type.GROUND: + case Type.FIRE: + return 2; + case Type.NORMAL: + case Type.FLYING: + case Type.ROCK: + case Type.BUG: + case Type.STEEL: + case Type.GRASS: + case Type.PSYCHIC: + case Type.ICE: + case Type.DRAGON: + case Type.FAIRY: + return 0.5; + case Type.POISON: + return 0; + default: + return 1; + } + case Type.FIRE: + switch (attackType) { + case Type.GROUND: + case Type.ROCK: + case Type.WATER: + return 2; + case Type.BUG: + case Type.STEEL: + case Type.FIRE: + case Type.GRASS: + case Type.ICE: + case Type.FAIRY: + return 0.5; + default: + return 1; + } + case Type.WATER: + switch (attackType) { + case Type.GRASS: + case Type.ELECTRIC: + return 2; + case Type.STEEL: + case Type.FIRE: + case Type.WATER: + case Type.ICE: + return 0.5; + default: + return 1; + } case Type.GRASS: + switch (attackType) { + case Type.FLYING: + case Type.POISON: + case Type.BUG: + case Type.FIRE: + case Type.ICE: + return 2; + case Type.GROUND: + case Type.WATER: + case Type.GRASS: + case Type.ELECTRIC: + return 0.5; + default: + return 1; + } + case Type.ELECTRIC: + switch (attackType) { + case Type.GROUND: + return 2; + case Type.FLYING: + case Type.STEEL: + case Type.ELECTRIC: + return 0.5; + default: + return 1; + } case Type.PSYCHIC: + switch (attackType) { + case Type.BUG: + case Type.GHOST: + case Type.DARK: + return 2; + case Type.FIGHTING: + case Type.PSYCHIC: + return 0.5; + default: + return 1; + } case Type.ICE: + switch (attackType) { + case Type.FIGHTING: + case Type.ROCK: + case Type.STEEL: + case Type.FIRE: + return 2; + case Type.ICE: + return 0.5; + default: + return 1; + } case Type.DRAGON: - case Type.FAIRY: - return 0.5; - case Type.POISON: - return 0; - default: - return 1; - } - case Type.FIRE: - switch (attackType) { - case Type.GROUND: - case Type.ROCK: - case Type.WATER: - return 2; - case Type.BUG: - case Type.STEEL: - case Type.FIRE: - case Type.GRASS: - case Type.ICE: - case Type.FAIRY: - return 0.5; - default: - return 1; - } - case Type.WATER: - switch (attackType) { - case Type.GRASS: - case Type.ELECTRIC: - return 2; - case Type.STEEL: - case Type.FIRE: - case Type.WATER: - case Type.ICE: - return 0.5; - default: - return 1; - } - case Type.GRASS: - switch (attackType) { - case Type.FLYING: - case Type.POISON: - case Type.BUG: - case Type.FIRE: - case Type.ICE: - return 2; - case Type.GROUND: - case Type.WATER: - case Type.GRASS: - case Type.ELECTRIC: - return 0.5; - default: - return 1; - } - case Type.ELECTRIC: - switch (attackType) { - case Type.GROUND: - return 2; - case Type.FLYING: - case Type.STEEL: - case Type.ELECTRIC: - return 0.5; - default: - return 1; - } - case Type.PSYCHIC: - switch (attackType) { - case Type.BUG: - case Type.GHOST: + switch (attackType) { + case Type.ICE: + case Type.DRAGON: + case Type.FAIRY: + return 2; + case Type.FIRE: + case Type.WATER: + case Type.GRASS: + case Type.ELECTRIC: + return 0.5; + default: + return 1; + } case Type.DARK: - return 2; - case Type.FIGHTING: - case Type.PSYCHIC: - return 0.5; - default: - return 1; - } - case Type.ICE: - switch (attackType) { - case Type.FIGHTING: - case Type.ROCK: - case Type.STEEL: - case Type.FIRE: - return 2; - case Type.ICE: - return 0.5; - default: - return 1; - } - case Type.DRAGON: - switch (attackType) { - case Type.ICE: - case Type.DRAGON: + switch (attackType) { + case Type.FIGHTING: + case Type.BUG: + case Type.FAIRY: + return 2; + case Type.GHOST: + case Type.DARK: + return 0.5; + case Type.PSYCHIC: + return 0; + default: + return 1; + } case Type.FAIRY: - return 2; - case Type.FIRE: - case Type.WATER: - case Type.GRASS: - case Type.ELECTRIC: - return 0.5; - default: + switch (attackType) { + case Type.POISON: + case Type.STEEL: + return 2; + case Type.FIGHTING: + case Type.BUG: + case Type.DARK: + return 0.5; + case Type.DRAGON: + return 0; + default: + return 1; + } + case Type.STELLAR: return 1; - } - case Type.DARK: - switch (attackType) { - case Type.FIGHTING: - case Type.BUG: - case Type.FAIRY: - return 2; - case Type.GHOST: - case Type.DARK: - return 0.5; - case Type.PSYCHIC: - return 0; - default: - return 1; - } - case Type.FAIRY: - switch (attackType) { - case Type.POISON: - case Type.STEEL: - return 2; - case Type.FIGHTING: - case Type.BUG: - case Type.DARK: - return 0.5; - case Type.DRAGON: - return 0; - default: - return 1; - } - case Type.STELLAR: - return 1; } return 1; @@ -295,86 +295,86 @@ export function getTypeDamageMultiplier(attackType: Type, defType: Type): TypeDa export function getTypeDamageMultiplierColor(multiplier: TypeDamageMultiplier, side: "defense" | "offense"): string | undefined { if (side === "offense") { switch (multiplier) { - case 0: - return "#929292"; - case 0.125: - return "#FF5500"; - case 0.25: - return "#FF7400"; - case 0.5: - return "#FE8E00"; - case 1: - return undefined; - case 2: - return "#4AA500"; - case 4: - return "#4BB400"; - case 8: - return "#52C200"; + case 0: + return "#929292"; + case 0.125: + return "#FF5500"; + case 0.25: + return "#FF7400"; + case 0.5: + return "#FE8E00"; + case 1: + return undefined; + case 2: + return "#4AA500"; + case 4: + return "#4BB400"; + case 8: + return "#52C200"; } } else if (side === "defense") { switch (multiplier) { - case 0: - return "#B1B100"; - case 0.125: - return "#2DB4FF"; - case 0.25: - return "#00A4FF"; - case 0.5: - return "#0093FF"; - case 1: - return undefined; - case 2: - return "#FE8E00"; - case 4: - return "#FF7400"; - case 8: - return "#FF5500"; + case 0: + return "#B1B100"; + case 0.125: + return "#2DB4FF"; + case 0.25: + return "#00A4FF"; + case 0.5: + return "#0093FF"; + case 1: + return undefined; + case 2: + return "#FE8E00"; + case 4: + return "#FF7400"; + case 8: + return "#FF5500"; } } } export function getTypeRgb(type: Type): [ integer, integer, integer ] { switch (type) { - case Type.NORMAL: - return [ 168, 168, 120 ]; - case Type.FIGHTING: - return [ 192, 48, 40 ]; - case Type.FLYING: - return [ 168, 144, 240 ]; - case Type.POISON: - return [ 160, 64, 160 ]; - case Type.GROUND: - return [ 224, 192, 104 ]; - case Type.ROCK: - return [ 184, 160, 56 ]; - case Type.BUG: - return [ 168, 184, 32 ]; - case Type.GHOST: - return [ 112, 88, 152 ]; - case Type.STEEL: - return [ 184, 184, 208 ]; - case Type.FIRE: - return [ 240, 128, 48 ]; - case Type.WATER: - return [ 104, 144, 240 ]; - case Type.GRASS: - return [ 120, 200, 80 ]; - case Type.ELECTRIC: - return [ 248, 208, 48 ]; - case Type.PSYCHIC: - return [ 248, 88, 136 ]; - case Type.ICE: - return [ 152, 216, 216 ]; - case Type.DRAGON: - return [ 112, 56, 248 ]; - case Type.DARK: - return [ 112, 88, 72 ]; - case Type.FAIRY: - return [ 232, 136, 200 ]; - case Type.STELLAR: - return [ 255, 255, 255 ]; - default: - return [ 0, 0, 0 ]; + case Type.NORMAL: + return [ 168, 168, 120 ]; + case Type.FIGHTING: + return [ 192, 48, 40 ]; + case Type.FLYING: + return [ 168, 144, 240 ]; + case Type.POISON: + return [ 160, 64, 160 ]; + case Type.GROUND: + return [ 224, 192, 104 ]; + case Type.ROCK: + return [ 184, 160, 56 ]; + case Type.BUG: + return [ 168, 184, 32 ]; + case Type.GHOST: + return [ 112, 88, 152 ]; + case Type.STEEL: + return [ 184, 184, 208 ]; + case Type.FIRE: + return [ 240, 128, 48 ]; + case Type.WATER: + return [ 104, 144, 240 ]; + case Type.GRASS: + return [ 120, 200, 80 ]; + case Type.ELECTRIC: + return [ 248, 208, 48 ]; + case Type.PSYCHIC: + return [ 248, 88, 136 ]; + case Type.ICE: + return [ 152, 216, 216 ]; + case Type.DRAGON: + return [ 112, 56, 248 ]; + case Type.DARK: + return [ 112, 88, 72 ]; + case Type.FAIRY: + return [ 232, 136, 200 ]; + case Type.STELLAR: + return [ 255, 255, 255 ]; + default: + return [ 0, 0, 0 ]; } } diff --git a/src/data/variant.ts b/src/data/variant.ts index b7a01a4be89..13869635f1e 100644 --- a/src/data/variant.ts +++ b/src/data/variant.ts @@ -10,22 +10,22 @@ export const variantColorCache = {}; export function getVariantTint(variant: Variant): integer { switch (variant) { - case 0: - return 0xf8c020; - case 1: - return 0x20f8f0; - case 2: - return 0xe81048; + case 0: + return 0xf8c020; + case 1: + return 0x20f8f0; + case 2: + return 0xe81048; } } export function getVariantIcon(variant: Variant): integer { switch (variant) { - case 0: - return VariantTier.STANDARD; - case 1: - return VariantTier.RARE; - case 2: - return VariantTier.EPIC; + case 0: + return VariantTier.STANDARD; + case 1: + return VariantTier.RARE; + case 2: + return VariantTier.EPIC; } } diff --git a/src/data/weather.ts b/src/data/weather.ts index 8dfa17c4ef8..20c03af77c8 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -33,10 +33,10 @@ export class Weather { isImmutable(): boolean { switch (this.weatherType) { - case WeatherType.HEAVY_RAIN: - case WeatherType.HARSH_SUN: - case WeatherType.STRONG_WINDS: - return true; + case WeatherType.HEAVY_RAIN: + case WeatherType.HARSH_SUN: + case WeatherType.STRONG_WINDS: + return true; } return false; @@ -44,9 +44,9 @@ export class Weather { isDamaging(): boolean { switch (this.weatherType) { - case WeatherType.SANDSTORM: - case WeatherType.HAIL: - return true; + case WeatherType.SANDSTORM: + case WeatherType.HAIL: + return true; } return false; @@ -54,10 +54,10 @@ export class Weather { isTypeDamageImmune(type: Type): boolean { switch (this.weatherType) { - case WeatherType.SANDSTORM: - return type === Type.GROUND || type === Type.ROCK || type === Type.STEEL; - case WeatherType.HAIL: - return type === Type.ICE; + case WeatherType.SANDSTORM: + return type === Type.GROUND || type === Type.ROCK || type === Type.STEEL; + case WeatherType.HAIL: + return type === Type.ICE; } return false; @@ -65,24 +65,24 @@ export class Weather { getAttackTypeMultiplier(attackType: Type): number { switch (this.weatherType) { - case WeatherType.SUNNY: - case WeatherType.HARSH_SUN: - if (attackType === Type.FIRE) { - return 1.5; - } - if (attackType === Type.WATER) { - return 0.5; - } - break; - case WeatherType.RAIN: - case WeatherType.HEAVY_RAIN: - if (attackType === Type.FIRE) { - return 0.5; - } - if (attackType === Type.WATER) { - return 1.5; - } - break; + case WeatherType.SUNNY: + case WeatherType.HARSH_SUN: + if (attackType === Type.FIRE) { + return 1.5; + } + if (attackType === Type.WATER) { + return 0.5; + } + break; + case WeatherType.RAIN: + case WeatherType.HEAVY_RAIN: + if (attackType === Type.FIRE) { + return 0.5; + } + if (attackType === Type.WATER) { + return 1.5; + } + break; } return 1; @@ -92,10 +92,10 @@ export class Weather { const moveType = user.getMoveType(move); switch (this.weatherType) { - case WeatherType.HARSH_SUN: - return move instanceof AttackMove && moveType === Type.WATER; - case WeatherType.HEAVY_RAIN: - return move instanceof AttackMove && moveType === Type.FIRE; + case WeatherType.HARSH_SUN: + return move instanceof AttackMove && moveType === Type.WATER; + case WeatherType.HEAVY_RAIN: + return move instanceof AttackMove && moveType === Type.FIRE; } return false; @@ -120,24 +120,24 @@ export class Weather { export function getWeatherStartMessage(weatherType: WeatherType): string | null { switch (weatherType) { - case WeatherType.SUNNY: - return i18next.t("weather:sunnyStartMessage"); - case WeatherType.RAIN: - return i18next.t("weather:rainStartMessage"); - case WeatherType.SANDSTORM: - return i18next.t("weather:sandstormStartMessage"); - case WeatherType.HAIL: - return i18next.t("weather:hailStartMessage"); - case WeatherType.SNOW: - return i18next.t("weather:snowStartMessage"); - case WeatherType.FOG: - return i18next.t("weather:fogStartMessage"); - case WeatherType.HEAVY_RAIN: - return i18next.t("weather:heavyRainStartMessage"); - case WeatherType.HARSH_SUN: - return i18next.t("weather:harshSunStartMessage"); - case WeatherType.STRONG_WINDS: - return i18next.t("weather:strongWindsStartMessage"); + case WeatherType.SUNNY: + return i18next.t("weather:sunnyStartMessage"); + case WeatherType.RAIN: + return i18next.t("weather:rainStartMessage"); + case WeatherType.SANDSTORM: + return i18next.t("weather:sandstormStartMessage"); + case WeatherType.HAIL: + return i18next.t("weather:hailStartMessage"); + case WeatherType.SNOW: + return i18next.t("weather:snowStartMessage"); + case WeatherType.FOG: + return i18next.t("weather:fogStartMessage"); + case WeatherType.HEAVY_RAIN: + return i18next.t("weather:heavyRainStartMessage"); + case WeatherType.HARSH_SUN: + return i18next.t("weather:harshSunStartMessage"); + case WeatherType.STRONG_WINDS: + return i18next.t("weather:strongWindsStartMessage"); } return null; @@ -145,24 +145,24 @@ export function getWeatherStartMessage(weatherType: WeatherType): string | null export function getWeatherLapseMessage(weatherType: WeatherType): string | null { switch (weatherType) { - case WeatherType.SUNNY: - return i18next.t("weather:sunnyLapseMessage"); - case WeatherType.RAIN: - return i18next.t("weather:rainLapseMessage"); - case WeatherType.SANDSTORM: - return i18next.t("weather:sandstormLapseMessage"); - case WeatherType.HAIL: - return i18next.t("weather:hailLapseMessage"); - case WeatherType.SNOW: - return i18next.t("weather:snowLapseMessage"); - case WeatherType.FOG: - return i18next.t("weather:fogLapseMessage"); - case WeatherType.HEAVY_RAIN: - return i18next.t("weather:heavyRainLapseMessage"); - case WeatherType.HARSH_SUN: - return i18next.t("weather:harshSunLapseMessage"); - case WeatherType.STRONG_WINDS: - return i18next.t("weather:strongWindsLapseMessage"); + case WeatherType.SUNNY: + return i18next.t("weather:sunnyLapseMessage"); + case WeatherType.RAIN: + return i18next.t("weather:rainLapseMessage"); + case WeatherType.SANDSTORM: + return i18next.t("weather:sandstormLapseMessage"); + case WeatherType.HAIL: + return i18next.t("weather:hailLapseMessage"); + case WeatherType.SNOW: + return i18next.t("weather:snowLapseMessage"); + case WeatherType.FOG: + return i18next.t("weather:fogLapseMessage"); + case WeatherType.HEAVY_RAIN: + return i18next.t("weather:heavyRainLapseMessage"); + case WeatherType.HARSH_SUN: + return i18next.t("weather:harshSunLapseMessage"); + case WeatherType.STRONG_WINDS: + return i18next.t("weather:strongWindsLapseMessage"); } return null; @@ -170,10 +170,10 @@ export function getWeatherLapseMessage(weatherType: WeatherType): string | null export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string | null { switch (weatherType) { - case WeatherType.SANDSTORM: - return i18next.t("weather:sandstormDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); - case WeatherType.HAIL: - return i18next.t("weather:hailDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); + case WeatherType.SANDSTORM: + return i18next.t("weather:sandstormDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); + case WeatherType.HAIL: + return i18next.t("weather:hailDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); } return null; @@ -181,24 +181,24 @@ export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokem export function getWeatherClearMessage(weatherType: WeatherType): string | null { switch (weatherType) { - case WeatherType.SUNNY: - return i18next.t("weather:sunnyClearMessage"); - case WeatherType.RAIN: - return i18next.t("weather:rainClearMessage"); - case WeatherType.SANDSTORM: - return i18next.t("weather:sandstormClearMessage"); - case WeatherType.HAIL: - return i18next.t("weather:hailClearMessage"); - case WeatherType.SNOW: - return i18next.t("weather:snowClearMessage"); - case WeatherType.FOG: - return i18next.t("weather:fogClearMessage"); - case WeatherType.HEAVY_RAIN: - return i18next.t("weather:heavyRainClearMessage"); - case WeatherType.HARSH_SUN: - return i18next.t("weather:harshSunClearMessage"); - case WeatherType.STRONG_WINDS: - return i18next.t("weather:strongWindsClearMessage"); + case WeatherType.SUNNY: + return i18next.t("weather:sunnyClearMessage"); + case WeatherType.RAIN: + return i18next.t("weather:rainClearMessage"); + case WeatherType.SANDSTORM: + return i18next.t("weather:sandstormClearMessage"); + case WeatherType.HAIL: + return i18next.t("weather:hailClearMessage"); + case WeatherType.SNOW: + return i18next.t("weather:snowClearMessage"); + case WeatherType.FOG: + return i18next.t("weather:fogClearMessage"); + case WeatherType.HEAVY_RAIN: + return i18next.t("weather:heavyRainClearMessage"); + case WeatherType.HARSH_SUN: + return i18next.t("weather:harshSunClearMessage"); + case WeatherType.STRONG_WINDS: + return i18next.t("weather:strongWindsClearMessage"); } return null; @@ -206,33 +206,33 @@ export function getWeatherClearMessage(weatherType: WeatherType): string | null export function getTerrainStartMessage(terrainType: TerrainType): string | null { switch (terrainType) { - case TerrainType.MISTY: - return i18next.t("terrain:mistyStartMessage"); - case TerrainType.ELECTRIC: - return i18next.t("terrain:electricStartMessage"); - case TerrainType.GRASSY: - return i18next.t("terrain:grassyStartMessage"); - case TerrainType.PSYCHIC: - return i18next.t("terrain:psychicStartMessage"); - default: - console.warn("getTerrainStartMessage not defined. Using default null"); - return null; + case TerrainType.MISTY: + return i18next.t("terrain:mistyStartMessage"); + case TerrainType.ELECTRIC: + return i18next.t("terrain:electricStartMessage"); + case TerrainType.GRASSY: + return i18next.t("terrain:grassyStartMessage"); + case TerrainType.PSYCHIC: + return i18next.t("terrain:psychicStartMessage"); + default: + console.warn("getTerrainStartMessage not defined. Using default null"); + return null; } } export function getTerrainClearMessage(terrainType: TerrainType): string | null { switch (terrainType) { - case TerrainType.MISTY: - return i18next.t("terrain:mistyClearMessage"); - case TerrainType.ELECTRIC: - return i18next.t("terrain:electricClearMessage"); - case TerrainType.GRASSY: - return i18next.t("terrain:grassyClearMessage"); - case TerrainType.PSYCHIC: - return i18next.t("terrain:psychicClearMessage"); - default: - console.warn("getTerrainClearMessage not defined. Using default null"); - return null; + case TerrainType.MISTY: + return i18next.t("terrain:mistyClearMessage"); + case TerrainType.ELECTRIC: + return i18next.t("terrain:electricClearMessage"); + case TerrainType.GRASSY: + return i18next.t("terrain:grassyClearMessage"); + case TerrainType.PSYCHIC: + return i18next.t("terrain:psychicClearMessage"); + default: + console.warn("getTerrainClearMessage not defined. Using default null"); + return null; } } @@ -252,126 +252,126 @@ export function getRandomWeatherType(arena: any /* Importing from arena causes a let weatherPool: WeatherPoolEntry[] = []; const hasSun = arena.getTimeOfDay() < 2; switch (arena.biomeType) { - case Biome.GRASS: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 7 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 3 }); - } - break; - case Biome.TALL_GRASS: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.RAIN, weight: 5 }, - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 8 }); - } - break; - case Biome.FOREST: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.RAIN, weight: 5 } - ]; - break; - case Biome.SEA: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 3 }, - { weatherType: WeatherType.RAIN, weight: 12 } - ]; - break; - case Biome.SWAMP: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 3 }, - { weatherType: WeatherType.RAIN, weight: 4 }, - { weatherType: WeatherType.FOG, weight: 1 } - ]; - break; - case Biome.BEACH: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.RAIN, weight: 3 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); - } - break; - case Biome.LAKE: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 10 }, - { weatherType: WeatherType.RAIN, weight: 5 }, - { weatherType: WeatherType.FOG, weight: 1 } - ]; - break; - case Biome.SEABED: - weatherPool = [ - { weatherType: WeatherType.RAIN, weight: 1 } - ]; - break; - case Biome.BADLANDS: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.SANDSTORM, weight: 2 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); - } - break; - case Biome.DESERT: - weatherPool = [ - { weatherType: WeatherType.SANDSTORM, weight: 2 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); - } - break; - case Biome.ICE_CAVE: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 3 }, - { weatherType: WeatherType.SNOW, weight: 4 }, - { weatherType: WeatherType.HAIL, weight: 1 } - ]; - break; - case Biome.MEADOW: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 2 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); - } - case Biome.VOLCANO: - weatherPool = [ - { weatherType: hasSun ? WeatherType.SUNNY : WeatherType.NONE, weight: 1 } - ]; - break; - case Biome.GRAVEYARD: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 3 }, - { weatherType: WeatherType.FOG, weight: 1 } - ]; - break; - case Biome.JUNGLE: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.RAIN, weight: 2 } - ]; - break; - case Biome.SNOWY_FOREST: - weatherPool = [ - { weatherType: WeatherType.SNOW, weight: 7 }, - { weatherType: WeatherType.HAIL, weight: 1 } - ]; - break; - case Biome.ISLAND: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 5 }, - { weatherType: WeatherType.RAIN, weight: 1 }, - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); - } - break; + case Biome.GRASS: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 7 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 3 }); + } + break; + case Biome.TALL_GRASS: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.RAIN, weight: 5 }, + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 8 }); + } + break; + case Biome.FOREST: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.RAIN, weight: 5 } + ]; + break; + case Biome.SEA: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 3 }, + { weatherType: WeatherType.RAIN, weight: 12 } + ]; + break; + case Biome.SWAMP: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 3 }, + { weatherType: WeatherType.RAIN, weight: 4 }, + { weatherType: WeatherType.FOG, weight: 1 } + ]; + break; + case Biome.BEACH: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.RAIN, weight: 3 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); + } + break; + case Biome.LAKE: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 10 }, + { weatherType: WeatherType.RAIN, weight: 5 }, + { weatherType: WeatherType.FOG, weight: 1 } + ]; + break; + case Biome.SEABED: + weatherPool = [ + { weatherType: WeatherType.RAIN, weight: 1 } + ]; + break; + case Biome.BADLANDS: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.SANDSTORM, weight: 2 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); + } + break; + case Biome.DESERT: + weatherPool = [ + { weatherType: WeatherType.SANDSTORM, weight: 2 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); + } + break; + case Biome.ICE_CAVE: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 3 }, + { weatherType: WeatherType.SNOW, weight: 4 }, + { weatherType: WeatherType.HAIL, weight: 1 } + ]; + break; + case Biome.MEADOW: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 2 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); + } + case Biome.VOLCANO: + weatherPool = [ + { weatherType: hasSun ? WeatherType.SUNNY : WeatherType.NONE, weight: 1 } + ]; + break; + case Biome.GRAVEYARD: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 3 }, + { weatherType: WeatherType.FOG, weight: 1 } + ]; + break; + case Biome.JUNGLE: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.RAIN, weight: 2 } + ]; + break; + case Biome.SNOWY_FOREST: + weatherPool = [ + { weatherType: WeatherType.SNOW, weight: 7 }, + { weatherType: WeatherType.HAIL, weight: 1 } + ]; + break; + case Biome.ISLAND: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 5 }, + { weatherType: WeatherType.RAIN, weight: 1 }, + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); + } + break; } if (weatherPool.length > 1) { diff --git a/src/field/anims.ts b/src/field/anims.ts index 52a15aa4f20..c73c52027c5 100644 --- a/src/field/anims.ts +++ b/src/field/anims.ts @@ -4,21 +4,21 @@ import * as Utils from "../utils"; export function addPokeballOpenParticles(scene: BattleScene, x: number, y: number, pokeballType: PokeballType): void { switch (pokeballType) { - case PokeballType.POKEBALL: - doDefaultPbOpenParticles(scene, x, y, 48); - break; - case PokeballType.GREAT_BALL: - doDefaultPbOpenParticles(scene, x, y, 96); - break; - case PokeballType.ULTRA_BALL: - doUbOpenParticles(scene, x, y, 8); - break; - case PokeballType.ROGUE_BALL: - doUbOpenParticles(scene, x, y, 10); - break; - case PokeballType.MASTER_BALL: - doMbOpenParticles(scene, x, y); - break; + case PokeballType.POKEBALL: + doDefaultPbOpenParticles(scene, x, y, 48); + break; + case PokeballType.GREAT_BALL: + doDefaultPbOpenParticles(scene, x, y, 96); + break; + case PokeballType.ULTRA_BALL: + doUbOpenParticles(scene, x, y, 8); + break; + case PokeballType.ROGUE_BALL: + doUbOpenParticles(scene, x, y, 10); + break; + case PokeballType.MASTER_BALL: + doMbOpenParticles(scene, x, y); + break; } } diff --git a/src/field/arena.ts b/src/field/arena.ts index ad1846884fc..7bfdf9a0000 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -129,18 +129,18 @@ export class Arena { if (ret.subLegendary || ret.legendary || ret.mythical) { switch (true) { - case (ret.baseTotal >= 720): - regen = level < 90; - break; - case (ret.baseTotal >= 670): - regen = level < 70; - break; - case (ret.baseTotal >= 580): - regen = level < 50; - break; - default: - regen = level < 30; - break; + case (ret.baseTotal >= 720): + regen = level < 90; + break; + case (ret.baseTotal >= 670): + regen = level < 70; + break; + case (ret.baseTotal >= 580): + regen = level < 50; + break; + default: + regen = level < 30; + break; } } } @@ -177,41 +177,41 @@ export class Arena { getSpeciesFormIndex(species: PokemonSpecies): integer { switch (species.speciesId) { - case Species.BURMY: - case Species.WORMADAM: - switch (this.biomeType) { - case Biome.BEACH: - return 1; - case Biome.SLUM: - return 2; - } - break; - case Species.ROTOM: - switch (this.biomeType) { - case Biome.VOLCANO: - return 1; - case Biome.SEA: - return 2; - case Biome.ICE_CAVE: - return 3; - case Biome.MOUNTAIN: - return 4; - case Biome.TALL_GRASS: - return 5; - } - break; - case Species.LYCANROC: - const timeOfDay = this.getTimeOfDay(); - switch (timeOfDay) { - case TimeOfDay.DAY: - case TimeOfDay.DAWN: - return 0; - case TimeOfDay.DUSK: - return 2; - case TimeOfDay.NIGHT: - return 1; - } - break; + case Species.BURMY: + case Species.WORMADAM: + switch (this.biomeType) { + case Biome.BEACH: + return 1; + case Biome.SLUM: + return 2; + } + break; + case Species.ROTOM: + switch (this.biomeType) { + case Biome.VOLCANO: + return 1; + case Biome.SEA: + return 2; + case Biome.ICE_CAVE: + return 3; + case Biome.MOUNTAIN: + return 4; + case Biome.TALL_GRASS: + return 5; + } + break; + case Species.LYCANROC: + const timeOfDay = this.getTimeOfDay(); + switch (timeOfDay) { + case TimeOfDay.DAY: + case TimeOfDay.DAWN: + return 0; + case TimeOfDay.DUSK: + return 2; + case TimeOfDay.NIGHT: + return 1; + } + break; } return 0; @@ -219,70 +219,70 @@ export class Arena { getTypeForBiome() { switch (this.biomeType) { - case Biome.TOWN: - case Biome.PLAINS: - case Biome.METROPOLIS: - return Type.NORMAL; - case Biome.GRASS: - case Biome.TALL_GRASS: - return Type.GRASS; - case Biome.FOREST: - case Biome.JUNGLE: - return Type.BUG; - case Biome.SLUM: - case Biome.SWAMP: - return Type.POISON; - case Biome.SEA: - case Biome.BEACH: - case Biome.LAKE: - case Biome.SEABED: - return Type.WATER; - case Biome.MOUNTAIN: - return Type.FLYING; - case Biome.BADLANDS: - return Type.GROUND; - case Biome.CAVE: - case Biome.DESERT: - return Type.ROCK; - case Biome.ICE_CAVE: - case Biome.SNOWY_FOREST: - return Type.ICE; - case Biome.MEADOW: - case Biome.FAIRY_CAVE: - case Biome.ISLAND: - return Type.FAIRY; - case Biome.POWER_PLANT: - return Type.ELECTRIC; - case Biome.VOLCANO: - return Type.FIRE; - case Biome.GRAVEYARD: - case Biome.TEMPLE: - return Type.GHOST; - case Biome.DOJO: - case Biome.CONSTRUCTION_SITE: - return Type.FIGHTING; - case Biome.FACTORY: - case Biome.LABORATORY: - return Type.STEEL; - case Biome.RUINS: - case Biome.SPACE: - return Type.PSYCHIC; - case Biome.WASTELAND: - case Biome.END: - return Type.DRAGON; - case Biome.ABYSS: - return Type.DARK; - default: - return Type.UNKNOWN; + case Biome.TOWN: + case Biome.PLAINS: + case Biome.METROPOLIS: + return Type.NORMAL; + case Biome.GRASS: + case Biome.TALL_GRASS: + return Type.GRASS; + case Biome.FOREST: + case Biome.JUNGLE: + return Type.BUG; + case Biome.SLUM: + case Biome.SWAMP: + return Type.POISON; + case Biome.SEA: + case Biome.BEACH: + case Biome.LAKE: + case Biome.SEABED: + return Type.WATER; + case Biome.MOUNTAIN: + return Type.FLYING; + case Biome.BADLANDS: + return Type.GROUND; + case Biome.CAVE: + case Biome.DESERT: + return Type.ROCK; + case Biome.ICE_CAVE: + case Biome.SNOWY_FOREST: + return Type.ICE; + case Biome.MEADOW: + case Biome.FAIRY_CAVE: + case Biome.ISLAND: + return Type.FAIRY; + case Biome.POWER_PLANT: + return Type.ELECTRIC; + case Biome.VOLCANO: + return Type.FIRE; + case Biome.GRAVEYARD: + case Biome.TEMPLE: + return Type.GHOST; + case Biome.DOJO: + case Biome.CONSTRUCTION_SITE: + return Type.FIGHTING; + case Biome.FACTORY: + case Biome.LABORATORY: + return Type.STEEL; + case Biome.RUINS: + case Biome.SPACE: + return Type.PSYCHIC; + case Biome.WASTELAND: + case Biome.END: + return Type.DRAGON; + case Biome.ABYSS: + return Type.DARK; + default: + return Type.UNKNOWN; } } getBgTerrainColorRatioForBiome(): number { switch (this.biomeType) { - case Biome.SPACE: - return 1; - case Biome.END: - return 0; + case Biome.SPACE: + return 1; + case Biome.END: + return 0; } return 131 / 180; @@ -424,52 +424,52 @@ export class Arena { */ getTrainerChance(): integer { switch (this.biomeType) { - case Biome.METROPOLIS: - return 2; - case Biome.SLUM: - case Biome.BEACH: - case Biome.DOJO: - case Biome.CONSTRUCTION_SITE: - return 4; - case Biome.PLAINS: - case Biome.GRASS: - case Biome.LAKE: - case Biome.CAVE: - return 6; - case Biome.TALL_GRASS: - case Biome.FOREST: - case Biome.SEA: - case Biome.SWAMP: - case Biome.MOUNTAIN: - case Biome.BADLANDS: - case Biome.DESERT: - case Biome.MEADOW: - case Biome.POWER_PLANT: - case Biome.GRAVEYARD: - case Biome.FACTORY: - case Biome.SNOWY_FOREST: - return 8; - case Biome.ICE_CAVE: - case Biome.VOLCANO: - case Biome.RUINS: - case Biome.WASTELAND: - case Biome.JUNGLE: - case Biome.FAIRY_CAVE: - return 12; - case Biome.SEABED: - case Biome.ABYSS: - case Biome.SPACE: - case Biome.TEMPLE: - return 16; - default: - return 0; + case Biome.METROPOLIS: + return 2; + case Biome.SLUM: + case Biome.BEACH: + case Biome.DOJO: + case Biome.CONSTRUCTION_SITE: + return 4; + case Biome.PLAINS: + case Biome.GRASS: + case Biome.LAKE: + case Biome.CAVE: + return 6; + case Biome.TALL_GRASS: + case Biome.FOREST: + case Biome.SEA: + case Biome.SWAMP: + case Biome.MOUNTAIN: + case Biome.BADLANDS: + case Biome.DESERT: + case Biome.MEADOW: + case Biome.POWER_PLANT: + case Biome.GRAVEYARD: + case Biome.FACTORY: + case Biome.SNOWY_FOREST: + return 8; + case Biome.ICE_CAVE: + case Biome.VOLCANO: + case Biome.RUINS: + case Biome.WASTELAND: + case Biome.JUNGLE: + case Biome.FAIRY_CAVE: + return 12; + case Biome.SEABED: + case Biome.ABYSS: + case Biome.SPACE: + case Biome.TEMPLE: + return 16; + default: + return 0; } } getTimeOfDay(): TimeOfDay { switch (this.biomeType) { - case Biome.ABYSS: - return TimeOfDay.NIGHT; + case Biome.ABYSS: + return TimeOfDay.NIGHT; } const waveCycle = ((this.scene.currentBattle?.waveIndex || 0) + this.scene.waveCycleOffset) % 40; @@ -491,35 +491,35 @@ export class Arena { isOutside(): boolean { switch (this.biomeType) { - case Biome.SEABED: - case Biome.CAVE: - case Biome.ICE_CAVE: - case Biome.POWER_PLANT: - case Biome.DOJO: - case Biome.FACTORY: - case Biome.ABYSS: - case Biome.FAIRY_CAVE: - case Biome.TEMPLE: - case Biome.LABORATORY: - return false; - default: - return true; + case Biome.SEABED: + case Biome.CAVE: + case Biome.ICE_CAVE: + case Biome.POWER_PLANT: + case Biome.DOJO: + case Biome.FACTORY: + case Biome.ABYSS: + case Biome.FAIRY_CAVE: + case Biome.TEMPLE: + case Biome.LABORATORY: + return false; + default: + return true; } } overrideTint(): [integer, integer, integer] { switch (Overrides.ARENA_TINT_OVERRIDE) { - case TimeOfDay.DUSK: - return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; - break; - case (TimeOfDay.NIGHT): - return [ 64, 64, 64 ]; - break; - case TimeOfDay.DAWN: - case TimeOfDay.DAY: - default: - return [ 128, 128, 128 ]; - break; + case TimeOfDay.DUSK: + return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; + break; + case (TimeOfDay.NIGHT): + return [ 64, 64, 64 ]; + break; + case TimeOfDay.DAWN: + case TimeOfDay.DAY: + default: + return [ 128, 128, 128 ]; + break; } } @@ -528,10 +528,10 @@ export class Arena { return this.overrideTint(); } switch (this.biomeType) { - case Biome.ABYSS: - return [ 64, 64, 64 ]; - default: - return [ 128, 128, 128 ]; + case Biome.ABYSS: + return [ 64, 64, 64 ]; + default: + return [ 128, 128, 128 ]; } } @@ -544,8 +544,8 @@ export class Arena { } switch (this.biomeType) { - default: - return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; + default: + return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; } } @@ -554,10 +554,10 @@ export class Arena { return this.overrideTint(); } switch (this.biomeType) { - case Biome.ABYSS: - case Biome.SPACE: - case Biome.END: - return this.getDayTint(); + case Biome.ABYSS: + case Biome.SPACE: + case Biome.END: + return this.getDayTint(); } if (!this.isOutside()) { @@ -565,8 +565,8 @@ export class Arena { } switch (this.biomeType) { - default: - return [ 48, 48, 98 ]; + default: + return [ 48, 48, 98 ]; } } @@ -747,77 +747,77 @@ export class Arena { getBgmLoopPoint(): number { switch (this.biomeType) { - case Biome.TOWN: - return 7.288; - case Biome.PLAINS: - return 17.485; - case Biome.GRASS: - return 1.995; - case Biome.TALL_GRASS: - return 9.608; - case Biome.METROPOLIS: - return 141.470; - case Biome.FOREST: - return 4.294; - case Biome.SEA: - return 0.024; - case Biome.SWAMP: - return 4.461; - case Biome.BEACH: - return 3.462; - case Biome.LAKE: - return 7.215; - case Biome.SEABED: - return 2.600; - case Biome.MOUNTAIN: - return 4.018; - case Biome.BADLANDS: - return 17.790; - case Biome.CAVE: - return 14.240; - case Biome.DESERT: - return 1.143; - case Biome.ICE_CAVE: - return 0.000; - case Biome.MEADOW: - return 3.891; - case Biome.POWER_PLANT: - return 9.447; - case Biome.VOLCANO: - return 17.637; - case Biome.GRAVEYARD: - return 3.232; - case Biome.DOJO: - return 6.205; - case Biome.FACTORY: - return 4.985; - case Biome.RUINS: - return 0.000; - case Biome.WASTELAND: - return 6.336; - case Biome.ABYSS: - return 5.130; - case Biome.SPACE: - return 20.036; - case Biome.CONSTRUCTION_SITE: - return 1.222; - case Biome.JUNGLE: - return 0.000; - case Biome.FAIRY_CAVE: - return 4.542; - case Biome.TEMPLE: - return 2.547; - case Biome.ISLAND: - return 2.751; - case Biome.LABORATORY: - return 114.862; - case Biome.SLUM: - return 0.000; - case Biome.SNOWY_FOREST: - return 3.047; - default: - console.warn(`missing bgm loop-point for biome "${Biome[this.biomeType]}" (=${this.biomeType})`); - return 0; + case Biome.TOWN: + return 7.288; + case Biome.PLAINS: + return 17.485; + case Biome.GRASS: + return 1.995; + case Biome.TALL_GRASS: + return 9.608; + case Biome.METROPOLIS: + return 141.470; + case Biome.FOREST: + return 4.294; + case Biome.SEA: + return 0.024; + case Biome.SWAMP: + return 4.461; + case Biome.BEACH: + return 3.462; + case Biome.LAKE: + return 7.215; + case Biome.SEABED: + return 2.600; + case Biome.MOUNTAIN: + return 4.018; + case Biome.BADLANDS: + return 17.790; + case Biome.CAVE: + return 14.240; + case Biome.DESERT: + return 1.143; + case Biome.ICE_CAVE: + return 0.000; + case Biome.MEADOW: + return 3.891; + case Biome.POWER_PLANT: + return 9.447; + case Biome.VOLCANO: + return 17.637; + case Biome.GRAVEYARD: + return 3.232; + case Biome.DOJO: + return 6.205; + case Biome.FACTORY: + return 4.985; + case Biome.RUINS: + return 0.000; + case Biome.WASTELAND: + return 6.336; + case Biome.ABYSS: + return 5.130; + case Biome.SPACE: + return 20.036; + case Biome.CONSTRUCTION_SITE: + return 1.222; + case Biome.JUNGLE: + return 0.000; + case Biome.FAIRY_CAVE: + return 4.542; + case Biome.TEMPLE: + return 2.547; + case Biome.ISLAND: + return 2.751; + case Biome.LABORATORY: + return 114.862; + case Biome.SLUM: + return 0.000; + case Biome.SNOWY_FOREST: + return 3.047; + default: + console.warn(`missing bgm loop-point for biome "${Biome[this.biomeType]}" (=${this.biomeType})`); + return 0; } } } @@ -828,32 +828,32 @@ export function getBiomeKey(biome: Biome): string { export function getBiomeHasProps(biomeType: Biome): boolean { switch (biomeType) { - case Biome.METROPOLIS: - case Biome.BEACH: - case Biome.LAKE: - case Biome.SEABED: - case Biome.MOUNTAIN: - case Biome.BADLANDS: - case Biome.CAVE: - case Biome.DESERT: - case Biome.ICE_CAVE: - case Biome.MEADOW: - case Biome.POWER_PLANT: - case Biome.VOLCANO: - case Biome.GRAVEYARD: - case Biome.FACTORY: - case Biome.RUINS: - case Biome.WASTELAND: - case Biome.ABYSS: - case Biome.CONSTRUCTION_SITE: - case Biome.JUNGLE: - case Biome.FAIRY_CAVE: - case Biome.TEMPLE: - case Biome.SNOWY_FOREST: - case Biome.ISLAND: - case Biome.LABORATORY: - case Biome.END: - return true; + case Biome.METROPOLIS: + case Biome.BEACH: + case Biome.LAKE: + case Biome.SEABED: + case Biome.MOUNTAIN: + case Biome.BADLANDS: + case Biome.CAVE: + case Biome.DESERT: + case Biome.ICE_CAVE: + case Biome.MEADOW: + case Biome.POWER_PLANT: + case Biome.VOLCANO: + case Biome.GRAVEYARD: + case Biome.FACTORY: + case Biome.RUINS: + case Biome.WASTELAND: + case Biome.ABYSS: + case Biome.CONSTRUCTION_SITE: + case Biome.JUNGLE: + case Biome.FAIRY_CAVE: + case Biome.TEMPLE: + case Biome.SNOWY_FOREST: + case Biome.ISLAND: + case Biome.LABORATORY: + case Biome.END: + return true; } return false; diff --git a/src/field/damage-number-handler.ts b/src/field/damage-number-handler.ts index ae0692da342..4ddcd2d3ee7 100644 --- a/src/field/damage-number-handler.ts +++ b/src/field/damage-number-handler.ts @@ -29,21 +29,21 @@ export default class DamageNumberHandler { let [ textColor, shadowColor ] : TextAndShadowArr = [ null, null ]; switch (result) { - case HitResult.SUPER_EFFECTIVE: - [ textColor, shadowColor ] = [ "#f8d030", "#b8a038" ]; - break; - case HitResult.NOT_VERY_EFFECTIVE: - [ textColor, shadowColor ] = [ "#f08030", "#c03028" ]; - break; - case HitResult.ONE_HIT_KO: - [ textColor, shadowColor ] = [ "#a040a0", "#483850" ]; - break; - case HitResult.HEAL: - [ textColor, shadowColor ] = [ "#78c850", "#588040" ]; - break; - default: - [ textColor, shadowColor ] = [ "#ffffff", "#636363" ]; - break; + case HitResult.SUPER_EFFECTIVE: + [ textColor, shadowColor ] = [ "#f8d030", "#b8a038" ]; + break; + case HitResult.NOT_VERY_EFFECTIVE: + [ textColor, shadowColor ] = [ "#f08030", "#c03028" ]; + break; + case HitResult.ONE_HIT_KO: + [ textColor, shadowColor ] = [ "#a040a0", "#483850" ]; + break; + case HitResult.HEAL: + [ textColor, shadowColor ] = [ "#78c850", "#588040" ]; + break; + default: + [ textColor, shadowColor ] = [ "#ffffff", "#636363" ]; + break; } if (textColor) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 9aaadf29657..0afbbf105e2 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -680,12 +680,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getFieldPositionOffset(): [ number, number ] { switch (this.fieldPosition) { - case FieldPosition.CENTER: - return [ 0, 0 ]; - case FieldPosition.LEFT: - return [ -32, -8 ]; - case FieldPosition.RIGHT: - return [ 32, 0 ]; + case FieldPosition.CENTER: + return [ 0, 0 ]; + case FieldPosition.LEFT: + return [ -32, -8 ]; + case FieldPosition.RIGHT: + return [ 32, 0 ]; } } @@ -917,39 +917,39 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let ret = statValue.value * this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated); switch (stat) { - case Stat.ATK: - if (this.getTag(BattlerTagType.SLOW_START)) { - ret >>= 1; - } - break; - case Stat.DEF: - if (this.isOfType(Type.ICE) && this.scene.arena.weather?.weatherType === WeatherType.SNOW) { - ret *= 1.5; - } - break; - case Stat.SPATK: - break; - case Stat.SPDEF: - if (this.isOfType(Type.ROCK) && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) { - ret *= 1.5; - } - break; - case Stat.SPD: - const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (this.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) { - ret *= 2; - } - if (this.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) { - ret >>= 2; - } + case Stat.ATK: + if (this.getTag(BattlerTagType.SLOW_START)) { + ret >>= 1; + } + break; + case Stat.DEF: + if (this.isOfType(Type.ICE) && this.scene.arena.weather?.weatherType === WeatherType.SNOW) { + ret *= 1.5; + } + break; + case Stat.SPATK: + break; + case Stat.SPDEF: + if (this.isOfType(Type.ROCK) && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) { + ret *= 1.5; + } + break; + case Stat.SPD: + const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + if (this.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) { + ret *= 2; + } + if (this.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) { + ret >>= 2; + } - if (this.getTag(BattlerTagType.SLOW_START)) { - ret >>= 1; - } - if (this.status && this.status.effect === StatusEffect.PARALYSIS) { - ret >>= 1; - } - break; + if (this.getTag(BattlerTagType.SLOW_START)) { + ret >>= 1; + } + if (this.status && this.status.effect === StatusEffect.PARALYSIS) { + ret >>= 1; + } + break; } const highestStatBoost = this.findTag(t => t instanceof HighestStatBoostTag && (t as HighestStatBoostTag).stat === stat) as HighestStatBoostTag; @@ -2338,14 +2338,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (opponent) { if (isCritical) { switch (stat) { - case Stat.ATK: - case Stat.SPATK: - statStage.value = Math.max(statStage.value, 0); - break; - case Stat.DEF: - case Stat.SPDEF: - statStage.value = Math.min(statStage.value, 0); - break; + case Stat.ATK: + case Stat.SPATK: + statStage.value = Math.max(statStage.value, 0); + break; + case Stat.DEF: + case Stat.SPDEF: + statStage.value = Math.min(statStage.value, 0); + break; } } if (!ignoreOppAbility) { @@ -2795,15 +2795,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // want to include is.Fainted() in case multi hit move ends early, still want to render message if (source.turnData.hitsLeft === 1 || this.isFainted()) { switch (result) { - case HitResult.SUPER_EFFECTIVE: - this.scene.queueMessage(i18next.t("battle:hitResultSuperEffective")); - break; - case HitResult.NOT_VERY_EFFECTIVE: - this.scene.queueMessage(i18next.t("battle:hitResultNotVeryEffective")); - break; - case HitResult.ONE_HIT_KO: - this.scene.queueMessage(i18next.t("battle:hitResultOneHitKO")); - break; + case HitResult.SUPER_EFFECTIVE: + this.scene.queueMessage(i18next.t("battle:hitResultSuperEffective")); + break; + case HitResult.NOT_VERY_EFFECTIVE: + this.scene.queueMessage(i18next.t("battle:hitResultNotVeryEffective")); + break; + case HitResult.ONE_HIT_KO: + this.scene.queueMessage(i18next.t("battle:hitResultOneHitKO")); + break; } } @@ -3355,53 +3355,53 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } switch (effect) { - case StatusEffect.POISON: - case StatusEffect.TOXIC: + case StatusEffect.POISON: + case StatusEffect.TOXIC: // Check if the Pokemon is immune to Poison/Toxic or if the source pokemon is canceling the immunity - const poisonImmunity = types.map(defType => { + const poisonImmunity = types.map(defType => { // Check if the Pokemon is not immune to Poison/Toxic - if (defType !== Type.POISON && defType !== Type.STEEL) { - return false; - } + if (defType !== Type.POISON && defType !== Type.STEEL) { + return false; + } - // Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity - const cancelImmunity = new Utils.BooleanHolder(false); - if (sourcePokemon) { - applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType); - if (cancelImmunity.value) { + // Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity + const cancelImmunity = new Utils.BooleanHolder(false); + if (sourcePokemon) { + applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType); + if (cancelImmunity.value) { + return false; + } + } + + return true; + }); + + if (this.isOfType(Type.POISON) || this.isOfType(Type.STEEL)) { + if (poisonImmunity.includes(true)) { return false; } } - - return true; - }); - - if (this.isOfType(Type.POISON) || this.isOfType(Type.STEEL)) { - if (poisonImmunity.includes(true)) { + break; + case StatusEffect.PARALYSIS: + if (this.isOfType(Type.ELECTRIC)) { return false; } - } - break; - case StatusEffect.PARALYSIS: - if (this.isOfType(Type.ELECTRIC)) { - return false; - } - break; - case StatusEffect.SLEEP: - if (this.isGrounded() && this.scene.arena.terrain?.terrainType === TerrainType.ELECTRIC) { - return false; - } - break; - case StatusEffect.FREEZE: - if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType))) { - return false; - } - break; - case StatusEffect.BURN: - if (this.isOfType(Type.FIRE)) { - return false; - } - break; + break; + case StatusEffect.SLEEP: + if (this.isGrounded() && this.scene.arena.terrain?.terrainType === TerrainType.ELECTRIC) { + return false; + } + break; + case StatusEffect.FREEZE: + if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType))) { + return false; + } + break; + case StatusEffect.BURN: + if (this.isOfType(Type.FIRE)) { + return false; + } + break; } const cancelled = new Utils.BooleanHolder(false); @@ -4501,35 +4501,35 @@ export class EnemyPokemon extends Pokemon { generateAndPopulateMoveset(formIndex?: integer): void { switch (true) { - case (this.species.speciesId === Species.SMEARGLE): - this.moveset = [ - new PokemonMove(Moves.SKETCH), - new PokemonMove(Moves.SKETCH), - new PokemonMove(Moves.SKETCH), - new PokemonMove(Moves.SKETCH) - ]; - break; - case (this.species.speciesId === Species.ETERNATUS): - this.moveset = (formIndex !== undefined ? formIndex : this.formIndex) - ? [ - new PokemonMove(Moves.DYNAMAX_CANNON), - new PokemonMove(Moves.CROSS_POISON), - new PokemonMove(Moves.FLAMETHROWER), - new PokemonMove(Moves.RECOVER, 0, -4) - ] - : [ - new PokemonMove(Moves.ETERNABEAM), - new PokemonMove(Moves.SLUDGE_BOMB), - new PokemonMove(Moves.FLAMETHROWER), - new PokemonMove(Moves.COSMIC_POWER) + case (this.species.speciesId === Species.SMEARGLE): + this.moveset = [ + new PokemonMove(Moves.SKETCH), + new PokemonMove(Moves.SKETCH), + new PokemonMove(Moves.SKETCH), + new PokemonMove(Moves.SKETCH) ]; - if (this.scene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) { - this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT); - } - break; - default: - super.generateAndPopulateMoveset(); - break; + break; + case (this.species.speciesId === Species.ETERNATUS): + this.moveset = (formIndex !== undefined ? formIndex : this.formIndex) + ? [ + new PokemonMove(Moves.DYNAMAX_CANNON), + new PokemonMove(Moves.CROSS_POISON), + new PokemonMove(Moves.FLAMETHROWER), + new PokemonMove(Moves.RECOVER, 0, -4) + ] + : [ + new PokemonMove(Moves.ETERNABEAM), + new PokemonMove(Moves.SLUDGE_BOMB), + new PokemonMove(Moves.FLAMETHROWER), + new PokemonMove(Moves.COSMIC_POWER) + ]; + if (this.scene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) { + this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT); + } + break; + default: + super.generateAndPopulateMoveset(); + break; } } @@ -4569,135 +4569,135 @@ export class EnemyPokemon extends Pokemon { } } switch (this.aiType) { - case AiType.RANDOM: // No enemy should spawn with this AI type in-game - const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)]!.moveId; // TODO: is the bang correct? - return { move: moveId, targets: this.getNextTargets(moveId) }; - case AiType.SMART_RANDOM: - case AiType.SMART: + case AiType.RANDOM: // No enemy should spawn with this AI type in-game + const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)]!.moveId; // TODO: is the bang correct? + return { move: moveId, targets: this.getNextTargets(moveId) }; + case AiType.SMART_RANDOM: + case AiType.SMART: /** * Search this Pokemon's move pool for moves that will KO an opposing target. * If there are any moves that can KO an opponent (i.e. a player Pokemon), * those moves are the only ones considered for selection on this turn. */ - const koMoves = movePool.filter(pkmnMove => { - if (!pkmnMove) { - return false; - } + const koMoves = movePool.filter(pkmnMove => { + if (!pkmnMove) { + return false; + } - const move = pkmnMove.getMove()!; - if (move.moveTarget === MoveTarget.ATTACKER) { - return false; - } + const move = pkmnMove.getMove()!; + if (move.moveTarget === MoveTarget.ATTACKER) { + return false; + } - const fieldPokemon = this.scene.getField(); - const moveTargets = getMoveTargets(this, move.id).targets - .map(ind => fieldPokemon[ind]) - .filter(p => this.isPlayer() !== p.isPlayer()); - // Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus - const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT); + const fieldPokemon = this.scene.getField(); + const moveTargets = getMoveTargets(this, move.id).targets + .map(ind => fieldPokemon[ind]) + .filter(p => this.isPlayer() !== p.isPlayer()); + // Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus + const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT); - return move.category !== MoveCategory.STATUS + return move.category !== MoveCategory.STATUS && moveTargets.some(p => { const doesNotFail = move.applyConditions(this, p, move) || [ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id); return doesNotFail && p.getAttackDamage(this, move, !p.battleData.abilityRevealed, false, isCritical).damage >= p.hp; }); - }, this); + }, this); - if (koMoves.length > 0) { - movePool = koMoves; - } + if (koMoves.length > 0) { + movePool = koMoves; + } - /** + /** * Move selection is based on the move's calculated "benefit score" against the * best possible target(s) (as determined by {@linkcode getNextTargets}). * For more information on how benefit scores are calculated, see `docs/enemy-ai.md`. */ - const moveScores = movePool.map(() => 0); - const moveTargets = Object.fromEntries(movePool.map(m => [ m!.moveId, this.getNextTargets(m!.moveId) ])); // TODO: are those bangs correct? - for (const m in movePool) { - const pokemonMove = movePool[m]!; // TODO: is the bang correct? - const move = pokemonMove.getMove(); + const moveScores = movePool.map(() => 0); + const moveTargets = Object.fromEntries(movePool.map(m => [ m!.moveId, this.getNextTargets(m!.moveId) ])); // TODO: are those bangs correct? + for (const m in movePool) { + const pokemonMove = movePool[m]!; // TODO: is the bang correct? + const move = pokemonMove.getMove(); - let moveScore = moveScores[m]; - const targetScores: integer[] = []; + let moveScore = moveScores[m]; + const targetScores: integer[] = []; - for (const mt of moveTargets[move.id]) { + for (const mt of moveTargets[move.id]) { // Prevent a target score from being calculated when the target is whoever attacks the user - if (mt === BattlerIndex.ATTACKER) { - break; - } + if (mt === BattlerIndex.ATTACKER) { + break; + } - const target = this.scene.getField()[mt]; - /** + const target = this.scene.getField()[mt]; + /** * The "target score" of a move is given by the move's user benefit score + the move's target benefit score. * If the target is an ally, the target benefit score is multiplied by -1. */ - let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); - if (Number.isNaN(targetScore)) { - console.error(`Move ${move.name} returned score of NaN`); - targetScore = 0; - } - /** + let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); + if (Number.isNaN(targetScore)) { + console.error(`Move ${move.name} returned score of NaN`); + targetScore = 0; + } + /** * If this move is unimplemented, or the move is known to fail when used, set its * target score to -20 */ - if ((move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && ![ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id)) { - targetScore = -20; - } else if (move instanceof AttackMove) { + if ((move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && ![ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id)) { + targetScore = -20; + } else if (move instanceof AttackMove) { /** * Attack moves are given extra multipliers to their base benefit score based on * the move's type effectiveness against the target and whether the move is a STAB move. */ - const effectiveness = target.getMoveEffectiveness(this, move, !target.battleData?.abilityRevealed); - if (target.isPlayer() !== this.isPlayer()) { - targetScore *= effectiveness; - if (this.isOfType(move.type)) { - targetScore *= 1.5; + const effectiveness = target.getMoveEffectiveness(this, move, !target.battleData?.abilityRevealed); + if (target.isPlayer() !== this.isPlayer()) { + targetScore *= effectiveness; + if (this.isOfType(move.type)) { + targetScore *= 1.5; + } + } else if (effectiveness) { + targetScore /= effectiveness; + if (this.isOfType(move.type)) { + targetScore /= 1.5; + } } - } else if (effectiveness) { - targetScore /= effectiveness; - if (this.isOfType(move.type)) { - targetScore /= 1.5; + /** If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point */ + if (!targetScore) { + targetScore = -20; } } - /** If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point */ - if (!targetScore) { - targetScore = -20; - } + targetScores.push(targetScore); } - targetScores.push(targetScore); + // When a move has multiple targets, its score is equal to the maximum target score across all targets + moveScore += Math.max(...targetScores); + + // could make smarter by checking opponent def/spdef + moveScores[m] = moveScore; } - // When a move has multiple targets, its score is equal to the maximum target score across all targets - moveScore += Math.max(...targetScores); - // could make smarter by checking opponent def/spdef - moveScores[m] = moveScore; - } + console.log(moveScores); - console.log(moveScores); - - // Sort the move pool in decreasing order of move score - const sortedMovePool = movePool.slice(0); - sortedMovePool.sort((a, b) => { - const scoreA = moveScores[movePool.indexOf(a)]; - const scoreB = moveScores[movePool.indexOf(b)]; - return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0; - }); - let r = 0; - if (this.aiType === AiType.SMART_RANDOM) { + // Sort the move pool in decreasing order of move score + const sortedMovePool = movePool.slice(0); + sortedMovePool.sort((a, b) => { + const scoreA = moveScores[movePool.indexOf(a)]; + const scoreB = moveScores[movePool.indexOf(b)]; + return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0; + }); + let r = 0; + if (this.aiType === AiType.SMART_RANDOM) { // Has a 5/8 chance to select the best move, and a 3/8 chance to advance to the next best move (and repeat this roll) - while (r < sortedMovePool.length - 1 && this.scene.randBattleSeedInt(8) >= 5) { - r++; - } - } else if (this.aiType === AiType.SMART) { + while (r < sortedMovePool.length - 1 && this.scene.randBattleSeedInt(8) >= 5) { + r++; + } + } else if (this.aiType === AiType.SMART) { // The chance to advance to the next best move increases when the compared moves' scores are closer to each other. - while (r < sortedMovePool.length - 1 && (moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) >= 0 + while (r < sortedMovePool.length - 1 && (moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) >= 0 && this.scene.randBattleSeedInt(100) < Math.round((moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) * 50)) { - r++; + r++; + } } - } - console.log(movePool.map(m => m!.getName()), moveScores, r, sortedMovePool.map(m => m!.getName())); // TODO: are those bangs correct? - return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] }; + console.log(movePool.map(m => m!.getName()), moveScores, r, sortedMovePool.map(m => m!.getName())); // TODO: are those bangs correct? + return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] }; } } @@ -4845,10 +4845,10 @@ export class EnemyPokemon extends Pokemon { } switch (this.scene.currentBattle.battleSpec) { - case BattleSpec.FINAL_BOSS: - if (!this.formIndex && this.bossSegmentIndex < 1) { - damage = Math.min(damage, this.hp - 1); - } + case BattleSpec.FINAL_BOSS: + if (!this.formIndex && this.bossSegmentIndex < 1) { + damage = Math.min(damage, this.hp - 1); + } } const ret = super.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase); diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 5110787452f..b77a156f401 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -65,16 +65,16 @@ export default class Trainer extends Phaser.GameObjects.Container { } switch (this.variant) { - case TrainerVariant.FEMALE: - if (!this.config.hasGenders) { - variant = TrainerVariant.DEFAULT; - } - break; - case TrainerVariant.DOUBLE: - if (!this.config.hasDouble) { - variant = TrainerVariant.DEFAULT; - } - break; + case TrainerVariant.FEMALE: + if (!this.config.hasGenders) { + variant = TrainerVariant.DEFAULT; + } + break; + case TrainerVariant.DOUBLE: + if (!this.config.hasDouble) { + variant = TrainerVariant.DEFAULT; + } + break; } console.log(Object.keys(trainerPartyTemplates)[Object.values(trainerPartyTemplates).indexOf(this.getPartyTemplate())]); @@ -229,21 +229,21 @@ export default class Trainer extends Phaser.GameObjects.Container { const strength = partyTemplate.getStrength(i); switch (strength) { - case PartyMemberStrength.WEAKER: - multiplier = 0.95; - break; - case PartyMemberStrength.WEAK: - multiplier = 1.0; - break; - case PartyMemberStrength.AVERAGE: - multiplier = 1.1; - break; - case PartyMemberStrength.STRONG: - multiplier = 1.2; - break; - case PartyMemberStrength.STRONGER: - multiplier = 1.25; - break; + case PartyMemberStrength.WEAKER: + multiplier = 0.95; + break; + case PartyMemberStrength.WEAK: + multiplier = 1.0; + break; + case PartyMemberStrength.AVERAGE: + multiplier = 1.1; + break; + case PartyMemberStrength.STRONG: + multiplier = 1.2; + break; + case PartyMemberStrength.STRONGER: + multiplier = 1.25; + break; } let levelOffset = 0; @@ -515,19 +515,19 @@ export default class Trainer extends Phaser.GameObjects.Container { getPartyMemberModifierChanceMultiplier(index: integer): number { switch (this.getPartyTemplate().getStrength(index)) { - case PartyMemberStrength.WEAKER: - return 0.75; - case PartyMemberStrength.WEAK: - return 0.675; - case PartyMemberStrength.AVERAGE: - return 0.5625; - case PartyMemberStrength.STRONG: - return 0.45; - case PartyMemberStrength.STRONGER: - return 0.375; - default: - console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0"); - return 0; + case PartyMemberStrength.WEAKER: + return 0.75; + case PartyMemberStrength.WEAK: + return 0.675; + case PartyMemberStrength.AVERAGE: + return 0.5625; + case PartyMemberStrength.STRONG: + return 0.45; + case PartyMemberStrength.STRONGER: + return 0.375; + default: + console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0"); + return 0; } } diff --git a/src/game-mode.ts b/src/game-mode.ts index 91e8933ea6e..8f1bb9356e6 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -92,10 +92,10 @@ export class GameMode implements GameModeConfig { return Overrides.STARTING_LEVEL_OVERRIDE; } switch (this.modeId) { - case GameModes.DAILY: - return 20; - default: - return 5; + case GameModes.DAILY: + return 20; + default: + return 5; } } @@ -117,19 +117,19 @@ export class GameMode implements GameModeConfig { */ getStartingBiome(scene: BattleScene): Biome { switch (this.modeId) { - case GameModes.DAILY: - return scene.generateRandomBiome(this.getWaveForDifficulty(1)); - default: - return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN; + case GameModes.DAILY: + return scene.generateRandomBiome(this.getWaveForDifficulty(1)); + default: + return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN; } } getWaveForDifficulty(waveIndex: integer, ignoreCurveChanges: boolean = false): integer { switch (this.modeId) { - case GameModes.DAILY: - return waveIndex + 30 + (!ignoreCurveChanges ? Math.floor(waveIndex / 5) : 0); - default: - return waveIndex; + case GameModes.DAILY: + return waveIndex + 30 + (!ignoreCurveChanges ? Math.floor(waveIndex / 5) : 0); + default: + return waveIndex; } } @@ -186,10 +186,10 @@ export class GameMode implements GameModeConfig { isTrainerBoss(waveIndex: integer, biomeType: Biome, offsetGym: boolean): boolean { switch (this.modeId) { - case GameModes.DAILY: - return waveIndex > 10 && waveIndex < 50 && !(waveIndex % 10); - default: - return (waveIndex % 30) === (offsetGym ? 0 : 20) && (biomeType !== Biome.END || this.isClassic || this.isWaveFinal(waveIndex)); + case GameModes.DAILY: + return waveIndex > 10 && waveIndex < 50 && !(waveIndex % 10); + default: + return (waveIndex % 30) === (offsetGym ? 0 : 20) && (biomeType !== Biome.END || this.isClassic || this.isWaveFinal(waveIndex)); } } @@ -211,14 +211,14 @@ export class GameMode implements GameModeConfig { */ isWaveFinal(waveIndex: integer, modeId: GameModes = this.modeId): boolean { switch (modeId) { - case GameModes.CLASSIC: - case GameModes.CHALLENGE: - return waveIndex === 200; - case GameModes.ENDLESS: - case GameModes.SPLICED_ENDLESS: - return !(waveIndex % 250); - case GameModes.DAILY: - return waveIndex === 50; + case GameModes.CLASSIC: + case GameModes.CHALLENGE: + return waveIndex === 200; + case GameModes.ENDLESS: + case GameModes.SPLICED_ENDLESS: + return !(waveIndex % 250); + case GameModes.DAILY: + return waveIndex === 50; } } @@ -287,40 +287,40 @@ export class GameMode implements GameModeConfig { getClearScoreBonus(): integer { switch (this.modeId) { - case GameModes.CLASSIC: - case GameModes.CHALLENGE: - return 5000; - case GameModes.DAILY: - return 2500; - default: - return 0; + case GameModes.CLASSIC: + case GameModes.CHALLENGE: + return 5000; + case GameModes.DAILY: + return 2500; + default: + return 0; } } getEnemyModifierChance(isBoss: boolean): integer { switch (this.modeId) { - case GameModes.CLASSIC: - case GameModes.CHALLENGE: - case GameModes.DAILY: - return !isBoss ? 18 : 6; - case GameModes.ENDLESS: - case GameModes.SPLICED_ENDLESS: - return !isBoss ? 12 : 4; + case GameModes.CLASSIC: + case GameModes.CHALLENGE: + case GameModes.DAILY: + return !isBoss ? 18 : 6; + case GameModes.ENDLESS: + case GameModes.SPLICED_ENDLESS: + return !isBoss ? 12 : 4; } } getName(): string { switch (this.modeId) { - case GameModes.CLASSIC: - return i18next.t("gameMode:classic"); - case GameModes.ENDLESS: - return i18next.t("gameMode:endless"); - case GameModes.SPLICED_ENDLESS: - return i18next.t("gameMode:endlessSpliced"); - case GameModes.DAILY: - return i18next.t("gameMode:dailyRun"); - case GameModes.CHALLENGE: - return i18next.t("gameMode:challenge"); + case GameModes.CLASSIC: + return i18next.t("gameMode:classic"); + case GameModes.ENDLESS: + return i18next.t("gameMode:endless"); + case GameModes.SPLICED_ENDLESS: + return i18next.t("gameMode:endlessSpliced"); + case GameModes.DAILY: + return i18next.t("gameMode:dailyRun"); + case GameModes.CHALLENGE: + return i18next.t("gameMode:challenge"); } } @@ -329,42 +329,42 @@ export class GameMode implements GameModeConfig { */ getMysteryEncounterLegalWaves(): [number, number] { switch (this.modeId) { - default: - return [ 0, 0 ]; - case GameModes.CLASSIC: - return CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES; - case GameModes.CHALLENGE: - return CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES; + default: + return [ 0, 0 ]; + case GameModes.CLASSIC: + return CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES; + case GameModes.CHALLENGE: + return CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES; } } static getModeName(modeId: GameModes): string { switch (modeId) { - case GameModes.CLASSIC: - return i18next.t("gameMode:classic"); - case GameModes.ENDLESS: - return i18next.t("gameMode:endless"); - case GameModes.SPLICED_ENDLESS: - return i18next.t("gameMode:endlessSpliced"); - case GameModes.DAILY: - return i18next.t("gameMode:dailyRun"); - case GameModes.CHALLENGE: - return i18next.t("gameMode:challenge"); + case GameModes.CLASSIC: + return i18next.t("gameMode:classic"); + case GameModes.ENDLESS: + return i18next.t("gameMode:endless"); + case GameModes.SPLICED_ENDLESS: + return i18next.t("gameMode:endlessSpliced"); + case GameModes.DAILY: + return i18next.t("gameMode:dailyRun"); + case GameModes.CHALLENGE: + return i18next.t("gameMode:challenge"); } } } export function getGameMode(gameMode: GameModes): GameMode { switch (gameMode) { - case GameModes.CLASSIC: - return new GameMode(GameModes.CLASSIC, { isClassic: true, hasTrainers: true, hasMysteryEncounters: true }, classicFixedBattles); - case GameModes.ENDLESS: - return new GameMode(GameModes.ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true }); - case GameModes.SPLICED_ENDLESS: - return new GameMode(GameModes.SPLICED_ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true, isSplicedOnly: true }); - case GameModes.DAILY: - return new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true }); - case GameModes.CHALLENGE: - return new GameMode(GameModes.CHALLENGE, { isClassic: true, hasTrainers: true, isChallenge: true, hasMysteryEncounters: true }, classicFixedBattles); + case GameModes.CLASSIC: + return new GameMode(GameModes.CLASSIC, { isClassic: true, hasTrainers: true, hasMysteryEncounters: true }, classicFixedBattles); + case GameModes.ENDLESS: + return new GameMode(GameModes.ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true }); + case GameModes.SPLICED_ENDLESS: + return new GameMode(GameModes.SPLICED_ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true, isSplicedOnly: true }); + case GameModes.DAILY: + return new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true }); + case GameModes.CHALLENGE: + return new GameMode(GameModes.CHALLENGE, { isClassic: true, hasTrainers: true, isChallenge: true, hasMysteryEncounters: true }, classicFixedBattles); } } diff --git a/src/loading-scene.ts b/src/loading-scene.ts index d6512486d0e..4f673fd2cfc 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -477,18 +477,18 @@ export class LoadingScene extends SceneBase { this.load.on(this.LOAD_EVENTS.FILE_COMPLETE, (key: string) => { assetText.setText(i18next.t("menu:loadingAsset", { assetName: key })); switch (key) { - case "loading_bg": - bg.setTexture("loading_bg"); - if (mobile) { - bg.setVisible(true); - } - break; - case "logo": - logo.setTexture("logo"); - if (mobile) { - logo.setVisible(true); - } - break; + case "loading_bg": + bg.setTexture("loading_bg"); + if (mobile) { + bg.setVisible(true); + } + break; + case "logo": + logo.setTexture("logo"); + if (mobile) { + logo.setVisible(true); + } + break; } }); diff --git a/src/messages.ts b/src/messages.ts index 1cd6a5966b6..91f550918e5 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -13,21 +13,21 @@ export function getPokemonNameWithAffix(pokemon: Pokemon | undefined): string { } switch (pokemon.scene.currentBattle.battleSpec) { - case BattleSpec.DEFAULT: - return !pokemon.isPlayer() - ? pokemon.hasTrainer() - ? i18next.t("battle:foePokemonWithAffix", { - pokemonName: pokemon.getNameToRender(), - }) - : i18next.t("battle:wildPokemonWithAffix", { - pokemonName: pokemon.getNameToRender(), - }) - : pokemon.getNameToRender(); - case BattleSpec.FINAL_BOSS: - return !pokemon.isPlayer() - ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender() }) - : pokemon.getNameToRender(); - default: - return pokemon.getNameToRender(); + case BattleSpec.DEFAULT: + return !pokemon.isPlayer() + ? pokemon.hasTrainer() + ? i18next.t("battle:foePokemonWithAffix", { + pokemonName: pokemon.getNameToRender(), + }) + : i18next.t("battle:wildPokemonWithAffix", { + pokemonName: pokemon.getNameToRender(), + }) + : pokemon.getNameToRender(); + case BattleSpec.FINAL_BOSS: + return !pokemon.isPlayer() + ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender() }) + : pokemon.getNameToRender(); + default: + return pokemon.getNameToRender(); } } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 32173a6fead..674c5f47c88 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -78,18 +78,18 @@ export class ModifierType { } 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; + 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) { @@ -502,42 +502,42 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge function getAttackTypeBoosterItemName(type: Type) { switch (type) { - case Type.NORMAL: - return "Silk Scarf"; - case Type.FIGHTING: - return "Black Belt"; - case Type.FLYING: - return "Sharp Beak"; - case Type.POISON: - return "Poison Barb"; - case Type.GROUND: - return "Soft Sand"; - case Type.ROCK: - return "Hard Stone"; - case Type.BUG: - return "Silver Powder"; - case Type.GHOST: - return "Spell Tag"; - case Type.STEEL: - return "Metal Coat"; - case Type.FIRE: - return "Charcoal"; - case Type.WATER: - return "Mystic Water"; - case Type.GRASS: - return "Miracle Seed"; - case Type.ELECTRIC: - return "Magnet"; - case Type.PSYCHIC: - return "Twisted Spoon"; - case Type.ICE: - return "Never-Melt Ice"; - case Type.DRAGON: - return "Dragon Fang"; - case Type.DARK: - return "Black Glasses"; - case Type.FAIRY: - return "Fairy Feather"; + case Type.NORMAL: + return "Silk Scarf"; + case Type.FIGHTING: + return "Black Belt"; + case Type.FLYING: + return "Sharp Beak"; + case Type.POISON: + return "Poison Barb"; + case Type.GROUND: + return "Soft Sand"; + case Type.ROCK: + return "Hard Stone"; + case Type.BUG: + return "Silver Powder"; + case Type.GHOST: + return "Spell Tag"; + case Type.STEEL: + return "Metal Coat"; + case Type.FIRE: + return "Charcoal"; + case Type.WATER: + return "Mystic Water"; + case Type.GRASS: + return "Miracle Seed"; + case Type.ELECTRIC: + return "Magnet"; + case Type.PSYCHIC: + return "Twisted Spoon"; + case Type.ICE: + return "Never-Melt Ice"; + case Type.DRAGON: + return "Dragon Fang"; + case Type.DARK: + return "Black Glasses"; + case Type.FAIRY: + return "Fairy Feather"; } } @@ -1149,15 +1149,15 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { foundN_SOLAR = false; formChangeItemTriggers.forEach((fc, i) => { 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; + 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) { @@ -1975,21 +1975,21 @@ let enemyBuffIgnoredPoolIndexes = {}; // eslint-disable-line @typescript-eslint/ export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool { let pool: ModifierPool; switch (poolType) { - case ModifierPoolType.PLAYER: - pool = modifierPool; - break; - case ModifierPoolType.WILD: - pool = wildModifierPool; - break; - case ModifierPoolType.TRAINER: - pool = trainerModifierPool; - break; - case ModifierPoolType.ENEMY_BUFF: - pool = enemyBuffModifierPool; - break; - case ModifierPoolType.DAILY_STARTER: - pool = dailyStarterModifierPool; - break; + case ModifierPoolType.PLAYER: + pool = modifierPool; + break; + case ModifierPoolType.WILD: + pool = wildModifierPool; + break; + case ModifierPoolType.TRAINER: + pool = trainerModifierPool; + break; + case ModifierPoolType.ENEMY_BUFF: + pool = enemyBuffModifierPool; + break; + case ModifierPoolType.DAILY_STARTER: + pool = dailyStarterModifierPool; + break; } return pool; } @@ -2060,23 +2060,23 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod 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; + 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; } } @@ -2247,15 +2247,15 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: PersistentModifier[], scene: BattleScene): EnemyPersistentModifier { let tierStackCount: number; switch (tier) { - case ModifierTier.ULTRA: - tierStackCount = 5; - break; - case ModifierTier.GREAT: - tierStackCount = 3; - break; - default: - tierStackCount = 1; - break; + case ModifierTier.ULTRA: + tierStackCount = 5; + break; + case ModifierTier.GREAT: + tierStackCount = 3; + break; + default: + tierStackCount = 1; + break; } const retryCount = 50; @@ -2321,21 +2321,21 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, 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; + 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; } if (tier === undefined) { const tierValue = randSeedInt(1024); diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 35d1a304461..2cd96496f45 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1695,12 +1695,12 @@ export class TurnStatusEffectModifier extends PokemonHeldItemModifier { super(type, pokemonId, stackCount); switch (type.id) { - case "TOXIC_ORB": - this.effect = StatusEffect.TOXIC; - break; - case "FLAME_ORB": - this.effect = StatusEffect.BURN; - break; + case "TOXIC_ORB": + this.effect = StatusEffect.TOXIC; + break; + case "FLAME_ORB": + this.effect = StatusEffect.BURN; + break; } } @@ -2684,15 +2684,15 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier { count.value *= (this.getStackCount() + 1); switch (this.getStackCount()) { - case 1: - power.value *= 0.4; - break; - case 2: - power.value *= 0.25; - break; - case 3: - power.value *= 0.175; - break; + case 1: + power.value *= 0.4; + break; + case 2: + power.value *= 0.25; + break; + case 3: + power.value *= 0.175; + break; } return true; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index cf66631bd96..e6f2eb69ff3 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -84,170 +84,170 @@ export class CommandPhase extends FieldPhase { let success: boolean; switch (command) { - case Command.FIGHT: - let useStruggle = false; - if (cursor === -1 || + case Command.FIGHT: + let useStruggle = false; + if (cursor === -1 || playerPokemon.trySelectMove(cursor, args[0] as boolean) || (useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m?.isUsable(playerPokemon)).length)) { - const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct? - const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; - const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2]; - if (!moveId) { - turnCommand.targets = [ this.fieldIndex ]; - } - console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); - if (moveTargets.targets.length > 1 && moveTargets.multiple) { - this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); - } - if (moveTargets.targets.length <= 1 || moveTargets.multiple) { + const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct? + const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; + const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2]; + if (!moveId) { + turnCommand.targets = [ this.fieldIndex ]; + } + console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); + if (moveTargets.targets.length > 1 && moveTargets.multiple) { + this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + } + if (moveTargets.targets.length <= 1 || moveTargets.multiple) { turnCommand.move!.targets = moveTargets.targets; //TODO: is the bang correct here? - } else if (playerPokemon.getTag(BattlerTagType.CHARGING) && playerPokemon.getMoveQueue().length >= 1) { + } else if (playerPokemon.getTag(BattlerTagType.CHARGING) && playerPokemon.getMoveQueue().length >= 1) { turnCommand.move!.targets = playerPokemon.getMoveQueue()[0].targets; //TODO: is the bang correct here? - } else { - this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); - } - this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; - success = true; - } else if (cursor < playerPokemon.getMoveset().length) { - const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct? - this.scene.ui.setMode(Mode.MESSAGE); + } else { + this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + } + this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; + success = true; + } else if (cursor < playerPokemon.getMoveset().length) { + const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct? + this.scene.ui.setMode(Mode.MESSAGE); - // Decides between a Disabled, Not Implemented, or No PP translation message - const errorMessage = + // 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 + const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator - this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); - }, null, true); - } - break; - case Command.BALL: - const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1); - if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (this.scene.currentBattle.isBattleMysteryEncounter() && !this.scene.currentBattle.mysteryEncounter!.catchAllowed) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballMysteryEncounter"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else { - const targets = this.scene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex()); - if (targets.length > 1) { + this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); + }, null, true); + } + break; + case Command.BALL: + const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1); + if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => { + this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { this.scene.ui.showText("", 0); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); - } else if (cursor < 5) { - const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true)); - if (targetPokemon?.isBoss() && targetPokemon?.bossSegmentIndex >= 1 && !targetPokemon?.hasAbility(Abilities.WONDER_GUARD, false, true) && cursor < PokeballType.MASTER_BALL) { + } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else if (this.scene.currentBattle.isBattleMysteryEncounter() && !this.scene.currentBattle.mysteryEncounter!.catchAllowed) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballMysteryEncounter"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else { + const targets = this.scene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex()); + if (targets.length > 1) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => { + this.scene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => { this.scene.ui.showText("", 0); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); - } else { - this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; + } else if (cursor < 5) { + const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true)); + if (targetPokemon?.isBoss() && targetPokemon?.bossSegmentIndex >= 1 && !targetPokemon?.hasAbility(Abilities.WONDER_GUARD, false, true) && cursor < PokeballType.MASTER_BALL) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else { + this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; this.scene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets; if (this.fieldIndex) { this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; } success = true; + } } } - } - break; - case Command.POKEMON: - case Command.RUN: - const isSwitch = command === Command.POKEMON; - const { currentBattle, arena } = this.scene; - const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed; - if (!isSwitch && (arena.biomeType === Biome.END || (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => { - this.scene.ui.showText("", 0); + break; + case Command.POKEMON: + case Command.RUN: + const isSwitch = command === Command.POKEMON; + const { currentBattle, arena } = this.scene; + const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed; + if (!isSwitch && (arena.biomeType === Biome.END || (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (!isSwitch && (currentBattle.battleType === BattleType.TRAINER || currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.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) { - this.scene.ui.setMode(Mode.MESSAGE); - } - this.scene.ui.showText(trappedAbMessages[0], null, () => { + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => { this.scene.ui.showText("", 0); - if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - } + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else if (!isSwitch && (currentBattle.battleType === BattleType.TRAINER || currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); } else { - const trapTag = playerPokemon.getTag(TrappedTag); - - // trapTag should be defined at this point, but just in case... - if (!trapTag) { + 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 }; - break; - } - - if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - } - this.scene.ui.showText( - i18next.t("battle:noEscapePokemon", { - pokemonName: trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId) ? getPokemonNameWithAffix(this.scene.getPokemonById(trapTag.sourceId)!) : "", - moveName: trapTag.getMoveName(), - escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee") - }), - null, - () => { + success = true; + if (!isSwitch && this.fieldIndex) { + currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; + } + } else if (trappedAbMessages.length > 0) { + if (!isSwitch) { + this.scene.ui.setMode(Mode.MESSAGE); + } + this.scene.ui.showText(trappedAbMessages[0], null, () => { this.scene.ui.showText("", 0); if (!isSwitch) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); } }, null, true); + } else { + const trapTag = playerPokemon.getTag(TrappedTag); + + // trapTag should be defined at this point, but just in case... + if (!trapTag) { + currentBattle.turnCommands[this.fieldIndex] = isSwitch + ? { command: Command.POKEMON, cursor: cursor, args: args } + : { command: Command.RUN }; + break; + } + + if (!isSwitch) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + } + this.scene.ui.showText( + i18next.t("battle:noEscapePokemon", { + pokemonName: trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId) ? getPokemonNameWithAffix(this.scene.getPokemonById(trapTag.sourceId)!) : "", + moveName: trapTag.getMoveName(), + escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee") + }), + null, + () => { + this.scene.ui.showText("", 0); + if (!isSwitch) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } + }, null, true); + } } - } - break; + break; } if (success!) { // TODO: is the bang correct? diff --git a/src/phases/damage-phase.ts b/src/phases/damage-phase.ts index 66b11512729..44e3dfd4182 100644 --- a/src/phases/damage-phase.ts +++ b/src/phases/damage-phase.ts @@ -41,16 +41,16 @@ export class DamagePhase extends PokemonPhase { applyDamage() { switch (this.damageResult) { - case HitResult.EFFECTIVE: - this.scene.playSound("se/hit"); - break; - case HitResult.SUPER_EFFECTIVE: - case HitResult.ONE_HIT_KO: - this.scene.playSound("se/hit_strong"); - break; - case HitResult.NOT_VERY_EFFECTIVE: - this.scene.playSound("se/hit_weak"); - break; + case HitResult.EFFECTIVE: + this.scene.playSound("se/hit"); + break; + case HitResult.SUPER_EFFECTIVE: + case HitResult.ONE_HIT_KO: + this.scene.playSound("se/hit_strong"); + break; + case HitResult.NOT_VERY_EFFECTIVE: + this.scene.playSound("se/hit_weak"); + break; } if (this.amount) { diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 3610ffed386..84f07f3ee94 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -494,31 +494,31 @@ export class EncounterPhase extends BattlePhase { tryOverrideForBattleSpec(): boolean { switch (this.scene.currentBattle.battleSpec) { - case BattleSpec.FINAL_BOSS: - const enemy = this.scene.getEnemyPokemon(); - this.scene.ui.showText(this.getEncounterMessage(), null, () => { - const localizationKey = "battleSpecDialogue:encounter"; - if (this.scene.ui.shouldSkipDialogue(localizationKey)) { + case BattleSpec.FINAL_BOSS: + const enemy = this.scene.getEnemyPokemon(); + this.scene.ui.showText(this.getEncounterMessage(), null, () => { + const localizationKey = "battleSpecDialogue:encounter"; + if (this.scene.ui.shouldSkipDialogue(localizationKey)) { // Logging mirrors logging found in dialogue-ui-handler - console.log(`Dialogue ${localizationKey} skipped`); - this.doEncounterCommon(false); - } else { - const count = 5643853 + this.scene.gameData.gameStats.classicSessionsPlayed; - // The line below checks if an English ordinal is necessary or not based on whether an entry for encounterLocalizationKey exists in the language or not. - const ordinalUsed = !i18next.exists(localizationKey, { fallbackLng: []}) || i18next.resolvedLanguage === "en" ? i18next.t("battleSpecDialogue:key", { count: count, ordinal: true }) : ""; - const cycleCount = count.toLocaleString() + ordinalUsed; - const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; - const genderStr = PlayerGender[genderIndex].toLowerCase(); - const encounterDialogue = i18next.t(localizationKey, { context: genderStr, cycleCount: cycleCount }); - if (!this.scene.gameData.getSeenDialogues()[localizationKey]) { - this.scene.gameData.saveSeenDialogue(localizationKey); - } - this.scene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => { + console.log(`Dialogue ${localizationKey} skipped`); this.doEncounterCommon(false); - }); - } - }, 1500, true); - return true; + } else { + const count = 5643853 + this.scene.gameData.gameStats.classicSessionsPlayed; + // The line below checks if an English ordinal is necessary or not based on whether an entry for encounterLocalizationKey exists in the language or not. + const ordinalUsed = !i18next.exists(localizationKey, { fallbackLng: []}) || i18next.resolvedLanguage === "en" ? i18next.t("battleSpecDialogue:key", { count: count, ordinal: true }) : ""; + const cycleCount = count.toLocaleString() + ordinalUsed; + const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; + const genderStr = PlayerGender[genderIndex].toLowerCase(); + const encounterDialogue = i18next.t(localizationKey, { context: genderStr, cycleCount: cycleCount }); + if (!this.scene.gameData.getSeenDialogues()[localizationKey]) { + this.scene.gameData.saveSeenDialogue(localizationKey); + } + this.scene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => { + this.doEncounterCommon(false); + }); + } + }, 1500, true); + return true; } return false; } diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 95105337f60..eee1fd52938 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -179,19 +179,19 @@ export class FaintPhase extends PokemonPhase { tryOverrideForBattleSpec(): boolean { switch (this.scene.currentBattle.battleSpec) { - case BattleSpec.FINAL_BOSS: - if (!this.player) { - const enemy = this.getPokemon(); - if (enemy.formIndex) { - this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint()); - } else { + case BattleSpec.FINAL_BOSS: + if (!this.player) { + const enemy = this.getPokemon(); + if (enemy.formIndex) { + this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint()); + } else { // Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase - enemy.hp++; - this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); - this.end(); + enemy.hp++; + this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); + this.end(); + } + return true; } - return true; - } } return false; diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index f50cfbd78ac..0af61918636 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -167,23 +167,23 @@ export class MovePhase extends BattlePhase { let healed = false; switch (this.pokemon.status.effect) { - case StatusEffect.PARALYSIS: - if (!this.pokemon.randSeedInt(4)) { - activated = true; - this.cancelled = true; - } - break; - case StatusEffect.SLEEP: - applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); - healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn; - activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); - this.cancelled = activated; - break; - case StatusEffect.FREEZE: - healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5); - activated = !healed; - this.cancelled = activated; - break; + case StatusEffect.PARALYSIS: + if (!this.pokemon.randSeedInt(4)) { + activated = true; + this.cancelled = true; + } + break; + case StatusEffect.SLEEP: + applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); + healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn; + activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); + this.cancelled = activated; + break; + case StatusEffect.FREEZE: + healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5); + activated = !healed; + this.cancelled = activated; + break; } if (activated) { diff --git a/src/phases/pokemon-anim-phase.ts b/src/phases/pokemon-anim-phase.ts index 6f1bfe8bc18..26ae11d1026 100644 --- a/src/phases/pokemon-anim-phase.ts +++ b/src/phases/pokemon-anim-phase.ts @@ -25,20 +25,20 @@ export class PokemonAnimPhase extends BattlePhase { super.start(); switch (this.key) { - case PokemonAnimType.SUBSTITUTE_ADD: - this.doSubstituteAddAnim(); - break; - case PokemonAnimType.SUBSTITUTE_PRE_MOVE: - this.doSubstitutePreMoveAnim(); - break; - case PokemonAnimType.SUBSTITUTE_POST_MOVE: - this.doSubstitutePostMoveAnim(); - break; - case PokemonAnimType.SUBSTITUTE_REMOVE: - this.doSubstituteRemoveAnim(); - break; - default: - this.end(); + case PokemonAnimType.SUBSTITUTE_ADD: + this.doSubstituteAddAnim(); + break; + case PokemonAnimType.SUBSTITUTE_PRE_MOVE: + this.doSubstitutePreMoveAnim(); + break; + case PokemonAnimType.SUBSTITUTE_POST_MOVE: + this.doSubstitutePostMoveAnim(); + break; + case PokemonAnimType.SUBSTITUTE_REMOVE: + this.doSubstituteRemoveAnim(); + break; + default: + this.end(); } } diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 285bbddde88..06681b733f0 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -26,16 +26,16 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { this.scene.queueMessage(getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); const damage = new Utils.NumberHolder(0); switch (pokemon.status.effect) { - case StatusEffect.POISON: - damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); - break; - case StatusEffect.TOXIC: - damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); - break; - case StatusEffect.BURN: - damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); - applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage); - break; + case StatusEffect.POISON: + damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); + break; + case StatusEffect.TOXIC: + damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); + break; + case StatusEffect.BURN: + damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); + applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage); + break; } if (damage.value) { // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index f9b3e978923..38d5cfb4a10 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -77,78 +77,78 @@ export class SelectModifierPhase extends BattlePhase { let cost: integer; const rerollCost = this.getRerollCost(this.scene.lockModifierTiers); switch (rowCursor) { - case 0: - switch (cursor) { case 0: - if (rerollCost < 0 || this.scene.money < rerollCost) { - this.scene.ui.playError(); - return false; - } else { - this.scene.reroll = true; - this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[])); - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); - if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - this.scene.money -= rerollCost; - this.scene.updateMoneyText(); - this.scene.animateMoneyChanged(false); - } - this.scene.playSound("se/buy"); - } - break; - case 1: - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { - if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { - const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + switch (cursor) { + case 0: + if (rerollCost < 0 || this.scene.money < rerollCost) { + this.scene.ui.playError(); + return false; + } else { + this.scene.reroll = true; + this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[])); + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); + if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + this.scene.money -= rerollCost; + this.scene.updateMoneyText(); + this.scene.animateMoneyChanged(false); + } + this.scene.playSound("se/buy"); + } + break; + case 1: + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { + if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { + const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; - const itemModifier = itemModifiers[itemIndex]; - this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); - } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); - } - }, PartyUiHandler.FilterItemMaxStacks); - break; - case 2: - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); - }); - break; - case 3: - if (rerollCost < 0) { - // Reroll lock button is also disabled when reroll is disabled - this.scene.ui.playError(); - return false; + const itemModifier = itemModifiers[itemIndex]; + this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); + } else { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); + } + }, PartyUiHandler.FilterItemMaxStacks); + break; + case 2: + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); + }); + break; + case 3: + if (rerollCost < 0) { + // Reroll lock button is also disabled when reroll is disabled + this.scene.ui.playError(); + return false; + } + this.scene.lockModifierTiers = !this.scene.lockModifierTiers; + const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; + uiHandler.setRerollCost(this.getRerollCost(this.scene.lockModifierTiers)); + uiHandler.updateLockRaritiesText(); + uiHandler.updateRerollCostText(); + return false; } - this.scene.lockModifierTiers = !this.scene.lockModifierTiers; - const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; - uiHandler.setRerollCost(this.getRerollCost(this.scene.lockModifierTiers)); - uiHandler.updateLockRaritiesText(); - uiHandler.updateRerollCostText(); - return false; - } - return true; - case 1: - if (this.typeOptions.length === 0) { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.MESSAGE); - super.end(); return true; - } - if (this.typeOptions[cursor].type) { - modifierType = this.typeOptions[cursor].type; - } - break; - default: - const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); - const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; - if (shopOption.type) { - modifierType = shopOption.type; - } - // Apply Black Sludge to healing item cost - const healingItemCost = new NumberHolder(shopOption.cost); - this.scene.applyModifier(HealShopCostModifier, true, healingItemCost); - cost = healingItemCost.value; - break; + case 1: + if (this.typeOptions.length === 0) { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.MESSAGE); + super.end(); + return true; + } + if (this.typeOptions[cursor].type) { + modifierType = this.typeOptions[cursor].type; + } + break; + default: + const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); + const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; + if (shopOption.type) { + modifierType = shopOption.type; + } + // Apply Black Sludge to healing item cost + const healingItemCost = new NumberHolder(shopOption.cost); + this.scene.applyModifier(HealShopCostModifier, true, healingItemCost); + cost = healingItemCost.value; + break; } if (cost! && (this.scene.money < cost) && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { // TODO: is the bang on cost correct? diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 25c079007fd..497d449912f 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -152,55 +152,55 @@ export class TurnStartPhase extends FieldPhase { } switch (turnCommand?.command) { - case Command.FIGHT: - const queuedMove = turnCommand.move; - pokemon.turnData.order = orderIndex++; - if (!queuedMove) { - continue; - } - const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move); - if (move.getMove().hasAttr(MoveHeaderAttr)) { - this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); - } - if (pokemon.isPlayer()) { - if (turnCommand.cursor === -1) { - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here? - } else { - const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here? - this.scene.pushPhase(playerPhase); + case Command.FIGHT: + const queuedMove = turnCommand.move; + pokemon.turnData.order = orderIndex++; + if (!queuedMove) { + continue; } - } else { - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here? - } - break; - case Command.BALL: - this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here? - break; - case Command.POKEMON: - const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH; - this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer())); - break; - case Command.RUN: - let runningPokemon = pokemon; - if (this.scene.currentBattle.double) { - const playerActivePokemon = field.filter(pokemon => { - if (!!pokemon) { - return pokemon.isPlayer() && pokemon.isActive(); + const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move); + if (move.getMove().hasAttr(MoveHeaderAttr)) { + this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); + } + if (pokemon.isPlayer()) { + if (turnCommand.cursor === -1) { + this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here? } else { - return; + const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here? + this.scene.pushPhase(playerPhase); } - }); - // if only one pokemon is alive, use that one - if (playerActivePokemon.length > 1) { - // find which active pokemon has faster speed - const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1]; - // check if either active pokemon has the ability "Run Away" - const hasRunAway = playerActivePokemon.find(p => p.hasAbility(Abilities.RUN_AWAY)); - runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; + } else { + this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here? } - } - this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex())); - break; + break; + case Command.BALL: + this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here? + break; + case Command.POKEMON: + const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH; + this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer())); + break; + case Command.RUN: + let runningPokemon = pokemon; + if (this.scene.currentBattle.double) { + const playerActivePokemon = field.filter(pokemon => { + if (!!pokemon) { + return pokemon.isPlayer() && pokemon.isActive(); + } else { + return; + } + }); + // if only one pokemon is alive, use that one + if (playerActivePokemon.length > 1) { + // find which active pokemon has faster speed + const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1]; + // check if either active pokemon has the ability "Run Away" + const hasRunAway = playerActivePokemon.find(p => p.hasAbility(Abilities.RUN_AWAY)); + runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; + } + } + this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex())); + break; } } diff --git a/src/system/achv.ts b/src/system/achv.ts index 7329dd41fd5..366813328e2 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -156,133 +156,133 @@ export function getAchievementDescription(localizationKey: string): string { const genderStr = PlayerGender[genderIndex].toLowerCase(); switch (localizationKey) { - case "10K_MONEY": - return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10K_MONEY.moneyAmount.toLocaleString("en-US") }); - case "100K_MONEY": - return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._100K_MONEY.moneyAmount.toLocaleString("en-US") }); - case "1M_MONEY": - return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._1M_MONEY.moneyAmount.toLocaleString("en-US") }); - case "10M_MONEY": - return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10M_MONEY.moneyAmount.toLocaleString("en-US") }); - case "250_DMG": - return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._250_DMG.damageAmount.toLocaleString("en-US") }); - case "1000_DMG": - return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._1000_DMG.damageAmount.toLocaleString("en-US") }); - case "2500_DMG": - return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._2500_DMG.damageAmount.toLocaleString("en-US") }); - case "10000_DMG": - return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._10000_DMG.damageAmount.toLocaleString("en-US") }); - case "250_HEAL": - return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._250_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); - case "1000_HEAL": - return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._1000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); - case "2500_HEAL": - return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._2500_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); - case "10000_HEAL": - return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._10000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); - case "LV_100": - return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_100.level }); - case "LV_250": - return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_250.level }); - case "LV_1000": - return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_1000.level }); - case "10_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._10_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "25_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._25_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "50_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._50_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "75_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._75_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "100_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "TRANSFER_MAX_STAT_STAGE": - return i18next.t("achv:TRANSFER_MAX_STAT_STAGE.description", { context: genderStr }); - case "MAX_FRIENDSHIP": - return i18next.t("achv:MAX_FRIENDSHIP.description", { context: genderStr }); - case "MEGA_EVOLVE": - return i18next.t("achv:MEGA_EVOLVE.description", { context: genderStr }); - case "GIGANTAMAX": - return i18next.t("achv:GIGANTAMAX.description", { context: genderStr }); - case "TERASTALLIZE": - return i18next.t("achv:TERASTALLIZE.description", { context: genderStr }); - case "STELLAR_TERASTALLIZE": - return i18next.t("achv:STELLAR_TERASTALLIZE.description", { context: genderStr }); - case "SPLICE": - return i18next.t("achv:SPLICE.description", { context: genderStr }); - case "MINI_BLACK_HOLE": - return i18next.t("achv:MINI_BLACK_HOLE.description", { context: genderStr }); - case "CATCH_MYTHICAL": - return i18next.t("achv:CATCH_MYTHICAL.description", { context: genderStr }); - case "CATCH_SUB_LEGENDARY": - return i18next.t("achv:CATCH_SUB_LEGENDARY.description", { context: genderStr }); - case "CATCH_LEGENDARY": - return i18next.t("achv:CATCH_LEGENDARY.description", { context: genderStr }); - case "SEE_SHINY": - return i18next.t("achv:SEE_SHINY.description", { context: genderStr }); - case "SHINY_PARTY": - return i18next.t("achv:SHINY_PARTY.description", { context: genderStr }); - case "HATCH_MYTHICAL": - return i18next.t("achv:HATCH_MYTHICAL.description", { context: genderStr }); - case "HATCH_SUB_LEGENDARY": - return i18next.t("achv:HATCH_SUB_LEGENDARY.description", { context: genderStr }); - case "HATCH_LEGENDARY": - return i18next.t("achv:HATCH_LEGENDARY.description", { context: genderStr }); - case "HATCH_SHINY": - return i18next.t("achv:HATCH_SHINY.description", { context: genderStr }); - case "HIDDEN_ABILITY": - return i18next.t("achv:HIDDEN_ABILITY.description", { context: genderStr }); - case "PERFECT_IVS": - return i18next.t("achv:PERFECT_IVS.description", { context: genderStr }); - case "CLASSIC_VICTORY": - return i18next.t("achv:CLASSIC_VICTORY.description", { context: genderStr }); - case "UNEVOLVED_CLASSIC_VICTORY": - return i18next.t("achv:UNEVOLVED_CLASSIC_VICTORY.description", { context: genderStr }); - case "MONO_GEN_ONE": - return i18next.t("achv:MONO_GEN_ONE.description", { context: genderStr }); - case "MONO_GEN_TWO": - return i18next.t("achv:MONO_GEN_TWO.description", { context: genderStr }); - case "MONO_GEN_THREE": - return i18next.t("achv:MONO_GEN_THREE.description", { context: genderStr }); - case "MONO_GEN_FOUR": - return i18next.t("achv:MONO_GEN_FOUR.description", { context: genderStr }); - case "MONO_GEN_FIVE": - return i18next.t("achv:MONO_GEN_FIVE.description", { context: genderStr }); - case "MONO_GEN_SIX": - return i18next.t("achv:MONO_GEN_SIX.description", { context: genderStr }); - case "MONO_GEN_SEVEN": - return i18next.t("achv:MONO_GEN_SEVEN.description", { context: genderStr }); - case "MONO_GEN_EIGHT": - return i18next.t("achv:MONO_GEN_EIGHT.description", { context: genderStr }); - case "MONO_GEN_NINE": - return i18next.t("achv:MONO_GEN_NINE.description", { context: genderStr }); - case "MONO_NORMAL": - case "MONO_FIGHTING": - case "MONO_FLYING": - case "MONO_POISON": - case "MONO_GROUND": - case "MONO_ROCK": - case "MONO_BUG": - case "MONO_GHOST": - case "MONO_STEEL": - case "MONO_FIRE": - case "MONO_WATER": - case "MONO_GRASS": - case "MONO_ELECTRIC": - case "MONO_PSYCHIC": - case "MONO_ICE": - case "MONO_DRAGON": - case "MONO_DARK": - case "MONO_FAIRY": - return i18next.t("achv:MonoType.description", { context: genderStr, "type": i18next.t(`pokemonInfo:Type.${localizationKey.slice(5)}`) }); - case "FRESH_START": - return i18next.t("achv:FRESH_START.description", { context: genderStr }); - case "INVERSE_BATTLE": - return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr }); - case "BREEDERS_IN_SPACE": - return i18next.t("achv:BREEDERS_IN_SPACE.description", { context: genderStr }); - default: - return ""; + case "10K_MONEY": + return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10K_MONEY.moneyAmount.toLocaleString("en-US") }); + case "100K_MONEY": + return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._100K_MONEY.moneyAmount.toLocaleString("en-US") }); + case "1M_MONEY": + return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._1M_MONEY.moneyAmount.toLocaleString("en-US") }); + case "10M_MONEY": + return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10M_MONEY.moneyAmount.toLocaleString("en-US") }); + case "250_DMG": + return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._250_DMG.damageAmount.toLocaleString("en-US") }); + case "1000_DMG": + return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._1000_DMG.damageAmount.toLocaleString("en-US") }); + case "2500_DMG": + return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._2500_DMG.damageAmount.toLocaleString("en-US") }); + case "10000_DMG": + return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._10000_DMG.damageAmount.toLocaleString("en-US") }); + case "250_HEAL": + return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._250_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); + case "1000_HEAL": + return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._1000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); + case "2500_HEAL": + return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._2500_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); + case "10000_HEAL": + return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._10000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); + case "LV_100": + return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_100.level }); + case "LV_250": + return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_250.level }); + case "LV_1000": + return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_1000.level }); + case "10_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._10_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "25_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._25_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "50_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._50_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "75_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._75_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "100_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "TRANSFER_MAX_STAT_STAGE": + return i18next.t("achv:TRANSFER_MAX_STAT_STAGE.description", { context: genderStr }); + case "MAX_FRIENDSHIP": + return i18next.t("achv:MAX_FRIENDSHIP.description", { context: genderStr }); + case "MEGA_EVOLVE": + return i18next.t("achv:MEGA_EVOLVE.description", { context: genderStr }); + case "GIGANTAMAX": + return i18next.t("achv:GIGANTAMAX.description", { context: genderStr }); + case "TERASTALLIZE": + return i18next.t("achv:TERASTALLIZE.description", { context: genderStr }); + case "STELLAR_TERASTALLIZE": + return i18next.t("achv:STELLAR_TERASTALLIZE.description", { context: genderStr }); + case "SPLICE": + return i18next.t("achv:SPLICE.description", { context: genderStr }); + case "MINI_BLACK_HOLE": + return i18next.t("achv:MINI_BLACK_HOLE.description", { context: genderStr }); + case "CATCH_MYTHICAL": + return i18next.t("achv:CATCH_MYTHICAL.description", { context: genderStr }); + case "CATCH_SUB_LEGENDARY": + return i18next.t("achv:CATCH_SUB_LEGENDARY.description", { context: genderStr }); + case "CATCH_LEGENDARY": + return i18next.t("achv:CATCH_LEGENDARY.description", { context: genderStr }); + case "SEE_SHINY": + return i18next.t("achv:SEE_SHINY.description", { context: genderStr }); + case "SHINY_PARTY": + return i18next.t("achv:SHINY_PARTY.description", { context: genderStr }); + case "HATCH_MYTHICAL": + return i18next.t("achv:HATCH_MYTHICAL.description", { context: genderStr }); + case "HATCH_SUB_LEGENDARY": + return i18next.t("achv:HATCH_SUB_LEGENDARY.description", { context: genderStr }); + case "HATCH_LEGENDARY": + return i18next.t("achv:HATCH_LEGENDARY.description", { context: genderStr }); + case "HATCH_SHINY": + return i18next.t("achv:HATCH_SHINY.description", { context: genderStr }); + case "HIDDEN_ABILITY": + return i18next.t("achv:HIDDEN_ABILITY.description", { context: genderStr }); + case "PERFECT_IVS": + return i18next.t("achv:PERFECT_IVS.description", { context: genderStr }); + case "CLASSIC_VICTORY": + return i18next.t("achv:CLASSIC_VICTORY.description", { context: genderStr }); + case "UNEVOLVED_CLASSIC_VICTORY": + return i18next.t("achv:UNEVOLVED_CLASSIC_VICTORY.description", { context: genderStr }); + case "MONO_GEN_ONE": + return i18next.t("achv:MONO_GEN_ONE.description", { context: genderStr }); + case "MONO_GEN_TWO": + return i18next.t("achv:MONO_GEN_TWO.description", { context: genderStr }); + case "MONO_GEN_THREE": + return i18next.t("achv:MONO_GEN_THREE.description", { context: genderStr }); + case "MONO_GEN_FOUR": + return i18next.t("achv:MONO_GEN_FOUR.description", { context: genderStr }); + case "MONO_GEN_FIVE": + return i18next.t("achv:MONO_GEN_FIVE.description", { context: genderStr }); + case "MONO_GEN_SIX": + return i18next.t("achv:MONO_GEN_SIX.description", { context: genderStr }); + case "MONO_GEN_SEVEN": + return i18next.t("achv:MONO_GEN_SEVEN.description", { context: genderStr }); + case "MONO_GEN_EIGHT": + return i18next.t("achv:MONO_GEN_EIGHT.description", { context: genderStr }); + case "MONO_GEN_NINE": + return i18next.t("achv:MONO_GEN_NINE.description", { context: genderStr }); + case "MONO_NORMAL": + case "MONO_FIGHTING": + case "MONO_FLYING": + case "MONO_POISON": + case "MONO_GROUND": + case "MONO_ROCK": + case "MONO_BUG": + case "MONO_GHOST": + case "MONO_STEEL": + case "MONO_FIRE": + case "MONO_WATER": + case "MONO_GRASS": + case "MONO_ELECTRIC": + case "MONO_PSYCHIC": + case "MONO_ICE": + case "MONO_DRAGON": + case "MONO_DARK": + case "MONO_FAIRY": + return i18next.t("achv:MonoType.description", { context: genderStr, "type": i18next.t(`pokemonInfo:Type.${localizationKey.slice(5)}`) }); + case "FRESH_START": + return i18next.t("achv:FRESH_START.description", { context: genderStr }); + case "INVERSE_BATTLE": + return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr }); + case "BREEDERS_IN_SPACE": + return i18next.t("achv:BREEDERS_IN_SPACE.description", { context: genderStr }); + default: + return ""; } } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 41746957d49..5f9aad63408 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -67,22 +67,22 @@ const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet n export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): string { switch (dataType) { - case GameDataType.SYSTEM: - return "data"; - case GameDataType.SESSION: - let ret = "sessionData"; - if (slotId) { - ret += slotId; - } - return ret; - case GameDataType.SETTINGS: - return "settings"; - case GameDataType.TUTORIALS: - return "tutorials"; - case GameDataType.SEEN_DIALOGUES: - return "seenDialogues"; - case GameDataType.RUN_HISTORY: - return "runHistoryData"; + case GameDataType.SYSTEM: + return "data"; + case GameDataType.SESSION: + let ret = "sessionData"; + if (slotId) { + ret += slotId; + } + return ret; + case GameDataType.SETTINGS: + return "settings"; + case GameDataType.TUTORIALS: + return "tutorials"; + case GameDataType.SEEN_DIALOGUES: + return "seenDialogues"; + case GameDataType.RUN_HISTORY: + return "runHistoryData"; } } @@ -1374,9 +1374,9 @@ export class GameData { const dataKey: string = `${getDataTypeKey(dataType, slotId)}_${loggedInUser?.username}`; const handleData = (dataStr: string) => { switch (dataType) { - case GameDataType.SYSTEM: - dataStr = this.convertSystemDataStr(dataStr, true); - break; + case GameDataType.SYSTEM: + dataStr = this.convertSystemDataStr(dataStr, true); + break; } const encryptedData = AES.encrypt(dataStr, saveKey); const blob = new Blob([ encryptedData.toString() ], { type: "text/json" }); @@ -1434,28 +1434,28 @@ export class GameData { try { dataName = GameDataType[dataType].toLowerCase(); switch (dataType) { - case GameDataType.SYSTEM: - dataStr = this.convertSystemDataStr(dataStr); - const systemData = this.parseSystemData(dataStr); - valid = !!systemData.dexData && !!systemData.timestamp; - break; - case GameDataType.SESSION: - const sessionData = this.parseSessionData(dataStr); - valid = !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp; - break; - case GameDataType.RUN_HISTORY: - const data = JSON.parse(dataStr); - const keys = Object.keys(data); - dataName = i18next.t("menuUiHandler:RUN_HISTORY").toLowerCase(); - keys.forEach((key) => { - const entryKeys = Object.keys(data[key]); - valid = [ "isFavorite", "isVictory", "entry" ].every(v => entryKeys.includes(v)) && entryKeys.length === 3; - }); - break; - case GameDataType.SETTINGS: - case GameDataType.TUTORIALS: - valid = true; - break; + case GameDataType.SYSTEM: + dataStr = this.convertSystemDataStr(dataStr); + const systemData = this.parseSystemData(dataStr); + valid = !!systemData.dexData && !!systemData.timestamp; + break; + case GameDataType.SESSION: + const sessionData = this.parseSessionData(dataStr); + valid = !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp; + break; + case GameDataType.RUN_HISTORY: + const data = JSON.parse(dataStr); + const keys = Object.keys(data); + dataName = i18next.t("menuUiHandler:RUN_HISTORY").toLowerCase(); + keys.forEach((key) => { + const entryKeys = Object.keys(data[key]); + valid = [ "isFavorite", "isVictory", "entry" ].every(v => entryKeys.includes(v)) && entryKeys.length === 3; + }); + break; + case GameDataType.SETTINGS: + case GameDataType.TUTORIALS: + valid = true; + break; } } catch (ex) { console.error(ex); diff --git a/src/system/settings/settings-gamepad.ts b/src/system/settings/settings-gamepad.ts index c96ec36f9a5..322b2baac9e 100644 --- a/src/system/settings/settings-gamepad.ts +++ b/src/system/settings/settings-gamepad.ts @@ -82,66 +82,66 @@ export const settingGamepadBlackList = [ export function setSettingGamepad(scene: BattleScene, setting: SettingGamepad, value: integer): boolean { switch (setting) { - case SettingGamepad.Gamepad_Support: + case SettingGamepad.Gamepad_Support: // if we change the value of the gamepad support, we call a method in the inputController to // activate or deactivate the controller listener - scene.inputController.setGamepadSupport(settingGamepadOptions[setting][value] !== "Disabled"); - break; - case SettingGamepad.Button_Action: - case SettingGamepad.Button_Cancel: - case SettingGamepad.Button_Menu: - case SettingGamepad.Button_Stats: - case SettingGamepad.Button_Cycle_Shiny: - case SettingGamepad.Button_Cycle_Form: - case SettingGamepad.Button_Cycle_Gender: - case SettingGamepad.Button_Cycle_Ability: - case SettingGamepad.Button_Cycle_Nature: - case SettingGamepad.Button_Cycle_Variant: - case SettingGamepad.Button_Speed_Up: - case SettingGamepad.Button_Slow_Down: - case SettingGamepad.Button_Submit: - if (value) { - if (scene.ui) { - const cancelHandler = (success: boolean = false) : boolean => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); - return success; - }; - scene.ui.setOverlayMode(Mode.GAMEPAD_BINDING, { - target: setting, - cancelHandler: cancelHandler, - }); + scene.inputController.setGamepadSupport(settingGamepadOptions[setting][value] !== "Disabled"); + break; + case SettingGamepad.Button_Action: + case SettingGamepad.Button_Cancel: + case SettingGamepad.Button_Menu: + case SettingGamepad.Button_Stats: + case SettingGamepad.Button_Cycle_Shiny: + case SettingGamepad.Button_Cycle_Form: + case SettingGamepad.Button_Cycle_Gender: + case SettingGamepad.Button_Cycle_Ability: + case SettingGamepad.Button_Cycle_Nature: + case SettingGamepad.Button_Cycle_Variant: + case SettingGamepad.Button_Speed_Up: + case SettingGamepad.Button_Slow_Down: + case SettingGamepad.Button_Submit: + if (value) { + if (scene.ui) { + const cancelHandler = (success: boolean = false) : boolean => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + return success; + }; + scene.ui.setOverlayMode(Mode.GAMEPAD_BINDING, { + target: setting, + cancelHandler: cancelHandler, + }); + } } - } - break; - case SettingGamepad.Controller: - if (value) { - const gp = scene.inputController.getGamepadsName(); - if (scene.ui && gp) { - const cancelHandler = () => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsGamepadUiHandler).setOptionCursor(Object.values(SettingGamepad).indexOf(SettingGamepad.Controller), 0, true); - (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + break; + case SettingGamepad.Controller: + if (value) { + const gp = scene.inputController.getGamepadsName(); + if (scene.ui && gp) { + const cancelHandler = () => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsGamepadUiHandler).setOptionCursor(Object.values(SettingGamepad).indexOf(SettingGamepad.Controller), 0, true); + (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + return false; + }; + const changeGamepadHandler = (gamepad: string) => { + scene.inputController.setChosenGamepad(gamepad); + cancelHandler(); + return true; + }; + scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + options: [ ...gp.map((g: string) => ({ + label: truncateString(g, 30), // Truncate the gamepad name for display + handler: () => changeGamepadHandler(g) + })), { + label: "Cancel", + handler: cancelHandler, + }] + }); return false; - }; - const changeGamepadHandler = (gamepad: string) => { - scene.inputController.setChosenGamepad(gamepad); - cancelHandler(); - return true; - }; - scene.ui.setOverlayMode(Mode.OPTION_SELECT, { - options: [ ...gp.map((g: string) => ({ - label: truncateString(g, 30), // Truncate the gamepad name for display - handler: () => changeGamepadHandler(g) - })), { - label: "Cancel", - handler: cancelHandler, - }] - }); - return false; + } } - } - break; + break; } return true; diff --git a/src/system/settings/settings-keyboard.ts b/src/system/settings/settings-keyboard.ts index d7cb994aca8..97990f61c86 100644 --- a/src/system/settings/settings-keyboard.ts +++ b/src/system/settings/settings-keyboard.ts @@ -135,53 +135,53 @@ export const settingKeyboardBlackList = [ export function setSettingKeyboard(scene: BattleScene, setting: SettingKeyboard, value: integer): boolean { switch (setting) { - case SettingKeyboard.Button_Up: - case SettingKeyboard.Button_Down: - case SettingKeyboard.Button_Left: - case SettingKeyboard.Button_Right: - case SettingKeyboard.Button_Action: - case SettingKeyboard.Button_Cancel: - case SettingKeyboard.Button_Menu: - case SettingKeyboard.Button_Stats: - case SettingKeyboard.Button_Cycle_Shiny: - case SettingKeyboard.Button_Cycle_Form: - case SettingKeyboard.Button_Cycle_Gender: - case SettingKeyboard.Button_Cycle_Ability: - case SettingKeyboard.Button_Cycle_Nature: - case SettingKeyboard.Button_Cycle_Variant: - case SettingKeyboard.Button_Speed_Up: - case SettingKeyboard.Button_Slow_Down: - case SettingKeyboard.Alt_Button_Up: - case SettingKeyboard.Alt_Button_Down: - case SettingKeyboard.Alt_Button_Left: - case SettingKeyboard.Alt_Button_Right: - case SettingKeyboard.Alt_Button_Action: - case SettingKeyboard.Alt_Button_Cancel: - case SettingKeyboard.Alt_Button_Menu: - case SettingKeyboard.Alt_Button_Stats: - case SettingKeyboard.Alt_Button_Cycle_Shiny: - case SettingKeyboard.Alt_Button_Cycle_Form: - case SettingKeyboard.Alt_Button_Cycle_Gender: - case SettingKeyboard.Alt_Button_Cycle_Ability: - case SettingKeyboard.Alt_Button_Cycle_Nature: - case SettingKeyboard.Alt_Button_Cycle_Variant: - case SettingKeyboard.Alt_Button_Speed_Up: - case SettingKeyboard.Alt_Button_Slow_Down: - case SettingKeyboard.Alt_Button_Submit: - if (value) { - if (scene.ui) { - const cancelHandler = (success: boolean = false) : boolean => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); - return success; - }; - scene.ui.setOverlayMode(Mode.KEYBOARD_BINDING, { - target: setting, - cancelHandler: cancelHandler, - }); + case SettingKeyboard.Button_Up: + case SettingKeyboard.Button_Down: + case SettingKeyboard.Button_Left: + case SettingKeyboard.Button_Right: + case SettingKeyboard.Button_Action: + case SettingKeyboard.Button_Cancel: + case SettingKeyboard.Button_Menu: + case SettingKeyboard.Button_Stats: + case SettingKeyboard.Button_Cycle_Shiny: + case SettingKeyboard.Button_Cycle_Form: + case SettingKeyboard.Button_Cycle_Gender: + case SettingKeyboard.Button_Cycle_Ability: + case SettingKeyboard.Button_Cycle_Nature: + case SettingKeyboard.Button_Cycle_Variant: + case SettingKeyboard.Button_Speed_Up: + case SettingKeyboard.Button_Slow_Down: + case SettingKeyboard.Alt_Button_Up: + case SettingKeyboard.Alt_Button_Down: + case SettingKeyboard.Alt_Button_Left: + case SettingKeyboard.Alt_Button_Right: + case SettingKeyboard.Alt_Button_Action: + case SettingKeyboard.Alt_Button_Cancel: + case SettingKeyboard.Alt_Button_Menu: + case SettingKeyboard.Alt_Button_Stats: + case SettingKeyboard.Alt_Button_Cycle_Shiny: + case SettingKeyboard.Alt_Button_Cycle_Form: + case SettingKeyboard.Alt_Button_Cycle_Gender: + case SettingKeyboard.Alt_Button_Cycle_Ability: + case SettingKeyboard.Alt_Button_Cycle_Nature: + case SettingKeyboard.Alt_Button_Cycle_Variant: + case SettingKeyboard.Alt_Button_Speed_Up: + case SettingKeyboard.Alt_Button_Slow_Down: + case SettingKeyboard.Alt_Button_Submit: + if (value) { + if (scene.ui) { + const cancelHandler = (success: boolean = false) : boolean => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); + return success; + }; + scene.ui.setOverlayMode(Mode.KEYBOARD_BINDING, { + target: setting, + cancelHandler: cancelHandler, + }); + } } - } - break; + break; // case SettingKeyboard.Default_Layout: // if (value && scene.ui) { // const cancelHandler = () => { diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 66021845c29..be440d5d93e 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -76,16 +76,16 @@ const SHOP_CURSOR_TARGET_OPTIONS: SettingOption[] = [ const shopCursorTargetIndexMap = SHOP_CURSOR_TARGET_OPTIONS.map(option => { switch (option.value) { - case "Rewards": - return ShopCursorTarget.REWARDS; - case "Shop": - return ShopCursorTarget.SHOP; - case "Reroll": - return ShopCursorTarget.REROLL; - case "Check Team": - return ShopCursorTarget.CHECK_TEAM; - default: - throw new Error(`Unknown value: ${option.value}`); + case "Rewards": + return ShopCursorTarget.REWARDS; + case "Shop": + return ShopCursorTarget.SHOP; + case "Reroll": + return ShopCursorTarget.REROLL; + case "Check Team": + return ShopCursorTarget.CHECK_TEAM; + default: + throw new Error(`Unknown value: ${option.value}`); } }); @@ -699,226 +699,226 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): return false; } switch (Setting[index].key) { - case SettingKeys.Game_Speed: - scene.gameSpeed = parseFloat(Setting[index].options[value].value.replace("x", "")); - break; - case SettingKeys.Master_Volume: - scene.masterVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case SettingKeys.BGM_Volume: - scene.bgmVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case SettingKeys.Field_Volume: - scene.fieldVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case SettingKeys.SE_Volume: - scene.seVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case SettingKeys.UI_Volume: - scene.uiVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - break; - case SettingKeys.Music_Preference: - scene.musicPreference = value; - break; - case SettingKeys.Damage_Numbers: - scene.damageNumbersMode = value; - break; - case SettingKeys.UI_Theme: - scene.uiTheme = value; - break; - case SettingKeys.Window_Type: - updateWindowType(scene, parseInt(Setting[index].options[value].value)); - break; - case SettingKeys.Tutorials: - scene.enableTutorials = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Move_Info: - scene.enableMoveInfo = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Enable_Retries: - scene.enableRetries = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Hide_IVs: - scene.hideIvs = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Skip_Seen_Dialogues: - scene.skipSeenDialogues = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Egg_Skip: - scene.eggSkipPreference = value; - break; - case SettingKeys.Battle_Style: - scene.battleStyle = value; - break; - case SettingKeys.Show_BGM_Bar: - scene.showBgmBar = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Candy_Upgrade_Notification: - if (scene.candyUpgradeNotification === value) { + case SettingKeys.Game_Speed: + scene.gameSpeed = parseFloat(Setting[index].options[value].value.replace("x", "")); break; - } - scene.candyUpgradeNotification = value; - scene.eventTarget.dispatchEvent(new CandyUpgradeNotificationChangedEvent(value)); - break; - case SettingKeys.Candy_Upgrade_Display: - scene.candyUpgradeDisplay = value; - case SettingKeys.Money_Format: - switch (Setting[index].options[value].value) { - case "Normal": - scene.moneyFormat = MoneyFormat.NORMAL; + case SettingKeys.Master_Volume: + scene.masterVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + scene.updateSoundVolume(); break; - case "Abbreviated": - scene.moneyFormat = MoneyFormat.ABBREVIATED; + case SettingKeys.BGM_Volume: + scene.bgmVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + scene.updateSoundVolume(); break; - } - scene.updateMoneyText(false); - break; - case SettingKeys.Sprite_Set: - scene.experimentalSprites = !!value; - if (value) { - scene.initExpSprites(); - } - break; - case SettingKeys.Move_Animations: - scene.moveAnimations = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Show_Moveset_Flyout: - scene.showMovesetFlyout = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Show_Arena_Flyout: - scene.showArenaFlyout = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Show_Time_Of_Day_Widget: - scene.showTimeOfDayWidget = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Time_Of_Day_Animation: - scene.timeOfDayAnimation = Setting[index].options[value].value === "Bounce" ? EaseType.BOUNCE : EaseType.BACK; - break; - case SettingKeys.Show_Stats_on_Level_Up: - scene.showLevelUpStats = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Shop_Cursor_Target: - const selectedValue = shopCursorTargetIndexMap[value]; - scene.shopCursorTarget = selectedValue; - break; - case SettingKeys.EXP_Gains_Speed: - scene.expGainsSpeed = value; - break; - case SettingKeys.EXP_Party_Display: - scene.expParty = value; - break; - case SettingKeys.HP_Bar_Speed: - scene.hpBarSpeed = value; - break; - case SettingKeys.Fusion_Palette_Swaps: - scene.fusionPaletteSwaps = !!value; - break; - case SettingKeys.Player_Gender: - if (scene.gameData) { - const female = Setting[index].options[value].value === "Girl"; - scene.gameData.gender = female ? PlayerGender.FEMALE : PlayerGender.MALE; - scene.trainer.setTexture(scene.trainer.texture.key.replace(female ? "m" : "f", female ? "f" : "m")); - } else { - return false; - } - break; - case SettingKeys.Touch_Controls: - scene.enableTouchControls = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); - const touchControls = document.getElementById("touchControls"); - if (touchControls) { - touchControls.classList.toggle("visible", scene.enableTouchControls); - } - break; - case SettingKeys.Vibration: - scene.enableVibration = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); - break; - case SettingKeys.Type_Hints: - scene.typeHints = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Language: - if (value) { - if (scene.ui) { - const cancelHandler = () => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(0, 0, true); - }; - const changeLocaleHandler = (locale: string): boolean => { - try { - i18next.changeLanguage(locale); - localStorage.setItem("prLang", locale); - cancelHandler(); - // Reload the whole game to apply the new locale since also some constants are translated - window.location.reload(); - return true; - } catch (error) { - console.error("Error changing locale:", error); - return false; - } - }; - scene.ui.setOverlayMode(Mode.OPTION_SELECT, { - options: [ - { - label: "English", - handler: () => changeLocaleHandler("en") - }, - { - label: "Español", - handler: () => changeLocaleHandler("es") - }, - { - label: "Italiano", - handler: () => changeLocaleHandler("it") - }, - { - label: "Français", - handler: () => changeLocaleHandler("fr") - }, - { - label: "Deutsch", - handler: () => changeLocaleHandler("de") - }, - { - label: "Português (BR)", - handler: () => changeLocaleHandler("pt-BR") - }, - { - label: "简体中文", - handler: () => changeLocaleHandler("zh-CN") - }, - { - label: "繁體中文", - handler: () => changeLocaleHandler("zh-TW") - }, - { - label: "한국어", - handler: () => changeLocaleHandler("ko") - }, - { - label: "日本語", - handler: () => changeLocaleHandler("ja") - }, - // { - // label: "Català", - // handler: () => changeLocaleHandler("ca-ES") - // }, - { - label: i18next.t("settings:back"), - handler: () => cancelHandler() - } - ], - maxOptions: 7 - }); + case SettingKeys.Field_Volume: + scene.fieldVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + scene.updateSoundVolume(); + break; + case SettingKeys.SE_Volume: + scene.seVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + scene.updateSoundVolume(); + break; + case SettingKeys.UI_Volume: + scene.uiVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + break; + case SettingKeys.Music_Preference: + scene.musicPreference = value; + break; + case SettingKeys.Damage_Numbers: + scene.damageNumbersMode = value; + break; + case SettingKeys.UI_Theme: + scene.uiTheme = value; + break; + case SettingKeys.Window_Type: + updateWindowType(scene, parseInt(Setting[index].options[value].value)); + break; + case SettingKeys.Tutorials: + scene.enableTutorials = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Move_Info: + scene.enableMoveInfo = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Enable_Retries: + scene.enableRetries = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Hide_IVs: + scene.hideIvs = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Skip_Seen_Dialogues: + scene.skipSeenDialogues = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Egg_Skip: + scene.eggSkipPreference = value; + break; + case SettingKeys.Battle_Style: + scene.battleStyle = value; + break; + case SettingKeys.Show_BGM_Bar: + scene.showBgmBar = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Candy_Upgrade_Notification: + if (scene.candyUpgradeNotification === value) { + break; + } + scene.candyUpgradeNotification = value; + scene.eventTarget.dispatchEvent(new CandyUpgradeNotificationChangedEvent(value)); + break; + case SettingKeys.Candy_Upgrade_Display: + scene.candyUpgradeDisplay = value; + case SettingKeys.Money_Format: + switch (Setting[index].options[value].value) { + case "Normal": + scene.moneyFormat = MoneyFormat.NORMAL; + break; + case "Abbreviated": + scene.moneyFormat = MoneyFormat.ABBREVIATED; + break; + } + scene.updateMoneyText(false); + break; + case SettingKeys.Sprite_Set: + scene.experimentalSprites = !!value; + if (value) { + scene.initExpSprites(); + } + break; + case SettingKeys.Move_Animations: + scene.moveAnimations = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Show_Moveset_Flyout: + scene.showMovesetFlyout = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Show_Arena_Flyout: + scene.showArenaFlyout = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Show_Time_Of_Day_Widget: + scene.showTimeOfDayWidget = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Time_Of_Day_Animation: + scene.timeOfDayAnimation = Setting[index].options[value].value === "Bounce" ? EaseType.BOUNCE : EaseType.BACK; + break; + case SettingKeys.Show_Stats_on_Level_Up: + scene.showLevelUpStats = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Shop_Cursor_Target: + const selectedValue = shopCursorTargetIndexMap[value]; + scene.shopCursorTarget = selectedValue; + break; + case SettingKeys.EXP_Gains_Speed: + scene.expGainsSpeed = value; + break; + case SettingKeys.EXP_Party_Display: + scene.expParty = value; + break; + case SettingKeys.HP_Bar_Speed: + scene.hpBarSpeed = value; + break; + case SettingKeys.Fusion_Palette_Swaps: + scene.fusionPaletteSwaps = !!value; + break; + case SettingKeys.Player_Gender: + if (scene.gameData) { + const female = Setting[index].options[value].value === "Girl"; + scene.gameData.gender = female ? PlayerGender.FEMALE : PlayerGender.MALE; + scene.trainer.setTexture(scene.trainer.texture.key.replace(female ? "m" : "f", female ? "f" : "m")); + } else { return false; } - } - break; - case SettingKeys.Shop_Overlay_Opacity: - scene.updateShopOverlayOpacity(parseInt(Setting[index].options[value].value) * .01); - break; + break; + case SettingKeys.Touch_Controls: + scene.enableTouchControls = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); + const touchControls = document.getElementById("touchControls"); + if (touchControls) { + touchControls.classList.toggle("visible", scene.enableTouchControls); + } + break; + case SettingKeys.Vibration: + scene.enableVibration = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); + break; + case SettingKeys.Type_Hints: + scene.typeHints = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Language: + if (value) { + if (scene.ui) { + const cancelHandler = () => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(0, 0, true); + }; + const changeLocaleHandler = (locale: string): boolean => { + try { + i18next.changeLanguage(locale); + localStorage.setItem("prLang", locale); + cancelHandler(); + // Reload the whole game to apply the new locale since also some constants are translated + window.location.reload(); + return true; + } catch (error) { + console.error("Error changing locale:", error); + return false; + } + }; + scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + options: [ + { + label: "English", + handler: () => changeLocaleHandler("en") + }, + { + label: "Español", + handler: () => changeLocaleHandler("es") + }, + { + label: "Italiano", + handler: () => changeLocaleHandler("it") + }, + { + label: "Français", + handler: () => changeLocaleHandler("fr") + }, + { + label: "Deutsch", + handler: () => changeLocaleHandler("de") + }, + { + label: "Português (BR)", + handler: () => changeLocaleHandler("pt-BR") + }, + { + label: "简体中文", + handler: () => changeLocaleHandler("zh-CN") + }, + { + label: "繁體中文", + handler: () => changeLocaleHandler("zh-TW") + }, + { + label: "한국어", + handler: () => changeLocaleHandler("ko") + }, + { + label: "日本語", + handler: () => changeLocaleHandler("ja") + }, + // { + // label: "Català", + // handler: () => changeLocaleHandler("ca-ES") + // }, + { + label: i18next.t("settings:back"), + handler: () => cancelHandler() + } + ], + maxOptions: 7 + }); + return false; + } + } + break; + case SettingKeys.Shop_Overlay_Opacity: + scene.updateShopOverlayOpacity(parseInt(Setting[index].options[value].value) * .01); + break; } return true; diff --git a/src/system/unlockables.ts b/src/system/unlockables.ts index 983909373fd..0a666e2c755 100644 --- a/src/system/unlockables.ts +++ b/src/system/unlockables.ts @@ -10,13 +10,13 @@ export enum Unlockables { export function getUnlockableName(unlockable: Unlockables) { switch (unlockable) { - case Unlockables.ENDLESS_MODE: - return `${GameMode.getModeName(GameModes.ENDLESS)} Mode`; - case Unlockables.MINI_BLACK_HOLE: - return i18next.t("modifierType:ModifierType.MINI_BLACK_HOLE.name"); - case Unlockables.SPLICED_ENDLESS_MODE: - return `${GameMode.getModeName(GameModes.SPLICED_ENDLESS)} Mode`; - case Unlockables.EVIOLITE: - return i18next.t("modifierType:ModifierType.EVIOLITE.name"); + case Unlockables.ENDLESS_MODE: + return `${GameMode.getModeName(GameModes.ENDLESS)} Mode`; + case Unlockables.MINI_BLACK_HOLE: + return i18next.t("modifierType:ModifierType.MINI_BLACK_HOLE.name"); + case Unlockables.SPLICED_ENDLESS_MODE: + return `${GameMode.getModeName(GameModes.SPLICED_ENDLESS)} Mode`; + case Unlockables.EVIOLITE: + return i18next.t("modifierType:ModifierType.EVIOLITE.name"); } } diff --git a/src/system/version_migration/versions/v1_0_4.ts b/src/system/version_migration/versions/v1_0_4.ts index c20e2a281e7..f16b6bcb6bb 100644 --- a/src/system/version_migration/versions/v1_0_4.ts +++ b/src/system/version_migration/versions/v1_0_4.ts @@ -108,15 +108,15 @@ export const sessionMigrators = [ } else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) { let maxBattles: number; switch (m.typeId) { - case "MAX_LURE": - maxBattles = 30; - break; - case "SUPER_LURE": - maxBattles = 15; - break; - default: - maxBattles = 10; - break; + case "MAX_LURE": + maxBattles = 30; + break; + case "SUPER_LURE": + maxBattles = 15; + break; + default: + maxBattles = 10; + break; } // From [ battlesLeft ] to [ maxBattles, battleCount ] diff --git a/src/system/voucher.ts b/src/system/voucher.ts index aca7b9fc2f2..b38fd528c9f 100644 --- a/src/system/voucher.ts +++ b/src/system/voucher.ts @@ -45,41 +45,41 @@ export class Voucher { getTier(): AchvTier { switch (this.voucherType) { - case VoucherType.REGULAR: - return AchvTier.COMMON; - case VoucherType.PLUS: - return AchvTier.GREAT; - case VoucherType.PREMIUM: - return AchvTier.ULTRA; - case VoucherType.GOLDEN: - return AchvTier.ROGUE; + case VoucherType.REGULAR: + return AchvTier.COMMON; + case VoucherType.PLUS: + return AchvTier.GREAT; + case VoucherType.PREMIUM: + return AchvTier.ULTRA; + case VoucherType.GOLDEN: + return AchvTier.ROGUE; } } } export function getVoucherTypeName(voucherType: VoucherType): string { switch (voucherType) { - case VoucherType.REGULAR: - return i18next.t("voucher:eggVoucher"); - case VoucherType.PLUS: - return i18next.t("voucher:eggVoucherPlus"); - case VoucherType.PREMIUM: - return i18next.t("voucher:eggVoucherPremium"); - case VoucherType.GOLDEN: - return i18next.t("voucher:eggVoucherGold"); + case VoucherType.REGULAR: + return i18next.t("voucher:eggVoucher"); + case VoucherType.PLUS: + return i18next.t("voucher:eggVoucherPlus"); + case VoucherType.PREMIUM: + return i18next.t("voucher:eggVoucherPremium"); + case VoucherType.GOLDEN: + return i18next.t("voucher:eggVoucherGold"); } } export function getVoucherTypeIcon(voucherType: VoucherType): string { switch (voucherType) { - case VoucherType.REGULAR: - return "coupon"; - case VoucherType.PLUS: - return "pair_of_tickets"; - case VoucherType.PREMIUM: - return "mystic_ticket"; - case VoucherType.GOLDEN: - return "golden_mystic_ticket"; + case VoucherType.REGULAR: + return "coupon"; + case VoucherType.PLUS: + return "pair_of_tickets"; + case VoucherType.PREMIUM: + return "mystic_ticket"; + case VoucherType.GOLDEN: + return "golden_mystic_ticket"; } } diff --git a/src/test/mystery-encounter/encounter-test-utils.ts b/src/test/mystery-encounter/encounter-test-utils.ts index 9fb2504c02b..f95a442d4c2 100644 --- a/src/test/mystery-encounter/encounter-test-utils.ts +++ b/src/test/mystery-encounter/encounter-test-utils.ts @@ -97,20 +97,20 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN uiHandler.unblockInput(); // input are blocked by 1s to prevent accidental input. Tests need to handle that switch (optionNo) { - default: - case 1: + default: + case 1: // no movement needed. Default cursor position - break; - case 2: - uiHandler.processInput(Button.RIGHT); - break; - case 3: - uiHandler.processInput(Button.DOWN); - break; - case 4: - uiHandler.processInput(Button.RIGHT); - uiHandler.processInput(Button.DOWN); - break; + break; + case 2: + uiHandler.processInput(Button.RIGHT); + break; + case 3: + uiHandler.processInput(Button.DOWN); + break; + case 4: + uiHandler.processInput(Button.RIGHT); + uiHandler.processInput(Button.DOWN); + break; } if (!isNullOrUndefined(secondaryOptionSelect?.pokemonNo)) { diff --git a/src/touch-controls.ts b/src/touch-controls.ts index 786d1203d12..93032ce59fe 100644 --- a/src/touch-controls.ts +++ b/src/touch-controls.ts @@ -122,20 +122,20 @@ export default class TouchControl { const button = Button[key]; switch (eventType) { - case "keydown": - this.events.emit("input_down", { - controller_type: "keyboard", - button: button, - isTouch: true - }); - break; - case "keyup": - this.events.emit("input_up", { - controller_type: "keyboard", - button: button, - isTouch: true - }); - break; + case "keydown": + this.events.emit("input_down", { + controller_type: "keyboard", + button: button, + isTouch: true + }); + break; + case "keyup": + this.events.emit("input_up", { + controller_type: "keyboard", + button: button, + isTouch: true + }); + break; } return true; } diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index 9ea1276352a..92b1653df3d 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -168,26 +168,26 @@ export class UiInputs { return; } switch (this.scene.ui?.getMode()) { - case Mode.MESSAGE: - const messageHandler = this.scene.ui.getHandler(); - if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) { + case Mode.MESSAGE: + const messageHandler = this.scene.ui.getHandler(); + if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) { + return; + } + case Mode.TITLE: + case Mode.COMMAND: + case Mode.MODIFIER_SELECT: + case Mode.MYSTERY_ENCOUNTER: + this.scene.ui.setOverlayMode(Mode.MENU); + break; + case Mode.STARTER_SELECT: + this.buttonTouch(); + break; + case Mode.MENU: + this.scene.ui.revertMode(); + this.scene.playSound("ui/select"); + break; + default: return; - } - case Mode.TITLE: - case Mode.COMMAND: - case Mode.MODIFIER_SELECT: - case Mode.MYSTERY_ENCOUNTER: - this.scene.ui.setOverlayMode(Mode.MENU); - break; - case Mode.STARTER_SELECT: - this.buttonTouch(); - break; - case Mode.MENU: - this.scene.ui.revertMode(); - this.scene.playSound("ui/select"); - break; - default: - return; } } diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index a12ffbc46bd..01fc5b00014 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -222,20 +222,20 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { } } else { switch (button) { - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else if (this.cursor === 0) { - success = this.setCursor(options.length - 1); - } - break; - case Button.DOWN: - if (this.cursor < options.length - 1) { - success = this.setCursor(this.cursor + 1); - } else { - success = this.setCursor(0); - } - break; + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else if (this.cursor === 0) { + success = this.setCursor(options.length - 1); + } + break; + case Button.DOWN: + if (this.cursor < options.length - 1) { + success = this.setCursor(this.cursor + 1); + } else { + success = this.setCursor(0); + } + break; } if (this.config?.supportHover) { // handle hover code if the element supports hover-handlers and the option has the optional hover-handler set. diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts index 4e0e2feea81..a1ced6145eb 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -245,51 +245,51 @@ export default class AchvsUiHandler extends MessageUiHandler { const rowIndex = Math.floor(this.cursor / this.COLS); const itemOffset = (this.scrollCursor * this.COLS); switch (button) { - case Button.UP: - if (this.cursor < this.COLS) { - if (this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1); - } else { + case Button.UP: + if (this.cursor < this.COLS) { + if (this.scrollCursor) { + success = this.setScrollCursor(this.scrollCursor - 1); + } else { // Wrap around to the last row - success = this.setScrollCursor(Math.ceil(this.currentTotal / this.COLS) - this.ROWS); - let newCursorIndex = this.cursor + (this.ROWS - 1) * this.COLS; - if (newCursorIndex > this.currentTotal - this.scrollCursor * this.COLS - 1) { - newCursorIndex -= this.COLS; + success = this.setScrollCursor(Math.ceil(this.currentTotal / this.COLS) - this.ROWS); + let newCursorIndex = this.cursor + (this.ROWS - 1) * this.COLS; + if (newCursorIndex > this.currentTotal - this.scrollCursor * this.COLS - 1) { + newCursorIndex -= this.COLS; + } + success = success && this.setCursor(newCursorIndex); } - success = success && this.setCursor(newCursorIndex); - } - } else { - success = this.setCursor(this.cursor - this.COLS); - } - break; - case Button.DOWN: - const canMoveDown = itemOffset + 1 < this.currentTotal; - if (rowIndex >= this.ROWS - 1) { - if (this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS && canMoveDown) { - // scroll down one row - success = this.setScrollCursor(this.scrollCursor + 1); } else { - // wrap back to the first row - success = this.setScrollCursor(0) && this.setCursor(this.cursor % this.COLS); + success = this.setCursor(this.cursor - this.COLS); } - } else if (canMoveDown) { - success = this.setCursor(Math.min(this.cursor + this.COLS, this.currentTotal - itemOffset - 1)); - } - break; - case Button.LEFT: - if (this.cursor % this.COLS === 0) { - success = this.setCursor(Math.min(this.cursor + this.COLS - 1, this.currentTotal - itemOffset - 1)); - } else { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.RIGHT: - if ((this.cursor + 1) % this.COLS === 0 || (this.cursor + itemOffset) === (this.currentTotal - 1)) { - success = this.setCursor(this.cursor - this.cursor % this.COLS); - } else { - success = this.setCursor(this.cursor + 1); - } - break; + break; + case Button.DOWN: + const canMoveDown = itemOffset + 1 < this.currentTotal; + if (rowIndex >= this.ROWS - 1) { + if (this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS && canMoveDown) { + // scroll down one row + success = this.setScrollCursor(this.scrollCursor + 1); + } else { + // wrap back to the first row + success = this.setScrollCursor(0) && this.setCursor(this.cursor % this.COLS); + } + } else if (canMoveDown) { + success = this.setCursor(Math.min(this.cursor + this.COLS, this.currentTotal - itemOffset - 1)); + } + break; + case Button.LEFT: + if (this.cursor % this.COLS === 0) { + success = this.setCursor(Math.min(this.cursor + this.COLS - 1, this.currentTotal - itemOffset - 1)); + } else { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.RIGHT: + if ((this.cursor + 1) % this.COLS === 0 || (this.cursor + itemOffset) === (this.currentTotal - 1)) { + success = this.setCursor(this.cursor - this.cursor % this.COLS); + } else { + success = this.setCursor(this.cursor + 1); + } + break; } } @@ -316,22 +316,22 @@ export default class AchvsUiHandler extends MessageUiHandler { if (update || pageChange) { switch (this.currentPage) { - case Page.ACHIEVEMENTS: - if (pageChange) { - this.titleBg.width = 174; - this.titleText.x = this.titleBg.width / 2; - this.scoreContainer.setVisible(true); - } - this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.COLS]]); - break; - case Page.VOUCHERS: - if (pageChange) { - this.titleBg.width = 220; - this.titleText.x = this.titleBg.width / 2; - this.scoreContainer.setVisible(false); - } - this.showVoucher(vouchers[Object.keys(vouchers)[cursor + this.scrollCursor * this.COLS]]); - break; + case Page.ACHIEVEMENTS: + if (pageChange) { + this.titleBg.width = 174; + this.titleText.x = this.titleBg.width / 2; + this.scoreContainer.setVisible(true); + } + this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.COLS]]); + break; + case Page.VOUCHERS: + if (pageChange) { + this.titleBg.width = 220; + this.titleText.x = this.titleBg.width / 2; + this.scoreContainer.setVisible(false); + } + this.showVoucher(vouchers[Object.keys(vouchers)[cursor + this.scrollCursor * this.COLS]]); + break; } } return ret; @@ -358,14 +358,14 @@ export default class AchvsUiHandler extends MessageUiHandler { } switch (this.currentPage) { - case Page.ACHIEVEMENTS: - this.updateAchvIcons(); - this.showAchv(achvs[Object.keys(achvs)[this.cursor + this.scrollCursor * this.COLS]]); - break; - case Page.VOUCHERS: - this.updateVoucherIcons(); - this.showVoucher(vouchers[Object.keys(vouchers)[this.cursor + this.scrollCursor * this.COLS]]); - break; + case Page.ACHIEVEMENTS: + this.updateAchvIcons(); + this.showAchv(achvs[Object.keys(achvs)[this.cursor + this.scrollCursor * this.COLS]]); + break; + case Page.VOUCHERS: + this.updateVoucherIcons(); + this.showVoucher(vouchers[Object.keys(vouchers)[this.cursor + this.scrollCursor * this.COLS]]); + break; } return true; } diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index 0c00d3403b6..a82f97244cd 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -215,20 +215,20 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { // Creates a proxy object to decide which text object needs to be updated let textObject: Phaser.GameObjects.Text; switch (fieldEffectInfo.effecType) { - case ArenaEffectType.PLAYER: - textObject = this.flyoutTextPlayer; - break; + case ArenaEffectType.PLAYER: + textObject = this.flyoutTextPlayer; + break; - case ArenaEffectType.WEATHER: - case ArenaEffectType.TERRAIN: - case ArenaEffectType.FIELD: - textObject = this.flyoutTextField; + case ArenaEffectType.WEATHER: + case ArenaEffectType.TERRAIN: + case ArenaEffectType.FIELD: + textObject = this.flyoutTextField; - break; + break; - case ArenaEffectType.ENEMY: - textObject = this.flyoutTextEnemy; - break; + case ArenaEffectType.ENEMY: + textObject = this.flyoutTextEnemy; + break; } textObject.text += fieldEffectInfo.name; @@ -253,81 +253,81 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { let foundIndex: number; switch (arenaEffectChangedEvent.constructor) { - case TagAddedEvent: - const tagAddedEvent = arenaEffectChangedEvent as TagAddedEvent; - const isArenaTrapTag = this.battleScene.arena.getTag(tagAddedEvent.arenaTagType) instanceof ArenaTrapTag; - let arenaEffectType: ArenaEffectType; + case TagAddedEvent: + const tagAddedEvent = arenaEffectChangedEvent as TagAddedEvent; + const isArenaTrapTag = this.battleScene.arena.getTag(tagAddedEvent.arenaTagType) instanceof ArenaTrapTag; + let arenaEffectType: ArenaEffectType; - if (tagAddedEvent.arenaTagSide === ArenaTagSide.BOTH) { - arenaEffectType = ArenaEffectType.FIELD; - } else if (tagAddedEvent.arenaTagSide === ArenaTagSide.PLAYER) { - arenaEffectType = ArenaEffectType.PLAYER; - } else { - arenaEffectType = ArenaEffectType.ENEMY; - } - - const existingTrapTagIndex = isArenaTrapTag ? this.fieldEffectInfo.findIndex(e => tagAddedEvent.arenaTagType === e.tagType && arenaEffectType === e.effecType) : -1; - let name: string = getFieldEffectText(ArenaTagType[tagAddedEvent.arenaTagType]); - - if (isArenaTrapTag) { - if (existingTrapTagIndex !== -1) { - const layers = tagAddedEvent.arenaTagMaxLayers > 1 ? ` (${tagAddedEvent.arenaTagLayers})` : ""; - this.fieldEffectInfo[existingTrapTagIndex].name = `${name}${layers}`; - break; - } else if (tagAddedEvent.arenaTagMaxLayers > 1) { - name = `${name} (${tagAddedEvent.arenaTagLayers})`; + if (tagAddedEvent.arenaTagSide === ArenaTagSide.BOTH) { + arenaEffectType = ArenaEffectType.FIELD; + } else if (tagAddedEvent.arenaTagSide === ArenaTagSide.PLAYER) { + arenaEffectType = ArenaEffectType.PLAYER; + } else { + arenaEffectType = ArenaEffectType.ENEMY; } - } - this.fieldEffectInfo.push({ - name, - effecType: arenaEffectType, - maxDuration: tagAddedEvent.duration, - duration: tagAddedEvent.duration, - tagType: tagAddedEvent.arenaTagType - }); - break; - case TagRemovedEvent: - const tagRemovedEvent = arenaEffectChangedEvent as TagRemovedEvent; - foundIndex = this.fieldEffectInfo.findIndex(info => info.tagType === tagRemovedEvent.arenaTagType); + const existingTrapTagIndex = isArenaTrapTag ? this.fieldEffectInfo.findIndex(e => tagAddedEvent.arenaTagType === e.tagType && arenaEffectType === e.effecType) : -1; + let name: string = getFieldEffectText(ArenaTagType[tagAddedEvent.arenaTagType]); - if (foundIndex !== -1) { // If the tag was being tracked, remove it - this.fieldEffectInfo.splice(foundIndex, 1); - } - break; + if (isArenaTrapTag) { + if (existingTrapTagIndex !== -1) { + const layers = tagAddedEvent.arenaTagMaxLayers > 1 ? ` (${tagAddedEvent.arenaTagLayers})` : ""; + this.fieldEffectInfo[existingTrapTagIndex].name = `${name}${layers}`; + break; + } else if (tagAddedEvent.arenaTagMaxLayers > 1) { + name = `${name} (${tagAddedEvent.arenaTagLayers})`; + } + } - case WeatherChangedEvent: - case TerrainChangedEvent: - const fieldEffectChangedEvent = arenaEffectChangedEvent as WeatherChangedEvent | TerrainChangedEvent; + this.fieldEffectInfo.push({ + name, + effecType: arenaEffectType, + maxDuration: tagAddedEvent.duration, + duration: tagAddedEvent.duration, + tagType: tagAddedEvent.arenaTagType + }); + break; + case TagRemovedEvent: + const tagRemovedEvent = arenaEffectChangedEvent as TagRemovedEvent; + foundIndex = this.fieldEffectInfo.findIndex(info => info.tagType === tagRemovedEvent.arenaTagType); - // Stores the old Weather/Terrain name in case it's in the array already - const oldName = + if (foundIndex !== -1) { // If the tag was being tracked, remove it + this.fieldEffectInfo.splice(foundIndex, 1); + } + break; + + case WeatherChangedEvent: + case TerrainChangedEvent: + const fieldEffectChangedEvent = arenaEffectChangedEvent as WeatherChangedEvent | TerrainChangedEvent; + + // Stores the old Weather/Terrain name in case it's in the array already + const oldName = getFieldEffectText(fieldEffectChangedEvent instanceof WeatherChangedEvent ? WeatherType[fieldEffectChangedEvent.oldWeatherType] : TerrainType[fieldEffectChangedEvent.oldTerrainType]); - // Stores the new Weather/Terrain info - const newInfo = { - name: + // Stores the new Weather/Terrain info + const newInfo = { + name: getFieldEffectText(fieldEffectChangedEvent instanceof WeatherChangedEvent ? WeatherType[fieldEffectChangedEvent.newWeatherType] : TerrainType[fieldEffectChangedEvent.newTerrainType]), - effecType: fieldEffectChangedEvent instanceof WeatherChangedEvent - ? ArenaEffectType.WEATHER - : ArenaEffectType.TERRAIN, - maxDuration: fieldEffectChangedEvent.duration, - duration: fieldEffectChangedEvent.duration }; + effecType: fieldEffectChangedEvent instanceof WeatherChangedEvent + ? ArenaEffectType.WEATHER + : ArenaEffectType.TERRAIN, + maxDuration: fieldEffectChangedEvent.duration, + duration: fieldEffectChangedEvent.duration }; - foundIndex = this.fieldEffectInfo.findIndex(info => [ newInfo.name, oldName ].includes(info.name)); - if (foundIndex === -1) { - if (newInfo.name !== undefined) { - this.fieldEffectInfo.push(newInfo); // Adds the info to the array if it doesn't already exist and is defined + foundIndex = this.fieldEffectInfo.findIndex(info => [ newInfo.name, oldName ].includes(info.name)); + if (foundIndex === -1) { + if (newInfo.name !== undefined) { + this.fieldEffectInfo.push(newInfo); // Adds the info to the array if it doesn't already exist and is defined + } + } else if (!newInfo.name) { + this.fieldEffectInfo.splice(foundIndex, 1); // Removes the old info if the new one is undefined + } else { + this.fieldEffectInfo[foundIndex] = newInfo; // Otherwise, replace the old info } - } else if (!newInfo.name) { - this.fieldEffectInfo.splice(foundIndex, 1); // Removes the old info if the new one is undefined - } else { - this.fieldEffectInfo[foundIndex] = newInfo; // Otherwise, replace the old info - } - break; + break; } this.updateFieldText(); diff --git a/src/ui/ball-ui-handler.ts b/src/ui/ball-ui-handler.ts index 0d97a79943d..9df6da36055 100644 --- a/src/ui/ball-ui-handler.ts +++ b/src/ui/ball-ui-handler.ts @@ -90,12 +90,12 @@ export default class BallUiHandler extends UiHandler { } } else { switch (button) { - case Button.UP: - success = this.setCursor(this.cursor ? this.cursor - 1 : pokeballTypeCount); - break; - case Button.DOWN: - success = this.setCursor(this.cursor < pokeballTypeCount ? this.cursor + 1 : 0); - break; + case Button.UP: + success = this.setCursor(this.cursor ? this.cursor - 1 : pokeballTypeCount); + break; + case Button.DOWN: + success = this.setCursor(this.cursor < pokeballTypeCount ? this.cursor + 1 : 0); + break; } } diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts index b2c0f1a1746..e2547a626de 100644 --- a/src/ui/challenges-select-ui-handler.ts +++ b/src/ui/challenges-select-ui-handler.ts @@ -376,66 +376,66 @@ export default class GameChallengesUiHandler extends UiHandler { } else { if (this.cursorObj?.visible && !this.startCursor.visible) { switch (button) { - case Button.UP: - if (this.cursor === 0) { - if (this.scrollCursor === 0) { + case Button.UP: + if (this.cursor === 0) { + if (this.scrollCursor === 0) { // When at the top of the menu and pressing UP, move to the bottommost item. - if (this.scene.gameMode.challenges.length > rowsToDisplay) { // If there are more than 9 challenges, scroll to the bottom + if (this.scene.gameMode.challenges.length > rowsToDisplay) { // If there are more than 9 challenges, scroll to the bottom // First, set the cursor to the last visible element, preparing for the scroll to the end. - const successA = this.setCursor(rowsToDisplay - 1); - // Then, adjust the scroll to display the bottommost elements of the menu. - const successB = this.setScrollCursor(this.scene.gameMode.challenges.length - rowsToDisplay); - success = successA && successB; // success is just there to play the little validation sound effect - } else { // If there are 9 or less challenges, just move to the bottom one - success = this.setCursor(this.scene.gameMode.challenges.length - 1); + const successA = this.setCursor(rowsToDisplay - 1); + // Then, adjust the scroll to display the bottommost elements of the menu. + const successB = this.setScrollCursor(this.scene.gameMode.challenges.length - rowsToDisplay); + success = successA && successB; // success is just there to play the little validation sound effect + } else { // If there are 9 or less challenges, just move to the bottom one + success = this.setCursor(this.scene.gameMode.challenges.length - 1); + } + } else { + success = this.setScrollCursor(this.scrollCursor - 1); } } else { - success = this.setScrollCursor(this.scrollCursor - 1); + success = this.setCursor(this.cursor - 1); } - } else { - success = this.setCursor(this.cursor - 1); - } - if (success) { - this.updateText(); - } - break; - case Button.DOWN: - if (this.cursor === rowsToDisplay - 1) { - if (this.scrollCursor < this.scene.gameMode.challenges.length - rowsToDisplay) { + if (success) { + this.updateText(); + } + break; + case Button.DOWN: + if (this.cursor === rowsToDisplay - 1) { + if (this.scrollCursor < this.scene.gameMode.challenges.length - rowsToDisplay) { // When at the bottom and pressing DOWN, scroll if possible. - success = this.setScrollCursor(this.scrollCursor + 1); - } else { + success = this.setScrollCursor(this.scrollCursor + 1); + } else { // When at the bottom of a scrolling menu and pressing DOWN, move to the topmost item. // First, set the cursor to the first visible element, preparing for the scroll to the top. - const successA = this.setCursor(0); - // Then, adjust the scroll to display the topmost elements of the menu. - const successB = this.setScrollCursor(0); - success = successA && successB; // success is just there to play the little validation sound effect - } - } else if (this.scene.gameMode.challenges.length < rowsToDisplay && this.cursor === this.scene.gameMode.challenges.length - 1) { + const successA = this.setCursor(0); + // Then, adjust the scroll to display the topmost elements of the menu. + const successB = this.setScrollCursor(0); + success = successA && successB; // success is just there to play the little validation sound effect + } + } else if (this.scene.gameMode.challenges.length < rowsToDisplay && this.cursor === this.scene.gameMode.challenges.length - 1) { // When at the bottom of a non-scrolling menu and pressing DOWN, move to the topmost item. - success = this.setCursor(0); - } else { - success = this.setCursor(this.cursor + 1); - } - if (success) { - this.updateText(); - } - break; - case Button.LEFT: + success = this.setCursor(0); + } else { + success = this.setCursor(this.cursor + 1); + } + if (success) { + this.updateText(); + } + break; + case Button.LEFT: // Moves the option cursor left, if possible. - success = this.getActiveChallenge().decreaseValue(); - if (success) { - this.updateText(); - } - break; - case Button.RIGHT: + success = this.getActiveChallenge().decreaseValue(); + if (success) { + this.updateText(); + } + break; + case Button.RIGHT: // Moves the option cursor right, if possible. - success = this.getActiveChallenge().increaseValue(); - if (success) { - this.updateText(); - } - break; + success = this.getActiveChallenge().increaseValue(); + if (success) { + this.updateText(); + } + break; } } } diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index 2f65fa0b457..0f5edc28675 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -89,54 +89,54 @@ export default class CommandUiHandler extends UiHandler { if (button === Button.ACTION) { switch (cursor) { // Fight - case Command.FIGHT: - if ((this.scene.getCurrentPhase() as CommandPhase).checkFightOverride()) { - return true; - } - ui.setMode(Mode.FIGHT, (this.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); - success = true; - break; + case Command.FIGHT: + if ((this.scene.getCurrentPhase() as CommandPhase).checkFightOverride()) { + return true; + } + ui.setMode(Mode.FIGHT, (this.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + success = true; + break; // Ball - case Command.BALL: - ui.setModeWithoutClear(Mode.BALL); - success = true; - break; + case Command.BALL: + ui.setModeWithoutClear(Mode.BALL); + success = true; + break; // Pokemon - case Command.POKEMON: - ui.setMode(Mode.PARTY, PartyUiMode.SWITCH, (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex(), null, PartyUiHandler.FilterNonFainted); - success = true; - break; + case Command.POKEMON: + ui.setMode(Mode.PARTY, PartyUiMode.SWITCH, (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex(), null, PartyUiHandler.FilterNonFainted); + success = true; + break; // Run - case Command.RUN: - (this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0); - success = true; - break; + case Command.RUN: + (this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0); + success = true; + break; } } else { (this.scene.getCurrentPhase() as CommandPhase).cancel(); } } else { switch (button) { - case Button.UP: - if (cursor >= 2) { - success = this.setCursor(cursor - 2); - } - break; - case Button.DOWN: - if (cursor < 2) { - success = this.setCursor(cursor + 2); - } - break; - case Button.LEFT: - if (cursor % 2 === 1) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor % 2 === 0) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor >= 2) { + success = this.setCursor(cursor - 2); + } + break; + case Button.DOWN: + if (cursor < 2) { + success = this.setCursor(cursor + 2); + } + break; + case Button.LEFT: + if (cursor % 2 === 1) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor % 2 === 0) { + success = this.setCursor(cursor + 1); + } + break; } } diff --git a/src/ui/daily-run-scoreboard.ts b/src/ui/daily-run-scoreboard.ts index b535a94d35c..b9c1c6ea49a 100644 --- a/src/ui/daily-run-scoreboard.ts +++ b/src/ui/daily-run-scoreboard.ts @@ -142,13 +142,13 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { entryContainer.add(scoreLabel); switch (this.category) { - case ScoreboardCategory.DAILY: - const waveLabel = addTextObject(this.scene, 68, 0, wave, TextStyle.WINDOW, { fontSize: "54px" }); - entryContainer.add(waveLabel); - break; - case ScoreboardCategory.WEEKLY: - scoreLabel.x -= 16; - break; + case ScoreboardCategory.DAILY: + const waveLabel = addTextObject(this.scene, 68, 0, wave, TextStyle.WINDOW, { fontSize: "54px" }); + entryContainer.add(waveLabel); + break; + case ScoreboardCategory.WEEKLY: + scoreLabel.x -= 16; + break; } return entryContainer; diff --git a/src/ui/dropdown.ts b/src/ui/dropdown.ts index eec44480c89..e16efe17036 100644 --- a/src/ui/dropdown.ts +++ b/src/ui/dropdown.ts @@ -115,18 +115,18 @@ export class DropDownOption extends Phaser.GameObjects.Container { */ private updateToggleIconColor(): void { switch (this.state) { - case DropDownState.ON: - this.toggle.setTint(this.onColor); - break; - case DropDownState.OFF: - this.toggle.setTint(this.offColor); - break; - case DropDownState.EXCLUDE: - this.toggle.setTint(this.excludeColor); - break; - case DropDownState.UNLOCKABLE: - this.toggle.setTint(this.unlockableColor); - break; + case DropDownState.ON: + this.toggle.setTint(this.onColor); + break; + case DropDownState.OFF: + this.toggle.setTint(this.offColor); + break; + case DropDownState.EXCLUDE: + this.toggle.setTint(this.excludeColor); + break; + case DropDownState.UNLOCKABLE: + this.toggle.setTint(this.unlockableColor); + break; } } @@ -500,18 +500,18 @@ export class DropDown extends Phaser.GameObjects.Container { }; switch (this.dropDownType) { - case DropDownType.MULTI: - case DropDownType.RADIAL: - return compareValues([ "val", "state" ]); + case DropDownType.MULTI: + case DropDownType.RADIAL: + return compareValues([ "val", "state" ]); - case DropDownType.HYBRID: - return compareValues([ "val", "state", "cursor" ]); + case DropDownType.HYBRID: + return compareValues([ "val", "state", "cursor" ]); - case DropDownType.SINGLE: - return compareValues([ "val", "state", "dir" ]); + case DropDownType.SINGLE: + return compareValues([ "val", "state", "dir" ]); - default: - return false; + default: + return false; } } diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 3aa009b1b31..8f977ba2ac0 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -127,47 +127,47 @@ export default class EggGachaUiHandler extends MessageUiHandler { gachaInfoContainer.add(gachaUpLabel); switch (gachaType as GachaType) { - case GachaType.LEGENDARY: - if ([ "de", "es" ].includes(currentLanguage)) { - gachaUpLabel.setAlign("center"); - gachaUpLabel.setY(0); - } - if ([ "pt-BR" ].includes(currentLanguage)) { - gachaUpLabel.setX(legendaryLabelX - 2); - } else { - gachaUpLabel.setX(legendaryLabelX); - } - gachaUpLabel.setY(legendaryLabelY); + case GachaType.LEGENDARY: + if ([ "de", "es" ].includes(currentLanguage)) { + gachaUpLabel.setAlign("center"); + gachaUpLabel.setY(0); + } + if ([ "pt-BR" ].includes(currentLanguage)) { + gachaUpLabel.setX(legendaryLabelX - 2); + } else { + gachaUpLabel.setX(legendaryLabelX); + } + gachaUpLabel.setY(legendaryLabelY); - const pokemonIcon = this.scene.add.sprite(pokemonIconX, pokemonIconY, "pokemon_icons_0"); - if ([ "pt-BR" ].includes(currentLanguage)) { - pokemonIcon.setX(pokemonIconX - 2); - } - pokemonIcon.setScale(0.5); - pokemonIcon.setOrigin(0, 0.5); + const pokemonIcon = this.scene.add.sprite(pokemonIconX, pokemonIconY, "pokemon_icons_0"); + if ([ "pt-BR" ].includes(currentLanguage)) { + pokemonIcon.setX(pokemonIconX - 2); + } + pokemonIcon.setScale(0.5); + pokemonIcon.setOrigin(0, 0.5); - gachaInfoContainer.add(pokemonIcon); - break; - case GachaType.MOVE: - if ([ "de", "es", "fr", "pt-BR" ].includes(currentLanguage)) { - gachaUpLabel.setAlign("center"); - gachaUpLabel.setY(0); - } + gachaInfoContainer.add(pokemonIcon); + break; + case GachaType.MOVE: + if ([ "de", "es", "fr", "pt-BR" ].includes(currentLanguage)) { + gachaUpLabel.setAlign("center"); + gachaUpLabel.setY(0); + } - gachaUpLabel.setText(i18next.t("egg:moveUPGacha")); - gachaUpLabel.setX(0); - gachaUpLabel.setOrigin(0.5, 0); - break; - case GachaType.SHINY: - if ([ "de", "fr", "ko" ].includes(currentLanguage)) { - gachaUpLabel.setAlign("center"); - gachaUpLabel.setY(0); - } + gachaUpLabel.setText(i18next.t("egg:moveUPGacha")); + gachaUpLabel.setX(0); + gachaUpLabel.setOrigin(0.5, 0); + break; + case GachaType.SHINY: + if ([ "de", "fr", "ko" ].includes(currentLanguage)) { + gachaUpLabel.setAlign("center"); + gachaUpLabel.setY(0); + } - gachaUpLabel.setText(i18next.t("egg:shinyUPGacha")); - gachaUpLabel.setX(0); - gachaUpLabel.setOrigin(0.5, 0); - break; + gachaUpLabel.setText(i18next.t("egg:shinyUPGacha")); + gachaUpLabel.setX(0); + gachaUpLabel.setOrigin(0.5, 0); + break; } const gachaKnob = this.scene.add.sprite(191, 89, "gacha_knob"); @@ -470,12 +470,12 @@ export default class EggGachaUiHandler extends MessageUiHandler { getGuaranteedEggTierFromPullCount(pullCount: number): EggTier { switch (pullCount) { - case 10: - return EggTier.RARE; - case 25: - return EggTier.EPIC; - default: - return EggTier.COMMON; + case 10: + return EggTier.RARE; + case 25: + return EggTier.EPIC; + default: + return EggTier.COMMON; } } @@ -567,11 +567,11 @@ export default class EggGachaUiHandler extends MessageUiHandler { updateGachaInfo(gachaType: GachaType): void { const infoContainer = this.gachaInfoContainers[gachaType]; switch (gachaType as GachaType) { - case GachaType.LEGENDARY: - const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.scene, new Date().getTime())); - const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite; - pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false)); - break; + case GachaType.LEGENDARY: + const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.scene, new Date().getTime())); + const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite; + pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false)); + break; } } @@ -638,106 +638,106 @@ export default class EggGachaUiHandler extends MessageUiHandler { } } else { switch (button) { - case Button.ACTION: - switch (this.cursor) { - case 0: - if (!this.scene.gameData.voucherCounts[VoucherType.REGULAR] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - error = true; - this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.REGULAR, 1); - } - this.pull(); - success = true; - } else { - error = true; - this.showError(i18next.t("egg:tooManyEggs")); - } - break; - case 2: - if (!this.scene.gameData.voucherCounts[VoucherType.PLUS] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - error = true; - this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 95 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.PLUS, 1); - } - this.pull(5); - success = true; - } else { - error = true; - this.showError(i18next.t("egg:tooManyEggs")); - } - break; - case 1: - case 3: - if ((this.cursor === 1 && this.scene.gameData.voucherCounts[VoucherType.REGULAR] < 10 && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) + case Button.ACTION: + switch (this.cursor) { + case 0: + if (!this.scene.gameData.voucherCounts[VoucherType.REGULAR] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + error = true; + this.showError(i18next.t("egg:notEnoughVouchers")); + } else if (this.scene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.REGULAR, 1); + } + this.pull(); + success = true; + } else { + error = true; + this.showError(i18next.t("egg:tooManyEggs")); + } + break; + case 2: + if (!this.scene.gameData.voucherCounts[VoucherType.PLUS] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + error = true; + this.showError(i18next.t("egg:notEnoughVouchers")); + } else if (this.scene.gameData.eggs.length < 95 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.PLUS, 1); + } + this.pull(5); + success = true; + } else { + error = true; + this.showError(i18next.t("egg:tooManyEggs")); + } + break; + case 1: + case 3: + if ((this.cursor === 1 && this.scene.gameData.voucherCounts[VoucherType.REGULAR] < 10 && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) || (this.cursor === 3 && !this.scene.gameData.voucherCounts[VoucherType.PREMIUM] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE)) { - error = true; - this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 90 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { - if (this.cursor === 3) { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.PREMIUM, 1); + error = true; + this.showError(i18next.t("egg:notEnoughVouchers")); + } else if (this.scene.gameData.eggs.length < 90 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + if (this.cursor === 3) { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.PREMIUM, 1); + } + } else { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.REGULAR, 10); + } + } + this.pull(10); + success = true; + } else { + error = true; + this.showError(i18next.t("egg:tooManyEggs")); } - } else { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.REGULAR, 10); + break; + case 4: + if (!this.scene.gameData.voucherCounts[VoucherType.GOLDEN] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + error = true; + this.showError(i18next.t("egg:notEnoughVouchers")); + } else if (this.scene.gameData.eggs.length < 75 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.GOLDEN, 1); + } + this.pull(25); + success = true; + } else { + error = true; + this.showError(i18next.t("egg:tooManyEggs")); } - } - this.pull(10); - success = true; - } else { - error = true; - this.showError(i18next.t("egg:tooManyEggs")); + break; + case 5: + ui.revertMode(); + success = true; + break; } break; - case 4: - if (!this.scene.gameData.voucherCounts[VoucherType.GOLDEN] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - error = true; - this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 75 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.GOLDEN, 1); - } - this.pull(25); - success = true; - } else { - error = true; - this.showError(i18next.t("egg:tooManyEggs")); - } - break; - case 5: - ui.revertMode(); + case Button.CANCEL: + this.getUi().revertMode(); success = true; break; - } - break; - case Button.CANCEL: - this.getUi().revertMode(); - success = true; - break; - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.DOWN: - if (this.cursor < 5) { - success = this.setCursor(this.cursor + 1); - } - break; - case Button.LEFT: - if (this.gachaCursor) { - success = this.setGachaCursor(this.gachaCursor - 1); - } - break; - case Button.RIGHT: - if (this.gachaCursor < Utils.getEnumKeys(GachaType).length - 1) { - success = this.setGachaCursor(this.gachaCursor + 1); - } - break; + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.DOWN: + if (this.cursor < 5) { + success = this.setCursor(this.cursor + 1); + } + break; + case Button.LEFT: + if (this.gachaCursor) { + success = this.setGachaCursor(this.gachaCursor - 1); + } + break; + case Button.RIGHT: + if (this.gachaCursor < Utils.getEnumKeys(GachaType).length - 1) { + success = this.setGachaCursor(this.gachaCursor + 1); + } + break; } } } diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 78894a7bf00..ee6641a1a27 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -152,26 +152,26 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { } } else { switch (button) { - case Button.UP: - if (cursor >= 2) { - success = this.setCursor(cursor - 2); - } - break; - case Button.DOWN: - if (cursor < 2) { - success = this.setCursor(cursor + 2); - } - break; - case Button.LEFT: - if (cursor % 2 === 1) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor % 2 === 0) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor >= 2) { + success = this.setCursor(cursor - 2); + } + break; + case Button.DOWN: + if (cursor < 2) { + success = this.setCursor(cursor + 2); + } + break; + case Button.LEFT: + if (cursor % 2 === 1) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor % 2 === 0) { + success = this.setCursor(cursor + 1); + } + break; } } diff --git a/src/ui/game-stats-ui-handler.ts b/src/ui/game-stats-ui-handler.ts index 4d7b0855092..671bed29036 100644 --- a/src/ui/game-stats-ui-handler.ts +++ b/src/ui/game-stats-ui-handler.ts @@ -351,16 +351,16 @@ export default class GameStatsUiHandler extends UiHandler { this.scene.ui.revertMode(); } else { switch (button) { - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.DOWN: - if (this.cursor < Math.ceil((Object.keys(displayStats).length - 18) / 2)) { - success = this.setCursor(this.cursor + 1); - } - break; + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.DOWN: + if (this.cursor < Math.ceil((Object.keys(displayStats).length - 18) / 2)) { + success = this.setCursor(this.cursor + 1); + } + break; } } diff --git a/src/ui/login-form-ui-handler.ts b/src/ui/login-form-ui-handler.ts index 192a6cefa7a..8be432ad6c1 100644 --- a/src/ui/login-form-ui-handler.ts +++ b/src/ui/login-form-ui-handler.ts @@ -97,18 +97,18 @@ export default class LoginFormUiHandler extends FormModalUiHandler { error = error.slice(0, colonIndex); } switch (error) { - case this.ERR_USERNAME: - return i18next.t("menu:invalidLoginUsername"); - case this.ERR_PASSWORD: - return i18next.t("menu:invalidLoginPassword"); - case this.ERR_ACCOUNT_EXIST: - return i18next.t("menu:accountNonExistent"); - case this.ERR_PASSWORD_MATCH: - return i18next.t("menu:unmatchingPassword"); - case this.ERR_NO_SAVES: - return i18next.t("menu:noSaves"); - case this.ERR_TOO_MANY_SAVES: - return i18next.t("menu:tooManySaves"); + case this.ERR_USERNAME: + return i18next.t("menu:invalidLoginUsername"); + case this.ERR_PASSWORD: + return i18next.t("menu:invalidLoginPassword"); + case this.ERR_ACCOUNT_EXIST: + return i18next.t("menu:accountNonExistent"); + case this.ERR_PASSWORD_MATCH: + return i18next.t("menu:unmatchingPassword"); + case this.ERR_NO_SAVES: + return i18next.t("menu:noSaves"); + case this.ERR_TOO_MANY_SAVES: + return i18next.t("menu:tooManySaves"); } return super.getReadableErrorMessage(error); diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index adebf9fb912..0f4c2d2f53e 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -468,148 +468,148 @@ export default class MenuUiHandler extends MessageUiHandler { } this.showText("", 0); switch (adjustedCursor) { - case MenuOptions.GAME_SETTINGS: - ui.setOverlayMode(Mode.SETTINGS); - success = true; - break; - case MenuOptions.ACHIEVEMENTS: - ui.setOverlayMode(Mode.ACHIEVEMENTS); - success = true; - break; - case MenuOptions.STATS: - ui.setOverlayMode(Mode.GAME_STATS); - success = true; - break; - case MenuOptions.RUN_HISTORY: - ui.setOverlayMode(Mode.RUN_HISTORY); - success = true; - break; - case MenuOptions.EGG_LIST: - if (this.scene.gameData.eggs.length) { + case MenuOptions.GAME_SETTINGS: + ui.setOverlayMode(Mode.SETTINGS); + success = true; + break; + case MenuOptions.ACHIEVEMENTS: + ui.setOverlayMode(Mode.ACHIEVEMENTS); + success = true; + break; + case MenuOptions.STATS: + ui.setOverlayMode(Mode.GAME_STATS); + success = true; + break; + case MenuOptions.RUN_HISTORY: + ui.setOverlayMode(Mode.RUN_HISTORY); + success = true; + break; + case MenuOptions.EGG_LIST: + if (this.scene.gameData.eggs.length) { + ui.revertMode(); + ui.setOverlayMode(Mode.EGG_LIST); + success = true; + } else { + ui.showText(i18next.t("menuUiHandler:noEggs"), null, () => ui.showText(""), Utils.fixedInt(1500)); + error = true; + } + break; + case MenuOptions.EGG_GACHA: ui.revertMode(); - ui.setOverlayMode(Mode.EGG_LIST); + ui.setOverlayMode(Mode.EGG_GACHA); success = true; - } else { - ui.showText(i18next.t("menuUiHandler:noEggs"), null, () => ui.showText(""), Utils.fixedInt(1500)); - error = true; - } - break; - case MenuOptions.EGG_GACHA: - ui.revertMode(); - ui.setOverlayMode(Mode.EGG_GACHA); - success = true; - break; - case MenuOptions.MANAGE_DATA: - if (!bypassLogin && !this.manageDataConfig.options.some(o => o.label === i18next.t("menuUiHandler:linkDiscord") || o.label === i18next.t("menuUiHandler:unlinkDiscord"))) { - this.manageDataConfig.options.splice(this.manageDataConfig.options.length - 1, 0, - { - label: loggedInUser?.discordId === "" ? i18next.t("menuUiHandler:linkDiscord") : i18next.t("menuUiHandler:unlinkDiscord"), - handler: () => { - if (loggedInUser?.discordId === "") { - const token = Utils.getCookie(Utils.sessionIdKey); - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); - const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; - const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&state=${token}&prompt=none`; - window.open(discordUrl, "_self"); - return true; - } else { - Utils.apiPost("/auth/discord/logout", undefined, undefined, true).then(res => { - if (!res.ok) { - console.error(`Unlink failed (${res.status}: ${res.statusText})`); - } - updateUserInfo().then(() => this.scene.reset(true, true)); - }); - return true; + break; + case MenuOptions.MANAGE_DATA: + if (!bypassLogin && !this.manageDataConfig.options.some(o => o.label === i18next.t("menuUiHandler:linkDiscord") || o.label === i18next.t("menuUiHandler:unlinkDiscord"))) { + this.manageDataConfig.options.splice(this.manageDataConfig.options.length - 1, 0, + { + label: loggedInUser?.discordId === "" ? i18next.t("menuUiHandler:linkDiscord") : i18next.t("menuUiHandler:unlinkDiscord"), + handler: () => { + if (loggedInUser?.discordId === "") { + const token = Utils.getCookie(Utils.sessionIdKey); + const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); + const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; + const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&state=${token}&prompt=none`; + window.open(discordUrl, "_self"); + return true; + } else { + Utils.apiPost("/auth/discord/logout", undefined, undefined, true).then(res => { + if (!res.ok) { + console.error(`Unlink failed (${res.status}: ${res.statusText})`); + } + updateUserInfo().then(() => this.scene.reset(true, true)); + }); + return true; + } } - } - }, - { - label: loggedInUser?.googleId === "" ? i18next.t("menuUiHandler:linkGoogle") : i18next.t("menuUiHandler:unlinkGoogle"), - handler: () => { - if (loggedInUser?.googleId === "") { - const token = Utils.getCookie(Utils.sessionIdKey); - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`); - const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; - const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&response_type=code&redirect_uri=${redirectUri}&scope=openid&state=${token}`; - window.open(googleUrl, "_self"); - return true; - } else { - Utils.apiPost("/auth/google/logout", undefined, undefined, true).then(res => { - if (!res.ok) { - console.error(`Unlink failed (${res.status}: ${res.statusText})`); - } - updateUserInfo().then(() => this.scene.reset(true, true)); - }); - return true; + }, + { + label: loggedInUser?.googleId === "" ? i18next.t("menuUiHandler:linkGoogle") : i18next.t("menuUiHandler:unlinkGoogle"), + handler: () => { + if (loggedInUser?.googleId === "") { + const token = Utils.getCookie(Utils.sessionIdKey); + const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`); + const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; + const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&response_type=code&redirect_uri=${redirectUri}&scope=openid&state=${token}`; + window.open(googleUrl, "_self"); + return true; + } else { + Utils.apiPost("/auth/google/logout", undefined, undefined, true).then(res => { + if (!res.ok) { + console.error(`Unlink failed (${res.status}: ${res.statusText})`); + } + updateUserInfo().then(() => this.scene.reset(true, true)); + }); + return true; + } } - } - }); - } - ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.manageDataConfig); - success = true; - break; - case MenuOptions.COMMUNITY: - ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.communityConfig); - success = true; - break; - case MenuOptions.SAVE_AND_QUIT: - if (this.scene.currentBattle) { + }); + } + ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.manageDataConfig); success = true; - const doSaveQuit = () => { - ui.setMode(Mode.LOADING, { - buttonActions: [], fadeOut: () => - this.scene.gameData.saveAll(this.scene, true, true, true, true).then(() => { + break; + case MenuOptions.COMMUNITY: + ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.communityConfig); + success = true; + break; + case MenuOptions.SAVE_AND_QUIT: + if (this.scene.currentBattle) { + success = true; + const doSaveQuit = () => { + ui.setMode(Mode.LOADING, { + buttonActions: [], fadeOut: () => + this.scene.gameData.saveAll(this.scene, true, true, true, true).then(() => { - this.scene.reset(true); - }) + this.scene.reset(true); + }) + }); + }; + if (this.scene.currentBattle.turn > 1) { + ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => { + if (!this.active) { + this.showText("", 0); + return; + } + ui.setOverlayMode(Mode.CONFIRM, doSaveQuit, () => { + ui.revertMode(); + this.showText("", 0); + }, false, -98); + }); + } else { + doSaveQuit(); + } + } else { + error = true; + } + break; + case MenuOptions.LOG_OUT: + success = true; + const doLogout = () => { + ui.setMode(Mode.LOADING, { + buttonActions: [], fadeOut: () => Utils.apiFetch("account/logout", true).then(res => { + if (!res.ok) { + console.error(`Log out failed (${res.status}: ${res.statusText})`); + } + Utils.removeCookie(Utils.sessionIdKey); + updateUserInfo().then(() => this.scene.reset(true, true)); + }) }); }; - if (this.scene.currentBattle.turn > 1) { + if (this.scene.currentBattle) { ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => { if (!this.active) { this.showText("", 0); return; } - ui.setOverlayMode(Mode.CONFIRM, doSaveQuit, () => { + ui.setOverlayMode(Mode.CONFIRM, doLogout, () => { ui.revertMode(); this.showText("", 0); }, false, -98); }); } else { - doSaveQuit(); + doLogout(); } - } else { - error = true; - } - break; - case MenuOptions.LOG_OUT: - success = true; - const doLogout = () => { - ui.setMode(Mode.LOADING, { - buttonActions: [], fadeOut: () => Utils.apiFetch("account/logout", true).then(res => { - if (!res.ok) { - console.error(`Log out failed (${res.status}: ${res.statusText})`); - } - Utils.removeCookie(Utils.sessionIdKey); - updateUserInfo().then(() => this.scene.reset(true, true)); - }) - }); - }; - if (this.scene.currentBattle) { - ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => { - if (!this.active) { - this.showText("", 0); - return; - } - ui.setOverlayMode(Mode.CONFIRM, doLogout, () => { - ui.revertMode(); - this.showText("", 0); - }, false, -98); - }); - } else { - doLogout(); - } - break; + break; } } else if (button === Button.CANCEL) { success = true; @@ -620,20 +620,20 @@ export default class MenuUiHandler extends MessageUiHandler { }); } else { switch (button) { - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else { - success = this.setCursor(this.menuOptions.length - 1); - } - break; - case Button.DOWN: - if (this.cursor + 1 < this.menuOptions.length) { - success = this.setCursor(this.cursor + 1); - } else { - success = this.setCursor(0); - } - break; + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else { + success = this.setCursor(this.menuOptions.length - 1); + } + break; + case Button.DOWN: + if (this.cursor + 1 < this.menuOptions.length) { + success = this.setCursor(this.cursor + 1); + } else { + success = this.setCursor(0); + } + break; } } diff --git a/src/ui/message-ui-handler.ts b/src/ui/message-ui-handler.ts index f1b8ed981ee..5ae4707e329 100644 --- a/src/ui/message-ui-handler.ts +++ b/src/ui/message-ui-handler.ts @@ -56,18 +56,18 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { let actionMatch: RegExpExecArray | null; while ((actionMatch = actionPattern.exec(text))) { switch (actionMatch[1]) { - case "c": - charVarMap.set(actionMatch.index, actionMatch[2]); - break; - case "d": - delayMap.set(actionMatch.index, parseInt(actionMatch[2])); - break; - case "s": - soundMap.set(actionMatch.index, actionMatch[2]); - break; - case "f": - fadeMap.set(actionMatch.index, parseInt(actionMatch[2])); - break; + case "c": + charVarMap.set(actionMatch.index, actionMatch[2]); + break; + case "d": + delayMap.set(actionMatch.index, parseInt(actionMatch[2])); + break; + case "s": + soundMap.set(actionMatch.index, actionMatch[2]); + break; + case "f": + fadeMap.set(actionMatch.index, parseInt(actionMatch[2])); + break; } text = text.slice(0, actionMatch.index) + text.slice(actionMatch.index + actionMatch[2].length + 4); } diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 1948d75ac4c..3f89ebe415f 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -357,79 +357,79 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } } else { switch (button) { - case Button.UP: - if (this.rowCursor === 0 && this.cursor === 3) { - success = this.setCursor(0); - } else if (this.rowCursor < this.shopOptionsRows.length + 1) { - success = this.setRowCursor(this.rowCursor + 1); - } - break; - case Button.DOWN: - if (this.rowCursor) { - success = this.setRowCursor(this.rowCursor - 1); - } else if (this.lockRarityButtonContainer.visible && this.cursor === 0) { - success = this.setCursor(3); - } - break; - case Button.LEFT: - if (!this.rowCursor) { - switch (this.cursor) { - case 0: - success = false; - break; - case 1: - if (this.lockRarityButtonContainer.visible) { - success = this.setCursor(3); - } else { - success = this.rerollButtonContainer.visible && this.setCursor(0); - } - break; - case 2: - if (this.transferButtonContainer.visible) { - success = this.setCursor(1); - } else if (this.rerollButtonContainer.visible) { - success = this.setCursor(0); - } else { - success = false; - } - break; + case Button.UP: + if (this.rowCursor === 0 && this.cursor === 3) { + success = this.setCursor(0); + } else if (this.rowCursor < this.shopOptionsRows.length + 1) { + success = this.setRowCursor(this.rowCursor + 1); } - } else if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else if (this.rowCursor === 1 && this.rerollButtonContainer.visible) { - success = this.setRowCursor(0); - } - break; - case Button.RIGHT: - if (!this.rowCursor) { - switch (this.cursor) { - case 0: - if (this.transferButtonContainer.visible) { - success = this.setCursor(1); - } else { - success = this.setCursor(2); - } - break; - case 1: - success = this.setCursor(2); - break; - case 2: - success = false; - break; - case 3: - if (this.transferButtonContainer.visible) { - success = this.setCursor(1); - } else { - success = this.setCursor(2); - } - break; + break; + case Button.DOWN: + if (this.rowCursor) { + success = this.setRowCursor(this.rowCursor - 1); + } else if (this.lockRarityButtonContainer.visible && this.cursor === 0) { + success = this.setCursor(3); } - } else if (this.cursor < this.getRowItems(this.rowCursor) - 1) { - success = this.setCursor(this.cursor + 1); - } else if (this.rowCursor === 1 && this.transferButtonContainer.visible) { - success = this.setRowCursor(0); - } - break; + break; + case Button.LEFT: + if (!this.rowCursor) { + switch (this.cursor) { + case 0: + success = false; + break; + case 1: + if (this.lockRarityButtonContainer.visible) { + success = this.setCursor(3); + } else { + success = this.rerollButtonContainer.visible && this.setCursor(0); + } + break; + case 2: + if (this.transferButtonContainer.visible) { + success = this.setCursor(1); + } else if (this.rerollButtonContainer.visible) { + success = this.setCursor(0); + } else { + success = false; + } + break; + } + } else if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else if (this.rowCursor === 1 && this.rerollButtonContainer.visible) { + success = this.setRowCursor(0); + } + break; + case Button.RIGHT: + if (!this.rowCursor) { + switch (this.cursor) { + case 0: + if (this.transferButtonContainer.visible) { + success = this.setCursor(1); + } else { + success = this.setCursor(2); + } + break; + case 1: + success = this.setCursor(2); + break; + case 2: + success = false; + break; + case 3: + if (this.transferButtonContainer.visible) { + success = this.setCursor(1); + } else { + success = this.setCursor(2); + } + break; + } + } else if (this.cursor < this.getRowItems(this.rowCursor) - 1) { + success = this.setCursor(this.cursor + 1); + } else if (this.rowCursor === 1 && this.transferButtonContainer.visible) { + success = this.setRowCursor(0); + } + break; } } @@ -527,12 +527,12 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { private getRowItems(rowCursor: integer): integer { switch (rowCursor) { - case 0: - return 3; - case 1: - return this.options.length; - default: - return this.shopOptionsRows[this.shopOptionsRows.length - (rowCursor - 1)].length; + case 0: + return 3; + case 1: + return this.options.length; + default: + return this.shopOptionsRows[this.shopOptionsRows.length - (rowCursor - 1)].length; } } diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index 0a838707432..b568f8b71de 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -159,16 +159,16 @@ export default class MysteryEncounterUiHandler extends UiHandler { } } else { switch (this.optionsContainer.getAll()?.length) { - default: - case 3: - success = this.handleTwoOptionMoveInput(button); - break; - case 4: - success = this.handleThreeOptionMoveInput(button); - break; - case 5: - success = this.handleFourOptionMoveInput(button); - break; + default: + case 3: + success = this.handleTwoOptionMoveInput(button); + break; + case 4: + success = this.handleThreeOptionMoveInput(button); + break; + case 5: + success = this.handleFourOptionMoveInput(button); + break; } this.displayOptionTooltip(); @@ -185,26 +185,26 @@ export default class MysteryEncounterUiHandler extends UiHandler { let success = false; const cursor = this.getCursor(); switch (button) { - case Button.UP: - if (cursor < this.viewPartyIndex) { - success = this.setCursor(this.viewPartyIndex); - } - break; - case Button.DOWN: - if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } - break; - case Button.LEFT: - if (cursor > 0) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor < this.viewPartyIndex) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor < this.viewPartyIndex) { + success = this.setCursor(this.viewPartyIndex); + } + break; + case Button.DOWN: + if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } + break; + case Button.LEFT: + if (cursor > 0) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor < this.viewPartyIndex) { + success = this.setCursor(cursor + 1); + } + break; } return success; @@ -214,34 +214,34 @@ export default class MysteryEncounterUiHandler extends UiHandler { let success = false; const cursor = this.getCursor(); switch (button) { - case Button.UP: - if (cursor === 2) { - success = this.setCursor(cursor - 2); - } else { - success = this.setCursor(this.viewPartyIndex); - } - break; - case Button.DOWN: - if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } else { - success = this.setCursor(2); - } - break; - case Button.LEFT: - if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } else if (cursor === 1) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor === 1) { - success = this.setCursor(this.viewPartyIndex); - } else if (cursor < 1) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor === 2) { + success = this.setCursor(cursor - 2); + } else { + success = this.setCursor(this.viewPartyIndex); + } + break; + case Button.DOWN: + if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } else { + success = this.setCursor(2); + } + break; + case Button.LEFT: + if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } else if (cursor === 1) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor === 1) { + success = this.setCursor(this.viewPartyIndex); + } else if (cursor < 1) { + success = this.setCursor(cursor + 1); + } + break; } return success; @@ -251,34 +251,34 @@ export default class MysteryEncounterUiHandler extends UiHandler { let success = false; const cursor = this.getCursor(); switch (button) { - case Button.UP: - if (cursor >= 2 && cursor !== this.viewPartyIndex) { - success = this.setCursor(cursor - 2); - } else { - success = this.setCursor(this.viewPartyIndex); - } - break; - case Button.DOWN: - if (cursor <= 1) { - success = this.setCursor(cursor + 2); - } else if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } - break; - case Button.LEFT: - if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } else if (cursor % 2 === 1) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor === 1) { - success = this.setCursor(this.viewPartyIndex); - } else if (cursor % 2 === 0 && cursor !== this.viewPartyIndex) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor >= 2 && cursor !== this.viewPartyIndex) { + success = this.setCursor(cursor - 2); + } else { + success = this.setCursor(this.viewPartyIndex); + } + break; + case Button.DOWN: + if (cursor <= 1) { + success = this.setCursor(cursor + 2); + } else if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } + break; + case Button.LEFT: + if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } else if (cursor % 2 === 1) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor === 1) { + success = this.setCursor(this.viewPartyIndex); + } else if (cursor % 2 === 0 && cursor !== this.viewPartyIndex) { + success = this.setCursor(cursor + 1); + } + break; } return success; @@ -351,16 +351,16 @@ export default class MysteryEncounterUiHandler extends UiHandler { let optionText: BBCodeText; switch (this.encounterOptions.length) { - default: - case 2: - optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); - break; - case 3: - optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); - break; - case 4: - optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); - break; + default: + case 2: + optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); + break; + case 3: + optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); + break; + case 4: + optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); + break; } this.optionsMeetsReqs.push(option.meetsRequirements(this.scene)); diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index cfc5e146f08..e96fde8d54f 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -539,42 +539,42 @@ export default class PartyUiHandler extends MessageUiHandler { return true; } else { switch (button) { - case Button.LEFT: + case Button.LEFT: /** Decrease quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - this.transferQuantities[option] = this.transferQuantities[option] === 1 ? this.transferQuantitiesMax[option] : this.transferQuantities[option] - 1; - this.updateOptions(); - success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ - } - break; - case Button.RIGHT: + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = this.transferQuantities[option] === 1 ? this.transferQuantitiesMax[option] : this.transferQuantities[option] - 1; + this.updateOptions(); + success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ + } + break; + case Button.RIGHT: /** Increase quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - this.transferQuantities[option] = this.transferQuantities[option] === this.transferQuantitiesMax[option] ? 1 : this.transferQuantities[option] + 1; - this.updateOptions(); - success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ - } - break; - case Button.UP: - /** If currently selecting items to transfer, reset quantity selection */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - if (option !== PartyOption.ALL) { - this.transferQuantities[option] = this.transferQuantitiesMax[option]; + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = this.transferQuantities[option] === this.transferQuantitiesMax[option] ? 1 : this.transferQuantities[option] + 1; + this.updateOptions(); + success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ } - this.updateOptions(); - } - success = this.setCursor(this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1); /** Move cursor */ - break; - case Button.DOWN: + break; + case Button.UP: /** If currently selecting items to transfer, reset quantity selection */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - if (option !== PartyOption.ALL) { - this.transferQuantities[option] = this.transferQuantitiesMax[option]; + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + if (option !== PartyOption.ALL) { + this.transferQuantities[option] = this.transferQuantitiesMax[option]; + } + this.updateOptions(); } - this.updateOptions(); - } - success = this.setCursor(this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0); /** Move cursor */ - break; + success = this.setCursor(this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1); /** Move cursor */ + break; + case Button.DOWN: + /** If currently selecting items to transfer, reset quantity selection */ + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + if (option !== PartyOption.ALL) { + this.transferQuantities[option] = this.transferQuantitiesMax[option]; + } + this.updateOptions(); + } + success = this.setCursor(this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0); /** Move cursor */ + break; } // show move description @@ -631,28 +631,28 @@ export default class PartyUiHandler extends MessageUiHandler { const battlerCount = this.scene.currentBattle.getBattlerCount(); switch (button) { - case Button.UP: - success = this.setCursor(this.cursor ? this.cursor < 6 ? this.cursor - 1 : slotCount - 1 : 6); - break; - case Button.DOWN: - success = this.setCursor(this.cursor < 6 ? this.cursor < slotCount - 1 ? this.cursor + 1 : 6 : 0); - break; - case Button.LEFT: - if (this.cursor >= battlerCount && this.cursor <= 6) { - success = this.setCursor(0); - } - break; - case Button.RIGHT: - if (slotCount === battlerCount) { - success = this.setCursor(6); + case Button.UP: + success = this.setCursor(this.cursor ? this.cursor < 6 ? this.cursor - 1 : slotCount - 1 : 6); break; - } else if (battlerCount >= 2 && slotCount > battlerCount && this.getCursor() === 0 && this.lastCursor === 1) { - success = this.setCursor(2); + case Button.DOWN: + success = this.setCursor(this.cursor < 6 ? this.cursor < slotCount - 1 ? this.cursor + 1 : 6 : 0); break; - } else if (slotCount > battlerCount && this.cursor < battlerCount) { - success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || battlerCount : battlerCount); + case Button.LEFT: + if (this.cursor >= battlerCount && this.cursor <= 6) { + success = this.setCursor(0); + } break; - } + case Button.RIGHT: + if (slotCount === battlerCount) { + success = this.setCursor(6); + break; + } else if (battlerCount >= 2 && slotCount > battlerCount && this.getCursor() === 0 && this.lastCursor === 1) { + success = this.setCursor(2); + break; + } else if (slotCount > battlerCount && this.cursor < battlerCount) { + success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || battlerCount : battlerCount); + break; + } } } @@ -773,19 +773,19 @@ export default class PartyUiHandler extends MessageUiHandler { let optionsMessage = i18next.t("partyUiHandler:doWhatWithThisPokemon"); switch (this.partyUiMode) { - case PartyUiMode.MOVE_MODIFIER: - optionsMessage = i18next.t("partyUiHandler:selectAMove"); - break; - case PartyUiMode.MODIFIER_TRANSFER: - if (!this.transferMode) { - optionsMessage = i18next.t("partyUiHandler:changeQuantity"); - } - break; - case PartyUiMode.SPLICE: - if (!this.transferMode) { - optionsMessage = i18next.t("partyUiHandler:selectAnotherPokemonToSplice"); - } - break; + case PartyUiMode.MOVE_MODIFIER: + optionsMessage = i18next.t("partyUiHandler:selectAMove"); + break; + case PartyUiMode.MODIFIER_TRANSFER: + if (!this.transferMode) { + optionsMessage = i18next.t("partyUiHandler:changeQuantity"); + } + break; + case PartyUiMode.SPLICE: + if (!this.transferMode) { + optionsMessage = i18next.t("partyUiHandler:selectAnotherPokemonToSplice"); + } + break; } this.showText(optionsMessage, 0); @@ -829,64 +829,64 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.partyUiMode !== PartyUiMode.MOVE_MODIFIER && this.partyUiMode !== PartyUiMode.REMEMBER_MOVE_MODIFIER && (this.transferMode || this.partyUiMode !== PartyUiMode.MODIFIER_TRANSFER)) { switch (this.partyUiMode) { - case PartyUiMode.SWITCH: - case PartyUiMode.FAINT_SWITCH: - case PartyUiMode.POST_BATTLE_SWITCH: - if (this.cursor >= this.scene.currentBattle.getBattlerCount()) { - const allowBatonModifierSwitch = + case PartyUiMode.SWITCH: + case PartyUiMode.FAINT_SWITCH: + case PartyUiMode.POST_BATTLE_SWITCH: + if (this.cursor >= this.scene.currentBattle.getBattlerCount()) { + const allowBatonModifierSwitch = this.partyUiMode !== PartyUiMode.FAINT_SWITCH && this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === this.scene.getPlayerField()[this.fieldIndex].id); - const moveHistory = this.scene.getPlayerField()[this.fieldIndex].getMoveHistory(); - const isBatonPassMove = this.partyUiMode === PartyUiMode.FAINT_SWITCH && moveHistory.length && allMoves[moveHistory[moveHistory.length - 1].move].getAttrs(ForceSwitchOutAttr)[0]?.isBatonPass() && moveHistory[moveHistory.length - 1].result === MoveResult.SUCCESS; + const moveHistory = this.scene.getPlayerField()[this.fieldIndex].getMoveHistory(); + const isBatonPassMove = this.partyUiMode === PartyUiMode.FAINT_SWITCH && moveHistory.length && allMoves[moveHistory[moveHistory.length - 1].move].getAttrs(ForceSwitchOutAttr)[0]?.isBatonPass() && moveHistory[moveHistory.length - 1].result === MoveResult.SUCCESS; - // isBatonPassMove and allowBatonModifierSwitch shouldn't ever be true - // at the same time, because they both explicitly check for a mutually - // exclusive partyUiMode. But better safe than sorry. - this.options.push(isBatonPassMove && !allowBatonModifierSwitch ? PartyOption.PASS_BATON : PartyOption.SEND_OUT); - if (allowBatonModifierSwitch && !isBatonPassMove) { + // isBatonPassMove and allowBatonModifierSwitch shouldn't ever be true + // at the same time, because they both explicitly check for a mutually + // exclusive partyUiMode. But better safe than sorry. + this.options.push(isBatonPassMove && !allowBatonModifierSwitch ? PartyOption.PASS_BATON : PartyOption.SEND_OUT); + if (allowBatonModifierSwitch && !isBatonPassMove) { // the BATON modifier gives an extra switch option for // pokemon-command switches, allowing buffs to be optionally passed - this.options.push(PartyOption.PASS_BATON); + this.options.push(PartyOption.PASS_BATON); + } } - } - break; - case PartyUiMode.REVIVAL_BLESSING: - this.options.push(PartyOption.REVIVE); - break; - case PartyUiMode.MODIFIER: - this.options.push(PartyOption.APPLY); - break; - case PartyUiMode.TM_MODIFIER: - this.options.push(PartyOption.TEACH); - break; - case PartyUiMode.MODIFIER_TRANSFER: - this.options.push(PartyOption.TRANSFER); - break; - case PartyUiMode.SPLICE: - if (this.transferMode) { - if (this.cursor !== this.transferCursor) { - this.options.push(PartyOption.SPLICE); - } - } else { + break; + case PartyUiMode.REVIVAL_BLESSING: + this.options.push(PartyOption.REVIVE); + break; + case PartyUiMode.MODIFIER: this.options.push(PartyOption.APPLY); - } - break; - case PartyUiMode.RELEASE: - this.options.push(PartyOption.RELEASE); - break; - case PartyUiMode.CHECK: - if (this.scene.getCurrentPhase() instanceof SelectModifierPhase) { - formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); - for (let i = 0; i < formChangeItemModifiers.length; i++) { - this.options.push(PartyOption.FORM_CHANGE_ITEM + i); + break; + case PartyUiMode.TM_MODIFIER: + this.options.push(PartyOption.TEACH); + break; + case PartyUiMode.MODIFIER_TRANSFER: + this.options.push(PartyOption.TRANSFER); + break; + case PartyUiMode.SPLICE: + if (this.transferMode) { + if (this.cursor !== this.transferCursor) { + this.options.push(PartyOption.SPLICE); + } + } else { + this.options.push(PartyOption.APPLY); } - } - break; - case PartyUiMode.SELECT: - this.options.push(PartyOption.SELECT); - break; + break; + case PartyUiMode.RELEASE: + this.options.push(PartyOption.RELEASE); + break; + case PartyUiMode.CHECK: + if (this.scene.getCurrentPhase() instanceof SelectModifierPhase) { + formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); + for (let i = 0; i < formChangeItemModifiers.length; i++) { + this.options.push(PartyOption.FORM_CHANGE_ITEM + i); + } + } + break; + case PartyUiMode.SELECT: + this.options.push(PartyOption.SELECT); + break; } this.options.push(PartyOption.SUMMARY); @@ -962,33 +962,33 @@ export default class PartyUiHandler extends MessageUiHandler { optionName = "↓"; } else if ((this.partyUiMode !== PartyUiMode.REMEMBER_MOVE_MODIFIER && (this.partyUiMode !== PartyUiMode.MODIFIER_TRANSFER || this.transferMode)) || option === PartyOption.CANCEL) { switch (option) { - case PartyOption.MOVE_1: - case PartyOption.MOVE_2: - case PartyOption.MOVE_3: - case PartyOption.MOVE_4: - const move = pokemon.moveset[option - PartyOption.MOVE_1]!; // TODO: is the bang correct? - if (this.showMovePp) { - const maxPP = move.getMovePp(); - const currPP = maxPP - move.ppUsed; - optionName = `${move.getName()} ${currPP}/${maxPP}`; - } else { - optionName = move.getName(); - } - break; - default: - 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}`; - } else if (option === PartyOption.UNPAUSE_EVOLUTION) { - optionName = `${pokemon.pauseEvolutions ? i18next.t("partyUiHandler:UNPAUSE_EVOLUTION") : i18next.t("partyUiHandler:PAUSE_EVOLUTION")}`; - } else { - if (this.localizedOptions.includes(option)) { - optionName = i18next.t(`partyUiHandler:${PartyOption[option]}`); + case PartyOption.MOVE_1: + case PartyOption.MOVE_2: + case PartyOption.MOVE_3: + case PartyOption.MOVE_4: + const move = pokemon.moveset[option - PartyOption.MOVE_1]!; // TODO: is the bang correct? + if (this.showMovePp) { + const maxPP = move.getMovePp(); + const currPP = maxPP - move.ppUsed; + optionName = `${move.getName()} ${currPP}/${maxPP}`; } else { - optionName = Utils.toReadableString(PartyOption[option]); + optionName = move.getName(); } - } - break; + break; + default: + 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}`; + } else if (option === PartyOption.UNPAUSE_EVOLUTION) { + optionName = `${pokemon.pauseEvolutions ? i18next.t("partyUiHandler:UNPAUSE_EVOLUTION") : i18next.t("partyUiHandler:PAUSE_EVOLUTION")}`; + } else { + if (this.localizedOptions.includes(option)) { + optionName = i18next.t(`partyUiHandler:${PartyOption[option]}`); + } else { + optionName = Utils.toReadableString(PartyOption[option]); + } + } + break; } } else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { const move = learnableLevelMoves[option]; diff --git a/src/ui/pokemon-icon-anim-handler.ts b/src/ui/pokemon-icon-anim-handler.ts index d6796d5cb5d..c7a24f69200 100644 --- a/src/ui/pokemon-icon-anim-handler.ts +++ b/src/ui/pokemon-icon-anim-handler.ts @@ -39,12 +39,12 @@ export default class PokemonIconAnimHandler { getModeYDelta(mode: PokemonIconAnimMode): number { switch (mode) { - case PokemonIconAnimMode.NONE: - return 0; - case PokemonIconAnimMode.PASSIVE: - return -1; - case PokemonIconAnimMode.ACTIVE: - return -2; + case PokemonIconAnimMode.NONE: + return 0; + case PokemonIconAnimMode.PASSIVE: + return -1; + case PokemonIconAnimMode.ACTIVE: + return -2; } } diff --git a/src/ui/registration-form-ui-handler.ts b/src/ui/registration-form-ui-handler.ts index c94d060c0b7..2f8486bcdb3 100644 --- a/src/ui/registration-form-ui-handler.ts +++ b/src/ui/registration-form-ui-handler.ts @@ -50,12 +50,12 @@ export default class RegistrationFormUiHandler extends FormModalUiHandler { error = error.slice(0, colonIndex); } switch (error) { - case "invalid username": - return i18next.t("menu:invalidRegisterUsername"); - case "invalid password": - return i18next.t("menu:invalidRegisterPassword"); - case "failed to add account record": - return i18next.t("menu:usernameAlreadyUsed"); + case "invalid username": + return i18next.t("menu:invalidRegisterUsername"); + case "invalid password": + return i18next.t("menu:invalidRegisterPassword"); + case "failed to add account record": + return i18next.t("menu:usernameAlreadyUsed"); } return super.getReadableErrorMessage(error); diff --git a/src/ui/run-history-ui-handler.ts b/src/ui/run-history-ui-handler.ts index 20de7fd832c..061f15d0956 100644 --- a/src/ui/run-history-ui-handler.ts +++ b/src/ui/run-history-ui-handler.ts @@ -118,28 +118,28 @@ export default class RunHistoryUiHandler extends MessageUiHandler { } } else if (this.runs.length > 0) { switch (button) { - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else if (this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1); - } else if (this.runs.length > 1) { + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else if (this.scrollCursor) { + success = this.setScrollCursor(this.scrollCursor - 1); + } else if (this.runs.length > 1) { // wrap around to the bottom - success = this.setCursor(Math.min(this.runs.length - 1, this.maxRows - 1)); - success = this.setScrollCursor(Math.max(0, this.runs.length - this.maxRows)) || success; - } - break; - case Button.DOWN: - if (this.cursor < Math.min(this.maxRows - 1, this.runs.length - this.scrollCursor - 1)) { - success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < this.runs.length - this.maxRows) { - success = this.setScrollCursor(this.scrollCursor + 1); - } else if (this.runs.length > 1) { + success = this.setCursor(Math.min(this.runs.length - 1, this.maxRows - 1)); + success = this.setScrollCursor(Math.max(0, this.runs.length - this.maxRows)) || success; + } + break; + case Button.DOWN: + if (this.cursor < Math.min(this.maxRows - 1, this.runs.length - this.scrollCursor - 1)) { + success = this.setCursor(this.cursor + 1); + } else if (this.scrollCursor < this.runs.length - this.maxRows) { + success = this.setScrollCursor(this.scrollCursor + 1); + } else if (this.runs.length > 1) { // wrap around to the top - success = this.setCursor(0); - success = this.setScrollCursor(0) || success; - } - break; + success = this.setCursor(0); + success = this.setScrollCursor(0) || success; + } + break; } } @@ -333,19 +333,19 @@ class RunEntryContainer extends Phaser.GameObjects.Container { const gameModeLabel = addTextObject(this.scene, 8, 19, "", TextStyle.WINDOW); let mode = ""; switch (data.gameMode) { - case GameModes.DAILY: - mode = i18next.t("gameMode:dailyRun"); - break; - case GameModes.SPLICED_ENDLESS: - case GameModes.ENDLESS: - mode = i18next.t("gameMode:endless"); - break; - case GameModes.CLASSIC: - mode = i18next.t("gameMode:classic"); - break; - case GameModes.CHALLENGE: - mode = i18next.t("gameMode:challenge"); - break; + case GameModes.DAILY: + mode = i18next.t("gameMode:dailyRun"); + break; + case GameModes.SPLICED_ENDLESS: + case GameModes.ENDLESS: + mode = i18next.t("gameMode:endless"); + break; + case GameModes.CLASSIC: + mode = i18next.t("gameMode:classic"); + break; + case GameModes.CHALLENGE: + mode = i18next.t("gameMode:challenge"); + break; } gameModeLabel.appendText(mode, false); if (data.gameMode === GameModes.SPLICED_ENDLESS) { diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 1976d5a997b..0ca47241136 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -229,14 +229,14 @@ export default class RunInfoUiHandler extends UiHandler { // Wild - Single and Doubles if (this.runInfo.battleType === BattleType.WILD || (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER && !this.runInfo.trainer)) { switch (this.runInfo.enemyParty.length) { - case 1: + case 1: // Wild - Singles - this.parseWildSingleDefeat(enemyContainer); - break; - case 2: + this.parseWildSingleDefeat(enemyContainer); + break; + case 2: //Wild - Doubles - this.parseWildDoubleDefeat(enemyContainer); - break; + this.parseWildDoubleDefeat(enemyContainer); + break; } } else if (this.runInfo.battleType === BattleType.TRAINER || (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER && this.runInfo.trainer)) { this.parseTrainerDefeat(enemyContainer); @@ -474,33 +474,33 @@ export default class RunInfoUiHandler extends UiHandler { modeText.setPosition(7, 5); modeText.appendText(i18next.t("runHistory:mode") + ": ", false); switch (this.runInfo.gameMode) { - case GameModes.DAILY: - modeText.appendText(`${i18next.t("gameMode:dailyRun")}`, false); - break; - case GameModes.SPLICED_ENDLESS: - modeText.appendText(`${i18next.t("gameMode:endlessSpliced")}`, false); - break; - case GameModes.CHALLENGE: - modeText.appendText(`${i18next.t("gameMode:challenge")}`, false); - modeText.appendText(`${i18next.t("runHistory:challengeRules")}: `); - modeText.setWrapMode(1); // wrap by word - modeText.setWrapWidth(500); - const rules: string[] = this.challengeParser(); - if (rules) { - for (let i = 0; i < rules.length; i++) { - if (i > 0) { - modeText.appendText(" + ", false); + case GameModes.DAILY: + modeText.appendText(`${i18next.t("gameMode:dailyRun")}`, false); + break; + case GameModes.SPLICED_ENDLESS: + modeText.appendText(`${i18next.t("gameMode:endlessSpliced")}`, false); + break; + case GameModes.CHALLENGE: + modeText.appendText(`${i18next.t("gameMode:challenge")}`, false); + modeText.appendText(`${i18next.t("runHistory:challengeRules")}: `); + modeText.setWrapMode(1); // wrap by word + modeText.setWrapWidth(500); + const rules: string[] = this.challengeParser(); + if (rules) { + for (let i = 0; i < rules.length; i++) { + if (i > 0) { + modeText.appendText(" + ", false); + } + modeText.appendText(rules[i], false); } - modeText.appendText(rules[i], false); } - } - break; - case GameModes.ENDLESS: - modeText.appendText(`${i18next.t("gameMode:endless")}`, false); - break; - case GameModes.CLASSIC: - modeText.appendText(`${i18next.t("gameMode:classic")}`, false); - break; + break; + case GameModes.ENDLESS: + modeText.appendText(`${i18next.t("gameMode:endless")}`, false); + break; + case GameModes.CLASSIC: + modeText.appendText(`${i18next.t("gameMode:classic")}`, false); + break; } // If the player achieves a personal best in Endless, the mode text will be tinted similarly to SSS luck to celebrate their achievement. @@ -577,23 +577,23 @@ export default class RunInfoUiHandler extends UiHandler { for (let i = 0; i < this.runInfo.challenges.length; i++) { if (this.runInfo.challenges[i].value !== 0) { switch (this.runInfo.challenges[i].id) { - case Challenges.SINGLE_GENERATION: - rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`)); - break; - case Challenges.SINGLE_TYPE: - const typeRule = Type[this.runInfo.challenges[i].value - 1]; - const typeTextColor = `[color=${TypeColor[typeRule]}]`; - const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`; - const typeText = typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]"; - rules.push(typeText); - break; - case Challenges.INVERSE_BATTLE: - rules.push(i18next.t("challenges:inverseBattle.shortName")); - break; - default: - const localisationKey = Challenges[this.runInfo.challenges[i].id].split("_").map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); - rules.push(i18next.t(`challenges:${localisationKey}.name`)); - break; + case Challenges.SINGLE_GENERATION: + rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`)); + break; + case Challenges.SINGLE_TYPE: + const typeRule = Type[this.runInfo.challenges[i].value - 1]; + const typeTextColor = `[color=${TypeColor[typeRule]}]`; + const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`; + const typeText = typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]"; + rules.push(typeText); + break; + case Challenges.INVERSE_BATTLE: + rules.push(i18next.t("challenges:inverseBattle.shortName")); + break; + default: + const localisationKey = Challenges[this.runInfo.challenges[i].id].split("_").map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); + rules.push(i18next.t(`challenges:${localisationKey}.name`)); + break; } } } @@ -911,36 +911,36 @@ export default class RunInfoUiHandler extends UiHandler { const error = false; switch (button) { - case Button.CANCEL: - success = true; - if (this.pageMode === RunInfoUiMode.MAIN) { - this.runInfoContainer.removeAll(true); - this.runResultContainer.removeAll(true); - this.partyContainer.removeAll(true); - this.runContainer.removeAll(true); - if (this.isVictory) { - this.hallofFameContainer.removeAll(true); + case Button.CANCEL: + success = true; + if (this.pageMode === RunInfoUiMode.MAIN) { + this.runInfoContainer.removeAll(true); + this.runResultContainer.removeAll(true); + this.partyContainer.removeAll(true); + this.runContainer.removeAll(true); + if (this.isVictory) { + this.hallofFameContainer.removeAll(true); + } + super.clear(); + this.runContainer.setVisible(false); + ui.revertMode(); + } else if (this.pageMode === RunInfoUiMode.HALL_OF_FAME) { + this.hallofFameContainer.setVisible(false); + this.pageMode = RunInfoUiMode.MAIN; + } else if (this.pageMode === RunInfoUiMode.ENDING_ART) { + this.endCardContainer.setVisible(false); + this.runContainer.remove(this.endCardContainer); + this.pageMode = RunInfoUiMode.MAIN; } - super.clear(); - this.runContainer.setVisible(false); - ui.revertMode(); - } else if (this.pageMode === RunInfoUiMode.HALL_OF_FAME) { - this.hallofFameContainer.setVisible(false); - this.pageMode = RunInfoUiMode.MAIN; - } else if (this.pageMode === RunInfoUiMode.ENDING_ART) { - this.endCardContainer.setVisible(false); - this.runContainer.remove(this.endCardContainer); - this.pageMode = RunInfoUiMode.MAIN; - } - break; - case Button.DOWN: - case Button.UP: - break; - case Button.CYCLE_FORM: - case Button.CYCLE_SHINY: - case Button.CYCLE_ABILITY: - this.buttonCycleOption(button); - break; + break; + case Button.DOWN: + case Button.UP: + break; + case Button.CYCLE_FORM: + case Button.CYCLE_SHINY: + case Button.CYCLE_ABILITY: + this.buttonCycleOption(button); + break; } if (success) { @@ -960,40 +960,40 @@ export default class RunInfoUiHandler extends UiHandler { */ private buttonCycleOption(button: Button) { switch (button) { - case Button.CYCLE_FORM: - if (this.isVictory && this.pageMode !== RunInfoUiMode.HALL_OF_FAME) { - if (!this.endCardContainer || !this.endCardContainer.visible) { - this.createVictorySplash(); - this.endCardContainer.setVisible(true); - this.runContainer.add(this.endCardContainer); - this.pageMode = RunInfoUiMode.ENDING_ART; - } else { - this.endCardContainer.setVisible(false); - this.runContainer.remove(this.endCardContainer); - this.pageMode = RunInfoUiMode.MAIN; + case Button.CYCLE_FORM: + if (this.isVictory && this.pageMode !== RunInfoUiMode.HALL_OF_FAME) { + if (!this.endCardContainer || !this.endCardContainer.visible) { + this.createVictorySplash(); + this.endCardContainer.setVisible(true); + this.runContainer.add(this.endCardContainer); + this.pageMode = RunInfoUiMode.ENDING_ART; + } else { + this.endCardContainer.setVisible(false); + this.runContainer.remove(this.endCardContainer); + this.pageMode = RunInfoUiMode.MAIN; + } } - } - break; - case Button.CYCLE_SHINY: - if (this.isVictory && this.pageMode !== RunInfoUiMode.ENDING_ART) { - if (!this.hallofFameContainer.visible) { - this.hallofFameContainer.setVisible(true); - this.pageMode = RunInfoUiMode.HALL_OF_FAME; - } else { - this.hallofFameContainer.setVisible(false); - this.pageMode = RunInfoUiMode.MAIN; + break; + case Button.CYCLE_SHINY: + if (this.isVictory && this.pageMode !== RunInfoUiMode.ENDING_ART) { + if (!this.hallofFameContainer.visible) { + this.hallofFameContainer.setVisible(true); + this.pageMode = RunInfoUiMode.HALL_OF_FAME; + } else { + this.hallofFameContainer.setVisible(false); + this.pageMode = RunInfoUiMode.MAIN; + } } - } - break; - case Button.CYCLE_ABILITY: - if (this.runInfo.modifiers.length !== 0 && this.pageMode === RunInfoUiMode.MAIN) { - if (this.partyVisibility) { - this.showParty(false); - } else { - this.showParty(true); + break; + case Button.CYCLE_ABILITY: + if (this.runInfo.modifiers.length !== 0 && this.pageMode === RunInfoUiMode.MAIN) { + if (this.partyVisibility) { + this.showParty(false); + } else { + this.showParty(true); + } } - } - break; + break; } } } diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 2e664db8d43..7bfecb1f99b 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -106,40 +106,40 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { error = true; } else { switch (this.uiMode) { - case SaveSlotUiMode.LOAD: - this.saveSlotSelectCallback = null; - originalCallback && originalCallback(cursor); - break; - case SaveSlotUiMode.SAVE: - const saveAndCallback = () => { - const originalCallback = this.saveSlotSelectCallback; + case SaveSlotUiMode.LOAD: this.saveSlotSelectCallback = null; - ui.revertMode(); - ui.showText("", 0); - ui.setMode(Mode.MESSAGE); originalCallback && originalCallback(cursor); - }; - if (this.sessionSlots[cursor].hasData) { - ui.showText(i18next.t("saveSlotSelectUiHandler:overwriteData"), null, () => { - ui.setOverlayMode(Mode.CONFIRM, () => { - this.scene.gameData.deleteSession(cursor).then(response => { - if (response === false) { - this.scene.reset(true); - } else { - saveAndCallback(); - } - }); - }, () => { - ui.revertMode(); - ui.showText("", 0); - }, false, 0, 19, 2000); - }); - } else if (this.sessionSlots[cursor].hasData === false) { - saveAndCallback(); - } else { - return false; - } - break; + break; + case SaveSlotUiMode.SAVE: + const saveAndCallback = () => { + const originalCallback = this.saveSlotSelectCallback; + this.saveSlotSelectCallback = null; + ui.revertMode(); + ui.showText("", 0); + ui.setMode(Mode.MESSAGE); + originalCallback && originalCallback(cursor); + }; + if (this.sessionSlots[cursor].hasData) { + ui.showText(i18next.t("saveSlotSelectUiHandler:overwriteData"), null, () => { + ui.setOverlayMode(Mode.CONFIRM, () => { + this.scene.gameData.deleteSession(cursor).then(response => { + if (response === false) { + this.scene.reset(true); + } else { + saveAndCallback(); + } + }); + }, () => { + ui.revertMode(); + ui.showText("", 0); + }, false, 0, 19, 2000); + }); + } else if (this.sessionSlots[cursor].hasData === false) { + saveAndCallback(); + } else { + return false; + } + break; } success = true; } @@ -151,26 +151,26 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } else { const cursorPosition = this.cursor + this.scrollCursor; switch (button) { - case Button.UP: - if (this.cursor) { + case Button.UP: + if (this.cursor) { // Check to prevent cursor from accessing a negative index - success = (this.cursor === 0) ? this.setCursor(this.cursor) : this.setCursor(this.cursor - 1, cursorPosition); - } else if (this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1, cursorPosition); - } - break; - case Button.DOWN: - if (this.cursor < (SLOTS_ON_SCREEN - 1)) { - success = this.setCursor(this.cursor + 1, cursorPosition); - } else if (this.scrollCursor < SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN) { - success = this.setScrollCursor(this.scrollCursor + 1, cursorPosition); - } - break; - case Button.RIGHT: - if (this.sessionSlots[cursorPosition].hasData && this.sessionSlots[cursorPosition].saveData) { - this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.sessionSlots[cursorPosition].saveData, RunDisplayMode.SESSION_PREVIEW); - success = true; - } + success = (this.cursor === 0) ? this.setCursor(this.cursor) : this.setCursor(this.cursor - 1, cursorPosition); + } else if (this.scrollCursor) { + success = this.setScrollCursor(this.scrollCursor - 1, cursorPosition); + } + break; + case Button.DOWN: + if (this.cursor < (SLOTS_ON_SCREEN - 1)) { + success = this.setCursor(this.cursor + 1, cursorPosition); + } else if (this.scrollCursor < SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN) { + success = this.setScrollCursor(this.scrollCursor + 1, cursorPosition); + } + break; + case Button.RIGHT: + if (this.sessionSlots[cursorPosition].hasData && this.sessionSlots[cursorPosition].saveData) { + this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.sessionSlots[cursorPosition].saveData, RunDisplayMode.SESSION_PREVIEW); + success = true; + } } } diff --git a/src/ui/scrollable-grid-handler.ts b/src/ui/scrollable-grid-handler.ts index f96f3c995b7..cced92a2083 100644 --- a/src/ui/scrollable-grid-handler.ts +++ b/src/ui/scrollable-grid-handler.ts @@ -106,48 +106,48 @@ export default class ScrollableGridUiHandler { const itemOffset = this.scrollCursor * this.COLUMNS; const lastVisibleIndex = Math.min(this.totalElements - 1, this.totalElements - maxScrollCursor * this.COLUMNS - 1); switch (button) { - case Button.UP: - if (currentRowIndex > 0) { - success = this.setCursor(this.cursor - this.COLUMNS); - } else if (this.scrollCursor > 0) { - success = this.setScrollCursor(this.scrollCursor - 1); - } else { + case Button.UP: + if (currentRowIndex > 0) { + success = this.setCursor(this.cursor - this.COLUMNS); + } else if (this.scrollCursor > 0) { + success = this.setScrollCursor(this.scrollCursor - 1); + } else { // wrap around to the last row - let newCursor = this.cursor + (onScreenRows - 1) * this.COLUMNS; - if (newCursor > lastVisibleIndex) { - newCursor -= this.COLUMNS; + let newCursor = this.cursor + (onScreenRows - 1) * this.COLUMNS; + if (newCursor > lastVisibleIndex) { + newCursor -= this.COLUMNS; + } + success = this.setScrollCursor(maxScrollCursor, newCursor); } - success = this.setScrollCursor(maxScrollCursor, newCursor); - } - break; - case Button.DOWN: - if (currentRowIndex < onScreenRows - 1) { + break; + case Button.DOWN: + if (currentRowIndex < onScreenRows - 1) { // Go down one row - success = this.setCursor(Math.min(this.cursor + this.COLUMNS, this.totalElements - itemOffset - 1)); - } else if (this.scrollCursor < maxScrollCursor) { + success = this.setCursor(Math.min(this.cursor + this.COLUMNS, this.totalElements - itemOffset - 1)); + } else if (this.scrollCursor < maxScrollCursor) { // Scroll down one row - success = this.setScrollCursor(this.scrollCursor + 1); - } else { + success = this.setScrollCursor(this.scrollCursor + 1); + } else { // Wrap around to the top row - success = this.setScrollCursor(0, this.cursor % this.COLUMNS); - } - break; - case Button.LEFT: - if (currentColumnIndex > 0) { - success = this.setCursor(this.cursor - 1); - } else if (this.scrollCursor === maxScrollCursor && currentRowIndex === onScreenRows - 1) { - success = this.setCursor(lastVisibleIndex); - } else { - success = this.setCursor(this.cursor + this.COLUMNS - 1); - } - break; - case Button.RIGHT: - if (currentColumnIndex < this.COLUMNS - 1 && this.cursor + itemOffset < this.totalElements - 1) { - success = this.setCursor(this.cursor + 1); - } else { - success = this.setCursor(this.cursor - currentColumnIndex); - } - break; + success = this.setScrollCursor(0, this.cursor % this.COLUMNS); + } + break; + case Button.LEFT: + if (currentColumnIndex > 0) { + success = this.setCursor(this.cursor - 1); + } else if (this.scrollCursor === maxScrollCursor && currentRowIndex === onScreenRows - 1) { + success = this.setCursor(lastVisibleIndex); + } else { + success = this.setCursor(this.cursor + this.COLUMNS - 1); + } + break; + case Button.RIGHT: + if (currentColumnIndex < this.COLUMNS - 1 && this.cursor + itemOffset < this.totalElements - 1) { + success = this.setCursor(this.cursor + 1); + } else { + success = this.setCursor(this.cursor - currentColumnIndex); + } + break; } return success; } diff --git a/src/ui/settings/abstract-binding-ui-handler.ts b/src/ui/settings/abstract-binding-ui-handler.ts index 9ee741f7bd4..9ebc3c493a4 100644 --- a/src/ui/settings/abstract-binding-ui-handler.ts +++ b/src/ui/settings/abstract-binding-ui-handler.ts @@ -170,22 +170,22 @@ export default abstract class AbstractBindingUiHandler extends UiHandler { const ui = this.getUi(); let success = false; switch (button) { - case Button.LEFT: - case Button.RIGHT: + case Button.LEFT: + case Button.RIGHT: // Toggle between action and cancel options. - const cursor = this.cursor ? 0 : 1; - success = this.setCursor(cursor); - break; - case Button.ACTION: + const cursor = this.cursor ? 0 : 1; + success = this.setCursor(cursor); + break; + case Button.ACTION: // Process actions based on current cursor position. - if (this.cursor === 0) { - this.cancelFn && this.cancelFn(); - } else { - success = this.swapAction(); - NavigationManager.getInstance().updateIcons(); - this.cancelFn && this.cancelFn(success); - } - break; + if (this.cursor === 0) { + this.cancelFn && this.cancelFn(); + } else { + success = this.swapAction(); + NavigationManager.getInstance().updateIcons(); + this.cancelFn && this.cancelFn(success); + } + break; } // Plays a select sound effect if an action was successfully processed. diff --git a/src/ui/settings/abstract-control-settings-ui-handler.ts b/src/ui/settings/abstract-control-settings-ui-handler.ts index 477eaf0a68b..69f8eb241d3 100644 --- a/src/ui/settings/abstract-control-settings-ui-handler.ts +++ b/src/ui/settings/abstract-control-settings-ui-handler.ts @@ -449,78 +449,78 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler const cursor = this.cursor + this.scrollCursor; // Calculate the absolute cursor position. const setting = this.setting[Object.keys(this.setting)[cursor]]; switch (button) { - case Button.ACTION: - if (!this.optionCursors || !this.optionValueLabels) { - return false; // TODO: is false correct as default? (previously was `undefined`) - } - if (this.settingBlacklisted.includes(setting) || !setting.includes("BUTTON_")) { - success = false; - } else { - success = this.setSetting(this.scene, setting, 1); - } - break; - case Button.UP: // Move up in the menu. - if (!this.optionValueLabels) { - return false; - } - if (cursor) { // If not at the top, move the cursor up. - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else {// If at the top of the visible items, scroll up. - success = this.setScrollCursor(this.scrollCursor - 1); + case Button.ACTION: + if (!this.optionCursors || !this.optionValueLabels) { + return false; // TODO: is false correct as default? (previously was `undefined`) } - } else { + if (this.settingBlacklisted.includes(setting) || !setting.includes("BUTTON_")) { + success = false; + } else { + success = this.setSetting(this.scene, setting, 1); + } + break; + case Button.UP: // Move up in the menu. + if (!this.optionValueLabels) { + return false; + } + if (cursor) { // If not at the top, move the cursor up. + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else {// If at the top of the visible items, scroll up. + success = this.setScrollCursor(this.scrollCursor - 1); + } + } else { // When at the top of the menu and pressing UP, move to the bottommost item. // First, set the cursor to the last visible element, preparing for the scroll to the end. - const successA = this.setCursor(this.rowsToDisplay - 1); - // Then, adjust the scroll to display the bottommost elements of the menu. - const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); - success = successA && successB; // success is just there to play the little validation sound effect - } - break; - case Button.DOWN: // Move down in the menu. - if (!this.optionValueLabels) { - return false; - } - if (cursor < this.optionValueLabels.length - 1) { - if (this.cursor < this.rowsToDisplay - 1) { - success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { - success = this.setScrollCursor(this.scrollCursor + 1); + const successA = this.setCursor(this.rowsToDisplay - 1); + // Then, adjust the scroll to display the bottommost elements of the menu. + const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); + success = successA && successB; // success is just there to play the little validation sound effect } - } else { + break; + case Button.DOWN: // Move down in the menu. + if (!this.optionValueLabels) { + return false; + } + if (cursor < this.optionValueLabels.length - 1) { + if (this.cursor < this.rowsToDisplay - 1) { + success = this.setCursor(this.cursor + 1); + } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { + success = this.setScrollCursor(this.scrollCursor + 1); + } + } else { // When at the bottom of the menu and pressing DOWN, move to the topmost item. // First, set the cursor to the first visible element, resetting the scroll to the top. - const successA = this.setCursor(0); - // Then, reset the scroll to start from the first element of the menu. - const successB = this.setScrollCursor(0); - success = successA && successB; // Indicates a successful cursor and scroll adjustment. - } - break; - case Button.LEFT: // Move selection left within the current option set. - if (!this.optionCursors || !this.optionValueLabels) { - return false; // TODO: is false correct as default? (previously was `undefined`) - } - if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { - success = false; - } else if (this.optionCursors[cursor]) { - success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); - } - break; - case Button.RIGHT: // Move selection right within the current option set. - if (!this.optionCursors || !this.optionValueLabels) { - return false; // TODO: is false correct as default? (previously was `undefined`) - } - if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { - success = false; - } else if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { - success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); - } - break; - case Button.CYCLE_FORM: - case Button.CYCLE_SHINY: - success = this.navigationContainer.navigate(button); - break; + const successA = this.setCursor(0); + // Then, reset the scroll to start from the first element of the menu. + const successB = this.setScrollCursor(0); + success = successA && successB; // Indicates a successful cursor and scroll adjustment. + } + break; + case Button.LEFT: // Move selection left within the current option set. + if (!this.optionCursors || !this.optionValueLabels) { + return false; // TODO: is false correct as default? (previously was `undefined`) + } + if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { + success = false; + } else if (this.optionCursors[cursor]) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); + } + break; + case Button.RIGHT: // Move selection right within the current option set. + if (!this.optionCursors || !this.optionValueLabels) { + return false; // TODO: is false correct as default? (previously was `undefined`) + } + if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { + success = false; + } else if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); + } + break; + case Button.CYCLE_FORM: + case Button.CYCLE_SHINY: + success = this.navigationContainer.navigate(button); + break; } } diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index 47eceddc813..83219e1ef5a 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -224,59 +224,59 @@ export default class AbstractSettingsUiHandler extends UiHandler { } else { const cursor = this.cursor + this.scrollCursor; switch (button) { - case Button.UP: - if (cursor) { - if (this.cursor) { - success = this.setCursor(this.cursor - 1); + case Button.UP: + if (cursor) { + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else { + success = this.setScrollCursor(this.scrollCursor - 1); + } } else { - success = this.setScrollCursor(this.scrollCursor - 1); - } - } else { // When at the top of the menu and pressing UP, move to the bottommost item. // First, set the cursor to the last visible element, preparing for the scroll to the end. - const successA = this.setCursor(this.rowsToDisplay - 1); - // Then, adjust the scroll to display the bottommost elements of the menu. - const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); - success = successA && successB; // success is just there to play the little validation sound effect - } - break; - case Button.DOWN: - if (cursor < this.optionValueLabels.length - 1) { - if (this.cursor < this.rowsToDisplay - 1) {// if the visual cursor is in the frame of 0 to 8 - success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { - success = this.setScrollCursor(this.scrollCursor + 1); + const successA = this.setCursor(this.rowsToDisplay - 1); + // Then, adjust the scroll to display the bottommost elements of the menu. + const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); + success = successA && successB; // success is just there to play the little validation sound effect } - } else { + break; + case Button.DOWN: + if (cursor < this.optionValueLabels.length - 1) { + if (this.cursor < this.rowsToDisplay - 1) {// if the visual cursor is in the frame of 0 to 8 + success = this.setCursor(this.cursor + 1); + } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { + success = this.setScrollCursor(this.scrollCursor + 1); + } + } else { // When at the bottom of the menu and pressing DOWN, move to the topmost item. // First, set the cursor to the first visible element, resetting the scroll to the top. - const successA = this.setCursor(0); - // Then, reset the scroll to start from the first element of the menu. - const successB = this.setScrollCursor(0); - success = successA && successB; // Indicates a successful cursor and scroll adjustment. - } - break; - case Button.LEFT: - if (this.optionCursors[cursor]) {// Moves the option cursor left, if possible. - success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); - } - break; - case Button.RIGHT: + const successA = this.setCursor(0); + // Then, reset the scroll to start from the first element of the menu. + const successB = this.setScrollCursor(0); + success = successA && successB; // Indicates a successful cursor and scroll adjustment. + } + break; + case Button.LEFT: + if (this.optionCursors[cursor]) {// Moves the option cursor left, if possible. + success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); + } + break; + case Button.RIGHT: // Moves the option cursor right, if possible. - if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { - success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); - } - break; - case Button.CYCLE_FORM: - case Button.CYCLE_SHINY: - success = this.navigationContainer.navigate(button); - break; - case Button.ACTION: - const setting: Setting = this.settings[cursor]; - if (setting?.activatable) { - success = this.activateSetting(setting); - } - break; + if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); + } + break; + case Button.CYCLE_FORM: + case Button.CYCLE_SHINY: + success = this.navigationContainer.navigate(button); + break; + case Button.ACTION: + const setting: Setting = this.settings[cursor]; + if (setting?.activatable) { + success = this.activateSetting(setting); + } + break; } } @@ -295,9 +295,9 @@ export default class AbstractSettingsUiHandler extends UiHandler { */ activateSetting(setting: Setting): boolean { switch (setting.key) { - case SettingKeys.Move_Touch_Controls: - this.scene.inputController.moveTouchControlsHandler.enableConfigurationMode(this.getUi(), this.scene); - return true; + case SettingKeys.Move_Touch_Controls: + this.scene.inputController.moveTouchControlsHandler.enableConfigurationMode(this.getUi(), this.scene); + return true; } return false; } diff --git a/src/ui/settings/navigationMenu.ts b/src/ui/settings/navigationMenu.ts index 45209c220fa..ab86fa1569a 100644 --- a/src/ui/settings/navigationMenu.ts +++ b/src/ui/settings/navigationMenu.ts @@ -198,12 +198,12 @@ export default class NavigationMenu extends Phaser.GameObjects.Container { navigate(button: Button): boolean { const navigationManager = NavigationManager.getInstance(); switch (button) { - case Button.CYCLE_FORM: - navigationManager.navigate(this.scene, LEFT); - return true; - case Button.CYCLE_SHINY: - navigationManager.navigate(this.scene, RIGHT); - return true; + case Button.CYCLE_FORM: + navigationManager.navigate(this.scene, LEFT); + return true; + case Button.CYCLE_SHINY: + navigationManager.navigate(this.scene, RIGHT); + return true; } return false; } diff --git a/src/ui/settings/settings-display-ui-handler.ts b/src/ui/settings/settings-display-ui-handler.ts index 3d602c50a78..a25dbf87b7d 100644 --- a/src/ui/settings/settings-display-ui-handler.ts +++ b/src/ui/settings/settings-display-ui-handler.ts @@ -23,79 +23,79 @@ export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler if (languageIndex >= 0) { const currentLocale = localStorage.getItem("prLang"); switch (currentLocale) { - case "en": - this.settings[languageIndex].options[0] = { - value: "English", - label: "English", - }; - break; - case "es": - this.settings[languageIndex].options[0] = { - value: "Español", - label: "Español", - }; - break; - case "it": - this.settings[languageIndex].options[0] = { - value: "Italiano", - label: "Italiano", - }; - break; - case "fr": - this.settings[languageIndex].options[0] = { - value: "Français", - label: "Français", - }; - break; - case "de": - this.settings[languageIndex].options[0] = { - value: "Deutsch", - label: "Deutsch", - }; - break; - case "pt-BR": - this.settings[languageIndex].options[0] = { - value: "Português (BR)", - label: "Português (BR)", - }; - break; - case "zh-CN": - this.settings[languageIndex].options[0] = { - value: "简体中文", - label: "简体中文", - }; - break; - case "zh-TW": - this.settings[languageIndex].options[0] = { - value: "繁體中文", - label: "繁體中文", - }; - break; - case "ko": - case "ko-KR": - this.settings[languageIndex].options[0] = { - value: "한국어", - label: "한국어", - }; - break; - case "ja": - this.settings[languageIndex].options[0] = { - value: "日本語", - label: "日本語", - }; - break; - case "ca-ES": - this.settings[languageIndex].options[0] = { - value: "Català", - label: "Català", - }; - break; - default: - this.settings[languageIndex].options[0] = { - value: "English", - label: "English", - }; - break; + case "en": + this.settings[languageIndex].options[0] = { + value: "English", + label: "English", + }; + break; + case "es": + this.settings[languageIndex].options[0] = { + value: "Español", + label: "Español", + }; + break; + case "it": + this.settings[languageIndex].options[0] = { + value: "Italiano", + label: "Italiano", + }; + break; + case "fr": + this.settings[languageIndex].options[0] = { + value: "Français", + label: "Français", + }; + break; + case "de": + this.settings[languageIndex].options[0] = { + value: "Deutsch", + label: "Deutsch", + }; + break; + case "pt-BR": + this.settings[languageIndex].options[0] = { + value: "Português (BR)", + label: "Português (BR)", + }; + break; + case "zh-CN": + this.settings[languageIndex].options[0] = { + value: "简体中文", + label: "简体中文", + }; + break; + case "zh-TW": + this.settings[languageIndex].options[0] = { + value: "繁體中文", + label: "繁體中文", + }; + break; + case "ko": + case "ko-KR": + this.settings[languageIndex].options[0] = { + value: "한국어", + label: "한국어", + }; + break; + case "ja": + this.settings[languageIndex].options[0] = { + value: "日本語", + label: "日本語", + }; + break; + case "ca-ES": + this.settings[languageIndex].options[0] = { + value: "Català", + label: "Català", + }; + break; + default: + this.settings[languageIndex].options[0] = { + value: "English", + label: "English", + }; + break; } } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 2be89052bc1..bb999dc736a 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1303,119 +1303,119 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } else if (this.startCursorObj.visible) { // this checks to see if the start button is selected switch (button) { - case Button.ACTION: - if (this.tryStart(true)) { - success = true; - } else { - error = true; - } - break; - case Button.UP: + case Button.ACTION: + if (this.tryStart(true)) { + success = true; + } else { + error = true; + } + break; + case Button.UP: // UP from start button: go to pokemon in team if any, otherwise filter - this.startCursorObj.setVisible(false); - if (this.starterSpecies.length > 0) { - this.starterIconsCursorIndex = this.starterSpecies.length - 1; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - } else { + this.startCursorObj.setVisible(false); + if (this.starterSpecies.length > 0) { + this.starterIconsCursorIndex = this.starterSpecies.length - 1; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + } else { + this.startCursorObj.setVisible(false); + this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); + this.setFilterMode(true); + } + success = true; + break; + case Button.DOWN: + // DOWN from start button: Go to filters this.startCursorObj.setVisible(false); this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); this.setFilterMode(true); - } - success = true; - break; - case Button.DOWN: - // DOWN from start button: Go to filters - this.startCursorObj.setVisible(false); - this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); - this.setFilterMode(true); - success = true; - break; - case Button.LEFT: - if (numberOfStarters > 0) { - this.startCursorObj.setVisible(false); - this.cursorObj.setVisible(true); - this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows - 1) * 9 + 8); // set last column success = true; - } - break; - case Button.RIGHT: - if (numberOfStarters > 0) { - this.startCursorObj.setVisible(false); - this.cursorObj.setVisible(true); - this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows - 1) * 9); // set first column - success = true; - } - break; + break; + case Button.LEFT: + if (numberOfStarters > 0) { + this.startCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows - 1) * 9 + 8); // set last column + success = true; + } + break; + case Button.RIGHT: + if (numberOfStarters > 0) { + this.startCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows - 1) * 9); // set first column + success = true; + } + break; } } else if (this.filterMode) { switch (button) { - case Button.LEFT: - if (this.filterBarCursor > 0) { - success = this.setCursor(this.filterBarCursor - 1); - } else { - success = this.setCursor(this.filterBar.numFilters - 1); - } - break; - case Button.RIGHT: - if (this.filterBarCursor < this.filterBar.numFilters - 1) { - success = this.setCursor(this.filterBarCursor + 1); - } else { - success = this.setCursor(0); - } - break; - case Button.UP: - if (this.filterBar.openDropDown) { - success = this.filterBar.decDropDownCursor(); - } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { - // UP from the last filter, move to start button - this.setFilterMode(false); - this.cursorObj.setVisible(false); - this.startCursorObj.setVisible(true); - success = true; - } else if (numberOfStarters > 0) { - // UP from filter bar to bottom of Pokemon list - this.setFilterMode(false); - this.scrollCursor = Math.max(0, numOfRows - 9); - this.updateScroll(); - const proportion = (this.filterBarCursor + 0.5) / this.filterBar.numFilters; - const targetCol = Math.min(8, Math.floor(proportion * 11)); - if (numberOfStarters % 9 > targetCol) { - this.setCursor(numberOfStarters - (numberOfStarters) % 9 + targetCol); + case Button.LEFT: + if (this.filterBarCursor > 0) { + success = this.setCursor(this.filterBarCursor - 1); } else { - this.setCursor(Math.max(numberOfStarters - (numberOfStarters) % 9 + targetCol - 9, 0)); + success = this.setCursor(this.filterBar.numFilters - 1); + } + break; + case Button.RIGHT: + if (this.filterBarCursor < this.filterBar.numFilters - 1) { + success = this.setCursor(this.filterBarCursor + 1); + } else { + success = this.setCursor(0); + } + break; + case Button.UP: + if (this.filterBar.openDropDown) { + success = this.filterBar.decDropDownCursor(); + } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { + // UP from the last filter, move to start button + this.setFilterMode(false); + this.cursorObj.setVisible(false); + this.startCursorObj.setVisible(true); + success = true; + } else if (numberOfStarters > 0) { + // UP from filter bar to bottom of Pokemon list + this.setFilterMode(false); + this.scrollCursor = Math.max(0, numOfRows - 9); + this.updateScroll(); + const proportion = (this.filterBarCursor + 0.5) / this.filterBar.numFilters; + const targetCol = Math.min(8, Math.floor(proportion * 11)); + if (numberOfStarters % 9 > targetCol) { + this.setCursor(numberOfStarters - (numberOfStarters) % 9 + targetCol); + } else { + this.setCursor(Math.max(numberOfStarters - (numberOfStarters) % 9 + targetCol - 9, 0)); + } + success = true; + } + break; + case Button.DOWN: + if (this.filterBar.openDropDown) { + success = this.filterBar.incDropDownCursor(); + } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { + // DOWN from the last filter, move to Pokemon in party if any + this.setFilterMode(false); + this.cursorObj.setVisible(false); + this.starterIconsCursorIndex = 0; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + success = true; + } else if (numberOfStarters > 0) { + // DOWN from filter bar to top of Pokemon list + this.setFilterMode(false); + this.scrollCursor = 0; + this.updateScroll(); + const proportion = this.filterBarCursor / Math.max(1, this.filterBar.numFilters - 1); + const targetCol = Math.min(8, Math.floor(proportion * 11)); + this.setCursor(Math.min(targetCol, numberOfStarters)); + success = true; + } + break; + case Button.ACTION: + if (!this.filterBar.openDropDown) { + this.filterBar.toggleDropDown(this.filterBarCursor); + } else { + this.filterBar.toggleOptionState(); } success = true; - } - break; - case Button.DOWN: - if (this.filterBar.openDropDown) { - success = this.filterBar.incDropDownCursor(); - } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { - // DOWN from the last filter, move to Pokemon in party if any - this.setFilterMode(false); - this.cursorObj.setVisible(false); - this.starterIconsCursorIndex = 0; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - success = true; - } else if (numberOfStarters > 0) { - // DOWN from filter bar to top of Pokemon list - this.setFilterMode(false); - this.scrollCursor = 0; - this.updateScroll(); - const proportion = this.filterBarCursor / Math.max(1, this.filterBar.numFilters - 1); - const targetCol = Math.min(8, Math.floor(proportion * 11)); - this.setCursor(Math.min(targetCol, numberOfStarters)); - success = true; - } - break; - case Button.ACTION: - if (!this.filterBar.openDropDown) { - this.filterBar.toggleDropDown(this.filterBarCursor); - } else { - this.filterBar.toggleOptionState(); - } - success = true; - break; + break; } } else { @@ -1856,259 +1856,259 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } else { const props = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); switch (button) { - case Button.CYCLE_SHINY: - if (this.canCycleShiny) { - starterAttributes.shiny = starterAttributes.shiny !== undefined ? !starterAttributes.shiny : false; + case Button.CYCLE_SHINY: + if (this.canCycleShiny) { + starterAttributes.shiny = starterAttributes.shiny !== undefined ? !starterAttributes.shiny : false; - if (starterAttributes.shiny) { + if (starterAttributes.shiny) { // Change to shiny, we need to get the proper default variant - const newProps = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); - const newVariant = starterAttributes.variant ? starterAttributes.variant as Variant : newProps.variant; - this.setSpeciesDetails(this.lastSpecies, true, undefined, undefined, newVariant, undefined, undefined); + const newProps = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); + const newVariant = starterAttributes.variant ? starterAttributes.variant as Variant : newProps.variant; + this.setSpeciesDetails(this.lastSpecies, true, undefined, undefined, newVariant, undefined, undefined); - this.scene.playSound("se/sparkle"); - // Set the variant label to the shiny tint - const tint = getVariantTint(newVariant); - this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant)); - this.pokemonShinyIcon.setTint(tint); - this.pokemonShinyIcon.setVisible(true); - } else { - this.setSpeciesDetails(this.lastSpecies, false, undefined, undefined, 0, undefined, undefined); - this.pokemonShinyIcon.setVisible(false); - success = true; - } - } - break; - case Button.CYCLE_FORM: - if (this.canCycleForm) { - const formCount = this.lastSpecies.forms.length; - let newFormIndex = props.formIndex; - do { - newFormIndex = (newFormIndex + 1) % formCount; - if (this.lastSpecies.forms[newFormIndex].isStarterSelectable && this.speciesStarterDexEntry!.caughtAttr! & this.scene.gameData.getFormAttr(newFormIndex)) { // TODO: are those bangs correct? - break; + this.scene.playSound("se/sparkle"); + // Set the variant label to the shiny tint + const tint = getVariantTint(newVariant); + this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant)); + this.pokemonShinyIcon.setTint(tint); + this.pokemonShinyIcon.setVisible(true); + } else { + this.setSpeciesDetails(this.lastSpecies, false, undefined, undefined, 0, undefined, undefined); + this.pokemonShinyIcon.setVisible(false); + success = true; } - } while (newFormIndex !== props.formIndex); - starterAttributes.form = newFormIndex; // store the selected form - this.setSpeciesDetails(this.lastSpecies, undefined, newFormIndex, undefined, undefined, undefined, undefined); - success = true; - } - break; - case Button.CYCLE_GENDER: - if (this.canCycleGender) { - starterAttributes.female = !props.female; - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, !props.female, undefined, undefined, undefined); - success = true; - } - break; - case Button.CYCLE_ABILITY: - if (this.canCycleAbility) { - const abilityCount = this.lastSpecies.getAbilityCount(); - const abilityAttr = this.scene.gameData.starterData[this.lastSpecies.speciesId].abilityAttr; - const hasAbility1 = abilityAttr & AbilityAttr.ABILITY_1; - let newAbilityIndex = this.abilityCursor; - do { - newAbilityIndex = (newAbilityIndex + 1) % abilityCount; - if (newAbilityIndex === 0) { - if (hasAbility1) { + } + break; + case Button.CYCLE_FORM: + if (this.canCycleForm) { + const formCount = this.lastSpecies.forms.length; + let newFormIndex = props.formIndex; + do { + newFormIndex = (newFormIndex + 1) % formCount; + if (this.lastSpecies.forms[newFormIndex].isStarterSelectable && this.speciesStarterDexEntry!.caughtAttr! & this.scene.gameData.getFormAttr(newFormIndex)) { // TODO: are those bangs correct? break; } - } else if (newAbilityIndex === 1) { + } while (newFormIndex !== props.formIndex); + starterAttributes.form = newFormIndex; // store the selected form + this.setSpeciesDetails(this.lastSpecies, undefined, newFormIndex, undefined, undefined, undefined, undefined); + success = true; + } + break; + case Button.CYCLE_GENDER: + if (this.canCycleGender) { + starterAttributes.female = !props.female; + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, !props.female, undefined, undefined, undefined); + success = true; + } + break; + case Button.CYCLE_ABILITY: + if (this.canCycleAbility) { + const abilityCount = this.lastSpecies.getAbilityCount(); + const abilityAttr = this.scene.gameData.starterData[this.lastSpecies.speciesId].abilityAttr; + const hasAbility1 = abilityAttr & AbilityAttr.ABILITY_1; + let newAbilityIndex = this.abilityCursor; + do { + newAbilityIndex = (newAbilityIndex + 1) % abilityCount; + if (newAbilityIndex === 0) { + if (hasAbility1) { + break; + } + } else if (newAbilityIndex === 1) { // If ability 1 and 2 are the same and ability 1 is unlocked, skip over ability 2 - if (this.lastSpecies.ability1 === this.lastSpecies.ability2 && hasAbility1) { - newAbilityIndex = (newAbilityIndex + 1) % abilityCount; - } - break; - } else { - if (abilityAttr & AbilityAttr.ABILITY_HIDDEN) { + if (this.lastSpecies.ability1 === this.lastSpecies.ability2 && hasAbility1) { + newAbilityIndex = (newAbilityIndex + 1) % abilityCount; + } break; + } else { + if (abilityAttr & AbilityAttr.ABILITY_HIDDEN) { + break; + } } - } - } while (newAbilityIndex !== this.abilityCursor); - starterAttributes.ability = newAbilityIndex; // store the selected ability + } while (newAbilityIndex !== this.abilityCursor); + starterAttributes.ability = newAbilityIndex; // store the selected ability - const { visible: tooltipVisible } = this.scene.ui.getTooltip(); + const { visible: tooltipVisible } = this.scene.ui.getTooltip(); - if (tooltipVisible && this.activeTooltip === "ABILITY") { - const newAbility = allAbilities[this.lastSpecies.getAbility(newAbilityIndex)]; - this.scene.ui.editTooltip(`${newAbility.name}`, `${newAbility.description}`); - } + if (tooltipVisible && this.activeTooltip === "ABILITY") { + const newAbility = allAbilities[this.lastSpecies.getAbility(newAbilityIndex)]; + this.scene.ui.editTooltip(`${newAbility.name}`, `${newAbility.description}`); + } - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, newAbilityIndex, undefined); - success = true; - } - break; - case Button.CYCLE_NATURE: - if (this.canCycleNature) { - const natures = this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr); - const natureIndex = natures.indexOf(this.natureCursor); - const newNature = natures[natureIndex < natures.length - 1 ? natureIndex + 1 : 0]; - // store cycled nature as default - starterAttributes.nature = newNature as unknown as integer; - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, newNature, undefined); - success = true; - } - break; - case Button.V: - if (this.canCycleVariant) { - let newVariant = props.variant; - do { - newVariant = (newVariant + 1) % 3; - if (!newVariant) { - if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.DEFAULT_VARIANT) { // TODO: is this bang correct? - break; - } - } else if (newVariant === 1) { - if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_2) { // TODO: is this bang correct? - break; - } - } else { - if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_3) { // TODO: is this bang correct? - break; - } - } - } while (newVariant !== props.variant); - starterAttributes.variant = newVariant; // store the selected variant - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, newVariant as Variant, undefined, undefined); - // Cycle tint based on current sprite tint - const tint = getVariantTint(newVariant as Variant); - this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant as Variant)); - this.pokemonShinyIcon.setTint(tint); - success = true; - } - break; - case Button.UP: - if (!this.starterIconsCursorObj.visible) { - if (currentRow > 0) { - if (this.scrollCursor > 0 && currentRow - this.scrollCursor === 0) { - this.scrollCursor--; - this.updateScroll(); - } - success = this.setCursor(this.cursor - 9); - } else { - this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredStarterContainers[this.cursor]); - this.setFilterMode(true); + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, newAbilityIndex, undefined); success = true; } - } else { - if (this.starterIconsCursorIndex === 0) { + break; + case Button.CYCLE_NATURE: + if (this.canCycleNature) { + const natures = this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr); + const natureIndex = natures.indexOf(this.natureCursor); + const newNature = natures[natureIndex < natures.length - 1 ? natureIndex + 1 : 0]; + // store cycled nature as default + starterAttributes.nature = newNature as unknown as integer; + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, newNature, undefined); + success = true; + } + break; + case Button.V: + if (this.canCycleVariant) { + let newVariant = props.variant; + do { + newVariant = (newVariant + 1) % 3; + if (!newVariant) { + if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.DEFAULT_VARIANT) { // TODO: is this bang correct? + break; + } + } else if (newVariant === 1) { + if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_2) { // TODO: is this bang correct? + break; + } + } else { + if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_3) { // TODO: is this bang correct? + break; + } + } + } while (newVariant !== props.variant); + starterAttributes.variant = newVariant; // store the selected variant + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, newVariant as Variant, undefined, undefined); + // Cycle tint based on current sprite tint + const tint = getVariantTint(newVariant as Variant); + this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant as Variant)); + this.pokemonShinyIcon.setTint(tint); + success = true; + } + break; + case Button.UP: + if (!this.starterIconsCursorObj.visible) { + if (currentRow > 0) { + if (this.scrollCursor > 0 && currentRow - this.scrollCursor === 0) { + this.scrollCursor--; + this.updateScroll(); + } + success = this.setCursor(this.cursor - 9); + } else { + this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredStarterContainers[this.cursor]); + this.setFilterMode(true); + success = true; + } + } else { + if (this.starterIconsCursorIndex === 0) { // Up from first Pokemon in the team > go to filter - this.starterIconsCursorObj.setVisible(false); - this.setSpecies(null); - this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); - this.setFilterMode(true); - } else { - this.starterIconsCursorIndex--; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - } - success = true; - } - break; - case Button.DOWN: - if (!this.starterIconsCursorObj.visible) { - if (currentRow < numOfRows - 1) { // not last row - if (currentRow - this.scrollCursor === 8) { // last row of visible starters - this.scrollCursor++; + this.starterIconsCursorObj.setVisible(false); + this.setSpecies(null); + this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); + this.setFilterMode(true); + } else { + this.starterIconsCursorIndex--; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); } - success = this.setCursor(this.cursor + 9); - this.updateScroll(); - } else if (numOfRows > 1) { - // DOWN from last row of Pokemon > Wrap around to first row - this.scrollCursor = 0; - this.updateScroll(); - success = this.setCursor(this.cursor % 9); - } else { - // DOWN from single row of Pokemon > Go to filters - this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredStarterContainers[this.cursor]); - this.setFilterMode(true); success = true; } - } else { - if (this.starterIconsCursorIndex <= this.starterSpecies.length - 2) { - this.starterIconsCursorIndex++; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); + break; + case Button.DOWN: + if (!this.starterIconsCursorObj.visible) { + if (currentRow < numOfRows - 1) { // not last row + if (currentRow - this.scrollCursor === 8) { // last row of visible starters + this.scrollCursor++; + } + success = this.setCursor(this.cursor + 9); + this.updateScroll(); + } else if (numOfRows > 1) { + // DOWN from last row of Pokemon > Wrap around to first row + this.scrollCursor = 0; + this.updateScroll(); + success = this.setCursor(this.cursor % 9); + } else { + // DOWN from single row of Pokemon > Go to filters + this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredStarterContainers[this.cursor]); + this.setFilterMode(true); + success = true; + } } else { - this.starterIconsCursorObj.setVisible(false); - this.setSpecies(null); - this.startCursorObj.setVisible(true); + if (this.starterIconsCursorIndex <= this.starterSpecies.length - 2) { + this.starterIconsCursorIndex++; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + } else { + this.starterIconsCursorObj.setVisible(false); + this.setSpecies(null); + this.startCursorObj.setVisible(true); + } + success = true; } - success = true; - } - break; - case Button.LEFT: - if (!this.starterIconsCursorObj.visible) { - if (this.cursor % 9 !== 0) { - success = this.setCursor(this.cursor - 1); - } else { + break; + case Button.LEFT: + if (!this.starterIconsCursorObj.visible) { + if (this.cursor % 9 !== 0) { + success = this.setCursor(this.cursor - 1); + } else { // LEFT from filtered Pokemon, on the left edge - if (this.starterSpecies.length === 0) { + if (this.starterSpecies.length === 0) { // no starter in team > wrap around to the last column - success = this.setCursor(this.cursor + Math.min(8, numberOfStarters - this.cursor)); + success = this.setCursor(this.cursor + Math.min(8, numberOfStarters - this.cursor)); - } else if (onScreenCurrentRow < 7) { + } else if (onScreenCurrentRow < 7) { // at least one pokemon in team > for the first 7 rows, go to closest starter - this.cursorObj.setVisible(false); - this.starterIconsCursorIndex = findClosestStarterIndex(this.cursorObj.y - 1, this.starterSpecies.length); - this.moveStarterIconsCursor(this.starterIconsCursorIndex); + this.cursorObj.setVisible(false); + this.starterIconsCursorIndex = findClosestStarterIndex(this.cursorObj.y - 1, this.starterSpecies.length); + this.moveStarterIconsCursor(this.starterIconsCursorIndex); - } else { + } else { // at least one pokemon in team > from the bottom 2 rows, go to start run button - this.cursorObj.setVisible(false); - this.setSpecies(null); - this.startCursorObj.setVisible(true); + this.cursorObj.setVisible(false); + this.setSpecies(null); + this.startCursorObj.setVisible(true); + } + success = true; } - success = true; - } - } else if (numberOfStarters > 0) { + } else if (numberOfStarters > 0) { // LEFT from team > Go to closest filtered Pokemon - const closestRowIndex = findClosestStarterRow(this.starterIconsCursorIndex, onScreenNumberOfRows); - this.starterIconsCursorObj.setVisible(false); - this.cursorObj.setVisible(true); - this.setCursor(Math.min(onScreenFirstIndex + closestRowIndex * 9 + 8, onScreenLastIndex)); - success = true; - } else { - // LEFT from team and no Pokemon in filter > do nothing - success = false; - } - break; - case Button.RIGHT: - if (!this.starterIconsCursorObj.visible) { - // is not right edge - if (this.cursor % 9 < (currentRow < numOfRows - 1 ? 8 : (numberOfStarters - 1) % 9)) { - success = this.setCursor(this.cursor + 1); - } else { - // RIGHT from filtered Pokemon, on the right edge - if (this.starterSpecies.length === 0) { - // no selected starter in team > wrap around to the first column - success = this.setCursor(this.cursor - Math.min(8, this.cursor % 9)); - - } else if (onScreenCurrentRow < 7) { - // at least one pokemon in team > for the first 7 rows, go to closest starter - this.cursorObj.setVisible(false); - this.starterIconsCursorIndex = findClosestStarterIndex(this.cursorObj.y - 1, this.starterSpecies.length); - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - - } else { - // at least one pokemon in team > from the bottom 2 rows, go to start run button - this.cursorObj.setVisible(false); - this.setSpecies(null); - this.startCursorObj.setVisible(true); - } + const closestRowIndex = findClosestStarterRow(this.starterIconsCursorIndex, onScreenNumberOfRows); + this.starterIconsCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(Math.min(onScreenFirstIndex + closestRowIndex * 9 + 8, onScreenLastIndex)); success = true; + } else { + // LEFT from team and no Pokemon in filter > do nothing + success = false; } - } else if (numberOfStarters > 0) { + break; + case Button.RIGHT: + if (!this.starterIconsCursorObj.visible) { + // is not right edge + if (this.cursor % 9 < (currentRow < numOfRows - 1 ? 8 : (numberOfStarters - 1) % 9)) { + success = this.setCursor(this.cursor + 1); + } else { + // RIGHT from filtered Pokemon, on the right edge + if (this.starterSpecies.length === 0) { + // no selected starter in team > wrap around to the first column + success = this.setCursor(this.cursor - Math.min(8, this.cursor % 9)); + + } else if (onScreenCurrentRow < 7) { + // at least one pokemon in team > for the first 7 rows, go to closest starter + this.cursorObj.setVisible(false); + this.starterIconsCursorIndex = findClosestStarterIndex(this.cursorObj.y - 1, this.starterSpecies.length); + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + + } else { + // at least one pokemon in team > from the bottom 2 rows, go to start run button + this.cursorObj.setVisible(false); + this.setSpecies(null); + this.startCursorObj.setVisible(true); + } + success = true; + } + } else if (numberOfStarters > 0) { // RIGHT from team > Go to closest filtered Pokemon - const closestRowIndex = findClosestStarterRow(this.starterIconsCursorIndex, onScreenNumberOfRows); - this.starterIconsCursorObj.setVisible(false); - this.cursorObj.setVisible(true); - this.setCursor(Math.min(onScreenFirstIndex + closestRowIndex * 9, onScreenLastIndex - (onScreenLastIndex % 9))); - success = true; - } else { + const closestRowIndex = findClosestStarterRow(this.starterIconsCursorIndex, onScreenNumberOfRows); + this.starterIconsCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(Math.min(onScreenFirstIndex + closestRowIndex * 9, onScreenLastIndex - (onScreenLastIndex % 9))); + success = true; + } else { // RIGHT from team and no Pokemon in filter > do nothing - success = false; - } - break; + success = false; + } + break; } } } @@ -2210,29 +2210,29 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (gamepadType === "touch") { gamepadType = "keyboard"; switch (iconSetting) { - case SettingKeyboard.Button_Cycle_Shiny: - iconPath = "R.png"; - break; - case SettingKeyboard.Button_Cycle_Form: - iconPath = "F.png"; - break; - case SettingKeyboard.Button_Cycle_Gender: - iconPath = "G.png"; - break; - case SettingKeyboard.Button_Cycle_Ability: - iconPath = "E.png"; - break; - case SettingKeyboard.Button_Cycle_Nature: - iconPath = "N.png"; - break; - case SettingKeyboard.Button_Cycle_Variant: - iconPath = "V.png"; - break; - case SettingKeyboard.Button_Stats: - iconPath = "C.png"; - break; - default: - break; + case SettingKeyboard.Button_Cycle_Shiny: + iconPath = "R.png"; + break; + case SettingKeyboard.Button_Cycle_Form: + iconPath = "F.png"; + break; + case SettingKeyboard.Button_Cycle_Gender: + iconPath = "G.png"; + break; + case SettingKeyboard.Button_Cycle_Ability: + iconPath = "E.png"; + break; + case SettingKeyboard.Button_Cycle_Nature: + iconPath = "N.png"; + break; + case SettingKeyboard.Button_Cycle_Variant: + iconPath = "V.png"; + break; + case SettingKeyboard.Button_Stats: + iconPath = "C.png"; + break; + default: + break; } } else { iconPath = this.scene.inputController?.getIconForLatestInputRecorded(iconSetting); @@ -2323,12 +2323,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { getValueLimit(): integer { const valueLimit = new Utils.IntegerHolder(0); switch (this.scene.gameMode.modeId) { - case GameModes.ENDLESS: - case GameModes.SPLICED_ENDLESS: - valueLimit.value = 15; - break; - default: - valueLimit.value = 10; + case GameModes.ENDLESS: + case GameModes.SPLICED_ENDLESS: + valueLimit.value = 15; + break; + default: + valueLimit.value = 10; } Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_POINTS, valueLimit); @@ -2532,22 +2532,22 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const sort = this.filterBar.getVals(DropDownColumn.SORT)[0]; this.filteredStarterContainers.sort((a, b) => { switch (sort.val) { - default: - break; - case SortCriteria.NUMBER: - return (a.species.speciesId - b.species.speciesId) * -sort.dir; - case SortCriteria.COST: - return (a.cost - b.cost) * -sort.dir; - case SortCriteria.CANDY: - const candyCountA = this.scene.gameData.starterData[a.species.speciesId].candyCount; - const candyCountB = this.scene.gameData.starterData[b.species.speciesId].candyCount; - return (candyCountA - candyCountB) * -sort.dir; - case SortCriteria.IV: - const avgIVsA = this.scene.gameData.dexData[a.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[a.species.speciesId].ivs.length; - const avgIVsB = this.scene.gameData.dexData[b.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[b.species.speciesId].ivs.length; - return (avgIVsA - avgIVsB) * -sort.dir; - case SortCriteria.NAME: - return a.species.name.localeCompare(b.species.name) * -sort.dir; + default: + break; + case SortCriteria.NUMBER: + return (a.species.speciesId - b.species.speciesId) * -sort.dir; + case SortCriteria.COST: + return (a.cost - b.cost) * -sort.dir; + case SortCriteria.CANDY: + const candyCountA = this.scene.gameData.starterData[a.species.speciesId].candyCount; + const candyCountB = this.scene.gameData.starterData[b.species.speciesId].candyCount; + return (candyCountA - candyCountB) * -sort.dir; + case SortCriteria.IV: + const avgIVsA = this.scene.gameData.dexData[a.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[a.species.speciesId].ivs.length; + const avgIVsB = this.scene.gameData.dexData[b.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[b.species.speciesId].ivs.length; + return (avgIVsA - avgIVsB) * -sort.dir; + case SortCriteria.NAME: + return a.species.name.localeCompare(b.species.name) * -sort.dir; } return 0; }); @@ -3362,16 +3362,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler { starter.label.setText(valueStr); let textStyle: TextStyle; switch (baseStarterValue - starterValue) { - case 0: - textStyle = TextStyle.WINDOW; - break; - case 1: - case 0.5: - textStyle = TextStyle.SUMMARY_BLUE; - break; - default: - textStyle = TextStyle.SUMMARY_GOLD; - break; + case 0: + textStyle = TextStyle.WINDOW; + break; + case 1: + case 0.5: + textStyle = TextStyle.SUMMARY_BLUE; + break; + default: + textStyle = TextStyle.SUMMARY_GOLD; + break; } if (baseStarterValue - starterValue > 0) { starter.label.setColor(this.getTextColor(textStyle)); diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 86b00f512a9..b35c0ca3303 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -406,22 +406,22 @@ export default class SummaryUiHandler extends UiHandler { this.genderText.setShadowColor(getGenderColor(this.pokemon.getGender(true), true)); switch (this.summaryUiMode) { - case SummaryUiMode.DEFAULT: - const page = args.length < 2 ? Page.PROFILE : args[2] as Page; - this.hideMoveEffect(true); - this.setCursor(page); - if (args.length > 3) { - this.selectCallback = args[3]; - } - break; - case SummaryUiMode.LEARN_MOVE: - this.newMove = args[2] as Move; - this.moveSelectFunction = args[3] as Function; + case SummaryUiMode.DEFAULT: + const page = args.length < 2 ? Page.PROFILE : args[2] as Page; + this.hideMoveEffect(true); + this.setCursor(page); + if (args.length > 3) { + this.selectCallback = args[3]; + } + break; + case SummaryUiMode.LEARN_MOVE: + this.newMove = args[2] as Move; + this.moveSelectFunction = args[3] as Function; - this.showMoveEffect(true); - this.setCursor(Page.MOVES); - this.showMoveSelect(); - break; + this.showMoveEffect(true); + this.setCursor(Page.MOVES); + this.showMoveSelect(); + break; } const fromSummary = args.length >= 2; @@ -489,25 +489,25 @@ export default class SummaryUiHandler extends UiHandler { success = true; } else { switch (button) { - case Button.UP: - success = this.setCursor(this.moveCursor ? this.moveCursor - 1 : 4); - break; - case Button.DOWN: - success = this.setCursor(this.moveCursor < 4 ? this.moveCursor + 1 : 0); - break; - case Button.LEFT: - this.moveSelect = false; - this.setCursor(Page.STATS); - if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { - this.hideMoveEffect(); - this.destroyBlinkCursor(); - success = true; + case Button.UP: + success = this.setCursor(this.moveCursor ? this.moveCursor - 1 : 4); break; - } else { - this.hideMoveSelect(); - success = true; + case Button.DOWN: + success = this.setCursor(this.moveCursor < 4 ? this.moveCursor + 1 : 0); break; - } + case Button.LEFT: + this.moveSelect = false; + this.setCursor(Page.STATS); + if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { + this.hideMoveEffect(); + this.destroyBlinkCursor(); + success = true; + break; + } else { + this.hideMoveSelect(); + success = true; + break; + } } } } else { @@ -546,35 +546,35 @@ export default class SummaryUiHandler extends UiHandler { } else { const pages = Utils.getEnumValues(Page); switch (button) { - case Button.UP: - case Button.DOWN: - if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { - break; - } else if (!fromPartyMode) { - break; - } - const isDown = button === Button.DOWN; - const party = this.scene.getParty(); - const partyMemberIndex = this.pokemon ? party.indexOf(this.pokemon) : -1; - if ((isDown && partyMemberIndex < party.length - 1) || (!isDown && partyMemberIndex)) { - const page = this.cursor; - this.clear(); - this.show([ party[partyMemberIndex + (isDown ? 1 : -1)], this.summaryUiMode, page ]); - } - break; - case Button.LEFT: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.RIGHT: - if (this.cursor < pages.length - 1) { - success = this.setCursor(this.cursor + 1); - if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.cursor === Page.MOVES) { - this.moveSelect = true; + case Button.UP: + case Button.DOWN: + if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { + break; + } else if (!fromPartyMode) { + break; } - } - break; + const isDown = button === Button.DOWN; + const party = this.scene.getParty(); + const partyMemberIndex = this.pokemon ? party.indexOf(this.pokemon) : -1; + if ((isDown && partyMemberIndex < party.length - 1) || (!isDown && partyMemberIndex)) { + const page = this.cursor; + this.clear(); + this.show([ party[partyMemberIndex + (isDown ? 1 : -1)], this.summaryUiMode, page ]); + } + break; + case Button.LEFT: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.RIGHT: + if (this.cursor < pages.length - 1) { + success = this.setCursor(this.cursor + 1); + if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.cursor === Page.MOVES) { + this.moveSelect = true; + } + } + break; } } } @@ -730,308 +730,308 @@ export default class SummaryUiHandler extends UiHandler { } switch (page) { - case Page.PROFILE: - const profileContainer = this.scene.add.container(0, -pageBg.height); - pageContainer.add(profileContainer); + case Page.PROFILE: + const profileContainer = this.scene.add.container(0, -pageBg.height); + pageContainer.add(profileContainer); - // TODO: should add field for original trainer name to Pokemon object, to support gift/traded Pokemon from MEs - const trainerText = addBBCodeTextObject(this.scene, 7, 12, `${i18next.t("pokemonSummary:ot")}/${getBBCodeFrag(loggedInUser?.username || i18next.t("pokemonSummary:unknown"), this.scene.gameData.gender === PlayerGender.FEMALE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE)}`, TextStyle.SUMMARY_ALT); - trainerText.setOrigin(0, 0); - profileContainer.add(trainerText); + // TODO: should add field for original trainer name to Pokemon object, to support gift/traded Pokemon from MEs + const trainerText = addBBCodeTextObject(this.scene, 7, 12, `${i18next.t("pokemonSummary:ot")}/${getBBCodeFrag(loggedInUser?.username || i18next.t("pokemonSummary:unknown"), this.scene.gameData.gender === PlayerGender.FEMALE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE)}`, TextStyle.SUMMARY_ALT); + trainerText.setOrigin(0, 0); + profileContainer.add(trainerText); - const trainerIdText = addTextObject(this.scene, 174, 12, this.scene.gameData.trainerId.toString(), TextStyle.SUMMARY_ALT); - trainerIdText.setOrigin(0, 0); - profileContainer.add(trainerIdText); + const trainerIdText = addTextObject(this.scene, 174, 12, this.scene.gameData.trainerId.toString(), TextStyle.SUMMARY_ALT); + trainerIdText.setOrigin(0, 0); + profileContainer.add(trainerIdText); - const typeLabel = addTextObject(this.scene, 7, 28, `${i18next.t("pokemonSummary:type")}/`, TextStyle.WINDOW_ALT); - typeLabel.setOrigin(0, 0); - profileContainer.add(typeLabel); + const typeLabel = addTextObject(this.scene, 7, 28, `${i18next.t("pokemonSummary:type")}/`, TextStyle.WINDOW_ALT); + typeLabel.setOrigin(0, 0); + profileContainer.add(typeLabel); - const getTypeIcon = (index: integer, type: Type, tera: boolean = false) => { - const xCoord = typeLabel.width * typeLabel.scale + 9 + 34 * index; - const typeIcon = !tera - ? this.scene.add.sprite(xCoord, 42, Utils.getLocalizedSpriteKey("types"), Type[type].toLowerCase()) - : this.scene.add.sprite(xCoord, 42, "type_tera"); - if (tera) { - typeIcon.setScale(0.5); - const typeRgb = getTypeRgb(type); - typeIcon.setTint(Phaser.Display.Color.GetColor(typeRgb[0], typeRgb[1], typeRgb[2])); + const getTypeIcon = (index: integer, type: Type, tera: boolean = false) => { + const xCoord = typeLabel.width * typeLabel.scale + 9 + 34 * index; + const typeIcon = !tera + ? this.scene.add.sprite(xCoord, 42, Utils.getLocalizedSpriteKey("types"), Type[type].toLowerCase()) + : this.scene.add.sprite(xCoord, 42, "type_tera"); + if (tera) { + typeIcon.setScale(0.5); + const typeRgb = getTypeRgb(type); + typeIcon.setTint(Phaser.Display.Color.GetColor(typeRgb[0], typeRgb[1], typeRgb[2])); + } + typeIcon.setOrigin(0, 1); + return typeIcon; + }; + + const types = this.pokemon?.getTypes(false, false, true)!; // TODO: is this bang correct? + profileContainer.add(getTypeIcon(0, types[0])); + if (types.length > 1) { + profileContainer.add(getTypeIcon(1, types[1])); + } + if (this.pokemon?.isTerastallized()) { + profileContainer.add(getTypeIcon(types.length, this.pokemon.getTeraType(), true)); } - typeIcon.setOrigin(0, 1); - return typeIcon; - }; - const types = this.pokemon?.getTypes(false, false, true)!; // TODO: is this bang correct? - profileContainer.add(getTypeIcon(0, types[0])); - if (types.length > 1) { - profileContainer.add(getTypeIcon(1, types[1])); - } - if (this.pokemon?.isTerastallized()) { - profileContainer.add(getTypeIcon(types.length, this.pokemon.getTeraType(), true)); - } + if (this.pokemon?.getLuck()) { + const luckLabelText = addTextObject(this.scene, 141, 28, i18next.t("common:luckIndicator"), TextStyle.SUMMARY_ALT); + luckLabelText.setOrigin(0, 0); + profileContainer.add(luckLabelText); - if (this.pokemon?.getLuck()) { - const luckLabelText = addTextObject(this.scene, 141, 28, i18next.t("common:luckIndicator"), TextStyle.SUMMARY_ALT); - luckLabelText.setOrigin(0, 0); - profileContainer.add(luckLabelText); + const luckText = addTextObject(this.scene, 141 + luckLabelText.displayWidth + 2, 28, this.pokemon.getLuck().toString(), TextStyle.SUMMARY); + luckText.setOrigin(0, 0); + luckText.setTint(getVariantTint((Math.min(this.pokemon.getLuck() - 1, 2)) as Variant)); + profileContainer.add(luckText); + } - const luckText = addTextObject(this.scene, 141 + luckLabelText.displayWidth + 2, 28, this.pokemon.getLuck().toString(), TextStyle.SUMMARY); - luckText.setOrigin(0, 0); - luckText.setTint(getVariantTint((Math.min(this.pokemon.getLuck() - 1, 2)) as Variant)); - profileContainer.add(luckText); - } - - this.abilityContainer = { - labelImage: this.scene.add.image(0, 0, "summary_profile_ability"), - ability: this.pokemon?.getAbility(true)!, // TODO: is this bang correct? - nameText: null, - descriptionText: null }; - - const allAbilityInfo = [ this.abilityContainer ]; // Creates an array to iterate through - // Only add to the array and set up displaying a passive if it's unlocked - if (this.pokemon?.hasPassive()) { - this.passiveContainer = { - labelImage: this.scene.add.image(0, 0, "summary_profile_passive"), - ability: this.pokemon.getPassiveAbility(), + this.abilityContainer = { + labelImage: this.scene.add.image(0, 0, "summary_profile_ability"), + ability: this.pokemon?.getAbility(true)!, // TODO: is this bang correct? nameText: null, descriptionText: null }; - allAbilityInfo.push(this.passiveContainer); - // Sets up the pixel button prompt image - this.abilityPrompt = this.scene.add.image(0, 0, !this.scene.inputController?.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a"); - this.abilityPrompt.setPosition(8, 43); - this.abilityPrompt.setVisible(true); - this.abilityPrompt.setOrigin(0, 0); - profileContainer.add(this.abilityPrompt); - } + const allAbilityInfo = [ this.abilityContainer ]; // Creates an array to iterate through + // Only add to the array and set up displaying a passive if it's unlocked + if (this.pokemon?.hasPassive()) { + this.passiveContainer = { + labelImage: this.scene.add.image(0, 0, "summary_profile_passive"), + ability: this.pokemon.getPassiveAbility(), + nameText: null, + descriptionText: null }; + allAbilityInfo.push(this.passiveContainer); - allAbilityInfo.forEach(abilityInfo => { - abilityInfo.labelImage.setPosition(17, 43); - abilityInfo.labelImage.setVisible(true); - abilityInfo.labelImage.setOrigin(0, 0); - profileContainer.add(abilityInfo.labelImage); - - abilityInfo.nameText = addTextObject(this.scene, 7, 66, abilityInfo.ability?.name!, TextStyle.SUMMARY_ALT); // TODO: is this bang correct? - abilityInfo.nameText.setOrigin(0, 1); - profileContainer.add(abilityInfo.nameText); - - abilityInfo.descriptionText = addTextObject(this.scene, 7, 69, abilityInfo.ability?.description!, TextStyle.WINDOW_ALT, { wordWrap: { width: 1224 }}); // TODO: is this bang correct? - abilityInfo.descriptionText.setOrigin(0, 0); - profileContainer.add(abilityInfo.descriptionText); - - // Sets up the mask that hides the description text to give an illusion of scrolling - const descriptionTextMaskRect = this.scene.make.graphics({}); - descriptionTextMaskRect.setScale(6); - descriptionTextMaskRect.fillStyle(0xFFFFFF); - descriptionTextMaskRect.beginPath(); - descriptionTextMaskRect.fillRect(110, 90.5, 206, 31); - - const abilityDescriptionTextMask = descriptionTextMaskRect.createGeometryMask(); - - abilityInfo.descriptionText.setMask(abilityDescriptionTextMask); - - const abilityDescriptionLineCount = Math.floor(abilityInfo.descriptionText.displayHeight / 14.83); - - // Animates the description text moving upwards - if (abilityDescriptionLineCount > 2) { - abilityInfo.descriptionText.setY(69); - this.descriptionScrollTween = this.scene.tweens.add({ - targets: abilityInfo.descriptionText, - delay: Utils.fixedInt(2000), - loop: -1, - hold: Utils.fixedInt(2000), - duration: Utils.fixedInt((abilityDescriptionLineCount - 2) * 2000), - y: `-=${14.83 * (abilityDescriptionLineCount - 2)}` - }); + // Sets up the pixel button prompt image + this.abilityPrompt = this.scene.add.image(0, 0, !this.scene.inputController?.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a"); + this.abilityPrompt.setPosition(8, 43); + this.abilityPrompt.setVisible(true); + this.abilityPrompt.setOrigin(0, 0); + profileContainer.add(this.abilityPrompt); } - }); - // Turn off visibility of passive info by default - this.passiveContainer?.labelImage.setVisible(false); - this.passiveContainer?.nameText?.setVisible(false); - this.passiveContainer?.descriptionText?.setVisible(false); - const closeFragment = getBBCodeFrag("", TextStyle.WINDOW_ALT); - const rawNature = Utils.toReadableString(Nature[this.pokemon?.getNature()!]); // TODO: is this bang correct? - const nature = `${getBBCodeFrag(Utils.toReadableString(getNatureName(this.pokemon?.getNature()!)), TextStyle.SUMMARY_RED)}${closeFragment}`; // TODO: is this bang correct? + allAbilityInfo.forEach(abilityInfo => { + abilityInfo.labelImage.setPosition(17, 43); + abilityInfo.labelImage.setVisible(true); + abilityInfo.labelImage.setOrigin(0, 0); + profileContainer.add(abilityInfo.labelImage); - const memoString = i18next.t("pokemonSummary:memoString", { - metFragment: i18next.t(`pokemonSummary:metFragment.${this.pokemon?.metBiome === -1 ? "apparently" : "normal"}`, { - biome: `${getBBCodeFrag(getBiomeName(this.pokemon?.metBiome!), TextStyle.SUMMARY_RED)}${closeFragment}`, // TODO: is this bang correct? - level: `${getBBCodeFrag(this.pokemon?.metLevel.toString()!, TextStyle.SUMMARY_RED)}${closeFragment}`, // TODO: is this bang correct? - wave: `${getBBCodeFrag((this.pokemon?.metWave ? this.pokemon.metWave.toString()! : i18next.t("pokemonSummary:unknownTrainer")), TextStyle.SUMMARY_RED)}${closeFragment}`, - }), - natureFragment: i18next.t(`pokemonSummary:natureFragment.${rawNature}`, { nature: nature }) - }); + abilityInfo.nameText = addTextObject(this.scene, 7, 66, abilityInfo.ability?.name!, TextStyle.SUMMARY_ALT); // TODO: is this bang correct? + abilityInfo.nameText.setOrigin(0, 1); + profileContainer.add(abilityInfo.nameText); - const memoText = addBBCodeTextObject(this.scene, 7, 113, String(memoString), TextStyle.WINDOW_ALT); - memoText.setOrigin(0, 0); - profileContainer.add(memoText); - break; - case Page.STATS: - const statsContainer = this.scene.add.container(0, -pageBg.height); - pageContainer.add(statsContainer); + abilityInfo.descriptionText = addTextObject(this.scene, 7, 69, abilityInfo.ability?.description!, TextStyle.WINDOW_ALT, { wordWrap: { width: 1224 }}); // TODO: is this bang correct? + abilityInfo.descriptionText.setOrigin(0, 0); + profileContainer.add(abilityInfo.descriptionText); - PERMANENT_STATS.forEach((stat, s) => { - const statName = i18next.t(getStatKey(stat)); - const rowIndex = s % 3; - const colIndex = Math.floor(s / 3); + // Sets up the mask that hides the description text to give an illusion of scrolling + const descriptionTextMaskRect = this.scene.make.graphics({}); + descriptionTextMaskRect.setScale(6); + descriptionTextMaskRect.fillStyle(0xFFFFFF); + descriptionTextMaskRect.beginPath(); + descriptionTextMaskRect.fillRect(110, 90.5, 206, 31); - const natureStatMultiplier = getNatureStatMultiplier(this.pokemon?.getNature()!, s); // TODO: is this bang correct? + const abilityDescriptionTextMask = descriptionTextMaskRect.createGeometryMask(); - const statLabel = addTextObject(this.scene, 27 + 115 * colIndex + (colIndex === 1 ? 5 : 0), 56 + 16 * rowIndex, statName, natureStatMultiplier === 1 ? TextStyle.SUMMARY : natureStatMultiplier > 1 ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE); - statLabel.setOrigin(0.5, 0); - statsContainer.add(statLabel); + abilityInfo.descriptionText.setMask(abilityDescriptionTextMask); - const statValueText = stat !== Stat.HP - ? Utils.formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct? - : `${Utils.formatStat(this.pokemon?.hp!, true)}/${Utils.formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct? + const abilityDescriptionLineCount = Math.floor(abilityInfo.descriptionText.displayHeight / 14.83); - const statValue = addTextObject(this.scene, 120 + 88 * colIndex, 56 + 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT); - statValue.setOrigin(1, 0); - statsContainer.add(statValue); - }); + // Animates the description text moving upwards + if (abilityDescriptionLineCount > 2) { + abilityInfo.descriptionText.setY(69); + this.descriptionScrollTween = this.scene.tweens.add({ + targets: abilityInfo.descriptionText, + delay: Utils.fixedInt(2000), + loop: -1, + hold: Utils.fixedInt(2000), + duration: Utils.fixedInt((abilityDescriptionLineCount - 2) * 2000), + y: `-=${14.83 * (abilityDescriptionLineCount - 2)}` + }); + } + }); + // Turn off visibility of passive info by default + this.passiveContainer?.labelImage.setVisible(false); + this.passiveContainer?.nameText?.setVisible(false); + this.passiveContainer?.descriptionText?.setVisible(false); - const itemModifiers = (this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + const closeFragment = getBBCodeFrag("", TextStyle.WINDOW_ALT); + const rawNature = Utils.toReadableString(Nature[this.pokemon?.getNature()!]); // TODO: is this bang correct? + const nature = `${getBBCodeFrag(Utils.toReadableString(getNatureName(this.pokemon?.getNature()!)), TextStyle.SUMMARY_RED)}${closeFragment}`; // TODO: is this bang correct? + + const memoString = i18next.t("pokemonSummary:memoString", { + metFragment: i18next.t(`pokemonSummary:metFragment.${this.pokemon?.metBiome === -1 ? "apparently" : "normal"}`, { + biome: `${getBBCodeFrag(getBiomeName(this.pokemon?.metBiome!), TextStyle.SUMMARY_RED)}${closeFragment}`, // TODO: is this bang correct? + level: `${getBBCodeFrag(this.pokemon?.metLevel.toString()!, TextStyle.SUMMARY_RED)}${closeFragment}`, // TODO: is this bang correct? + wave: `${getBBCodeFrag((this.pokemon?.metWave ? this.pokemon.metWave.toString()! : i18next.t("pokemonSummary:unknownTrainer")), TextStyle.SUMMARY_RED)}${closeFragment}`, + }), + natureFragment: i18next.t(`pokemonSummary:natureFragment.${rawNature}`, { nature: nature }) + }); + + const memoText = addBBCodeTextObject(this.scene, 7, 113, String(memoString), TextStyle.WINDOW_ALT); + memoText.setOrigin(0, 0); + profileContainer.add(memoText); + break; + case Page.STATS: + const statsContainer = this.scene.add.container(0, -pageBg.height); + pageContainer.add(statsContainer); + + PERMANENT_STATS.forEach((stat, s) => { + const statName = i18next.t(getStatKey(stat)); + const rowIndex = s % 3; + const colIndex = Math.floor(s / 3); + + const natureStatMultiplier = getNatureStatMultiplier(this.pokemon?.getNature()!, s); // TODO: is this bang correct? + + const statLabel = addTextObject(this.scene, 27 + 115 * colIndex + (colIndex === 1 ? 5 : 0), 56 + 16 * rowIndex, statName, natureStatMultiplier === 1 ? TextStyle.SUMMARY : natureStatMultiplier > 1 ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE); + statLabel.setOrigin(0.5, 0); + statsContainer.add(statLabel); + + const statValueText = stat !== Stat.HP + ? Utils.formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct? + : `${Utils.formatStat(this.pokemon?.hp!, true)}/${Utils.formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct? + + const statValue = addTextObject(this.scene, 120 + 88 * colIndex, 56 + 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT); + statValue.setOrigin(1, 0); + statsContainer.add(statValue); + }); + + const itemModifiers = (this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.pokemon?.id, this.playerParty) as PokemonHeldItemModifier[]) - .sort(modifierSortFunc); + .sort(modifierSortFunc); - itemModifiers.forEach((item, i) => { - const icon = item.getIcon(this.scene, true); + itemModifiers.forEach((item, i) => { + const icon = item.getIcon(this.scene, true); - icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); - statsContainer.add(icon); + icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); + statsContainer.add(icon); - icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); - icon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip(item.type.name, item.type.getDescription(this.scene), true)); - icon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); - }); + icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); + icon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip(item.type.name, item.type.getDescription(this.scene), true)); + icon.on("pointerout", () => (this.scene as BattleScene).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? - const pkmSpeciesGrowthRate = this.pokemon?.species.growthRate!; // TODO: is this bang correct? - const relLvExp = getLevelRelExp(pkmLvl + 1, pkmSpeciesGrowthRate); - const expRatio = pkmLvl < this.scene.getMaxExpLevel() ? pkmLvlExp / relLvExp : 0; + 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? + const pkmSpeciesGrowthRate = this.pokemon?.species.growthRate!; // TODO: is this bang correct? + const relLvExp = getLevelRelExp(pkmLvl + 1, pkmSpeciesGrowthRate); + const expRatio = pkmLvl < this.scene.getMaxExpLevel() ? pkmLvlExp / relLvExp : 0; - const expLabel = addTextObject(this.scene, 6, 112, i18next.t("pokemonSummary:expPoints"), TextStyle.SUMMARY); - expLabel.setOrigin(0, 0); - statsContainer.add(expLabel); + const expLabel = addTextObject(this.scene, 6, 112, i18next.t("pokemonSummary:expPoints"), TextStyle.SUMMARY); + expLabel.setOrigin(0, 0); + statsContainer.add(expLabel); - const nextLvExpLabel = addTextObject(this.scene, 6, 128, i18next.t("pokemonSummary:nextLv"), TextStyle.SUMMARY); - nextLvExpLabel.setOrigin(0, 0); - statsContainer.add(nextLvExpLabel); + const nextLvExpLabel = addTextObject(this.scene, 6, 128, i18next.t("pokemonSummary:nextLv"), TextStyle.SUMMARY); + nextLvExpLabel.setOrigin(0, 0); + statsContainer.add(nextLvExpLabel); - const expText = addTextObject(this.scene, 208, 112, pkmExp.toString(), TextStyle.WINDOW_ALT); - expText.setOrigin(1, 0); - statsContainer.add(expText); + const expText = addTextObject(this.scene, 208, 112, pkmExp.toString(), TextStyle.WINDOW_ALT); + expText.setOrigin(1, 0); + statsContainer.add(expText); - const nextLvExp = pkmLvl < this.scene.getMaxExpLevel() - ? getLevelTotalExp(pkmLvl + 1, pkmSpeciesGrowthRate) - pkmExp - : 0; - const nextLvExpText = addTextObject(this.scene, 208, 128, nextLvExp.toString(), TextStyle.WINDOW_ALT); - nextLvExpText.setOrigin(1, 0); - statsContainer.add(nextLvExpText); + const nextLvExp = pkmLvl < this.scene.getMaxExpLevel() + ? getLevelTotalExp(pkmLvl + 1, pkmSpeciesGrowthRate) - pkmExp + : 0; + const nextLvExpText = addTextObject(this.scene, 208, 128, nextLvExp.toString(), TextStyle.WINDOW_ALT); + nextLvExpText.setOrigin(1, 0); + statsContainer.add(nextLvExpText); - const expOverlay = this.scene.add.image(140, 145, "summary_stats_overlay_exp"); - expOverlay.setOrigin(0, 0); - statsContainer.add(expOverlay); + const expOverlay = this.scene.add.image(140, 145, "summary_stats_overlay_exp"); + expOverlay.setOrigin(0, 0); + statsContainer.add(expOverlay); - const expMaskRect = this.scene.make.graphics({}); - expMaskRect.setScale(6); - expMaskRect.fillStyle(0xFFFFFF); - expMaskRect.beginPath(); - expMaskRect.fillRect(140 + pageContainer.x, 145 + pageContainer.y + 21, Math.floor(expRatio * 64), 3); + const expMaskRect = this.scene.make.graphics({}); + expMaskRect.setScale(6); + expMaskRect.fillStyle(0xFFFFFF); + expMaskRect.beginPath(); + expMaskRect.fillRect(140 + pageContainer.x, 145 + pageContainer.y + 21, Math.floor(expRatio * 64), 3); - const expMask = expMaskRect.createGeometryMask(); + const expMask = expMaskRect.createGeometryMask(); - expOverlay.setMask(expMask); - break; - case Page.MOVES: - this.movesContainer = this.scene.add.container(5, -pageBg.height + 26); - pageContainer.add(this.movesContainer); + expOverlay.setMask(expMask); + break; + case Page.MOVES: + this.movesContainer = this.scene.add.container(5, -pageBg.height + 26); + pageContainer.add(this.movesContainer); - this.extraMoveRowContainer = this.scene.add.container(0, 64); - this.extraMoveRowContainer.setVisible(false); - this.movesContainer.add(this.extraMoveRowContainer); + this.extraMoveRowContainer = this.scene.add.container(0, 64); + this.extraMoveRowContainer.setVisible(false); + this.movesContainer.add(this.extraMoveRowContainer); - const extraRowOverlay = this.scene.add.image(-2, 1, "summary_moves_overlay_row"); - extraRowOverlay.setOrigin(0, 1); - this.extraMoveRowContainer.add(extraRowOverlay); + const extraRowOverlay = this.scene.add.image(-2, 1, "summary_moves_overlay_row"); + extraRowOverlay.setOrigin(0, 1); + this.extraMoveRowContainer.add(extraRowOverlay); - const extraRowText = addTextObject(this.scene, 35, 0, this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.newMove ? this.newMove.name : i18next.t("pokemonSummary:cancel"), - this.summaryUiMode === SummaryUiMode.LEARN_MOVE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY); - extraRowText.setOrigin(0, 1); - this.extraMoveRowContainer.add(extraRowText); + const extraRowText = addTextObject(this.scene, 35, 0, this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.newMove ? this.newMove.name : i18next.t("pokemonSummary:cancel"), + this.summaryUiMode === SummaryUiMode.LEARN_MOVE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY); + extraRowText.setOrigin(0, 1); + this.extraMoveRowContainer.add(extraRowText); - if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { - this.extraMoveRowContainer.setVisible(true); + if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { + this.extraMoveRowContainer.setVisible(true); - if (this.newMove && this.pokemon) { - const spriteKey = Utils.getLocalizedSpriteKey("types"); - const moveType = this.pokemon.getMoveType(this.newMove); - const newMoveTypeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); - newMoveTypeIcon.setOrigin(0, 1); - this.extraMoveRowContainer.add(newMoveTypeIcon); - } - const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); - ppOverlay.setOrigin(0, 1); - this.extraMoveRowContainer.add(ppOverlay); + if (this.newMove && this.pokemon) { + const spriteKey = Utils.getLocalizedSpriteKey("types"); + const moveType = this.pokemon.getMoveType(this.newMove); + const newMoveTypeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); + newMoveTypeIcon.setOrigin(0, 1); + this.extraMoveRowContainer.add(newMoveTypeIcon); + } + const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); + ppOverlay.setOrigin(0, 1); + this.extraMoveRowContainer.add(ppOverlay); - const pp = Utils.padInt(this.newMove?.pp!, 2, " "); // TODO: is this bang correct? - const ppText = addTextObject(this.scene, 173, 1, `${pp}/${pp}`, TextStyle.WINDOW); - ppText.setOrigin(0, 1); - this.extraMoveRowContainer.add(ppText); - } - - this.moveRowsContainer = this.scene.add.container(0, 0); - this.movesContainer.add(this.moveRowsContainer); - - for (let m = 0; m < 4; m++) { - const move: PokemonMove | null = this.pokemon && this.pokemon.moveset.length > m ? this.pokemon?.moveset[m] : null; - const moveRowContainer = this.scene.add.container(0, 16 * m); - this.moveRowsContainer.add(moveRowContainer); - - if (move && this.pokemon) { - const spriteKey = Utils.getLocalizedSpriteKey("types"); - const moveType = this.pokemon.getMoveType(move.getMove()); - const typeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); - typeIcon.setOrigin(0, 1); - moveRowContainer.add(typeIcon); + const pp = Utils.padInt(this.newMove?.pp!, 2, " "); // TODO: is this bang correct? + const ppText = addTextObject(this.scene, 173, 1, `${pp}/${pp}`, TextStyle.WINDOW); + ppText.setOrigin(0, 1); + this.extraMoveRowContainer.add(ppText); } - const moveText = addTextObject(this.scene, 35, 0, move ? move.getName() : "-", TextStyle.SUMMARY); - moveText.setOrigin(0, 1); - moveRowContainer.add(moveText); + this.moveRowsContainer = this.scene.add.container(0, 0); + this.movesContainer.add(this.moveRowsContainer); - const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); - ppOverlay.setOrigin(0, 1); - moveRowContainer.add(ppOverlay); + for (let m = 0; m < 4; m++) { + const move: PokemonMove | null = this.pokemon && this.pokemon.moveset.length > m ? this.pokemon?.moveset[m] : null; + const moveRowContainer = this.scene.add.container(0, 16 * m); + this.moveRowsContainer.add(moveRowContainer); - const ppText = addTextObject(this.scene, 173, 1, "--/--", TextStyle.WINDOW); - ppText.setOrigin(0, 1); + if (move && this.pokemon) { + const spriteKey = Utils.getLocalizedSpriteKey("types"); + const moveType = this.pokemon.getMoveType(move.getMove()); + const typeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); + typeIcon.setOrigin(0, 1); + moveRowContainer.add(typeIcon); + } - if (move) { - const maxPP = move.getMovePp(); - const pp = maxPP - move.ppUsed; - ppText.setText(`${Utils.padInt(pp, 2, " ")}/${Utils.padInt(maxPP, 2, " ")}`); + const moveText = addTextObject(this.scene, 35, 0, move ? move.getName() : "-", TextStyle.SUMMARY); + moveText.setOrigin(0, 1); + moveRowContainer.add(moveText); + + const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); + ppOverlay.setOrigin(0, 1); + moveRowContainer.add(ppOverlay); + + const ppText = addTextObject(this.scene, 173, 1, "--/--", TextStyle.WINDOW); + ppText.setOrigin(0, 1); + + if (move) { + const maxPP = move.getMovePp(); + const pp = maxPP - move.ppUsed; + ppText.setText(`${Utils.padInt(pp, 2, " ")}/${Utils.padInt(maxPP, 2, " ")}`); + } + + moveRowContainer.add(ppText); } - moveRowContainer.add(ppText); - } + this.moveDescriptionText = addTextObject(this.scene, 2, 84, "", TextStyle.WINDOW_ALT, { wordWrap: { width: 1212 }}); + this.movesContainer.add(this.moveDescriptionText); - this.moveDescriptionText = addTextObject(this.scene, 2, 84, "", TextStyle.WINDOW_ALT, { wordWrap: { width: 1212 }}); - this.movesContainer.add(this.moveDescriptionText); + const moveDescriptionTextMaskRect = this.scene.make.graphics({}); + moveDescriptionTextMaskRect.setScale(6); + moveDescriptionTextMaskRect.fillStyle(0xFFFFFF); + moveDescriptionTextMaskRect.beginPath(); + moveDescriptionTextMaskRect.fillRect(112, 130, 202, 46); - const moveDescriptionTextMaskRect = this.scene.make.graphics({}); - moveDescriptionTextMaskRect.setScale(6); - moveDescriptionTextMaskRect.fillStyle(0xFFFFFF); - moveDescriptionTextMaskRect.beginPath(); - moveDescriptionTextMaskRect.fillRect(112, 130, 202, 46); + const moveDescriptionTextMask = moveDescriptionTextMaskRect.createGeometryMask(); - const moveDescriptionTextMask = moveDescriptionTextMaskRect.createGeometryMask(); - - this.moveDescriptionText.setMask(moveDescriptionTextMask); - break; + this.moveDescriptionText.setMask(moveDescriptionTextMask); + break; } } diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index 85b671c2617..4c55a4b960e 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -71,26 +71,26 @@ export default class TargetSelectUiHandler extends UiHandler { success = false; } else { switch (button) { - case Button.UP: - if (this.cursor < BattlerIndex.ENEMY && this.targets.findIndex(t => t >= BattlerIndex.ENEMY) > -1) { - success = this.setCursor(this.targets.find(t => t >= BattlerIndex.ENEMY)!); // TODO: is the bang correct here? - } - break; - case Button.DOWN: - if (this.cursor >= BattlerIndex.ENEMY && this.targets.findIndex(t => t < BattlerIndex.ENEMY) > -1) { - success = this.setCursor(this.targets.find(t => t < BattlerIndex.ENEMY)!); // TODO: is the bang correct here? - } - break; - case Button.LEFT: - if (this.cursor % 2 && this.targets.findIndex(t => t === this.cursor - 1) > -1) { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.RIGHT: - if (!(this.cursor % 2) && this.targets.findIndex(t => t === this.cursor + 1) > -1) { - success = this.setCursor(this.cursor + 1); - } - break; + case Button.UP: + if (this.cursor < BattlerIndex.ENEMY && this.targets.findIndex(t => t >= BattlerIndex.ENEMY) > -1) { + success = this.setCursor(this.targets.find(t => t >= BattlerIndex.ENEMY)!); // TODO: is the bang correct here? + } + break; + case Button.DOWN: + if (this.cursor >= BattlerIndex.ENEMY && this.targets.findIndex(t => t < BattlerIndex.ENEMY) > -1) { + success = this.setCursor(this.targets.find(t => t < BattlerIndex.ENEMY)!); // TODO: is the bang correct here? + } + break; + case Button.LEFT: + if (this.cursor % 2 && this.targets.findIndex(t => t === this.cursor - 1) > -1) { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.RIGHT: + if (!(this.cursor % 2) && this.targets.findIndex(t => t === this.cursor + 1) > -1) { + success = this.setCursor(this.cursor + 1); + } + break; } } diff --git a/src/ui/text.ts b/src/ui/text.ts index 22dd3f4cd6a..069aa8680fc 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -129,84 +129,84 @@ export function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraSty } switch (style) { - case TextStyle.SUMMARY: - case TextStyle.SUMMARY_ALT: - case TextStyle.SUMMARY_BLUE: - case TextStyle.SUMMARY_RED: - case TextStyle.SUMMARY_PINK: - case TextStyle.SUMMARY_GOLD: - case TextStyle.SUMMARY_GRAY: - case TextStyle.SUMMARY_GREEN: - case TextStyle.WINDOW: - case TextStyle.WINDOW_ALT: - shadowXpos = 3; - shadowYpos = 3; - break; - case TextStyle.STATS_LABEL: - let fontSizeLabel = "96px"; - switch (lang) { - case "de": + case TextStyle.SUMMARY: + case TextStyle.SUMMARY_ALT: + case TextStyle.SUMMARY_BLUE: + case TextStyle.SUMMARY_RED: + case TextStyle.SUMMARY_PINK: + case TextStyle.SUMMARY_GOLD: + case TextStyle.SUMMARY_GRAY: + case TextStyle.SUMMARY_GREEN: + case TextStyle.WINDOW: + case TextStyle.WINDOW_ALT: shadowXpos = 3; shadowYpos = 3; - fontSizeLabel = "80px"; break; - default: - fontSizeLabel = "96px"; + case TextStyle.STATS_LABEL: + let fontSizeLabel = "96px"; + switch (lang) { + case "de": + shadowXpos = 3; + shadowYpos = 3; + fontSizeLabel = "80px"; + break; + default: + fontSizeLabel = "96px"; + break; + } + styleOptions.fontSize = fontSizeLabel; break; - } - styleOptions.fontSize = fontSizeLabel; - break; - case TextStyle.STATS_VALUE: - shadowXpos = 3; - shadowYpos = 3; - let fontSizeValue = "96px"; - switch (lang) { - case "de": - fontSizeValue = "80px"; + case TextStyle.STATS_VALUE: + shadowXpos = 3; + shadowYpos = 3; + let fontSizeValue = "96px"; + switch (lang) { + case "de": + fontSizeValue = "80px"; + break; + default: + fontSizeValue = "96px"; + break; + } + styleOptions.fontSize = fontSizeValue; break; - default: - fontSizeValue = "96px"; + case TextStyle.MESSAGE: + case TextStyle.SETTINGS_LABEL: + case TextStyle.SETTINGS_LOCKED: + case TextStyle.SETTINGS_SELECTED: + break; + case TextStyle.BATTLE_INFO: + case TextStyle.MONEY: + case TextStyle.TOOLTIP_TITLE: + styleOptions.fontSize = defaultFontSize - 24; + shadowXpos = 3.5; + shadowYpos = 3.5; + break; + case TextStyle.PARTY: + case TextStyle.PARTY_RED: + styleOptions.fontSize = defaultFontSize - 30; + styleOptions.fontFamily = "pkmnems"; + break; + case TextStyle.TOOLTIP_CONTENT: + styleOptions.fontSize = defaultFontSize - 32; + shadowXpos = 3; + shadowYpos = 3; + break; + case TextStyle.MOVE_INFO_CONTENT: + styleOptions.fontSize = defaultFontSize - 40; + shadowXpos = 3; + shadowYpos = 3; + break; + case TextStyle.SMALLER_WINDOW_ALT: + styleOptions.fontSize = defaultFontSize - 36; + shadowXpos = 3; + shadowYpos = 3; + break; + case TextStyle.BGM_BAR: + styleOptions.fontSize = defaultFontSize - 24; + shadowXpos = 3; + shadowYpos = 3; break; - } - styleOptions.fontSize = fontSizeValue; - break; - case TextStyle.MESSAGE: - case TextStyle.SETTINGS_LABEL: - case TextStyle.SETTINGS_LOCKED: - case TextStyle.SETTINGS_SELECTED: - break; - case TextStyle.BATTLE_INFO: - case TextStyle.MONEY: - case TextStyle.TOOLTIP_TITLE: - styleOptions.fontSize = defaultFontSize - 24; - shadowXpos = 3.5; - shadowYpos = 3.5; - break; - case TextStyle.PARTY: - case TextStyle.PARTY_RED: - styleOptions.fontSize = defaultFontSize - 30; - styleOptions.fontFamily = "pkmnems"; - break; - case TextStyle.TOOLTIP_CONTENT: - styleOptions.fontSize = defaultFontSize - 32; - shadowXpos = 3; - shadowYpos = 3; - break; - case TextStyle.MOVE_INFO_CONTENT: - styleOptions.fontSize = defaultFontSize - 40; - shadowXpos = 3; - shadowYpos = 3; - break; - case TextStyle.SMALLER_WINDOW_ALT: - styleOptions.fontSize = defaultFontSize - 36; - shadowXpos = 3; - shadowYpos = 3; - break; - case TextStyle.BGM_BAR: - styleOptions.fontSize = defaultFontSize - 24; - shadowXpos = 3; - shadowYpos = 3; - break; } const shadowColor = getTextColor(style, true, uiTheme); @@ -257,110 +257,110 @@ export function getTextWithColors(content: string, primaryStyle: TextStyle, uiTh export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: UiTheme = UiTheme.DEFAULT): string { const isLegacyTheme = uiTheme === UiTheme.LEGACY; switch (textStyle) { - case TextStyle.MESSAGE: - return !shadow ? "#f8f8f8" : "#6b5a73"; - case TextStyle.WINDOW: - case TextStyle.MOVE_INFO_CONTENT: - case TextStyle.MOVE_PP_FULL: - case TextStyle.TOOLTIP_CONTENT: - case TextStyle.SETTINGS_VALUE: - if (isLegacyTheme) { + case TextStyle.MESSAGE: + return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.WINDOW: + case TextStyle.MOVE_INFO_CONTENT: + case TextStyle.MOVE_PP_FULL: + case TextStyle.TOOLTIP_CONTENT: + case TextStyle.SETTINGS_VALUE: + if (isLegacyTheme) { + return !shadow ? "#484848" : "#d0d0c8"; + } + return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.MOVE_PP_HALF_FULL: + if (isLegacyTheme) { + return !shadow ? "#a68e17" : "#ebd773"; + } + return !shadow ? "#ccbe00" : "#6e672c"; + case TextStyle.MOVE_PP_NEAR_EMPTY: + if (isLegacyTheme) { + return !shadow ? "#d64b00" : "#f7b18b"; + } + return !shadow ? "#d64b00" : "#69402a"; + case TextStyle.MOVE_PP_EMPTY: + if (isLegacyTheme) { + return !shadow ? "#e13d3d" : "#fca2a2"; + } + return !shadow ? "#e13d3d" : "#632929"; + case TextStyle.WINDOW_ALT: return !shadow ? "#484848" : "#d0d0c8"; - } - return !shadow ? "#f8f8f8" : "#6b5a73"; - case TextStyle.MOVE_PP_HALF_FULL: - if (isLegacyTheme) { - return !shadow ? "#a68e17" : "#ebd773"; - } - return !shadow ? "#ccbe00" : "#6e672c"; - case TextStyle.MOVE_PP_NEAR_EMPTY: - if (isLegacyTheme) { - return !shadow ? "#d64b00" : "#f7b18b"; - } - return !shadow ? "#d64b00" : "#69402a"; - case TextStyle.MOVE_PP_EMPTY: - if (isLegacyTheme) { - return !shadow ? "#e13d3d" : "#fca2a2"; - } - return !shadow ? "#e13d3d" : "#632929"; - case TextStyle.WINDOW_ALT: - return !shadow ? "#484848" : "#d0d0c8"; - case TextStyle.BATTLE_INFO: - if (isLegacyTheme) { - return !shadow ? "#404040" : "#ded6b5"; - } - return !shadow ? "#f8f8f8" : "#6b5a73"; - case TextStyle.PARTY: - return !shadow ? "#f8f8f8" : "#707070"; - case TextStyle.PARTY_RED: - return !shadow ? "#f89890" : "#984038"; - case TextStyle.SUMMARY: - return !shadow ? "#f8f8f8" : "#636363"; - case TextStyle.SUMMARY_ALT: - if (isLegacyTheme) { + case TextStyle.BATTLE_INFO: + if (isLegacyTheme) { + return !shadow ? "#404040" : "#ded6b5"; + } + return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.PARTY: + return !shadow ? "#f8f8f8" : "#707070"; + case TextStyle.PARTY_RED: + return !shadow ? "#f89890" : "#984038"; + case TextStyle.SUMMARY: return !shadow ? "#f8f8f8" : "#636363"; - } - return !shadow ? "#484848" : "#d0d0c8"; - case TextStyle.SUMMARY_RED: - case TextStyle.TOOLTIP_TITLE: - return !shadow ? "#e70808" : "#ffbd73"; - case TextStyle.SUMMARY_BLUE: - return !shadow ? "#40c8f8" : "#006090"; - case TextStyle.SUMMARY_PINK: - return !shadow ? "#f89890" : "#984038"; - case TextStyle.SUMMARY_GOLD: - case TextStyle.MONEY: - return !shadow ? "#e8e8a8" : "#a0a060"; - case TextStyle.SETTINGS_LOCKED: - case TextStyle.SUMMARY_GRAY: - return !shadow ? "#a0a0a0" : "#636363"; - case TextStyle.STATS_LABEL: - return !shadow ? "#f8b050" : "#c07800"; - case TextStyle.STATS_VALUE: - if (isLegacyTheme) { + case TextStyle.SUMMARY_ALT: + if (isLegacyTheme) { + return !shadow ? "#f8f8f8" : "#636363"; + } return !shadow ? "#484848" : "#d0d0c8"; - } - return !shadow ? "#f8f8f8" : "#6b5a73"; - case TextStyle.SUMMARY_GREEN: - return !shadow ? "#78c850" : "#306850"; - case TextStyle.SETTINGS_LABEL: - case TextStyle.PERFECT_IV: - return !shadow ? "#f8b050" : "#c07800"; - case TextStyle.SETTINGS_SELECTED: - return !shadow ? "#f88880" : "#f83018"; - case TextStyle.SMALLER_WINDOW_ALT: - return !shadow ? "#484848" : "#d0d0c8"; - case TextStyle.BGM_BAR: - return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.SUMMARY_RED: + case TextStyle.TOOLTIP_TITLE: + return !shadow ? "#e70808" : "#ffbd73"; + case TextStyle.SUMMARY_BLUE: + return !shadow ? "#40c8f8" : "#006090"; + case TextStyle.SUMMARY_PINK: + return !shadow ? "#f89890" : "#984038"; + case TextStyle.SUMMARY_GOLD: + case TextStyle.MONEY: + return !shadow ? "#e8e8a8" : "#a0a060"; + case TextStyle.SETTINGS_LOCKED: + case TextStyle.SUMMARY_GRAY: + return !shadow ? "#a0a0a0" : "#636363"; + case TextStyle.STATS_LABEL: + return !shadow ? "#f8b050" : "#c07800"; + case TextStyle.STATS_VALUE: + if (isLegacyTheme) { + return !shadow ? "#484848" : "#d0d0c8"; + } + return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.SUMMARY_GREEN: + return !shadow ? "#78c850" : "#306850"; + case TextStyle.SETTINGS_LABEL: + case TextStyle.PERFECT_IV: + return !shadow ? "#f8b050" : "#c07800"; + case TextStyle.SETTINGS_SELECTED: + return !shadow ? "#f88880" : "#f83018"; + case TextStyle.SMALLER_WINDOW_ALT: + return !shadow ? "#484848" : "#d0d0c8"; + case TextStyle.BGM_BAR: + return !shadow ? "#f8f8f8" : "#6b5a73"; } } export function getModifierTierTextTint(tier: ModifierTier): integer { switch (tier) { - case ModifierTier.COMMON: - return 0xf8f8f8; - case ModifierTier.GREAT: - return 0x4998f8; - case ModifierTier.ULTRA: - return 0xf8d038; - case ModifierTier.ROGUE: - return 0xdb4343; - case ModifierTier.MASTER: - return 0xe331c5; - case ModifierTier.LUXURY: - return 0xe74c18; + case ModifierTier.COMMON: + return 0xf8f8f8; + case ModifierTier.GREAT: + return 0x4998f8; + case ModifierTier.ULTRA: + return 0xf8d038; + case ModifierTier.ROGUE: + return 0xdb4343; + case ModifierTier.MASTER: + return 0xe331c5; + case ModifierTier.LUXURY: + return 0xe74c18; } } export function getEggTierTextTint(tier: EggTier): integer { switch (tier) { - case EggTier.COMMON: - return getModifierTierTextTint(ModifierTier.COMMON); - case EggTier.RARE: - return getModifierTierTextTint(ModifierTier.GREAT); - case EggTier.EPIC: - return getModifierTierTextTint(ModifierTier.ULTRA); - case EggTier.LEGENDARY: - return getModifierTierTextTint(ModifierTier.MASTER); + case EggTier.COMMON: + return getModifierTierTextTint(ModifierTier.COMMON); + case EggTier.RARE: + return getModifierTierTextTint(ModifierTier.GREAT); + case EggTier.EPIC: + return getModifierTierTextTint(ModifierTier.ULTRA); + case EggTier.LEGENDARY: + return getModifierTierTextTint(ModifierTier.MASTER); } } diff --git a/src/ui/ui-theme.ts b/src/ui/ui-theme.ts index 8ff50667248..89c56384bd0 100644 --- a/src/ui/ui-theme.ts +++ b/src/ui/ui-theme.ts @@ -10,12 +10,12 @@ export enum WindowVariant { export function getWindowVariantSuffix(windowVariant: WindowVariant): string { switch (windowVariant) { - case WindowVariant.THIN: - return "_thin"; - case WindowVariant.XTHIN: - return "_xthin"; - default: - return ""; + case WindowVariant.THIN: + return "_thin"; + case WindowVariant.XTHIN: + return "_xthin"; + default: + return ""; } } diff --git a/src/utils.ts b/src/utils.ts index c2ee7100909..8a35a4b3f07 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -194,23 +194,23 @@ export function formatLargeNumber(count: integer, threshold: integer): string { const ret = count.toString(); let suffix = ""; switch (Math.ceil(ret.length / 3) - 1) { - case 1: - suffix = "K"; - break; - case 2: - suffix = "M"; - break; - case 3: - suffix = "B"; - break; - case 4: - suffix = "T"; - break; - case 5: - suffix = "q"; - break; - default: - return "?"; + case 1: + suffix = "K"; + break; + case 2: + suffix = "M"; + break; + case 3: + suffix = "B"; + break; + case 4: + suffix = "T"; + break; + case 5: + suffix = "q"; + break; + default: + return "?"; } const digits = ((ret.length + 2) % 3) + 1; let decimalNumber = ret.slice(digits, digits + 2); @@ -487,18 +487,18 @@ export function verifyLang(lang?: string): boolean { } switch (lang) { - case "es": - case "fr": - case "de": - case "it": - case "zh-CN": - case "zh-TW": - case "pt-BR": - case "ko": - case "ja": - return true; - default: - return false; + case "es": + case "fr": + case "de": + case "it": + case "zh-CN": + case "zh-TW": + case "pt-BR": + case "ko": + case "ja": + return true; + default: + return false; } } From e6c06d57be18ef893aa19f5efab81bb67379638f Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Sun, 20 Oct 2024 23:55:07 +0200 Subject: [PATCH 02/24] [P All][Bug] Various ME bugfixes (copy) (#4695) * Mystery Encounter bugfixes * more ME bug fixes * update allowed pokemon in ME requirements * some unit test cleanup and general tidying * fix null exception on isBattleMysteryEncounter * clean up tsdocs and fix pokemon hasAbility check * fix double battle crash in challenge mode with a single eligible pokemon * apply suggestions from PR#4619's code reviews * revert fix for Keldeo crashes + implement fix suggestion from PR #4619 * fix session migration for PokemonCustomData * prevent test failure due to keldeo fix --------- Co-authored-by: ImperialSympathizer --- public/images/items.json | 10 +-- public/images/items.png | Bin 58664 -> 58527 bytes public/images/items/black_sludge.png | Bin 1026 -> 285 bytes src/battle-scene.ts | 37 ++++++-- ...pokemon-data.ts => custom-pokemon-data.ts} | 9 +- .../an-offer-you-cant-refuse-encounter.ts | 4 +- .../encounters/bug-type-superfan-encounter.ts | 13 ++- .../encounters/clowning-around-encounter.ts | 28 +++--- .../encounters/dancing-lessons-encounter.ts | 2 +- .../encounters/fight-or-flight-encounter.ts | 2 +- .../encounters/part-timer-encounter.ts | 2 +- .../shady-vitamin-dealer-encounter.ts | 2 +- .../slumbering-snorlax-encounter.ts | 6 +- .../the-expert-pokemon-breeder-encounter.ts | 28 +++--- .../encounters/the-strong-stuff-encounter.ts | 4 +- .../encounters/uncommon-breed-encounter.ts | 2 +- .../encounters/weird-dream-encounter.ts | 8 +- .../mystery-encounter-requirements.ts | 35 +++++--- .../mystery-encounters/mystery-encounter.ts | 2 +- .../utils/encounter-phase-utils.ts | 10 +-- src/field/pokemon.ts | 80 ++++++++++++------ src/modifier/modifier-type.ts | 4 +- src/modifier/modifier.ts | 2 +- src/phases/encounter-phase.ts | 10 ++- src/phases/mystery-encounter-phases.ts | 2 +- src/system/pokemon-data.ts | 19 +++-- .../version_migration/version_converter.ts | 15 ++++ .../version_migration/versions/v1_1_0.ts | 32 +++++++ .../bug-type-superfan-encounter.test.ts | 7 +- .../clowning-around-encounter.test.ts | 16 ++-- .../the-strong-stuff-encounter.test.ts | 4 +- .../encounters/weird-dream-encounter.test.ts | 2 +- 32 files changed, 263 insertions(+), 134 deletions(-) rename src/data/{mystery-encounters/mystery-encounter-pokemon-data.ts => custom-pokemon-data.ts} (68%) create mode 100644 src/system/version_migration/versions/v1_1_0.ts diff --git a/public/images/items.json b/public/images/items.json index 779823d1293..05d021b6a06 100644 --- a/public/images/items.json +++ b/public/images/items.json @@ -3416,12 +3416,12 @@ "rotated": false, "trimmed": true, "sourceSize": { - "w": 24, - "h": 24 + "w": 32, + "h": 32 }, "spriteSourceSize": { - "x": 1, - "y": 2, + "x": 5, + "y": 7, "w": 22, "h": 19 }, @@ -8415,6 +8415,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:934ea4080bad980d4fea720cc771f133:ed564bc47b79b15a763de57045178e88:110e074689c9edd2c54833ce2e4d9270$" + "smartupdate": "$TexturePacker:SmartUpdate:9ef21166268f7487fc9ff8d0f9b996e4:82658ac7bdd4c2b417e1f59168179262:110e074689c9edd2c54833ce2e4d9270$" } } diff --git a/public/images/items.png b/public/images/items.png index 5f032b30cfb048c2d0cf71ec1e6ad3f051d832c9..8aaa0281c0088a4b0ded764af22c3d5456ce167f 100644 GIT binary patch literal 58527 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 literal 58664 zcmV*IKxe;+P)eEYJmOHfSv^xDv^OF=z7#$;!jQa|^)+x^qh z(?mh?`uBf(eqUf?_A4yn#q{8Yb-RK(_nD51kB$BN#I@t;o~yUGi*ks@@cH5J`n_Im zZE@4Una}n8yvqCJ!e+S4*KU@t<@){jSy=tT^Ppdp^wh5A&rWWR_Vwtazm+20;Po$_2u#ke!$(TBD2{$GJ|ic_wpqCBXi z2FdS$vh1K9c)RJ|Kv#uschagP*0Y zoWMI;Y32IToN^V0UugZv5pUc4eq_?1kXp^R3|BpEFlNMaoNVQ!MQmvdpo|<-Nye$A zT*ZeXMD;(~Cwj*OK^xwLo+5`&X7H_NSce}QEP=I#S z)wc1~7a}+xXBJnc<9G4$2C%3z}F!<~Kqn&?QZH1X8h4sf9ld~)>77d+IGz=p4fYlcj79vL=C{E~G`dX9GvQ7mB?p`%%0000AbW%=J0RR90 zd;kCbC7a7)pnCuS010qNS#tmY4#WTe4#WYKD-Ig~03ZNKL_t(|+U&gvd=*u;@1JvW zccpKI%+m=R2!sq^7$On~7@LqV6U@+`Kp;xO7%(8iRU(Rf@&W>y76L;01~q^X1Ohl9 zjev~^3W%Tx7g~sd3Q?clwiT83{psPq)*h<%uBwy7sqcOF^VZ35QkCJPzV%yc?X_3! z^7H$D_J6DC`VC*Zn)rJwMhKxF@RKmF*BdCFIR1qE&HPui@;bk7Vc)fu{=XmhbL7U4 z_?^E0nByKdHRFlf+-iMh{+_SDPP?Hy{(+`Vx?fX6OaCwX$5K*;6S5;E#Xr(}-@?ijGxP=JyYicdos9CvVVFmiIklQ1hUzneF|e(P)dpV#yA50y0ekwYc< zO|9VCF&y4@>;PUxMCJt_BI$de11>P*yZy+0gDYB=*!NF#c*56lzgAUP<@1EkUY70i z=T`Mz!$9ma5}$z>_J|!At5obF(L3V#N?Hi7Z!%J@&?#> z`Q6<5wZuz2(@XM84sB{GDLHhgsYPI92fzYcJN%Q93<80mDuxO^1lo8Nsldoi;C0VE z*PW2*I{usAF9lvUU~J#DdBCg6d7s`9cxC(exf8%^mGizEJ%QJ1AHVZy;N<{JKYaMZ zHETXRd{}Se9yce2784sQ=_@a6z<^++=ESS1d28d>;Qbi9>+E+nZ8~)M5ZL8I&CNGk ziUA}dg32p9+mx3VQAGywcWOvs|8*V<%_H;#n6B47G3SXV=IEBA zR|Tf)d#bXks}c9?bFDllE2y{gZuReh}cR)gt!+3NGFSD&};+Y6@a z?;);uJCwzxhcmzqmoC=*boq$++T#XDu~b*v!6sSv>oXaT1u?zp4wi4Jq-sQLn0z^N)LYfn{SR+g$SpcMu$DJ%?m zg{k@iTEJ=5lqs`U_3qP0)fdnLPOBf@UbcGC`SYs2fEIAt`}W(wD_(Vo1dj|ETUuIL z4ezB3;sW8LuI?$d>;WvHu}oTAW3dJ=kX4t7U|quUth{vn&>{4BbLQk9BIN6@x0V?% zno|RPyhRpU9W~C~@Tn`IdwhTZ4s+#-5di{S&(E6}XZ!lyw zdqJP^%FmJQ=NEw?v$93!BX26d^X+&vWcFTs{E9gF$9kT=Q`gt589R3S-5*zGWGpxl zxZCAgtjl{csF`~ZODv-m!-2&nnY<`20A}T-*VaN>v9sh5W*0ZFzqR$I@gi>|-`INN zMpIKwO$`8Re(>hkC39+PbszGQq8FDJrW|O9OXbC~c|v4)-E;l?b&wd?_4JCt_uYrU z^d3FtVAw)rb+>eB;_*Y^%EGXP$o6FY^;=~hlDhwXV1~SEJj%Wg$M(Ung~(?2`A+@0 zhaWzVVGEI!oiDqo?0frd$_kOa7ysutWnWB2#+_dOPd-@#|8B_GPu6_02K*k%$lSAF zK}N<{uepcuij83w0#8GQynt68@QPgF$ZJM^e#wyL%lSa8r1^4l+6|#QP*&X#fK^vl z$B=C-$*(PGa+cQ)CtgfpUdT5TUZg+R0x~j8me=*`>E{vGbt

?z@lBAUfj?z?JaC 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 diff --git a/public/images/items/black_sludge.png b/public/images/items/black_sludge.png index 39684a403108232447925646dd56e61186de91c0..37aa31de43eaa5314563d42550b2fc912c1a4210 100644 GIT binary patch literal 285 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10LMXr|#1%--S+b?Odrfh1 zx0hFxf`UTZnx~V1qKqX$e!&b5&u*jvIklcHjv*Dd-cEMxVs_*?a%|F{|N8F}72Ch0 z*iCp5)pV$BSKXZryU%a@wP4MehToe0a=of8?ui-)?*3-$W_;rpDLg~+Oy4}-)iPfE z$-E4QSsur3VBF9WyhCiZBtyVCu8tzN)puSC_$&^3y5kU|f$gXIvT(NAy`NIzYwipE ZW0)Dp%oLNzu)Ha0{(J4HS} zS3f{kKR{VPK370NT0lZnLPAhNLRmvZT0}!yL_}0XL|sHhTSY@(MMPaiMqEZjPDV#y zMo3vkNmEBeU`Iw)M@CpjNMT1wTS!G@NJd~tM^H#dS4c-$NJv#lM`1}vV@XI|Nl8^n zNm)rrU`a`0NlIKwNMcG#WlBp@N=sBqO;t-tV@pj@OHNo!OJYn*WK2w4Oig1=PGwC_ zXH8I8PE1x#Ok++>R8CK3PflV_Qe96|TTo9{QBqrGXMJdBc4=yWYHNaOYlLfRd~Ium zZES;WZFy~Ngl%tpZfu5bY>IAegl=w!Zf}KeZHI4diEnR+Z*Ychaei=diE(d+a&L=r zZ;f(qkaBQ`a&U=qaf5PleR6Vta&wGxafx$ri*#|2ba9e&a*lL!h;(#|b#sk%bB}d& ziFI~}b#{t&c8GR&jdysCcz20-d60W~k$ZcKet(yLfSG@Rm4Jbof`XfZgO!7WoP&g# zgn^=jgq?(ikc5Ssg@l`hg_VVcmxYF$g@}}fhn|LqpoWN=hlQSphMI?mqlbu}hlrtv ziJ6CrmxzU=h=!hshnk6or-_M|iHV?ziK2>#qKb)}iix0#ikgawql$~6i;J0yi=B&$ zq>PKAjEtU)jiZf@rHziKj*F>|jH8Z^rjC!OkBym+jirx{p^%ZIl98yBl&X`Itdy0m zm6orUm#~&_Lxr7i+QEB~r zPKXBfybUXo)DjaFmRaf;#+2riE%t)C$~yn}o*4_)M>rOy7iL$)P2z$3gPnt~dF_#f w4PnbC?W$sQMhO%fejYAXRskL+HU|0u0CSsUMn*K#UjP6A07*qoM6N<$g7)&aN&o-= diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 0d482a5f1a5..096a84dde3d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -86,7 +86,7 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; -import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, mysteryEncountersByBiome, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; +import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, mysteryEncountersByBiome } from "#app/data/mystery-encounters/mystery-encounters"; 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"; @@ -1207,12 +1207,10 @@ export default class BattleScene extends SceneBase { // Check for mystery encounter // Can only occur in place of a standard (non-boss) wild battle, waves 10-180 - if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex, mysteryEncounterType) || newBattleType === BattleType.MYSTERY_ENCOUNTER) { + if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex) || newBattleType === BattleType.MYSTERY_ENCOUNTER) { newBattleType = BattleType.MYSTERY_ENCOUNTER; - // Reset base spawn weight + // Reset to base spawn weight this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT; - } else if (newBattleType === BattleType.WILD) { - this.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS; } } @@ -2364,6 +2362,19 @@ export default class BattleScene extends SceneBase { return false; } + /** + * Will search for a specific phase in {@linkcode phaseQueuePrepend} via filter, and remove the first result if a match is found. + * @param phaseFilter filter function + */ + tryRemoveUnshiftedPhase(phaseFilter: (phase: Phase) => boolean): boolean { + const phaseIndex = this.phaseQueuePrepend.findIndex(phaseFilter); + if (phaseIndex > -1) { + this.phaseQueuePrepend.splice(phaseIndex, 1); + return true; + } + return false; + } + /** * Tries to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase() * @param phase {@linkcode Phase} the phase to be added @@ -3125,18 +3136,26 @@ export default class BattleScene extends SceneBase { } } + /** + * Returns if a wave COULD spawn a {@linkcode MysteryEncounter}. + * Even if returns `true`, does not guarantee that a wave will actually be a ME. + * That check is made in {@linkcode BattleScene.isWaveMysteryEncounter} instead. + */ + isMysteryEncounterValidForWave(battleType: BattleType, waveIndex: number): boolean { + const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves(); + return this.gameMode.hasMysteryEncounters && battleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave; + } + /** * Determines whether a wave should randomly generate a {@linkcode MysteryEncounter}. * Currently, the only modes that MEs are allowed in are Classic and Challenge. * Additionally, MEs cannot spawn outside of waves 10-180 in those modes - * * @param newBattleType * @param waveIndex - * @param sessionDataEncounterType */ - private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number, sessionDataEncounterType?: MysteryEncounterType): boolean { + private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number): boolean { const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves(); - if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave) { + if (this.isMysteryEncounterValidForWave(newBattleType, waveIndex)) { // Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance; const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents; diff --git a/src/data/mystery-encounters/mystery-encounter-pokemon-data.ts b/src/data/custom-pokemon-data.ts similarity index 68% rename from src/data/mystery-encounters/mystery-encounter-pokemon-data.ts rename to src/data/custom-pokemon-data.ts index fc6ce313d41..2e94123fc84 100644 --- a/src/data/mystery-encounters/mystery-encounter-pokemon-data.ts +++ b/src/data/custom-pokemon-data.ts @@ -1,18 +1,20 @@ import { Abilities } from "#enums/abilities"; import { Type } from "#app/data/type"; import { isNullOrUndefined } from "#app/utils"; +import { Nature } from "#enums/nature"; /** * Data that can customize a Pokemon in non-standard ways from its Species - * Currently only used by Mystery Encounters, may need to be renamed if it becomes more widely used + * Currently only used by Mystery Encounters and Mints. */ -export class MysteryEncounterPokemonData { +export class CustomPokemonData { public spriteScale: number; public ability: Abilities | -1; public passive: Abilities | -1; + public nature: Nature | -1; public types: Type[]; - constructor(data?: MysteryEncounterPokemonData | Partial) { + constructor(data?: CustomPokemonData | Partial) { if (!isNullOrUndefined(data)) { Object.assign(this, data); } @@ -20,6 +22,7 @@ export class MysteryEncounterPokemonData { this.spriteScale = this.spriteScale ?? -1; this.ability = this.ability ?? -1; this.passive = this.passive ?? -1; + this.nature = this.nature ?? -1; this.types = this.types ?? []; } } 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 ab892ae00f2..8dc4eca25bf 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 @@ -133,8 +133,8 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .withPrimaryPokemonRequirement(new CombinationPokemonRequirement( - new MoveRequirement(EXTORTION_MOVES), - new AbilityRequirement(EXTORTION_ABILITIES)) + new MoveRequirement(EXTORTION_MOVES, true), + new AbilityRequirement(EXTORTION_ABILITIES, true)) ) .withDialogue({ buttonLabel: `${namespace}:option.2.label`, 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 b5d47cf6912..d316ab14cde 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -42,6 +42,8 @@ import { AttackTypeBoosterModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, + GigantamaxAccessModifier, + MegaEvolutionAccessModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import i18next from "i18next"; @@ -356,10 +358,17 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = }, ]; } else { - // If player has any evolution/form change items that are valid for their party, will spawn one of those items in addition to a Master Ball - const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(scene, modifierTypes.MASTER_BALL)!, generateModifierTypeOption(scene, modifierTypes.MAX_LURE)! ]; + // 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(scene, modifierTypes.MASTER_BALL)! ]; const specialOptions: ModifierTypeOption[] = []; + if (!scene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) { + modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.MEGA_BRACELET)!); + } + if (!scene.findModifier(m => m instanceof GigantamaxAccessModifier)) { + modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.DYNAMAX_BAND)!); + } const nonRareEvolutionModifier = generateModifierTypeOption(scene, modifierTypes.EVOLUTION_ITEM); if (nonRareEvolutionModifier) { specialOptions.push(nonRareEvolutionModifier); diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index be52ab42c9d..57c8aa7a561 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -28,7 +28,7 @@ import { BattlerIndex } from "#app/battle"; import { Moves } from "#enums/moves"; import { EncounterBattleAnim } from "#app/data/battle-anims"; import { MoveCategory } from "#app/data/move"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { EncounterAnim } from "#enums/encounter-anims"; import { Challenges } from "#enums/challenges"; @@ -133,7 +133,7 @@ export const ClowningAroundEncounter: MysteryEncounter = }, { // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter species: getPokemonSpecies(Species.BLACEPHALON), - mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ ability: ability, types: [ randSeedInt(18), randSeedInt(18) ]}), + customPokemonData: new CustomPokemonData({ ability: ability, types: [ randSeedInt(18), randSeedInt(18) ]}), isBoss: true, moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ] }, @@ -353,15 +353,15 @@ export const ClowningAroundEncounter: MysteryEncounter = newTypes.push(secondType); // Apply the type changes (to both base and fusion, if pokemon is fused) - if (!pokemon.mysteryEncounterPokemonData) { - pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!pokemon.customPokemonData) { + pokemon.customPokemonData = new CustomPokemonData(); } - pokemon.mysteryEncounterPokemonData.types = newTypes; + pokemon.customPokemonData.types = newTypes; if (pokemon.isFusion()) { - if (!pokemon.fusionMysteryEncounterPokemonData) { - pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!pokemon.fusionCustomPokemonData) { + pokemon.fusionCustomPokemonData = new CustomPokemonData(); } - pokemon.fusionMysteryEncounterPokemonData.types = newTypes; + pokemon.fusionCustomPokemonData.types = newTypes; } } }) @@ -426,15 +426,15 @@ function onYesAbilitySwap(scene: BattleScene, resolve) { // Do ability swap const encounter = scene.currentBattle.mysteryEncounter!; if (pokemon.isFusion()) { - if (!pokemon.fusionMysteryEncounterPokemonData) { - pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!pokemon.fusionCustomPokemonData) { + pokemon.fusionCustomPokemonData = new CustomPokemonData(); } - pokemon.fusionMysteryEncounterPokemonData.ability = encounter.misc.ability; + pokemon.fusionCustomPokemonData.ability = encounter.misc.ability; } else { - if (!pokemon.mysteryEncounterPokemonData) { - pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!pokemon.customPokemonData) { + pokemon.customPokemonData = new CustomPokemonData(); } - pokemon.mysteryEncounterPokemonData.ability = encounter.misc.ability; + pokemon.customPokemonData.ability = encounter.misc.ability; } encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender()); scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true)); diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index d7f71194f48..55d7ce0e92d 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -236,7 +236,7 @@ export const DancingLessonsEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically + .withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, 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 380662ca817..889868519d2 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -145,7 +145,7 @@ export const FightOrFlightEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically + .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withDialogue({ buttonLabel: `${namespace}:option.2.label`, buttonTooltip: `${namespace}:option.2.tooltip`, diff --git a/src/data/mystery-encounters/encounters/part-timer-encounter.ts b/src/data/mystery-encounters/encounters/part-timer-encounter.ts index 17a3a366569..092d2ab2673 100644 --- a/src/data/mystery-encounters/encounters/part-timer-encounter.ts +++ b/src/data/mystery-encounters/encounters/part-timer-encounter.ts @@ -227,7 +227,7 @@ export const PartTimerEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically + .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, 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 5b609a2b1c3..d30c97b27de 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -138,7 +138,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = newNature = randSeedInt(25) as Nature; } - chosenPokemon.nature = newNature; + chosenPokemon.customPokemonData.nature = newNature; encounter.setDialogueToken("newNature", getNatureName(newNature)); queueEncounterMessage(scene, `${namespace}:cheap_side_effects`); setEncounterExp(scene, [ chosenPokemon.id ], 100); diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 3a4bf465a78..8ea19e1225b 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -18,7 +18,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { BerryType } from "#enums/berry-type"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; /** i18n namespace for the encounter */ const namespace = "mysteryEncounters/slumberingSnorlax"; @@ -72,7 +72,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = stackCount: 2 }, ], - mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }), + customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), aiType: AiType.SMART // Required to ensure Snorlax uses Sleep Talk while it is asleep }; const config: EnemyPartyConfig = { @@ -143,7 +143,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) + .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true)) .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, 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 945e7ee188d..9c10d33d019 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 @@ -25,6 +25,7 @@ import { achvs } from "#app/system/achv"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { Type } from "#app/data/type"; import { getPokeballTintColor } from "#app/data/pokeball"; +import { PokemonHeldItemModifier } from "#app/modifier/modifier"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theExpertPokemonBreeder"; @@ -163,7 +164,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = if (pokemon2CommonEggs > 0) { const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") }); pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); - encounter.setDialogueToken("pokemon1CommonEggs", eggsText); + encounter.setDialogueToken("pokemon2CommonEggs", eggsText); } encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip; @@ -221,7 +222,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.misc.chosenPokemon = pokemon1; encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender()); const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs); - setEncounterRewards(scene, { fillRemaining: true }, eggOptions); + setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene)); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1); @@ -247,9 +248,6 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.onGameOver = onGameOver; await initBattleWithEnemyConfig(scene, config); }) - .withPostOptionPhase(async (scene: BattleScene) => { - await doPostEncounterCleanup(scene); - }) .build() ) .withOption( @@ -273,7 +271,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.misc.chosenPokemon = pokemon2; encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender()); const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs); - setEncounterRewards(scene, { fillRemaining: true }, eggOptions); + setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene)); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2); @@ -299,9 +297,6 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.onGameOver = onGameOver; await initBattleWithEnemyConfig(scene, config); }) - .withPostOptionPhase(async (scene: BattleScene) => { - await doPostEncounterCleanup(scene); - }) .build() ) .withOption( @@ -325,7 +320,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.misc.chosenPokemon = pokemon3; encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender()); const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs); - setEncounterRewards(scene, { fillRemaining: true }, eggOptions); + setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene)); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3); @@ -351,9 +346,6 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.onGameOver = onGameOver; await initBattleWithEnemyConfig(scene, config); }) - .withPostOptionPhase(async (scene: BattleScene) => { - await doPostEncounterCleanup(scene); - }) .build() ) .withOutroDialogue([ @@ -521,19 +513,19 @@ function checkAchievement(scene: BattleScene) { } } -async function restorePartyAndHeldItems(scene: BattleScene) { +function restorePartyAndHeldItems(scene: BattleScene) { const encounter = scene.currentBattle.mysteryEncounter!; // Restore original party scene.getParty().push(...encounter.misc.originalParty); // Restore held items const originalHeldItems = encounter.misc.originalPartyHeldItems; - originalHeldItems.forEach(pokemonHeldItemsList => { + originalHeldItems.forEach((pokemonHeldItemsList: PokemonHeldItemModifier[]) => { pokemonHeldItemsList.forEach(heldItem => { scene.addModifier(heldItem, true, false, false, true); }); }); - await scene.updateModifiers(true); + scene.updateModifiers(true); } function onGameOver(scene: BattleScene) { @@ -609,13 +601,13 @@ function onGameOver(scene: BattleScene) { return false; } -async function doPostEncounterCleanup(scene: BattleScene) { +function doPostEncounterCleanup(scene: BattleScene) { const encounter = scene.currentBattle.mysteryEncounter!; if (!encounter.misc.encounterFailed) { // Give achievement if in Space biome checkAchievement(scene); // Give 20 friendship to the chosen pokemon encounter.misc.chosenPokemon.addFriendship(FRIENDSHIP_ADDED); - await restorePartyAndHeldItems(scene); + restorePartyAndHeldItems(scene); } } 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 03cf86d06a5..397d2af9522 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -14,7 +14,7 @@ import { BattlerIndex } from "#app/battle"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { Stat } from "#enums/stat"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; @@ -79,7 +79,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = species: getPokemonSpecies(Species.SHUCKLE), isBoss: true, bossSegments: 5, - mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }), + customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), nature: Nature.BOLD, moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ], modifierConfigs: [ diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 13594f273d9..b7ce3bab48c 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -210,7 +210,7 @@ export const UncommonBreedEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically + .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 6e2f8352480..b97a22dbe51 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -12,7 +12,7 @@ import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from " import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { achvs } from "#app/system/achv"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; @@ -379,10 +379,10 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon newType = randSeedInt(18) as Type; } newTypes.push(newType); - if (!newPokemon.mysteryEncounterPokemonData) { - newPokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!newPokemon.customPokemonData) { + newPokemon.customPokemonData = new CustomPokemonData(); } - newPokemon.mysteryEncounterPokemonData.types = newTypes; + newPokemon.customPokemonData.types = newTypes; for (const item of transformation.heldItems) { item.pokemonId = newPokemon.id; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index 86e04e80807..a57cedc8fa3 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -15,6 +15,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import { SpeciesFormKey } from "#enums/species-form-key"; +import { allAbilities } from "#app/data/ability"; export interface EncounterRequirement { meetsRequirement(scene: BattleScene): boolean; // Boolean to see if a requirement is met @@ -476,9 +477,11 @@ export class MoveRequirement extends EncounterPokemonRequirement { requiredMoves: Moves[] = []; minNumberOfPokemon: number; invertQuery: boolean; + excludeDisallowedPokemon: boolean; - constructor(moves: Moves | Moves[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { + constructor(moves: Moves | Moves[], excludeDisallowedPokemon: boolean, minNumberOfPokemon: number = 1, invertQuery: boolean = false) { super(); + this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; this.requiredMoves = Array.isArray(moves) ? moves : [ moves ]; @@ -494,10 +497,15 @@ export class MoveRequirement extends EncounterPokemonRequirement { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { if (!this.invertQuery) { - return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length > 0).length > 0); + // get the Pokemon with at least one move in the required moves list + return partyPokemon.filter((pokemon) => + (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) + && pokemon.moveset.some((move) => move?.moveId && this.requiredMoves.includes(move.moveId))); } else { // for an inverted query, we only want to get the pokemon that don't have ANY of the listed moves - return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length === 0).length === 0); + return partyPokemon.filter((pokemon) => + (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) + && !pokemon.moveset.some((move) => move?.moveId && this.requiredMoves.includes(move.moveId))); } } @@ -559,9 +567,11 @@ export class AbilityRequirement extends EncounterPokemonRequirement { requiredAbilities: Abilities[]; minNumberOfPokemon: number; invertQuery: boolean; + excludeDisallowedPokemon: boolean; - constructor(abilities: Abilities | Abilities[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { + constructor(abilities: Abilities | Abilities[], excludeDisallowedPokemon: boolean, minNumberOfPokemon: number = 1, invertQuery: boolean = false) { super(); + this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; this.requiredAbilities = Array.isArray(abilities) ? abilities : [ abilities ]; @@ -577,16 +587,21 @@ export class AbilityRequirement extends EncounterPokemonRequirement { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { if (!this.invertQuery) { - return partyPokemon.filter((pokemon) => this.requiredAbilities.some((ability) => pokemon.getAbility().id === ability)); + return partyPokemon.filter((pokemon) => + (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) + && this.requiredAbilities.some((ability) => pokemon.hasAbility(ability, false))); } else { - // for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilitiess - return partyPokemon.filter((pokemon) => this.requiredAbilities.filter((ability) => pokemon.getAbility().id === ability).length === 0); + // for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilities + return partyPokemon.filter((pokemon) => + (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) + && this.requiredAbilities.filter((ability) => pokemon.hasAbility(ability, false)).length === 0); } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - if (pokemon?.getAbility().id && this.requiredAbilities.some(a => pokemon.getAbility().id === a)) { - return [ "ability", pokemon.getAbility().name ]; + override getDialogueToken(_scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + const matchingAbility = this.requiredAbilities.find(a => pokemon?.hasAbility(a, false)); + if (!isNullOrUndefined(matchingAbility)) { + return [ "ability", allAbilities[matchingAbility].name ]; } return [ "ability", "" ]; } diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index 7e175957e21..ee9eb159e10 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -325,7 +325,7 @@ export default class MysteryEncounter implements IMysteryEncounter { if (activeMon.length > 0) { this.primaryPokemon = activeMon[0]; } else { - this.primaryPokemon = scene.getParty().filter(p => !p.isFainted())[0]; + this.primaryPokemon = scene.getParty().filter(p => p.isAllowedInBattle())[0]; } return true; } diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 485b955f998..5cd2fbffd5f 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -27,7 +27,7 @@ import { Status, StatusEffect } from "#app/data/status-effect"; import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-config"; import PokemonSpecies from "#app/data/pokemon-species"; import { Egg, IEggOptions } from "#app/data/egg"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import HeldModifierConfig from "#app/interfaces/held-modifier-config"; import { MovePhase } from "#app/phases/move-phase"; import { EggLapsePhase } from "#app/phases/egg-lapse-phase"; @@ -71,7 +71,7 @@ export interface EnemyPokemonConfig { nickname?: string; bossSegments?: number; bossSegmentModifier?: number; // Additive to the determined segment number - mysteryEncounterPokemonData?: MysteryEncounterPokemonData; + customPokemonData?: CustomPokemonData; formIndex?: number; abilityIndex?: number; level?: number; @@ -145,7 +145,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: newTrainer.setVisible(false); scene.field.add(newTrainer); scene.currentBattle.trainer = newTrainer; - loadEnemyAssets.push(newTrainer.loadAssets()); + loadEnemyAssets.push(newTrainer.loadAssets().then(() => newTrainer.initSprite())); battle.enemyLevels = scene.currentBattle.trainer.getPartyLevels(scene.currentBattle.waveIndex); } else { @@ -250,8 +250,8 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: } // Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.) - if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) { - enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData; + if (!isNullOrUndefined(config.customPokemonData)) { + enemyPokemon.customPokemonData = config.customPokemonData; } // Set Boss diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0afbbf105e2..7f2b4ec015d 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -62,7 +62,7 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph import { Challenges } from "#enums/challenges"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { SwitchType } from "#enums/switch-type"; import { SpeciesFormKey } from "#enums/species-form-key"; import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates"; @@ -114,7 +114,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public fusionVariant: Variant; public fusionGender: Gender; public fusionLuck: integer; - public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData | null; + public fusionCustomPokemonData: CustomPokemonData | null; private summonDataPrimer: PokemonSummonData | null; @@ -122,7 +122,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public battleData: PokemonBattleData; public battleSummonData: PokemonBattleSummonData; public turnData: PokemonTurnData; - public mysteryEncounterPokemonData: MysteryEncounterPokemonData; + public customPokemonData: CustomPokemonData; /** Used by Mystery Encounters to execute pokemon-specific logic (such as stat boosts) at start of battle */ public mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void; @@ -193,7 +193,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.nature = dataSource.nature || 0 as Nature; this.nickname = dataSource.nickname; - this.natureOverride = dataSource.natureOverride !== undefined ? dataSource.natureOverride : -1; this.moveset = dataSource.moveset; this.status = dataSource.status!; // TODO: is this bang correct? this.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship; @@ -212,9 +211,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.fusionVariant = dataSource.fusionVariant || 0; this.fusionGender = dataSource.fusionGender; this.fusionLuck = dataSource.fusionLuck; - this.fusionMysteryEncounterPokemonData = dataSource.fusionMysteryEncounterPokemonData; + this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData; this.usedTMs = dataSource.usedTMs ?? []; - this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(dataSource.mysteryEncounterPokemonData); + this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData); } else { this.id = Utils.randSeedInt(4294967296); this.ivs = ivs || Utils.getIvsFromId(this.id); @@ -235,7 +234,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.variant = this.shiny ? this.generateVariant() : 0; } - this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + this.customPokemonData = new CustomPokemonData(); if (nature !== undefined) { this.setNature(nature); @@ -243,8 +242,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.generateNature(); } - this.natureOverride = -1; - this.friendship = species.baseFriendship; this.metLevel = level; this.metBiome = scene.currentBattle ? scene.arena.biomeType : -1; @@ -593,8 +590,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const formKey = this.getFormKey(); if (this.isMax() === true || formKey === "segin-starmobile" || formKey === "schedar-starmobile" || formKey === "navi-starmobile" || formKey === "ruchbah-starmobile" || formKey === "caph-starmobile") { return 1.5; - } else if (this.mysteryEncounterPokemonData.spriteScale > 0) { - return this.mysteryEncounterPokemonData.spriteScale; + } else if (this.customPokemonData.spriteScale > 0) { + return this.customPokemonData.spriteScale; } return 1; } @@ -1023,7 +1020,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getNature(): Nature { - return this.natureOverride !== -1 ? this.natureOverride : this.nature; + return this.customPokemonData.nature !== -1 ? this.customPokemonData.nature : this.nature; } setNature(nature: Nature): void { @@ -1198,15 +1195,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!types.length || !includeTeraType) { if (!ignoreOverride && this.summonData?.types && this.summonData.types.length > 0) { this.summonData.types.forEach(t => types.push(t)); - } else if (this.mysteryEncounterPokemonData.types && this.mysteryEncounterPokemonData.types.length > 0) { + } else if (this.customPokemonData.types && this.customPokemonData.types.length > 0) { // "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters - types.push(this.mysteryEncounterPokemonData.types[0]); + types.push(this.customPokemonData.types[0]); // Fusing a Pokemon onto something with "permanently changed" types will still apply the fusion's types as normal const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride); if (fusionSpeciesForm) { // Check if the fusion Pokemon also had "permanently changed" types - const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types; + const fusionMETypes = this.fusionCustomPokemonData?.types; if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) { types.push(fusionMETypes[1]); } else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) { @@ -1218,8 +1215,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - if (types.length === 1 && this.mysteryEncounterPokemonData.types.length >= 2) { - types.push(this.mysteryEncounterPokemonData.types[1]); + if (types.length === 1 && this.customPokemonData.types.length >= 2) { + types.push(this.customPokemonData.types[1]); } } else { const speciesForm = this.getSpeciesForm(ignoreOverride); @@ -1230,7 +1227,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (fusionSpeciesForm) { // Check if the fusion Pokemon also had "permanently changed" types // Otherwise, use standard fusion type logic - const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types; + const fusionMETypes = this.fusionCustomPokemonData?.types; if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) { types.push(fusionMETypes[1]); } else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) { @@ -1262,6 +1259,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } + // If both types are the same (can happen in weird custom typing scenarios), reduce to single type + if (types.length > 1 && types[0] === types[1]) { + types.splice(0, 1); + } + return types; } @@ -1288,14 +1290,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; } if (this.isFusion()) { - if (!isNullOrUndefined(this.fusionMysteryEncounterPokemonData?.ability) && this.fusionMysteryEncounterPokemonData.ability !== -1) { - return allAbilities[this.fusionMysteryEncounterPokemonData.ability]; + if (!isNullOrUndefined(this.fusionCustomPokemonData?.ability) && this.fusionCustomPokemonData.ability !== -1) { + return allAbilities[this.fusionCustomPokemonData.ability]; } else { return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)]; } } - if (!isNullOrUndefined(this.mysteryEncounterPokemonData.ability) && this.mysteryEncounterPokemonData.ability !== -1) { - return allAbilities[this.mysteryEncounterPokemonData.ability]; + if (!isNullOrUndefined(this.customPokemonData.ability) && this.customPokemonData.ability !== -1) { + return allAbilities[this.customPokemonData.ability]; } let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex); if (abilityId === Abilities.NONE) { @@ -1318,8 +1320,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) { return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE]; } - if (!isNullOrUndefined(this.mysteryEncounterPokemonData.passive) && this.mysteryEncounterPokemonData.passive !== -1) { - return allAbilities[this.mysteryEncounterPokemonData.passive]; + if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) { + return allAbilities[this.customPokemonData.passive]; } let starterSpeciesId = this.species.speciesId; @@ -2018,7 +2020,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.fusionVariant = 0; this.fusionGender = 0; this.fusionLuck = 0; - this.fusionMysteryEncounterPokemonData = null; + this.fusionCustomPokemonData = null; this.generateName(); this.calculateStats(); @@ -2187,7 +2189,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.moveset.push(new PokemonMove(movePool[index][0], 0, 0)); } - this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger); + // Trigger FormChange, except for enemy Pokemon during Mystery Encounters, to avoid crashes + if (this.isPlayer() || !this.scene.currentBattle?.isBattleMysteryEncounter() || !this.scene.currentBattle?.mysteryEncounter) { + this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger); + } } trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean { @@ -4292,12 +4297,33 @@ export class PlayerPokemon extends Pokemon { changeForm(formChange: SpeciesFormChange): Promise { return new Promise(resolve => { + const previousFormIndex = this.formIndex; this.formIndex = Math.max(this.species.forms.findIndex(f => f.formKey === formChange.formKey), 0); this.generateName(); const abilityCount = this.getSpeciesForm().getAbilityCount(); if (this.abilityIndex >= abilityCount) { // Shouldn't happen this.abilityIndex = abilityCount - 1; } + + // In cases where a form change updates the type of a Pokemon from its previous form (Arceus, Silvally, Castform, etc.), + // persist that type change in customPokemonData if necessary + const baseForm = this.species.forms[previousFormIndex]; + const baseFormTypes = [ baseForm.type1, baseForm.type2 ]; + if (this.customPokemonData.types.length > 0) { + if (this.getSpeciesForm().type1 !== baseFormTypes[0]) { + this.customPokemonData.types[0] = this.getSpeciesForm().type1; + } + + const type2 = this.getSpeciesForm().type2; + if (!isNullOrUndefined(type2) && type2 !== baseFormTypes[1]) { + if (this.customPokemonData.types.length > 1) { + this.customPokemonData.types[1] = type2; + } else { + this.customPokemonData.types.push(type2); + } + } + } + this.compatibleTms.splice(0, this.compatibleTms.length); this.generateCompatibleTms(); const updateAndResolve = () => { @@ -4334,7 +4360,7 @@ export class PlayerPokemon extends Pokemon { this.fusionVariant = pokemon.variant; this.fusionGender = pokemon.gender; this.fusionLuck = pokemon.luck; - this.fusionMysteryEncounterPokemonData = pokemon.mysteryEncounterPokemonData; + this.fusionCustomPokemonData = pokemon.customPokemonData; if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) { this.pauseEvolutions = true; } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 674c5f47c88..8e7853a41bb 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1126,7 +1126,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { } class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { - constructor(rare: boolean) { + constructor(isRareFormChangeItem: boolean) { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in FormChangeItem)) { return new FormChangeItemModifierType(pregenArgs[0] as FormChangeItem); @@ -1167,7 +1167,7 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { } return formChangeItemTriggers; }).flat()) - ].flat().flatMap(fc => fc.item).filter(i => (i && i < 100) === rare); + ].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) { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 2cd96496f45..b699c2483c9 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2181,7 +2181,7 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier { * @returns */ override apply(playerPokemon: PlayerPokemon): boolean { - playerPokemon.natureOverride = this.nature; + playerPokemon.customPokemonData.nature = this.nature; let speciesId = playerPokemon.species.speciesId; playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1); diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 84f07f3ee94..b7071c4cc6f 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -35,6 +35,7 @@ import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-d import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { getGoldenBugNetSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { Biome } from "#enums/biome"; +import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; export class EncounterPhase extends BattlePhase { private loaded: boolean; @@ -68,7 +69,7 @@ export class EncounterPhase extends BattlePhase { this.scene.executeWithSeedOffset(() => { const currentSessionEncounterType = battle.mysteryEncounterType; battle.mysteryEncounter = this.scene.getMysteryEncounter(currentSessionEncounterType); - }, battle.waveIndex << 4); + }, battle.waveIndex * 16); } const mysteryEncounter = battle.mysteryEncounter; if (mysteryEncounter) { @@ -251,6 +252,13 @@ export class EncounterPhase extends BattlePhase { this.scene.updateModifiers(true); }*/ + const { battleType, waveIndex } = this.scene.currentBattle; + if (this.scene.isMysteryEncounterValidForWave(battleType, waveIndex) && !this.scene.currentBattle.isBattleMysteryEncounter()) { + // Increment ME spawn chance if an ME could have spawned but did not + // Only do this AFTER session has been saved to avoid duplicating increments + this.scene.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS; + } + for (const pokemon of this.scene.getParty()) { if (pokemon) { pokemon.resetBattleData(); diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index 0166b2d6abd..49e78fa5369 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -402,7 +402,7 @@ export class MysteryEncounterBattlePhase extends Phase { } } - const availablePartyMembers = scene.getParty().filter(p => !p.isFainted()); + const availablePartyMembers = scene.getParty().filter(p => p.isAllowedInBattle()); if (!availablePartyMembers[0].isOnField()) { scene.pushPhase(new SummonPhase(scene, 0)); diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 8240b6bcf84..cddc5798872 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -12,7 +12,7 @@ import { loadBattlerTag } from "../data/battler-tags"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; export default class PokemonData { public id: integer; @@ -33,7 +33,6 @@ export default class PokemonData { public stats: integer[]; public ivs: integer[]; public nature: Nature; - public natureOverride: Nature | -1; public moveset: (PokemonMove | null)[]; public status: Status | null; public friendship: integer; @@ -54,14 +53,20 @@ export default class PokemonData { public fusionVariant: Variant; public fusionGender: Gender; public fusionLuck: integer; - public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData; public boss: boolean; public bossSegments?: integer; public summonData: PokemonSummonData; + /** Data that can customize a Pokemon in non-standard ways from its Species */ - public mysteryEncounterPokemonData: MysteryEncounterPokemonData; + public customPokemonData: CustomPokemonData; + public fusionCustomPokemonData: CustomPokemonData; + + // Deprecated attributes, needed for now to allow SessionData migration (see PR#4619 comments) + public natureOverride: Nature | -1; + public mysteryEncounterPokemonData: CustomPokemonData | null; + public fusionMysteryEncounterPokemonData: CustomPokemonData | null; constructor(source: Pokemon | any, forHistory: boolean = false) { const sourcePokemon = source instanceof Pokemon ? source : null; @@ -107,9 +112,13 @@ export default class PokemonData { this.fusionVariant = source.fusionVariant; this.fusionGender = source.fusionGender; this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0); + this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData); this.usedTMs = source.usedTMs ?? []; - this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(source.mysteryEncounterPokemonData); + this.customPokemonData = new CustomPokemonData(source.customPokemonData); + + this.mysteryEncounterPokemonData = new CustomPokemonData(source.mysteryEncounterPokemonData); + this.fusionMysteryEncounterPokemonData = new CustomPokemonData(source.fusionMysteryEncounterPokemonData); if (!forHistory) { this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss); diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts index f93e09b7a90..e96afb5cbd4 100644 --- a/src/system/version_migration/version_converter.ts +++ b/src/system/version_migration/version_converter.ts @@ -4,6 +4,9 @@ import { version } from "../../../package.json"; // --- v1.0.4 (and below) PATCHES --- // import * as v1_0_4 from "./versions/v1_0_4"; +// --- v1.1.0 PATCHES --- // +import * as v1_1_0 from "./versions/v1_1_0"; + const LATEST_VERSION = version.split(".").map(value => parseInt(value)); /** @@ -131,6 +134,10 @@ class SessionVersionConverter extends VersionConverter { this.callMigrators(data, v1_0_4.sessionMigrators); } } + if (curMinor <= 1) { + console.log("Applying v1.1.0 session data migration!"); + this.callMigrators(data, v1_1_0.sessionMigrators); + } } console.log(`Session data successfully migrated to v${version}!`); @@ -153,6 +160,10 @@ class SystemVersionConverter extends VersionConverter { this.callMigrators(data, v1_0_4.systemMigrators); } } + if (curMinor <= 1) { + console.log("Applying v1.1.0 system data migraton!"); + this.callMigrators(data, v1_1_0.systemMigrators); + } } console.log(`System data successfully migrated to v${version}!`); @@ -175,6 +186,10 @@ class SettingsVersionConverter extends VersionConverter { this.callMigrators(data, v1_0_4.settingsMigrators); } } + if (curMinor <= 1) { + console.log("Applying v1.1.0 settings data migraton!"); + this.callMigrators(data, v1_1_0.settingsMigrators); + } } console.log(`System data successfully migrated to v${version}!`); diff --git a/src/system/version_migration/versions/v1_1_0.ts b/src/system/version_migration/versions/v1_1_0.ts new file mode 100644 index 00000000000..aac554c4531 --- /dev/null +++ b/src/system/version_migration/versions/v1_1_0.ts @@ -0,0 +1,32 @@ +import { SessionSaveData } from "../../game-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; + +export const systemMigrators = [] as const; + +export const settingsMigrators = [] as const; + +export const sessionMigrators = [ + /** + * Converts old Pokemon natureOverride and mysteryEncounterData + * to use the new conjoined {@linkcode Pokemon.customPokemonData} structure instead. + * @param data {@linkcode SessionSaveData} + */ + function migrateCustomPokemonDataAndNatureOverrides(data: SessionSaveData) { + // Fix Pokemon nature overrides and custom data migration + data.party.forEach(pokemon => { + if (pokemon["mysteryEncounterPokemonData"]) { + pokemon.customPokemonData = new CustomPokemonData(pokemon["mysteryEncounterPokemonData"]); + pokemon["mysteryEncounterPokemonData"] = null; + } + if (pokemon["fusionMysteryEncounterPokemonData"]) { + pokemon.fusionCustomPokemonData = new CustomPokemonData(pokemon["fusionMysteryEncounterPokemonData"]); + pokemon["fusionMysteryEncounterPokemonData"] = null; + } + pokemon.customPokemonData = pokemon.customPokemonData ?? new CustomPokemonData(); + if (pokemon["natureOverride"] && pokemon["natureOverride"] >= 0) { + pokemon.customPokemonData.nature = pokemon["natureOverride"]; + pokemon["natureOverride"] = -1; + } + }); + } +] as const; diff --git a/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index 0dc02e03c6d..e0f37c7e045 100644 --- a/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -476,10 +476,11 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(3); + expect(modifierSelectHandler.options.length).toEqual(4); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MASTER_BALL"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MAX_LURE"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toBe("FORM_CHANGE_ITEM"); + 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"); }); it("should leave encounter without battle", async () => { diff --git a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index b92a4d96adc..82ed558e6db 100644 --- a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -118,11 +118,11 @@ describe("Clowning Around - Mystery Encounter", () => { }); expect(config.pokemonConfigs?.[1]).toEqual({ species: getPokemonSpecies(Species.BLACEPHALON), - mysteryEncounterPokemonData: expect.anything(), + customPokemonData: expect.anything(), isBoss: true, moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ] }); - expect(config.pokemonConfigs?.[1].mysteryEncounterPokemonData?.types.length).toBe(2); + expect(config.pokemonConfigs?.[1].customPokemonData?.types.length).toBe(2); expect([ Abilities.STURDY, Abilities.PICKUP, @@ -139,8 +139,8 @@ describe("Clowning Around - Mystery Encounter", () => { Abilities.MAGICIAN, Abilities.SHEER_FORCE, Abilities.PRANKSTER - ]).toContain(config.pokemonConfigs?.[1].mysteryEncounterPokemonData?.ability); - expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs?.[1].mysteryEncounterPokemonData?.ability); + ]).toContain(config.pokemonConfigs?.[1].customPokemonData?.ability); + expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs?.[1].customPokemonData?.ability); await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled()); await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled()); expect(onInitResult).toBe(true); @@ -219,7 +219,7 @@ describe("Clowning Around - Mystery Encounter", () => { await game.phaseInterceptor.to(NewBattlePhase, false); const leadPokemon = scene.getParty()[0]; - expect(leadPokemon.mysteryEncounterPokemonData?.ability).toBe(abilityToTrain); + expect(leadPokemon.customPokemonData?.ability).toBe(abilityToTrain); }); }); @@ -340,9 +340,9 @@ describe("Clowning Around - Mystery Encounter", () => { scene.getParty()[2].moveset = []; await runMysteryEncounterToEnd(game, 3); - const leadTypesAfter = scene.getParty()[0].mysteryEncounterPokemonData?.types; - const secondaryTypesAfter = scene.getParty()[1].mysteryEncounterPokemonData?.types; - const thirdTypesAfter = scene.getParty()[2].mysteryEncounterPokemonData?.types; + const leadTypesAfter = scene.getParty()[0].customPokemonData?.types; + const secondaryTypesAfter = scene.getParty()[1].customPokemonData?.types; + const thirdTypesAfter = scene.getParty()[2].customPokemonData?.types; expect(leadTypesAfter.length).toBe(2); expect(leadTypesAfter[0]).toBe(Type.WATER); diff --git a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index f64124790b7..4d95ff31d36 100644 --- a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -21,7 +21,7 @@ import { BerryModifier, PokemonBaseStatTotalModifier } from "#app/modifier/modif import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; @@ -109,7 +109,7 @@ describe("The Strong Stuff - Mystery Encounter", () => { species: getPokemonSpecies(Species.SHUCKLE), isBoss: true, bossSegments: 5, - mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }), + customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), nature: Nature.BOLD, moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ], modifierConfigs: expect.any(Array), diff --git a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index f4d055c69aa..0d463655a52 100644 --- a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -122,7 +122,7 @@ describe("Weird Dream - Mystery Encounter", () => { for (let i = 0; i < pokemonAfter.length; i++) { const newPokemon = pokemonAfter[i]; expect(newPokemon.getSpeciesForm().speciesId).not.toBe(pokemonPrior[i].getSpeciesForm().speciesId); - expect(newPokemon.mysteryEncounterPokemonData?.types.length).toBe(2); + expect(newPokemon.customPokemonData?.types.length).toBe(2); } const plus90To110 = bstDiff.filter(bst => bst > 80); From 75a114a89f0e7fd1d8509e6573973f61cb1bc0f7 Mon Sep 17 00:00:00 2001 From: Lugiad <2070109+Adri1@users.noreply.github.com> Date: Mon, 21 Oct 2024 04:45:12 +0200 Subject: [PATCH 03/24] [Localization][UI/UX] emerald-pro font update (#4697) * Add files via upload * Delete public/fonts/pokemon-emerald-pro.ttf * Rename pokemon-emerald-pro2.ttf to pokemon-emerald-pro.ttf --- public/fonts/pokemon-emerald-pro.ttf | Bin 132104 -> 93528 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/fonts/pokemon-emerald-pro.ttf b/public/fonts/pokemon-emerald-pro.ttf index 509bd1aae868ee0779cd3443c72bcbd76e3f9d21..84e49ebbc40def70813370e460324d84e021cc69 100644 GIT binary patch literal 93528 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 Date: Mon, 21 Oct 2024 02:29:21 -0700 Subject: [PATCH 04/24] Replace duplicate code with `getDoubleBattleChance()` (#4690) --- src/battle-scene.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 096a84dde3d..a21e1b09342 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1191,10 +1191,7 @@ export default class BattleScene extends SceneBase { if (trainerConfigs[trainerType].doubleOnly) { doubleTrainer = true; } else if (trainerConfigs[trainerType].hasDouble) { - const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8); - this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance); - playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance)); - doubleTrainer = !Utils.randSeedInt(doubleChance.value); + doubleTrainer = !Utils.randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField)); // Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance if (trainerConfigs[trainerType].trainerTypeDouble && ![ TrainerType.TATE, TrainerType.LIZA ].includes(trainerType)) { doubleTrainer = false; From f51814467a3c32a1f0653b57ae1cd7e734c58ed2 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Mon, 21 Oct 2024 07:59:23 -0700 Subject: [PATCH 05/24] [P3] Fix mistimed sound effect in `LearnMovePhase` (#4698) --- src/phases/learn-move-phase.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index eb7cfbb65ef..fefda384092 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -170,13 +170,16 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { pokemon.setMove(index, this.moveId); initMoveAnim(this.scene, this.moveId).then(() => { loadMoveAnimAssets(this.scene, [ this.moveId ], true); - this.scene.playSound("level_up_fanfare"); // Sound loaded into game as is }); this.scene.ui.setMode(this.messageMode); const learnMoveText = i18next.t("battle:learnMove", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }); - textMessage = textMessage ? textMessage + "$" + learnMoveText : learnMoveText; - await this.scene.ui.showTextPromise(textMessage, this.messageMode === Mode.EVOLUTION_SCENE ? 1000 : undefined, true); - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true); - this.end(); + if (textMessage) { + await this.scene.ui.showTextPromise(textMessage); + } + this.scene.playSound("level_up_fanfare"); // Sound loaded into game as is + this.scene.ui.showText(learnMoveText, null, () => { + this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true); + this.end(); + }, this.messageMode === Mode.EVOLUTION_SCENE ? 1000 : undefined, true); } } From 966b07f62b3e2ca71719357918b681ddbbaf4f66 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 21 Oct 2024 08:00:58 -0700 Subject: [PATCH 06/24] [Misc] Shiny overrides can now force Pokemon to not be shiny (#4699) Also fixes random shinies breaking tests --- src/field/pokemon.ts | 15 +++++-- src/overrides.ts | 8 ++-- src/test/utils/helpers/challengeModeHelper.ts | 6 ++- src/test/utils/helpers/classicModeHelper.ts | 10 +++-- src/test/utils/helpers/dailyModeHelper.ts | 6 ++- src/test/utils/helpers/overridesHelper.ts | 42 ++++++++++++++++--- 6 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 7f2b4ec015d..0ee879ebf97 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3982,10 +3982,14 @@ export class PlayerPokemon extends Pokemon { if (Overrides.SHINY_OVERRIDE) { this.shiny = true; this.initShinySparkle(); - if (Overrides.VARIANT_OVERRIDE) { - this.variant = Overrides.VARIANT_OVERRIDE; - } + } else if (Overrides.SHINY_OVERRIDE === false) { + this.shiny = false; } + + if (Overrides.VARIANT_OVERRIDE !== null && this.shiny) { + this.variant = Overrides.VARIANT_OVERRIDE; + } + if (!dataSource) { if (this.scene.gameMode.isDaily) { this.generateAndPopulateMoveset(); @@ -4474,10 +4478,13 @@ export class EnemyPokemon extends Pokemon { if (Overrides.OPP_SHINY_OVERRIDE) { this.shiny = true; this.initShinySparkle(); + } else if (Overrides.OPP_SHINY_OVERRIDE === false) { + this.shiny = false; } + if (this.shiny) { this.variant = this.generateVariant(); - if (Overrides.OPP_VARIANT_OVERRIDE) { + if (Overrides.OPP_VARIANT_OVERRIDE !== null) { this.variant = Overrides.OPP_VARIANT_OVERRIDE; } } diff --git a/src/overrides.ts b/src/overrides.ts index 211d430a835..e1bfbd240f0 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -113,8 +113,8 @@ class DefaultOverrides { readonly STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly GENDER_OVERRIDE: Gender | null = null; readonly MOVESET_OVERRIDE: Moves | Array = []; - readonly SHINY_OVERRIDE: boolean = false; - readonly VARIANT_OVERRIDE: Variant = 0; + readonly SHINY_OVERRIDE: boolean | null = null; + readonly VARIANT_OVERRIDE: Variant | null = null; // -------------------------- // OPPONENT / ENEMY OVERRIDES @@ -134,8 +134,8 @@ class DefaultOverrides { readonly OPP_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly OPP_GENDER_OVERRIDE: Gender | null = null; readonly OPP_MOVESET_OVERRIDE: Moves | Array = []; - readonly OPP_SHINY_OVERRIDE: boolean = false; - readonly OPP_VARIANT_OVERRIDE: Variant = 0; + readonly OPP_SHINY_OVERRIDE: boolean | null = null; + readonly OPP_VARIANT_OVERRIDE: Variant | null = null; readonly OPP_IVS_OVERRIDE: number | number[] = []; readonly OPP_FORM_OVERRIDES: Partial> = {}; /** diff --git a/src/test/utils/helpers/challengeModeHelper.ts b/src/test/utils/helpers/challengeModeHelper.ts index 184f11f505c..5210d942d5a 100644 --- a/src/test/utils/helpers/challengeModeHelper.ts +++ b/src/test/utils/helpers/challengeModeHelper.ts @@ -38,6 +38,10 @@ export class ChallengeModeHelper extends GameManagerHelper { async runToSummon(species?: Species[]) { await this.game.runToTitle(); + if (this.game.override.disableShinies) { + this.game.override.shiny(false).enemyShiny(false); + } + this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => { this.game.scene.gameMode.challenges = this.challenges; const starters = generateStarter(this.game.scene, species); @@ -47,7 +51,7 @@ export class ChallengeModeHelper extends GameManagerHelper { }); await this.game.phaseInterceptor.run(EncounterPhase); - if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) { + if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) { this.game.removeEnemyHeldItems(); } } diff --git a/src/test/utils/helpers/classicModeHelper.ts b/src/test/utils/helpers/classicModeHelper.ts index 55e995fc9dc..80d0b86de7b 100644 --- a/src/test/utils/helpers/classicModeHelper.ts +++ b/src/test/utils/helpers/classicModeHelper.ts @@ -20,9 +20,13 @@ export class ClassicModeHelper extends GameManagerHelper { * @param species - Optional array of species to summon. * @returns A promise that resolves when the summon phase is reached. */ - async runToSummon(species?: Species[]) { + async runToSummon(species?: Species[]): Promise { await this.game.runToTitle(); + if (this.game.override.disableShinies) { + this.game.override.shiny(false).enemyShiny(false); + } + this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => { this.game.scene.gameMode = getGameMode(GameModes.CLASSIC); const starters = generateStarter(this.game.scene, species); @@ -32,7 +36,7 @@ export class ClassicModeHelper extends GameManagerHelper { }); await this.game.phaseInterceptor.run(EncounterPhase); - if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) { + if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) { this.game.removeEnemyHeldItems(); } } @@ -42,7 +46,7 @@ export class ClassicModeHelper extends GameManagerHelper { * @param species - Optional array of species to start the battle with. * @returns A promise that resolves when the battle is started. */ - async startBattle(species?: Species[]) { + async startBattle(species?: Species[]): Promise { await this.runToSummon(species); if (this.game.scene.battleStyle === BattleStyle.SWITCH) { diff --git a/src/test/utils/helpers/dailyModeHelper.ts b/src/test/utils/helpers/dailyModeHelper.ts index e40fada8ac7..813544f85df 100644 --- a/src/test/utils/helpers/dailyModeHelper.ts +++ b/src/test/utils/helpers/dailyModeHelper.ts @@ -21,6 +21,10 @@ export class DailyModeHelper extends GameManagerHelper { async runToSummon() { await this.game.runToTitle(); + if (this.game.override.disableShinies) { + this.game.override.shiny(false).enemyShiny(false); + } + this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => { const titlePhase = new TitlePhase(this.game.scene); titlePhase.initDailyRun(); @@ -33,7 +37,7 @@ export class DailyModeHelper extends GameManagerHelper { await this.game.phaseInterceptor.to(EncounterPhase); - if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) { + if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) { this.game.removeEnemyHeldItems(); } } diff --git a/src/test/utils/helpers/overridesHelper.ts b/src/test/utils/helpers/overridesHelper.ts index 27fd9552fe4..ec4d8dbbe4c 100644 --- a/src/test/utils/helpers/overridesHelper.ts +++ b/src/test/utils/helpers/overridesHelper.ts @@ -19,6 +19,11 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; * Helper to handle overrides in tests */ export class OverridesHelper extends GameManagerHelper { + /** If `true`, removes the starting items from enemies at the start of each test; default `true` */ + public removeEnemyStartingItems: boolean = true; + /** If `true`, sets the shiny overrides to disable shinies at the start of each test; default `true` */ + public disableShinies: boolean = true; + /** * Override the starting biome * @warning Any event listeners that are attached to [NewArenaEvent](events\battle-scene.ts) may need to be handled down the line @@ -368,23 +373,50 @@ export class OverridesHelper extends GameManagerHelper { /** * Override player shininess - * @param shininess Whether the player's Pokemon should be shiny. + * @param shininess - `true` or `false` to force the player's pokemon to be shiny or not shiny, + * `null` to disable the override and re-enable RNG shinies. */ - shinyLevel(shininess: boolean): this { + shiny(shininess: boolean | null): this { vi.spyOn(Overrides, "SHINY_OVERRIDE", "get").mockReturnValue(shininess); - this.log(`Set player Pokemon as ${shininess ? "" : "not "}shiny!`); + if (shininess === null) { + this.log("Disabled player Pokemon shiny override!"); + } else { + this.log(`Set player Pokemon to be ${shininess ? "" : "not "}shiny!`); + } return this; } + /** * Override player shiny variant - * @param variant The player's shiny variant. + * @param variant - The player's shiny variant. */ - variantLevel(variant: Variant): this { + shinyVariant(variant: Variant): this { vi.spyOn(Overrides, "VARIANT_OVERRIDE", "get").mockReturnValue(variant); this.log(`Set player Pokemon's shiny variant to ${variant}!`); return this; } + /** + * Override enemy shininess + * @param shininess - `true` or `false` to force the enemy's pokemon to be shiny or not shiny, + * `null` to disable the override and re-enable RNG shinies. + * @param variant - (Optional) The enemy's shiny {@linkcode Variant}. + */ + enemyShiny(shininess: boolean | null, variant?: Variant): this { + vi.spyOn(Overrides, "OPP_SHINY_OVERRIDE", "get").mockReturnValue(shininess); + if (shininess === null) { + this.log("Disabled enemy Pokemon shiny override!"); + } else { + this.log(`Set enemy Pokemon to be ${shininess ? "" : "not "}shiny!`); + } + + if (variant !== undefined) { + vi.spyOn(Overrides, "OPP_VARIANT_OVERRIDE", "get").mockReturnValue(variant); + this.log(`Set enemy shiny variant to be ${variant}!`); + } + return this; + } + /** * Override the enemy (Pokemon) to have the given amount of health segments * @param healthSegments the number of segments to give From f7797603a1511177b36c57a8c9c7d413c2fb22aa Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 21 Oct 2024 08:03:12 -0700 Subject: [PATCH 07/24] [P2] Fix oversight where hazards cannnot affect Pokemon that set it (#4693) Fixes #935 --- src/data/arena-tag.ts | 2 +- src/test/moves/toxic_spikes.test.ts | 55 +++++++++++++++-------------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 6507d34dcfd..ed4c2789165 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -626,7 +626,7 @@ export class ArenaTrapTag extends ArenaTag { * @returns `true` if this hazard affects the given Pokemon; `false` otherwise. */ override apply(arena: Arena, simulated: boolean, pokemon: Pokemon): boolean { - if (this.sourceId === pokemon.id || (this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) { + if ((this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) { return false; } diff --git a/src/test/moves/toxic_spikes.test.ts b/src/test/moves/toxic_spikes.test.ts index bac1ccdccd8..a5c63a2652f 100644 --- a/src/test/moves/toxic_spikes.test.ts +++ b/src/test/moves/toxic_spikes.test.ts @@ -9,8 +9,6 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; - describe("Moves - Toxic Spikes", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -34,10 +32,10 @@ describe("Moves - Toxic Spikes", () => { .enemyAbility(Abilities.BALL_FETCH) .ability(Abilities.BALL_FETCH) .enemyMoveset(Moves.SPLASH) - .moveset([ Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR ]); + .moveset([ Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR, Moves.COURT_CHANGE ]); }); - it("should not affect the opponent if they do not switch", async() => { + it("should not affect the opponent if they do not switch", async () => { await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]); const enemy = game.scene.getEnemyField()[0]; @@ -51,9 +49,9 @@ describe("Moves - Toxic Spikes", () => { expect(enemy.hp).toBe(enemy.getMaxHp()); expect(enemy.status?.effect).toBeUndefined(); - }, TIMEOUT); + }); - it("should poison the opponent if they switch into 1 layer", async() => { + it("should poison the opponent if they switch into 1 layer", async () => { await game.classicMode.runToSummon([ Species.MIGHTYENA ]); game.move.select(Moves.TOXIC_SPIKES); @@ -65,9 +63,9 @@ describe("Moves - Toxic Spikes", () => { expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); expect(enemy.status?.effect).toBe(StatusEffect.POISON); - }, TIMEOUT); + }); - it("should badly poison the opponent if they switch into 2 layers", async() => { + it("should badly poison the opponent if they switch into 2 layers", async () => { await game.classicMode.runToSummon([ Species.MIGHTYENA ]); game.move.select(Moves.TOXIC_SPIKES); @@ -80,27 +78,32 @@ describe("Moves - Toxic Spikes", () => { const enemy = game.scene.getEnemyField()[0]; expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); expect(enemy.status?.effect).toBe(StatusEffect.TOXIC); - }, TIMEOUT); + }); - it("should be removed if a grounded poison pokemon switches in", async() => { - game.override.enemySpecies(Species.GRIMER); - await game.classicMode.runToSummon([ Species.MIGHTYENA ]); + it("should be removed if a grounded poison pokemon switches in", async () => { + await game.classicMode.runToSummon([ Species.MUK, Species.PIDGEY ]); + + const muk = game.scene.getPlayerPokemon()!; game.move.select(Moves.TOXIC_SPIKES); - await game.phaseInterceptor.to("TurnEndPhase"); - game.move.select(Moves.TOXIC_SPIKES); - await game.phaseInterceptor.to("TurnEndPhase"); - game.move.select(Moves.ROAR); - await game.phaseInterceptor.to("TurnEndPhase"); - - const enemy = game.scene.getEnemyField()[0]; - expect(enemy.hp).toBe(enemy.getMaxHp()); - expect(enemy.status?.effect).toBeUndefined(); + await game.toNextTurn(); + // also make sure the toxic spikes are removed even if the pokemon + // that set them up is the one switching in (https://github.com/pagefaultgames/pokerogue/issues/935) + game.move.select(Moves.COURT_CHANGE); + await game.toNextTurn(); + game.doSwitchPokemon(1); + await game.toNextTurn(); + game.doSwitchPokemon(1); + await game.toNextTurn(); + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + expect(muk.isFullHp()).toBe(true); + expect(muk.status?.effect).toBeUndefined(); expect(game.scene.arena.tags.length).toBe(0); - }, TIMEOUT); + }); - it("shouldn't create multiple layers per use in doubles", async() => { + it("shouldn't create multiple layers per use in doubles", async () => { await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]); game.move.select(Moves.TOXIC_SPIKES); @@ -109,9 +112,9 @@ describe("Moves - Toxic Spikes", () => { const arenaTags = (game.scene.arena.getTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag); expect(arenaTags.tagType).toBe(ArenaTagType.TOXIC_SPIKES); expect(arenaTags.layers).toBe(1); - }, TIMEOUT); + }); - it("should persist through reload", async() => { + it("should persist through reload", async () => { game.override.startingWave(1); const scene = game.scene; const gameData = new GameData(scene); @@ -132,5 +135,5 @@ describe("Moves - Toxic Spikes", () => { expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags); localStorage.removeItem("sessionTestData"); - }, TIMEOUT); + }); }); From 467841d167de63a2adb2c9169503fc1dcabadfbd Mon Sep 17 00:00:00 2001 From: PrabbyDD <147005742+PrabbyDD@users.noreply.github.com> Date: Mon, 21 Oct 2024 18:33:41 -0700 Subject: [PATCH 08/24] [P2] Nightmare triggers at turn end instead of after move (#4702) --- src/data/battler-tags.ts | 2 +- src/test/moves/nightmare.test.ts | 54 ++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/test/moves/nightmare.test.ts diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 4977a8da5a9..e0616c341be 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -838,7 +838,7 @@ export class SeedTag extends BattlerTag { export class NightmareTag extends BattlerTag { constructor() { - super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.AFTER_MOVE, 1, Moves.NIGHTMARE); + super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.TURN_END, 1, Moves.NIGHTMARE); } onAdd(pokemon: Pokemon): void { diff --git a/src/test/moves/nightmare.test.ts b/src/test/moves/nightmare.test.ts new file mode 100644 index 00000000000..61b133a3280 --- /dev/null +++ b/src/test/moves/nightmare.test.ts @@ -0,0 +1,54 @@ +import { CommandPhase } from "#app/phases/command-phase"; +import { TurnInitPhase } from "#app/phases/turn-init-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { StatusEffect } from "#app/data/status-effect"; + +describe("Moves - Nightmare", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override.battleType("single") + .enemySpecies(Species.RATTATA) + .enemyMoveset(Moves.SPLASH) + .enemyAbility(Abilities.BALL_FETCH) + .enemyStatusEffect(StatusEffect.SLEEP) + .startingLevel(5) + .moveset([ Moves.NIGHTMARE, Moves.SPLASH ]); + }); + + it("lowers enemy hp by 1/4 each turn while asleep", async () => { + await game.classicMode.startBattle([ Species.HYPNO ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const enemyMaxHP = enemyPokemon.hp; + game.move.select(Moves.NIGHTMARE); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(enemyPokemon.hp).toBe(enemyMaxHP - Math.floor(enemyMaxHP / 4)); + + // take a second turn to make sure damage occurs again + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.SPLASH); + + await game.phaseInterceptor.to(TurnInitPhase); + expect(enemyPokemon.hp).toBe(enemyMaxHP - Math.floor(enemyMaxHP / 4) - Math.floor(enemyMaxHP / 4)); + }); +}); From c2eb9de9df6cd7ba625dd73c3812274f47994731 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Mon, 21 Oct 2024 18:41:25 -0700 Subject: [PATCH 09/24] [Ability] Partially Implement Infiltrator (does not work with mist) (#4636) * Implement Infiltrator * Integration tests + Infiltrator is (P) again * Fix screen tests * Fix `hitsSubstitute` * docs for Infiltrator attr --- src/data/ability.ts | 27 ++++++- src/data/arena-tag.ts | 23 +++++- src/data/move.ts | 15 ++-- src/field/pokemon.ts | 28 +++++-- src/phases/stat-stage-change-phase.ts | 3 +- src/test/abilities/infiltrator.test.ts | 107 +++++++++++++++++++++++++ src/test/moves/aurora_veil.test.ts | 2 +- src/test/moves/light_screen.test.ts | 2 +- src/test/moves/reflect.test.ts | 2 +- 9 files changed, 190 insertions(+), 19 deletions(-) create mode 100644 src/test/abilities/infiltrator.test.ts diff --git a/src/data/ability.ts b/src/data/ability.ts index 33f6e0522f7..0d5cf2751ce 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -4342,6 +4342,30 @@ export class AlwaysHitAbAttr extends AbAttr { } /** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */ export class IgnoreProtectOnContactAbAttr extends AbAttr { } +/** + * Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Infiltrator_(Ability) | Infiltrator}. + * Allows the source's moves to bypass the effects of opposing Light Screen, Reflect, Aurora Veil, Safeguard, Mist, and Substitute. + */ +export class InfiltratorAbAttr extends AbAttr { + /** + * Sets a flag to bypass screens, Substitute, Safeguard, and Mist + * @param pokemon n/a + * @param passive n/a + * @param simulated n/a + * @param cancelled n/a + * @param args `[0]` a {@linkcode Utils.BooleanHolder | BooleanHolder} containing the flag + * @returns `true` if the bypass flag was successfully set; `false` otherwise. + */ + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: null, args: any[]): boolean { + const bypassed = args[0]; + if (args[0] instanceof Utils.BooleanHolder) { + bypassed.value = true; + return true; + } + return false; + } +} + export class UncopiableAbilityAbAttr extends AbAttr { constructor() { super(false); @@ -5321,7 +5345,8 @@ export function initAbilities() { .attr(PostSummonTransformAbAttr) .attr(UncopiableAbilityAbAttr), new Ability(Abilities.INFILTRATOR, 5) - .unimplemented(), + .attr(InfiltratorAbAttr) + .partial(), // does not bypass Mist new Ability(Abilities.MUMMY, 5) .attr(PostDefendAbilityGiveAbAttr, Abilities.MUMMY) .bypassFaint(), diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index ed4c2789165..aa6aec6f73a 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -7,7 +7,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon"; import { StatusEffect } from "#app/data/status-effect"; import { BattlerIndex } from "#app/battle"; -import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability"; +import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, InfiltratorAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability"; import { Stat } from "#enums/stat"; import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; import i18next from "i18next"; @@ -130,7 +130,18 @@ export class MistTag extends ArenaTag { * to flag the stat reduction as cancelled * @returns `true` if a stat reduction was cancelled; `false` otherwise */ - override apply(arena: Arena, simulated: boolean, cancelled: BooleanHolder): boolean { + override apply(arena: Arena, simulated: boolean, attacker: Pokemon, cancelled: BooleanHolder): boolean { + // `StatStageChangePhase` currently doesn't have a reference to the source of stat drops, + // so this code currently has no effect on gameplay. + if (attacker) { + const bypassed = new BooleanHolder(false); + // TODO: Allow this to be simulated + applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); + if (bypassed.value) { + return false; + } + } + cancelled.value = true; if (!simulated) { @@ -169,12 +180,18 @@ export class WeakenMoveScreenTag extends ArenaTag { * * @param arena the {@linkcode Arena} where the move is applied. * @param simulated n/a + * @param attacker the attacking {@linkcode Pokemon} * @param moveCategory the attacking move's {@linkcode MoveCategory}. * @param damageMultiplier A {@linkcode NumberHolder} containing the damage multiplier * @returns `true` if the attacking move was weakened; `false` otherwise. */ - override apply(arena: Arena, simulated: boolean, moveCategory: MoveCategory, damageMultiplier: NumberHolder): boolean { + override apply(arena: Arena, simulated: boolean, attacker: Pokemon, moveCategory: MoveCategory, damageMultiplier: NumberHolder): boolean { if (this.weakenedCategories.includes(moveCategory)) { + const bypassed = new BooleanHolder(false); + applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); + if (bypassed.value) { + return false; + } damageMultiplier.value = arena.scene.currentBattle.double ? 2732 / 4096 : 0.5; return true; } diff --git a/src/data/move.ts b/src/data/move.ts index 309a2d3a7eb..0d9c57bf094 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -8,7 +8,7 @@ import { Constructor, NumberHolder } from "#app/utils"; import * as Utils from "../utils"; import { WeatherType } from "./weather"; import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag"; -import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability"; +import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, InfiltratorAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability"; import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier"; import { BattlerIndex, BattleType } from "../battle"; import { TerrainType } from "./terrain"; @@ -346,7 +346,11 @@ export default class Move implements Localizable { return false; } - return !user.hasAbility(Abilities.INFILTRATOR) + const bypassed = new Utils.BooleanHolder(false); + // TODO: Allow this to be simulated + applyAbAttrs(InfiltratorAbAttr, user, null, false, bypassed); + + return !bypassed.value && !this.hasFlag(MoveFlags.SOUND_BASED) && !this.hasFlag(MoveFlags.IGNORE_SUBSTITUTE); } @@ -2074,7 +2078,7 @@ export class StatusEffectAttr extends MoveEffectAttr { } } - if (user !== target && target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)) { + if (user !== target && target.isSafeguarded(user)) { if (move.category === MoveCategory.STATUS) { user.scene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) })); } @@ -5161,7 +5165,7 @@ export class ConfuseAttr extends AddBattlerTagAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!this.selfTarget && target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)) { + if (!this.selfTarget && target.isSafeguarded(user)) { if (move.category === MoveCategory.STATUS) { user.scene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) })); } @@ -7598,6 +7602,7 @@ export function initMoves() { .ignoresVirtual(), new StatusMove(Moves.TRANSFORM, Type.NORMAL, -1, 10, -1, 0, 1) .attr(TransformAttr) + .condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE)) .ignoresProtect(), new AttackMove(Moves.BUBBLE, Type.WATER, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1) .attr(StatStageChangeAttr, [ Stat.SPD ], -1) @@ -8028,7 +8033,7 @@ export function initMoves() { .attr(RemoveScreensAttr), new StatusMove(Moves.YAWN, Type.NORMAL, -1, 10, -1, 0, 3) .attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true) - .condition((user, target, move) => !target.status && !target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)), + .condition((user, target, move) => !target.status && !target.isSafeguarded(user)), new AttackMove(Moves.KNOCK_OFF, Type.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(RemoveHeldItemAttr, false), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0ee879ebf97..94b9fd12540 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -22,7 +22,7 @@ import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/ import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags"; import { WeatherType } from "#app/data/weather"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; -import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "#app/data/ability"; +import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr } from "#app/data/ability"; import PokemonData from "#app/system/pokemon-data"; import { BattlerIndex } from "#app/battle"; import { Mode } from "#app/ui/ui"; @@ -2610,7 +2610,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */ const screenMultiplier = new Utils.NumberHolder(1); - this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, moveCategory, screenMultiplier); + this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, source, moveCategory, screenMultiplier); /** * For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if: @@ -3352,13 +3352,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - const types = this.getTypes(true, true); - - const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (sourcePokemon && sourcePokemon !== this && this.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) { + if (sourcePokemon && sourcePokemon !== this && this.isSafeguarded(sourcePokemon)) { return false; } + const types = this.getTypes(true, true); + switch (effect) { case StatusEffect.POISON: case StatusEffect.TOXIC: @@ -3504,6 +3503,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } + /** + * Checks if this Pokemon is protected by Safeguard + * @param attacker the {@linkcode Pokemon} inflicting status on this Pokemon + * @returns `true` if this Pokemon is protected by Safeguard; `false` otherwise. + */ + isSafeguarded(attacker: Pokemon): boolean { + const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + if (this.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) { + const bypassed = new Utils.BooleanHolder(false); + if (attacker) { + applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); + } + return !bypassed.value; + } + return false; + } + primeSummonData(summonDataPrimer: PokemonSummonData): void { this.summonDataPrimer = summonDataPrimer; } diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 4c13b883445..2d4b3ce6c6f 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -64,7 +64,8 @@ export class StatStageChangePhase extends PokemonPhase { const cancelled = new BooleanHolder(false); if (!this.selfTarget && stages.value < 0) { - this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, cancelled); + // TODO: add a reference to the source of the stat change to fix Infiltrator interaction + this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, null, false, cancelled); } if (!cancelled.value && !this.selfTarget && stages.value < 0) { diff --git a/src/test/abilities/infiltrator.test.ts b/src/test/abilities/infiltrator.test.ts new file mode 100644 index 00000000000..01c5cef7796 --- /dev/null +++ b/src/test/abilities/infiltrator.test.ts @@ -0,0 +1,107 @@ +import { ArenaTagSide } from "#app/data/arena-tag"; +import { allMoves } from "#app/data/move"; +import { ArenaTagType } from "#enums/arena-tag-type"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Stat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Infiltrator", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.TACKLE, Moves.WATER_GUN, Moves.SPORE, Moves.BABY_DOLL_EYES ]) + .ability(Abilities.INFILTRATOR) + .battleType("single") + .disableCrits() + .enemySpecies(Species.SNORLAX) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .startingLevel(100) + .enemyLevel(100); + }); + + it.each([ + { effectName: "Light Screen", tagType: ArenaTagType.LIGHT_SCREEN, move: Moves.WATER_GUN }, + { effectName: "Reflect", tagType: ArenaTagType.REFLECT, move: Moves.TACKLE }, + { effectName: "Aurora Veil", tagType: ArenaTagType.AURORA_VEIL, move: Moves.TACKLE } + ])("should bypass the target's $effectName", async ({ tagType, move }) => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const player = game.scene.getPlayerPokemon()!; + const enemy = game.scene.getEnemyPokemon()!; + + const preScreenDmg = enemy.getAttackDamage(player, allMoves[move]).damage; + + game.scene.arena.addTag(tagType, 1, Moves.NONE, enemy.id, ArenaTagSide.ENEMY, true); + + const postScreenDmg = enemy.getAttackDamage(player, allMoves[move]).damage; + + expect(postScreenDmg).toBe(preScreenDmg); + expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.INFILTRATOR); + }); + + it("should bypass the target's Safeguard", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const player = game.scene.getPlayerPokemon()!; + const enemy = game.scene.getEnemyPokemon()!; + + game.scene.arena.addTag(ArenaTagType.SAFEGUARD, 1, Moves.NONE, enemy.id, ArenaTagSide.ENEMY, true); + + game.move.select(Moves.SPORE); + + await game.phaseInterceptor.to("BerryPhase", false); + expect(enemy.status?.effect).toBe(StatusEffect.SLEEP); + expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.INFILTRATOR); + }); + + // TODO: fix this interaction to pass this test + it.skip("should bypass the target's Mist", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const player = game.scene.getPlayerPokemon()!; + const enemy = game.scene.getEnemyPokemon()!; + + game.scene.arena.addTag(ArenaTagType.MIST, 1, Moves.NONE, enemy.id, ArenaTagSide.ENEMY, true); + + game.move.select(Moves.BABY_DOLL_EYES); + + await game.phaseInterceptor.to("MoveEndPhase"); + expect(enemy.getStatStage(Stat.ATK)).toBe(-1); + expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.INFILTRATOR); + }); + + it("should bypass the target's Substitute", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const player = game.scene.getPlayerPokemon()!; + const enemy = game.scene.getEnemyPokemon()!; + + enemy.addTag(BattlerTagType.SUBSTITUTE, 1, Moves.NONE, enemy.id); + + game.move.select(Moves.BABY_DOLL_EYES); + + await game.phaseInterceptor.to("MoveEndPhase"); + expect(enemy.getStatStage(Stat.ATK)).toBe(-1); + expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.INFILTRATOR); + }); +}); diff --git a/src/test/moves/aurora_veil.test.ts b/src/test/moves/aurora_veil.test.ts index 243ba3a3269..e68117a2f59 100644 --- a/src/test/moves/aurora_veil.test.ts +++ b/src/test/moves/aurora_veil.test.ts @@ -111,7 +111,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (defender.scene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) { - defender.scene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, false, move.category, multiplierHolder); + defender.scene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, false, attacker, move.category, multiplierHolder); } return move.power * multiplierHolder.value; diff --git a/src/test/moves/light_screen.test.ts b/src/test/moves/light_screen.test.ts index 11b8144bb4e..af14d9273e6 100644 --- a/src/test/moves/light_screen.test.ts +++ b/src/test/moves/light_screen.test.ts @@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (defender.scene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) { - defender.scene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, move.category, multiplierHolder); + defender.scene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, attacker, move.category, multiplierHolder); } return move.power * multiplierHolder.value; diff --git a/src/test/moves/reflect.test.ts b/src/test/moves/reflect.test.ts index b18b2423895..3bf415ea75c 100644 --- a/src/test/moves/reflect.test.ts +++ b/src/test/moves/reflect.test.ts @@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (defender.scene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) { - defender.scene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, move.category, multiplierHolder); + defender.scene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, attacker, move.category, multiplierHolder); } return move.power * multiplierHolder.value; From 7066a15ceb2eee2df7ba13778294f198bbee9c53 Mon Sep 17 00:00:00 2001 From: Mason S <132116525+ElizaAlex@users.noreply.github.com> Date: Tue, 22 Oct 2024 02:53:00 -0400 Subject: [PATCH 10/24] [Refactor] Added `BattlerTagLapseType.AFTER_HIT` (#3655) * [Refactor] Added ON_GET_HIT BattlerTagLapseType Adjusted BeakBlastChargingTag and ShellTrapTag to use new lapse type Adjusted MoveEffectPhase to now lapse all tags with the ON_GET_HIT lapse type * [Refactor] Added ON_GET_HIT BattlerTagLapseType Adjusted BeakBlastChargingTag and ShellTrapTag to use new lapse type Adjusted MoveEffectPhase to now lapse all tags with the ON_GET_HIT lapse type * Fix nits * Rename `ON_GET_HIT` to `AFTER_HIT` Change `isOpponentTo` to `isOpponent` * Fix a couple minor nits * Remove single-use function --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/battler-tags.ts | 165 ++++++++++++++++++++------------ src/field/pokemon.ts | 9 ++ src/phases/move-effect-phase.ts | 6 +- 3 files changed, 113 insertions(+), 67 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index e0616c341be..c3b7765d062 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1,29 +1,44 @@ -import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims"; -import { getPokemonNameWithAffix } from "../messages"; -import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; -import { StatusEffect } from "./status-effect"; -import * as Utils from "../utils"; -import { ChargeAttr, MoveFlags, allMoves, MoveCategory, applyMoveAttrs, StatusCategoryOnAllyAttr, HealOnAllyAttr, ConsecutiveUseDoublePowerAttr } from "./move"; -import { Type } from "./type"; -import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability"; -import { TerrainType } from "./terrain"; -import { WeatherType } from "./weather"; -import { allAbilities } from "./ability"; -import { SpeciesFormChangeManualTrigger } from "./pokemon-forms"; -import { Abilities } from "#enums/abilities"; -import { BattlerTagType } from "#enums/battler-tag-type"; -import { Moves } from "#enums/moves"; -import { Species } from "#enums/species"; -import i18next from "#app/plugins/i18n"; -import { Stat, type BattleStat, type EffectiveStat, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat"; +import BattleScene from "#app/battle-scene"; +import { + allAbilities, + applyAbAttrs, + BlockNonDirectDamageAbAttr, + FlinchEffectAbAttr, + ProtectStatAbAttr, + ReverseDrainAbAttr +} from "#app/data/ability"; +import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims"; +import Move, { + allMoves, + applyMoveAttrs, + ChargeAttr, + ConsecutiveUseDoublePowerAttr, + HealOnAllyAttr, + MoveCategory, + MoveFlags, + StatusCategoryOnAllyAttr +} from "#app/data/move"; +import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms"; +import { StatusEffect } from "#app/data/status-effect"; +import { TerrainType } from "#app/data/terrain"; +import { Type } from "#app/data/type"; +import { WeatherType } from "#app/data/weather"; +import Pokemon, { HitResult, MoveResult } from "#app/field/pokemon"; +import { getPokemonNameWithAffix } from "#app/messages"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MovePhase } from "#app/phases/move-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; -import { StatStageChangePhase, StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; -import { PokemonAnimType } from "#app/enums/pokemon-anim-type"; -import BattleScene from "#app/battle-scene"; +import { StatStageChangeCallback, StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; +import i18next from "#app/plugins/i18n"; +import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Moves } from "#enums/moves"; +import { PokemonAnimType } from "#enums/pokemon-anim-type"; +import { Species } from "#enums/species"; +import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat } from "#enums/stat"; export enum BattlerTagLapseType { FAINT, @@ -33,6 +48,7 @@ export enum BattlerTagLapseType { MOVE_EFFECT, TURN_END, HIT, + AFTER_HIT, CUSTOM } @@ -405,7 +421,7 @@ export class RechargingTag extends BattlerTag { */ export class BeakBlastChargingTag extends BattlerTag { constructor() { - super(BattlerTagType.BEAK_BLAST_CHARGING, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], 1, Moves.BEAK_BLAST); + super(BattlerTagType.BEAK_BLAST_CHARGING, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END, BattlerTagLapseType.AFTER_HIT ], 1, Moves.BEAK_BLAST); } onAdd(pokemon: Pokemon): void { @@ -421,16 +437,13 @@ export class BeakBlastChargingTag extends BattlerTag { * 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 - * @returns `true` if invoked with the CUSTOM lapse type; `false` otherwise + * @returns `true` if invoked with the `AFTER_HIT` lapse type */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { - if (lapseType === BattlerTagLapseType.CUSTOM) { - const effectPhase = pokemon.scene.getCurrentPhase(); - if (effectPhase instanceof MoveEffectPhase) { - const attacker = effectPhase.getPokemon(); - if (effectPhase.move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { - attacker.trySetStatus(StatusEffect.BURN, true, pokemon); - } + if (lapseType === BattlerTagLapseType.AFTER_HIT) { + const phaseData = getMoveEffectPhaseData(pokemon); + if (phaseData?.move.hasFlag(MoveFlags.MAKES_CONTACT)) { + phaseData.attacker.trySetStatus(StatusEffect.BURN, true, pokemon); } return true; } @@ -444,11 +457,10 @@ export class BeakBlastChargingTag extends BattlerTag { * @see {@link https://bulbapedia.bulbagarden.net/wiki/Shell_Trap_(move) | Shell Trap} */ export class ShellTrapTag extends BattlerTag { - public activated: boolean; + public activated: boolean = false; constructor() { - super(BattlerTagType.SHELL_TRAP, BattlerTagLapseType.TURN_END, 1); - this.activated = false; + super(BattlerTagType.SHELL_TRAP, [ BattlerTagLapseType.TURN_END, BattlerTagLapseType.AFTER_HIT ], 1); } onAdd(pokemon: Pokemon): void { @@ -459,25 +471,33 @@ 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 - * @returns `true` if invoked with the `CUSTOM` lapse type; `false` otherwise + * @returns `true` if invoked with the `AFTER_HIT` lapse type */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { - if (lapseType === BattlerTagLapseType.CUSTOM) { - const shellTrapPhaseIndex = pokemon.scene.phaseQueue.findIndex( - phase => phase instanceof MovePhase && phase.pokemon === pokemon - ); - const firstMovePhaseIndex = pokemon.scene.phaseQueue.findIndex( - phase => phase instanceof MovePhase - ); + if (lapseType === BattlerTagLapseType.AFTER_HIT) { + const phaseData = getMoveEffectPhaseData(pokemon); - if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) { - const shellTrapMovePhase = pokemon.scene.phaseQueue.splice(shellTrapPhaseIndex, 1)[0]; - pokemon.scene.prependToPhase(shellTrapMovePhase, MovePhase); + // Trap should only be triggered by opponent's Physical moves + if (phaseData?.move.category === MoveCategory.PHYSICAL && pokemon.isOpponent(phaseData.attacker)) { + const shellTrapPhaseIndex = pokemon.scene.phaseQueue.findIndex( + phase => phase instanceof MovePhase && phase.pokemon === pokemon + ); + const firstMovePhaseIndex = pokemon.scene.phaseQueue.findIndex( + phase => phase instanceof MovePhase + ); + + // Only shift MovePhase timing if it's not already next up + if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) { + const shellTrapMovePhase = pokemon.scene.phaseQueue.splice(shellTrapPhaseIndex, 1)[0]; + pokemon.scene.prependToPhase(shellTrapMovePhase, MovePhase); + } + + this.activated = true; } - this.activated = true; return true; } + return super.lapse(pokemon, lapseType); } } @@ -641,7 +661,7 @@ export class ConfusedTag extends BattlerTag { if (pokemon.randSeedInt(3) === 0) { const atk = pokemon.getEffectiveStat(Stat.ATK); const def = pokemon.getEffectiveStat(Stat.DEF); - const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100)); + const damage = toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100)); pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself")); pokemon.damageAndUpdate(damage); pokemon.battleData.hitCount++; @@ -812,13 +832,13 @@ export class SeedTag extends BattlerTag { if (ret) { const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex); if (source) { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED)); - const damage = pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8)); + const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8)); const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false); pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), !reverseDrain ? damage : damage * -1, @@ -860,11 +880,11 @@ export class NightmareTag extends BattlerTag { pokemon.scene.queueMessage(i18next.t("battlerTags:nightmareLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 4)); + pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4)); } } @@ -1004,7 +1024,7 @@ export class IngrainTag extends TrappedTag { new PokemonHealPhase( pokemon.scene, pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / 16), + toDmgValue(pokemon.getMaxHp() / 16), i18next.t("battlerTags:ingrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), true ) @@ -1067,7 +1087,7 @@ export class AquaRingTag extends BattlerTag { new PokemonHealPhase( pokemon.scene, pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / 16), + toDmgValue(pokemon.getMaxHp() / 16), i18next.t("battlerTags:aquaRingLapse", { moveName: this.getMoveName(), pokemonName: getPokemonNameWithAffix(pokemon) @@ -1161,11 +1181,11 @@ export abstract class DamagingTrapTag extends TrappedTag { ); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim)); - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8)); + pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8)); } } @@ -1356,7 +1376,7 @@ export class ContactDamageProtectedTag extends ProtectedTag { if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { const attacker = effectPhase.getPokemon(); if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { - attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); + attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); } } } @@ -1709,7 +1729,7 @@ export class SemiInvulnerableTag extends BattlerTag { onRemove(pokemon: Pokemon): void { // Wait 2 frames before setting visible for battle animations that don't immediately show the sprite invisible pokemon.scene.tweens.addCounter({ - duration: Utils.getFrameMs(2), + duration: getFrameMs(2), onComplete: () => pokemon.setVisible(true) }); } @@ -1860,12 +1880,12 @@ export class SaltCuredTag extends BattlerTag { if (ret) { pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE)); - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { const pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER); - pokemon.damageAndUpdate(Utils.toDmgValue(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8)); + pokemon.damageAndUpdate(toDmgValue(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8)); pokemon.scene.queueMessage( i18next.t("battlerTags:saltCuredLapse", { @@ -1907,11 +1927,11 @@ export class CursedTag extends BattlerTag { if (ret) { pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE)); - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 4)); + pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4)); pokemon.scene.queueMessage(i18next.t("battlerTags:cursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } } @@ -2173,7 +2193,7 @@ export class GulpMissileTag extends BattlerTag { return true; } - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled); if (!cancelled.value) { @@ -2289,7 +2309,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { * @returns `true` if the move cannot be used because the target is an ally */ override isMoveTargetRestricted(move: Moves, user: Pokemon, target: Pokemon) { - const moveCategory = new Utils.IntegerHolder(allMoves[move].category); + const moveCategory = new NumberHolder(allMoves[move].category); applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory); if (allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS ) { return true; @@ -2506,7 +2526,7 @@ export class MysteryEncounterPostSummonTag extends BattlerTag { const ret = super.lapse(pokemon, lapseType); if (lapseType === BattlerTagLapseType.CUSTOM) { - const cancelled = new Utils.BooleanHolder(false); + const cancelled = new BooleanHolder(false); applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); if (!cancelled.value) { if (pokemon.mysteryEncounterBattleEffects) { @@ -2955,3 +2975,22 @@ export function loadBattlerTag(source: BattlerTag | any): BattlerTag { tag.loadTag(source); return tag; } + +/** + * 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 + * corresponding {@linkcode Move} and user {@linkcode Pokemon} + */ +function getMoveEffectPhaseData(pokemon: Pokemon): {phase: MoveEffectPhase, attacker: Pokemon, move: Move} | null { + const phase = pokemon.scene.getCurrentPhase(); + if (phase instanceof MoveEffectPhase) { + return { + phase : phase, + attacker : phase.getPokemon(), + move : phase.move.getMove() + }; + } + return null; +} diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 94b9fd12540..d320880c52a 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2290,6 +2290,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate); } + /** + * Compares if `this` and {@linkcode target} are on the same team. + * @param target the {@linkcode Pokemon} to compare against. + * @returns `true` if the two pokemon are allies, `false` otherwise + */ + public isOpponent(target: Pokemon): boolean { + return this.isPlayer() !== target.isPlayer(); + } + getOpponent(targetIndex: integer): Pokemon | null { const ret = this.getOpponents()[targetIndex]; if (ret.summonData) { diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index dc880f85e23..8d1a255d268 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -280,10 +280,8 @@ export class MoveEffectPhase extends PokemonPhase { if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); } - target.lapseTag(BattlerTagType.BEAK_BLAST_CHARGING); - if (move.category === MoveCategory.PHYSICAL && user.isPlayer() !== target.isPlayer()) { - target.lapseTag(BattlerTagType.SHELL_TRAP); - } + target.lapseTags(BattlerTagLapseType.AFTER_HIT); + })).then(() => { // Apply the user's post-attack ability effects applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => { From 181f59882a02fcab129c45672cc2d397aaff3049 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:37:13 -0700 Subject: [PATCH 11/24] [P2] Fix Early Bird (#4632) * Fix Early Bird, add tests * Update tsdocs for Early Bird's `AbAttr` Rename `turnCount` to `toxicTurnCount` and `turnsRemaining` to `sleepTurnsRemaining` in `status-effect.ts` * Fix Toxic Orb test * Redundant code :despair: * Fix status override to set the number of sleep turns --- src/data/ability.ts | 19 ++++- src/data/move.ts | 16 ++-- src/data/status-effect.ts | 24 +++--- src/field/pokemon.ts | 25 +++--- src/phases/move-phase.ts | 7 +- src/phases/obtain-status-effect-phase.ts | 14 ++-- src/phases/post-summon-phase.ts | 2 +- src/phases/post-turn-status-effect-phase.ts | 2 +- src/system/pokemon-data.ts | 2 +- src/test/abilities/early_bird.test.ts | 93 +++++++++++++++++++++ src/test/abilities/magic_guard.test.ts | 4 +- src/test/data/status-effect.test.ts | 66 ++++++++++++++- src/test/items/toxic_orb.test.ts | 23 ++--- src/test/moves/nightmare.test.ts | 10 +-- src/test/moves/will_o_wisp.test.ts | 53 ++++++++++++ 15 files changed, 292 insertions(+), 68 deletions(-) create mode 100644 src/test/abilities/early_bird.test.ts create mode 100644 src/test/moves/will_o_wisp.test.ts diff --git a/src/data/ability.ts b/src/data/ability.ts index 0d5cf2751ce..ebdd5105bb4 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -4200,6 +4200,11 @@ export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr { export class BlockRedirectAbAttr extends AbAttr { } +/** + * Used by Early Bird, makes the pokemon wake up faster + * @param statusEffect - The {@linkcode StatusEffect} to check for + * @see {@linkcode apply} + */ export class ReduceStatusEffectDurationAbAttr extends AbAttr { private statusEffect: StatusEffect; @@ -4209,9 +4214,19 @@ export class ReduceStatusEffectDurationAbAttr extends AbAttr { this.statusEffect = statusEffect; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + /** + * Reduces the number of sleep turns remaining by an extra 1 when applied + * @param args - The args passed to the `AbAttr`: + * - `[0]` - The {@linkcode StatusEffect} of the Pokemon + * - `[1]` - The number of turns remaining until the status is healed + * @returns `true` if the ability was applied + */ + apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (!(args[1] instanceof Utils.NumberHolder)) { + return false; + } if (args[0] === this.statusEffect) { - (args[1] as Utils.IntegerHolder).value = Utils.toDmgValue((args[1] as Utils.IntegerHolder).value / 2); + args[1].value -= 1; return true; } diff --git a/src/data/move.ts b/src/data/move.ts index 0d9c57bf094..ec25844909e 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2050,15 +2050,15 @@ export class WaterShurikenMultiHitTypeAttr extends ChangeMultiHitTypeAttr { export class StatusEffectAttr extends MoveEffectAttr { public effect: StatusEffect; - public cureTurn: integer | null; - public overrideStatus: boolean; + public turnsRemaining?: number; + public overrideStatus: boolean = false; - constructor(effect: StatusEffect, selfTarget?: boolean, cureTurn?: integer, overrideStatus?: boolean) { + constructor(effect: StatusEffect, selfTarget?: boolean, turnsRemaining?: number, overrideStatus: boolean = false) { super(selfTarget, MoveEffectTrigger.HIT); this.effect = effect; - this.cureTurn = cureTurn!; // TODO: is this bang correct? - this.overrideStatus = !!overrideStatus; + this.turnsRemaining = turnsRemaining; + this.overrideStatus = overrideStatus; } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { @@ -2085,7 +2085,7 @@ export class StatusEffectAttr extends MoveEffectAttr { return false; } if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0)) - && pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) { + && pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining)) { applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect); return true; } @@ -2102,8 +2102,8 @@ export class StatusEffectAttr extends MoveEffectAttr { export class MultiStatusEffectAttr extends StatusEffectAttr { public effects: StatusEffect[]; - constructor(effects: StatusEffect[], selfTarget?: boolean, cureTurn?: integer, overrideStatus?: boolean) { - super(effects[0], selfTarget, cureTurn, overrideStatus); + constructor(effects: StatusEffect[], selfTarget?: boolean, turnsRemaining?: number, overrideStatus?: boolean) { + super(effects[0], selfTarget, turnsRemaining, overrideStatus); this.effects = effects; } diff --git a/src/data/status-effect.ts b/src/data/status-effect.ts index 4319985f43a..56e754ac407 100644 --- a/src/data/status-effect.ts +++ b/src/data/status-effect.ts @@ -1,4 +1,4 @@ -import * as Utils from "../utils"; +import { randIntRange } from "#app/utils"; import { StatusEffect } from "#enums/status-effect"; import i18next, { ParseKeys } from "i18next"; @@ -6,17 +6,21 @@ export { StatusEffect }; export class Status { public effect: StatusEffect; - public turnCount: integer; - public cureTurn: integer | null; + /** Toxic damage is `1/16 max HP * toxicTurnCount` */ + public toxicTurnCount: number = 0; + public sleepTurnsRemaining?: number; - constructor(effect: StatusEffect, turnCount: integer = 0, cureTurn?: integer) { + constructor(effect: StatusEffect, toxicTurnCount: number = 0, sleepTurnsRemaining?: number) { this.effect = effect; - this.turnCount = turnCount === undefined ? 0 : turnCount; - this.cureTurn = cureTurn!; // TODO: is this bang correct? + this.toxicTurnCount = toxicTurnCount; + this.sleepTurnsRemaining = sleepTurnsRemaining; } incrementTurn(): void { - this.turnCount++; + this.toxicTurnCount++; + if (this.sleepTurnsRemaining) { + this.sleepTurnsRemaining--; + } } isPostTurn(): boolean { @@ -107,7 +111,7 @@ export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect): * Returns a random non-volatile StatusEffect */ export function generateRandomStatusEffect(): StatusEffect { - return Utils.randIntRange(1, 6); + return randIntRange(1, 6); } /** @@ -123,7 +127,7 @@ export function getRandomStatusEffect(statusEffectA: StatusEffect, statusEffectB return statusEffectA; } - return Utils.randIntRange(0, 2) ? statusEffectA : statusEffectB; + return randIntRange(0, 2) ? statusEffectA : statusEffectB; } /** @@ -140,7 +144,7 @@ export function getRandomStatus(statusA: Status | null, statusB: Status | null): } - return Utils.randIntRange(0, 2) ? statusA : statusB; + return randIntRange(0, 2) ? statusA : statusB; } /** diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index d320880c52a..a3d7429ed9b 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -22,7 +22,7 @@ import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/ import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags"; import { WeatherType } from "#app/data/weather"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; -import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr } from "#app/data/ability"; +import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr } from "#app/data/ability"; import PokemonData from "#app/system/pokemon-data"; import { BattlerIndex } from "#app/battle"; import { Mode } from "#app/ui/ui"; @@ -3430,7 +3430,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return true; } - trySetStatus(effect: StatusEffect | undefined, asPhase: boolean = false, sourcePokemon: Pokemon | null = null, cureTurn: integer | null = 0, sourceText: string | null = null): boolean { + trySetStatus(effect?: StatusEffect, asPhase: boolean = false, sourcePokemon: Pokemon | null = null, turnsRemaining: number = 0, sourceText: string | null = null): boolean { if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) { return false; } @@ -3444,15 +3444,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (asPhase) { - this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText, sourcePokemon)); + this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, turnsRemaining, sourceText, sourcePokemon)); return true; } - let statusCureTurn: Utils.IntegerHolder; + let sleepTurnsRemaining: Utils.NumberHolder; if (effect === StatusEffect.SLEEP) { - statusCureTurn = new Utils.IntegerHolder(this.randSeedIntRange(2, 4)); - applyAbAttrs(ReduceStatusEffectDurationAbAttr, this, null, false, effect, statusCureTurn); + sleepTurnsRemaining = new Utils.NumberHolder(this.randSeedIntRange(2, 4)); this.setFrameRate(4); @@ -3472,9 +3471,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - statusCureTurn = statusCureTurn!; // tell TS compiler it's defined + sleepTurnsRemaining = sleepTurnsRemaining!; // tell TS compiler it's defined effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call - this.status = new Status(effect, 0, statusCureTurn?.value); + this.status = new Status(effect, 0, sleepTurnsRemaining?.value); if (effect !== StatusEffect.FAINT) { this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true); @@ -4001,7 +4000,7 @@ export class PlayerPokemon extends Pokemon { super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); if (Overrides.STATUS_OVERRIDE) { - this.status = new Status(Overrides.STATUS_OVERRIDE); + this.status = new Status(Overrides.STATUS_OVERRIDE, 0, 4); } if (Overrides.SHINY_OVERRIDE) { @@ -4481,7 +4480,7 @@ export class EnemyPokemon extends Pokemon { } if (Overrides.OPP_STATUS_OVERRIDE) { - this.status = new Status(Overrides.OPP_STATUS_OVERRIDE); + this.status = new Status(Overrides.OPP_STATUS_OVERRIDE, 0, 4); } if (Overrides.OPP_GENDER_OVERRIDE) { @@ -4490,9 +4489,11 @@ export class EnemyPokemon extends Pokemon { const speciesId = this.species.speciesId; - if (speciesId in Overrides.OPP_FORM_OVERRIDES + if ( + speciesId in Overrides.OPP_FORM_OVERRIDES && Overrides.OPP_FORM_OVERRIDES[speciesId] - && this.species.forms[Overrides.OPP_FORM_OVERRIDES[speciesId]]) { + && this.species.forms[Overrides.OPP_FORM_OVERRIDES[speciesId]] + ) { this.formIndex = Overrides.OPP_FORM_OVERRIDES[speciesId] ?? 0; } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 0af61918636..e9d8887e9cb 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; import BattleScene from "#app/battle-scene"; -import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr } from "#app/data/ability"; +import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr, ReduceStatusEffectDurationAbAttr } from "#app/data/ability"; import { CommonAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags"; import { allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, ChargeAttr, CopyMoveAttr, HealStatusEffectAttr, MoveFlags, PreMoveMessageAttr } from "#app/data/move"; @@ -175,7 +175,10 @@ export class MovePhase extends BattlePhase { break; case StatusEffect.SLEEP: applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); - healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn; + const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0); + applyAbAttrs(ReduceStatusEffectDurationAbAttr, this.pokemon, null, false, this.pokemon.status.effect, turnsRemaining); + this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value; + healed = this.pokemon.status.sleepTurnsRemaining <= 0; activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); this.cancelled = activated; break; diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts index c396fa7ba59..01384b932cb 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -8,26 +8,26 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonPhase } from "./pokemon-phase"; export class ObtainStatusEffectPhase extends PokemonPhase { - private statusEffect?: StatusEffect | undefined; - private cureTurn?: integer | null; + private statusEffect?: StatusEffect; + private turnsRemaining?: number; private sourceText?: string | null; private sourcePokemon?: Pokemon | null; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, cureTurn?: integer | null, sourceText?: string | null, sourcePokemon?: Pokemon | null) { + constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, turnsRemaining?: number, sourceText?: string | null, sourcePokemon?: Pokemon | null) { super(scene, battlerIndex); this.statusEffect = statusEffect; - this.cureTurn = cureTurn; + this.turnsRemaining = turnsRemaining; this.sourceText = sourceText; - this.sourcePokemon = sourcePokemon; // For tracking which Pokemon caused the status effect + this.sourcePokemon = sourcePokemon; } start() { const pokemon = this.getPokemon(); if (pokemon && !pokemon.status) { if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) { - if (this.cureTurn) { - pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct? + if (this.turnsRemaining) { + pokemon.status!.sleepTurnsRemaining = this.turnsRemaining; } pokemon.updateInfo(true); new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => { diff --git a/src/phases/post-summon-phase.ts b/src/phases/post-summon-phase.ts index 617bb8b1cfe..3db98d9926c 100644 --- a/src/phases/post-summon-phase.ts +++ b/src/phases/post-summon-phase.ts @@ -18,7 +18,7 @@ export class PostSummonPhase extends PokemonPhase { const pokemon = this.getPokemon(); if (pokemon.status?.effect === StatusEffect.TOXIC) { - pokemon.status.turnCount = 0; + pokemon.status.toxicTurnCount = 0; } this.scene.arena.applyTags(ArenaTrapTag, false, pokemon); diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 06681b733f0..2efd992a2b5 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -30,7 +30,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); break; case StatusEffect.TOXIC: - damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); + damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.toxicTurnCount), 1); break; case StatusEffect.BURN: damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index cddc5798872..e681c995b26 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -137,7 +137,7 @@ export default class PokemonData { this.moveset = (source.moveset || [ new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.GROWL) ]).filter(m => m).map((m: any) => new PokemonMove(m.moveId, m.ppUsed, m.ppUp)); if (!forHistory) { this.status = source.status - ? new Status(source.status.effect, source.status.turnCount, source.status.cureTurn) + ? new Status(source.status.effect, source.status.toxicTurnCount, source.status.sleepTurnsRemaining) : null; } diff --git a/src/test/abilities/early_bird.test.ts b/src/test/abilities/early_bird.test.ts new file mode 100644 index 00000000000..a69290fa1e4 --- /dev/null +++ b/src/test/abilities/early_bird.test.ts @@ -0,0 +1,93 @@ +import { Status } from "#app/data/status-effect"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Early Bird", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.REST, Moves.BELLY_DRUM, Moves.SPLASH ]) + .ability(Abilities.EARLY_BIRD) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("reduces Rest's sleep time to 1 turn", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const player = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.BELLY_DRUM); + await game.toNextTurn(); + game.move.select(Moves.REST); + await game.toNextTurn(); + + expect(player.status?.effect).toBe(StatusEffect.SLEEP); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status?.effect).toBe(StatusEffect.SLEEP); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status?.effect).toBeUndefined(); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + }); + + it("reduces 3-turn sleep to 1 turn", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const player = game.scene.getPlayerPokemon()!; + player.status = new Status(StatusEffect.SLEEP, 0, 4); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status?.effect).toBe(StatusEffect.SLEEP); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status?.effect).toBeUndefined(); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + }); + + it("reduces 1-turn sleep to 0 turns", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const player = game.scene.getPlayerPokemon()!; + player.status = new Status(StatusEffect.SLEEP, 0, 2); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status?.effect).toBeUndefined(); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + }); +}); diff --git a/src/test/abilities/magic_guard.test.ts b/src/test/abilities/magic_guard.test.ts index 614f983e76e..8075eac66f2 100644 --- a/src/test/abilities/magic_guard.test.ts +++ b/src/test/abilities/magic_guard.test.ts @@ -150,7 +150,7 @@ describe("Abilities - Magic Guard", () => { const enemyPokemon = game.scene.getEnemyPokemon()!; - const toxicStartCounter = enemyPokemon.status!.turnCount; + const toxicStartCounter = enemyPokemon.status!.toxicTurnCount; //should be 0 await game.phaseInterceptor.to(TurnEndPhase); @@ -162,7 +162,7 @@ describe("Abilities - Magic Guard", () => { * - The enemy Pokemon's hypothetical CatchRateMultiplier should be 1.5 */ expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); - expect(enemyPokemon.status!.turnCount).toBeGreaterThan(toxicStartCounter); + expect(enemyPokemon.status!.toxicTurnCount).toBeGreaterThan(toxicStartCounter); expect(getStatusEffectCatchRateMultiplier(enemyPokemon.status!.effect)).toBe(1.5); } ); diff --git a/src/test/data/status-effect.test.ts b/src/test/data/status-effect.test.ts index bca3bd21c70..8b37da45d8d 100644 --- a/src/test/data/status-effect.test.ts +++ b/src/test/data/status-effect.test.ts @@ -1,4 +1,5 @@ import { + Status, StatusEffect, getStatusEffectActivationText, getStatusEffectDescriptor, @@ -6,14 +7,19 @@ import { getStatusEffectObtainText, getStatusEffectOverlapText, } from "#app/data/status-effect"; +import { MoveResult } from "#app/field/pokemon"; +import GameManager from "#app/test/utils/gameManager"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; import { mockI18next } from "#test/utils/testUtils"; import i18next from "i18next"; -import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const pokemonName = "PKM"; const sourceText = "SOURCE"; -describe("status-effect", () => { +describe("Status Effect Messages", () => { beforeAll(() => { i18next.init(); }); @@ -299,3 +305,59 @@ describe("status-effect", () => { vi.resetAllMocks(); }); }); + +describe("Status Effects - Sleep", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should last the appropriate number of turns", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const player = game.scene.getPlayerPokemon()!; + player.status = new Status(StatusEffect.SLEEP, 0, 4); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status.effect).toBe(StatusEffect.SLEEP); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status.effect).toBe(StatusEffect.SLEEP); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status.effect).toBe(StatusEffect.SLEEP); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status?.effect).toBeUndefined(); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + }); +}); diff --git a/src/test/items/toxic_orb.test.ts b/src/test/items/toxic_orb.test.ts index 63c7b6245f5..583e302126c 100644 --- a/src/test/items/toxic_orb.test.ts +++ b/src/test/items/toxic_orb.test.ts @@ -7,8 +7,6 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const TIMEOUT = 20 * 1000; - describe("Items - Toxic orb", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -27,10 +25,10 @@ describe("Items - Toxic orb", () => { game = new GameManager(phaserGame); game.override .battleType("single") - .enemySpecies(Species.RATTATA) + .enemySpecies(Species.MAGIKARP) .ability(Abilities.BALL_FETCH) .enemyAbility(Abilities.BALL_FETCH) - .moveset([ Moves.SPLASH ]) + .moveset(Moves.SPLASH) .enemyMoveset(Moves.SPLASH) .startingHeldItems([{ name: "TOXIC_ORB", @@ -39,22 +37,19 @@ describe("Items - Toxic orb", () => { vi.spyOn(i18next, "t"); }); - it("badly poisons the holder", async () => { - await game.classicMode.startBattle([ Species.MIGHTYENA ]); + it("should badly poison the holder", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); - const player = game.scene.getPlayerField()[0]; + const player = game.scene.getPlayerPokemon()!; + expect(player.getHeldItems()[0].type.id).toBe("TOXIC_ORB"); game.move.select(Moves.SPLASH); await game.phaseInterceptor.to("TurnEndPhase"); - // Toxic orb should trigger here - await game.phaseInterceptor.run("MessagePhase"); + await game.phaseInterceptor.to("MessagePhase"); expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.obtainSource", expect.anything()); - await game.toNextTurn(); - expect(player.status?.effect).toBe(StatusEffect.TOXIC); - // Damage should not have ticked yet. - expect(player.status?.turnCount).toBe(0); - }, TIMEOUT); + expect(player.status?.toxicTurnCount).toBe(0); + }); }); diff --git a/src/test/moves/nightmare.test.ts b/src/test/moves/nightmare.test.ts index 61b133a3280..f4c485ff1b4 100644 --- a/src/test/moves/nightmare.test.ts +++ b/src/test/moves/nightmare.test.ts @@ -1,12 +1,10 @@ -import { CommandPhase } from "#app/phases/command-phase"; -import { TurnInitPhase } from "#app/phases/turn-init-phase"; +import { StatusEffect } from "#app/data/status-effect"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { StatusEffect } from "#app/data/status-effect"; describe("Moves - Nightmare", () => { let phaserGame: Phaser.Game; @@ -39,16 +37,16 @@ describe("Moves - Nightmare", () => { const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyMaxHP = enemyPokemon.hp; + game.move.select(Moves.NIGHTMARE); - await game.phaseInterceptor.to(TurnInitPhase); + await game.toNextTurn(); expect(enemyPokemon.hp).toBe(enemyMaxHP - Math.floor(enemyMaxHP / 4)); // take a second turn to make sure damage occurs again - await game.phaseInterceptor.to(CommandPhase); game.move.select(Moves.SPLASH); + await game.toNextTurn(); - await game.phaseInterceptor.to(TurnInitPhase); expect(enemyPokemon.hp).toBe(enemyMaxHP - Math.floor(enemyMaxHP / 4) - Math.floor(enemyMaxHP / 4)); }); }); diff --git a/src/test/moves/will_o_wisp.test.ts b/src/test/moves/will_o_wisp.test.ts new file mode 100644 index 00000000000..39729d331ad --- /dev/null +++ b/src/test/moves/will_o_wisp.test.ts @@ -0,0 +1,53 @@ +import { BattlerIndex } from "#app/battle"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Will-O-Wisp", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.WILL_O_WISP, Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should burn the opponent", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const enemy = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.WILL_O_WISP); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.move.forceHit(); + await game.toNextTurn(); + + expect(enemy.status?.effect).toBe(StatusEffect.BURN); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(enemy.status?.effect).toBe(StatusEffect.BURN); + }); +}); From 5e7f2042fcba5ea38de0f6d7298499d93019a9fc Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:05:37 -0700 Subject: [PATCH 12/24] [UI][QoL] Cursor defaults to Fight at the start of each new wave (#4666) * cursors are dumb * update * fixed? * maybe solution * fix in! * Possible cursor fixes --------- Co-authored-by: frutescens Co-authored-by: Opaque02 <66582645+Opaque02@users.noreply.github.com> --- src/phases/command-phase.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index e6f2eb69ff3..6d4d46c51c9 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -30,6 +30,15 @@ export class CommandPhase extends FieldPhase { start() { super.start(); + const commandUiHandler = this.scene.ui.handlers[Mode.COMMAND]; + if (commandUiHandler) { + if (this.scene.currentBattle.turn === 1 || commandUiHandler.getCursor() === Command.POKEMON) { + commandUiHandler.setCursor(Command.FIGHT); + } else { + commandUiHandler.setCursor(commandUiHandler.getCursor()); + } + } + 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 From 96e5f2d763d3bf225ae685675f54b1fb8aa5ae37 Mon Sep 17 00:00:00 2001 From: damocleas Date: Tue, 22 Oct 2024 21:07:28 -0400 Subject: [PATCH 13/24] Starmobile Stat Adjustments (#4704) Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> --- src/data/pokemon-species.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 947ac939989..96d1eb430fb 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -2580,11 +2580,11 @@ export function initSpecies() { new PokemonSpecies(Species.VAROOM, 9, false, false, false, "Single-Cyl Pokémon", Type.STEEL, Type.POISON, 1, 35, Abilities.OVERCOAT, Abilities.NONE, Abilities.SLOW_START, 300, 45, 70, 63, 30, 45, 47, 190, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.REVAVROOM, 9, false, false, false, "Multi-Cyl Pokémon", Type.STEEL, Type.POISON, 1.8, 120, Abilities.OVERCOAT, Abilities.NONE, Abilities.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, GrowthRate.MEDIUM_FAST, 50, false, false, new PokemonForm("Normal", "", Type.STEEL, Type.POISON, 1.8, 120, Abilities.OVERCOAT, Abilities.NONE, Abilities.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, false, null, true), - new PokemonForm("Segin Starmobile", "segin-starmobile", Type.STEEL, Type.DARK, 1.8, 240, Abilities.INTIMIDATE, Abilities.NONE, Abilities.INTIMIDATE, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), - new PokemonForm("Schedar Starmobile", "schedar-starmobile", Type.STEEL, Type.FIRE, 1.8, 240, Abilities.SPEED_BOOST, Abilities.NONE, Abilities.SPEED_BOOST, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), - new PokemonForm("Navi Starmobile", "navi-starmobile", Type.STEEL, Type.POISON, 1.8, 240, Abilities.TOXIC_DEBRIS, Abilities.NONE, Abilities.TOXIC_DEBRIS, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), - new PokemonForm("Ruchbah Starmobile", "ruchbah-starmobile", Type.STEEL, Type.FAIRY, 1.8, 240, Abilities.MISTY_SURGE, Abilities.NONE, Abilities.MISTY_SURGE, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), - new PokemonForm("Caph Starmobile", "caph-starmobile", Type.STEEL, Type.FIGHTING, 1.8, 240, Abilities.STAMINA, Abilities.NONE, Abilities.STAMINA, 600, 120, 129, 100, 59, 77, 115, 75, 50, 175), + new PokemonForm("Segin Starmobile", "segin-starmobile", Type.STEEL, Type.DARK, 1.8, 240, Abilities.INTIMIDATE, Abilities.NONE, Abilities.INTIMIDATE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175), + new PokemonForm("Schedar Starmobile", "schedar-starmobile", Type.STEEL, Type.FIRE, 1.8, 240, Abilities.SPEED_BOOST, Abilities.NONE, Abilities.SPEED_BOOST, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175), + new PokemonForm("Navi Starmobile", "navi-starmobile", Type.STEEL, Type.POISON, 1.8, 240, Abilities.TOXIC_DEBRIS, Abilities.NONE, Abilities.TOXIC_DEBRIS, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175), + new PokemonForm("Ruchbah Starmobile", "ruchbah-starmobile", Type.STEEL, Type.FAIRY, 1.8, 240, Abilities.MISTY_SURGE, Abilities.NONE, Abilities.MISTY_SURGE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175), + new PokemonForm("Caph Starmobile", "caph-starmobile", Type.STEEL, Type.FIGHTING, 1.8, 240, Abilities.STAMINA, Abilities.NONE, Abilities.STAMINA, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175), ), new PokemonSpecies(Species.CYCLIZAR, 9, false, false, false, "Mount Pokémon", Type.DRAGON, Type.NORMAL, 1.6, 63, Abilities.SHED_SKIN, Abilities.NONE, Abilities.REGENERATOR, 501, 70, 95, 65, 85, 65, 121, 190, 50, 175, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.ORTHWORM, 9, false, false, false, "Earthworm Pokémon", Type.STEEL, null, 2.5, 310, Abilities.EARTH_EATER, Abilities.NONE, Abilities.SAND_VEIL, 480, 70, 85, 145, 60, 55, 65, 25, 50, 240, GrowthRate.SLOW, 50, false), From 0fe57b44b5ca2fcc934759147a29b46e07b0f76b Mon Sep 17 00:00:00 2001 From: Blitzy <118096277+Blitz425@users.noreply.github.com> Date: Tue, 22 Oct 2024 20:13:10 -0500 Subject: [PATCH 14/24] [Balance] Add Exclusive Moves from Prior Evolutions via Memory Mushroom (#4681) --- src/data/balance/pokemon-level-moves.ts | 479 +++++++++++++++++++----- 1 file changed, 389 insertions(+), 90 deletions(-) diff --git a/src/data/balance/pokemon-level-moves.ts b/src/data/balance/pokemon-level-moves.ts index b5608093df2..53f547c4504 100644 --- a/src/data/balance/pokemon-level-moves.ts +++ b/src/data/balance/pokemon-level-moves.ts @@ -93,6 +93,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.GROWL ], [ 1, Moves.EMBER ], [ 1, Moves.SMOKESCREEN ], + [ 1, Moves.FIRE_SPIN ], // Previous Stage Move [ 12, Moves.DRAGON_BREATH ], [ 19, Moves.FIRE_FANG ], [ 24, Moves.SLASH ], @@ -174,6 +175,9 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.METAPOD]: [ [ EVOLVE_MOVE, Moves.HARDEN ], + [ RELEARN_MOVE, Moves.TACKLE ], // Previous Stage Move + [ RELEARN_MOVE, Moves.STRING_SHOT ], // Previous Stage Move + [ RELEARN_MOVE, Moves.BUG_BITE ], // Previous Stage Move [ 1, Moves.HARDEN ], ], [Species.BUTTERFREE]: [ @@ -203,10 +207,17 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.KAKUNA]: [ [ EVOLVE_MOVE, Moves.HARDEN ], + [ RELEARN_MOVE, Moves.POISON_STING ], // Previous Stage Move + [ RELEARN_MOVE, Moves.STRING_SHOT ], // Previous Stage Move + [ RELEARN_MOVE, Moves.BUG_BITE ], // Previous Stage Move [ 1, Moves.HARDEN ], ], [Species.BEEDRILL]: [ [ EVOLVE_MOVE, Moves.TWINEEDLE ], + [ 1, Moves.POISON_STING ], // Previous Stage Move + [ 1, Moves.STRING_SHOT ], // Previous Stage Move + [ 1, Moves.HARDEN ], // Previous Stage Move + [ 1, Moves.BUG_BITE ], // Previous Stage Move [ 1, Moves.FURY_ATTACK ], [ 11, Moves.FURY_CUTTER ], [ 14, Moves.RAGE ], @@ -454,6 +465,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.POISON_STING ], [ 1, Moves.DEFENSE_CURL ], [ 1, Moves.CRUSH_CLAW ], + [ 1, Moves.AGILITY ], // Previous Stage Move [ 9, Moves.ROLLOUT ], [ 12, Moves.FURY_CUTTER ], [ 15, Moves.RAPID_SPIN ], @@ -961,6 +973,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.SCRATCH ], [ 1, Moves.LEER ], [ 1, Moves.FOCUS_ENERGY ], + [ 1, Moves.COVET ], // Previous Stage Move [ 1, Moves.FLING ], [ 5, Moves.FURY_SWIPES ], [ 8, Moves.LOW_KICK ], @@ -1044,10 +1057,6 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.POLIWRATH]: [ [ EVOLVE_MOVE, Moves.DYNAMIC_PUNCH ], - [ 1, Moves.BUBBLE_BEAM ], - [ 1, Moves.BODY_SLAM ], - [ 1, Moves.HYPNOSIS ], - [ 1, Moves.WATER_SPORT ], [ RELEARN_MOVE, Moves.POUND ], [ RELEARN_MOVE, Moves.DOUBLE_EDGE ], [ RELEARN_MOVE, Moves.WATER_GUN ], @@ -1057,13 +1066,18 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ RELEARN_MOVE, Moves.MUD_SHOT ], [ RELEARN_MOVE, Moves.EARTH_POWER ], [ RELEARN_MOVE, Moves.CIRCLE_THROW ], + [ 1, Moves.BUBBLE_BEAM ], + [ 1, Moves.BODY_SLAM ], + [ 1, Moves.HYPNOSIS ], + [ 1, Moves.WATER_SPORT ], ], [Species.ABRA]: [ [ 1, Moves.TELEPORT ], - [ 1, Moves.CONFUSION ], //Custom + [ 1, Moves.CONFUSION ], // Custom ], [Species.KADABRA]: [ - [ EVOLVE_MOVE, Moves.PSYBEAM ], //LGPE + [ EVOLVE_MOVE, Moves.PSYBEAM ], // LGPE + [ 1, Moves.CONFUSION ], // Previous Stage Move, Custom [ 1, Moves.DISABLE ], [ 1, Moves.TELEPORT ], [ 1, Moves.KINESIS ], @@ -1184,10 +1198,18 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ RELEARN_MOVE, Moves.STOCKPILE ], [ RELEARN_MOVE, Moves.SWALLOW ], [ RELEARN_MOVE, Moves.SPIT_UP ], + [ RELEARN_MOVE, Moves.WRAP ], // Previous Stage Move + [ RELEARN_MOVE, Moves.GROWTH ], // Previous Stage Move + [ RELEARN_MOVE, Moves.ACID ], // Previous Stage Move + [ RELEARN_MOVE, Moves.KNOCK_OFF ], // Previous Stage Move [ RELEARN_MOVE, Moves.GASTRO_ACID ], + [ RELEARN_MOVE, Moves.POISON_JAB ], // Previous Stage Move + [ RELEARN_MOVE, Moves.SLAM ], // Previous Stage Move [ RELEARN_MOVE, Moves.POWER_WHIP ], [ 1, Moves.VINE_WHIP ], [ 1, Moves.SLEEP_POWDER ], + [ 1, Moves.POISON_POWDER ], // Previous Stage Move + [ 1, Moves.STUN_SPORE ], // Previous Stage Move [ 1, Moves.SWEET_SCENT ], [ 1, Moves.RAZOR_LEAF ], [ 44, Moves.LEAF_BLADE ], @@ -1262,6 +1284,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.TACKLE ], [ 1, Moves.DEFENSE_CURL ], [ 1, Moves.ROCK_POLISH ], + [ 1, Moves.ROLLOUT ], // Previous Stage Move [ 1, Moves.HEAVY_SLAM ], [ 16, Moves.ROCK_THROW ], [ 18, Moves.SMACK_DOWN ], @@ -1548,7 +1571,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.GASTLY]: [ [ 1, Moves.CONFUSE_RAY ], [ 1, Moves.LICK ], - [ 1, Moves.ACID ], //Custom + [ 1, Moves.ACID ], // Custom [ 4, Moves.HYPNOSIS ], [ 8, Moves.MEAN_LOOK ], [ 12, Moves.PAYBACK ], @@ -1567,6 +1590,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.HYPNOSIS ], [ 1, Moves.CONFUSE_RAY ], [ 1, Moves.LICK ], + [ 1, Moves.ACID ], // Previous Stage Move, Custom [ 1, Moves.MEAN_LOOK ], [ 12, Moves.PAYBACK ], [ 16, Moves.SPITE ], @@ -1583,6 +1607,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.HYPNOSIS ], [ 1, Moves.CONFUSE_RAY ], [ 1, Moves.LICK ], + [ 1, Moves.ACID ], // Previous Stage Move, Custom [ 1, Moves.PERISH_SONG ], [ 1, Moves.MEAN_LOOK ], [ 1, Moves.SHADOW_PUNCH ], @@ -1609,7 +1634,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 12, Moves.DRAGON_BREATH ], [ 16, Moves.CURSE ], [ 20, Moves.ROCK_SLIDE ], - [ 22, Moves.GYRO_BALL ], //Custom, from USUM + [ 22, Moves.GYRO_BALL ], // Custom, from USUM [ 24, Moves.SCREECH ], [ 28, Moves.SAND_TOMB ], [ 32, Moves.STEALTH_ROCK ], @@ -1849,7 +1874,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.LICKITUNG]: [ [ 1, Moves.DEFENSE_CURL ], [ 1, Moves.LICK ], - [ 1, Moves.TACKLE ], //Custom + [ 1, Moves.TACKLE ], // Custom [ 6, Moves.REST ], [ 12, Moves.SUPERSONIC ], [ 18, Moves.WRAP ], @@ -2090,6 +2115,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.MR_MIME]: [ [ 1, Moves.POUND ], + [ 1, Moves.TICKLE ], // Previous Stage Move [ 1, Moves.BATON_PASS ], [ 1, Moves.ENCORE ], [ 1, Moves.COPYCAT ], @@ -2122,7 +2148,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 20, Moves.DOUBLE_HIT ], [ 24, Moves.SLASH ], [ 28, Moves.FOCUS_ENERGY ], - [ 30, Moves.STEEL_WING ], //Custom + [ 30, Moves.STEEL_WING ], // Custom [ 32, Moves.AGILITY ], [ 36, Moves.AIR_SLASH ], [ 40, Moves.X_SCISSOR ], @@ -2279,6 +2305,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.VAPOREON]: [ [ EVOLVE_MOVE, Moves.BOUNCY_BUBBLE ], + [ RELEARN_MOVE, Moves.VEEVEE_VOLLEY ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.TAKE_DOWN ], [ 1, Moves.DOUBLE_EDGE ], @@ -2306,6 +2333,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.JOLTEON]: [ [ EVOLVE_MOVE, Moves.BUZZY_BUZZ ], + [ RELEARN_MOVE, Moves.VEEVEE_VOLLEY ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.TAKE_DOWN ], [ 1, Moves.DOUBLE_EDGE ], @@ -2333,6 +2361,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.FLAREON]: [ [ EVOLVE_MOVE, Moves.SIZZLY_SLIDE ], + [ RELEARN_MOVE, Moves.VEEVEE_VOLLEY ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.TAKE_DOWN ], [ 1, Moves.DOUBLE_EDGE ], @@ -2463,6 +2492,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.SNORLAX]: [ [ 1, Moves.TACKLE ], [ 1, Moves.SCREECH ], + [ 1, Moves.ODOR_SLEUTH ], // Previous Stage Move [ 1, Moves.DEFENSE_CURL ], [ 1, Moves.METRONOME ], [ 1, Moves.LICK ], @@ -2632,7 +2662,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.CHIKORITA]: [ [ 1, Moves.TACKLE ], [ 1, Moves.GROWL ], - [ 5, Moves.RAZOR_LEAF ], //Custom, moved from 6 to 5 + [ 5, Moves.RAZOR_LEAF ], // Custom, moved from 6 to 5 [ 9, Moves.POISON_POWDER ], [ 12, Moves.SYNTHESIS ], [ 17, Moves.REFLECT ], @@ -2682,8 +2712,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.CYNDAQUIL]: [ [ 1, Moves.TACKLE ], [ 1, Moves.LEER ], - [ 5, Moves.EMBER ], //Custom, moved from 10 to 5 - [ 10, Moves.SMOKESCREEN ], //Custom, moved from 6 to 10 + [ 5, Moves.EMBER ], // Custom, moved from 10 to 5 + [ 10, Moves.SMOKESCREEN ], // Custom, moved from 6 to 10 [ 13, Moves.QUICK_ATTACK ], [ 19, Moves.FLAME_WHEEL ], [ 22, Moves.DEFENSE_CURL ], @@ -2737,7 +2767,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.TOTODILE]: [ [ 1, Moves.SCRATCH ], [ 1, Moves.LEER ], - [ 5, Moves.WATER_GUN ], //Custom, moved from 6 to 5 + [ 5, Moves.WATER_GUN ], // Custom, moved from 6 to 5 [ 9, Moves.BITE ], [ 13, Moves.SCARY_FACE ], [ 19, Moves.ICE_FANG ], @@ -3149,6 +3179,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.MOONBLAST ], ], [Species.MARILL]: [ + [ 1, Moves.SPLASH ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.TAIL_WHIP ], [ 1, Moves.WATER_GUN ], @@ -3168,6 +3199,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 36, Moves.SUPERPOWER ], ], [Species.AZUMARILL]: [ + [ 1, Moves.SPLASH ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.TAIL_WHIP ], [ 1, Moves.WATER_GUN ], @@ -3189,6 +3221,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.SUDOWOODO]: [ [ EVOLVE_MOVE, Moves.SLAM ], [ 1, Moves.ROCK_THROW ], + [ 1, Moves.TACKLE ], // Previous Stage Move, Custom [ 1, Moves.FLAIL ], [ 1, Moves.FAKE_TEARS ], [ 1, Moves.HAMMER_ARM ], @@ -3222,6 +3255,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.HYDRO_PUMP ], [ 1, Moves.BELLY_DRUM ], [ 1, Moves.POUND ], + [ 1, Moves.WATER_SPORT ], // Previous Stage Move ], [Species.HOPPIP]: [ [ 1, Moves.TACKLE ], @@ -3315,9 +3349,12 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 39, Moves.SEED_BOMB ], ], [Species.SUNFLORA]: [ + [ RELEARN_MOVE, Moves.SEED_BOMB ], // Previous Stage Move [ 1, Moves.POUND ], [ 1, Moves.TACKLE ], [ 1, Moves.GROWTH ], + [ 1, Moves.ENDEAVOR ], // Previous Stage Move + [ 1, Moves.SYNTHESIS ], // Previous Stage Move [ 4, Moves.INGRAIN ], [ 7, Moves.ABSORB ], [ 10, Moves.MEGA_DRAIN ], @@ -3382,6 +3419,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.ESPEON]: [ [ EVOLVE_MOVE, Moves.GLITZY_GLOW ], + [ RELEARN_MOVE, Moves.VEEVEE_VOLLEY ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.TAKE_DOWN ], [ 1, Moves.DOUBLE_EDGE ], @@ -3408,6 +3446,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.UMBREON]: [ [ EVOLVE_MOVE, Moves.BADDY_BAD ], + [ RELEARN_MOVE, Moves.VEEVEE_VOLLEY ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.TAKE_DOWN ], [ 1, Moves.DOUBLE_EDGE ], @@ -3464,6 +3503,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 15, Moves.DISABLE ], [ 18, Moves.WATER_PULSE ], [ 21, Moves.HEADBUTT ], + [ 24, Moves.ZEN_HEADBUTT ], // Previous Stage Move, Galar Slowking Level [ 27, Moves.AMNESIA ], [ 30, Moves.SURF ], [ 33, Moves.SLACK_OFF ], @@ -3562,7 +3602,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.DUNSPARCE]: [ [ 1, Moves.DEFENSE_CURL ], [ 1, Moves.FLAIL ], - [ 1, Moves.TACKLE ], //Custom + [ 1, Moves.TACKLE ], // Custom [ 4, Moves.MUD_SLAP ], [ 8, Moves.ROLLOUT ], [ 12, Moves.GLARE ], @@ -3609,6 +3649,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 12, Moves.DRAGON_BREATH ], [ 16, Moves.CURSE ], [ 20, Moves.ROCK_SLIDE ], + [ 22, Moves.GYRO_BALL ], // Custom from USUM [ 24, Moves.SCREECH ], [ 28, Moves.SAND_TOMB ], [ 32, Moves.STEALTH_ROCK ], @@ -3688,6 +3729,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 20, Moves.DOUBLE_HIT ], [ 24, Moves.SLASH ], [ 28, Moves.FOCUS_ENERGY ], + [ 30, Moves.STEEL_WING ], // Custom [ 32, Moves.IRON_DEFENSE ], [ 36, Moves.IRON_HEAD ], [ 40, Moves.X_SCISSOR ], @@ -3765,8 +3807,11 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.SCRATCH ], [ 1, Moves.LEER ], [ 1, Moves.LICK ], - [ 1, Moves.FAKE_TEARS ], [ 1, Moves.COVET ], + [ 1, Moves.FLING ], // Previous Stage Move + [ 1, Moves.BABY_DOLL_EYES ], // Previous Stage Move + [ 1, Moves.FAKE_TEARS ], + [ 1, Moves.CHARM ], // Previous Stage Move [ 8, Moves.FURY_SWIPES ], [ 13, Moves.PAYBACK ], [ 17, Moves.SWEET_SCENT ], @@ -3783,7 +3828,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.SLUGMA]: [ [ 1, Moves.SMOG ], [ 1, Moves.YAWN ], - [ 5, Moves.EMBER ], //Custom, Moved from Level 6 to 5 + [ 5, Moves.EMBER ], // Custom, Moved from Level 6 to 5 [ 8, Moves.ROCK_THROW ], [ 13, Moves.HARDEN ], [ 20, Moves.CLEAR_SMOG ], @@ -3898,7 +3943,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 48, Moves.SOAK ], [ 54, Moves.HYPER_BEAM ], ], - [Species.DELIBIRD]: [ //Given a custom level up learnset + [Species.DELIBIRD]: [ // Given a custom level up learnset [ 1, Moves.PRESENT ], [ 1, Moves.METRONOME ], [ 5, Moves.FAKE_OUT ], @@ -3991,6 +4036,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 62, Moves.INFERNO ], ], [Species.KINGDRA]: [ + [ RELEARN_MOVE, Moves.LASER_FOCUS ], // Previous Stage Move [ 1, Moves.LEER ], [ 1, Moves.WATER_GUN ], [ 1, Moves.SMOKESCREEN ], @@ -4025,9 +4071,17 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.DONPHAN]: [ [ EVOLVE_MOVE, Moves.FURY_ATTACK ], - [ 1, Moves.HORN_ATTACK ], + [ 1, Moves.TACKLE ], // Previous Stage Move [ 1, Moves.GROWL ], + [ 1, Moves.HORN_ATTACK ], [ 1, Moves.DEFENSE_CURL ], + [ 1, Moves.ODOR_SLEUTH ], // Previous Stage Move + [ 1, Moves.FLAIL ], // Previous Stage Move + [ 1, Moves.ENDURE ], // Previous Stage Move + [ 1, Moves.TAKE_DOWN ], // Previous Stage Move + [ 1, Moves.CHARM ], // Previous Stage Move + [ 1, Moves.LAST_RESORT ], // Previous Stage Move + [ 1, Moves.DOUBLE_EDGE ], // Previous Stage Move [ 1, Moves.THUNDER_FANG ], [ 1, Moves.FIRE_FANG ], [ 1, Moves.BULLDOZE ], @@ -4047,6 +4101,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.CONVERSION ], [ 1, Moves.RECYCLE ], [ 1, Moves.MAGNET_RISE ], + [ 1, Moves.MAGIC_COAT ], // Previous Stage Move [ 15, Moves.THUNDER_SHOCK ], [ 20, Moves.PSYBEAM ], [ 25, Moves.CONVERSION_2 ], @@ -4513,6 +4568,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.MARSHTOMP]: [ [ EVOLVE_MOVE, Moves.MUD_SHOT ], + [ RELEARN_MOVE, Moves.SURF ], // Previous Stage Move [ RELEARN_MOVE, Moves.ROCK_SMASH ], [ 1, Moves.TACKLE ], [ 1, Moves.GROWL ], @@ -4634,10 +4690,15 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.SILCOON]: [ [ EVOLVE_MOVE, Moves.HARDEN ], + [ RELEARN_MOVE, Moves.TACKLE ], // Previous Stage Move + [ RELEARN_MOVE, Moves.STRING_SHOT ], // Previous Stage Move + [ RELEARN_MOVE, Moves.POISON_STING ], // Previous Stage Move + [ RELEARN_MOVE, Moves.BUG_BITE ], // Previous Stage Move [ 1, Moves.HARDEN ], ], [Species.BEAUTIFLY]: [ [ EVOLVE_MOVE, Moves.GUST ], + [ 1, Moves.TACKLE ], // Previous Stage Move [ 1, Moves.BUG_BITE ], [ 1, Moves.GUST ], [ 1, Moves.HARDEN ], @@ -4658,10 +4719,15 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.CASCOON]: [ [ EVOLVE_MOVE, Moves.HARDEN ], + [ RELEARN_MOVE, Moves.TACKLE ], // Previous Stage Move + [ RELEARN_MOVE, Moves.STRING_SHOT ], // Previous Stage Move + [ RELEARN_MOVE, Moves.POISON_STING ], // Previous Stage Move + [ RELEARN_MOVE, Moves.BUG_BITE ], // Previous Stage Move [ 1, Moves.HARDEN ], ], [Species.DUSTOX]: [ [ EVOLVE_MOVE, Moves.GUST ], + [ 1, Moves.TACKLE ], // Previous Stage Move [ 1, Moves.BUG_BITE ], [ 1, Moves.GUST ], [ 1, Moves.HARDEN ], @@ -4701,6 +4767,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.ABSORB ], [ 1, Moves.FLAIL ], [ 1, Moves.FAKE_OUT ], + [ 1, Moves.RAIN_DANCE ], // Previous Stage Move [ 1, Moves.KNOCK_OFF ], [ 1, Moves.TEETER_DANCE ], [ 1, Moves.ASTONISH ], @@ -4728,6 +4795,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ RELEARN_MOVE, Moves.ASTONISH ], [ RELEARN_MOVE, Moves.ENERGY_BALL ], [ RELEARN_MOVE, Moves.ZEN_HEADBUTT ], + [ RELEARN_MOVE, Moves.LEECH_SEED ], // Previous Stage Move + [ RELEARN_MOVE, Moves.GIGA_DRAIN ], // Previous Stage Move [ 1, Moves.FAKE_OUT ], [ 1, Moves.BUBBLE_BEAM ], [ 1, Moves.RAIN_DANCE ], @@ -4757,8 +4826,10 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.EXPLOSION ], [ 1, Moves.TACKLE ], [ 1, Moves.HARDEN ], + [ 1, Moves.BIDE ], // Previous Stage Move [ 1, Moves.ABSORB ], [ 1, Moves.ASTONISH ], + [ 1, Moves.HEADBUTT ], // Previous Stage Move [ 9, Moves.GROWTH ], [ 12, Moves.ROLLOUT ], [ 18, Moves.MEGA_DRAIN ], @@ -4773,11 +4844,13 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ EVOLVE_MOVE, Moves.LEAF_BLADE ], [ RELEARN_MOVE, Moves.WHIRLWIND ], [ RELEARN_MOVE, Moves.TACKLE ], + [ RELEARN_MOVE, Moves.BIDE ], // Previous Stage Move [ RELEARN_MOVE, Moves.ABSORB ], [ RELEARN_MOVE, Moves.MEGA_DRAIN ], [ RELEARN_MOVE, Moves.GROWTH ], [ RELEARN_MOVE, Moves.RAZOR_LEAF ], [ RELEARN_MOVE, Moves.HARDEN ], + [ RELEARN_MOVE, Moves.HEADBUTT ], // Previous Stage Move [ RELEARN_MOVE, Moves.EXPLOSION ], [ RELEARN_MOVE, Moves.ROLLOUT ], [ RELEARN_MOVE, Moves.SWAGGER ], @@ -4930,11 +5003,17 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 38, Moves.STICKY_WEB ], ], [Species.MASQUERAIN]: [ + [ RELEARN_MOVE, Moves.BATON_PASS ], // Previous Stage Move + [ RELEARN_MOVE, Moves.STICKY_WEB ], // Previous Stage Move [ 1, Moves.WHIRLWIND ], [ 1, Moves.WATER_GUN ], [ 1, Moves.QUICK_ATTACK ], [ 1, Moves.SWEET_SCENT ], [ 1, Moves.SOAK ], + [ 1, Moves.BUBBLE_BEAM ], // Previous Stage Move + [ 1, Moves.AGILITY ], // Previous Stage Move + [ 1, Moves.MIST ], // Previous Stage Move + [ 1, Moves.HAZE ], // Previous Stage Move [ 1, Moves.OMINOUS_WIND ], [ 17, Moves.GUST ], [ 22, Moves.SCARY_FACE ], @@ -4963,6 +5042,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ EVOLVE_MOVE, Moves.MACH_PUNCH ], [ RELEARN_MOVE, Moves.SPORE ], [ 1, Moves.POISON_POWDER ], + [ 1, Moves.GIGA_DRAIN ], // Previous Stage Move [ 1, Moves.GROWTH ], [ 1, Moves.TOXIC ], [ 1, Moves.ABSORB ], @@ -4994,9 +5074,16 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 38, Moves.PLAY_ROUGH ], ], [Species.VIGOROTH]: [ + [ RELEARN_MOVE, Moves.PLAY_ROUGH ], // Previous Stage Move [ 1, Moves.SCRATCH ], + [ 1, Moves.YAWN ], // Previous Stage Move [ 1, Moves.FOCUS_ENERGY ], + [ 1, Moves.SLACK_OFF ], // Previous Stage Move [ 1, Moves.ENCORE ], + [ 1, Moves.HEADBUTT ], // Previous Stage Move + [ 1, Moves.AMNESIA ], // Previous Stage Move + [ 1, Moves.COVET ], // Previous Stage Move + [ 1, Moves.FLAIL ], // Previous Stage Move [ 1, Moves.UPROAR ], [ 14, Moves.FURY_SWIPES ], [ 17, Moves.ENDURE ], @@ -5008,11 +5095,20 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.SLAKING]: [ [ EVOLVE_MOVE, Moves.SWAGGER ], + [ RELEARN_MOVE, Moves.PLAY_ROUGH ], // Previous Stage Move + [ RELEARN_MOVE, Moves.FOCUS_PUNCH ], // Previous Stage Move [ 1, Moves.SUCKER_PUNCH ], [ 1, Moves.SCRATCH ], [ 1, Moves.YAWN ], + [ 1, Moves.FOCUS_ENERGY ], // Previous Stage Move [ 1, Moves.ENCORE ], [ 1, Moves.SLACK_OFF ], + [ 1, Moves.UPROAR ], // Previous Stage Move + [ 1, Moves.FURY_SWIPES ], // Previous Stage Move + [ 1, Moves.ENDURE ], // Previous Stage Move + [ 1, Moves.HEADBUTT ], // Previous Stage Move + [ 1, Moves.SLASH ], // Previous Stage Move + [ 1, Moves.REVERSAL ], // Previous Stage Move [ 17, Moves.AMNESIA ], [ 23, Moves.COVET ], [ 27, Moves.THROAT_CHOP ], @@ -5148,6 +5244,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.BRINE ], [ 1, Moves.TACKLE ], [ 1, Moves.FOCUS_ENERGY ], + [ 1, Moves.SAND_ATTACK ], // Previous Stage Move [ 1, Moves.ARM_THRUST ], [ 10, Moves.FAKE_OUT ], [ 13, Moves.FORCE_PALM ], @@ -5351,6 +5448,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.DETECT ], [ 1, Moves.WORK_UP ], [ 1, Moves.BIDE ], + [ 1, Moves.REVERSAL ], // Previous Stage Move [ 12, Moves.ENDURE ], [ 15, Moves.FEINT ], [ 17, Moves.FORCE_PALM ], @@ -5520,6 +5618,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.POISON_GAS ], [ 1, Moves.WRING_OUT ], [ 1, Moves.SLUDGE ], + [ 1, Moves.PAIN_SPLIT ], // Previous Stage Move [ 12, Moves.AMNESIA ], [ 17, Moves.ACID_SPRAY ], [ 20, Moves.ENCORE ], @@ -5565,7 +5664,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.WAILMER]: [ [ 1, Moves.SPLASH ], - [ 1, Moves.TACKLE ], //Custom + [ 1, Moves.TACKLE ], // Custom [ 3, Moves.GROWL ], [ 6, Moves.ASTONISH ], [ 12, Moves.WATER_GUN ], @@ -5586,6 +5685,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.SOAK ], [ 1, Moves.NOBLE_ROAR ], [ 1, Moves.SPLASH ], + [ 1, Moves.TACKLE ], // Previous Stage Move, Custom [ 1, Moves.GROWL ], [ 1, Moves.ASTONISH ], [ 1, Moves.WATER_GUN ], @@ -5620,6 +5720,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.CAMERUPT]: [ [ EVOLVE_MOVE, Moves.ROCK_SLIDE ], + [ RELEARN_MOVE, Moves.FLAMETHROWER ], // Previous Stage Move + [ RELEARN_MOVE, Moves.DOUBLE_EDGE ], // Previous Stage Move [ 1, Moves.FISSURE ], [ 1, Moves.ERUPTION ], [ 1, Moves.GROWL ], @@ -5658,7 +5760,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.SPOINK]: [ [ 1, Moves.SPLASH ], - [ 5, Moves.CONFUSION ], //Custom, Moved from Level 7 to 5 + [ 5, Moves.CONFUSION ], // Custom, Moved from Level 7 to 5 [ 10, Moves.GROWL ], [ 14, Moves.PSYBEAM ], [ 18, Moves.PSYCH_UP ], @@ -5676,6 +5778,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.BELCH ], [ 1, Moves.SPLASH ], [ 1, Moves.CONFUSION ], + [ 1, Moves.GROWL ], // Previous Stage Move [ 1, Moves.PSYBEAM ], [ 18, Moves.PSYCH_UP ], [ 22, Moves.CONFUSE_RAY ], @@ -6167,7 +6270,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.SHUPPET]: [ [ 1, Moves.ASTONISH ], - [ 1, Moves.PURSUIT ], //Custom + [ 1, Moves.PURSUIT ], // Custom [ 4, Moves.SCREECH ], [ 7, Moves.NIGHT_SHADE ], [ 10, Moves.SPITE ], @@ -6183,6 +6286,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.BANETTE]: [ [ EVOLVE_MOVE, Moves.KNOCK_OFF ], + [ 1, Moves.ASTONISH ], // Previous Stage Move + [ 1, Moves.PURSUIT ], // Previous Stage Move, Custom [ 1, Moves.SCREECH ], [ 1, Moves.NIGHT_SHADE ], [ 1, Moves.SPITE ], @@ -6199,7 +6304,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.DUSKULL]: [ [ 1, Moves.ASTONISH ], [ 1, Moves.LEER ], - [ 1, Moves.PURSUIT ], //Custom + [ 1, Moves.PURSUIT ], // Custom [ 4, Moves.DISABLE ], [ 8, Moves.SHADOW_SNEAK ], [ 12, Moves.CONFUSE_RAY ], @@ -6221,6 +6326,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.BIND ], [ 1, Moves.ASTONISH ], [ 1, Moves.LEER ], + [ 1, Moves.PURSUIT ], // Previous Stage Move, Custom [ 1, Moves.DISABLE ], [ 1, Moves.SHADOW_SNEAK ], [ 12, Moves.CONFUSE_RAY ], @@ -6252,7 +6358,10 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.CHIMECHO]: [ [ 1, Moves.HEALING_WISH ], + [ 1, Moves.LAST_RESORT ], // Previous Stage Move + [ 1, Moves.ENTRAINMENT ], // Previous Stage Move [ 1, Moves.WRAP ], + [ 1, Moves.PSYWAVE ], // Previous Stage Move, Custom [ 1, Moves.GROWL ], [ 1, Moves.ASTONISH ], [ 1, Moves.CONFUSION ], @@ -6392,6 +6501,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 50, Moves.SHELL_SMASH ], ], [Species.HUNTAIL]: [ + [ 1, Moves.CLAMP ], // Previous Stage Move [ 1, Moves.WATER_GUN ], [ 1, Moves.IRON_DEFENSE ], [ 1, Moves.SHELL_SMASH ], @@ -6412,6 +6522,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 50, Moves.HYDRO_PUMP ], ], [Species.GOREBYSS]: [ + [ 1, Moves.CLAMP ], // Previous Stage Move [ 1, Moves.WATER_GUN ], [ 1, Moves.IRON_DEFENSE ], [ 1, Moves.SHELL_SMASH ], @@ -6497,6 +6608,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.SALAMENCE]: [ [ EVOLVE_MOVE, Moves.FLY ], + [ RELEARN_MOVE, Moves.OUTRAGE ], // Previous Stage Move [ 1, Moves.PROTECT ], [ 1, Moves.DRAGON_TAIL ], [ 1, Moves.DUAL_WINGBEAT ], @@ -6712,7 +6824,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 98, Moves.DOOM_DESIRE ], ], [Species.DEOXYS]: [ - [ 1, Moves.CONFUSION ], //Custom + [ 1, Moves.CONFUSION ], // Custom [ 1, Moves.LEER ], [ 1, Moves.WRAP ], [ 7, Moves.NIGHT_SHADE ], @@ -6731,8 +6843,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.TURTWIG]: [ [ 1, Moves.TACKLE ], [ 5, Moves.WITHDRAW ], - [ 5, Moves.LEAFAGE ], //Custom, moved from 10 to 5, BDSP - [ 9, Moves.GROWTH ], //Fill empty moveslot, from BDSP level 6 + [ 5, Moves.LEAFAGE ], // Custom, moved from 10 to 5, BDSP + [ 9, Moves.GROWTH ], // Fill empty moveslot, from BDSP level 6 [ 13, Moves.RAZOR_LEAF ], [ 17, Moves.CURSE ], [ 21, Moves.BITE ], @@ -6748,6 +6860,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.ABSORB ], [ 1, Moves.WITHDRAW ], [ 1, Moves.LEAFAGE ], + [ 1, Moves.GROWTH ], // Previous Stage Move [ 13, Moves.RAZOR_LEAF ], [ 17, Moves.CURSE ], [ 22, Moves.BITE ], @@ -6763,6 +6876,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.TACKLE ], [ 1, Moves.ABSORB ], [ 1, Moves.LEAFAGE ], + [ 1, Moves.GROWTH ], // Previous Stage Move [ 1, Moves.RAZOR_LEAF ], [ 1, Moves.WITHDRAW ], [ 1, Moves.WOOD_HAMMER ], @@ -6779,7 +6893,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.CHIMCHAR]: [ [ 1, Moves.SCRATCH ], [ 1, Moves.LEER ], - [ 5, Moves.EMBER ], //Custom, moved from 7 to 5 + [ 5, Moves.EMBER ], // Custom, moved from 7 to 5 [ 9, Moves.TAUNT ], [ 15, Moves.FURY_SWIPES ], [ 17, Moves.FLAME_WHEEL ], @@ -6793,6 +6907,9 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.MONFERNO]: [ [ EVOLVE_MOVE, Moves.MACH_PUNCH ], + [ RELEARN_MOVE, Moves.NASTY_PLOT ], // Previous Stage Move + [ RELEARN_MOVE, Moves.FACADE ], // Previous Stage Move + [ RELEARN_MOVE, Moves.FLAMETHROWER ], // Previous Stage Move [ 1, Moves.SCRATCH ], [ 1, Moves.LEER ], [ 1, Moves.EMBER ], @@ -6810,7 +6927,10 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.INFERNAPE]: [ [ EVOLVE_MOVE, Moves.CLOSE_COMBAT ], [ RELEARN_MOVE, Moves.TAUNT ], + [ RELEARN_MOVE, Moves.NASTY_PLOT ], // Previous Stage Move + [ RELEARN_MOVE, Moves.FACADE ], // Previous Stage Move [ RELEARN_MOVE, Moves.SLACK_OFF ], + [ RELEARN_MOVE, Moves.FLAMETHROWER ], // Previous Stage Move [ 1, Moves.SCRATCH ], [ 1, Moves.LEER ], [ 1, Moves.EMBER ], @@ -6828,7 +6948,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.PIPLUP]: [ [ 1, Moves.POUND ], [ 4, Moves.GROWL ], - [ 5, Moves.WATER_GUN ], //Custom, moved from 8 to 5 + [ 5, Moves.WATER_GUN ], // Custom, moved from 8 to 5 [ 11, Moves.CHARM ], [ 15, Moves.PECK ], [ 18, Moves.BUBBLE_BEAM ], @@ -6845,6 +6965,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.TACKLE ], [ 1, Moves.GROWL ], [ 1, Moves.WATER_GUN ], + [ 1, Moves.CHARM ], // Previous Stage Move [ 15, Moves.PECK ], [ 19, Moves.BUBBLE_BEAM ], [ 24, Moves.SWAGGER ], @@ -6860,6 +6981,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.TACKLE ], [ 1, Moves.GROWL ], [ 1, Moves.WATER_GUN ], + [ 1, Moves.CHARM ], // Previous Stage Move [ 1, Moves.METAL_CLAW ], [ 11, Moves.SWORDS_DANCE ], [ 15, Moves.PECK ], @@ -6963,6 +7085,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.TACKLE ], [ 1, Moves.GROWL ], [ 1, Moves.BIDE ], + [ 1, Moves.STRUGGLE_BUG ], // Previous Stage Move + [ 1, Moves.BUG_BITE ], // Previous Stage Move [ 14, Moves.ABSORB ], [ 18, Moves.SING ], [ 22, Moves.FOCUS_ENERGY ], @@ -7113,13 +7237,14 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.BURMY]: [ [ 1, Moves.PROTECT ], - [ 1, Moves.STRUGGLE_BUG ], //Custom + [ 1, Moves.STRUGGLE_BUG ], // Custom [ 10, Moves.TACKLE ], [ 15, Moves.BUG_BITE ], [ 20, Moves.STRING_SHOT ], ], [Species.WORMADAM]: [ [ EVOLVE_MOVE, Moves.QUIVER_DANCE ], + [ 1, Moves.STRUGGLE_BUG ], // Previous Stage Move, Custom [ 1, Moves.TACKLE ], [ 1, Moves.PROTECT ], [ 1, Moves.SUCKER_PUNCH ], @@ -7140,6 +7265,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.MOTHIM]: [ [ EVOLVE_MOVE, Moves.QUIVER_DANCE ], + [ 1, Moves.STRUGGLE_BUG ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.PROTECT ], [ 1, Moves.BUG_BITE ], @@ -7221,6 +7347,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 49, Moves.WAVE_CRASH ], ], [Species.FLOATZEL]: [ + [ 1, Moves.TACKLE ], // Previous Stage Move [ 1, Moves.GROWL ], [ 1, Moves.QUICK_ATTACK ], [ 1, Moves.CRUNCH ], @@ -7368,6 +7495,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.LOPUNNY]: [ [ EVOLVE_MOVE, Moves.RETURN ], + [ 1, Moves.FRUSTRATION ], // Previous Stage Move [ 1, Moves.POUND ], [ 1, Moves.DEFENSE_CURL ], [ 1, Moves.SPLASH ], @@ -7389,6 +7517,16 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 56, Moves.HIGH_JUMP_KICK ], ], [Species.MISMAGIUS]: [ + // Previous Stage Relearn Learnset + [ RELEARN_MOVE, Moves.CONFUSION ], + [ RELEARN_MOVE, Moves.CONFUSE_RAY ], + [ RELEARN_MOVE, Moves.MEAN_LOOK ], + [ RELEARN_MOVE, Moves.HEX ], + [ RELEARN_MOVE, Moves.PSYBEAM ], + [ RELEARN_MOVE, Moves.PAIN_SPLIT ], + [ RELEARN_MOVE, Moves.PAYBACK ], + [ RELEARN_MOVE, Moves.SHADOW_BALL ], + [ RELEARN_MOVE, Moves.PERISH_SONG ], [ 1, Moves.GROWL ], [ 1, Moves.SPITE ], [ 1, Moves.PSYWAVE ], @@ -7400,11 +7538,18 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.MYSTICAL_FIRE ], ], [Species.HONCHKROW]: [ - [ 1, Moves.WING_ATTACK ], - [ 1, Moves.HAZE ], + [ 1, Moves.PECK ], // Previous Stage Move [ 1, Moves.ASTONISH ], + [ 1, Moves.GUST ], // Previous Stage Move + [ 1, Moves.HAZE ], + [ 1, Moves.WING_ATTACK ], + [ 1, Moves.NIGHT_SHADE ], // Previous Stage Move + [ 1, Moves.ASSURANCE ], // Previous Stage Move + [ 1, Moves.TAUNT ], // Previous Stage Move + [ 1, Moves.MEAN_LOOK ], // Previous Stage Move [ 1, Moves.SUCKER_PUNCH ], [ 1, Moves.NIGHT_SLASH ], + [ 1, Moves.TORMENT ], // Previous Stage Move [ 1, Moves.QUASH ], [ 1, Moves.PURSUIT ], [ 25, Moves.SWAGGER ], @@ -7449,7 +7594,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.CHINGLING]: [ [ 1, Moves.WRAP ], - [ 1, Moves.PSYWAVE ], //Custom + [ 1, Moves.PSYWAVE ], // Custom [ 4, Moves.GROWL ], [ 7, Moves.ASTONISH ], [ 10, Moves.CONFUSION ], @@ -7482,6 +7627,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.SMOKESCREEN ], [ 1, Moves.POISON_GAS ], [ 1, Moves.FEINT ], + [ 1, Moves.ACID_SPRAY ], // Previous Stage Move [ 12, Moves.FURY_SWIPES ], [ 15, Moves.FOCUS_ENERGY ], [ 18, Moves.BITE ], @@ -7533,7 +7679,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.BONSLY]: [ [ 1, Moves.FAKE_TEARS ], [ 1, Moves.COPYCAT ], - [ 1, Moves.TACKLE ], //Custom + [ 1, Moves.TACKLE ], // Custom [ 4, Moves.FLAIL ], [ 8, Moves.ROCK_THROW ], [ 12, Moves.BLOCK ], @@ -7554,11 +7700,11 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 4, Moves.BATON_PASS ], [ 8, Moves.ENCORE ], [ 12, Moves.CONFUSION ], - [ 16, Moves.MIMIC ], //Custom, swapped with Role Play to be closer to USUM + [ 16, Moves.MIMIC ], // Custom, swapped with Role Play to be closer to USUM [ 20, Moves.PROTECT ], [ 24, Moves.RECYCLE ], [ 28, Moves.PSYBEAM ], - [ 32, Moves.ROLE_PLAY ], //Custom, swapped with Mimic + [ 32, Moves.ROLE_PLAY ], // Custom, swapped with Mimic [ 36, Moves.LIGHT_SCREEN ], [ 36, Moves.REFLECT ], [ 36, Moves.SAFEGUARD ], @@ -7696,6 +7842,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.LUCARIO]: [ [ EVOLVE_MOVE, Moves.AURA_SPHERE ], [ 1, Moves.QUICK_ATTACK ], + [ 1, Moves.ENDURE ], // Previous Stage Move [ 1, Moves.SCREECH ], [ 1, Moves.REVERSAL ], [ 1, Moves.DETECT ], @@ -7836,7 +7983,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.CARNIVINE]: [ [ 1, Moves.BIND ], [ 1, Moves.GROWTH ], - [ 1, Moves.LEAFAGE ], //Custom + [ 1, Moves.LEAFAGE ], // Custom [ 7, Moves.BITE ], [ 11, Moves.VINE_WHIP ], [ 17, Moves.SWEET_SCENT ], @@ -7973,6 +8120,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.SUPERSONIC ], [ 1, Moves.DEFENSE_CURL ], [ 1, Moves.LICK ], + [ 1, Moves.TACKLE ], // Previous Stage Move, Custom [ 1, Moves.ROLLOUT ], [ 1, Moves.WRING_OUT ], [ 6, Moves.REST ], @@ -8086,7 +8234,9 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ RELEARN_MOVE, Moves.HYPNOSIS ], [ 1, Moves.TACKLE ], [ 1, Moves.DOUBLE_TEAM ], + [ 1, Moves.AIR_CUTTER ], // Previous Stage Move [ 1, Moves.NIGHT_SLASH ], + [ 1, Moves.WING_ATTACK ], // Previous Stage Move [ 1, Moves.AIR_SLASH ], [ 1, Moves.BUG_BUZZ ], [ 14, Moves.QUICK_ATTACK ], @@ -8102,6 +8252,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.LEAFEON]: [ [ EVOLVE_MOVE, Moves.SAPPY_SEED ], + [ RELEARN_MOVE, Moves.VEEVEE_VOLLEY ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.TAKE_DOWN ], [ 1, Moves.DOUBLE_EDGE ], @@ -8129,6 +8280,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.GLACEON]: [ [ EVOLVE_MOVE, Moves.FREEZY_FROST ], + [ RELEARN_MOVE, Moves.VEEVEE_VOLLEY ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.TAKE_DOWN ], [ 1, Moves.DOUBLE_EDGE ], @@ -8154,8 +8306,11 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 60, Moves.LAST_RESORT ], ], [Species.GLISCOR]: [ + [ 1, Moves.POISON_STING ], // Previous Stage Move [ 1, Moves.SAND_ATTACK ], [ 1, Moves.HARDEN ], + [ 1, Moves.POISON_TAIL ], // Previous Stage Move + [ 1, Moves.SLASH ], // Previous Stage Move [ 1, Moves.POISON_JAB ], [ 1, Moves.THUNDER_FANG ], [ 1, Moves.ICE_FANG ], @@ -8248,8 +8403,10 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.PROBOPASS]: [ [ EVOLVE_MOVE, Moves.TRI_ATTACK ], [ 1, Moves.TACKLE ], + [ 1, Moves.HARDEN ], // Previous Stage Move [ 1, Moves.IRON_DEFENSE ], [ 1, Moves.BLOCK ], + [ 1, Moves.ROCK_THROW ], // Previous Stage Move [ 1, Moves.GRAVITY ], [ 1, Moves.MAGNET_RISE ], [ 1, Moves.WIDE_GUARD ], @@ -8275,6 +8432,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.LEER ], [ 1, Moves.DISABLE ], [ 1, Moves.ASTONISH ], + [ 1, Moves.PURSUIT ], // Previous Stage Move, Custom [ 1, Moves.SHADOW_PUNCH ], [ 1, Moves.GRAVITY ], [ 1, Moves.SHADOW_SNEAK ], @@ -8298,6 +8456,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.POWDER_SNOW ], [ 1, Moves.PROTECT ], [ 1, Moves.DESTINY_BOND ], + [ 1, Moves.WEATHER_BALL ], // Previous Stage Move [ 1, Moves.CRUNCH ], [ 1, Moves.ASTONISH ], [ 1, Moves.ICE_FANG ], @@ -8538,7 +8697,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.DARKRAI]: [ [ 1, Moves.DISABLE ], [ 1, Moves.OMINOUS_WIND ], - [ 1, Moves.PURSUIT ], //Custom + [ 1, Moves.PURSUIT ], // Custom [ 11, Moves.QUICK_ATTACK ], [ 20, Moves.HYPNOSIS ], [ 29, Moves.SUCKER_PUNCH ], @@ -8551,7 +8710,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 93, Moves.DARK_PULSE ], ], [Species.SHAYMIN]: [ - [ 1, Moves.LEAFAGE ], //Custom + [ 1, Moves.LEAFAGE ], // Custom [ 1, Moves.GROWTH ], [ 10, Moves.MAGICAL_LEAF ], [ 19, Moves.LEECH_SEED ], @@ -8603,7 +8762,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.SNIVY]: [ [ 1, Moves.TACKLE ], [ 4, Moves.LEER ], - [ 5, Moves.VINE_WHIP ], //Custom, moved from 7 to 5 + [ 5, Moves.VINE_WHIP ], // Custom, moved from 7 to 5 [ 10, Moves.WRAP ], [ 13, Moves.GROWTH ], [ 16, Moves.MAGICAL_LEAF ], @@ -8651,7 +8810,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.TEPIG]: [ [ 1, Moves.TACKLE ], [ 3, Moves.TAIL_WHIP ], - [ 5, Moves.EMBER ], //Custom, moved from 7 to 5 + [ 5, Moves.EMBER ], // Custom, moved from 7 to 5 [ 9, Moves.ENDURE ], [ 13, Moves.DEFENSE_CURL ], [ 15, Moves.FLAME_CHARGE ], @@ -8705,7 +8864,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.OSHAWOTT]: [ [ 1, Moves.TACKLE ], [ 5, Moves.TAIL_WHIP ], - [ 5, Moves.WATER_GUN ], //Custom, moved from 7 to 5 + [ 5, Moves.WATER_GUN ], // Custom, moved from 7 to 5 [ 11, Moves.SOAK ], [ 13, Moves.FOCUS_ENERGY ], [ 17, Moves.RAZOR_SHELL ], @@ -8776,6 +8935,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.WATCHOG]: [ [ EVOLVE_MOVE, Moves.CONFUSE_RAY ], + [ RELEARN_MOVE, Moves.WORK_UP ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.LEER ], [ 1, Moves.BITE ], @@ -8895,6 +9055,19 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 43, Moves.CRUNCH ], ], [Species.SIMISAGE]: [ + // Previous Stage Relearn Learnset + [ RELEARN_MOVE, Moves.SCRATCH ], + [ RELEARN_MOVE, Moves.PLAY_NICE ], + [ RELEARN_MOVE, Moves.VINE_WHIP ], + [ RELEARN_MOVE, Moves.LEECH_SEED ], + [ RELEARN_MOVE, Moves.BITE ], + [ RELEARN_MOVE, Moves.TORMENT ], + [ RELEARN_MOVE, Moves.FLING ], + [ RELEARN_MOVE, Moves.ACROBATICS ], + [ RELEARN_MOVE, Moves.GRASS_KNOT ], + [ RELEARN_MOVE, Moves.RECYCLE ], + [ RELEARN_MOVE, Moves.NATURAL_GIFT ], + [ RELEARN_MOVE, Moves.CRUNCH ], [ 1, Moves.LEER ], [ 1, Moves.LICK ], [ 1, Moves.FURY_SWIPES ], @@ -8919,6 +9092,19 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 43, Moves.CRUNCH ], ], [Species.SIMISEAR]: [ + // Previous Stage Relearn Learnset + [ RELEARN_MOVE, Moves.SCRATCH ], + [ RELEARN_MOVE, Moves.PLAY_NICE ], + [ RELEARN_MOVE, Moves.INCINERATE ], + [ RELEARN_MOVE, Moves.YAWN ], + [ RELEARN_MOVE, Moves.BITE ], + [ RELEARN_MOVE, Moves.AMNESIA ], + [ RELEARN_MOVE, Moves.FLING ], + [ RELEARN_MOVE, Moves.ACROBATICS ], + [ RELEARN_MOVE, Moves.FIRE_BLAST ], + [ RELEARN_MOVE, Moves.RECYCLE ], + [ RELEARN_MOVE, Moves.NATURAL_GIFT ], + [ RELEARN_MOVE, Moves.CRUNCH ], [ 1, Moves.LEER ], [ 1, Moves.LICK ], [ 1, Moves.FURY_SWIPES ], @@ -8943,6 +9129,19 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 43, Moves.CRUNCH ], ], [Species.SIMIPOUR]: [ + // Previous Stage Relearn Learnset + [ RELEARN_MOVE, Moves.SCRATCH ], + [ RELEARN_MOVE, Moves.PLAY_NICE ], + [ RELEARN_MOVE, Moves.WATER_GUN ], + [ RELEARN_MOVE, Moves.WATER_SPORT ], + [ RELEARN_MOVE, Moves.BITE ], + [ RELEARN_MOVE, Moves.TAUNT ], + [ RELEARN_MOVE, Moves.FLING ], + [ RELEARN_MOVE, Moves.ACROBATICS ], + [ RELEARN_MOVE, Moves.BRINE ], + [ RELEARN_MOVE, Moves.RECYCLE ], + [ RELEARN_MOVE, Moves.NATURAL_GIFT ], + [ RELEARN_MOVE, Moves.CRUNCH ], [ 1, Moves.LEER ], [ 1, Moves.LICK ], [ 1, Moves.FURY_SWIPES ], @@ -8967,6 +9166,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 52, Moves.WONDER_ROOM ], ], [Species.MUSHARNA]: [ + [ 1, Moves.PSYWAVE ], // Previous Stage Move [ 1, Moves.PSYBEAM ], [ 1, Moves.PSYCHIC ], [ 1, Moves.HYPNOSIS ], @@ -9295,7 +9495,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 70, Moves.HYDRO_PUMP ], ], [Species.THROH]: [ - [ 1, Moves.ROCK_SMASH ], //Custom + [ 1, Moves.ROCK_SMASH ], // Custom [ 1, Moves.LEER ], [ 1, Moves.BIDE ], [ 1, Moves.MAT_BLOCK ], @@ -9355,9 +9555,15 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.LEAVANNY]: [ [ EVOLVE_MOVE, Moves.SLASH ], [ RELEARN_MOVE, Moves.BUG_BITE ], + [ RELEARN_MOVE, Moves.STICKY_WEB ], // Previous Stage Move + [ RELEARN_MOVE, Moves.BUG_BUZZ ], // Previous Stage Move + [ 1, Moves.PROTECT ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.RAZOR_LEAF ], [ 1, Moves.STRING_SHOT ], + [ 1, Moves.GRASS_WHISTLE ], // Previous Stage Move + [ 1, Moves.ENDURE ], // Previous Stage Move + [ 1, Moves.FLAIL ], // Previous Stage Move [ 1, Moves.FALSE_SWIPE ], [ 22, Moves.STRUGGLE_BUG ], [ 29, Moves.FELL_STINGER ], @@ -9890,6 +10096,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.TORMENT ], [ 1, Moves.U_TURN ], [ 1, Moves.HONE_CLAWS ], + [ 1, Moves.SCARY_FACE ], // Previous Stage Move [ 1, Moves.PURSUIT ], [ 12, Moves.FURY_SWIPES ], [ 20, Moves.TAUNT ], @@ -9964,6 +10171,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 28, Moves.FAKE_TEARS ], [ 34, Moves.HEAL_BLOCK ], [ 35, Moves.PSYCH_UP ], + [ 40, Moves.PSYCHIC ], // Previous Stage Move, Gothitelle Level [ 46, Moves.FLATTER ], [ 52, Moves.FUTURE_SIGHT ], [ 58, Moves.MAGIC_ROOM ], @@ -10081,7 +10289,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.VANILLITE]: [ [ 1, Moves.HARDEN ], [ 1, Moves.ASTONISH ], - [ 1, Moves.POWDER_SNOW ], //Custom + [ 1, Moves.POWDER_SNOW ], // Custom [ 4, Moves.TAUNT ], [ 8, Moves.MIST ], [ 12, Moves.ICY_WIND ], @@ -10100,6 +10308,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.HARDEN ], [ 1, Moves.TAUNT ], [ 1, Moves.ASTONISH ], + [ 1, Moves.POWDER_SNOW ], // Previous Stage Move, Custom [ 12, Moves.ICY_WIND ], [ 16, Moves.AVALANCHE ], [ 20, Moves.HAIL ], @@ -10116,6 +10325,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.HARDEN ], [ 1, Moves.TAUNT ], [ 1, Moves.ASTONISH ], + [ 1, Moves.POWDER_SNOW ], // Previous Stage Move, Custom [ 1, Moves.WEATHER_BALL ], [ 1, Moves.ICICLE_CRASH ], [ 1, Moves.FREEZE_DRY ], @@ -10428,6 +10638,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.EELEKTRIK]: [ [ EVOLVE_MOVE, Moves.CRUNCH ], + [ 1, Moves.TACKLE ], // Previous Stage Move [ 1, Moves.HEADBUTT ], [ 1, Moves.THUNDER_WAVE ], [ 1, Moves.SPARK ], @@ -10445,7 +10656,15 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 74, Moves.THRASH ], ], [Species.EELEKTROSS]: [ + [ RELEARN_MOVE, Moves.THUNDERBOLT ], // Previous Stage Move + [ RELEARN_MOVE, Moves.ACID_SPRAY ], // Previous Stage Move + [ 1, Moves.TACKLE ], // Previous Stage Move [ 1, Moves.HEADBUTT ], + [ 1, Moves.THUNDER_WAVE ], // Previous Stage Move + [ 1, Moves.SPARK ], // Previous Stage Move + [ 1, Moves.CHARGE_BEAM ], // Previous Stage Move + [ 1, Moves.ION_DELUGE ], // Previous Stage Move + [ 1, Moves.BIND ], // Previous Stage Move [ 1, Moves.THRASH ], [ 1, Moves.ACID ], [ 1, Moves.ZAP_CANNON ], @@ -10688,6 +10907,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.BODY_SLAM ], [ 1, Moves.ACID ], [ 1, Moves.ABSORB ], + [ 1, Moves.PROTECT ], // Previous Stage Move [ 1, Moves.QUICK_ATTACK ], [ 1, Moves.DOUBLE_TEAM ], [ 1, Moves.ACID_ARMOR ], @@ -10881,6 +11101,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.BRAVIARY]: [ [ EVOLVE_MOVE, Moves.SUPERPOWER ], + [ RELEARN_MOVE, Moves.BRAVE_BIRD ], // Previous Stage Move [ 1, Moves.WING_ATTACK ], [ 1, Moves.LEER ], [ 1, Moves.PECK ], @@ -11422,6 +11643,10 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.GROWL ], [ 1, Moves.WATER_GUN ], [ 1, Moves.QUICK_ATTACK ], + [ 1, Moves.ROUND ], // Previous Stage Move + [ 1, Moves.FLING ], // Previous Stage Move + [ 1, Moves.SMACK_DOWN ], // Previous Stage Move + [ 1, Moves.BOUNCE ], // Previous Stage Move [ 1, Moves.HAZE ], [ 1, Moves.MAT_BLOCK ], [ 1, Moves.ROLE_PLAY ], @@ -11529,10 +11754,19 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.SPEWPA]: [ [ EVOLVE_MOVE, Moves.PROTECT ], + [ RELEARN_MOVE, Moves.TACKLE ], // Previous Stage Move + [ RELEARN_MOVE, Moves.STRING_SHOT ], // Previous Stage Move + [ RELEARN_MOVE, Moves.STUN_SPORE ], // Previous Stage Move + [ RELEARN_MOVE, Moves.BUG_BITE ], // Previous Stage Move [ 1, Moves.HARDEN ], ], [Species.VIVILLON]: [ [ EVOLVE_MOVE, Moves.GUST ], + [ 1, Moves.PROTECT ], // Previous Stage Move + [ 1, Moves.TACKLE ], // Previous Stage Move + [ 1, Moves.STRING_SHOT ], // Previous Stage Move + [ 1, Moves.HARDEN ], // Previous Stage Move + [ 1, Moves.BUG_BITE ], // Previous Stage Move [ 1, Moves.POISON_POWDER ], [ 1, Moves.STUN_SPORE ], [ 1, Moves.SLEEP_POWDER ], @@ -11615,6 +11849,10 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 58, Moves.SOLAR_BEAM ], ], [Species.FLORGES]: [ + [ 1, Moves.VINE_WHIP ], // Previous Stage Move + [ 1, Moves.TACKLE ], // Previous Stage Move + [ 1, Moves.FAIRY_WIND ], // Previous Stage Move + [ 1, Moves.RAZOR_LEAF ], // Previous Stage Move [ 1, Moves.SOLAR_BEAM ], [ 1, Moves.PETAL_DANCE ], [ 1, Moves.SAFEGUARD ], @@ -12106,6 +12344,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.SYLVEON]: [ [ EVOLVE_MOVE, Moves.SPARKLY_SWIRL ], + [ RELEARN_MOVE, Moves.VEEVEE_VOLLEY ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.TAKE_DOWN ], [ 1, Moves.DOUBLE_EDGE ], @@ -12216,6 +12455,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.TACKLE ], [ 1, Moves.WATER_GUN ], [ 1, Moves.ABSORB ], + [ 1, Moves.ACID_ARMOR ], // Previous Stage Move [ 1, Moves.DRAGON_BREATH ], [ 1, Moves.POISON_TAIL ], [ 1, Moves.FEINT ], @@ -12225,6 +12465,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 20, Moves.FLAIL ], [ 25, Moves.WATER_PULSE ], [ 30, Moves.RAIN_DANCE ], + [ 35, Moves.DRAGON_PULSE ], // Previous Stage Move, NatDex / Hisui Goodra Level [ 43, Moves.CURSE ], [ 49, Moves.BODY_SLAM ], [ 58, Moves.MUDDY_WATER ], @@ -12285,7 +12526,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.PUMPKABOO]: [ [ 1, Moves.ASTONISH ], [ 1, Moves.TRICK_OR_TREAT ], - [ 1, Moves.LEAFAGE ], //Custom + [ 1, Moves.LEAFAGE ], // Custom [ 4, Moves.SHADOW_SNEAK ], [ 8, Moves.CONFUSE_RAY ], [ 12, Moves.RAZOR_LEAF ], @@ -12302,6 +12543,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.CONFUSE_RAY ], [ 1, Moves.EXPLOSION ], [ 1, Moves.ASTONISH ], + [ 1, Moves.LEAFAGE ], // Previous Stage Move, Custom [ 1, Moves.SHADOW_SNEAK ], [ 1, Moves.TRICK_OR_TREAT ], [ 1, Moves.MOONBLAST ], @@ -12813,11 +13055,14 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.CRABOMINABLE]: [ [ EVOLVE_MOVE, Moves.ICE_PUNCH ], + [ RELEARN_MOVE, Moves.CRABHAMMER ], // Previous Stage Move + [ 1, Moves.VISE_GRIP ], // Previous Stage Move [ 1, Moves.LEER ], [ 1, Moves.PROTECT ], [ 1, Moves.ROCK_SMASH ], [ 1, Moves.BUBBLE ], [ 1, Moves.PURSUIT ], + [ 1, Moves.PAYBACK ], // Previous Stage Move [ 17, Moves.BUBBLE_BEAM ], [ 22, Moves.BRICK_BREAK ], [ 25, Moves.SLAM ], @@ -13006,6 +13251,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.BUG_BITE ], [ 1, Moves.WIDE_GUARD ], [ 1, Moves.INFESTATION ], + [ 1, Moves.WATER_SPORT ], // Previous Stage Move [ 1, Moves.SPIDER_WEB ], [ 12, Moves.BUBBLE_BEAM ], [ 16, Moves.AQUA_RING ], @@ -13154,7 +13400,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.BOUNSWEET]: [ [ 1, Moves.SPLASH ], - [ 1, Moves.LEAFAGE ], //Custom + [ 1, Moves.LEAFAGE ], // Custom [ 4, Moves.PLAY_NICE ], [ 8, Moves.RAPID_SPIN ], [ 12, Moves.RAZOR_LEAF ], @@ -13165,6 +13411,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 32, Moves.AROMATIC_MIST ], ], [Species.STEENEE]: [ + [ 1, Moves.LEAFAGE ], // Previous Stage Move, Custom [ 1, Moves.RAZOR_LEAF ], [ 1, Moves.SPLASH ], [ 1, Moves.FLAIL ], @@ -13179,6 +13426,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.TSAREENA]: [ [ EVOLVE_MOVE, Moves.TROP_KICK ], + [ 1, Moves.LEAFAGE ], // Previous Stage Move, Custom [ 1, Moves.RAZOR_LEAF ], [ 1, Moves.SPLASH ], [ 1, Moves.FLAIL ], @@ -13303,7 +13551,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 68, Moves.SANDSTORM ], ], [Species.PYUKUMUKU]: [ - [ 1, Moves.COUNTER ], //Custom, Moved from Level 20 to 1 + [ 1, Moves.COUNTER ], // Custom, Moved from Level 20 to 1 [ 1, Moves.HARDEN ], [ 1, Moves.BATON_PASS ], [ 1, Moves.BIDE ], @@ -13312,7 +13560,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 5, Moves.HELPING_HAND ], [ 10, Moves.TAUNT ], [ 15, Moves.SAFEGUARD ], - [ 20, Moves.MIRROR_COAT ], //Custom + [ 20, Moves.MIRROR_COAT ], // Custom [ 25, Moves.PURIFY ], [ 30, Moves.CURSE ], [ 35, Moves.GASTRO_ACID ], @@ -13629,15 +13877,19 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.COSMOG]: [ [ 1, Moves.TELEPORT ], [ 1, Moves.SPLASH ], - [ 1, Moves.STORED_POWER ], //Custom + [ 1, Moves.STORED_POWER ], // Custom ], [Species.COSMOEM]: [ [ EVOLVE_MOVE, Moves.COSMIC_POWER ], [ 1, Moves.TELEPORT ], + [ 1, Moves.SPLASH ], // Previous Stage Move + [ 1, Moves.STORED_POWER ], // Previous Stage Move, Custom ], [Species.SOLGALEO]: [ [ EVOLVE_MOVE, Moves.SUNSTEEL_STRIKE ], [ 1, Moves.TELEPORT ], + [ 1, Moves.SPLASH ], // Previous Stage Move + [ 1, Moves.STORED_POWER ], // Previous Stage Move, Custom [ 1, Moves.METAL_CLAW ], [ 1, Moves.COSMIC_POWER ], [ 1, Moves.NOBLE_ROAR ], @@ -13660,6 +13912,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.CONFUSION ], [ 1, Moves.HYPNOSIS ], [ 1, Moves.TELEPORT ], + [ 1, Moves.SPLASH ], // Previous Stage Move + [ 1, Moves.STORED_POWER ], // Previous Stage Move, Custom [ 1, Moves.COSMIC_POWER ], [ 7, Moves.NIGHT_SHADE ], [ 14, Moves.CONFUSE_RAY ], @@ -13826,7 +14080,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.MAGEARNA]: [ [ 1, Moves.HELPING_HAND ], [ 1, Moves.GYRO_BALL ], - [ 1, Moves.DISARMING_VOICE ], //Custom + [ 1, Moves.DISARMING_VOICE ], // Custom [ 1, Moves.CRAFTY_SHIELD ], [ 1, Moves.GEAR_UP ], [ 6, Moves.DEFENSE_CURL ], @@ -13867,7 +14121,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 99, Moves.CLOSE_COMBAT ], ], [Species.POIPOLE]: [ - [ RELEARN_MOVE, Moves.DRAGON_PULSE ], //Custom, made relearn + [ RELEARN_MOVE, Moves.DRAGON_PULSE ], // Custom, made relearn [ 1, Moves.GROWL ], [ 1, Moves.ACID ], [ 1, Moves.PECK ], @@ -13986,7 +14240,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.GROOKEY]: [ [ 1, Moves.SCRATCH ], [ 1, Moves.GROWL ], - [ 5, Moves.BRANCH_POKE ], //Custom, moved from 6 to 5 + [ 5, Moves.BRANCH_POKE ], // Custom, moved from 6 to 5 [ 8, Moves.TAUNT ], [ 12, Moves.RAZOR_LEAF ], [ 17, Moves.SCREECH ], @@ -14031,7 +14285,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.SCORBUNNY]: [ [ 1, Moves.TACKLE ], [ 1, Moves.GROWL ], - [ 5, Moves.EMBER ], //Custom, moved from 6 to 5 + [ 5, Moves.EMBER ], // Custom, moved from 6 to 5 [ 8, Moves.QUICK_ATTACK ], [ 12, Moves.DOUBLE_KICK ], [ 17, Moves.FLAME_CHARGE ], @@ -14073,7 +14327,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.SOBBLE]: [ [ 1, Moves.POUND ], [ 1, Moves.GROWL ], - [ 5, Moves.WATER_GUN ], //Custom, moved from 6 to 5 + [ 5, Moves.WATER_GUN ], // Custom, moved from 6 to 5 [ 8, Moves.BIND ], [ 12, Moves.WATER_PULSE ], [ 17, Moves.TEARFUL_LOOK ], @@ -14400,10 +14654,11 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.APPLIN]: [ [ 1, Moves.WITHDRAW ], [ 1, Moves.ASTONISH ], - [ 1, Moves.LEAFAGE ], //Custom + [ 1, Moves.LEAFAGE ], // Custom ], [Species.FLAPPLE]: [ [ EVOLVE_MOVE, Moves.WING_ATTACK ], + [ 1, Moves.LEAFAGE ], // Previous Stage Move, Custom [ 1, Moves.GROWTH ], [ 1, Moves.WITHDRAW ], [ 1, Moves.TWISTER ], @@ -14423,6 +14678,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.APPLETUN]: [ [ EVOLVE_MOVE, Moves.HEADBUTT ], + [ 1, Moves.LEAFAGE ], // Previous Stage Move, Custom [ 1, Moves.GROWTH ], [ 1, Moves.WITHDRAW ], [ 1, Moves.SWEET_SCENT ], @@ -14443,7 +14699,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.SILICOBRA]: [ [ 1, Moves.SAND_ATTACK ], [ 1, Moves.WRAP ], - [ 1, Moves.MUD_SLAP ], //Custom + [ 1, Moves.MUD_SLAP ], // Custom [ 5, Moves.MINIMIZE ], [ 10, Moves.BRUTAL_SWING ], [ 15, Moves.BULLDOZE ], @@ -14458,6 +14714,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.SANDACONDA]: [ [ 1, Moves.SAND_ATTACK ], [ 1, Moves.WRAP ], + [ 1, Moves.MUD_SLAP ], // Previous Stage Move, Custom [ 1, Moves.MINIMIZE ], [ 1, Moves.BRUTAL_SWING ], [ 15, Moves.BULLDOZE ], @@ -14605,7 +14862,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.SINISTEA]: [ [ 1, Moves.WITHDRAW ], [ 1, Moves.ASTONISH ], - [ 1, Moves.ABSORB ], //Custom + [ 1, Moves.ABSORB ], // Custom [ 6, Moves.AROMATIC_MIST ], [ 12, Moves.MEGA_DRAIN ], [ 24, Moves.SUCKER_PUNCH ], @@ -14618,6 +14875,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.POLTEAGEIST]: [ [ EVOLVE_MOVE, Moves.TEATIME ], + [ 1, Moves.ABSORB ], // Previous Stage Move, Custom [ 1, Moves.MEGA_DRAIN ], [ 1, Moves.WITHDRAW ], [ 1, Moves.ASTONISH ], @@ -14805,6 +15063,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.MR_RIME]: [ [ 1, Moves.POUND ], + [ 1, Moves.BARRIER ], // Previous Stage Move + [ 1, Moves.TICKLE ], // Previous Stage Move [ 1, Moves.MIMIC ], [ 1, Moves.LIGHT_SCREEN ], [ 1, Moves.REFLECT ], @@ -15133,6 +15393,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.DRAGAPULT]: [ [ EVOLVE_MOVE, Moves.DRAGON_DARTS ], + [ RELEARN_MOVE, Moves.DRAGON_PULSE ], // Previous Stage Move [ 1, Moves.BITE ], [ 1, Moves.QUICK_ATTACK ], [ 1, Moves.DRAGON_BREATH ], @@ -15339,6 +15600,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.WYRDEER]: [ [ EVOLVE_MOVE, Moves.PSYSHIELD_BASH ], [ 1, Moves.TACKLE ], + [ 1, Moves.ME_FIRST ], // Previous Stage Move [ 3, Moves.LEER ], [ 7, Moves.ASTONISH ], [ 10, Moves.HYPNOSIS ], @@ -15355,6 +15617,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.KLEAVOR]: [ [ EVOLVE_MOVE, Moves.STONE_AXE ], + [ 1, Moves.WING_ATTACK ], // Previous Stage Move + [ 1, Moves.AIR_SLASH ], // Previous Stage Move [ 1, Moves.LEER ], [ 1, Moves.QUICK_ATTACK ], [ 4, Moves.FURY_CUTTER ], @@ -15364,6 +15628,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 20, Moves.DOUBLE_HIT ], [ 24, Moves.SLASH ], [ 28, Moves.FOCUS_ENERGY ], + [ 30, Moves.STEEL_WING ], // Custom [ 32, Moves.AGILITY ], [ 36, Moves.ROCK_SLIDE ], [ 40, Moves.X_SCISSOR ], @@ -15374,8 +15639,11 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.SCRATCH ], [ 1, Moves.LEER ], [ 1, Moves.LICK ], - [ 1, Moves.FAKE_TEARS ], [ 1, Moves.COVET ], + [ 1, Moves.FLING ], // Previous Stage Move + [ 1, Moves.BABY_DOLL_EYES ], // Previous Stage Move + [ 1, Moves.FAKE_TEARS ], + [ 1, Moves.CHARM ], // Previous Stage Moves [ 8, Moves.FURY_SWIPES ], [ 13, Moves.PAYBACK ], [ 17, Moves.SWEET_SCENT ], @@ -15390,6 +15658,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 64, Moves.HAMMER_ARM ], ], [Species.BASCULEGION]: [ + [ RELEARN_MOVE, Moves.FINAL_GAMBIT ], // Previous Stage Move, White Stripe currently shares moveset with other forms [ 1, Moves.TAIL_WHIP ], [ 1, Moves.WATER_GUN ], [ 1, Moves.SHADOW_BALL ], @@ -15949,10 +16218,12 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.GARGANACL]: [ [ EVOLVE_MOVE, Moves.HAMMER_ARM ], + [ RELEARN_MOVE, Moves.IRON_DEFENSE ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.HARDEN ], [ 1, Moves.BLOCK ], [ 1, Moves.ROCK_BLAST ], + [ 1, Moves.SMACK_DOWN ], // Previous Stage Move [ 1, Moves.WIDE_GUARD ], [ 5, Moves.ROCK_THROW ], [ 7, Moves.MUD_SHOT ], @@ -16140,6 +16411,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ EVOLVE_MOVE, Moves.DOODLE ], [ 1, Moves.SCRATCH ], [ 1, Moves.LEER ], + [ 1, Moves.BITE ], // Previous Stage Move [ 5, Moves.ACID_SPRAY ], [ 8, Moves.FURY_SWIPES ], [ 11, Moves.SWITCHEROO ], @@ -16294,6 +16566,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.TACKLE ], [ 1, Moves.CONFUSION ], [ 1, Moves.DEFENSE_CURL ], + [ 1, Moves.MUD_SHOT ], // Previous Stage Move + [ 1, Moves.DIG ], // Previous Stage Move [ 4, Moves.SAND_ATTACK ], [ 7, Moves.STRUGGLE_BUG ], [ 11, Moves.ROLLOUT ], @@ -16717,6 +16991,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.LEER ], [ 1, Moves.COUNTER ], [ 1, Moves.FOCUS_ENERGY ], + [ 1, Moves.COVET ], // Previous Stage Move [ 1, Moves.FLING ], [ 5, Moves.FURY_SWIPES ], [ 8, Moves.LOW_KICK ], @@ -16734,6 +17009,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.CLODSIRE]: [ [ EVOLVE_MOVE, Moves.AMNESIA ], + [ 1, Moves.TACKLE ], // Previous Stage Move [ 1, Moves.TAIL_WHIP ], [ 1, Moves.POISON_STING ], [ 4, Moves.TOXIC_SPIKES ], @@ -16768,6 +17044,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.DUDUNSPARCE]: [ [ 1, Moves.DEFENSE_CURL ], [ 1, Moves.FLAIL ], + [ 1, Moves.TACKLE ], // Previous Stage Move, Custom [ 4, Moves.MUD_SLAP ], [ 8, Moves.ROLLOUT ], [ 12, Moves.GLARE ], @@ -16864,7 +17141,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.CONFUSE_RAY ], [ 1, Moves.SPITE ], [ 1, Moves.ASTONISH ], - [ 1, Moves.PSYBEAM ], //Custom, moved from 7 to 1 + [ 1, Moves.PSYBEAM ], // Custom, moved from 7 to 1 [ 14, Moves.MEAN_LOOK ], [ 21, Moves.MEMENTO ], [ 28, Moves.WISH ], @@ -16939,7 +17216,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.IRON_BUNDLE]: [ [ RELEARN_MOVE, Moves.ELECTRIC_TERRAIN ], [ 1, Moves.PRESENT ], - [ 1, Moves.WATER_GUN ], //Custom + [ 1, Moves.WATER_GUN ], // Custom [ 7, Moves.POWDER_SNOW ], [ 14, Moves.WHIRLPOOL ], [ 21, Moves.TAKE_DOWN ], @@ -17058,6 +17335,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 18, Moves.FOCUS_ENERGY ], [ 24, Moves.BITE ], [ 29, Moves.ICE_FANG ], + [ 32, Moves.DRAGON_CLAW ], // Previous Stage Move, Frigibax Level [ 40, Moves.TAKE_DOWN ], [ 45, Moves.ICE_BEAM ], [ 50, Moves.CRUNCH ], @@ -17305,6 +17583,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.DIPPLIN]: [ [ EVOLVE_MOVE, Moves.DOUBLE_HIT ], [ RELEARN_MOVE, Moves.DRAGON_CHEER ], // Custom + [ 1, Moves.LEAFAGE ], [ 1, Moves.WITHDRAW ], [ 1, Moves.SWEET_SCENT ], [ 1, Moves.RECYCLE ], @@ -17324,7 +17603,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.STUN_SPORE ], [ 1, Moves.WITHDRAW ], [ 1, Moves.ASTONISH ], - [ 5, Moves.ABSORB ], //Custom, Moved from Level 6 to 5 + [ 5, Moves.ABSORB ], // Custom, Moved from Level 6 to 5 [ 12, Moves.LIFE_DEW ], [ 18, Moves.FOUL_PLAY ], [ 24, Moves.MEGA_DRAIN ], @@ -17337,6 +17616,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.SINISTCHA]: [ [ EVOLVE_MOVE, Moves.MATCHA_GOTCHA ], + [ RELEARN_MOVE, Moves.GIGA_DRAIN ], // Previous Stage Move [ 1, Moves.STUN_SPORE ], [ 1, Moves.WITHDRAW ], [ 1, Moves.ASTONISH ], @@ -17419,6 +17699,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.ARCHALUDON]: [ [ EVOLVE_MOVE, Moves.ELECTRO_SHOT ], + [ RELEARN_MOVE, Moves.LASER_FOCUS ], // Previous Stage Move [ 1, Moves.LEER ], [ 1, Moves.METAL_CLAW ], [ 6, Moves.ROCK_SMASH ], @@ -17438,6 +17719,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ RELEARN_MOVE, Moves.YAWN ], [ RELEARN_MOVE, Moves.DOUBLE_HIT ], [ RELEARN_MOVE, Moves.INFESTATION ], + [ RELEARN_MOVE, Moves.DRAGON_CHEER ], // Previous Stage Move, Custom + [ 1, Moves.LEAFAGE ], // Previous Stage Move, Custom [ 1, Moves.WITHDRAW ], [ 1, Moves.SWEET_SCENT ], [ 1, Moves.RECYCLE ], @@ -17809,6 +18092,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.DEFENSE_CURL ], [ 1, Moves.CHARGE ], [ 1, Moves.ROCK_POLISH ], + [ 1, Moves.ROLLOUT ], // Previous Stage Move [ 1, Moves.HEAVY_SLAM ], [ 12, Moves.SPARK ], [ 16, Moves.ROCK_THROW ], @@ -18051,6 +18335,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.GALAR_MR_MIME]: [ [ 1, Moves.POUND ], + [ 1, Moves.BARRIER ], // Previous Stage Move + [ 1, Moves.TICKLE ], // Previous Stage Move [ 1, Moves.MIMIC ], [ 1, Moves.LIGHT_SCREEN ], [ 1, Moves.REFLECT ], @@ -18411,6 +18697,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.TACKLE ], [ 1, Moves.TAIL_WHIP ], [ 1, Moves.WATER_GUN ], + [ 1, Moves.SOAK ], // Previous Stage Move [ 1, Moves.SLASH ], [ 1, Moves.MEGAHORN ], [ 1, Moves.SUCKER_PUNCH ], @@ -18436,6 +18723,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.STUN_SPORE ], [ 1, Moves.SLEEP_POWDER ], [ 1, Moves.GIGA_DRAIN ], + [ 1, Moves.CHARM ], // Previous Stage Move [ 1, Moves.SYNTHESIS ], [ 1, Moves.SUNNY_DAY ], [ 1, Moves.HELPING_HAND ], @@ -18487,6 +18775,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.HISUI_BRAVIARY]: [ [ EVOLVE_MOVE, Moves.ESPER_WING ], + [ RELEARN_MOVE, Moves.BRAVE_BIRD ], // Previous Stage Move [ 1, Moves.WING_ATTACK ], [ 1, Moves.LEER ], [ 1, Moves.PECK ], @@ -18511,6 +18800,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.ABSORB ], [ 1, Moves.ACID_ARMOR ], [ 1, Moves.DRAGON_BREATH ], + [ 1, Moves.BODY_SLAM ], // Previous Stage Move [ 15, Moves.PROTECT ], [ 20, Moves.FLAIL ], [ 25, Moves.WATER_PULSE ], @@ -18525,6 +18815,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.TACKLE ], [ 1, Moves.WATER_GUN ], [ 1, Moves.ABSORB ], + [ 1, Moves.ACID_ARMOR ], // Previous Stage Move [ 1, Moves.DRAGON_BREATH ], [ 1, Moves.FEINT ], [ 1, Moves.ACID_SPRAY ], @@ -18565,9 +18856,11 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { ], [Species.HISUI_DECIDUEYE]: [ [ EVOLVE_MOVE, Moves.TRIPLE_ARROWS ], + [ RELEARN_MOVE, Moves.NASTY_PLOT ], // Previous Stage Move [ 1, Moves.TACKLE ], [ 1, Moves.GROWL ], [ 1, Moves.U_TURN ], + [ 1, Moves.ASTONISH ], // Previous Stage Move [ 1, Moves.LEAF_STORM ], [ 1, Moves.LEAFAGE ], [ 9, Moves.PECK ], @@ -18633,7 +18926,7 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { }; export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { - [Species.PIKACHU]: { //Custom + [Species.PIKACHU]: { // Custom 1: [ [ 1, Moves.TAIL_WHIP ], [ 1, Moves.GROWL ], @@ -18648,14 +18941,14 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { [ 8, Moves.DOUBLE_TEAM ], [ 12, Moves.ELECTRO_BALL ], [ 16, Moves.FEINT ], - [ 20, Moves.ZIPPY_ZAP ], //Custom + [ 20, Moves.ZIPPY_ZAP ], // Custom [ 24, Moves.AGILITY ], [ 28, Moves.IRON_TAIL ], [ 32, Moves.DISCHARGE ], - [ 34, Moves.FLOATY_FALL ], //Custom + [ 34, Moves.FLOATY_FALL ], // Custom [ 36, Moves.THUNDERBOLT ], [ 40, Moves.LIGHT_SCREEN ], - [ 42, Moves.SPLISHY_SPLASH ], //Custom + [ 42, Moves.SPLISHY_SPLASH ], // Custom [ 44, Moves.THUNDER ], [ 48, Moves.PIKA_PAPOW ], ], @@ -18816,19 +19109,19 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { [ 8, Moves.DOUBLE_TEAM ], [ 12, Moves.ELECTRO_BALL ], [ 16, Moves.FEINT ], - [ 20, Moves.ZIPPY_ZAP ], //Custom + [ 20, Moves.ZIPPY_ZAP ], // Custom [ 24, Moves.AGILITY ], [ 28, Moves.IRON_TAIL ], [ 32, Moves.DISCHARGE ], - [ 34, Moves.FLOATY_FALL ], //Custom + [ 34, Moves.FLOATY_FALL ], // Custom [ 36, Moves.THUNDERBOLT ], [ 40, Moves.LIGHT_SCREEN ], - [ 42, Moves.SPLISHY_SPLASH ], //Custom + [ 42, Moves.SPLISHY_SPLASH ], // Custom [ 44, Moves.THUNDER ], [ 48, Moves.PIKA_PAPOW ], ], }, - [Species.EEVEE]: { //Custom + [Species.EEVEE]: { // Custom 1: [ [ 1, Moves.TACKLE ], [ 1, Moves.TAIL_WHIP ], @@ -18838,21 +19131,21 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { [ 5, Moves.SAND_ATTACK ], [ 10, Moves.QUICK_ATTACK ], [ 15, Moves.BABY_DOLL_EYES ], - [ 18, Moves.BOUNCY_BUBBLE ], //Custom - [ 18, Moves.SIZZLY_SLIDE ], //Custom - [ 18, Moves.BUZZY_BUZZ ], //Custom + [ 18, Moves.BOUNCY_BUBBLE ], // Custom + [ 18, Moves.SIZZLY_SLIDE ], // Custom + [ 18, Moves.BUZZY_BUZZ ], // Custom [ 20, Moves.SWIFT ], [ 25, Moves.BITE ], [ 30, Moves.COPYCAT ], - [ 33, Moves.BADDY_BAD ], //Custom - [ 33, Moves.GLITZY_GLOW ], //Custom + [ 33, Moves.BADDY_BAD ], // Custom + [ 33, Moves.GLITZY_GLOW ], // Custom [ 35, Moves.BATON_PASS ], - [ 40, Moves.VEEVEE_VOLLEY ], //Custom, replaces Take Down - [ 43, Moves.FREEZY_FROST ], //Custom - [ 43, Moves.SAPPY_SEED ], //Custom + [ 40, Moves.VEEVEE_VOLLEY ], // Custom, replaces Take Down + [ 43, Moves.FREEZY_FROST ], // Custom + [ 43, Moves.SAPPY_SEED ], // Custom [ 45, Moves.CHARM ], [ 50, Moves.DOUBLE_EDGE ], - [ 53, Moves.SPARKLY_SWIRL ], //Custom + [ 53, Moves.SPARKLY_SWIRL ], // Custom [ 55, Moves.LAST_RESORT ], ], 2: [ @@ -18864,27 +19157,27 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { [ 5, Moves.SAND_ATTACK ], [ 10, Moves.QUICK_ATTACK ], [ 15, Moves.BABY_DOLL_EYES ], - [ 18, Moves.BOUNCY_BUBBLE ], //Custom - [ 18, Moves.SIZZLY_SLIDE ], //Custom - [ 18, Moves.BUZZY_BUZZ ], //Custom + [ 18, Moves.BOUNCY_BUBBLE ], // Custom + [ 18, Moves.SIZZLY_SLIDE ], // Custom + [ 18, Moves.BUZZY_BUZZ ], // Custom [ 20, Moves.SWIFT ], [ 25, Moves.BITE ], [ 30, Moves.COPYCAT ], - [ 33, Moves.BADDY_BAD ], //Custom - [ 33, Moves.GLITZY_GLOW ], //Custom + [ 33, Moves.BADDY_BAD ], // Custom + [ 33, Moves.GLITZY_GLOW ], // Custom [ 35, Moves.BATON_PASS ], - [ 40, Moves.VEEVEE_VOLLEY ], //Custom, replaces Take Down - [ 43, Moves.FREEZY_FROST ], //Custom - [ 43, Moves.SAPPY_SEED ], //Custom + [ 40, Moves.VEEVEE_VOLLEY ], // Custom, replaces Take Down + [ 43, Moves.FREEZY_FROST ], // Custom + [ 43, Moves.SAPPY_SEED ], // Custom [ 45, Moves.CHARM ], [ 50, Moves.DOUBLE_EDGE ], - [ 53, Moves.SPARKLY_SWIRL ], //Custom + [ 53, Moves.SPARKLY_SWIRL ], // Custom [ 55, Moves.LAST_RESORT ], ], }, [Species.DEOXYS]: { 1: [ - [ 1, Moves.CONFUSION ], //Custom + [ 1, Moves.CONFUSION ], // Custom [ 1, Moves.WRAP ], [ 1, Moves.LEER ], [ 7, Moves.NIGHT_SHADE ], @@ -18901,7 +19194,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { [ 73, Moves.HYPER_BEAM ], ], 2: [ - [ 1, Moves.CONFUSION ], //Custom + [ 1, Moves.CONFUSION ], // Custom [ 1, Moves.WRAP ], [ 1, Moves.LEER ], [ 7, Moves.NIGHT_SHADE ], @@ -18920,7 +19213,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { [ 73, Moves.MIRROR_COAT ], ], 3: [ - [ 1, Moves.CONFUSION ], //Custom + [ 1, Moves.CONFUSION ], // Custom [ 1, Moves.WRAP ], [ 1, Moves.LEER ], [ 7, Moves.NIGHT_SHADE ], @@ -18940,6 +19233,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { [Species.WORMADAM]: { 1: [ [ EVOLVE_MOVE, Moves.QUIVER_DANCE ], + [ 1, Moves.STRUGGLE_BUG ], // Previous Stage Move, Custom [ 1, Moves.TACKLE ], [ 1, Moves.PROTECT ], [ 1, Moves.SUCKER_PUNCH ], @@ -18960,6 +19254,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { ], 2: [ [ EVOLVE_MOVE, Moves.QUIVER_DANCE ], + [ 1, Moves.STRUGGLE_BUG ], // Previous Stage Move, Custom [ 1, Moves.METAL_BURST ], [ 1, Moves.TACKLE ], [ 1, Moves.PROTECT ], @@ -19064,7 +19359,7 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { }, [Species.SHAYMIN]: { 1: [ - [ 1, Moves.LEAFAGE ], //Custom + [ 1, Moves.LEAFAGE ], // Custom [ 1, Moves.GROWTH ], [ 10, Moves.MAGICAL_LEAF ], [ 19, Moves.LEECH_SEED ], @@ -19166,6 +19461,10 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { [ 1, Moves.GROWL ], [ 1, Moves.WATER_GUN ], [ 1, Moves.QUICK_ATTACK ], + [ 1, Moves.ROUND ], // Previous Stage Move + [ 1, Moves.FLING ], // Previous Stage Move + [ 1, Moves.SMACK_DOWN ], // Previous Stage Move + [ 1, Moves.BOUNCE ], // Previous Stage Move [ 1, Moves.HAZE ], [ 1, Moves.ROLE_PLAY ], [ 1, Moves.NIGHT_SLASH ], From 1ad4f3b376a60f33f55a734a061d0dec9b74bc40 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 22 Oct 2024 20:11:02 -0700 Subject: [PATCH 15/24] [Test] Add `STATUS_ACTIVATION_OVERRIDE` to `overrides.ts` (#4689) This applies to Paralysis and Freeze Added Paralysis test to demonstrate usage - Consolidate `this.cancel()` calls --- src/overrides.ts | 2 + src/phases/move-phase.ts | 18 ++- src/test/data/status-effect.test.ts | 118 ++++++++++------ src/test/utils/helpers/moveHelper.ts | 36 +++-- src/test/utils/helpers/overridesHelper.ts | 157 ++++++++++++---------- 5 files changed, 203 insertions(+), 128 deletions(-) diff --git a/src/overrides.ts b/src/overrides.ts index e1bfbd240f0..6760db79205 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -75,6 +75,8 @@ class DefaultOverrides { readonly ITEM_UNLOCK_OVERRIDE: Unlockables[] = []; /** Set to `true` to show all tutorials */ readonly BYPASS_TUTORIAL_SKIP_OVERRIDE: boolean = false; + /** Set to `true` to force Paralysis and Freeze to always activate, or `false` to force them to not activate */ + readonly STATUS_ACTIVATION_OVERRIDE: boolean | null = null; // ---------------- // PLAYER OVERRIDES diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index e9d8887e9cb..66dbd06f5be 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -11,6 +11,7 @@ import { getTerrainBlockMessage } from "#app/data/weather"; import { MoveUsedEvent } from "#app/events/battle-scene"; import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; +import Overrides from "#app/overrides"; import { BattlePhase } from "#app/phases/battle-phase"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; @@ -168,10 +169,7 @@ export class MovePhase extends BattlePhase { switch (this.pokemon.status.effect) { case StatusEffect.PARALYSIS: - if (!this.pokemon.randSeedInt(4)) { - activated = true; - this.cancelled = true; - } + activated = (!this.pokemon.randSeedInt(4) || Overrides.STATUS_ACTIVATION_OVERRIDE === true) && Overrides.STATUS_ACTIVATION_OVERRIDE !== false; break; case StatusEffect.SLEEP: applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); @@ -180,16 +178,22 @@ export class MovePhase extends BattlePhase { this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value; healed = this.pokemon.status.sleepTurnsRemaining <= 0; activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); - this.cancelled = activated; break; case StatusEffect.FREEZE: - healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5); + healed = + !!this.move.getMove().findAttr((attr) => + attr instanceof HealStatusEffectAttr + && attr.selfTarget + && attr.isOfEffect(StatusEffect.FREEZE)) + || (!this.pokemon.randSeedInt(5) && Overrides.STATUS_ACTIVATION_OVERRIDE !== true) + || Overrides.STATUS_ACTIVATION_OVERRIDE === false; + activated = !healed; - this.cancelled = activated; break; } if (activated) { + this.cancel(); this.scene.queueMessage(getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.getBattlerIndex(), undefined, CommonAnim.POISON + (this.pokemon.status.effect - 1))); } else if (healed) { diff --git a/src/test/data/status-effect.test.ts b/src/test/data/status-effect.test.ts index 8b37da45d8d..1b1a97fc51f 100644 --- a/src/test/data/status-effect.test.ts +++ b/src/test/data/status-effect.test.ts @@ -8,10 +8,10 @@ import { getStatusEffectOverlapText, } from "#app/data/status-effect"; import { MoveResult } from "#app/field/pokemon"; -import GameManager from "#app/test/utils/gameManager"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; import { mockI18next } from "#test/utils/testUtils"; import i18next from "i18next"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; @@ -306,58 +306,98 @@ describe("Status Effect Messages", () => { }); }); -describe("Status Effects - Sleep", () => { - let phaserGame: Phaser.Game; - let game: GameManager; +describe("Status Effects", () => { + describe("Paralysis", () => { + let phaserGame: Phaser.Game; + let game: GameManager; - beforeAll(() => { - phaserGame = new Phaser.Game({ - type: Phaser.HEADLESS, + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override + .enemySpecies(Species.MAGIKARP) + .enemyMoveset(Moves.SPLASH) + .enemyAbility(Abilities.BALL_FETCH) + .moveset([ Moves.QUICK_ATTACK ]) + .ability(Abilities.BALL_FETCH) + .statusEffect(StatusEffect.PARALYSIS); + }); + + it("causes the pokemon's move to fail when activated", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.QUICK_ATTACK); + await game.move.forceStatusActivation(true); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.isFullHp()).toBe(true); + expect(game.scene.getPlayerPokemon()!.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); }); }); - afterEach(() => { - game.phaseInterceptor.restoreOg(); - }); + describe("Sleep", () => { + let phaserGame: Phaser.Game; + let game: GameManager; - beforeEach(() => { - game = new GameManager(phaserGame); - game.override - .moveset([ Moves.SPLASH ]) - .ability(Abilities.BALL_FETCH) - .battleType("single") - .disableCrits() - .enemySpecies(Species.MAGIKARP) - .enemyAbility(Abilities.BALL_FETCH) - .enemyMoveset(Moves.SPLASH); - }); + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); - it("should last the appropriate number of turns", async () => { - await game.classicMode.startBattle([ Species.FEEBAS ]); + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); - const player = game.scene.getPlayerPokemon()!; - player.status = new Status(StatusEffect.SLEEP, 0, 4); + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); - game.move.select(Moves.SPLASH); - await game.toNextTurn(); + it("should last the appropriate number of turns", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); - expect(player.status.effect).toBe(StatusEffect.SLEEP); + const player = game.scene.getPlayerPokemon()!; + player.status = new Status(StatusEffect.SLEEP, 0, 4); - game.move.select(Moves.SPLASH); - await game.toNextTurn(); + game.move.select(Moves.SPLASH); + await game.toNextTurn(); - expect(player.status.effect).toBe(StatusEffect.SLEEP); + expect(player.status.effect).toBe(StatusEffect.SLEEP); - game.move.select(Moves.SPLASH); - await game.toNextTurn(); + game.move.select(Moves.SPLASH); + await game.toNextTurn(); - expect(player.status.effect).toBe(StatusEffect.SLEEP); - expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); + expect(player.status.effect).toBe(StatusEffect.SLEEP); - game.move.select(Moves.SPLASH); - await game.toNextTurn(); + game.move.select(Moves.SPLASH); + await game.toNextTurn(); - expect(player.status?.effect).toBeUndefined(); - expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + expect(player.status.effect).toBe(StatusEffect.SLEEP); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(player.status?.effect).toBeUndefined(); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + }); }); }); diff --git a/src/test/utils/helpers/moveHelper.ts b/src/test/utils/helpers/moveHelper.ts index a0667d91f4c..73fe63395fd 100644 --- a/src/test/utils/helpers/moveHelper.ts +++ b/src/test/utils/helpers/moveHelper.ts @@ -1,12 +1,13 @@ import { BattlerIndex } from "#app/battle"; -import { Moves } from "#app/enums/moves"; +import Overrides from "#app/overrides"; import { CommandPhase } from "#app/phases/command-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Command } from "#app/ui/command-ui-handler"; import { Mode } from "#app/ui/ui"; +import { Moves } from "#enums/moves"; +import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { GameManagerHelper } from "#test/utils/helpers/gameManagerHelper"; import { vi } from "vitest"; -import { getMovePosition } from "../gameManagerUtils"; -import { GameManagerHelper } from "./gameManagerHelper"; /** * Helper to handle a Pokemon's move @@ -17,7 +18,7 @@ export class MoveHelper extends GameManagerHelper { * {@linkcode MoveEffectPhase.hitCheck | hitCheck}'s return value to `true`. * Used to force a move to hit. */ - async forceHit(): Promise { + public async forceHit(): Promise { await this.game.phaseInterceptor.to(MoveEffectPhase, false); vi.spyOn(this.game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true); } @@ -26,9 +27,9 @@ export class MoveHelper extends GameManagerHelper { * Intercepts {@linkcode MoveEffectPhase} and mocks the * {@linkcode MoveEffectPhase.hitCheck | hitCheck}'s return value to `false`. * Used to force a move to miss. - * @param firstTargetOnly Whether the move should force miss on the first target only, in the case of multi-target moves. + * @param firstTargetOnly - Whether the move should force miss on the first target only, in the case of multi-target moves. */ - async forceMiss(firstTargetOnly: boolean = false): Promise { + public async forceMiss(firstTargetOnly: boolean = false): Promise { await this.game.phaseInterceptor.to(MoveEffectPhase, false); const hitCheck = vi.spyOn(this.game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck"); @@ -40,12 +41,12 @@ export class MoveHelper extends GameManagerHelper { } /** - * Select the move to be used by the given Pokemon(-index). Triggers during the next {@linkcode CommandPhase} - * @param move the move to use - * @param pkmIndex the pokemon index. Relevant for double-battles only (defaults to 0) - * @param targetIndex The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves, or `null` if a manual call to `selectTarget()` is required - */ - select(move: Moves, pkmIndex: 0 | 1 = 0, targetIndex?: BattlerIndex | null) { + * Select the move to be used by the given Pokemon(-index). Triggers during the next {@linkcode CommandPhase} + * @param move - the move to use + * @param pkmIndex - the pokemon index. Relevant for double-battles only (defaults to 0) + * @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves, or `null` if a manual call to `selectTarget()` is required + */ + public select(move: Moves, pkmIndex: 0 | 1 = 0, targetIndex?: BattlerIndex | null) { const movePosition = getMovePosition(this.game.scene, pkmIndex, move); this.game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { @@ -59,4 +60,15 @@ export class MoveHelper extends GameManagerHelper { this.game.selectTarget(movePosition, targetIndex); } } + + /** + * Forces the Paralysis or Freeze status to activate on the next move by temporarily mocking {@linkcode Overrides.STATUS_ACTIVATION_OVERRIDE}, + * advancing to the next `MovePhase`, and then resetting the override to `null` + * @param activated - `true` to force the status to activate, `false` to force the status to not activate (will cause Freeze to heal) + */ + public async forceStatusActivation(activated: boolean): Promise { + vi.spyOn(Overrides, "STATUS_ACTIVATION_OVERRIDE", "get").mockReturnValue(activated); + await this.game.phaseInterceptor.to("MovePhase"); + vi.spyOn(Overrides, "STATUS_ACTIVATION_OVERRIDE", "get").mockReturnValue(null); + } } diff --git a/src/test/utils/helpers/overridesHelper.ts b/src/test/utils/helpers/overridesHelper.ts index ec4d8dbbe4c..404f5c34a26 100644 --- a/src/test/utils/helpers/overridesHelper.ts +++ b/src/test/utils/helpers/overridesHelper.ts @@ -29,7 +29,7 @@ export class OverridesHelper extends GameManagerHelper { * @warning Any event listeners that are attached to [NewArenaEvent](events\battle-scene.ts) may need to be handled down the line * @param biome the biome to set */ - startingBiome(biome: Biome): this { + public startingBiome(biome: Biome): this { this.game.scene.newArena(biome); this.log(`Starting biome set to ${Biome[biome]} (=${biome})!`); return this; @@ -38,9 +38,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the starting wave (index) * @param wave the wave (index) to set. Classic: `1`-`200` - * @returns this + * @returns `this` */ - startingWave(wave: number): this { + public startingWave(wave: number): this { vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(wave); this.log(`Starting wave set to ${wave}!`); return this; @@ -49,9 +49,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the player (pokemon) starting level * @param level the (pokemon) level to set - * @returns this + * @returns `this` */ - startingLevel(level: Species | number): this { + public startingLevel(level: Species | number): this { vi.spyOn(Overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(level); this.log(`Player Pokemon starting level set to ${level}!`); return this; @@ -62,7 +62,7 @@ export class OverridesHelper extends GameManagerHelper { * @param value the XP multiplier to set * @returns `this` */ - xpMultiplier(value: number): this { + public xpMultiplier(value: number): this { vi.spyOn(Overrides, "XP_MULTIPLIER_OVERRIDE", "get").mockReturnValue(value); this.log(`XP Multiplier set to ${value}!`); return this; @@ -71,9 +71,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the player (pokemon) starting held items * @param items the items to hold - * @returns this + * @returns `this` */ - startingHeldItems(items: ModifierOverride[]) { + 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); return this; @@ -82,9 +82,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the player (pokemon) {@linkcode Species | species} * @param species the (pokemon) {@linkcode Species | species} to set - * @returns this + * @returns `this` */ - starterSpecies(species: Species | number): this { + public starterSpecies(species: Species | number): this { vi.spyOn(Overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(species); this.log(`Player Pokemon species set to ${Species[species]} (=${species})!`); return this; @@ -92,9 +92,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the player (pokemon) to be a random fusion - * @returns this + * @returns `this` */ - enableStarterFusion(): this { + public enableStarterFusion(): this { vi.spyOn(Overrides, "STARTER_FUSION_OVERRIDE", "get").mockReturnValue(true); this.log("Player Pokemon is a random fusion!"); return this; @@ -103,9 +103,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the player (pokemon) fusion species * @param species the fusion species to set - * @returns this + * @returns `this` */ - starterFusionSpecies(species: Species | number): this { + public starterFusionSpecies(species: Species | number): this { vi.spyOn(Overrides, "STARTER_FUSION_SPECIES_OVERRIDE", "get").mockReturnValue(species); this.log(`Player Pokemon fusion species set to ${Species[species]} (=${species})!`); return this; @@ -114,9 +114,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the player (pokemons) forms * @param forms the (pokemon) forms to set - * @returns this + * @returns `this` */ - starterForms(forms: Partial>): this { + public starterForms(forms: Partial>): this { vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue(forms); const formsStr = Object.entries(forms) .map(([ speciesId, formIndex ]) => `${Species[speciesId]}=${formIndex}`) @@ -128,9 +128,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the player's starting modifiers * @param modifiers the modifiers to set - * @returns this + * @returns `this` */ - startingModifier(modifiers: ModifierOverride[]): 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; @@ -139,9 +139,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the player (pokemon) {@linkcode Abilities | ability} * @param ability the (pokemon) {@linkcode Abilities | ability} to set - * @returns this + * @returns `this` */ - ability(ability: Abilities): this { + public ability(ability: Abilities): this { vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(ability); this.log(`Player Pokemon ability set to ${Abilities[ability]} (=${ability})!`); return this; @@ -150,9 +150,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the player (pokemon) **passive** {@linkcode Abilities | ability} * @param passiveAbility the (pokemon) **passive** {@linkcode Abilities | ability} to set - * @returns this + * @returns `this` */ - passiveAbility(passiveAbility: Abilities): this { + public passiveAbility(passiveAbility: Abilities): this { vi.spyOn(Overrides, "PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(passiveAbility); this.log(`Player Pokemon PASSIVE ability set to ${Abilities[passiveAbility]} (=${passiveAbility})!`); return this; @@ -161,9 +161,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the player (pokemon) {@linkcode Moves | moves}set * @param moveset the {@linkcode Moves | moves}set to set - * @returns this + * @returns `this` */ - moveset(moveset: Moves | Moves[]): this { + public moveset(moveset: Moves | Moves[]): this { vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue(moveset); if (!Array.isArray(moveset)) { moveset = [ moveset ]; @@ -178,7 +178,7 @@ export class OverridesHelper extends GameManagerHelper { * @param statusEffect the {@linkcode StatusEffect | status-effect} to set * @returns */ - statusEffect(statusEffect: StatusEffect): this { + public statusEffect(statusEffect: StatusEffect): this { vi.spyOn(Overrides, "STATUS_OVERRIDE", "get").mockReturnValue(statusEffect); this.log(`Player Pokemon status-effect set to ${StatusEffect[statusEffect]} (=${statusEffect})!`); return this; @@ -186,9 +186,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override each wave to not have standard trainer battles - * @returns this + * @returns `this` */ - disableTrainerWaves(): this { + public disableTrainerWaves(): this { const realFn = getGameMode; vi.spyOn(GameMode, "getGameMode").mockImplementation((gameMode: GameModes) => { const mode = realFn(gameMode); @@ -201,9 +201,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override each wave to not have critical hits - * @returns this + * @returns `this` */ - disableCrits() { + public disableCrits(): this { vi.spyOn(Overrides, "NEVER_CRIT_OVERRIDE", "get").mockReturnValue(true); this.log("Critical hits are disabled!"); return this; @@ -212,9 +212,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the {@linkcode WeatherType | weather (type)} * @param type {@linkcode WeatherType | weather type} to set - * @returns this + * @returns `this` */ - weather(type: WeatherType): this { + public weather(type: WeatherType): this { vi.spyOn(Overrides, "WEATHER_OVERRIDE", "get").mockReturnValue(type); this.log(`Weather set to ${Weather[type]} (=${type})!`); return this; @@ -223,9 +223,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the seed * @param seed the seed to set - * @returns this + * @returns `this` */ - seed(seed: string): this { + public seed(seed: string): this { vi.spyOn(this.game.scene, "resetSeed").mockImplementation(() => { this.game.scene.waveSeed = seed; Phaser.Math.RND.sow([ seed ]); @@ -239,9 +239,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the battle type (single or double) * @param battleType battle type to set - * @returns this + * @returns `this` */ - battleType(battleType: "single" | "double" | null): this { + public battleType(battleType: "single" | "double" | null): this { vi.spyOn(Overrides, "BATTLE_TYPE_OVERRIDE", "get").mockReturnValue(battleType); this.log(`Battle type set to ${battleType} only!`); return this; @@ -250,9 +250,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the enemy (pokemon) {@linkcode Species | species} * @param species the (pokemon) {@linkcode Species | species} to set - * @returns this + * @returns `this` */ - enemySpecies(species: Species | number): this { + public enemySpecies(species: Species | number): this { vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(species); this.log(`Enemy Pokemon species set to ${Species[species]} (=${species})!`); return this; @@ -260,9 +260,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the enemy (pokemon) to be a random fusion - * @returns this + * @returns `this` */ - enableEnemyFusion(): this { + public enableEnemyFusion(): this { vi.spyOn(Overrides, "OPP_FUSION_OVERRIDE", "get").mockReturnValue(true); this.log("Enemy Pokemon is a random fusion!"); return this; @@ -271,9 +271,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the enemy (pokemon) fusion species * @param species the fusion species to set - * @returns this + * @returns `this` */ - enemyFusionSpecies(species: Species | number): this { + public enemyFusionSpecies(species: Species | number): this { vi.spyOn(Overrides, "OPP_FUSION_SPECIES_OVERRIDE", "get").mockReturnValue(species); this.log(`Enemy Pokemon fusion species set to ${Species[species]} (=${species})!`); return this; @@ -282,9 +282,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the enemy (pokemon) {@linkcode Abilities | ability} * @param ability the (pokemon) {@linkcode Abilities | ability} to set - * @returns this + * @returns `this` */ - enemyAbility(ability: Abilities): this { + public enemyAbility(ability: Abilities): this { vi.spyOn(Overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(ability); this.log(`Enemy Pokemon ability set to ${Abilities[ability]} (=${ability})!`); return this; @@ -293,9 +293,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the enemy (pokemon) **passive** {@linkcode Abilities | ability} * @param passiveAbility the (pokemon) **passive** {@linkcode Abilities | ability} to set - * @returns this + * @returns `this` */ - enemyPassiveAbility(passiveAbility: Abilities): this { + public enemyPassiveAbility(passiveAbility: Abilities): this { vi.spyOn(Overrides, "OPP_PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(passiveAbility); this.log(`Enemy Pokemon PASSIVE ability set to ${Abilities[passiveAbility]} (=${passiveAbility})!`); return this; @@ -304,9 +304,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the enemy (pokemon) {@linkcode Moves | moves}set * @param moveset the {@linkcode Moves | moves}set to set - * @returns this + * @returns `this` */ - enemyMoveset(moveset: Moves | Moves[]): this { + public enemyMoveset(moveset: Moves | Moves[]): this { vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue(moveset); if (!Array.isArray(moveset)) { moveset = [ moveset ]; @@ -319,9 +319,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the enemy (pokemon) level * @param level the level to set - * @returns this + * @returns `this` */ - enemyLevel(level: number): this { + public enemyLevel(level: number): this { vi.spyOn(Overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(level); this.log(`Enemy Pokemon level set to ${level}!`); return this; @@ -332,7 +332,7 @@ export class OverridesHelper extends GameManagerHelper { * @param statusEffect the {@linkcode StatusEffect | status-effect} to set * @returns */ - enemyStatusEffect(statusEffect: StatusEffect): this { + public enemyStatusEffect(statusEffect: StatusEffect): this { vi.spyOn(Overrides, "OPP_STATUS_OVERRIDE", "get").mockReturnValue(statusEffect); this.log(`Enemy Pokemon status-effect set to ${StatusEffect[statusEffect]} (=${statusEffect})!`); return this; @@ -341,9 +341,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the enemy (pokemon) held items * @param items the items to hold - * @returns this + * @returns `this` */ - enemyHeldItems(items: ModifierOverride[]) { + public enemyHeldItems(items: ModifierOverride[]): this { vi.spyOn(Overrides, "OPP_HELD_ITEMS_OVERRIDE", "get").mockReturnValue(items); this.log("Enemy Pokemon held items set to:", items); return this; @@ -354,7 +354,7 @@ export class OverridesHelper extends GameManagerHelper { * @param unlockable The Unlockable(s) to enable. * @returns `this` */ - enableUnlockable(unlockable: Unlockables[]) { + public enableUnlockable(unlockable: Unlockables[]): this { vi.spyOn(Overrides, "ITEM_UNLOCK_OVERRIDE", "get").mockReturnValue(unlockable); this.log("Temporarily unlocked the following content: ", unlockable); return this; @@ -363,9 +363,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the items rolled at the end of a battle * @param items the items to be rolled - * @returns this + * @returns `this` */ - itemRewards(items: ModifierOverride[]) { + public itemRewards(items: ModifierOverride[]): this { vi.spyOn(Overrides, "ITEM_REWARD_OVERRIDE", "get").mockReturnValue(items); this.log("Item rewards set to:", items); return this; @@ -375,8 +375,9 @@ export class OverridesHelper extends GameManagerHelper { * Override player shininess * @param shininess - `true` or `false` to force the player's pokemon to be shiny or not shiny, * `null` to disable the override and re-enable RNG shinies. + * @returns `this` */ - shiny(shininess: boolean | null): this { + public shiny(shininess: boolean | null): this { vi.spyOn(Overrides, "SHINY_OVERRIDE", "get").mockReturnValue(shininess); if (shininess === null) { this.log("Disabled player Pokemon shiny override!"); @@ -389,8 +390,9 @@ export class OverridesHelper extends GameManagerHelper { /** * Override player shiny variant * @param variant - The player's shiny variant. + * @returns `this` */ - shinyVariant(variant: Variant): this { + public shinyVariant(variant: Variant): this { vi.spyOn(Overrides, "VARIANT_OVERRIDE", "get").mockReturnValue(variant); this.log(`Set player Pokemon's shiny variant to ${variant}!`); return this; @@ -420,23 +422,38 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the enemy (Pokemon) to have the given amount of health segments * @param healthSegments the number of segments to give - * default: 0, the health segments will be handled like in the game based on wave, level and species - * 1: the Pokemon will not be a boss - * 2+: the Pokemon will be a boss with the given number of health segments - * @returns this + * - `0` (default): the health segments will be handled like in the game based on wave, level and species + * - `1`: the Pokemon will not be a boss + * - `2`+: the Pokemon will be a boss with the given number of health segments + * @returns `this` */ - enemyHealthSegments(healthSegments: number) { + public enemyHealthSegments(healthSegments: number): this { vi.spyOn(Overrides, "OPP_HEALTH_SEGMENTS_OVERRIDE", "get").mockReturnValue(healthSegments); this.log("Enemy Pokemon health segments set to:", healthSegments); return this; } + /** + * Override statuses (Paralysis and Freeze) to always or never activate + * @param activate - `true` to force activation, `false` to force no activation, `null` to disable the override + * @returns `this` + */ + public statusActivation(activate: boolean | null): this { + vi.spyOn(Overrides, "STATUS_ACTIVATION_OVERRIDE", "get").mockReturnValue(activate); + if (activate !== null) { + this.log(`Paralysis and Freeze forced to ${activate ? "always" : "never"} activate!`); + } else { + this.log("Status activation override disabled!"); + } + return this; + } + /** * Override the encounter chance for a mystery encounter. * @param percentage the encounter chance in % - * @returns spy instance + * @returns `this` */ - mysteryEncounterChance(percentage: number) { + public mysteryEncounterChance(percentage: number): this { const maxRate: number = 256; // 100% const rate = maxRate * (percentage / 100); vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(rate); @@ -446,10 +463,10 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the encounter chance for a mystery encounter. - * @returns spy instance - * @param tier + * @param tier - The {@linkcode MysteryEncounterTier} to encounter + * @returns `this` */ - mysteryEncounterTier(tier: MysteryEncounterTier) { + public mysteryEncounterTier(tier: MysteryEncounterTier): this { vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_TIER_OVERRIDE", "get").mockReturnValue(tier); this.log(`Mystery encounter tier set to ${tier}!`); return this; @@ -457,10 +474,10 @@ export class OverridesHelper extends GameManagerHelper { /** * Override the encounter that spawns for the scene - * @param encounterType - * @returns spy instance + * @param encounterType - The {@linkcode MysteryEncounterType} of the encounter + * @returns `this` */ - mysteryEncounter(encounterType: MysteryEncounterType) { + public mysteryEncounter(encounterType: MysteryEncounterType): this { vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_OVERRIDE", "get").mockReturnValue(encounterType); this.log(`Mystery encounter override set to ${encounterType}!`); return this; From a0baf892977b9d78d14b73c1c652d909b0a331a6 Mon Sep 17 00:00:00 2001 From: bjparker1226 <32974077+bjparker1226@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:04:37 -0400 Subject: [PATCH 16/24] [P2] Fix dark deal reducing transformed Pokemon's held item stack to 1 (#4707) --- src/data/mystery-encounters/encounters/dark-deal-encounter.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 7f199b5487c..2c13086ccb8 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -172,7 +172,8 @@ export const DarkDealEncounter: MysteryEncounter = isBoss: true, modifierConfigs: bossModifiers.map(m => { return { - modifier: m + modifier: m, + stackCount: m.getStackCount(), }; }) }; From 16b71943669eda5a1d3519e58e3dba5b959e9d89 Mon Sep 17 00:00:00 2001 From: Moka <54149968+MokaStitcher@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:05:13 +0200 Subject: [PATCH 17/24] [P3] Fix Egg Summary not showing new abilities in blue (#4712) --- src/ui/pokemon-info-container.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 242e59c599b..5c3a22639dd 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -279,11 +279,8 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonAbilityText.setColor(getTextColor(abilityTextStyle, false, this.scene.uiTheme)); this.pokemonAbilityText.setShadowColor(getTextColor(abilityTextStyle, true, this.scene.uiTheme)); - - const ownedAbilityAttrs = pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr; - // Check if the player owns ability for the root form - const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(ownedAbilityAttrs); + const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(starterEntry.abilityAttr); if (!playerOwnsThisAbility) { this.pokemonAbilityLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); From 03025b267464d6e306dd30fc92042fef11490c3b Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Wed, 23 Oct 2024 08:08:40 -0700 Subject: [PATCH 18/24] [P2] Fix various charge move bugs (#4595) * Add charge move classes and phase * Integrate `MoveChargePhase` in battle phase sequence * Fix Protean + charge move interaction * Fix effect chance applying to semi-invulnerability * Remove `ChargeAttr` and fix ChargeAnim loading * Restore move history entry for charge phases * Gravity now cancels Fly, etc. after charge turn * Dig integration tests * Fly integration tests * Dive integration test + fix Dive in Harsh Sun bug * Solar Beam integration tests + `CHARGING` tag fixes * Fix dive test * Electro Shot integration tests * fix import in MoveChargePhase * Electro Shot Multi Lens test * Geomancy integration tests * Fix duplicate move queue * Update import * Docs + Fix Meteor Beam being boosted by Sheer Force * Fix volt absorb test * Apply PigeonBar's suggested move-phase changes Co-authored-by: PigeonBar <56974298+PigeonBar@users.noreply.github.com> * Make Electro Shot Sheer Force boosted again * Apply PigeonBar's feedback pt. 2 * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Fix mistimed/dupe showMoveText and leftover TODO --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: PigeonBar <56974298+PigeonBar@users.noreply.github.com> --- src/data/ability.ts | 8 +- src/data/arena-tag.ts | 3 + src/data/battle-anims.ts | 31 +- src/data/battler-tags.ts | 7 +- src/data/move.ts | 390 +++++++++++++++---------- src/field/pokemon.ts | 4 +- src/phases/move-charge-phase.ts | 84 ++++++ src/phases/move-effect-phase.ts | 163 +++++------ src/phases/move-phase.ts | 59 ++-- src/test/abilities/volt_absorb.test.ts | 4 +- src/test/arena/arena_gravity.test.ts | 80 +++-- src/test/moves/dig.test.ts | 114 ++++++++ src/test/moves/dive.test.ts | 137 +++++++++ src/test/moves/electro_shot.test.ts | 104 +++++++ src/test/moves/fly.test.ts | 122 ++++++++ src/test/moves/geomancy.test.ts | 78 +++++ src/test/moves/solar_beam.test.ts | 102 +++++++ src/test/moves/whirlwind.test.ts | 3 +- 18 files changed, 1188 insertions(+), 305 deletions(-) create mode 100644 src/phases/move-charge-phase.ts create mode 100644 src/test/moves/dig.test.ts create mode 100644 src/test/moves/dive.test.ts create mode 100644 src/test/moves/electro_shot.test.ts create mode 100644 src/test/moves/fly.test.ts create mode 100644 src/test/moves/geomancy.test.ts create mode 100644 src/test/moves/solar_beam.test.ts diff --git a/src/data/ability.ts b/src/data/ability.ts index ebdd5105bb4..cc95045f8b7 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -7,7 +7,7 @@ import { Weather, WeatherType } from "./weather"; import { BattlerTag, GroundedTag } from "./battler-tags"; import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect"; import { Gender } from "./gender"; -import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move"; +import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, SacrificialAttr, SacrificialAttrOnHit, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { BerryModifier, PokemonHeldItemModifier } from "../modifier/modifier"; import { TerrainType } from "./terrain"; @@ -1139,7 +1139,9 @@ export class MoveEffectChanceMultiplierAbAttr extends AbAttr { apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { // Disable showAbility during getTargetBenefitScore this.showAbility = args[4]; - if ((args[0] as Utils.NumberHolder).value <= 0 || (args[1] as Move).id === Moves.ORDER_UP) { + + const exceptMoves = [ Moves.ORDER_UP, Moves.ELECTRO_SHOT ]; + if ((args[0] as Utils.NumberHolder).value <= 0 || exceptMoves.includes((args[1] as Move).id)) { return false; } @@ -1329,7 +1331,6 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { */ const exceptAttrs: Constructor[] = [ MultiHitAttr, - ChargeAttr, SacrificialAttr, SacrificialAttrOnHit ]; @@ -1345,6 +1346,7 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { /** Also check if this move is an Attack move and if it's only targeting one Pokemon */ return numTargets === 1 + && !move.isChargingMove() && !exceptAttrs.some(attr => move.hasAttr(attr)) && !exceptMoves.some(id => move.id === id) && move.category !== MoveCategory.STATUS; diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index aa6aec6f73a..d2c95b7ccdf 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -970,6 +970,9 @@ export class GravityTag extends ArenaTag { if (pokemon !== null) { pokemon.removeTag(BattlerTagType.FLOATING); pokemon.removeTag(BattlerTagType.TELEKINESIS); + if (pokemon.getTag(BattlerTagType.FLYING)) { + pokemon.addTag(BattlerTagType.INTERRUPTED); + } } }); } diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 03bf0809fa6..37900a3ab5a 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -1,6 +1,6 @@ //import { battleAnimRawData } from "./battle-anim-raw-data"; import BattleScene from "../battle-scene"; -import { AttackMove, BeakBlastHeaderAttr, ChargeAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move"; +import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move"; import Pokemon from "../field/pokemon"; import * as Utils from "../utils"; import { BattlerIndex } from "../battle"; @@ -476,8 +476,11 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise { } else { const loadedCheckTimer = setInterval(() => { if (moveAnims.get(move) !== null) { - const chargeAttr = allMoves[move].getAttrs(ChargeAttr)[0] || allMoves[move].getAttrs(DelayedAttackAttr)[0]; - if (chargeAttr && chargeAnims.get(chargeAttr.chargeAnim) === null) { + const chargeAnimSource = (allMoves[move].isChargingMove()) + ? allMoves[move] + : (allMoves[move].getAttrs(DelayedAttackAttr)[0] + ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]); + if (chargeAnimSource && chargeAnims.get(chargeAnimSource.chargeAnim) === null) { return; } clearInterval(loadedCheckTimer); @@ -507,11 +510,12 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise { } else { populateMoveAnim(move, ba); } - const chargeAttr = allMoves[move].getAttrs(ChargeAttr)[0] - || allMoves[move].getAttrs(DelayedAttackAttr)[0] - || allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]; - if (chargeAttr) { - initMoveChargeAnim(scene, chargeAttr.chargeAnim).then(() => resolve()); + const chargeAnimSource = (allMoves[move].isChargingMove()) + ? allMoves[move] + : (allMoves[move].getAttrs(DelayedAttackAttr)[0] + ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]); + if (chargeAnimSource) { + initMoveChargeAnim(scene, chargeAnimSource.chargeAnim).then(() => resolve()); } else { resolve(); } @@ -638,11 +642,12 @@ export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLo return new Promise(resolve => { const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat(); for (const moveId of moveIds) { - const chargeAttr = allMoves[moveId].getAttrs(ChargeAttr)[0] - || allMoves[moveId].getAttrs(DelayedAttackAttr)[0] - || allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0]; - if (chargeAttr) { - const moveChargeAnims = chargeAnims.get(chargeAttr.chargeAnim); + const chargeAnimSource = (allMoves[moveId].isChargingMove()) + ? allMoves[moveId] + : (allMoves[moveId].getAttrs(DelayedAttackAttr)[0] + ?? allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0]); + if (chargeAnimSource) { + const moveChargeAnims = chargeAnims.get(chargeAnimSource.chargeAnim); moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct? if (Array.isArray(moveChargeAnims)) { moveAnimations.push(moveChargeAnims[1]); diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index c3b7765d062..d671c56ab26 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -11,7 +11,6 @@ import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/d import Move, { allMoves, applyMoveAttrs, - ChargeAttr, ConsecutiveUseDoublePowerAttr, HealOnAllyAttr, MoveCategory, @@ -949,10 +948,6 @@ export class EncoreTag extends BattlerTag { return false; } - if (allMoves[repeatableMove.move].hasAttr(ChargeAttr) && repeatableMove.result === MoveResult.OTHER) { - return false; - } - this.moveId = repeatableMove.move; return true; @@ -2591,7 +2586,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { // This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY // Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts const moveObj = allMoves[lastMove.move]; - const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr); + const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY); const validLastMoveResult = (lastMove.result === MoveResult.SUCCESS) || (lastMove.result === MoveResult.MISS); if (lastMove.move === move && validLastMoveResult && lastMove.move !== Moves.STRUGGLE && !isUnaffected) { return true; diff --git a/src/data/move.ts b/src/data/move.ts index ec25844909e..cf88fad0ac5 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -289,10 +289,9 @@ export default class Move implements Localizable { } /** - * Getter function that returns if the move targets itself or an ally + * Getter function that returns if the move targets the user or its ally * @returns boolean */ - isAllyTarget(): boolean { switch (this.moveTarget) { case MoveTarget.USER: @@ -306,6 +305,10 @@ export default class Move implements Localizable { return false; } + isChargingMove(): this is ChargingMove { + return false; + } + /** * Checks if the move is immune to certain types. * Currently looks at cases of Grass types with powder moves and Dark types with moves affected by Prankster. @@ -893,6 +896,85 @@ export class SelfStatusMove extends Move { } } +type SubMove = new (...args: any[]) => Move; + +function ChargeMove(Base: TBase) { + return class extends Base { + /** The animation to play during the move's charging phase */ + public readonly chargeAnim: ChargeAnim = ChargeAnim[`${Moves[this.id]}_CHARGING`]; + /** The message to show during the move's charging phase */ + private _chargeText: string; + + /** Move attributes that apply during the move's charging phase */ + public chargeAttrs: MoveAttr[] = []; + + override isChargingMove(): this is ChargingMove { + return true; + } + + /** + * Sets the text to be displayed during this move's charging phase. + * References to the user Pokemon should be written as "{USER}", and + * references to the target Pokemon should be written as "{TARGET}". + * @param chargeText the text to set + * @returns this {@linkcode Move} (for chaining API purposes) + */ + chargeText(chargeText: string): this { + this._chargeText = chargeText; + return this; + } + + /** + * Queues the charge text to display to the player + * @param user the {@linkcode Pokemon} using this move + * @param target the {@linkcode Pokemon} targeted by this move (optional) + */ + showChargeText(user: Pokemon, target?: Pokemon): void { + user.scene.queueMessage(this._chargeText + .replace("{USER}", getPokemonNameWithAffix(user)) + .replace("{TARGET}", getPokemonNameWithAffix(target)) + ); + } + + /** + * Gets all charge attributes of the given attribute type. + * @param attrType any attribute that extends {@linkcode MoveAttr} + * @returns Array of attributes that match `attrType`, or an empty array if + * no matches are found. + */ + getChargeAttrs(attrType: Constructor): T[] { + return this.chargeAttrs.filter((attr): attr is T => attr instanceof attrType); + } + + /** + * Checks if this move has an attribute of the given type. + * @param attrType any attribute that extends {@linkcode MoveAttr} + * @returns `true` if a matching attribute is found; `false` otherwise + */ + hasChargeAttr(attrType: Constructor): boolean { + return this.chargeAttrs.some((attr) => attr instanceof attrType); + } + + /** + * Adds an attribute to this move to be applied during the move's charging phase + * @param ChargeAttrType the type of {@linkcode MoveAttr} being added + * @param args the parameters to construct the given {@linkcode MoveAttr} with + * @returns this {@linkcode Move} (for chaining API purposes) + */ + chargeAttr>(ChargeAttrType: T, ...args: ConstructorParameters): this { + const chargeAttr = new ChargeAttrType(...args); + this.chargeAttrs.push(chargeAttr); + + return this; + } + }; +} + +export class ChargingAttackMove extends ChargeMove(AttackMove) {} +export class ChargingSelfStatusMove extends ChargeMove(SelfStatusMove) {} + +export type ChargingMove = ChargingAttackMove | ChargingSelfStatusMove; + /** * Base class defining all {@linkcode Move} Attributes * @abstract @@ -2574,6 +2656,63 @@ export class OneHitKOAttr extends MoveAttr { } } +/** + * Attribute that allows charge moves to resolve in 1 turn under a given condition. + * Should only be used for {@linkcode ChargingMove | ChargingMoves} as a `chargeAttr`. + * @extends MoveAttr + */ +export class InstantChargeAttr extends MoveAttr { + /** The condition in which the move with this attribute instantly charges */ + protected readonly condition: UserMoveConditionFunc; + + constructor(condition: UserMoveConditionFunc) { + super(true); + this.condition = condition; + } + + /** + * Flags the move with this attribute as instantly charged if this attribute's condition is met. + * @param user the {@linkcode Pokemon} using the move + * @param target n/a + * @param move the {@linkcode Move} associated with this attribute + * @param args + * - `[0]` a {@linkcode Utils.BooleanHolder | BooleanHolder} for the "instant charge" flag + * @returns `true` if the instant charge condition is met; `false` otherwise. + */ + override apply(user: Pokemon, target: Pokemon | null, move: Move, args: any[]): boolean { + const instantCharge = args[0]; + if (!(instantCharge instanceof Utils.BooleanHolder)) { + return false; + } + + if (this.condition(user, move)) { + instantCharge.value = true; + return true; + } + return false; + } +} + +/** + * Attribute that allows charge moves to resolve in 1 turn while specific {@linkcode WeatherType | Weather} + * is active. Should only be used for {@linkcode ChargingMove | ChargingMoves} as a `chargeAttr`. + * @extends InstantChargeAttr + */ +export class WeatherInstantChargeAttr extends InstantChargeAttr { + constructor(weatherTypes: WeatherType[]) { + super((user, move) => { + const currentWeather = user.scene.arena.weather; + + if (Utils.isNullOrUndefined(currentWeather?.weatherType)) { + return false; + } else { + return !currentWeather?.isEffectSuppressed(user.scene) + && weatherTypes.includes(currentWeather?.weatherType); + } + }); + } +} + export class OverrideMoveEffectAttr extends MoveAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise { //const overridden = args[0] as Utils.BooleanHolder; @@ -2582,111 +2721,6 @@ export class OverrideMoveEffectAttr extends MoveAttr { } } -export class ChargeAttr extends OverrideMoveEffectAttr { - public chargeAnim: ChargeAnim; - private chargeText: string; - private tagType: BattlerTagType | null; - private chargeEffect: boolean; - public followUpPriority: integer | null; - - constructor(chargeAnim: ChargeAnim, chargeText: string, tagType?: BattlerTagType | null, chargeEffect: boolean = false) { - super(); - - this.chargeAnim = chargeAnim; - this.chargeText = chargeText; - this.tagType = tagType!; // TODO: is this bang correct? - this.chargeEffect = chargeEffect; - } - - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { - return new Promise(resolve => { - const lastMove = user.getLastXMoves().find(() => true); - if (!lastMove || lastMove.move !== move.id || (lastMove.result !== MoveResult.OTHER && lastMove.turn !== user.scene.currentBattle.turn)) { - (args[0] as Utils.BooleanHolder).value = true; - new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene, false, () => { - user.scene.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user))); - if (this.tagType) { - user.addTag(this.tagType, 1, move.id, user.id); - } - if (this.chargeEffect) { - applyMoveAttrs(MoveEffectAttr, user, target, move); - } - user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); - user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }); - user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id); - resolve(true); - }); - } else { - user.lapseTag(BattlerTagType.CHARGING); - resolve(false); - } - }); - } - - usedChargeEffect(user: Pokemon, target: Pokemon | null, move: Move): boolean { - if (!this.chargeEffect) { - return false; - } - // Account for move history being populated when this function is called - const lastMoves = user.getLastXMoves(2); - return lastMoves.length === 2 && lastMoves[1].move === move.id && lastMoves[1].result === MoveResult.OTHER; - } -} - -export class SunlightChargeAttr extends ChargeAttr { - constructor(chargeAnim: ChargeAnim, chargeText: string) { - super(chargeAnim, chargeText); - } - - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { - return new Promise(resolve => { - const weatherType = user.scene.arena.weather?.weatherType; - if (!user.scene.arena.weather?.isEffectSuppressed(user.scene) && (weatherType === WeatherType.SUNNY || weatherType === WeatherType.HARSH_SUN)) { - resolve(false); - } else { - super.apply(user, target, move, args).then(result => resolve(result)); - } - }); - } -} - -export class ElectroShotChargeAttr extends ChargeAttr { - private statIncreaseApplied: boolean; - constructor() { - super(ChargeAnim.ELECTRO_SHOT_CHARGING, i18next.t("moveTriggers:absorbedElectricity", { pokemonName: "{USER}" }), null, true); - // Add a flag because ChargeAttr skills use themselves twice instead of once over one-to-two turns - this.statIncreaseApplied = false; - } - - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { - return new Promise(resolve => { - const weatherType = user.scene.arena.weather?.weatherType; - if (!user.scene.arena.weather?.isEffectSuppressed(user.scene) && (weatherType === WeatherType.RAIN || weatherType === WeatherType.HEAVY_RAIN)) { - // Apply the SPATK increase every call when used in the rain - const statChangeAttr = new StatStageChangeAttr([ Stat.SPATK ], 1, true); - statChangeAttr.apply(user, target, move, args); - // After the SPATK is raised, execute the move resolution e.g. deal damage - resolve(false); - } else { - if (!this.statIncreaseApplied) { - // Apply the SPATK increase only if it hasn't been applied before e.g. on the first turn charge up animation - const statChangeAttr = new StatStageChangeAttr([ Stat.SPATK ], 1, true); - statChangeAttr.apply(user, target, move, args); - // Set the flag to true so that on the following turn it doesn't raise SPATK a second time - this.statIncreaseApplied = true; - } - super.apply(user, target, move, args).then(result => { - if (!result) { - // On the second turn, reset the statIncreaseApplied flag without applying the SPATK increase - this.statIncreaseApplied = false; - } - resolve(result); - }); - } - }); - } -} - export class DelayedAttackAttr extends OverrideMoveEffectAttr { public tagType: ArenaTagType; public chargeAnim: ChargeAnim; @@ -4878,6 +4912,37 @@ export const frenzyMissFunc: UserMoveConditionFunc = (user: Pokemon, move: Move) return true; }; +/** + * Attribute that grants {@link https://bulbapedia.bulbagarden.net/wiki/Semi-invulnerable_turn | semi-invulnerability} to the user during + * the associated move's charging phase. Should only be used for {@linkcode ChargingMove | ChargingMoves} as a `chargeAttr`. + * @extends MoveEffectAttr + */ +export class SemiInvulnerableAttr extends MoveEffectAttr { + /** The type of {@linkcode SemiInvulnerableTag} to grant to the user */ + public tagType: BattlerTagType; + + constructor(tagType: BattlerTagType) { + super(true); + this.tagType = tagType; + } + + /** + * Grants a {@linkcode SemiInvulnerableTag} to the associated move's user. + * @param user the {@linkcode Pokemon} using the move + * @param target n/a + * @param move the {@linkcode Move} being used + * @param args n/a + * @returns `true` if semi-invulnerability was successfully granted; `false` otherwise. + */ + override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { + if (!super.apply(user, target, move, args)) { + return false; + } + + return user.addTag(this.tagType, 1, move.id, user.id); + } +} + export class AddBattlerTagAttr extends MoveEffectAttr { public tagType: BattlerTagType; public turnCountMin: integer; @@ -6138,7 +6203,7 @@ const lastMoveCopiableCondition: MoveConditionFunc = (user, target, move) => { return false; } - if (allMoves[copiableMove].hasAttr(ChargeAttr)) { + if (allMoves[copiableMove].isChargingMove()) { return false; } @@ -6286,7 +6351,7 @@ const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => { return false; } - if (allMoves[copiableMove.move].hasAttr(ChargeAttr) && copiableMove.result === MoveResult.OTHER) { + if (allMoves[copiableMove.move].isChargingMove() && copiableMove.result === MoveResult.OTHER) { return false; } @@ -6985,6 +7050,20 @@ function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null }); } +function applyMoveChargeAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: ChargingMove, args: any[]): Promise { + return new Promise(resolve => { + const chargeAttrPromises: Promise[] = []; + const chargeMoveAttrs = move.chargeAttrs.filter(a => attrFilter(a)); + for (const attr of chargeMoveAttrs) { + const result = attr.apply(user, target, move, args); + if (result instanceof Promise) { + chargeAttrPromises.push(result); + } + } + Promise.allSettled(chargeAttrPromises).then(() => resolve()); + }); +} + export function applyMoveAttrs(attrType: Constructor, user: Pokemon | null, target: Pokemon | null, move: Move, ...args: any[]): Promise { return applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args); } @@ -6993,6 +7072,10 @@ export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon return applyMoveAttrsInternal(attrFilter, user, target, move, args); } +export function applyMoveChargeAttrs(attrType: Constructor, user: Pokemon | null, target: Pokemon | null, move: ChargingMove, ...args: any[]): Promise { + return applyMoveChargeAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args); +} + export class MoveCondition { protected func: MoveConditionFunc; @@ -7237,8 +7320,8 @@ export function initMoves() { new AttackMove(Moves.GUILLOTINE, Type.NORMAL, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1) .attr(OneHitKOAttr) .attr(OneHitKOAccuracyAttr), - new AttackMove(Moves.RAZOR_WIND, Type.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 1) - .attr(ChargeAttr, ChargeAnim.RAZOR_WIND_CHARGING, i18next.t("moveTriggers:whippedUpAWhirlwind", { pokemonName: "{USER}" })) + new ChargingAttackMove(Moves.RAZOR_WIND, Type.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 1) + .chargeText(i18next.t("moveTriggers:whippedUpAWhirlwind", { pokemonName: "{USER}" })) .attr(HighCritAttr) .windMove() .ignoresVirtual() @@ -7258,8 +7341,9 @@ export function initMoves() { .hidesTarget() .windMove() .partial(), // Should force random switches - new AttackMove(Moves.FLY, Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, -1, 0, 1) - .attr(ChargeAttr, ChargeAnim.FLY_CHARGING, i18next.t("moveTriggers:flewUpHigh", { pokemonName: "{USER}" }), BattlerTagType.FLYING) + new ChargingAttackMove(Moves.FLY, Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, -1, 0, 1) + .chargeText(i18next.t("moveTriggers:flewUpHigh", { pokemonName: "{USER}" })) + .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) .condition(failOnGravityCondition) .ignoresVirtual(), new AttackMove(Moves.BIND, Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, 0, 1) @@ -7408,8 +7492,9 @@ export function initMoves() { .makesContact(false) .slicingMove() .target(MoveTarget.ALL_NEAR_ENEMIES), - new AttackMove(Moves.SOLAR_BEAM, Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 1) - .attr(SunlightChargeAttr, ChargeAnim.SOLAR_BEAM_CHARGING, i18next.t("moveTriggers:tookInSunlight", { pokemonName: "{USER}" })) + new ChargingAttackMove(Moves.SOLAR_BEAM, Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 1) + .chargeText(i18next.t("moveTriggers:tookInSunlight", { pokemonName: "{USER}" })) + .chargeAttr(WeatherInstantChargeAttr, [ WeatherType.SUNNY, WeatherType.HARSH_SUN ]) .attr(AntiSunlightPowerDecreaseAttr) .ignoresVirtual(), new StatusMove(Moves.POISON_POWDER, Type.POISON, 75, 35, -1, 0, 1) @@ -7458,8 +7543,9 @@ export function initMoves() { .attr(OneHitKOAccuracyAttr) .attr(HitsTagAttr, BattlerTagType.UNDERGROUND) .makesContact(false), - new AttackMove(Moves.DIG, Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 1) - .attr(ChargeAttr, ChargeAnim.DIG_CHARGING, i18next.t("moveTriggers:dugAHole", { pokemonName: "{USER}" }), BattlerTagType.UNDERGROUND) + new ChargingAttackMove(Moves.DIG, Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 1) + .chargeText(i18next.t("moveTriggers:dugAHole", { pokemonName: "{USER}" })) + .chargeAttr(SemiInvulnerableAttr, BattlerTagType.UNDERGROUND) .ignoresVirtual(), new StatusMove(Moves.TOXIC, Type.POISON, 90, 10, -1, 0, 1) .attr(StatusEffectAttr, StatusEffect.TOXIC) @@ -7555,9 +7641,9 @@ export function initMoves() { .attr(TrapAttr, BattlerTagType.CLAMP), new AttackMove(Moves.SWIFT, Type.NORMAL, MoveCategory.SPECIAL, 60, -1, 20, -1, 0, 1) .target(MoveTarget.ALL_NEAR_ENEMIES), - new AttackMove(Moves.SKULL_BASH, Type.NORMAL, MoveCategory.PHYSICAL, 130, 100, 10, -1, 0, 1) - .attr(ChargeAttr, ChargeAnim.SKULL_BASH_CHARGING, i18next.t("moveTriggers:loweredItsHead", { pokemonName: "{USER}" }), null, true) - .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true) + new ChargingAttackMove(Moves.SKULL_BASH, Type.NORMAL, MoveCategory.PHYSICAL, 130, 100, 10, -1, 0, 1) + .chargeText(i18next.t("moveTriggers:loweredItsHead", { pokemonName: "{USER}" })) + .chargeAttr(StatStageChangeAttr, [ Stat.DEF ], 1, true) .ignoresVirtual(), new AttackMove(Moves.SPIKE_CANNON, Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 15, -1, 0, 1) .attr(MultiHitAttr) @@ -7594,8 +7680,8 @@ export function initMoves() { .triageMove(), new StatusMove(Moves.LOVELY_KISS, Type.NORMAL, 75, 10, -1, 0, 1) .attr(StatusEffectAttr, StatusEffect.SLEEP), - new AttackMove(Moves.SKY_ATTACK, Type.FLYING, MoveCategory.PHYSICAL, 140, 90, 5, 30, 0, 1) - .attr(ChargeAttr, ChargeAnim.SKY_ATTACK_CHARGING, i18next.t("moveTriggers:isGlowing", { pokemonName: "{USER}" })) + new ChargingAttackMove(Moves.SKY_ATTACK, Type.FLYING, MoveCategory.PHYSICAL, 140, 90, 5, 30, 0, 1) + .chargeText(i18next.t("moveTriggers:isGlowing", { pokemonName: "{USER}" })) .attr(HighCritAttr) .attr(FlinchAttr) .makesContact(false) @@ -8060,9 +8146,10 @@ export function initMoves() { new AttackMove(Moves.SECRET_POWER, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, 30, 0, 3) .makesContact(false) .attr(SecretPowerAttr), - new AttackMove(Moves.DIVE, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 3) - .attr(ChargeAttr, ChargeAnim.DIVE_CHARGING, i18next.t("moveTriggers:hidUnderwater", { pokemonName: "{USER}" }), BattlerTagType.UNDERWATER, true) - .attr(GulpMissileTagAttr) + new ChargingAttackMove(Moves.DIVE, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 3) + .chargeText(i18next.t("moveTriggers:hidUnderwater", { pokemonName: "{USER}" })) + .chargeAttr(SemiInvulnerableAttr, BattlerTagType.UNDERWATER) + .chargeAttr(GulpMissileTagAttr) .ignoresVirtual(), new AttackMove(Moves.ARM_THRUST, Type.FIGHTING, MoveCategory.PHYSICAL, 15, 100, 20, -1, 0, 3) .attr(MultiHitAttr), @@ -8195,8 +8282,9 @@ export function initMoves() { .attr(RechargeAttr), new SelfStatusMove(Moves.BULK_UP, Type.FIGHTING, -1, 20, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], 1, true), - new AttackMove(Moves.BOUNCE, Type.FLYING, MoveCategory.PHYSICAL, 85, 85, 5, 30, 0, 3) - .attr(ChargeAttr, ChargeAnim.BOUNCE_CHARGING, i18next.t("moveTriggers:sprangUp", { pokemonName: "{USER}" }), BattlerTagType.FLYING) + new ChargingAttackMove(Moves.BOUNCE, Type.FLYING, MoveCategory.PHYSICAL, 85, 85, 5, 30, 0, 3) + .chargeText(i18next.t("moveTriggers:sprangUp", { pokemonName: "{USER}" })) + .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) .attr(StatusEffectAttr, StatusEffect.PARALYSIS) .condition(failOnGravityCondition) .ignoresVirtual(), @@ -8551,8 +8639,9 @@ export function initMoves() { new AttackMove(Moves.OMINOUS_WIND, Type.GHOST, MoveCategory.SPECIAL, 60, 100, 5, 10, 0, 4) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true) .windMove(), - new AttackMove(Moves.SHADOW_FORCE, Type.GHOST, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4) - .attr(ChargeAttr, ChargeAnim.SHADOW_FORCE_CHARGING, i18next.t("moveTriggers:vanishedInstantly", { pokemonName: "{USER}" }), BattlerTagType.HIDDEN) + new ChargingAttackMove(Moves.SHADOW_FORCE, Type.GHOST, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4) + .chargeText(i18next.t("moveTriggers:vanishedInstantly", { pokemonName: "{USER}" })) + .chargeAttr(SemiInvulnerableAttr, BattlerTagType.HIDDEN) .ignoresProtect() .ignoresVirtual(), new SelfStatusMove(Moves.HONE_CLAWS, Type.DARK, -1, 15, -1, 0, 5) @@ -8675,12 +8764,13 @@ export function initMoves() { .attr( MovePowerMultiplierAttr, (user, target, move) => target.status || target.hasAbility(Abilities.COMATOSE) ? 2 : 1), - new AttackMove(Moves.SKY_DROP, Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5) - .partial() // Should immobilize the target, Flying types should take no damage. cf https://bulbapedia.bulbagarden.net/wiki/Sky_Drop_(move) and https://www.smogon.com/dex/sv/moves/sky-drop/ - .attr(ChargeAttr, ChargeAnim.SKY_DROP_CHARGING, i18next.t("moveTriggers:tookTargetIntoSky", { pokemonName: "{USER}", targetName: "{TARGET}" }), BattlerTagType.FLYING) // TODO: Add 2nd turn message + new ChargingAttackMove(Moves.SKY_DROP, Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5) + .chargeText(i18next.t("moveTriggers:tookTargetIntoSky", { pokemonName: "{USER}", targetName: "{TARGET}" })) + .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) .condition(failOnGravityCondition) .condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE)) - .ignoresVirtual(), + .ignoresVirtual() + .partial(), // Should immobilize the target, Flying types should take no damage. cf https://bulbapedia.bulbagarden.net/wiki/Sky_Drop_(move) and https://www.smogon.com/dex/sv/moves/sky-drop/ new SelfStatusMove(Moves.SHIFT_GEAR, Type.STEEL, -1, 10, -1, 0, 5) .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true) .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true), @@ -8830,12 +8920,12 @@ export function initMoves() { new AttackMove(Moves.FIERY_DANCE, Type.FIRE, MoveCategory.SPECIAL, 80, 100, 10, 50, 0, 5) .attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) .danceMove(), - new AttackMove(Moves.FREEZE_SHOCK, Type.ICE, MoveCategory.PHYSICAL, 140, 90, 5, 30, 0, 5) - .attr(ChargeAttr, ChargeAnim.FREEZE_SHOCK_CHARGING, i18next.t("moveTriggers:becameCloakedInFreezingLight", { pokemonName: "{USER}" })) + new ChargingAttackMove(Moves.FREEZE_SHOCK, Type.ICE, MoveCategory.PHYSICAL, 140, 90, 5, 30, 0, 5) + .chargeText(i18next.t("moveTriggers:becameCloakedInFreezingLight", { pokemonName: "{USER}" })) .attr(StatusEffectAttr, StatusEffect.PARALYSIS) .makesContact(false), - new AttackMove(Moves.ICE_BURN, Type.ICE, MoveCategory.SPECIAL, 140, 90, 5, 30, 0, 5) - .attr(ChargeAttr, ChargeAnim.ICE_BURN_CHARGING, i18next.t("moveTriggers:becameCloakedInFreezingAir", { pokemonName: "{USER}" })) + new ChargingAttackMove(Moves.ICE_BURN, Type.ICE, MoveCategory.SPECIAL, 140, 90, 5, 30, 0, 5) + .chargeText(i18next.t("moveTriggers:becameCloakedInFreezingAir", { pokemonName: "{USER}" })) .attr(StatusEffectAttr, StatusEffect.BURN) .ignoresVirtual(), new AttackMove(Moves.SNARL, Type.DARK, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5) @@ -8877,8 +8967,9 @@ export function initMoves() { .target(MoveTarget.ENEMY_SIDE), new AttackMove(Moves.FELL_STINGER, Type.BUG, MoveCategory.PHYSICAL, 50, 100, 25, -1, 0, 6) .attr(PostVictoryStatStageChangeAttr, [ Stat.ATK ], 3, true ), - new AttackMove(Moves.PHANTOM_FORCE, Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6) - .attr(ChargeAttr, ChargeAnim.PHANTOM_FORCE_CHARGING, i18next.t("moveTriggers:vanishedInstantly", { pokemonName: "{USER}" }), BattlerTagType.HIDDEN) + new ChargingAttackMove(Moves.PHANTOM_FORCE, Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6) + .chargeText(i18next.t("moveTriggers:vanishedInstantly", { pokemonName: "{USER}" })) + .chargeAttr(SemiInvulnerableAttr, BattlerTagType.HIDDEN) .ignoresProtect() .ignoresVirtual(), new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6) @@ -8988,8 +9079,8 @@ export function initMoves() { .ignoresSubstitute() .powderMove() .unimplemented(), - new SelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6) - .attr(ChargeAttr, ChargeAnim.GEOMANCY_CHARGING, i18next.t("moveTriggers:isChargingPower", { pokemonName: "{USER}" })) + new ChargingSelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6) + .chargeText(i18next.t("moveTriggers:isChargingPower", { pokemonName: "{USER}" })) .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true) .ignoresVirtual(), new StatusMove(Moves.MAGNETIC_FLUX, Type.ELECTRIC, -1, 20, -1, 0, 6) @@ -9198,8 +9289,9 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.ATK ], -1) .condition((user, target, move) => target.getStatStage(Stat.ATK) > -6) .triageMove(), - new AttackMove(Moves.SOLAR_BLADE, Type.GRASS, MoveCategory.PHYSICAL, 125, 100, 10, -1, 0, 7) - .attr(SunlightChargeAttr, ChargeAnim.SOLAR_BLADE_CHARGING, i18next.t("moveTriggers:isGlowing", { pokemonName: "{USER}" })) + new ChargingAttackMove(Moves.SOLAR_BLADE, Type.GRASS, MoveCategory.PHYSICAL, 125, 100, 10, -1, 0, 7) + .chargeText(i18next.t("moveTriggers:isGlowing", { pokemonName: "{USER}" })) + .chargeAttr(WeatherInstantChargeAttr, [ WeatherType.SUNNY, WeatherType.HARSH_SUN ]) .attr(AntiSunlightPowerDecreaseAttr) .slicingMove(), new AttackMove(Moves.LEAFAGE, Type.GRASS, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 7) @@ -9625,9 +9717,9 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.DEF ], -1, true, null, true, false, MoveEffectTrigger.HIT, false, true) .attr(MultiHitAttr) .makesContact(false), - new AttackMove(Moves.METEOR_BEAM, Type.ROCK, MoveCategory.SPECIAL, 120, 90, 10, 100, 0, 8) - .attr(ChargeAttr, ChargeAnim.METEOR_BEAM_CHARGING, i18next.t("moveTriggers:isOverflowingWithSpacePower", { pokemonName: "{USER}" }), null, true) - .attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) + new ChargingAttackMove(Moves.METEOR_BEAM, Type.ROCK, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 8) + .chargeText(i18next.t("moveTriggers:isOverflowingWithSpacePower", { pokemonName: "{USER}" })) + .chargeAttr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) .ignoresVirtual(), new AttackMove(Moves.SHELL_SIDE_ARM, Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 20, 0, 8) .attr(ShellSideArmCategoryAttr) @@ -10079,8 +10171,10 @@ export function initMoves() { .attr(IvyCudgelTypeAttr) .attr(HighCritAttr) .makesContact(false), - new AttackMove(Moves.ELECTRO_SHOT, Type.ELECTRIC, MoveCategory.SPECIAL, 130, 100, 10, 100, 0, 9) - .attr(ElectroShotChargeAttr) + new ChargingAttackMove(Moves.ELECTRO_SHOT, Type.ELECTRIC, MoveCategory.SPECIAL, 130, 100, 10, 100, 0, 9) + .chargeText(i18next.t("moveTriggers:absorbedElectricity", { pokemonName: "{USER}" })) + .chargeAttr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) + .chargeAttr(WeatherInstantChargeAttr, [ WeatherType.RAIN, WeatherType.HEAVY_RAIN ]) .ignoresVirtual(), new AttackMove(Moves.TERA_STARSTORM, Type.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) .attr(TeraMoveCategoryAttr) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index a3d7429ed9b..6c4ae3b7ff9 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3,7 +3,7 @@ import BattleScene, { AnySound } from "#app/battle-scene"; import { Variant, VariantSet, variantColorCache } from "#app/data/variant"; import { variantData } from "#app/data/variant"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr } from "#app/data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr } from "#app/data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { starterPassiveAbilities } from "#app/data/balance/passives"; @@ -2121,7 +2121,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // Trainers get a weight bump to stat buffing moves movePool = movePool.map(m => [ m[0], m[1] * (allMoves[m[0]].getAttrs(StatStageChangeAttr).some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1) ]); // Trainers get a weight decrease to multiturn moves - movePool = movePool.map(m => [ m[0], m[1] * (!!allMoves[m[0]].hasAttr(ChargeAttr) || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1) ]); + movePool = movePool.map(m => [ m[0], m[1] * (!!allMoves[m[0]].isChargingMove() || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1) ]); } // Weight towards higher power moves, by reducing the power of moves below the highest power. diff --git a/src/phases/move-charge-phase.ts b/src/phases/move-charge-phase.ts new file mode 100644 index 00000000000..d1dc340b81b --- /dev/null +++ b/src/phases/move-charge-phase.ts @@ -0,0 +1,84 @@ +import BattleScene from "#app/battle-scene"; +import { BattlerIndex } from "#app/battle"; +import { MoveChargeAnim } from "#app/data/battle-anims"; +import { applyMoveChargeAttrs, MoveEffectAttr, InstantChargeAttr } from "#app/data/move"; +import Pokemon, { MoveResult, PokemonMove } from "#app/field/pokemon"; +import { BooleanHolder } from "#app/utils"; +import { MovePhase } from "#app/phases/move-phase"; +import { PokemonPhase } from "#app/phases/pokemon-phase"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { MoveEndPhase } from "#app/phases/move-end-phase"; + +/** + * Phase for the "charging turn" of two-turn moves (e.g. Dig). + * @extends {@linkcode PokemonPhase} + */ +export class MoveChargePhase extends PokemonPhase { + /** The move instance that this phase applies */ + public move: PokemonMove; + /** The field index targeted by the move (Charging moves assume single target) */ + public targetIndex: BattlerIndex; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, targetIndex: BattlerIndex, move: PokemonMove) { + super(scene, battlerIndex); + this.move = move; + this.targetIndex = targetIndex; + } + + public override start() { + super.start(); + + const user = this.getUserPokemon(); + const target = this.getTargetPokemon(); + const move = this.move.getMove(); + + // If the target is somehow not defined, or the move is somehow not a ChargingMove, + // immediately end this phase. + if (!target || !(move.isChargingMove())) { + console.warn("Invalid parameters for MoveChargePhase"); + return super.end(); + } + + new MoveChargeAnim(move.chargeAnim, move.id, user).play(this.scene, false, () => { + move.showChargeText(user, target); + + applyMoveChargeAttrs(MoveEffectAttr, user, target, move).then(() => { + user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id); + this.end(); + }); + }); + } + + /** Checks the move's instant charge conditions, then ends this phase. */ + public override end() { + const user = this.getUserPokemon(); + const move = this.move.getMove(); + + if (move.isChargingMove()) { + const instantCharge = new BooleanHolder(false); + + applyMoveChargeAttrs(InstantChargeAttr, user, null, move, instantCharge); + + if (instantCharge.value) { + // this MoveEndPhase will be duplicated by the queued MovePhase if not removed + this.scene.tryRemovePhase((phase) => phase instanceof MoveEndPhase && phase.getPokemon() === user); + // queue a new MovePhase for this move's attack phase + this.scene.unshiftPhase(new MovePhase(this.scene, user, [ this.targetIndex ], this.move, false)); + } else { + user.getMoveQueue().push({ move: move.id, targets: [ this.targetIndex ]}); + } + + // Add this move's charging phase to the user's move history + user.pushMoveHistory({ move: this.move.moveId, targets: [ this.targetIndex ], result: MoveResult.OTHER }); + } + super.end(); + } + + public getUserPokemon(): Pokemon { + return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex]; + } + + public getTargetPokemon(): Pokemon | undefined { + return this.scene.getField(true).find((p) => this.targetIndex === p.getBattlerIndex()); + } +} diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 8d1a255d268..2b898f7d66b 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -4,7 +4,7 @@ import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr, import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag"; import { MoveAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags"; -import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, OneHitKOAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move"; +import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, OneHitKOAttr, MoveEffectTrigger, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Moves } from "#app/enums/moves"; @@ -24,10 +24,10 @@ export class MoveEffectPhase extends PokemonPhase { super(scene, battlerIndex); this.move = move; /** - * In double battles, if the right Pokemon selects a spread move and the left Pokemon dies - * with no party members available to switch in, then the right Pokemon takes the index - * of the left Pokemon and gets hit unless this is checked. - */ + * In double battles, if the right Pokemon selects a spread move and the left Pokemon dies + * with no party members available to switch in, then the right Pokemon takes the index + * of the left Pokemon and gets hit unless this is checked. + */ if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) { const i = targets.indexOf(battlerIndex); targets.splice(i, i + 1); @@ -49,9 +49,9 @@ export class MoveEffectPhase extends PokemonPhase { } /** - * Does an effect from this move override other effects on this turn? - * e.g. Charging moves (Fly, etc.) on their first turn of use. - */ + * 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 Utils.BooleanHolder(false); /** The {@linkcode Move} object from {@linkcode allMoves} invoked by this phase */ const move = this.move.getMove(); @@ -66,10 +66,10 @@ export class MoveEffectPhase extends PokemonPhase { user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); /** - * If this phase is for the first hit of the invoked move, - * resolve the move's total hit count. This block combines the - * effects of the move itself, Parental Bond, and Multi-Lens to do so. - */ + * If this phase is for the first hit of the invoked move, + * resolve the move's total hit count. This block combines the + * effects of the move itself, Parental Bond, and Multi-Lens to do so. + */ if (user.turnData.hitsLeft === -1) { const hitCount = new Utils.IntegerHolder(1); // Assume single target for multi hit @@ -86,16 +86,16 @@ export class MoveEffectPhase extends PokemonPhase { } /** - * Log to be entered into the user's move history once the move result is resolved. - * Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully - * used in the sense of "Does it have an effect on the user?". - */ + * Log to be entered into the user's move history once the move result is resolved. + * Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully + * used in the sense of "Does it have an effect on the user?". + */ const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual }; /** - * Stores results of hit checks of the invoked move against all targets, organized by battler index. - * @see {@linkcode hitCheck} - */ + * Stores results of hit checks of the invoked move against all targets, organized by battler index. + * @see {@linkcode hitCheck} + */ const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); const hasActiveTargets = targets.some(t => t.isActive(true)); @@ -104,11 +104,10 @@ export class MoveEffectPhase extends PokemonPhase { && !targets[0].getTag(SemiInvulnerableTag); /** - * If no targets are left for the move to hit (FAIL), or the invoked move is single-target - * (and not random target) and failed the hit check against its target (MISS), log the move - * as FAILed or MISSed (depending on the conditions above) and end this phase. - */ - + * If no targets are left for the move to hit (FAIL), or the invoked move is single-target + * (and not random target) and failed the hit check against its target (MISS), log the move + * as FAILed or MISSed (depending on the conditions above) and end this phase. + */ if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]] && !targets[0].getTag(ProtectedTag) && !isImmune)) { this.stopMultiHit(); if (hasActiveTargets) { @@ -154,9 +153,9 @@ export class MoveEffectPhase extends PokemonPhase { && !target.getTag(SemiInvulnerableTag); /** - * If the move missed a target, stop all future hits against that target - * and move on to the next target (if there is one). - */ + * If the move missed a target, stop all future hits against that target + * and move on to the next target (if there is one). + */ if (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()]) { this.stopMultiHit(target); this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); @@ -177,23 +176,23 @@ export class MoveEffectPhase extends PokemonPhase { } /** - * Since all fail/miss checks have applied, the move is considered successfully applied. - * It's worth noting that if the move has no effect or is protected against, this assignment - * is overwritten and the move is logged as a FAIL. - */ + * Since all fail/miss checks have applied, the move is considered successfully applied. + * It's worth noting that if the move has no effect or is protected against, this assignment + * is overwritten and the move is logged as a FAIL. + */ moveHistoryEntry.result = MoveResult.SUCCESS; /** - * Stores the result of applying the invoked move to the target. - * If the target is protected, the result is always `NO_EFFECT`. - * Otherwise, the hit result is based on type effectiveness, immunities, - * and other factors that may negate the attack or status application. - * - * Internally, the call to {@linkcode Pokemon.apply} is where damage is calculated - * (for attack moves) and the target's HP is updated. However, this isn't - * made visible to the user until the resulting {@linkcode DamagePhase} - * is invoked. - */ + * Stores the result of applying the invoked move to the target. + * If the target is protected, the result is always `NO_EFFECT`. + * Otherwise, the hit result is based on type effectiveness, immunities, + * and other factors that may negate the attack or status application. + * + * Internally, the call to {@linkcode Pokemon.apply} is where damage is calculated + * (for attack moves) and the target's HP is updated. However, this isn't + * made visible to the user until the resulting {@linkcode DamagePhase} + * is invoked. + */ const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT; /** Does {@linkcode hitResult} indicate that damage was dealt to the target? */ @@ -211,9 +210,9 @@ export class MoveEffectPhase extends PokemonPhase { } /** - * If the move has no effect on the target (i.e. the target is protected or immune), - * change the logged move result to FAIL. - */ + * If the move has no effect on the target (i.e. the target is protected or immune), + * change the logged move result to FAIL. + */ if (hitResult === HitResult.NO_EFFECT) { moveHistoryEntry.result = MoveResult.FAIL; } @@ -222,43 +221,41 @@ export class MoveEffectPhase extends PokemonPhase { const lastHit = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()); /** - * If the user can change forms by using the invoked move, - * it only changes forms after the move's last hit - * (see Relic Song's interaction with Parental Bond when used by Meloetta). - */ + * If the user can change forms by using the invoked move, + * it only changes forms after the move's last hit + * (see Relic Song's interaction with Parental Bond when used by Meloetta). + */ if (lastHit) { this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); } /** - * Create a Promise that applys *all* effects from the invoked move's MoveEffectAttrs. - * These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger - * type requires different conditions to be met with respect to the move's hit result. - */ + * Create a Promise that applys *all* effects from the invoked move's MoveEffectAttrs. + * These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger + * type requires different conditions to be met with respect to the move's hit result. + */ applyAttrs.push(new Promise(resolve => { // Apply all effects with PRE_MOVE triggers (if the target isn't immune to the move) applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && hitResult !== HitResult.NO_EFFECT, user, target, move).then(() => { // All other effects require the move to not have failed or have been cancelled to trigger if (hitResult !== HitResult.FAIL) { - /** Are the move's effects tied to the first turn of a charge move? */ - const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget() ?? null, move)); /** - * If the invoked move's effects are meant to trigger during the move's "charge turn," - * ignore all effects after this point. - * Otherwise, apply all self-targeted POST_APPLY effects. - */ - Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY - && attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move)).then(() => { + * If the invoked move's effects are meant to trigger during the move's "charge turn," + * ignore all effects after this point. + * Otherwise, apply all self-targeted POST_APPLY effects. + */ + applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY + && attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move).then(() => { // All effects past this point require the move to have hit the target if (hitResult !== HitResult.NO_EFFECT) { // Apply all non-self-targeted POST_APPLY effects applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY && !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, this.move.getMove()).then(() => { /** - * If the move hit, and the target doesn't have Shield Dust, - * apply the chance to flinch the target gained from King's Rock - */ + * If the move hit, and the target doesn't have Shield Dust, + * apply the chance to flinch the target gained from King's Rock + */ if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !move.hitsSubstitute(user, target)) { const flinched = new Utils.BooleanHolder(false); user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); @@ -267,7 +264,7 @@ export class MoveEffectPhase extends PokemonPhase { } } // If the move was not protected against, apply all HIT effects - Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT + Utils.executeIf(!isProtected, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && (!attr.firstTargetOnly || firstTarget), user, target, this.move.getMove()).then(() => { // Apply the target's post-defend ability effects (as long as the target is active or can otherwise apply them) return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => { @@ -286,9 +283,9 @@ export class MoveEffectPhase extends PokemonPhase { // Apply the user's post-attack ability effects applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => { /** - * If the invoked move is an attack, apply the user's chance to - * steal an item from the target granted by Grip Claw - */ + * If the invoked move is an attack, apply the user's chance to + * steal an item from the target granted by Grip Claw + */ if (this.move.getMove() instanceof AttackMove) { this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); } @@ -343,12 +340,12 @@ export class MoveEffectPhase extends PokemonPhase { end() { const user = this.getUserPokemon(); /** - * If this phase isn't for the invoked move's last strike, - * unshift another MoveEffectPhase for the next strike. - * Otherwise, queue a message indicating the number of times the move has struck - * (if the move has struck more than once), then apply the heal from Shell Bell - * to the user. - */ + * If this phase isn't for the invoked move's last strike, + * unshift another MoveEffectPhase for the next strike. + * Otherwise, queue a message indicating the number of times the move has struck + * (if the move has struck more than once), then apply the heal from Shell Bell + * to the user. + */ if (user) { if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) { this.scene.unshiftPhase(this.getNewHitPhase()); @@ -447,9 +444,9 @@ export class MoveEffectPhase extends PokemonPhase { } /** - * Removes the given {@linkcode Pokemon} from this phase's target list - * @param target {@linkcode Pokemon} the Pokemon to be removed - */ + * Removes the given {@linkcode Pokemon} from this phase's target list + * @param target {@linkcode Pokemon} the Pokemon to be removed + */ removeTarget(target: Pokemon): void { const targetIndex = this.targets.findIndex(ind => ind === target.getBattlerIndex()); if (targetIndex !== -1) { @@ -458,19 +455,19 @@ export class MoveEffectPhase extends PokemonPhase { } /** - * Prevents subsequent strikes of this phase's invoked move from occurring - * @param target {@linkcode Pokemon} if defined, only stop subsequent - * strikes against this Pokemon - */ + * Prevents subsequent strikes of this phase's invoked move from occurring + * @param target {@linkcode Pokemon} if defined, only stop subsequent + * strikes against this Pokemon + */ stopMultiHit(target?: Pokemon): void { /** If given a specific target, remove the target from subsequent strikes */ if (target) { this.removeTarget(target); } /** - * If no target specified, or the specified target was the last of this move's - * targets, completely cancel all subsequent strikes. - */ + * If no target specified, or the specified target was the last of this move's + * targets, completely cancel all subsequent strikes. + */ if (!target || this.targets.length === 0 ) { this.getUserPokemon()!.turnData.hitCount = 1; // TODO: is the bang correct here? this.getUserPokemon()!.turnData.hitsLeft = 1; // TODO: is the bang correct here? diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 66dbd06f5be..a516fd8593d 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -3,13 +3,13 @@ import BattleScene from "#app/battle-scene"; import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr, ReduceStatusEffectDurationAbAttr } from "#app/data/ability"; import { CommonAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags"; -import { allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, ChargeAttr, CopyMoveAttr, HealStatusEffectAttr, MoveFlags, PreMoveMessageAttr } from "#app/data/move"; +import { allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, CopyMoveAttr, HealStatusEffectAttr, MoveFlags, PreMoveMessageAttr } from "#app/data/move"; import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; import { Type } from "#app/data/type"; import { getTerrainBlockMessage } from "#app/data/weather"; import { MoveUsedEvent } from "#app/events/battle-scene"; -import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon"; +import Pokemon, { MoveResult, PokemonMove } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { BattlePhase } from "#app/phases/battle-phase"; @@ -23,6 +23,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; +import { MoveChargePhase } from "#app/phases/move-charge-phase"; export class MovePhase extends BattlePhase { protected _pokemon: Pokemon; @@ -135,6 +136,8 @@ export class MovePhase extends BattlePhase { if (this.cancelled || this.failed) { this.handlePreMoveFailures(); + } else if (this.move.getMove().isChargingMove() && !this.pokemon.getTag(BattlerTagType.CHARGING)) { + this.chargeMove(); } else { this.useMove(); } @@ -226,12 +229,15 @@ export class MovePhase extends BattlePhase { this.showMoveText(); - // TODO: Clean up implementation of two-turn moves. if (moveQueue.length > 0) { // Using .shift here clears out two turn moves once they've been used this.ignorePp = moveQueue.shift()?.ignorePP ?? false; } + if (this.pokemon.getTag(BattlerTagType.CHARGING)?.sourceMove === this.move.moveId) { + this.pokemon.lapseTag(BattlerTagType.CHARGING); + } + // "commit" to using the move, deducting PP. if (!this.ignorePp) { const ppUsed = 1 + this.getPpIncreaseFromPressure(targets); @@ -295,6 +301,9 @@ export class MovePhase extends BattlePhase { } this.showFailedText(failedText); + + // Remove the user from its semi-invulnerable state (if applicable) + this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); } // Handle Dancer, which triggers immediately after a move is used (rather than waiting on `this.end()`). @@ -306,6 +315,35 @@ export class MovePhase extends BattlePhase { } } + /** Queues a {@linkcode MoveChargePhase} for this phase's invoked move. */ + protected chargeMove() { + const move = this.move.getMove(); + const targets = this.getActiveTargetPokemon(); + + if (move.applyConditions(this.pokemon, targets[0], move)) { + // Protean and Libero apply on the charging turn of charge moves + applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); + + this.showMoveText(); + this.scene.unshiftPhase(new MoveChargePhase(this.scene, this.pokemon.getBattlerIndex(), this.targets[0], this.move)); + } else { + this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual }); + + let failedText: string | undefined; + const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new BooleanHolder(false)); + + if (failureMessage) { + failedText = failureMessage; + } + + this.showMoveText(); + this.showFailedText(failedText); + + // Remove the user from its semi-invulnerable state (if applicable) + this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); + } + } + /** * Queues a {@linkcode MoveEndPhase} if the move wasn't a {@linkcode followUp} and {@linkcode canMove()} returns `true`, * then ends the phase. @@ -419,8 +457,6 @@ export class MovePhase extends BattlePhase { * - Lapses `AFTER_MOVE` tags: * - This handles the effects of {@link Moves.SUBSTITUTE Substitute} * - Removes the second turn of charge moves - * - * TODO: handle charge moves more gracefully */ protected handlePreMoveFailures(): void { if (this.cancelled || this.failed) { @@ -452,18 +488,7 @@ export class MovePhase extends BattlePhase { return; } - if (this.move.getMove().hasAttr(ChargeAttr)) { - const lastMove = this.pokemon.getLastXMoves() as TurnMove[]; - if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER) { - this.scene.queueMessage(i18next.t("battle:useMove", { - pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), - moveName: this.move.getName() - }), 500); - return; - } - } - - if (this.pokemon.getTag(BattlerTagType.RECHARGING || BattlerTagType.INTERRUPTED)) { + if (this.pokemon.getTag(BattlerTagType.RECHARGING) || this.pokemon.getTag(BattlerTagType.INTERRUPTED)) { return; } diff --git a/src/test/abilities/volt_absorb.test.ts b/src/test/abilities/volt_absorb.test.ts index ec82b00ec5a..4fee7653b99 100644 --- a/src/test/abilities/volt_absorb.test.ts +++ b/src/test/abilities/volt_absorb.test.ts @@ -52,6 +52,7 @@ describe("Abilities - Volt Absorb", () => { expect(playerPokemon.getTag(BattlerTagType.CHARGED)).toBeDefined(); expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase"); }); + it("should activate regardless of accuracy checks", async () => { game.override.moveset(Moves.THUNDERBOLT); game.override.enemyMoveset(Moves.SPLASH); @@ -71,6 +72,7 @@ describe("Abilities - Volt Absorb", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); }); + it("regardless of accuracy should not trigger on pokemon in semi invulnerable state", async () => { game.override.moveset(Moves.THUNDERBOLT); game.override.enemyMoveset(Moves.DIVE); @@ -84,9 +86,7 @@ describe("Abilities - Volt Absorb", () => { game.move.select(Moves.THUNDERBOLT); enemyPokemon.hp = enemyPokemon.hp - 1; await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); - await game.phaseInterceptor.to("MoveEffectPhase"); - await game.move.forceMiss(); await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); }); diff --git a/src/test/arena/arena_gravity.test.ts b/src/test/arena/arena_gravity.test.ts index b6982896571..13e9c23a35c 100644 --- a/src/test/arena/arena_gravity.test.ts +++ b/src/test/arena/arena_gravity.test.ts @@ -1,8 +1,8 @@ +import { BattlerIndex } from "#app/battle"; import { allMoves } from "#app/data/move"; -import { Abilities } from "#app/enums/abilities"; -import { ArenaTagType } from "#app/enums/arena-tag-type"; -import { MoveEffectPhase } from "#app/phases/move-effect-phase"; -import { TurnEndPhase } from "#app/phases/turn-end-phase"; +import { Abilities } from "#enums/abilities"; +import { ArenaTagType } from "#enums/arena-tag-type"; +import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; @@ -31,7 +31,8 @@ describe("Arena - Gravity", () => { .ability(Abilities.UNNERVE) .enemyAbility(Abilities.BALL_FETCH) .enemySpecies(Species.SHUCKLE) - .enemyMoveset(Moves.SPLASH); + .enemyMoveset(Moves.SPLASH) + .enemyLevel(5); }); // Reference: https://bulbapedia.bulbagarden.net/wiki/Gravity_(move) @@ -42,102 +43,121 @@ describe("Arena - Gravity", () => { vi.spyOn(moveToCheck, "calculateBattleAccuracy"); // Setup Gravity on first turn - await game.startBattle([ Species.PIKACHU ]); + await game.classicMode.startBattle([ Species.PIKACHU ]); game.move.select(Moves.GRAVITY); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); expect(game.scene.arena.getTag(ArenaTagType.GRAVITY)).toBeDefined(); // Use non-OHKO move on second turn await game.toNextTurn(); game.move.select(Moves.TACKLE); - await game.phaseInterceptor.to(MoveEffectPhase); + await game.phaseInterceptor.to("MoveEffectPhase"); - expect(moveToCheck.calculateBattleAccuracy).toHaveReturnedWith(100 * 1.67); + expect(moveToCheck.calculateBattleAccuracy).toHaveLastReturnedWith(100 * 1.67); }); it("OHKO move accuracy is not affected", async () => { - game.override.startingLevel(5); - game.override.enemyLevel(5); - /** See Fissure {@link https://bulbapedia.bulbagarden.net/wiki/Fissure_(move)} */ const moveToCheck = allMoves[Moves.FISSURE]; vi.spyOn(moveToCheck, "calculateBattleAccuracy"); // Setup Gravity on first turn - await game.startBattle([ Species.PIKACHU ]); + await game.classicMode.startBattle([ Species.PIKACHU ]); game.move.select(Moves.GRAVITY); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); expect(game.scene.arena.getTag(ArenaTagType.GRAVITY)).toBeDefined(); // Use OHKO move on second turn await game.toNextTurn(); game.move.select(Moves.FISSURE); - await game.phaseInterceptor.to(MoveEffectPhase); + await game.phaseInterceptor.to("MoveEffectPhase"); - expect(moveToCheck.calculateBattleAccuracy).toHaveReturnedWith(30); + expect(moveToCheck.calculateBattleAccuracy).toHaveLastReturnedWith(30); }); describe("Against flying types", () => { it("can be hit by ground-type moves now", async () => { game.override - .startingLevel(5) - .enemyLevel(5) .enemySpecies(Species.PIDGEOT) .moveset([ Moves.GRAVITY, Moves.EARTHQUAKE ]); - await game.startBattle([ Species.PIKACHU ]); + await game.classicMode.startBattle([ Species.PIKACHU ]); const pidgeot = game.scene.getEnemyPokemon()!; vi.spyOn(pidgeot, "getAttackTypeEffectiveness"); // Try earthquake on 1st turn (fails!); game.move.select(Moves.EARTHQUAKE); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); - expect(pidgeot.getAttackTypeEffectiveness).toHaveReturnedWith(0); + expect(pidgeot.getAttackTypeEffectiveness).toHaveLastReturnedWith(0); // Setup Gravity on 2nd turn await game.toNextTurn(); game.move.select(Moves.GRAVITY); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); expect(game.scene.arena.getTag(ArenaTagType.GRAVITY)).toBeDefined(); // Use ground move on 3rd turn await game.toNextTurn(); game.move.select(Moves.EARTHQUAKE); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); - expect(pidgeot.getAttackTypeEffectiveness).toHaveReturnedWith(1); + expect(pidgeot.getAttackTypeEffectiveness).toHaveLastReturnedWith(1); }); it("keeps super-effective moves super-effective after using gravity", async () => { game.override - .startingLevel(5) - .enemyLevel(5) .enemySpecies(Species.PIDGEOT) .moveset([ Moves.GRAVITY, Moves.THUNDERBOLT ]); - await game.startBattle([ Species.PIKACHU ]); + await game.classicMode.startBattle([ Species.PIKACHU ]); const pidgeot = game.scene.getEnemyPokemon()!; vi.spyOn(pidgeot, "getAttackTypeEffectiveness"); // Setup Gravity on 1st turn game.move.select(Moves.GRAVITY); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); expect(game.scene.arena.getTag(ArenaTagType.GRAVITY)).toBeDefined(); // Use electric move on 2nd turn await game.toNextTurn(); game.move.select(Moves.THUNDERBOLT); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); - expect(pidgeot.getAttackTypeEffectiveness).toHaveReturnedWith(2); + expect(pidgeot.getAttackTypeEffectiveness).toHaveLastReturnedWith(2); }); }); + + it("cancels Fly if its user is semi-invulnerable", async () => { + game.override + .enemySpecies(Species.SNORLAX) + .enemyMoveset(Moves.FLY) + .moveset([ Moves.GRAVITY, Moves.SPLASH ]); + + await game.classicMode.startBattle([ Species.CHARIZARD ]); + + const charizard = game.scene.getPlayerPokemon()!; + const snorlax = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.SPLASH); + + await game.toNextTurn(); + expect(snorlax.getTag(BattlerTagType.FLYING)).toBeDefined(); + + game.move.select(Moves.GRAVITY); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + + await game.phaseInterceptor.to("MoveEffectPhase"); + expect(snorlax.getTag(BattlerTagType.INTERRUPTED)).toBeDefined(); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(charizard.hp).toBe(charizard.getMaxHp()); + }); }); diff --git a/src/test/moves/dig.test.ts b/src/test/moves/dig.test.ts new file mode 100644 index 00000000000..4c6b5d3b75d --- /dev/null +++ b/src/test/moves/dig.test.ts @@ -0,0 +1,114 @@ +import { BattlerIndex } from "#app/battle"; +import { allMoves } from "#app/data/move"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import { MoveResult } from "#app/field/pokemon"; +import { describe, beforeAll, afterEach, beforeEach, it, expect } from "vitest"; +import GameManager from "#test/utils/gameManager"; + +describe("Moves - Dig", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset(Moves.DIG) + .battleType("single") + .startingLevel(100) + .enemySpecies(Species.SNORLAX) + .enemyLevel(100) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.TACKLE); + }); + + it("should make the user semi-invulnerable, then attack over 2 turns", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.DIG); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.UNDERGROUND)).toBeDefined(); + expect(enemyPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.MISS); + expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp()); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + expect(playerPokemon.getMoveQueue()[0].move).toBe(Moves.DIG); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.UNDERGROUND)).toBeUndefined(); + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + expect(playerPokemon.getMoveHistory()).toHaveLength(2); + + const playerDig = playerPokemon.getMoveset().find(mv => mv && mv.moveId === Moves.DIG); + expect(playerDig?.ppUsed).toBe(1); + }); + + it("should not allow the user to evade attacks from Pokemon with No Guard", async () => { + game.override.enemyAbility(Abilities.NO_GUARD); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.DIG); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.hp).toBeLessThan(playerPokemon.getMaxHp()); + expect(enemyPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + }); + + it("should not expend PP when the attack phase is cancelled", async () => { + game.override + .enemyAbility(Abilities.NO_GUARD) + .enemyMoveset(Moves.SPORE); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.DIG); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.UNDERGROUND)).toBeUndefined(); + expect(playerPokemon.status?.effect).toBe(StatusEffect.SLEEP); + + const playerDig = playerPokemon.getMoveset().find(mv => mv && mv.moveId === Moves.DIG); + expect(playerDig?.ppUsed).toBe(0); + }); + + it("should cause the user to take double damage from Earthquake", async () => { + await game.classicMode.startBattle([ Species.DONDOZO ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + const preDigEarthquakeDmg = playerPokemon.getAttackDamage(enemyPokemon, allMoves[Moves.EARTHQUAKE]).damage; + + game.move.select(Moves.DIG); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + + await game.phaseInterceptor.to("MoveEffectPhase"); + + const postDigEarthquakeDmg = playerPokemon.getAttackDamage(enemyPokemon, allMoves[Moves.EARTHQUAKE]).damage; + // these hopefully get avoid rounding errors :shrug: + expect(postDigEarthquakeDmg).toBeGreaterThanOrEqual(2 * preDigEarthquakeDmg); + expect(postDigEarthquakeDmg).toBeLessThan(2 * (preDigEarthquakeDmg + 1)); + }); +}); diff --git a/src/test/moves/dive.test.ts b/src/test/moves/dive.test.ts new file mode 100644 index 00000000000..b60416d7740 --- /dev/null +++ b/src/test/moves/dive.test.ts @@ -0,0 +1,137 @@ +import { BattlerTagType } from "#enums/battler-tag-type"; +import { StatusEffect } from "#enums/status-effect"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest"; +import { WeatherType } from "#enums/weather-type"; + +describe("Moves - Dive", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset(Moves.DIVE) + .battleType("single") + .startingLevel(100) + .enemySpecies(Species.SNORLAX) + .enemyLevel(100) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.TACKLE); + }); + + it("should make the user semi-invulnerable, then attack over 2 turns", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.DIVE); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.UNDERWATER)).toBeDefined(); + expect(enemyPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.MISS); + expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp()); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + expect(playerPokemon.getMoveQueue()[0].move).toBe(Moves.DIVE); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.UNDERWATER)).toBeUndefined(); + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + expect(playerPokemon.getMoveHistory()).toHaveLength(2); + + const playerDive = playerPokemon.getMoveset().find(mv => mv && mv.moveId === Moves.DIVE); + expect(playerDive?.ppUsed).toBe(1); + }); + + it("should not allow the user to evade attacks from Pokemon with No Guard", async () => { + game.override.enemyAbility(Abilities.NO_GUARD); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.DIVE); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.hp).toBeLessThan(playerPokemon.getMaxHp()); + expect(enemyPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + }); + + it("should not expend PP when the attack phase is cancelled", async () => { + game.override + .enemyAbility(Abilities.NO_GUARD) + .enemyMoveset(Moves.SPORE); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.DIVE); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.UNDERWATER)).toBeUndefined(); + expect(playerPokemon.status?.effect).toBe(StatusEffect.SLEEP); + + const playerDive = playerPokemon.getMoveset().find(mv => mv && mv.moveId === Moves.DIVE); + expect(playerDive?.ppUsed).toBe(0); + }); + + it("should trigger on-contact post-defend ability effects", async () => { + game.override + .enemyAbility(Abilities.ROUGH_SKIN) + .enemyMoveset(Moves.SPLASH); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.DIVE); + + await game.phaseInterceptor.to("TurnEndPhase"); + + await game.phaseInterceptor.to("MoveEndPhase"); + expect(playerPokemon.hp).toBeLessThan(playerPokemon.getMaxHp()); + expect(enemyPokemon.battleData.abilitiesApplied[0]).toBe(Abilities.ROUGH_SKIN); + }); + + it("should cancel attack after Harsh Sunlight is set", async () => { + game.override.enemyMoveset(Moves.SPLASH); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.DIVE); + + await game.phaseInterceptor.to("TurnEndPhase"); + await game.phaseInterceptor.to("TurnStartPhase", false); + game.scene.arena.trySetWeather(WeatherType.HARSH_SUN, false); + + await game.phaseInterceptor.to("MoveEndPhase"); + expect(playerPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + expect(playerPokemon.getTag(BattlerTagType.UNDERWATER)).toBeUndefined(); + + const playerDive = playerPokemon.getMoveset().find(mv => mv && mv.moveId === Moves.DIVE); + expect(playerDive?.ppUsed).toBe(1); + }); +}); diff --git a/src/test/moves/electro_shot.test.ts b/src/test/moves/electro_shot.test.ts new file mode 100644 index 00000000000..1373b4941eb --- /dev/null +++ b/src/test/moves/electro_shot.test.ts @@ -0,0 +1,104 @@ +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Stat } from "#enums/stat"; +import { WeatherType } from "#enums/weather-type"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest"; + +describe("Moves - Electro Shot", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset(Moves.ELECTRO_SHOT) + .battleType("single") + .startingLevel(100) + .enemySpecies(Species.SNORLAX) + .enemyLevel(100) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should increase the user's Sp. Atk on the first turn, then attack on the second turn", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.ELECTRO_SHOT); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.CHARGING)).toBeDefined(); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + expect(playerPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.OTHER); + expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.CHARGING)).toBeUndefined(); + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + expect(playerPokemon.getMoveHistory()).toHaveLength(2); + expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1); + expect(playerPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + + const playerElectroShot = playerPokemon.getMoveset().find(mv => mv && mv.moveId === Moves.ELECTRO_SHOT); + expect(playerElectroShot?.ppUsed).toBe(1); + }); + + it.each([ + { weatherType: WeatherType.RAIN, name: "Rain" }, + { weatherType: WeatherType.HEAVY_RAIN, name: "Heavy Rain" } + ])("should fully resolve in one turn if $name is active", async ({ weatherType }) => { + game.override.weather(weatherType); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.ELECTRO_SHOT); + + await game.phaseInterceptor.to("MoveEffectPhase", false); + expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1); + + await game.phaseInterceptor.to("MoveEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.CHARGING)).toBeUndefined(); + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + expect(playerPokemon.getMoveHistory()).toHaveLength(2); + expect(playerPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + + const playerElectroShot = playerPokemon.getMoveset().find(mv => mv && mv.moveId === Moves.ELECTRO_SHOT); + expect(playerElectroShot?.ppUsed).toBe(1); + }); + + it("should only increase Sp. Atk once with Multi-Lens", async () => { + game.override + .weather(WeatherType.RAIN) + .startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.ELECTRO_SHOT); + + await game.phaseInterceptor.to("MoveEndPhase"); + expect(playerPokemon.turnData.hitCount).toBe(2); + expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1); + }); +}); diff --git a/src/test/moves/fly.test.ts b/src/test/moves/fly.test.ts new file mode 100644 index 00000000000..6ae758fe3dc --- /dev/null +++ b/src/test/moves/fly.test.ts @@ -0,0 +1,122 @@ +import { BattlerTagType } from "#enums/battler-tag-type"; +import { StatusEffect } from "#enums/status-effect"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; +import { BattlerIndex } from "#app/battle"; +import { allMoves } from "#app/data/move"; + +describe("Moves - Fly", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset(Moves.FLY) + .battleType("single") + .startingLevel(100) + .enemySpecies(Species.SNORLAX) + .enemyLevel(100) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.TACKLE); + + vi.spyOn(allMoves[Moves.FLY], "accuracy", "get").mockReturnValue(100); + }); + + it("should make the user semi-invulnerable, then attack over 2 turns", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.FLY); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.FLYING)).toBeDefined(); + expect(enemyPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.MISS); + expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp()); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + expect(playerPokemon.getMoveQueue()[0].move).toBe(Moves.FLY); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.FLYING)).toBeUndefined(); + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + expect(playerPokemon.getMoveHistory()).toHaveLength(2); + + const playerFly = playerPokemon.getMoveset().find(mv => mv && mv.moveId === Moves.FLY); + expect(playerFly?.ppUsed).toBe(1); + }); + + it("should not allow the user to evade attacks from Pokemon with No Guard", async () => { + game.override.enemyAbility(Abilities.NO_GUARD); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.FLY); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.hp).toBeLessThan(playerPokemon.getMaxHp()); + expect(enemyPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + }); + + it("should not expend PP when the attack phase is cancelled", async () => { + game.override + .enemyAbility(Abilities.NO_GUARD) + .enemyMoveset(Moves.SPORE); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.FLY); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.FLYING)).toBeUndefined(); + expect(playerPokemon.status?.effect).toBe(StatusEffect.SLEEP); + + const playerFly = playerPokemon.getMoveset().find(mv => mv && mv.moveId === Moves.FLY); + expect(playerFly?.ppUsed).toBe(0); + }); + + it("should be cancelled when another Pokemon uses Gravity", async () => { + game.override.enemyMoveset([ Moves.SPLASH, Moves.GRAVITY ]); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.FLY); + + await game.forceEnemyMove(Moves.SPLASH); + + await game.toNextTurn(); + await game.forceEnemyMove(Moves.GRAVITY); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + + const playerFly = playerPokemon.getMoveset().find(mv => mv && mv.moveId === Moves.FLY); + expect(playerFly?.ppUsed).toBe(0); + }); +}); diff --git a/src/test/moves/geomancy.test.ts b/src/test/moves/geomancy.test.ts new file mode 100644 index 00000000000..6e2f40b9144 --- /dev/null +++ b/src/test/moves/geomancy.test.ts @@ -0,0 +1,78 @@ +import { EffectiveStat, Stat } from "#enums/stat"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest"; + +describe("Moves - Geomancy", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset(Moves.GEOMANCY) + .battleType("single") + .startingLevel(100) + .enemySpecies(Species.SNORLAX) + .enemyLevel(100) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should boost the user's stats on the second turn of use", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const player = game.scene.getPlayerPokemon()!; + const affectedStats: EffectiveStat[] = [ Stat.SPATK, Stat.SPDEF, Stat.SPD ]; + + game.move.select(Moves.GEOMANCY); + + await game.phaseInterceptor.to("TurnEndPhase"); + affectedStats.forEach((stat) => expect(player.getStatStage(stat)).toBe(0)); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.OTHER); + + await game.phaseInterceptor.to("TurnEndPhase"); + affectedStats.forEach((stat) => expect(player.getStatStage(stat)).toBe(2)); + expect(player.getMoveHistory()).toHaveLength(2); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + + const playerGeomancy = player.getMoveset().find((mv) => mv && mv.moveId === Moves.GEOMANCY); + expect(playerGeomancy?.ppUsed).toBe(1); + }); + + it("should execute over 2 turns between waves", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const player = game.scene.getPlayerPokemon()!; + const affectedStats: EffectiveStat[] = [ Stat.SPATK, Stat.SPDEF, Stat.SPD ]; + + game.move.select(Moves.GEOMANCY); + + await game.phaseInterceptor.to("MoveEndPhase", false); + await game.doKillOpponents(); + + await game.toNextWave(); + + await game.phaseInterceptor.to("TurnEndPhase"); + affectedStats.forEach((stat) => expect(player.getStatStage(stat)).toBe(2)); + expect(player.getMoveHistory()).toHaveLength(2); + expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + + const playerGeomancy = player.getMoveset().find((mv) => mv && mv.moveId === Moves.GEOMANCY); + expect(playerGeomancy?.ppUsed).toBe(1); + }); +}); diff --git a/src/test/moves/solar_beam.test.ts b/src/test/moves/solar_beam.test.ts new file mode 100644 index 00000000000..ebec338932a --- /dev/null +++ b/src/test/moves/solar_beam.test.ts @@ -0,0 +1,102 @@ +import { allMoves } from "#app/data/move"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { WeatherType } from "#enums/weather-type"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; + +describe("Moves - Solar Beam", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset(Moves.SOLAR_BEAM) + .battleType("single") + .startingLevel(100) + .enemySpecies(Species.SNORLAX) + .enemyLevel(100) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should deal damage in two turns if no weather is active", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.SOLAR_BEAM); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.CHARGING)).toBeDefined(); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + expect(playerPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.OTHER); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.CHARGING)).toBeUndefined(); + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + expect(playerPokemon.getMoveHistory()).toHaveLength(2); + expect(playerPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + + const playerSolarBeam = playerPokemon.getMoveset().find(mv => mv && mv.moveId === Moves.SOLAR_BEAM); + expect(playerSolarBeam?.ppUsed).toBe(1); + }); + + it.each([ + { weatherType: WeatherType.SUNNY, name: "Sun" }, + { weatherType: WeatherType.HARSH_SUN, name: "Harsh Sun" } + ])("should deal damage in one turn if $name is active", async ({ weatherType }) => { + game.override.weather(weatherType); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.SOLAR_BEAM); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getTag(BattlerTagType.CHARGING)).toBeUndefined(); + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + expect(playerPokemon.getMoveHistory()).toHaveLength(2); + expect(playerPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); + + const playerSolarBeam = playerPokemon.getMoveset().find(mv => mv && mv.moveId === Moves.SOLAR_BEAM); + expect(playerSolarBeam?.ppUsed).toBe(1); + }); + + it.each([ + { weatherType: WeatherType.RAIN, name: "Rain" }, + { weatherType: WeatherType.HEAVY_RAIN, name: "Heavy Rain" } + ])("should have its power halved in $name", async ({ weatherType }) => { + game.override.weather(weatherType); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const solarBeam = allMoves[Moves.SOLAR_BEAM]; + + vi.spyOn(solarBeam, "calculateBattlePower"); + + game.move.select(Moves.SOLAR_BEAM); + + await game.phaseInterceptor.to("TurnEndPhase"); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(solarBeam.calculateBattlePower).toHaveLastReturnedWith(60); + }); +}); diff --git a/src/test/moves/whirlwind.test.ts b/src/test/moves/whirlwind.test.ts index cc31b2591a2..c16f38111f2 100644 --- a/src/test/moves/whirlwind.test.ts +++ b/src/test/moves/whirlwind.test.ts @@ -41,7 +41,8 @@ describe("Moves - Whirlwind", () => { const staraptor = game.scene.getPlayerPokemon()!; game.move.select(move); - await game.toNextTurn(); + + await game.phaseInterceptor.to("BerryPhase", false); expect(staraptor.findTag((t) => t.tagType === BattlerTagType.FLYING)).toBeDefined(); expect(game.scene.getEnemyPokemon()!.getLastXMoves(1)[0].result).toBe(MoveResult.MISS); From fd38ab4cb48cca69ac40bda26fdfe49fe334647c Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Wed, 23 Oct 2024 08:10:43 -0700 Subject: [PATCH 19/24] [P2] Missing Minior form (violet) now spawns in the wild (#4711) --- src/battle-scene.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a21e1b09342..3cbf4d7b422 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1387,7 +1387,7 @@ export default class BattleScene extends SceneBase { case Species.ZYGARDE: return Utils.randSeedInt(4); case Species.MINIOR: - return Utils.randSeedInt(6); + return Utils.randSeedInt(7); case Species.ALCREMIE: return Utils.randSeedInt(9); case Species.MEOWSTIC: From 958d79140c704e76b0d20165e1130e3bb6e5dff5 Mon Sep 17 00:00:00 2001 From: schmidtc1 <62030095+schmidtc1@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:12:53 -0400 Subject: [PATCH 20/24] [P2] Fixes Transform/Imposter not updating type/battle stat changes immediately; set move PP to 5 when transforming (#3462) * Adds updateInfo to transform move/ability, mirrors Transform functionality in Imposter * Implements functionality for reducing pp to 5 or less for each move when transforming * Refactors to async/await pattern, adds back removed anims/sounds from last commit * Eslint fix attempt * Update src/data/ability.ts per DayKev's suggestion Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Merge and fix conflicts * Adds unit tests for pp-change with transform/imposter * Updates to consistency in syntax/deprecated code --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- public/locales | 2 +- src/data/ability.ts | 22 ++++++---- src/data/move.ts | 67 ++++++++++++++++------------- src/test/abilities/imposter.test.ts | 22 +++++++--- src/test/moves/transform.test.ts | 22 +++++++--- 5 files changed, 84 insertions(+), 51 deletions(-) diff --git a/public/locales b/public/locales index fc4a1effd51..3ccef8472dd 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit fc4a1effd5170def3c8314208a52cd0d8e6913ef +Subproject commit 3ccef8472dd7cc7c362538489954cb8fdad27e5f diff --git a/src/data/ability.ts b/src/data/ability.ts index cc95045f8b7..d761657f5cd 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2422,11 +2422,12 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { super(true); } - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + async applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): Promise { const targets = pokemon.getOpponents(); if (simulated || !targets.length) { return simulated; } + const promises: Promise[] = []; let target: Pokemon; if (targets.length > 1) { @@ -2435,7 +2436,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { target = targets[0]; } - target = target!; // compiler doesn't know its guranteed to be defined + target = target!; pokemon.summonData.speciesForm = target.getSpeciesForm(); pokemon.summonData.fusionSpeciesForm = target.getFusionSpeciesForm(); pokemon.summonData.ability = target.getAbility().id; @@ -2452,18 +2453,23 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { pokemon.setStatStage(s, target.getStatStage(s)); } - pokemon.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m?.moveId ?? Moves.NONE, m?.ppUsed, m?.ppUp)); + pokemon.summonData.moveset = target.getMoveset().map(m => { + const pp = m?.getMove().pp ?? 0; + // if PP value is less than 5, do nothing. If greater, we need to reduce the value to 5 using a negative ppUp value. + const ppUp = pp <= 5 ? 0 : (5 - pp) / Math.max(Math.floor(pp / 5), 1); + return new PokemonMove(m?.moveId ?? Moves.NONE, 0, ppUp); + }); pokemon.summonData.types = target.getTypes(); + promises.push(pokemon.updateInfo()); - + pokemon.scene.queueMessage(i18next.t("abilityTriggers:postSummonTransform", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), targetName: target!.name, })); pokemon.scene.playSound("battle_anims/PRSFX- Transform"); - - pokemon.loadAssets(false).then(() => { + promises.push(pokemon.loadAssets(false).then(() => { pokemon.playAnim(); pokemon.updateInfo(); - }); + })); - pokemon.scene.queueMessage(i18next.t("abilityTriggers:postSummonTransform", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), targetName: target.name, })); + await Promise.all(promises); return true; } diff --git a/src/data/move.ts b/src/data/move.ts index cf88fad0ac5..efdd4568927 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -6643,42 +6643,49 @@ export class SuppressAbilitiesIfActedAttr extends MoveEffectAttr { } export class TransformAttr extends MoveEffectAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { - return new Promise(resolve => { - if (!super.apply(user, target, move, args)) { - return resolve(false); - } + async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + if (!super.apply(user, target, move, args)) { + return false; + } - user.summonData.speciesForm = target.getSpeciesForm(); - user.summonData.fusionSpeciesForm = target.getFusionSpeciesForm(); - user.summonData.ability = target.getAbility().id; - user.summonData.gender = target.getGender(); - user.summonData.fusionGender = target.getFusionGender(); + const promises: Promise[] = []; + user.summonData.speciesForm = target.getSpeciesForm(); + user.summonData.fusionSpeciesForm = target.getFusionSpeciesForm(); + user.summonData.ability = target.getAbility().id; + user.summonData.gender = target.getGender(); + user.summonData.fusionGender = target.getFusionGender(); - // Power Trick's effect will not preserved after using Transform - user.removeTag(BattlerTagType.POWER_TRICK); + // Power Trick's effect will not preserved after using Transform + user.removeTag(BattlerTagType.POWER_TRICK); - // Copy all stats (except HP) - for (const s of EFFECTIVE_STATS) { - user.setStat(s, target.getStat(s, false), false); - } + // Copy all stats (except HP) + for (const s of EFFECTIVE_STATS) { + user.setStat(s, target.getStat(s, false), false); + } - // Copy all stat stages - for (const s of BATTLE_STATS) { - user.setStatStage(s, target.getStatStage(s)); - } + // Copy all stat stages + for (const s of BATTLE_STATS) { + user.setStatStage(s, target.getStatStage(s)); + } - user.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m?.moveId!, m?.ppUsed, m?.ppUp)); // TODO: is this bang correct? - user.summonData.types = target.getTypes(); - - user.scene.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); - - user.loadAssets(false).then(() => { - user.playAnim(); - user.updateInfo(); - resolve(true); - }); + user.summonData.moveset = target.getMoveset().map(m => { + const pp = m?.getMove().pp ?? 0; + // if PP value is less than 5, do nothing. If greater, we need to reduce the value to 5 using a negative ppUp value. + const ppUp = pp <= 5 ? 0 : (5 - pp) / Math.max(Math.floor(pp / 5), 1); + return new PokemonMove(m?.moveId!, 0, ppUp); }); + user.summonData.types = target.getTypes(); + promises.push(user.updateInfo()); + + user.scene.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); + + promises.push(user.loadAssets(false).then(() => { + user.playAnim(); + user.updateInfo(); + })); + + await Promise.all(promises); + return true; } } diff --git a/src/test/abilities/imposter.test.ts b/src/test/abilities/imposter.test.ts index b7b8e0c5cca..7aaac5ca8c4 100644 --- a/src/test/abilities/imposter.test.ts +++ b/src/test/abilities/imposter.test.ts @@ -36,9 +36,7 @@ describe("Abilities - Imposter", () => { }); it("should copy species, ability, gender, all stats except HP, all stat stages, moveset, and types of target", async () => { - await game.startBattle([ - Species.DITTO - ]); + await game.classicMode.startBattle([ Species.DITTO ]); game.move.select(Moves.SPLASH); await game.phaseInterceptor.to(TurnEndPhase); @@ -78,9 +76,7 @@ describe("Abilities - Imposter", () => { it("should copy in-battle overridden stats", async () => { game.override.enemyMoveset([ Moves.POWER_SPLIT ]); - await game.startBattle([ - Species.DITTO - ]); + await game.classicMode.startBattle([ Species.DITTO ]); const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; @@ -97,4 +93,18 @@ describe("Abilities - Imposter", () => { expect(player.getStat(Stat.SPATK, false)).toBe(avgSpAtk); expect(enemy.getStat(Stat.SPATK, false)).toBe(avgSpAtk); }); + + it("should set each move's pp to a maximum of 5", async () => { + game.override.enemyMoveset([ Moves.SWORDS_DANCE, Moves.GROWL, Moves.SKETCH, Moves.RECOVER ]); + + await game.classicMode.startBattle([ Species.DITTO ]); + const player = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.TACKLE); + await game.phaseInterceptor.to(TurnEndPhase); + + player.getMoveset().forEach(move => { + expect(move!.getMovePp()).toBeLessThanOrEqual(5); + }); + }); }); diff --git a/src/test/moves/transform.test.ts b/src/test/moves/transform.test.ts index 079fdfa5685..8c0f5eda7b2 100644 --- a/src/test/moves/transform.test.ts +++ b/src/test/moves/transform.test.ts @@ -36,9 +36,7 @@ describe("Moves - Transform", () => { }); it("should copy species, ability, gender, all stats except HP, all stat stages, moveset, and types of target", async () => { - await game.startBattle([ - Species.DITTO - ]); + await game.classicMode.startBattle([ Species.DITTO ]); game.move.select(Moves.TRANSFORM); await game.phaseInterceptor.to(TurnEndPhase); @@ -78,9 +76,7 @@ describe("Moves - Transform", () => { it("should copy in-battle overridden stats", async () => { game.override.enemyMoveset([ Moves.POWER_SPLIT ]); - await game.startBattle([ - Species.DITTO - ]); + await game.classicMode.startBattle([ Species.DITTO ]); const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; @@ -97,4 +93,18 @@ describe("Moves - Transform", () => { expect(player.getStat(Stat.SPATK, false)).toBe(avgSpAtk); expect(enemy.getStat(Stat.SPATK, false)).toBe(avgSpAtk); }); + + it ("should set each move's pp to a maximum of 5", async () => { + game.override.enemyMoveset([ Moves.SWORDS_DANCE, Moves.GROWL, Moves.SKETCH, Moves.RECOVER ]); + + await game.classicMode.startBattle([ Species.DITTO ]); + const player = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.TRANSFORM); + await game.phaseInterceptor.to(TurnEndPhase); + + player.getMoveset().forEach(move => { + expect(move!.getMovePp()).toBeLessThanOrEqual(5); + }); + }); }); From c7e9eaf43510919e97051e9bcd6dfde97dde0b20 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Wed, 23 Oct 2024 08:24:50 -0700 Subject: [PATCH 21/24] [P2] Fix binding, etc. not being removed when switching with Baton Pass (#4709) * Fix binding, etc. not being removed when switching with Baton Pass * New baton pass test --- src/phases/switch-summon-phase.ts | 3 ++- src/test/moves/baton_pass.test.ts | 28 ++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 07761b10d6e..37652b3cfa4 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -65,8 +65,9 @@ export class SwitchSummonPhase extends SummonPhase { const pokemon = this.getPokemon(); + (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); + if (this.switchType === SwitchType.SWITCH) { - (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); const substitute = pokemon.getTag(SubstituteTag); if (substitute) { this.scene.tweens.add({ diff --git a/src/test/moves/baton_pass.test.ts b/src/test/moves/baton_pass.test.ts index 5e6e4be21ba..9d4a9358715 100644 --- a/src/test/moves/baton_pass.test.ts +++ b/src/test/moves/baton_pass.test.ts @@ -34,7 +34,7 @@ describe("Moves - Baton Pass", () => { .disableCrits(); }); - it("transfers all stat stages when player uses it", async() => { + it("transfers all stat stages when player uses it", async () => { // arrange await game.classicMode.startBattle([ Species.RAICHU, Species.SHUCKLE ]); @@ -91,7 +91,7 @@ describe("Moves - Baton Pass", () => { ]); }, 20000); - it("doesn't transfer effects that aren't transferrable", async() => { + it("doesn't transfer effects that aren't transferrable", async () => { game.override.enemyMoveset([ Moves.SALT_CURE ]); await game.classicMode.startBattle([ Species.PIKACHU, Species.FEEBAS ]); @@ -106,4 +106,28 @@ describe("Moves - Baton Pass", () => { expect(player2.findTag((t) => t.tagType === BattlerTagType.SALT_CURED)).toBeUndefined(); }, 20000); + + it("doesn't allow binding effects from the user to persist", async () => { + game.override.moveset([ Moves.FIRE_SPIN, Moves.BATON_PASS ]); + + await game.classicMode.startBattle([ Species.MAGIKARP, Species.FEEBAS ]); + + const enemy = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.FIRE_SPIN); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.move.forceHit(); + + await game.toNextTurn(); + + expect(enemy.getTag(BattlerTagType.FIRE_SPIN)).toBeDefined(); + + game.move.select(Moves.BATON_PASS); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + + game.doSelectPartyPokemon(1); + await game.toNextTurn(); + + expect(enemy.getTag(BattlerTagType.FIRE_SPIN)).toBeUndefined(); + }); }); From a13550ec4464b9147622aa87d9ce32fc32446d34 Mon Sep 17 00:00:00 2001 From: Moka <54149968+MokaStitcher@users.noreply.github.com> Date: Wed, 23 Oct 2024 21:46:57 +0200 Subject: [PATCH 22/24] [Balance][ME] Various ME Balance changes (#4700) * balance changes and updates to various MEs * fix import to new item * fix import to new item * Update src/data/mystery-encounters/utils/encounter-pokemon-utils.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/phases/select-modifier-phase.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/modifier/modifier.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/modifier/modifier.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * revert item atlas changes * eslint * revert 'revert item atlas' * update locale repo to latest commit * Fix fiery fallout missing argument * [balance] Training session ME does not update Seen/Defeated GameStats * [balance] update Weird Dream ME maximum spawn wave * [ME] update CombinationRequirements to allow AND or OR combinations * refactor: CombinationPokemonRequirement `.Some()` and `Every()` * chore: rename `orRequirements` to `requirements` * fix: returns of `Some()` and `Any()` * apply `Some()` / `Any()` pattern to `CombinationSceneRequirement` too * revert 'offer you can't refuse' giving Silver Pokeball' * Apply code review suggestions * [me] Weird Dream: apply same old gateau logic to team in options 1 and 2 --------- Co-authored-by: ImperialSympathizer Co-authored-by: ImperialSympathizer <110984302+ben-lear@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: innerthunder --- public/images/items.json | 797 +++++++++--------- public/images/items.png | Bin 58527 -> 58657 bytes public/images/items/pb_silver.png | Bin 0 -> 556 bytes public/images/trainer/future_self_f.json | 41 + public/images/trainer/future_self_f.png | Bin 0 -> 664 bytes public/images/trainer/future_self_m.json | 41 + public/images/trainer/future_self_m.png | Bin 0 -> 695 bytes public/locales | 2 +- src/data/balance/pokemon-evolutions.ts | 6 +- .../an-offer-you-cant-refuse-encounter.ts | 15 +- .../encounters/bug-type-superfan-encounter.ts | 26 +- .../encounters/clowning-around-encounter.ts | 15 +- .../encounters/dark-deal-encounter.ts | 10 +- .../encounters/delibirdy-encounter.ts | 25 +- .../encounters/fiery-fallout-encounter.ts | 78 +- .../mysterious-challengers-encounter.ts | 8 +- .../shady-vitamin-dealer-encounter.ts | 2 +- .../the-expert-pokemon-breeder-encounter.ts | 27 +- .../encounters/training-session-encounter.ts | 1 + .../encounters/trash-to-treasure-encounter.ts | 2 +- .../encounters/weird-dream-encounter.ts | 381 ++++++--- .../mystery-encounter-requirements.ts | 127 ++- .../mystery-encounters/mystery-encounter.ts | 14 + .../requirements/requirement-groups.ts | 17 + .../utils/encounter-pokemon-utils.ts | 20 + src/data/trainer-config.ts | 18 +- src/enums/trainer-type.ts | 2 + src/field/pokemon.ts | 2 - src/modifier/modifier-type.ts | 9 +- src/modifier/modifier.ts | 66 +- src/phases/select-modifier-phase.ts | 11 +- src/phases/victory-phase.ts | 9 +- src/system/game-data.ts | 4 + src/system/pokemon-data.ts | 3 +- .../encounters/delibirdy-encounter.test.ts | 48 +- .../fiery-fallout-encounter.test.ts | 47 +- .../trash-to-treasure-encounter.test.ts | 2 +- .../encounters/weird-dream-encounter.test.ts | 67 +- 38 files changed, 1244 insertions(+), 699 deletions(-) create mode 100644 public/images/items/pb_silver.png create mode 100644 public/images/trainer/future_self_f.json create mode 100644 public/images/trainer/future_self_f.png create mode 100644 public/images/trainer/future_self_m.json create mode 100644 public/images/trainer/future_self_m.png diff --git a/public/images/items.json b/public/images/items.json index 05d021b6a06..3c9cff7a35a 100644 --- a/public/images/items.json +++ b/public/images/items.json @@ -6583,7 +6583,7 @@ } }, { - "filename": "rb", + "filename": "pb_silver", "rotated": false, "trimmed": true, "sourceSize": { @@ -6666,6 +6666,27 @@ "h": 19 } }, + { + "filename": "rb", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 6, + "w": 20, + "h": 20 + }, + "frame": { + "x": 254, + "y": 320, + "w": 20, + "h": 20 + } + }, { "filename": "smooth_meteorite", "rotated": false, @@ -6680,27 +6701,6 @@ "w": 20, "h": 20 }, - "frame": { - "x": 254, - "y": 320, - "w": 20, - "h": 20 - } - }, - { - "filename": "strange_ball", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 6, - "w": 20, - "h": 20 - }, "frame": { "x": 274, "y": 325, @@ -6709,7 +6709,7 @@ } }, { - "filename": "ub", + "filename": "strange_ball", "rotated": false, "trimmed": true, "sourceSize": { @@ -6751,7 +6751,7 @@ } }, { - "filename": "apicot_berry", + "filename": "ub", "rotated": false, "trimmed": true, "sourceSize": { @@ -6761,13 +6761,13 @@ "spriteSourceSize": { "x": 6, "y": 6, - "w": 19, + "w": 20, "h": 20 }, "frame": { "x": 221, "y": 337, - "w": 19, + "w": 20, "h": 20 } }, @@ -6793,7 +6793,7 @@ } }, { - "filename": "big_mushroom", + "filename": "apicot_berry", "rotated": false, "trimmed": true, "sourceSize": { @@ -6804,13 +6804,13 @@ "x": 6, "y": 6, "w": 19, - "h": 19 + "h": 20 }, "frame": { "x": 343, "y": 287, "w": 19, - "h": 19 + "h": 20 } }, { @@ -6834,6 +6834,27 @@ "h": 20 } }, + { + "filename": "big_mushroom", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 6, + "w": 19, + "h": 19 + }, + "frame": { + "x": 318, + "y": 306, + "w": 19, + "h": 19 + } + }, { "filename": "hard_stone", "rotated": false, @@ -6849,33 +6870,12 @@ "h": 20 }, "frame": { - "x": 318, - "y": 306, + "x": 314, + "y": 325, "w": 19, "h": 20 } }, - { - "filename": "miracle_seed", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 7, - "w": 19, - "h": 19 - }, - "frame": { - "x": 337, - "y": 306, - "w": 19, - "h": 19 - } - }, { "filename": "wl_ability_urge", "rotated": false, @@ -6891,12 +6891,33 @@ "h": 18 }, "frame": { - "x": 314, - "y": 326, + "x": 337, + "y": 307, "w": 20, "h": 18 } }, + { + "filename": "miracle_seed", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 7, + "w": 19, + "h": 19 + }, + "frame": { + "x": 333, + "y": 325, + "w": 19, + "h": 19 + } + }, { "filename": "wl_antidote", "rotated": false, @@ -6912,12 +6933,54 @@ "h": 18 }, "frame": { - "x": 356, + "x": 357, "y": 307, "w": 20, "h": 18 } }, + { + "filename": "wl_awakening", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 8, + "w": 20, + "h": 18 + }, + "frame": { + "x": 352, + "y": 325, + "w": 20, + "h": 18 + } + }, + { + "filename": "wl_burn_heal", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 8, + "w": 20, + "h": 18 + }, + "frame": { + "x": 241, + "y": 340, + "w": 20, + "h": 18 + } + }, { "filename": "golden_egg", "rotated": false, @@ -6933,33 +6996,12 @@ "h": 20 }, "frame": { - "x": 376, - "y": 307, + "x": 372, + "y": 325, "w": 17, "h": 20 } }, - { - "filename": "wl_awakening", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 8, - "w": 20, - "h": 18 - }, - "frame": { - "x": 393, - "y": 241, - "w": 20, - "h": 18 - } - }, { "filename": "toxic_orb", "rotated": false, @@ -6975,96 +7017,12 @@ "h": 18 }, "frame": { - "x": 413, - "y": 258, + "x": 377, + "y": 307, "w": 18, "h": 18 } }, - { - "filename": "wl_burn_heal", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 8, - "w": 20, - "h": 18 - }, - "frame": { - "x": 393, - "y": 259, - "w": 20, - "h": 18 - } - }, - { - "filename": "ampharosite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 377, - "y": 243, - "w": 16, - "h": 16 - } - }, - { - "filename": "audinite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 377, - "y": 259, - "w": 16, - "h": 16 - } - }, - { - "filename": "relic_gold", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 9, - "y": 11, - "w": 15, - "h": 11 - }, - "frame": { - "x": 377, - "y": 275, - "w": 15, - "h": 11 - } - }, { "filename": "lucky_egg", "rotated": false, @@ -7080,8 +7038,8 @@ "h": 20 }, "frame": { - "x": 381, - "y": 286, + "x": 389, + "y": 325, "w": 17, "h": 20 } @@ -7101,8 +7059,8 @@ "h": 18 }, "frame": { - "x": 398, - "y": 277, + "x": 352, + "y": 343, "w": 20, "h": 18 } @@ -7122,8 +7080,8 @@ "h": 18 }, "frame": { - "x": 398, - "y": 295, + "x": 372, + "y": 345, "w": 20, "h": 18 } @@ -7143,8 +7101,8 @@ "h": 18 }, "frame": { - "x": 393, - "y": 313, + "x": 392, + "y": 345, "w": 20, "h": 18 } @@ -7164,14 +7122,14 @@ "h": 18 }, "frame": { - "x": 240, - "y": 340, + "x": 378, + "y": 241, "w": 20, "h": 18 } }, { - "filename": "banettite", + "filename": "relic_gold", "rotated": false, "trimmed": true, "sourceSize": { @@ -7179,16 +7137,16 @@ "h": 32 }, "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 + "x": 9, + "y": 11, + "w": 15, + "h": 11 }, "frame": { - "x": 413, - "y": 313, - "w": 16, - "h": 16 + "x": 398, + "y": 241, + "w": 15, + "h": 11 } }, { @@ -7206,8 +7164,8 @@ "h": 18 }, "frame": { - "x": 202, - "y": 358, + "x": 377, + "y": 259, "w": 20, "h": 18 } @@ -7227,8 +7185,8 @@ "h": 18 }, "frame": { - "x": 201, - "y": 376, + "x": 381, + "y": 277, "w": 20, "h": 18 } @@ -7248,8 +7206,8 @@ "h": 18 }, "frame": { - "x": 201, - "y": 394, + "x": 397, + "y": 259, "w": 20, "h": 18 } @@ -7269,33 +7227,12 @@ "h": 18 }, "frame": { - "x": 201, - "y": 412, + "x": 401, + "y": 277, "w": 20, "h": 18 } }, - { - "filename": "beedrillite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 222, - "y": 357, - "w": 16, - "h": 16 - } - }, { "filename": "wl_ice_heal", "rotated": false, @@ -7311,14 +7248,14 @@ "h": 18 }, "frame": { - "x": 238, - "y": 358, + "x": 395, + "y": 295, "w": 20, "h": 18 } }, { - "filename": "blastoisinite", + "filename": "ampharosite", "rotated": false, "trimmed": true, "sourceSize": { @@ -7332,8 +7269,29 @@ "h": 16 }, "frame": { - "x": 222, - "y": 373, + "x": 415, + "y": 295, + "w": 16, + "h": 16 + } + }, + { + "filename": "audinite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 415, + "y": 311, "w": 16, "h": 16 } @@ -7353,12 +7311,33 @@ "h": 18 }, "frame": { - "x": 221, - "y": 389, + "x": 406, + "y": 327, "w": 20, "h": 18 } }, + { + "filename": "banettite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 412, + "y": 345, + "w": 16, + "h": 16 + } + }, { "filename": "wl_item_urge", "rotated": false, @@ -7375,7 +7354,7 @@ }, "frame": { "x": 221, - "y": 407, + "y": 357, "w": 20, "h": 18 } @@ -7396,7 +7375,7 @@ }, "frame": { "x": 241, - "y": 376, + "y": 358, "w": 20, "h": 18 } @@ -7416,8 +7395,8 @@ "h": 18 }, "frame": { - "x": 241, - "y": 394, + "x": 201, + "y": 360, "w": 20, "h": 18 } @@ -7437,8 +7416,8 @@ "h": 18 }, "frame": { - "x": 241, - "y": 412, + "x": 201, + "y": 378, "w": 20, "h": 18 } @@ -7458,8 +7437,8 @@ "h": 18 }, "frame": { - "x": 258, - "y": 358, + "x": 221, + "y": 375, "w": 20, "h": 18 } @@ -7479,8 +7458,8 @@ "h": 18 }, "frame": { - "x": 261, - "y": 376, + "x": 201, + "y": 396, "w": 20, "h": 18 } @@ -7500,8 +7479,8 @@ "h": 18 }, "frame": { - "x": 261, - "y": 394, + "x": 221, + "y": 393, "w": 20, "h": 18 } @@ -7521,8 +7500,8 @@ "h": 18 }, "frame": { - "x": 261, - "y": 412, + "x": 241, + "y": 376, "w": 20, "h": 18 } @@ -7542,12 +7521,33 @@ "h": 18 }, "frame": { - "x": 278, - "y": 345, + "x": 241, + "y": 394, "w": 20, "h": 18 } }, + { + "filename": "beedrillite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 201, + "y": 414, + "w": 16, + "h": 16 + } + }, { "filename": "wl_super_potion", "rotated": false, @@ -7563,12 +7563,33 @@ "h": 18 }, "frame": { - "x": 298, + "x": 261, "y": 345, "w": 20, "h": 18 } }, + { + "filename": "blastoisinite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 261, + "y": 363, + "w": 16, + "h": 16 + } + }, { "filename": "blazikenite", "rotated": false, @@ -7584,8 +7605,8 @@ "h": 16 }, "frame": { - "x": 318, - "y": 344, + "x": 281, + "y": 345, "w": 16, "h": 16 } @@ -7605,8 +7626,8 @@ "h": 16 }, "frame": { - "x": 334, - "y": 326, + "x": 261, + "y": 379, "w": 16, "h": 16 } @@ -7626,8 +7647,8 @@ "h": 16 }, "frame": { - "x": 334, - "y": 342, + "x": 297, + "y": 345, "w": 16, "h": 16 } @@ -7647,8 +7668,8 @@ "h": 16 }, "frame": { - "x": 350, - "y": 325, + "x": 261, + "y": 395, "w": 16, "h": 16 } @@ -7668,8 +7689,8 @@ "h": 16 }, "frame": { - "x": 350, - "y": 341, + "x": 313, + "y": 345, "w": 16, "h": 16 } @@ -7689,8 +7710,8 @@ "h": 16 }, "frame": { - "x": 366, - "y": 327, + "x": 217, + "y": 414, "w": 16, "h": 16 } @@ -7710,8 +7731,8 @@ "h": 16 }, "frame": { - "x": 366, - "y": 343, + "x": 233, + "y": 412, "w": 16, "h": 16 } @@ -7731,8 +7752,8 @@ "h": 16 }, "frame": { - "x": 382, - "y": 331, + "x": 249, + "y": 412, "w": 16, "h": 16 } @@ -7752,8 +7773,8 @@ "h": 16 }, "frame": { - "x": 398, - "y": 331, + "x": 265, + "y": 411, "w": 16, "h": 16 } @@ -7773,8 +7794,8 @@ "h": 16 }, "frame": { - "x": 414, - "y": 329, + "x": 329, + "y": 345, "w": 16, "h": 16 } @@ -7794,8 +7815,8 @@ "h": 16 }, "frame": { - "x": 382, - "y": 347, + "x": 277, + "y": 363, "w": 16, "h": 16 } @@ -7815,8 +7836,8 @@ "h": 16 }, "frame": { - "x": 398, - "y": 347, + "x": 277, + "y": 379, "w": 16, "h": 16 } @@ -7836,8 +7857,8 @@ "h": 16 }, "frame": { - "x": 414, - "y": 345, + "x": 277, + "y": 395, "w": 16, "h": 16 } @@ -7857,8 +7878,8 @@ "h": 16 }, "frame": { - "x": 281, - "y": 363, + "x": 293, + "y": 361, "w": 16, "h": 16 } @@ -7878,8 +7899,8 @@ "h": 16 }, "frame": { - "x": 281, - "y": 379, + "x": 309, + "y": 361, "w": 16, "h": 16 } @@ -7899,8 +7920,8 @@ "h": 16 }, "frame": { - "x": 297, - "y": 363, + "x": 293, + "y": 377, "w": 16, "h": 16 } @@ -7920,8 +7941,8 @@ "h": 16 }, "frame": { - "x": 281, - "y": 395, + "x": 325, + "y": 361, "w": 16, "h": 16 } @@ -7941,8 +7962,8 @@ "h": 16 }, "frame": { - "x": 297, - "y": 379, + "x": 293, + "y": 393, "w": 16, "h": 16 } @@ -7961,6 +7982,90 @@ "w": 16, "h": 16 }, + "frame": { + "x": 309, + "y": 377, + "w": 16, + "h": 16 + } + }, + { + "filename": "mawilite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 309, + "y": 393, + "w": 16, + "h": 16 + } + }, + { + "filename": "medichamite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 325, + "y": 377, + "w": 16, + "h": 16 + } + }, + { + "filename": "metagrossite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 325, + "y": 393, + "w": 16, + "h": 16 + } + }, + { + "filename": "mewtwonite_x", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, "frame": { "x": 281, "y": 411, @@ -7968,90 +8073,6 @@ "h": 16 } }, - { - "filename": "mawilite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 297, - "y": 395, - "w": 16, - "h": 16 - } - }, - { - "filename": "medichamite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 297, - "y": 411, - "w": 16, - "h": 16 - } - }, - { - "filename": "metagrossite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 313, - "y": 363, - "w": 16, - "h": 16 - } - }, - { - "filename": "mewtwonite_x", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 313, - "y": 379, - "w": 16, - "h": 16 - } - }, { "filename": "mewtwonite_y", "rotated": false, @@ -8067,8 +8088,8 @@ "h": 16 }, "frame": { - "x": 313, - "y": 395, + "x": 297, + "y": 409, "w": 16, "h": 16 } @@ -8089,7 +8110,7 @@ }, "frame": { "x": 313, - "y": 411, + "y": 409, "w": 16, "h": 16 } @@ -8109,8 +8130,8 @@ "h": 16 }, "frame": { - "x": 350, - "y": 357, + "x": 329, + "y": 409, "w": 16, "h": 16 } @@ -8130,8 +8151,8 @@ "h": 16 }, "frame": { - "x": 366, - "y": 359, + "x": 341, + "y": 361, "w": 16, "h": 16 } @@ -8151,8 +8172,8 @@ "h": 16 }, "frame": { - "x": 334, - "y": 358, + "x": 341, + "y": 377, "w": 16, "h": 16 } @@ -8172,8 +8193,8 @@ "h": 16 }, "frame": { - "x": 382, - "y": 363, + "x": 341, + "y": 393, "w": 16, "h": 16 } @@ -8193,8 +8214,8 @@ "h": 16 }, "frame": { - "x": 398, - "y": 363, + "x": 345, + "y": 409, "w": 16, "h": 16 } @@ -8214,7 +8235,7 @@ "h": 16 }, "frame": { - "x": 414, + "x": 412, "y": 361, "w": 16, "h": 16 @@ -8235,8 +8256,8 @@ "h": 16 }, "frame": { - "x": 329, - "y": 374, + "x": 357, + "y": 363, "w": 16, "h": 16 } @@ -8256,8 +8277,8 @@ "h": 16 }, "frame": { - "x": 329, - "y": 390, + "x": 357, + "y": 379, "w": 16, "h": 16 } @@ -8277,8 +8298,8 @@ "h": 16 }, "frame": { - "x": 329, - "y": 406, + "x": 373, + "y": 363, "w": 16, "h": 16 } @@ -8298,8 +8319,8 @@ "h": 16 }, "frame": { - "x": 345, - "y": 374, + "x": 373, + "y": 379, "w": 16, "h": 16 } @@ -8319,8 +8340,8 @@ "h": 16 }, "frame": { - "x": 345, - "y": 390, + "x": 389, + "y": 363, "w": 16, "h": 16 } @@ -8340,8 +8361,8 @@ "h": 16 }, "frame": { - "x": 345, - "y": 406, + "x": 389, + "y": 379, "w": 16, "h": 16 } @@ -8361,8 +8382,8 @@ "h": 16 }, "frame": { - "x": 361, - "y": 375, + "x": 405, + "y": 377, "w": 16, "h": 16 } @@ -8383,7 +8404,7 @@ }, "frame": { "x": 361, - "y": 391, + "y": 395, "w": 16, "h": 16 } @@ -8403,8 +8424,8 @@ "h": 16 }, "frame": { - "x": 361, - "y": 407, + "x": 377, + "y": 395, "w": 16, "h": 16 } @@ -8415,6 +8436,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:9ef21166268f7487fc9ff8d0f9b996e4:82658ac7bdd4c2b417e1f59168179262:110e074689c9edd2c54833ce2e4d9270$" + "smartupdate": "$TexturePacker:SmartUpdate:875c6d67e72590dfc6d319101aa31cfa:dd2bb865ecbc5ac7b975ddf70b993334:110e074689c9edd2c54833ce2e4d9270$" } } diff --git a/public/images/items.png b/public/images/items.png index 8aaa0281c0088a4b0ded764af22c3d5456ce167f..1bd7b3af9c3314850fba4eed749f7e3aaf46d8f0 100644 GIT binary patch literal 58657 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 VnKdzxs!0I6wACM})u>HkJz_A)z9D zP}fyZcX0cMC@X*9U}mSMr|0A2dwF?ToSQxVd-?Ce*YEqt_|)RYhPslfj<)X8-QDia z-t&%mO-*fjW=3CspOv}a!`;CA)!oxYd1QRt^V5R0#mB?Dhll!#k*Uq`Dz)z*QP<>} z0V~`4qph5xs^rXau$S+xmR4y#^+xZdisW18)T`G1xrw#ATTZUEdDAH+`Di=)r`fst zcF^;v3jAoWY_Pv2RP+x!?Ib*X{c7)GCndG*;l695LR+a5a@)MOdv)dBnac2ZVgB%P zQIM@GAtG`Q8&c}I`xFt`d-im5{ut~C*y?e(S*-NjG~e#lkg{|fPZx{rKh*KOI#}vH z_F`AbQ%a#E-MN6SMY%j6-rN;L*8aH-sIQ3(2+FN)iPEiZI?G~r{hpr`@gY(s{JqN4 zmLHcpZ_?5;w011-D){O98}0o3TrhD4k$`PmADu)p>(Y@yp%!l=*L8e4G}%xgHyR zav(Y_A^w^yluuoEm*v!J0#o{4HYbU>n~;#Fd-jm3wHU;1u3c6LOwhkzKtiKtqy@)R z(&lNoyEtXo{g)e%z0_s#v3&kuBBig%wLfpiFkd^WMSovC_xSHr^4*WpZ{bk)ET@NQ z|12IMixI7z7Qvgi$pDMgP)$C|=E2tu2y0oQJF7rCM&+#AjqV!9=K&J`!=H$x+nn=< zAW`SF_TMUbt@^HYOp#r83rM7WHMTRO+x%}D(mRfRI2hJyf9w3+`%ze_5~~~Nuss$u zZvYwfZMlb-FvLU(I*x@~{;52BF{{07Y7XU2yB!-3p%4n}8L}=_BkEHA=AXo_q!hhs zz%?B3zN;v7D@Sg7+gFqa|J^9W!jia_g2oxV^ja72WLA5pNtgd&X>Pv<(_?RX0Y4@3 zp_Q^wTt^B~S7jzMsqIq0ez$P#)geT{fS03{1@)t#CdZq2B*Mfg$GzHEL!kVd0N5Vd z8ahb-zW9H~UD9Z#0A}nDgwEn^%uM$XzOoSPX=k(nXh5y>R4m~rq>rhD- z(x4Oq>dTdzuPxs4z0&;L8$`i76R`07C zuNNx3?(Uvmu3yR>jO(FhzaQ&qm_o;ax9<&=l_Pm`Tb>E-BJQB}yPhCN>(!2?6?5tb zWs{QwzekPLfKku*h|IfQYo|u(`mdTcHfHrZW3|v7gz3cWgN+NfXmeshRBI~((T)jj zR4f{35Ic82?uDPcBQ^ye)5tJ$2A|K#C_~d?!O2Z3$bsmrBYxfKPU?m8XWT`r=HX51 z!^vmfjP1>^mcM7AK?({;-={JCsk+v{-90^t$**#I7kus-oAg0t9?w2iWqVd`^>nR< zrqkp2)luJ0`P~>#&)Eckx5O&ao&Zyet{r5C8%@)>rrzqSM;}e|0f_l}f@~g}|52o& z`NQCfr61();#)}bpH-hLfWh*g(*JiE30DxP%NqW`4Bj|97b`2G0qFi3FK^Et5i9-}Zb`Mh}^C0MoYjJMovz~vhS#UbbGe0{QH}M+;&N-DpB7c|Mi=!2N0SzU|r3zVnrX99u?z* z&--+XAucp6g@Z>yd2b|CoKzo{0N5$Tsqa>zwIsQ*QW;x%iST9iYW*%G^LURe*kK{H zJGQ$U_o2g(?|p@mY3(({uS=-P|D?z7M(XowGqw5@r#Is7ikai8H<|j>wR!u^!Hi$d zht%WP{3iE|ozfwtQ3Nf)yXb=9^P31eOD)DvVe#?bXt2qt-$*TWRUm_N-gw`-DhPHk z>wNmX#rOloQDy4Ny6Uob(*HDbP7K)%3&gMrDHZ&fm~9gImv}4$9XN>wqZJSTC;BF; zgR(@UfwK_!VaEfN@3a3iq`V`L+?8{5a86r{nMrO2P#m_?jbo2o+Fq!Xc0zM~+Vej* zEIMkZ5VgNBQK#-(c!5$++3cc_}*kgTbD@#>lBPCB&V^~6A zpN5q#=o_*C5Pp?8#+b$j0G*oK_|W)lQ4Hhg>YGUTt@moT9U3>^NxzbF5|(Ujl64zd zNTZa4C(NfZP&OaeuT$|`p`m)QirnX^*7*Hhx3n0n^dH>{8h&AlpVfFJXPYwR$YheT zn&Eu{gjesFwYNzFUG~}UI{W(i>=%|=fv{7LuKa`abW5`wxt9{QcBl_F7v(P|e-vBy> zm|N7?z+k%t4q`g4k;8w!825U{4j(>0oDAe3?(xyRZ!bjsu9s%&Eq6vSJmNOkk8yhU z(0j3)Nu819@}nTey=z^uMo;+QJD4 zM9T`^v|pM3F;%cKxOwq;c~*!S7tajPjESVA5V#p&WW97b8%(rnUWBuWxhUuLP|9=f`_fnCxh~$52t_5mue7=#cWOP>6sW~ z_r|55RL8qh@*;UT?PcAxW6{m;-v~?IZ9yo83@#PD}Tq1Qr3;h@El=b`8u|*+t;>lol#wJ=iW8`*D43m~GO?ixVH9QR?k*e7V~7 zdx@os`MdlQV{S2aHY!-s%Sikx)rVl)1+pF{XVEixWt7>-wtDz2rssIY-!VRdE!>i5 znkKwB^CJbY0l+qYAs1}4*6MS?TVRlF-TKS+gTWKK1$ss}{^+}pzX5;9<}Vdk)h${d zdlN8Y((qxpaX1}6VHDN#n4?sFDU7A_#qqC$pzsQgo3B>Ii@4% zY6qCK?=a(jqlDZspkG_Hyv;+5nT38tEi(7Pcm2)3nLKN}lr34#(VkMW;oFv)yJ8v- zs&}c!GvFPt`Igf404b$rui;=9q?sZ>H@N({H(!fF(MspTz~d3QhbQg)NtIPqb$Yyh z@i7-~em;S7?4>nn)^TCJTazI;-$mYva=0U5Zf{R|Hcy;{`i*5TU5b#_6#2PRW1@W|yIrwW21vnh*@(-ibRmmR%% zr_sQ6=nTR8vJzSBZkBafCQW08wt?@TQ`EK%jAi5nTe z6QS1bpM4mG8MAQ*lYak(6qGO!(sjxuveaMklhS{=%!6>QZ=;}~p!3uiWBeDG*Q|b+ z=BqCkb37o%35J1GxkQK#I1MHepkrHfV}Q<2)aqDSqXVVu2$w9KICiLu2|G>QmLm#l zFl5O}$V-GUjnmx;%lR7l!W`b#l)w6eiJiq~6%2DZ)PEh8GF5v4YUC@PncZNoO!^S^ zGmPUn!!EZgTdoj08C!=ciiC@K8)*LQw5b%vcVYCa#u;1-94usSOnf3~a*fgVO&79?Wt>zabrGESiQAD> zP%qN9({4xrnxG)Q)F%v}nDORJ9IA=1&9o#$bca$Xby-5L+TDH-^8yzy^6Bo+%T=t$ zeKKblKlJm;DnFk=D{j(_fOhX0?HiX-eBEQspXz45}_v3v#xTDU2rXS;2>mF1of*Z+=iW!4zFf1~fE7hwX_S50i4K?{XKYs#glD--Ge->jr zfKrh|jcLR(Q=0fe+}K;%?XM?!!*He0T4J1`qp|-a=MYMq#!DCKB0a>%)GkGHB zxITr5m};}0wgIN9EaRz3{TCfdU&|8Y+6r|aIm2Dy@tUm|Rd3aqcLPg5TCK}vmw7r>=}P1K(xBWk6ClX6-khsFRa2OFTcvr|0Y1 za*9`vf9T>8(nXreJpAHLorRQ+VI0D=Iz64f#QhsWX5Eb`;<6n!`yMb(*+l$?_i|3e zRM*@i`?LIcgB$}fH4a>(b)(>c;lVXW*TK5_X4P&r-fA$$N+1t2K7C2od^J4_XNzho z`S5Vcz3LOGD$WnfnL=W=K{#beBkfd?iYkSNWy$)9i!{t*gG-xq@ApD}#8?4Zy~Vb> zbCSmo!L@)UEmd+LR(js-wBhPBrre5eO@9em5*O)?61@yJ?|o5=@G(k3_ooGZE>C*) zDnjF%FSMW>G9LvWxl=~#fjB&T+6U?MaA!gPsmsFUm)-Ax%@n6iz13;myj=ViD(b1M z1FWsr{+Cr|lHRv`k&ZTwj#T>6?@X)Y4RjC!2F4qnG5e%C=0B>|YFA0pMiOtenjb#Z zo?Tu1b$Lk639(rm3LJ9c6xo$ANtBiS;%VU1ilvUKdm2P`+i&WlO1#^5FCrISOBJ-= zwuQ5PVx>qzNI0H)SWwHQJ+>=j5qj$p+-(|wSg9&MTm1=`6z7`~=)H!Q)6bXpUaOHf zCprc5cz%m(3fuStYBgvw==}-E**TZE%6XCwim1tX;s9#f;+d?yPIKQ+KxVjQY2Jjf z*LuLwQFly#Ptb+kK4YN*H%hr=Qqevb7BLkhfTA>wDVIhm^y1%vF)I!jnlvdquxPtkaVKpW6ZZ5JL_pA`OG?Sm_AkkOBUrdwHeJLp zWg}%2eh4v#S^{{#6^`S^0bf{0cTmcycD$`>?YSeo(BEHyoPHuaz}qZZ0r3E zK`3@rL=1DuoKh8?A8=jHL{{oo8I!di*jqD6GIZ0F!?$F3j}Lx<^{tfcWF}gw=9fb8kN2+Y?a}L|$qwCxJfNIZz|BXZS z8@wO(7e?>@6XweYBJ9uwg>Zx3VLve_AE7Do6aCXgmwC^V?J-8}KQVUn7}W8;Ar}l+ zfvs?3gprH#8Mpq0NhGmj>iKjJ#M4P*m0DT7O`Vk0F77;dd_oRKh6_aDRAY_|<>N32{w|9pQB-neWy3N@Ry;RDrmtDF9)`4MT6B?sGA z(}=mK+oc{m-Tv=wjnHo`6{Me;tBqqh}TL>)zPd2nkA zYG0XhEnE~-TbDYD)I<0$zni}%E{$ih`n!KtXLKxgkl(567yJ>*eolU``RkmR;oAro zy$&%&51DSn>Y87|H$*hqMkIYTs6EpC@`@w^xP{&Vry-ZoJ_$*~G-u|i!U|fp$7FH} z1M+KYzzY@S!#L3d@le59R@E(>lst{e3U9tj0{`%x!+md>5`64YWw-~>;ovvO->jnn zR;qU)EppcxQbsee35Ef1~mq(QCPGUmf8!uybA? z`=3a@$G1%GR>*U>WsbF{)p;+HhT-!G#afx%7K4-AbAJ5>_Q(y7x#wq%cA`^2M zJ2%|30h8EQU%`XGoSYY9RPtT`s`>p?p@@-`gUPD5Qm#7xFx;7f0#2B&9{Z!^$cek1 zgB>I0tj;*z-k1F*L$#3aS4U(NPS6#S=ap-hjX%(o6WT7-ZIB;-O18)3Zzj+KFj5fD z5yNJ(q41$m_(GcAeJJR2+2@7~biXYQg=*dhFh&5a!u&2{u}V5(PdXmrhAQ&&yGr^P5sV0sB>S$w?pBC22GZO?N;FBbiX`~a^nLS7M4m4+G{pZi7o}_Z=#2r$<_6528 zi``5ewDBgA5NwzEp}>kQlT-_`y(_EFqxblxjNi2LJXT<~z<&NCVRC9!L;0@S1}z<0J_$ z(l>cc@;LHCWwl8HjWO}LxlI+Fc*3E<3&tUa#`)(3E$WcOl9B;r1JKI#@q7Cnm&m)G zo>%z2{fY#C<-l$EnUo#jWwzl^`kB#F&zMh;hhe9I1P2S7!U>7&gK0$jjqy58*bG{& z+D=MVrD3(?eRm>&CV+*R{aXMo#kuzKNVd4Ua`B#iN?ly|ZalOdH+-Y}#4%nP3i^4Z z)o-Tl8$?_RMfwq@Jb~-R)3suFYwXBQ*`BD}ncj!j&voLq76xy^iRI@x4tTBf$QjEjkgO(b0A*@k6NXgl;LE10aKG;@tC zy*zWx^Cw7{8d@0o+N@D^5@B~z;q}W&;9deeBrKmMZbk;!?!y>9dKr8<&J=lhT-bD6 z-+c5R2xhK^lt?aO=BKl_>QVzatug?~fQRqk?YHU< z1vAR=DT>AU_u>@|N972+oShFBZy72bMrMO~-1p1f4+Xd9!#|K=^T=#B`eA)b76H9e z>)KX2|6t~`ND18dZjcv@l4|fEMl3z+^%J{#jJH{7t<)aIv`%WE<>l4%M*@1iwa}>f z>2Hp!ZQjpeE_vK3{!k8>7Xqb{<(Er)qgV5q!nT3~i)tZo6z}2fvsgbLcC5=&4;`Ta z_cqz`ZUE>aE5`gggar$@(efH5gcnajIF*YwC9z_sO2arJVE>7oAQ!8kEd@&*7AmoW zwf~qF9aEz@#E>KxK}$Yy_~K^Di!O4D2(cg5w}QK2?XZbr{UNvYyP(=R8(8%}RdJe6 z_VLl=ovMxiltgeV6p2TS9?mYO*#B}dV~kH1=TV6whJHb`-{HaTRK9hj2pY%NruY7M$u_#pa7$^S3Xye?~Mn z)=`4YrWza(OH>uvPI}zp>hH-Ul7sZLee^P0g2Oo2X0BmhOSZSK>e2L($VVo>p-kZ4 z*5^Hq4uzOy}vs~VX~#4Wve!A6)=)1$*!K{kV#eV+#Do= zr+l}%qLaF%k4<;`gwA3stcr_9q7*TX?BeBBmxC}%>%<77Oc)Xb5r8W8vYJb>8jRyk zIxM90RKMrf-N~ML7}bS}sh$jLUM*^%E2_OFO<$WSC74Nl-TC!ekdne^L@esWwhqoJ zGJ5j$lU>!zWHG)Kv)jh+_vpv=vFT>QqyQu}ji!&RXGsq-LrE-1M{!Kn$w_E@lZ{*) z>|AP5svKOuvMNEpoAG0D@Z)+k)hyT7y(P?(uE%&^d((9KXP+7SQ9fC(%#9c;-IKR+ zsbiiQJv;7Z+rUCQ_!@VW+6hFtzCIn>3v8b4!cOT(cSG**0vWD$Z!S4I7wqImT)zEmgG#SlEhKZf!LQZ5sZkV{Z zrdFnYuXLtnD`m6?0&;J*k{gC=_gZ`?t^FWfTlZQ1>$_7t7V$X73DdhytIxMysb2{7 zf#?zJ8)=q&AY^1)HO)b7F_Cv%ym~6(sMWe5{ek+XISo0J)QJ4I{S{Q1Bv=_XQ;aHZg+R_=5Y| z=bk5~XFoVRy>_i?_osc#|L)qb2zY#&H2HgNzn`$)9dvPh9C}vgdUlK6b}nj95Wh_% zheRUQHJy;q^6n=|j487E8rosCX<}0{_KEfoYy~?8AwOKfiItok{1~Dq07RAHU@NZP zRB&0M`2wlEmi3cn&ydR5D1h~s5bA(bRLbFlF(hS882x?i>|h?*i!Lq{_d7Ns#F^Rr zK)hpIN7MCv@SE6*TVCf&DxW%tA1!)6cE=Mfo21-UJ;&$AFc@JnWqXTUY)A;E2BG^j zd~~rgFn+NME4aTJe$Dk-O(6_%XoHPvYC}d=7|9IW(o4bo%EezxudtKi^5^wcimw1B zw>UG}GvFQd&n)+Z>#rZq_X@o+z1cTWbYs(zFkr0edn+^@>T576D%;VY$_R$0PEXz0VYXZFrzzz}RafPeX1=JL{VJW+Ad5h+&5%_MzSLYs94P>!M__ z(Kj_~+@ZR7=duBjJ_BT<5_3CRBdKm5>3PLk3R^IoJA7zSvR>UO>XGeLaB`Th+YA&# zUq`_b&U$&7l;WnJ{I5fjnBs%AK#X6tywgJ)PIbiOUqQoFlFK?`jidq{5W{gHDQ3UE zz7n0_5yvH<;PYax`>W+3d7jcj~U z)msb*{Np!1RpBHbg19bcRVL$MF`BEXTy%BZUk5);J;x8I2F}#3qW> zq$HOD*HMwOugrE==qTJWEzE|&rnRGqEtf9IOhUw`+>6B#K$l~0IV{KsdYb&g@&X^N;)Y?w$Y{^ zNKB@nO-x9}l9~`qEe0(ss}ty|cxlDR{*pcITK!URlEx~p>53a=@Wr?n{ogzb`w3@< zI@|B>%U7Y#IewPROB1>0T2)pi{TPwpOb`5wr@1}0r~3ajW$|A2*mGkMY@6^FPEKM9 zU{cGvg$$!5j98Sr8Qj%1_Q!i&7<|f>=Z|tu6-7A^nDY1O2P@E)@oQq4z^w%`?l$@* z8dP9Qum35(G~flJ;TaJVL*THX6dOt2JDSW!yIP#Lsr_k^CEH6I1&VC8!vW8REvPYX zzyJKh(YHrTOqhQeo|3ttJ+GcU7q^c=f!kH>8emvEdPsxbi$r~DRZSE>EjfyvyhAjI zqJy1O)&aIn2v&iGq^+R4LF$tFfv?4XU57+l%c_I8zarDJ@~OrSbA_F8TEm9LfbuhW zvKMatro0s0tJ1-I#!QTS=5kgoQR{7YJKzoNOJ>yc4h%8yP|IY1;Y<$NyV2kqjg7Ld zNO5K|rOcYA=mr|aQGyULNp|8fDKhtxjF>XkxD zmY%4%I=$-p+kwXh2L&~4$l)ATtQBAA%Wb}=0hTN&(fCuHnOVpPwNubwkIGVUX_UOf zTz!5`#)Y(C0+5Q%0%O`( z^at#50Q#cLASN@P3^H@6tnPmEU_g1^_u?OiMK5c2mak_;H(1l}iQ(B;1rfY;1I9nr zvSvD|>FA_Ur1!OTr#BI&??W%5^CF#zJ}Nr!S9wzM!BYSX{Q*mVq+U0-iVJ_9+szBm z7%&|}6=D?<@`%kb_*AZassx#slr_tN_~lUWyIYNL4iC$_q2h#=A;A^CGjg4FXXaBw z6OobWD|tUZ&S1BAC`p&G->Sm2<=X+9x~inv!CD-*y3?Yp>hObbVbBhowH4_?eFNb$ z;X)3S*480`?B)pDlaWClR-zO|`5{+%?t%;1D^DE!i5q_oWuc_FyDd9JZ+Nl<(TwZq zO8%|7yK@>kI=->gKqV=xU>t_(MW|`*7{yqHtm=9>F$qaUxpMUTQ}_rO05yDY4ykfx zg9Y?1c)nxg$udbV*N)?rA<7eH6CJm7pRP=0>#p{JQ9=C>##I*Afp7hn!9 zt{b!-4a9)GZ9|sYLcv$*s5_r*x@IF5qb0dZL1}@Ph7JT}vVBo>IagKDDnJQTRnGr9 zu^<+dIk(|J2z#Ut=YUIcd)0<1)y5daSxiu5l=V{Z$colNNNO8o-kA%<8NrYY0g5== zVK7}|Y#=FMTUdR)c%YZQCL|CKOoQfe3`ly@v@G6}l9ZIRp&met_mlbL2%s+FurEhizx_}TkAyZxGv<8~i62H~Oejtv zUcE=xNGwUIEe>c&9c11Ay;>9LGr#q$+`@GaG}Fbs3^7sFB-QNYZYtZcTP{G$0LiSbFcK5t4#ZsVGP zWm1uQiECUu)H%lQvD}{gGVT85aN}e#cA|=991#Jwc6KKlg*Auva<6Ts*CK!{8knnJ1!;CPM$p#5y1GO>g2I zTk@2o%}#n=p#spLIK2k#L-<5#y#GEk{CMkvAo&>V zFz3ZwvQgJz1nU>Rqti9#-S#Fg$9{Zb z#t7^!XP`Yzvb0If^M{k0h|f9a0+{0A7SiuAv$QQ#j9x_4|2E zJby@PsQD2noNW2)Shn%cNF<{)K5!kA$9S3sg#ptX?g&9Cd(V6s$Ti}9ih?bZIEbFB zjN>&nqLNirgD3)*smV$A@leRnIbfzuj;{@6KebgCX0OgKwr#G=UW`4;o!zkGm&GD$raCf0=>e*gV_Kk`ZE>zAoSv zWH3vtVv)?M(!%QI6V%bq+W_WyUD2?OPN9`ZgQO?~y^vybUUrJx{)|L~BIm@w+wW+u zy`T2rZxgYrz|i8B@ikfG;A=oLQzJnBf?Yi)<-h}ZX9iIRJ&*&hDcc9X!W#nK zOx7_{3@u+w(z5X><0F$)lQCb;u;CdLW}Xc~Z87T+*0;su^2F(Ok~dLedEMLFO4>;Y z6twC^EZ#?`)q(5*NV6jR-1odmSD{F z;o9%!#p}T(z;TF6Bo(C)<*=$Bco||LmjSmAS5AFKqGm-AfjCJ?sY0k$ z0OkmY|EARmW`x<$&;$Sc9B^~^TQU5)MS+jzBf6y{J3G!2(|gguie>XBGS#C*8>j?$ zqR07M)W8_HQKG*rg!h#Y6>6eJRt+MwIPtKk! zLb`R|WG6|i~eep4GE#+||2KaY10QlB?bp6wjT5ati$ag3Va1}M^ zxTC&Y`5qZM{&$oaq;H?*x{mo7(9;)qOl(6~0v#skU%8YiE&?K5+rJ~_i#dPgxqUm0 zXzWO9OA7eP_NXk~9B;&5a3r8Pxc$v<>=HV^N z*X_WPg0-NHbDGYEyXUhrPi~wGeZ&lp*{3Xf@ZGir4uuJ>_cFl2!MuTtsGgW1&(2$D zw&S~7*fsWXV>>_|0?n(ezREBD7qqB}`qzWzHPA~oF+a1fFanpi6d2ujUaxJZgvEzI zA(K9J;9829#4ba56wuJ(Hm=|e%LjzSI{q>IgSo>E(A+tNhy&`~#SIE3Z!|qz@r*TS zT1#;{D}a-iz-~HUZu~fK-kWAq-1lXR*xxzGB?4aQo9$MO3`R0xSEZS2@cSfsW~XxT zP9+|z87HJ08b{Z3JbqE0l~OUw{{d94@bf9AQ}PT8?wEDCEtz)g%*TC9QV_J`HI^^p zXN zTPbD*6IFA|?zM5rS1Du3Uq^>-MPY_Y0Ndx0Ex?BFgzk?i(IEJt|?STY!r9`RLYzQ(h9kdo1T&&iN{SCK(V8Ob0 z2N3vEklrL#bMyiYJTVXhT+rBkOPaoqPX_X(+6uerF)^!-pMu=UttnJ~em1u>Ud0Z0 z0Ur>*iSU3BWUaFUe}qlS0JR8lzygR&Iiv;n z!Ww+%|IsXGu|Ocf`h!jYEp!Mx@l}P{K?dYk2KaTf7FIs#K%;t742)Czc=1B~OMhJx zNM2q27xvsv+8YLYXW}?pX+(?LGOLVI2r0$S3B`u+WXUWf8kB0>4t3n;@_LFXPHJ#PmFVemD?s`L zJr#0~Ve#j(4m`WCcNUde?q)=%3k?oafWy%&m;9zSi>#TzJiDy2lzOvhU|OP01*!oL6L{iYP_EFshEO1$Idt6uh+kh1FN_%ZEH%>+c|wp=Pu zDd4v5R_#z91quWOKyoO6s5~n@VsH*VI^MtcA}@Yt!WHe8mtnC9`aV(ARYbSq!P5I% zZ07VIC&eX#s5>h#_irwKD#fRM@6Dxj*uL{CQO^)CHJX=l{uwZL|4j(YW4j-|cGXLw zsqDC%;C47$++}xJf-?i~9vgV;U zp8nG!EE+(7+>|oMZ_%q~#>5)C7s|XxLJho-C@GXRSLzFULi&6#F(+uJZt+poLkMZH z5UIkDO1~u$1DNh?qsPod{A!xN1oX>V7K>wPF&x!aiBNncG&@!KU z$uN?A(D7TJ$)?uVfOtq1%ydEh{QSIshsf`eL0nwiAlKP1F^eIu?bv0d^=B*Ugx233 z8jhS@8mHBD7^`ys+k(ri_n;;Jb4I@cyy20O8r;#XKfFyC|AIO3kCC4+aLeAAX@J%n z-c#7`3sn8|Xy=ZiC?s=F%4}%OhF-C&Le%pi~YIZBs=4AnP z9+`^5yBv|)xj9JX()7$xEZ^s3=HQjB&ErKnm23DlBhTf_1!CtOD}3gcFw>?EgxoUx z2)2VVh`I7Zd4ImGQ}1G^(Vjm}iAv&bf1-bY z7dGa4u{U8N?e8RX6)tNxcqm!tyPGqtiq9Q?X4PV=EA>7YH8|ub4N&_qi6&4@SBDs$`1GVZQ{nZP&8b#%v7o2!x{GK47ni0cm^p2WmJy_Qb}bY)9Q}$eUDgR z8a~A~dZnx!2cMq##V$!QfTEjf4E*+XO_!+MO5stW*-n-J+8+==(0SCYb`=^77?(B4 z{cXPt_d(`qsEVMdYR~=$Q9m&hUQunL!&im|=h{t>tYNt&Ev~nyb{>J)vEzDwrJsbX zan|ZCXK?FKES(mnP+GVl>+()0^p5aWs^P=ls0Vv^d;D2}Ua&ePg;`ZRq1CPeV+a$| zw?Jj9O|r}^>Vq|w{R-dPay?93sfsfoZQqqT2T<@Ua?QGHHQtNVKh6Q`m}3sR%U(2F z19%y!m)n>X+Wu~|z~fS3%|O?_e)a&7Hjh-pBs1hivh9ko5}iZ>lo@@;^Qd!9IEQP;w&0%kF&;>MJUvLxg$W!?b6=T0x(PAc&Z! zDdVipUd!Ud|9burc9&=K7)z zTZ^WcIz2s2SWpDWD3SY)O}!K9_uNoa6t;rYqpUh?p3baPgQaU4;o!@@ez0&ALXbeQ z4tqvn1!ur5=9D%88wbrhw`ZHDDX6HXHd$Y6?=nh%x_7TrHyuVsZtf+h!0Sy&g8#r{7{W@5e4)#dnp+E z^)%(|AKO9y(HMy-`K-+u&Gu|qfH0j4+N5th&41#`L29+K-b4xhSr70@p``qi2W=cu zyM1@wV7d?2d6^0^k?J#*>kiwje&1svVp(n3qX5JD8K^ep0re+9&q(9o+c!WX&hL6)~M-K8P48BKo=LMquado%bPtajs^KAkZ z3H1$hgBZm+CGI;{O3HTxIX!u@OpT|WDxQgPpy6Y#?P$jK^oJ=J3gBLWE6cd9$X4as zYm%R8qs-1@gu<#Y(z%8rf9QKNNJt1}_1ap8{?lnzSS+I3#ePvKZrnhaOF89(9H`P+ zC3e#hNxyS*e%>+89Td-R4J&UlZ};{5)q5z8JlZv4f%E3E0ls&$IQiuD12B#787#Y{AeK4AL&QbYKd512v! z%g15()5Qa)I6Kh9UUY;hsU*GqUR9e*1A>fhe$;?xKA4J!5AauQIy={Dn5}mE;E>=S(U>4%Vd7&34|=j}4^Q!V z+2(Ie@#KA_+T=cW{xsjfv646dG)8Ngq^YW9`}7GZqjoEF!6ua>g3k!FhZoj%4j`6J zF<{>$ zHB9@Z-&u&C)&vrTG{}sw%qA)dlyyuJq)?)y(TQWyPJ|&` zPb0lRnN9X(2uDwxtWK{zEL0XRD|>2TpD@50wsK~8P09A+LHiHj*7VP;0^p;K*)L-R zFlz?%%nOzRehYZ0wVg71l2^b&r3eM4(*EP20>~&hM^{(-N32KhU!k&Uoetqxo86dv z{ujuH9Aq6(zshhi??{WCyT%_s=O0q%qOp}C$afqe-w-%N@6Dt+r;=#s@^O3hlg@WF ziiGViwXxuG{8Ply`0HZl z(ykF-71W@)p;isk#+Tbyx3YYiN?^)nkIwB>lQut6!?LRWewnMCp^_!Fencz`rq&(y zBDp70wX`JZpB!QkqGS5Gnixl04(Q=pi3Lj{M931RH}}8>BiW#i+=ue)c9YjRXpz4? zmpBJ6e4KdJ?q3VBZVHIBbP~tE@XYCvetzZ?lNBf`LuZ2 zN+s!LUOt%CabEgf&J;)GYX@Q-e*$k~%DD+iF*o}q*^8@9n2P%4ohiI>b`v25jMqX& z!3lqb9*#pZBT7Vwu-zeiMTGDhdi3*9a$F3mOUN*|3hw|nUP& zL^6*$Z+9zD=`msg8AQueU(mhB>x#^cYr2M;C6#NuIxQ6G+w1k028t0o{f1;anAtGX8ZWacUwV#qk^dsKA zC2XeWAJuZrfJO40oq9ljBAYJDSkop(IMGgDx^3>FSsFWk-Xa}U*^#Vkmc#jac!z3}yQ=gwshUi*mJ=}xlipNH zhYm4}5A%Ol$P^>$G*R}8tm)C#%V2%X{i}GhW^o#s3M+BOKsC%_@CX{CqK^81G+lQ%+|Sq5+bU64 z??kj!LUhr42w~MFL@!Z-)q9IBL<=HX)YUB(t9OasdoR&j5Z?X%p6C5*|NG2+W@cyZ zJ@=e5AP9R^OUqr~^wb>26)b>S4z^AJ(0emgkOy~GHbs$hv2_XQp4Ev&a4=VW@(7JR zCqt@&-0xK|$#Lb0;Vo@d<0o{?qn>tHReoqd<%p^dJs+Zx&Y>TbkV7>evhs^$gB9t3 zyGvBwKaPc?u>H&SU$@MyNFxgfjmpYtj`QrCx|6cAQ)D@w;4obmViERJWohJG;S~f6 zD1{pm(zUT+usXU4#@_-KXFI(*~SF1u#E2Cr2)J-i7Ohn3=SfM;6FpreX;ZF0Uc43=zjw#j$7>524{G zg$6S#?53N<89BG3EtCA}(2sO-zJeD^bb(UMoDAgU>Pr5v{;m3_< zi8vxE6~0@|lAl>wm<>3c$Ugk}xJovz(R+2C%(e!Yc1Y>JRi5nx8k-CTu~nyVXs9O{ zBr=L|VSN-Eb}f8h=j=mM4URp>um}Zv-SG(sGv%jBFEKg8k!UwV(aj|R3*o_ zzumk`JUkpdn?iH8UTy7__%c`cWRArrF^O8AR{E!5JE7< z1_q>OgjgJgsi;YMkMgWQ|8oYxiQ;aruX(9Bq2fZXrEY!X>|RQbc% z4^pL0@PGU}%n|u{IC_eWt)o=vlSfAQ;QFU6rQbBy)ETSI@ufs`Frx6gu@TL5LtOY8 z5i763XR@=y0n!Q=`Wk3uRYA=9f!La7fjgt#i}#`#2k$cKX?$qVcob(TAxRGG7DpJS z`<(nkfO7?&;N)q{afX#gKdi|9{6)<*kzr$+h;I^U&sdNDbT)9DwHV@gz1``px89)} zI8+cQfBL;;-3$8PAtDQQ4MDZ($huX{82sdfw6XZXJ`IKmbhyJ5v!YcrOf|;?q#L&8 zvDxUGo1iXBd`?==4B9FjTf5=qiwi*p#n}?>kwzn1L>zBltY`3j@~4#CT*jz;jlxJN ze`mTzEi&r;tNZD4bTDjH7!vqlXXFftYISt-JW*g<2b)tgC8k3ub?Yut&K1_KSVX=6 zD~rD?yxatGMsd{FsdZwbj*`!*jbw!prSI{JBiD$Qx2AO~C5tgetPa^`{vslVNFA1B z`_80fh?ODyvpy^|5(@;e%rUp&{O|f89U9gBn-7Dh7i(PzhxRXs8#C3zJhbnHd&$-3VYyb7& z`snYk{-l)aNwZ7U%zN`iVRmmi<>&K9DENve-Q6lzj=UT0Fxwknn%dPBOk{6W7f7{j zO>HXLK?~E$X03aB1e?o!mm(9X_ka~z3*_#madhK8qSdE*T6$64j zK!5SF*H&-tww&kY?V@@kgByi!{85?HvcXsk^+7J;|)D#utmq~oHxBT8>in3%W)PC`U%c7#PcF6di^#-kA?s-;e zE;~Phm33p|Rz(p7jgT^d`kE3R6~5*&!>WoA>X4Nk)p;p>{nZbWk!l>M+ep_0A{D)FaE>AxsU zEpj*^HIJkljevpI&TlHG*=b6N=lznV zsvLhwt19&lZ0Lm-fhCscBY~K5Yf4%h)+g58sDJP@ikdKCqlpusRGq!rMXqKxK0XfX zAU3zN3NLv2U1+_r&^Ve|7xHSvYjjJ3t)_>pf?ge7mi{|e8R=QPfKO=Od|C_ALuvB2 zBYG;bY6_mJ+n}?_qb`5$GW<&x9mrE;za2Slhiskg5w}(~)5jg=#rM+m%Vp0?Vya77hG~%3i2=PW>n+*A7`!$3A`bq&^J*ckg`CYcYtiQe3rpNx{o7z!qdbtB3&~VNiyKLyDhTV)SC8T-QkXJ#hpOBhXOk>O zQ8S?}@zycq#F5|8+H&45Q}bD7zxg~kloE{oy-Q~ZA09zv%s75=hwxcnzu0N3BKFko zN{>)?pl32KfQYMNV3d_j-P4e7`Y`>QGWe1m*b>OD6zTPTYXFc6^zB+?T1?PTV}Y%D zT4S&UmmR(N6qO48+yxMd7sPV{Y)*F{$R4qDym>mdefTv%hnh5?^J!UmHtNnOh<&H; z28l5uLs1PJRJ`Xt_Y^7!HK#uDYCj7BHn|lNnT57gYpNxyA>?UbI+?(@j^Me&i+85w z^WJskxOQo&-xyz*t1~=HN}XcUM^}#f`%Cu3Yr4EY+Q{NuUipd*^o`MH=Q2}x6$1bL z<9s0j)zfl%i__!z9Bmj>B0)Z!$_f(Gk1=dF*B+1G%4$&;$ihrmtD!Dhpu> z3uD@jmesii@Ig6jtkb@dmEXsoVML%1P+X=AgeEGl%icsTS_T8ZP?NmZG#@C{%l`qA z`UdN889o^}m<|loH9>!W=z}S6nC0w%rfF{)(B1?PNUUMD=?ujdiP?3VTE9WC<==dPeSw_S#yeIZS!r}_rACQZ>v^fh)Cbd&L3?7&wF5cdcWSfw(y~GA00HeC41W%gYsCH7 zl=|zneFlOt?6x7mkE;)4|2EPct&fcO3zbvvPweJCr`siX4H4vn9CEG(-S)wr zSWaJ=QOIXpQb^67bzjp5y-I>nX`oCW%}Lc@CW-g^)mm5pP52L1f(pr>j{U@P$x>(N z*nM+fzrNudd*qn)5|vG{REA{~F6u69ZwoTI4ibqFAvV;s`aH8-Lo(sM&-5$`&1G9O z^-fmW{-palV}-P#kpK)eh9f?LzJn;>aHL(}PyQht{+^;OCsZG~|jFD?i)RU6*WzZ@(Z%?3NKu)Ouh z2uTaA=i=uU@OvUFx*C0E6}|8-AUn-@sSjFnsy~o5PvDF`a)G3{F=c!iS)P#AlV<}D zp0U4^Nx|?HFnwJf1+QvmGIu@IqRi+wtZ`!#vn2=agd$5L#%poRo62*4Z(P0nJiOCP zKuWDYPfj;~8+(3tci0|a4FPafVcWhY;F#huMrhVmP1_H+8x;^t9;mO|rd{y}(2bwvXwQd!Vy$|^$kox;)?nLGaAWm3H3R%Ko z{aRXTkK0ueRQl<`%t92xh)K+ME8lRX1>__dd8hpoZ^YPy)o1A@`JRQj54~i%O(<~$Tvjn;chF*a=}hdv;UMLj!0=>$#YOGx3loDLR5(Z zSM2I^9mOZf3q!7?8n0Rs0MNmNb`?43&Bw~I=kAr_6uPEpn(qpH*{`e-cg6^@D)Xm1 zP5fWtVxYkh1G%-{vK))>?eiq34>g|5onNndM>QdmP89goJ#*X$r+*sXfM{Q!=?|J{%rT3ZKCWSYb)Jf&SNq~~*SVtVz^%2jk1I)hvD@JThIWX1 z+(n#VB&((Wa8g9UQq{|MYEhhRg`;ul;xCI3p|45QT;+RbR9Ox)$m**`w(0DJURmwwR$^#cg2)nSA#zylswi7(GIUf z8{7Pu3~vs4x;Kr#;C_K_$>wT6e9a4%>u+S1;LT4~K>m{A+ z_}+{My7@c*_Ey2;Z$RXmFv*J2_m4>gago<$+&d(tNW3i?W)Ol6n{qCD=W-Z8XAaF5 z=yUCzUO9g|t3m>yIF#KwiyZ{m^Cba9L?K@hDHtz6`-HDeW4?#8ysWeskL6^M6t(jD zsnZiD0H{+qjg*I^0^7{C**IZD7#S&Ug?Nw`s|6Jm==@t(-Zke>gjhKE{c$wHa0TAi z7D=uY6cl)nHWptN*r|A{ml1;5Mb0u~KNL{T_$b1lV}|RvdCA^&5II1C-(B?E5~EJD zGfmMkY9dkBw4RLe_B&b_!prH{r9#5rFh3ejMl?W3Q~lpAR%HHlixcu5ORqfH)O6J` zc^R%Sr3sm|9YKQ#+dSL^0U!zl1i@grPI7HL$oHzie`wT!haHDnmYh+7eE<;rm)p$k z?9|k)-@;o4pM%npT#m8wECwf1YNjC4NxB8Q`QT+9J_$^?&>zfs-kG43G)g-Zq@Dfh zqfJ^jgHZ1v=i(rmrcJ0qE#-MY}el_kwNUu>Q7=xpEew z0FlmUw{yokmJT#|X*q68&J^ zP*Rw%oEMENFr;DnNigMFf79rEwM4t0E$3a9`1(e<_T`=&I78XOTQCSdd>JM1F2QJ> zE%Ao>LdW7%r!_pDQk=)2VxWc@F+C4J>R4{g+cKo<;Nre8PAzd?ga?ILP2_$$K9aE8 zqc_a5X8+2~K*a8fyW5~pY*#~6&%AJ-jz<0LelOe0jio^GL32xc=|sCH(u|FmrTSG5 zlXO7V1%~mNNCiVK>KFYJnzC2hU(P}r3vZ+KVRCXkco(qhyUzt-kR-qCuT~y+WQB(A zfXOc*3#=%mUu9|JzQCb1rrg@3F&SKw$J!Ib30$yW6>TQm7 zqX>>vwZ+cphu1@ENRJ|i#jDt&zzcqAt2p3=t5vI<7tU`jGKi=*uR0z{=AKe~ z1f7k9FpaYo_t{}({L%X2cjCUAs+skxTRUe_`CuZbVm88E=AO`XJGd|W75gtsuV{F# zuR)9uBb&>a$w7K~120HIJh zt7{IEA0Oiq657YD`Xhk79MO-xP2@yF8b*?_6(+j}+r=ZF6N4;IX=*{HWWnDNa~?!~~xKqTk@I zMr(8&rhz>@9A_BD*sl-&MD)mMa4JKxgd$KPHxele<&IUty$%n7a&;r5>0N;;qZci| zapB7-4F1kT6UTKAA}ky9g1fu>)u*wJf#KMdc<|Y(Gv9uSbWpt0#XMz%ie0kR9cgy; z$57C#%#-69zfx&sCj?&S7_sn2!gG?WU3aw!TNC%G!(sfm79HbXLJ)1Wd7S?t-{0Wh zhB3&MAfdC1a@cTPc5|RqLlFmLhmz@6W5*>KHqN}@@7lY9K;hsE4AU^&`{P7bpWvaD zVF3Iq@B7Eo#g9zY*FTXDug#hHUP4(*0T$B#Uiu5dw6j-*=@`3qZVCf}g^NA1<&NOU z72A{JGrwl(H|9s@;u|?jp1@@2-}^i-n$1vzd!v6bMCX1+PvBkP0Uv11_bUdQfjeG( z=r>yHVT@HVe>g?^)}D1VT^Ron!NJBby!$(gJ|`7Z-dCG*U;Ckoy*;Dx*M}2DLXB6i zI`Wa#vJR&fH;-J6X}m*+%BHSzRUzY>f?$ zOY6<6_LobXiGTn>h{ukA1=jO#_z(P$ci}a)0RByfv1z`>m-Oz?DXQ~xneBk<`4Xr! z!4@S#Z0wK3%SPVQX1ZT$l$&W$qes70RY_;n-g=&0CKdj0vs8^mB}x5s)Dd$W2wcBHUC?>Plk14{GN zfn{u`{giT%CEHQbbttsk>m2znW$I=Xh=B=NVzfO8Mvd@*1xn23{fRnJS{<0SF&Hmn z(Eb>yQmzMh?+k#j&5e&58()}H-S(}!M+9+TE06YEDWcm%n*nl}sX*#+<0(*dey9OX zwHWC0;Va3Y&2rf?;fW1IOHsHF5IZ6QJ0_Fw+o2%_+=fQOM%rRG0y)A<>WKJR34E+t z${QFgFBvM&=m)vDU?$u|C{Q479ka<9--z+Yq&rS`{|6WQ$nlMeuX(@T`|;(4zfr>4 z8eGE#9*0c_9C*ah!nQbjBR$g+~z~VqHu;uv=l~lCy zT8%IaEeI;QR#|Q)3qi=5onDu2uz|V`H_11RVBnhZ`Q2ga>?e~Ks6N{v&!O)R7&}CT z8pd{v*b#=X`}C1syB_l3#MiXKq#mjLqK!X^p(&V0vyvbG4iFlF`%bs{VvDzReSQ>( zOEbF%xGcflI^)qsb#RAyfPAbkrg%-Qsz))}buEyGR4=iV?HhC;P?OVBJ9)PdBJKjg zm?1}3x_@tx+(ESFZ5Scw{A)_-<}8y$r%`o`Xr`&Nm;2YH6-rKXgsJVdD8OW>jET`` z6k2IB6!M?%Pz=KK{xgT4`4VscwSs|{_EzzWR}dM;__(z5x^(`(zxTG{Upt0m)#>=m zSGm7Lo1*Tf#x&Q0teEJ$U!hWuJqw+5o~hV_vbqZTtS|q#Z$dd&jk(gA`^AK>|NJ%- zyCR-p0fC2aW^E?{D+TaB-s*F0V=c*dq?~BKwCV!|iCZaugPr#J%-?^eOyq+8yqId;Jb6_1;lloJAqnl4 z;{Io>A&E>~lPx76)76r^QJ_iU*>R|MM{NnOCM1v;q8;Z$a)duN9DAm*(U5TA%Pg^y zLCN^$OSc?-=U9#;?#I5P-)*u4dfoPx4Jg;nAs(d-Z2W(`Dvj%F+r=QM@7l6jh2!Za zx3xEvCQm@R535w8ug93Wl7${wHv(Hjm}>Hfwi?!Iycska;V7q6X+au`c%O~78&28I zD-I0mg5C=v*T{OZtJiBBETAnBsE4NDI7t`6sp8TJ=@lZukXO%AQP=9 z<>_e$A=uOc4d;lkL_jaHd4^=UJG~BO_|PxX5uFHR7#h8!MPl-a5qUR-;8i0-zIhGR!EkDswz(BwZs~^)%uh*EvzLJNG@ocfx$wi@6!OSG*9U9wzyV zPbC%$_GN-6JpOzQ03PsgQM!?K`S4{XAapDVs<@EqG4owO4qJf5hh83;B^DBMLhKcs zwwL#bq0P$;>ojTNSljsAu+7#_d?{gOAy!?ly5qoQ&j9v#dj+A)3v zpta$dzP8=d(2iKbtJ~}f6m=R3m~L#~uERNUbWHfLP~U{B3yJCM{1^n|w8@)PkhKY- z_)}lZR^7Nm-B2UoO#?E2+1U;J!^0TjC^Gn%lmx|U>}$D+ z&@-1xeEwmKp04Ws_VyYe=bw{#2oe$z)$;OG{Mp>>=`$X2ZE^$}_&4&2?&hi`7ZQAZ zSMcKpVc5Ro(HtJPK;F&syKPWfsk-NZ<7k%U7>~FumI-0}73C!ORE8L_H6$#`ROeqH zF`uQ@9LroO?lDEDE?0&Q?|N;;UFF?zH)ObmyqanDP>v6GJYH(XBV5vU)zSGrET7?$ zfAq9MtJ@w8fOInRNO__R1rt6;>(<^io@f`E^Bcqs508U2U0U{wZ&o`7i~UtDYJNKL znzt{_?AxcJ(nnFv-m5vVc?hU}Rdi$0V;2cjZ;H=|MdIM#k`NNta=u&`6Hvo8l$_jb z$383Q=B%l)sr!nPp%_Tr$za*9`K!GmYf$4459bMHW%dzddEWnMm;s73V-iDS9Ond4 z%b{0co4?Q}&>k(x!>FSDoq>uU+1xmMy&c%dt8#4ahVqrDY3`yjX<2n2{x0YB5_TyR z2jNK-#l4s2T0HeAWJ^w#%ECn4ntiOtLtWTw)z#5VA?XD$YOEbEh(vZ`0HHHUDwCK1 z??|;@H|!Zuz)$mkwM76ot(_RZr+oL_-wL|A+FF;(IajT3co>yQA<4(V02$mD`0G48 zzRO|8?Y7v-_SV+(RHSTHmVF8FgP(Zc0_G=He>>UAv1g>Q%G~W0)YR07CmJI&2H~c# z=axjGGJejVm`dVLvR``@HcVi;0m!;Bw;Op~Lu%Hh!JTXiTh>$l|3sjQ`VhI9@IDUf zb!TmTDhX>242&wZFTtHXF=Qa5$j=JGkTfH~+v037n4j;Jap%WOIeT=_U&hoAi=~2A z_9KI-_LvpCHgp~DBGHmNlarM-3p3Tj+AXb^M0}JKvDhkqMn~g02G09PlUjXxfIm+5 zgE^RqcyNtz>Hj&w0ME~#!> zS#S?qmr0xMxn>XD<2RBpx$iKR;DNR0O?x zRB~`fjR5wP2=@*tuN``P5^v7Kg)AkP4=DA%q*m>eq{ZP9jRxwxESeDPHMs*TXbLr8 zi3qoM@`!L`myVB&EZ&}{%sz-FkP{cj&-hP%%F2pe&H8KbCBQKY40|$mJozW(C%Y?- zi@5y9m*n9u)wPcJWn0-W3-S9XTKX>-J-pmsHv_Hj`Uu^E2T9Iaxv=20(JOQR^P{f0 z)Yy11^*KK-D9$na0m4v_;z=&S{;mtS>B! ziqLo;Ayl&cJ6Hdp7?JRm4>G$v$&+#d@w2Ub(`_ckzLM8-U0?)Rsai71Ue%l!Vnu0D zp?grSt6d#Lj4>Qe;=kNUCv&p!YX>!-u@kVIYg&z~Z9O2$vQL6PDLu2*0v)PYz8U$) z_8xz3@72)t;sFYujhzzt{!Q064x*B%o6;Gq@Ab!CkTNqh_0w)n-k)S-^lY!D%5#eC zNOO0KUmus|GB3zbt5LEOEc;EtegY~$Hk1h5puxdrZaBCjXC#mDBn}E4C~J1%>RKURPASkt;f7<(l)U zX+&JRF*v88+P+tEW<1Q9wq)Sy9_wok2$Nv)utF{A$uxe0MP~zOu*$3z2i{I`oRmgZ zt@mB1ioX+3hz(~4bsMIUGUkqtvnnHujae-%S&faIAQ{d_FOwEphrCWIYLZ0^YNg=M zp*3&7{AVP#eiYK+ka9h1;A6hjz?Z9=4$P{Gyr(7e@;+xV(XNKPc$O;QDs_Cda(XEn zD*#A#PyUZ8iTtet977LJO zNZby1Ke4nX<|F+tna)MN7{P6X4o`AXP1crtD>hX~GhP2)Gy5Q5qH7#YbJKcrW98Wu zc7?+TC2EVgLya3p)&^ps0oODbsE8oE3H?VpC*d38!y$mS{GuxH10=4GAOIXiT<&^O zrcWg>0k*LCe)}UKJw8V=tyYhXQ49M$fQ;DnGhW;kfXnA(OT}umHOUdQwH_J$kN=W+;n!cfx=EAwhfQ|zq zqXjhAC<$I)V_oxo2e#2Vza)x;(P%IldBn;sw21t(b5Dzc!)@V3&n4TWP!{Qo7)hPg2`OTu=& z^2I-=1hBF73Li38Z47al@)7hH4|23Q=8L_yaUPY#r&faf%IHz|Fkg7{R&1-~O?~~7 zWR%H1z3rv%l|!HYnGPF*?fDF;n-Sv8!ePuC90^N)GG!b;)Bay zZ(m6Yl(av~EQ7JYXZn1f5vJ9(`0bt{%MTB|m*kKcJk>AsJ|8iWW!hz(C4V9BzMW}& zj{!`%Yakl&V5__hB!1`N9Ca5rX&G2udzirxxPKLdpMK$2YqW1BEEL-No)hQwk7;{< z1!IMuhlOpqdxaG$`rwj9M%)kYt*n65oEH~8=LGK{9c+{L^K;!d?;mQJ6del= z_#j3`K_|!YhY4#I-=`^kmh(wXFsWWUK65sG?RdVG@$Fkr`}=_BCMM5G`6nrmftND0 zpwBCYnH|??yA4MB#rBwC78EX-(VA#`@>p&dE~G#)kd;#hFS~WB&GMj+R$hXw-k)OD z-eW{4$;+Uh--btE@~lBjHxX4N=NU%)j+~uORu7?QGpLc@i}#8+`exdIR7Ej9dx^}$ zIkm?^xM=dI=YhwReLB@|Z@SSPhV{Y!F z_gWeY*4O0sNy10bH%^fJf&B!)*Y+gdeL(eFG^@$ynyc}M(_xok2H!u9>K)^TQtIi; zB{xjmBZ+(E@RPd3^}ZI*R9(C-gH zuM)Y`ST}OxmBH~*O3@3af7`OFfRR4%3>(H6gVglL5Qqg23-l;8@|NT%$7(L$)#1PW zgKg!6KIuy~`IqbOPj`QVGip=pKmIg7*a`|pv+Q;vd8*m|ILJ88l;aUXjqYy`E?!9a zo2`vH0aMXzsHcm(S6)o`JWhQDm_A;MV23P!cw)hPyBv~S?l^0~*uPh!)rO`IgIHTz zx6M$HzNQbU87xLx@ARm6#lQ23L9@9y$oK1JOaZt4>23rVii`7!R@&xazY9JrKXNk4 zpGR2ukW<^oIX8*|Z05F<5s|ZnOlf|E6yyTTef03LoH~>IZ*Yw5PTnvzi#mU8aLf6R zZ|(K*;2D(e-NC-4C@;*O0?Wb*} zKd{OQAU66LrgXcZG9109G=!0dcjAI)*6Jm#LL&{pv=IN|fd@9Uj0;N_$OZ?JyeEKp z;Wjd%>0oKHM>!oDpc7V5xmsVmAyOCldQybe*o4pC$Mo;O0z=eaY=i8NPOzh<;JPjr z&dJc#@DI{i-?bu4J=gK7og@!L*~a;Kl*?hvEroEnz-E7e=avrBLBgQ)2S;IQ<>Agw z^^k&6r_n5-@ad^{1_p~gS+{6$TEq%5O3KP!7Q7>YW{l>|E9lrXRZv}UfY1GetJ4+|CFBZ4N!PZ->IaWwy-BC8G)4NF*Nq*ZVA~hnagicz{ zU(TP{x_dCYyU6}{R?-SYq?flhG8snJ8z1EZ&m{uxUY0aK&n(AciKJT}j&|CRvAV>A z02B@9vh!VdZCUO>xo~E0EQ2gD(&1}O))fJ0CvrIRlI~L+`y9e2@|dbEYd7F$*UN$* zzUe-Ra<0bje}RvC)NyE6;8-wr)r7D|n3S~Mxz$%o~0*vOSZl@!7~ z)^&!Pe~prq)a~XkM72T-L2q)4R`pIO2gqh%=Yog}UA74!j=KmI{FJvOqs~^bnq^N1 zP>ee}h-_1RI}TQ`R0ta_-$sUDCJ0S6d+gQ%0g#dso9@MMsN8P}K)Wkf6Q@KKc587$ z)zA0t7rUc~^1#`n`9!i>6{Z~N?A_Y_c{dC%m-)L*U9+BH%5;e*krS8Svp#4u<>?qB zyS_irwBLn_=5e`+2Mfjw<5v`7$y?x`XPJC=`8({!;3nGjdDjHtV&>-mpanjlr{IJ9 z{i`~%iNL1=Qv-x z`IFx@YPceQ;}pE)Dmc?%0(~X^gxv`+TmO5}M&JyQnIX@wm%>LqpB9%%#UGy@Iy~sO zriUF|9t<7LG5LYQt`n3EA3q+ZXreW`teSjW&Yf^*?a}gmAiIj+NEeKr68_kYWOZ}o<#{ZT5E2VZW-t3WfcEz_O8-^Tk?T&5*ut_p$dh9-PB3X zBoYV4fY%bT+G8b4q%lFPy*ZM+&OTxw#AYg(&W8pY(>tAl;)u~O7^#j0VgbRF4ey#P z2jn);fv3j(#(4@3CsF+4)aL=OS?MEkyS*wji-RueKcSBmfq>E25`f$dMGK=EH2co>1UUk zUylWrALYB#tw{vZ4+u*7N{e_OFO_(m;6O|*BB$SvmuOdcXuN(%4~PaVzekM55@miy zIn+mjG)t6}v8DZ?$W3=C{gP6O|lq>0(8 zVePfSX@pDhNCJXf3-XQpSz}gL_vgXWWcI_`rWZT*xr@TY(^O1+2mmr1s|$d7+BAT zw1n>IB?_JKqv63|H-BB-hZajb3yKc3*=>lPnz>#HF||;z&$w=kn(?=}M06)uqu@KD z33t)C5goU)ANUJrm7nj~g7&5#lG7;JLewB)#>MD$eTU8uc^^^Ecy@1nw1c;m#?ANc zuXa|FTsyYEU*G^9n8o2^~_iFUyc;LHyo@yPi8UJH|k zj)4IfWr!S_;V1nWTGM2th~;17BX`7&2^o(K0UM>NigT^6!6h~j*3t9 zLv1KVluS)6RBl^ZmQ0;BkO4^_*eex$dV$Z|8jkt4BFL-;0LcaabFTzKt!tp|^F?h%ou5sZJc6_lG=@dV2?+RH-&TC@ zp$)>`)FenFo_@4Z75XA&Lc+^wkDjCPiiyr( zXFOjz4flifszeCt2?*_?3$``Ts-k1teijT@7f zI3gci=$`~}@-D4_W}j&Rl9vC=?#X1o=0Sl-*D(){salc)T9xO=O21n^#`fUZZ8!^> z1KNiAcaH`!5Nn@3Dd;&rG2t_Px*B@m!3Zh>#LofO&ISj4`0mpWY4yx{A`HuMGfGo_ z@~_gqh`&488?jlx5>=56s@(jgLR9R61rVdn0oOj-fVZ?Zd9_+n!0>)ZvmW< zY8`MvghqvnvX%*lLFCQn^)HwPl`St!wy{&!N_ro|KkZ=QB+rwt3|dhqENh~JpKHT= zV{0BB{BJ~Q&8>n73!E8rHCY;CKOmN*7$^=qTgDe3dk+&Quc)Uv_1T);9VZpAiX6f85Z6^+j zCODMa@clBOkUgtB@Jw*>w7wAj2|&Q1B-AlWG(ZA~o?dF^czlj!IMEVtf87g&Ek?l^ zi=Dz6W71KyUd{@tzbM-!)u;r0#bG88ra=DJ^8O#@;i|LbkBP<;7ugd4x^`Xxb4F!#z(s| zc?+n?b{-h*v<&Ah3&=g%_u>2#!8vsONBIi;Jfhh-4sP8|y?Zju|>SS{Nck ziS`N#6bM{1RUH+}ctD#S?YEZXhGxq__I&g|D;YnQUHNGDoV@x`6+9b)H~ToIO7Gy) zF^_=JVMhwi+VRmSLk@MU4((v(!}*wH=;?x9Z%>U!^=C;pXCG7;lM%U2y-WsdRfZ$4 zEQz`?B0u)ODUfXN?&A-f9upCRJ@|tmw;qkbMu=MT(uxWxw~e$R+fR_-at+|EOnkg3 zhz`u{aTe7VG_5q{+;Uje+CKLIv$dcHqW#W2D*o`ML*-3wG$yV;nn&-p*D{`~GuDd= z;n6O}nL?CbPgeuUa1=pbsUA9Yag=~7gwMxwEmN6t_o}eNpV8Afm|PK3(=+5N6wG1w zsU+}EPR+1e+IC(<`VnuighnxnY6Ru#j^qmka}e;*L34gqj5I-KAdN|24LbV>2$n-jJc)+re%5; z6&J5~rX#VDN8Vq)W5XHA5kkSU!q3sDumw6}J?)p;rD5EoFPlD|q%@hGT9!iKx)|nv z=YPE^DHK>II%H+lG56#!#|uIK-b~tq4_6vVyCUkbzBoreqEe=hK=N9i zsCabO0OK@eADslTHtP+8^L#086<;>o=&uoQJDh~_*rj=;U8rlB^@bD`=QjAy;>bC6 zTO~?2%AJSKMdZZJa|w(ANJ5>LavO7sJjbD={6DPMg|z50&djq#)0{q+m&dl2Z&hg# z{0}49rR?-kTB3rq*!ZeK7VP0NPds;>(_i85EK`{lRu@&;W|8O0o~PP@KT2rVbCl`+ zw7GfGGE|vi2W6C@)%n!e)2Tw3`K#@v&`w6X|>)CJZa*MIGb6yZveF_;kx*!QyYaohvnW zQ@-Z6*B@40yhx(7RF-Y@b*j$S7m;E-8F8K9POK9xLf6oTdgdv^lc?YdcSNMXExKP) z7em4@i*#2G8DeS{-X)!9KG?=6XCn2AH)`_?)NnfJzc{R&F~BnO4vlxJYyVz)I+t}9 zrAo@nXUQeZG1wl@24eR`2GU3f@&R{_mq^lUYVA6 zwW-Gln9A@#i>h>Wn~x`p-ON?tNno5v{>un7!XH2qi;BR4fg%S7q@o1uW*Id0ZIH=}dQWprxq$Y{VOxDS5|oAij%X&zk|-ro2T zChr*{x#V&+v@7Py0z)UG-qpWRQd>52Xz%QvHC-7Ioh7h1Md(A^BN!C)YcPpUJ#y#RLr!`;>ZkshTCtWJL*Ip7Oay zpfzoqlrjiIm&(A0FZ+iFV2bge&>PR<*t%)qPs{^aW6#_@ks-h&_?XyZ^&h71b(gX% zDJXyKC)ZH(hPwtU(|0X0_3iU2#YhoJQBwT{*Wu1*o`#UxHv5?C#hQ16dEIaJ#A*); zL_DM!RDWe_4^E-zu9x9UPK#5&weqg;^=iU+Fus-z$@p{u%;9EY|MKDV+&%hm8USv z92zbq9LBM03!cs$0p9eLAPx`NxIO-F?9j08epc@BekRpHNzR(3!=ytv8uT#6Cy!av zDh)U{B=Y#i4WdD^(Dh{I>hU3Fv2<=d`EKSeEmk6XQc`_i$ZK%>Rio%55dj%)tXlOF zt_I1b%B815+TTL>W~2fM>}hG;hqFkTvUyk-KFa^b`+r)1%xq?5JA%ed9D0%t21-+w zy{TTswGUfJFLs;U?~U6Uh-(_c!InA)Uu*{kw%Qb8CB@W44mW8w=!FxLsTN_Xy(dzL z_g9E5b3x2UYctKkLU|2Z3I8Wh^-r1nnAzwpL)-m4Af`?|xpBE+aIpIg4j5!3;JPY^ zN!_y<7jL}Z*DBwn^2?2;&NXiSi;4`-$le5F z%P5-~r1j5$68Il&PN#<0C}waGOQr^^bWL|oCdDEZC&0L%zHfOk7XEnFhs+K{U1Mm24p#zGHR$VwDPA3)nHG*lL{Jvo0&7~i5wdk@!Il{QY z>z{O5;OOx-Ee(y4tOC!E2`Hw}J`&d~3Xaa)4>M^{ip9tML;$5PNbCSX$>a6ZI*^AwnK6)FDueA{PD!Bs+3xyMEQgO(3^ptN5#qwh}!vQhSb*K zJbo%)#~WmsM|$+G-zj+9_V3_4%@_9 zB_bw1!y$l}N#!ZA+C&=aj&D|4)H!`lE61;{ z{8N~$%yqB$6Q5tF3|UV3Gi902*EwtMZ*jpmbkqzC)I^PkC_{l`oapZcTa5I_=I!2V zUB;KbETRqs`Ox||3CgxZANnW86))%9cP3+bCnueH9PDlW_Mr=0!T!)n?G4YnatHvQHkE?_>Ahki*`D5{FC$@#&g;iJbXB*uy`FN#bqQR+d)^7o8Bte+x&yfShFcL_Rxz>HSSk zH63*Q?3vD*#6_{D0#1Y0^MzwFp`4YGOWw*Xq zQv*UTw4*`m>!@_^Ts65H;jJ!QwEscw7;%Iaq+ehIm5A1Stp{n9JLhRk0CG-YQh}6{ zJ5MbSlpJc-Uf#>7=ou?p=+b5z5Mh0k|HTk1(iL5rl-AjCSL&ZfxSk;F8$(*W=^ZbG zh{WE|zJwD%65sZ($`xT&eOi8GuQX?N)|abo8jo2U4Lo9tL^e#bsVDrVwIP2_;8*e7 z?!)`P2uRjBM(xI$b&5e*>W^!Q(Xg=_Z7nFE&1yLr2&AINHsHNU==b$a%zmnu$oktR zaP0;TLKGFbr&OM$XJjgB^SS>(S^Z@|SZsB@(t>I46rjmKLpk8`A&uQ#H_f{N@LFEg(3@9d8!;lHEYe)9Bwf|(EY#E&@sRM|Ta zMHFnJqN3X&OqFEFO7FPR3Y&7k#)?_p^ClJs0w>3JzSF`LQiFUqK^$h*^;y_3!3#?n zYjH_${nHiVuFC!C~A3@wgV=xZTdXUzd z1ZF;4?^j;K!0n;wXmqU=fU=)gU#HGZe;J960P)_}TwSKEA1|k-UY;~As$iC_)#DGH z&i?f_zv5z~p$KKXA#d8qqYrl07GDp-4^(TBh&3W>c_Zh=E=g}JHS$%DunKh*Wp=6z zi7eD|8d>lCz|`jg{O4uVUWk3x*Yt)cZqLV-4ZImW>2~R^EjmXtzaDdj_5Ulbh%$T! zowaP%kC+3UD3$v(RMq{?Ip4%YW)u}0LuinPrW!*pZEQrA?Cj)Q^mJrga=weX4t+Qg zd-L3P=tDrCetdiU%ZlJvou%$nvFlUb&)O+@$exgrv>RpuhAJo?lxHvQI$g+#xw-5HM^VmZ5{GP@b@1J8odF>c%XD6QW`^+6W|8tpuJmB4yh@swGEag}>Ci+cz-C}@c;L?z& zI=nt!N+_hJ`kj^pKv)!e>P{-HS~Z2)k-;bw*fChl)(<-yc)gE1D4&7s(X@Yj)m`;p zH($rFI*^g5XSr4yOg@_dQy<*llxDb~tH(Oz zKkT;F8F=uLlh#NhX37Q3Yev8-xjy0l9vHx6)dL%aF3$Q9OT(}Ie;+UU&cj?wG=2)0 z)!F@-qLrZhGqyS8dx#UKRGPz|i23+E?!4UqztKHyR(Qw8Cs;%_Bo#&|I{KM@W~N8A z=FA*dub%{yLWk&LK9L<=VQw`|oG}S8c1s1#kihl*SFuEyX0=D8>$*CD;)#>`w5uN= zd3yk71V_Q|F?F?8HZ{}X3#p@>AoO%c#~ATKro!4%Yp<%t0{`p_OJ-TWg9KDTuwKqN zhIydk7q)FPJi9OgEVmePtUjl@M>y_3KLj|r>~fg@cngTG?t(LIbvbFLQnOD{{r58~ zkj0v({wuQYf_o&+-%8GSL+`Qk*G`Qwg-|XPiRkL}KKptq+yb9wrhyC;n;;4gEwp$_ zB6iET*&425dCx4CxrRYJdU(g-k104f{E~jXtM_T%TmmhFaLTW%wOFTwp6HugQAfNl z#EWY*q_fH4hh~lT^F^=avX1J=q2wf)C>k4gra3PwQ!jhdewRK^4%^A}7n9A%khRlr zfxo$GIYq1WuCZ*wEFe}Do0+F9MWFS00<5;64OHGks)CQw?E*FAl!)TvV&KmoV%bTs zK&{B=a_rixxGR4o|M@UR^%Dil&Wp)d)+q1?d_l4En3q;ZiL*a1#$QLcF$gy>ajd4J zqJT7=oLry9usuJX$Ld*z2iS2-1+P@?r0-D@PId!G#qnB^U$avwaAJ@gj}OLjm53l4Urvo!W$b#~^58ip zin&dqRdXgbz}DvdWmY?Ug08>mhYcoxgcBKrI9oDJ5lmyNa%B6ff(*?HgWWTGvj|45tmW!mul9{IS9+RF)Kr z1NJdkGtMYz_au3b5ZQy^IS?Q795cX76LO{&NmvX9h@sD)S#CVD5p#!3F&>(Ulh%cf zLv6Qso^Xods8jB$(jKq(gk4lw*W6DdHoB2u-Hw6?tCs>Y z16HgWsaq7Q?#2|!-97%b9eHY)-XiYP#uqYBWF#4=zK+NDYjW}%;#uw5K$a(>T^p_H z^cy#^OnhuTzQuXGtV=`5>4XYt2`y}=n@~)o1IJmU(o2R{7!@OT`^P6xYkybX)xUti zkSO6W+jAnqct2gE$ktO?b`efT;>1fuW`#G6_OBkvk=83)e*Fr-9BC~mnr|5?zO_{l zG~xst!tv=QSLU03AZ||bTVnVr6~F$;!ihQEwSNSnKjwJp@Xj9sLDSbvAM~5{_}XNT zJq9o-u&04NFhYQ_MIWhi+OtmBliV61(M`E{qd&nzhj~{-Xx8r zUmqIAwsGU#VFM7`a_~%c#Kn79L>iFZ{kQF?zqxsG-A=@PK0(SCBJ~0Op64TI^q3IVY4>XOCJMq5PeoNAQ;?< ze#G&IKNNG43Sjo}QXmyiWnF&t_8wu_d~p9P^#P@{sFt84<6TgnDq( z&@)C3V+`jH4-tTl6Fgo9*5F#6h5h*VaC@}laDyQIT(z{r{>10PGtBr(ultW&nh74u zRqEQwJY=shbz&mbsRQu$Ras&^hOqIeS^KFI9=CemV-dIux2kr+8cxYR(l^~?a;6SM zFz)yxv?c-i3x$cF`1aWVFia7aUlOr0e}SauD;E!QX)bKlM?HLt>rq1jx3B7*u$&S1 z7n}X)o(a-7kdAc}8lvMX#E%Wxn?FXwZq*{g!_i z&s+#9u;UrLEubZWQt+<*`3ls}oIHA>PNp&E+ekTg!k<@~w)$+stBypFBTdUAv;V?* z0c}+XbCOmzy@xf%+|6K4aWHR)LJRf^FAsvbhd&=%Cp>3m^&7tYr@tj7AwTEnEPIF} zLjNg*L@$J&`eFOzIC0KQ9>nu8@c%nWp+oSE?Ey-EMj6r3pYTi{Xs?~yyHR516?r>s zgwl!f?CjwobbWn^5ApU>_%d@S_es~UV9Z3KDqH66M_MBf3M@et-@M`~o=EN88x+EY zc+dX4wzOT)f;3k8++H$)mcE=2zw9BNh2QKsIIiN}ie!1yg|M&IOBOrIe+*CFFz7pv273^(o@em7}YN+t(}h-SnIZFuM423ZF@{S2-0_O{Cn2w8t7* z<+g7t1o`CzeS9GDAwCeaQ^5-rgX0w>7S8B~te2$5&paNJlfG zZozpOFq(GCbT5U7S(YX&QwQ!2pdy;RcAS%vkBY~@e;d-#PgbSxv?Z4Nm%>U57G%I1IUuc(ZE;iBHTcZd}ebj8sbL0 z8A#OtkeSWZx;ssfQz|TKmgBH7*l&y1$79m#(}!vLFKiZ9m$^T= zKb?l=c1^HSA<9Vl-fNWf^~K)A#9RUyHt{PSze@?JV;&?X#n_b=467&r<-$ zXhWhmPX%~X7yiJvUZE)HcW^N~8v}n;VWgR6j7avlJUS(vjsR^jeoDE`bGW#cMx9+F zAEIWtH{7u;d-mFdMAAO(??%_WayuSqBe?Wz;#G_xV_~oU zUOQ@hhyO^>rWfJh8+y{W#7>e;J;0px0lCdNIYjJMg70I;?;15_VO1{uV?DYJZlQBdZ1JG)m|)|4^j1KHZZ6tzu|u?nvGX8(KX2m*>mS4-H-Z7xr^K zMjzc5+GQJhI*8vhY`F9uBK(-d+c5&`=RoSlzuX;V!>K=H4*A(8aBt(o>3T3#Ev&@Y;`Bz``7Ys;8(w zfcerl48JgeiqXZFelL5Sc2_8RzW(^j?X4SfGw@MF=$Jna-`@C-?$?Zp*Q?aFtZaza z1N_vlL-EzE+wF4(V8i_H=_P)I4#IxoPSBqcIZByl{CsR`^ot&Q~4-6wh7&=aN$#6ALNpQ=LFcy@I3 zWVLUL@UG+o<$47eBN_<09)V^qW@S#*>o7VOIn#~%DL{_s!lPzu7sM?g9T5Iedz`j^ zVFoeS7W~AI?v>y%|9W^Ipx95>>r!W9_&=~<)g9Tj54+sd{*j}0p%kJp_Vrz9pOcc; zNCW+i4OauEA$b(8a@6xiYcbZZ^tlQuPEOxRF{5b!$W&hfvHATk4x0P=twuz7!if z31RPk>IT)RvX@Kcu;Ow>z)_Gmf>hYtHvtXv#uSEu5Q<#Klco`5Bi?3iv&Ik|iYeX3 zz}bxpti<#d(+&^m*+ zyXKz-k`#xi`-Y^up42Ni&hGPm8NFQItL(8%xEKC3TJ%90w=;pA#w< z6F`2X1BaIP`f3mZIs}gdGoD$#T)^S425zb|%8sGs5K{eS=)q_`kB5c!1PJV2flj;% z{P+fjQ~0%bRg~=NdKOXxflGnL5>6$G{s?Kw-fzaLq|u4 z^s4x6=CGC6C#<%b+w{BZp-ld$nN0rhW@{;3QU!X{zN?Y-FDVjqH9MOsxF>q~7$J#> zs^ICOr8PzyS>^-jSw|0dXY87Z*V}3Uq^44%v<_#Dw8?>F)3N(yU?j@jtQK(NGB7Jy zSz0os#Cp-V_3sgyTUv8*P4SD?){G2Zb>jB7kYmNq^n=`Ha1K*we{K|?daa1{km67p4)TQ_#j*x40APZ-ixL#Zec z6K#!#ef=v{C%KOUM++3RHzbz-e96jox~3W<)~6M04>VAKjAPh-zkeI5@Hr~DigIB= zJTe0LM{l-RCP1(+T&MLMzKY+@DYK$INIC49MaL;c645Vc%RrJ zBQDL`V3$Nk^*Wz1bH?PrlC#?d1{BEXWov^s zSTj-Te^6yI$t|y5dX@>Up?*`CG4TTvj7Jw2UbBc=ZZ95xCrzkNxb&;jgi4{BvJh;~ z8sk{W+5&}u`3=XCCfxQpRN{Rz=@R1l$3@oCT@-Q48xab zK$e63%XB3lJ|d76<>ZD+HIYGfbIA+QeWU~L1uCrKHcsqbkM2(Q5PK7v^B@L$_~)rI zDIedn2W!)>V|k8rsqw2d+x&|}tKG_J8Ag0H_nF$9xp*#hq_8m0x^)xI$WXxfI&C0V zOe|EoheuwDh(E4o!}aZPEBq|#xb%#H3%X`Vy1AExjf+^d>#Uj+I%h{rKOHG+GXJeJ zF*I^l`E#}yBhljaw}v2O6;IGo1*Fg`ZmF603+WD#6s9$$cDva6C(>;G7?4fKVV(63 zH)M<@6*YX73NnA>42=bM4)gz4%V_8Ie8_sTsRn76R#a>^yI}0w4YD&z@o~R!g_(xX zIWgp1`HRAVUGTu(&ThWHvE`H$eL;K%f=MIF@q~i3kL$f{U)V374heIDNli11b2ooP zn=|JtD8F5Z`*rNIQ^5Vqc02&z9|)U6y>uuJN$46LAekQZCbv$9i-k~vCH)Dr<8#z= zKfuYsUi4f&RqgGNi6XDmOfIH%2VMk*t~g8Pr&2ob8K`3w{k99V-rHw^qU+4J%A0*C zRFAnAS_B9FK}v(={UW^A{q=-VW2i=op8t*v1KpjlOP>MU>cgo%$bBLvROYuD8v3z>F;Aqtd{ZsKBpR>&bwd&2eye8yBf-h;>^`l<1O=VVPF%a}&}Fy$V^4tia#Y z7ToqP;mEM?j8oeY*3LrNZ0^HMRrNP7ZV56?U0mY&U{pJdB-ZY(m+5FUmtjLE$f;#; zi7`9iH_ZQ$Gj8LDe2e05yq1LYr}Kp9AZB->!s zs)D@Dw1$+_ps9gW6J$Gm1eRsQek498c-tTE$*SmcGF_s?M9I~Yq~UEqvFB&WUTBr{ zk-$3Au%V$LGE+`yHmD|mcGiPr&)CpO{{+WpQcoC@Rv6qQemVAnXdj>qohnjAqltDLJ&?dSD( zP4RsgCjIG9T_A4Y85LsDlnI1?nc2HXi3{KCeGHOT3B=|8bt)SU$(U^(V=~j+@zDJn zBZ)9^ooPn-MLxTlf=07}1w$lO7WNt1-zLoiWAH9k<+8Qb8{9!FTH!RtF4D0XG=6`K(gHoleWy?zn5XL-j_viza1fTo z$XQZiV&VGhFR1Fj*B7ROZOJhx**{3x_?Cz*l4w*MyoU=)8<3pBie24@2JI3W`jjs6 zft{~_agSd^*DTFaS>-gTH8*ib+nE*I?1C+91wX%v4z>|Xo-*E8tN54cCUep9YGW$l zU8F8b9VP(Tb=aBcU5puWt~{>iXI#S&ke9cdXk|%8bz#z5ZZtm3@#YaiwVdhM8&ux= zLy`cvn%#8Ec=Dr+iI3PnHXToHT7+5A@+_Hf0jXO8YhRXW_hb_5jq=x$VdhoIV=hoo zO|Jnhi}>$ne0WOInu0v2kgtK#5N08~J37I;^gX5svc{p9m61T-kSG9%FDXc|EC3DLb6xtrh#Aq$w%fHsidF z$HZ=>pH~HaoP++zrggPO$9DfE- zp)45P3uPBnEO~0@LAG?bwnqo86jMuOd;`%=7-$FJk@fW~AunR?bpZ#;q zV+-Le`2Ygvv=)<5(X~5#zSr(uZr?0=A&YZSyb0c%`&@UB&KcIUl;G5IX z*+Z$m|#fApl z_@>7FkUnuvAq>giZhh%^r1gQ5c$hgLb}^=~eGW;JwDy~a^K!bHor2rSY7zm)bJ(+a zHwXb&5*@JK9DA;+h8Tk zVx)SvA_^9GKO(wC_j?F)WI@l$3q9i)&% zM4NRdo?kXdY?oAK6|+3|po>7Ocj04+4UHm(NAoq^cgEfzVJwuzv$NKQBX;k8E2b4f zaTSsB>VIX_lHh1#_gucZqIaA^c`kB1Aw}cg9FyJFl%;HH`h!@T=w_?vqhOED)?(9U zjpO&eSVP{P$v3I_P3vaNzVB~A$2JnC=s;u!laAR1xnKQyHnjNi1;zsm*j~$CJzM}R zC(4=GJdn2Q3p&x8APVh+*YmBIT>b(D1I)$w^nbn#(m+td7C`Pu;)X0e6aW5d=WY>( zlJ79!Onx2McC4XbI2z84c|7k<4>XP*76SB}qlni{6FqZ*a+?U83MbXs*%ASRXf~4@ zC0)dZBGX>KF$vT`@i;hgiNy1p8`Jv-rBuVF%#NibpKU9u)eODhrNM? zZ5{Xd>eHMVmOAh#sK?lQK8hOcif%k7i4bgremkf=DQ@!ghCKh6R41$wh-vSe!3uHo z*aQZ@8w?22C{sPX`o1dIDF5G6M|37J19dO8U(}PYT_%x;*H~CC3fa-W*aKB~!OO%< zDgSqseUjis8=I6l#I^Su#w72Z5Snr#99aJ^xajS#v7Jsr4lxXq zu`_73sv}p9rpH7zA{v!y>T~A}9QoGm!OndHq|B%@(p7(Dpy&DZeL{mQy@k~%ZdN20 z#LW}>HM%Nc-iLmj>U-JK;Sqeh$Lb~X2W?95%fgq<39H)z^%J~5qV=yoarr>5rR-yH zoP=3X@)T{}_p@>D-_HaLc^~JlzB2{vF0&)dGF0+*k)jV++-7>usu$Ozk%3%95ra)w zIjbaUuUb}9Q&w884&EKQA{s;2hm@wqwwpkE|E~j_GzMKRldqsCgj^+)=M(kKHgBUE`DZY?|Ta}nbB)vEvUb!E?d*w9sE*W=V&O$aM5Y--q4!(;{ zCVZ1jjRiVZHkxLF>;6uIE41X3vc2tqNzEFBTEE?6<0Iw!F>iM`&CG|C|xi&a2G zjFv{+EKRW<*23kg%hhw9V15j?mx*tE7^Xzebsv#4GY7Ze9L1;|E5Gp4>1=WT0Nye zZnpRdBNKe6N?}Daugp}yKMj=%0`jRud4*>d){Xl-^FV?;425TQ+jMw};Q(_QzasYZ*ddmU7O;@xfJmaR6nCFzl& zx%N%#+VR%l=yy~v^Q?Je1nX?V04?41M~kcufs=H5L^|d9%WYyyu7N#d@}Bg`E}hCp zKOf5homGa@N-hKj9=i-dYp)M;S&*?F6*pa#O_Jhu`C8)&xZezAVW*UDbIGl z!yC=}CW!j5q+b$$PD5a2ltd?}wG7V_^t6tGMjGn{=Q_c|l8wqRLVXJ}{0 zU~~ikz|R)uF-#$e#=82t)m7eQO#D-^ zn~18!oihxQH+L&{N&C_=5!w>r z1wW<=kfPs1=5x|H=x9ohqxtS)|BD%VmjGK?k=$MrvG)r=Ml0?gm!g$JwE}*N30m&G zNpYFx>T;t3nHDx{FhAGdDc&yLo$d+=5}-14$@#R4Iz2Tynw`llD6jSObY1i-l4$Xi z-MLsBH?RhNDSR1l1PvZqT4-$VhcYvKZzjfF2t$V!DXgRl{pD+9Y2!X#kQfm`vg$ua zmtF4s-b^27Rn?hMo+gAX#j00~eQOmdyfiUNWc|9;|IgG=h`2$Q3?v#<6_>D%qN25N zKc1w`n?26;Bc{*VIo+}k7KUZLGcA}TfVhY0KO(z2(PlR1-mX;f`5u{-3bxK5pX6dG zKlXp285@Vr{^C!0>KOUgSlF@!BNiAZ1}^{y5t;Q1h2ABkH)K?o0k`>o z_^w1?H`_cV^1)61?%~$%ylE}LiP7O|cfE0D9)E^IaBSqJhRoE+8xrxkHu)7`Dqcfc ze?rZxoYu4O(}jn8Ip#1w!!#|<-Vs$%>4hqc{F`HjB>f@u$EXrwS6WG?#8=vUd;IyL zogMP%5f!{CwbeUrx9ochfcuW3hl8}AO@p3eV;AaOGY-CnkBFg^Aj72l*p%6%pm$;e_^1&&hsXW{5q*XkR2kB zq^;zC%HeVE^l(?8@U5M|R7w$h;n|7};qtA{j9dh?Y=gqRRq@lCRNa~mzs&|#Di9|B zBhj8mZuu6B^`ba2S@RaBt(-nqR4CEE`-J^oWZwVtxs6|JW}Z9G8ku?Z|-5=J>ojkxTt~f8?NTpU;1W@kCPVw05k-KoR*z zpEVR6V&bE(rH6mM3k>~ovYrMU#xnF3bGYvvrP}%8MAV*@+!iPq1Zaq^!^{U#YZu6qDmmfjdt2S0wo(h+C(*x13^MS$EQD{4_ zz0)#A_if?Rm^6dpKiE)?btkN^iN`m(OkN~JN>zsbKf9`M_s3;Z6YPiL0kuWk@$ zX2%2dfu8iRCmd40>vZ|xP)FlH>#TW3X2dj-&{9+46MXr|!L_SJ$o;_qn7l7r;=7e)e~2+C!tAIE#<%{7M++*r{h?z8dd}JSx>H_f>4^4<>JY+9<Z zqcKjQFj~XzkdSJ0FXD~mNIKj*#zb_57V1wByWmL&whz7bCW8eMMna49@1ysP8V@}9 z_#|=h@x7uLOMg)pPN^om@zA=wb`MV9Y1!KH>iB-!(&1QxEi7Qy_gvaZlDZ$Fssn@I zkzB8;m}>@a)ug^KsB;=+*rDdITqSl?Vmy*)gc@8$CDsX%HFQ@H=|C_^47(Uc$T}Id z0Yd8C-G4@c|6PMW(xpJ439U%9tRJa!;KJy-G0UKj;4?dp2x#*8pKj>A;|lK|G(VB^ zns^>j;(?t1?)SgT@6zDeH(hQz%IV{uKJm=DVGI5E86k2xQ4+v?&AIK?6t~hz*)MSx z+6W*{{<}iv zf7CPFfJ}K!WTSEEcv{Y6>5|*P2?kQOzyD?V`%yEI(6X!&BLmoYixP-Z;?^mOd5wG6ONlNsf!tPr&r7w>_ zf8+r=l+d&qrtmKU33?xc*Vr$wJ5OD0^Fsu-Pfx!ze@oMpU^x5f4CftNF6mJjaJvuc zQv3MZ2cpE3hDv6VsXwAQI3APKf>3ly`fn;7{|vs>BU}KLonhIWXN27@JZx><`(TR+ z0ST4=!XBgPhs})}hhW+7Kxf~4iwiegS*Hv_)n<&I66idQtobT7At1)jg)46d`_nai zgZNPy2-_;H;AvCPrU{yS%+iFQGf#=ORGjgsA{Y^8VD`bl0Z=Gx&`4H+9VAljoJkhz z;h3N$^p-_AWdP%cz#(}goM%5uOrV)$Vy%fwr%PG6%X>+5WtTEA5?IOkz!-fYe>7N) zooir1e7mWcy?&;@3%?;G{a#52M$e(>onBPik@80bOM+8!rqa`=QXQM;2Jfo(W}RiA zxcZ>!1=$(}ZId8V7gOR56l<07mQ^+F9_u2MA_Z<=yi6oaVUyW-uERvyG< z1%sAOn|a*&sq5HR?`v-%tDH+Rpv%aFi~!-!w0N!X-sb+5K>P^wf%SY}#MI{Hg7o;ba#D$o0ZBYMQ`l0B_UbSM=f}CtcEhf+C0G}>84wJ29Wv$ z2rNlGcAeX{AQ6m`kKcyw2i@S9W1V)av`P68l>AYd0F{-zZ~yDH*k@~~<{cO)eLRnY z`qC|f_$^#0JVgBm7t%%hb{jQ7{tVcWx?}ad_4Be~@CiR_h#Nxy79B1cIIq9N>ibDn z$s?~?s=J5w*iXl2onmMMHJt-ze+m<}Mz+t-D>D#c=ag zUHPkQ`10lIAtL-m^U`nR>mN_P(Q259Qxd=H|2($5Vb!OjY3n=|*KGVY%95kMm%K(b zT0#@5a0Q~@Taz`UqPJ*0zlp+~_4%L=Sm8zi9e1=L9+oLh?Ta*J4Cq!RHD?|?hT<>BHUsnyUVo{4ViRxOFNJF6u*lnUfb0%2o#MBM-m+Zo;y|*vr8@;?< z(a5UEV+cIE)O?h5lV@UIx+VwU0SISc^)bBNjwydwLH0k&zAo%h=d`=zZS7dM`l)yk z>7S>?-p-6Dn{KyGjx>@(N6Lq@AKHCGYKxd}wUjU>wx`oE(z zj&hsbX2lNzEp2{Nf{r8LYDD~k5;+R-e0l6FM>e-A zu_((x|2lPRbd?=7Nyen4$4lsSv9Xz1+)Zv-qC#@5CZe;@dz9$nuhN?eazU#>g%XHA zi<#>meoOYgf^bEyt?MedP^P6_Sk-Q8mmXzR%O&pxq@_kWd(;0hmPxoMK_bniE^I^! z+0}e$>FS|HB-1ZdY7Q@Oe&18-6f27m8qguOW}Xj7Ee1<^v3PS>ELm@D$qC|^w*_F= zS+(tRD+SiA0{Tr)OnZ%FrgYSe8G4aSpy@)bM_Ndep&-2d%y_ja@|U6ZVo3W?0V_ea zy<%@Lf3l=t(8b>)9$}+7v2pM4SOC@JvD6yzE9kDw$ZMc%y3UWM=sVjCe+VUl0+()A zC%L^f0721sQ-!*_Bg>aYC~x(+=cRB$etzi`u#7T&=_!w2iJ$exa&1jDv0RBa z%5*SBN7+Vm4_B1+wryoMq}2BTxV7xlJK^@PHmN?P{gSnKDm2-()%7uE(7c7*tJ^C0@vkv6Fn)3)4?_b9yx$)HHGWd)MWFV72E9qkSM>11!pI72% z_4SV%1#}XBz2Ms93QYSyDe1yO`_E5bi!$u>=e@cbgO@cm162f%WT8%u_i9xd&y+i+ zR~E2zV&%Tl_)lzPz)7r&QjE=8l4stAm()PRps&W#p2Sho6A2g#tU3l?V${(-5)K7hEx#`Mg1xh%wW0t96ZyuOlEQt#IkR=!H(5@qdSV zH>-1B7Y^I>lUoi&o|Dqs%kA;+hHj^LWWVTBcOT`s); zdtj`Nai{ey2Pgc(*1q?8q4EaCMvkRYfBhw%<7RK-qH)kwNlCP{DhSB;>ud0Sx8ggF zDm^Wk-zT8ML!HGyZM6u{?|io$glLGBCV~h{}xr% z?{#Yh!3+_mSRndU(a}gLti-R!*8$_E5X14Yd|pcD30f145+?cIC)DkCe;?fpXUcE8 zbwh#=nruUk2mdSk4a@a#NQ!4Ci&50O71Q5<`M9YE1h@^LKkb{nMa84PV`5^wArH=U zwPU6T-3`=Jql9+l(0r?;#){w_A0Xcz1{zf z-&#CK1joO2Maw%&gOM62f^53>)7H)SV}IgQe_~cYMv+jM!$^YH(MtX8Fg7qxAv^DIlI~Z(#wB-{yG`v8G6#nZ_P(? zNCdHZq8%>%mH5PNYfAwxxEH_WOGSTtqYm_soy_Oo0(-b_C$WCO6Pvq_bR^n`EB{hV z68iXV6pycWQjFrZWYR$xXTwznk){?P=mi4@F@t9MNP=g(Y zFqSQJ;5fC@`)}FIZ!9&gjQ7wlk7JrqE_C8OKbu83ZC3mqwi#iw#D(QdCyq$sJ%a{FE;d%2Rvg5aGQ^ALole)h?)jXU!0=Z2} z7AeYH3Y#eJ$rnk4jZ-OPihP@Ds5cov!7n51Gc8*u{;>Wv=Ov^>=!a^2rNwBSS34#8 zN^8~~9R8Mn;PU2eES*sFEOyo_6rQHd6nlD?UNY@9`^?5O+sEq1L3eXirU4wns}d$# zxM*>p;5fzlsE@uo`BFVKk_DRR8vS8{xWsC1-Y&2`X0O?1`8Zp56NHu+F^7)HmR_=b zifg~e4;m5chX)$S8tbqTx)nAuG_X<9d3`ZW&cq%BoHa1h^t3HguU z(31q%0!_b9@0!&v7am}&V~`|4aD9L)MWTUB;MLoIOBzIS&{O(`y%W-;$GT3&pMW<* z8G`#y%w^&q+cPTqs)~xkg%*auP(7e3p%d;vo$~o-pv-9Mm3>dm<*K)VaWCY5_Jyhz zyb-(CwjE)WCW_%e2CL6#>J*{>doMmU7K2|#Lydt}M)ThEJ6rpk>~^L#6z!%W*1ym(DiMA) z)I`oN783ziSwg{u~ZN!}PEo*QxKag*WFS88=9GzAfpxpFuwJ^W5t@X^DFYBu8ruL^F^-=GEzGj|?@4sFt3ajI9~!-f zt!^15O@D+2fzB$`T>t&e*XFSBjx%dEpn5pjz`h#C0EE;xe^VNk$pe-jDXx6O7M1a)!?#%A}YwzKP|EC2Y zR=0sdiG&fy#uofH6r@!}~!{Xtuh%wUZr}Qjy=IT>cQ5Yvt#4RM(?})fC?6ulP ztx+zjvCbl96*=a41F2039QOf7*5~qT*tR>jy5t(UtCU0upA!!A5>$>pDOH}``lm?9 zZI1kMc>9_lQcmS-ui>;sN3)25ch@46N85OopESlCa0Wd>bXrc|f#QEIUO%+*RsN+L z@2OZpS?7x?YLa|d%*JTfk&?)qM!q1G!a`(6%#!$cqe-7xTJE4_x46{|e*f!!l(4=j zNDu*%09D&!iHcPGOb>0QI3th}$9$YHzkI-g%tS+wogI2}BK~E&j!h4s^Q-Os*PGOO zO`tiMzl#`!27-2JPJP-|C6wSyH?#*Btr8Pu$VhCHaK%kUHUR;q?dg{gD-42LrBM$3r0B|MHSN zJ-Di27&3$9c*BY&-!>trk`VUc?G*+(7#*ANv}XmKExtfupm;D$;9H)m%~!4BiqC7+ z=$NnWZhVti<3T;31$g?NJPRujQUfgC(7wYD)x=ZAJ4KiU)B>CDZ4L_w`OPERMuG}S z0vP&~D|z~m-jAEJ64%Gy^Oy-IAw~h40>yZbX|CDMZE$B?fSa1V6UD^$vlkX}dkf#> zIxYfpSd#03F@8)-x_nk^02gs>tB%KrKTtYVDruq-mxUqAJT1K#5xwKu5-8n@mZ83fmP?^zsY^(kHE z(L6gwrV(CdQp2k*>@)y@z+2oLXR=YFYyN`TddP9wd?obv$;mWuTApFnReYu1c4b8c zZfHHF)33$JKp&2!W$}5Ph%Mc4*p9<}3YU^r1o92j`;ePJ;2#=%;pD=CY`wfbC{a)j za`*Y0HBmB*+Z_f|6859=L{DP?;v*%*U+C&7ZHnMX1hcWw zYG2*@G^@fYC7qSwaLNu%a>O}*P6M+iy zKL5YG9~=#iZ#(+V5OO}jfZgtJ3A=Y;H+zv05QVz#Xgf@Z!9PBcuW*2oQQ@-|M5vj^ z!eBMr))ej>g2OKA@= zhRD5@ctujwwQq}9a4T;AF`pK|F4}H_7UvTmiYGxW?)Za0yqxTbSW;$uRSkJ>BqvUk znnJC*n5C6rJBa+EZ^$>!mvaLMwZSKfy-4A^QSRektAF_%T$eUKPi7(V1Uw(9VMHT5 z62y(GAuj`2ZQ%hYk!U=g5+Va|df8Ec1F!>#a|PU(7fQ~jvUWbz7rSHGoi-TC*oV>9 zImu+^f$+Rt!1yx^7kX^&yLwApi~ZM9;TO^UVgoEM15x%^$F5$;p(?zFc1yk-2!o6t zg{Us-Bs#28exm5ft+2CxRM0w9F=F*oZ0?uO=HtfQ3~2`%n6>{NNmU1y>#_)Fz&`xu z`UAI!{rv1bmUikRhev)G8K`FcXMEhRn+~6_g%~SUWdz=IglV55e?D-02wVWbdM$GWET{WVEs`OQ zw1Fu3AKlIc!6;o|wEd=?uUggqDn~*pCHmI8hAW0BySlD;)GBT8Nv%wYsWX+QSx{eZ zuP<(0^mkHrPd+=t_ByT!*t0#{U2jmg4%%0{*xH0h;DRRBLq zk4a)s1PZ#CbEjN7Zm;I^AvoWr&06e1??+LIz>{zsV0{J6Czcl>S)icCnfC0U>+ZBn zCHgAQZaOp(;1y?%{(K+hk+D)bAG`7sYBQg57ZIW86LclpAB8SwK!7S&Tsw{jnk0Jg zWlQk_;%(>EW7U=!tG|aTsytgC@g@UGB3(d0j4Y(%QW?O4&W^~fUwJk_zx8<^iDPzKsBKOH`e|U* zzP+(CGgeq{bG*lFC0S2qW;#wBs7MSP4ieGVMO)Nh&eVenZN11A{!3+1jw)tyP?4*Z zLSD>?o7Yn$W4$mBDFV*#Fu7KDd9dSeyAqHoOme4r!VB;Sm-a8j=MideP7Fy{s`?F6 zi-$54BT=orzR#mj2TNP??Ky6e5Zruh603G(Eb}EVNnRlFK*dR7YUZq(wHqHu_^`Ne z79V?GN0H!tzz;y3g|@i|G~h_!`!;1SWdV$snrte0a8DF4XnNyjta1-{rxD><&KbfY zKDRj#C3qe8{F^m1OT)TWTxWJSw1V6zam~Y`;1``h#uUzkr z)khBcA6^xf0gIISAaeewy1aCyNce+)b{XW2uN_LYP`fH^ehR&8#pBL{B3Shf2Q~51 zUCbaTNV~&8!_CHd?#pJMaRCtP=YSp4a#Y98RwkJQcVoXKSA+fN$;Y$O*w`cX!|jYd z3ns!zY^+}y!&zYnyY#iG(^l=MyTo0j@Wwfj_!IcM?l%a@%A&i5nj{Rjcq1{53{|jX z=tyAEa9OGS-s{b>5fSBrKr^pQ^tRS;EGaY6bprj%mqXQg$#6H>sOmOr_3;Tu`tx+{ z=v{MDXtPgWONhygR_X_j;oMeG$vs%OWxmz{{l#s~&#juUC(qj2Lbta@Zzh^BpXtWG zR#<2&R}Rl%_*Xkwwks)r`l8O&w5Xo^WS9-DQIyLlVe;bXUN}fnfs|cILBiYVpz|I1 zb7EXD=)f;WewkW-N09=%cCj$pS38&bikQs&c@BM@6q1MAUI58)LA%R343R>$C50-tI!709=sK#m+PQ%zbOD_P#N3Uhur8%Iu~Lf&Xs zvH?ro5%~akC%uw$1U1<^sG-G}vR^)Cr}*6yIByYh+Z_lPPu{1dDL)$^)>3$S+Ev0HOFpCgX8^vhBkiey72KibF1sU*Fv0MYz?}`O@4uCR7^mN&ax~9_bultY z$cSDfa;^&Emezmw7v9jOpp>7>&xveAm^&q7ghKGHWcp(cW{_G@64s?!(L3qsauN~@ zVIYXp7pUir1~BF$M1{i5`bw>#omjC#nL{)3~kOZu*?ng7i-l!p}MdIKkGL zEOWtagqDI=7B^-}5fSPd8tT9~oTZ9bHZ@y?h|SH-h)o5$`mN17DXPRKZ0DJRcg_Bl zDb%>N4*tCs7TPza82OQ=pYsSv^S~?%1wW940Kl0qlBE_(6{LO*WGgH&I{)4zrRG$6 zj}66PUsmv*ST8nnseFFEcn`-VZ0+Vq^Flsh1dDx7$5~Zs2Wy~1%h6ZU8?)FiSi)DZ zAmT^|6aFXgW^Qq}h^J3Cn~PTiC!j_j>k5rzV=CGT+@70Y+@!c{KlaOtuXsPpZ+1ES zP75q`#CO?HVa(SYnrBo0TS++jh9DkKV5Gh#_j73k0;aQTUa{ZcqwL#kT(w>Hg2E_P z^k18%7t@&3+WY+08}XatK^J6G%;%nF`y*|ZAj#!z6bTXuMP)8{@)2)SMx3i_ps&7d zc*8q4jemPNl6yBM^QDhK>|agaY3<2!{VNGEw#|E!srVkE&-y1ShUg&&hO zX29`xB7$jcZC}B^{_wfo4M1@jz6(!cu~Y$>?y*KI*f(zHS(|FqJDI6@d}5UI+#pGW zu00m~cD${~f#EzZ4SnaXFv(?G0ZRY-=iNi9deZzWmEc=ms_(&MTKrWXg6>2JvF+X= zZRKICA}OK4PH#>8bi}+Bjyl%D{3v2I2fD<9l!I*k)cfAPjTV^>*lUxnl+!le-MoiB z9pGabR3V(&K1_&`O0}QKXrdVm#HEiS^+3rOQYH!6H0mDv2OD3Vup`mHj%(16L47n| zze!-gjcr2?aws-f!8Z=aj-z&HUQivn9*`)SWzqJD8fl8&b$_93tIsp9JApAYJ^bOc zmHoIuj)KlI|MX#~2BW&g)^)cKNTdHLO&{@{WqXIc#1B0fp(2@r&BVUBk@ zrJspYd1=czEs*84>KPvbPufxn!P{A3VH(m53({(ernLyMP&y9n0X@7eQRqkw!C2iRiSILJ;z@+(+56bhBr&wAm-{4fm4g;96Hu$)qXUmeio=1pDW!io9iNAsEGz$}3rCz7EIr)W|1y8i%q7FO z?DsO@H2+I)C!>U~b?*BLR8vEc&Xnx0g#i z_UK4d_;So(PCMx9lKe%W{N)EcMAFtW+l4Y$FA`x}oMYbZ{6@dHSPl5kU0(7MZYR%v zslE){<9E3o->YZwEK(<+TjOZ|_(}G0R96T1W~JB?ZPV#hO?^>MoYwTkxw?S*vaK&! z+Un73r2uRyi)K`W*B%&M9cWXvv96(8kWg+=>VGdYT_F-q_2&tD|KTFN1wq6FAl?J`yl~60|0-bKy0i z?E6iT#__-hGLL^B#a?(5n;(jXNoMi3Cp{ zM4?KLqn2pHm?94)gR@>DCFiE5SPfI1g){=UWp3nOYMX=CLm#vj-VWUq7V28+9X5EF ztHSlbFh)e|Z=vl8;IHs~IH-dP-!^I4gk|CyyrE)?nobzwb39(pAzq z<-JiTr*^k5!zPyuLbS02#=h@)^^}}s)oQ~j09*;`Ydb@-IX+NyaZTZBCp_o_*RbTCLb4fj6pv7@^9eqb|0!3CgK2 ze(T}!L_)i?lsAQQZyJle3@{V9J9DlavKy%Ly6ZAQo8t`C+2*v(3`9!-AX-j^&`DgL z|0rwb;P7cU~n@hv3xqJy^b{kBM0{ke#VGvyc;TEuemPgcZ-Ik zO2SbTVeJgcF8Oe5^28?Jet%vGqe7KtWafeR1Uq{{f2}Q{58% z#w-Ro(bBXnixx~}Z|rh$S)zMKQ*kRjZq;kG=$VRlGF0j7+=~{uEF7oHh#n)OXsf3K z8|=jxD*huu6td@FX$Oeahc@WhQD%Uo9N0GFsBT13h(OXV4Qsl9_E?;%`w632TFW(l zC*;R#6THWI1X`27<|R^WYEq89(c0}poy_O{Y}Hun-P~k5{7N5j`8tE5<_5Sx>L?{; zJ>RyzxV^OD2A((|<{xL~qLWa(mes}nm{f~apcXT1_=S?t{*2ZI0%~KU=6Xa*bnwG5 zV{`r%8rZAO-x1|{z0q8E_dca_4ko>r3&;Ma^<4e5TogIP!sb=cio!3J_=du$e^|<($|i;D5gpiuW$drINm$X?;Sfs zmh2QiSOJ`LF_&TMwkimrgasxToR2WU?23}&VSck{d1Dp zJX#||+NI4gDN+*66W;yC3d z_)cKKcw(5h_gGQ&ig>d|HmA)@bA4ATR9=3^Vy&;K_A$OTET+LZDm~2{A5TuTAZdch z_GF{&fmjbr99o*sa@kQ&NsDanh@i3sNOwr<={F;i21A zNjUtzM^oZ(2_U}Blo&bSoM8e2<^AC2>#O~I?&RY;IlH41hl>TT^Is6A&W7?zsX9eO zd-F~S&gQq+t{2T~>%BquD%zg^75}GKshm3^`L1Q|8_|o#nf~jBg#CBvDyG>vH5+H)-!Ob%4#;IQfJr~eUFfG4B>w5v+?T;KWV3m~IUf`^3%c}TUYt18Bb=f{eC|M;};M!+L_BFwwU zpJ#5ECm0%2Rce_^?{OLpFL#Ue^8ejHc8ECpuN~!wPDtcDu~3 zINc&bk#&bE3k5yS0%Mvjh(W|b%H?*xDH;C-m z@#(0aWlL`1BzDj@adYkG)#Rfdb1SRUuK*u27`hM)hki|l(oj9?z7OqAU!p3_4xPV*3YrF>b?QfWj_pLB!hn&3GVo7npsIyUww6r)~8_9>fVE?a8 z(58jIXN|5MRlc((UyH%gp};979Z(%2g&u_fU;-;hFCALX5Xaf>2MQ9PhNVFaj?X4b3-Obc`|rVN&1w$8l-}0>OA7Z;RE{_`3>8*5D!HYPHgsL5c!2Im>+qy zltq7$f%72Gc&eDP%i4U%pkDgfEAKzJWzorKwlpH~VX06cY5(tG2v!Zhd0)Tx@m*J5 ziHz&aCss2bD*od~QBqhIVCGf|elN!%$XB$g9itk5lMm$@RH2tDi{*LlCw7}B(6Jc0 z5J_QEP<|-`8Kpc##Zrl0sEYVtFl#j)kC}+*N-1`uE2B9zSx>uQ5-)-*k3cA6^hTAp z(3*hq^G|N?bng4e;d75?>DXMP|4vbbm<&G3HBZr(JPd?t(kiGgh|57!+Z_E-c59k@ zS5+NF3h{^<7#?LK=BfetNjBY59{}&mxa*plh(Kr9g8Cvn?`UYeZoDr;rOXRUXM|`+ zE!{Fee3*Yi*q@xb=@a}krFsX7VLy9C=$O@>ENi~1S=SU4mh`BlTpLaxuQbXxT7 zL}2%iKv)cTdW|MA4aodENR&`M@j;{<8;}qB=#$fuQqg(KarS``YVIj9Z~?_`e)0-n zxE8ExV2XBtHVi|=wN{J$UfBM)oSm}JQJ4ZT?Vv0smdVO4g<{U zeFsW_Rw^f#8jZ1?=jmf@FYHpYCXSK{Ld9d}hUnIbP-C}pLhnJbeEUhwZI?1P>ZF{) z7->b^#k27BtlETX;GWVq1HkaaSKQRina>)Z&(;aS&tsoxZlnpI06fyx24$2O{GUWL zioIfE?zt1ovfe?sOgSGd$>xbrcDIks{zUIeB6(f(-c2B;iwZ|=B^e3v#KeixF8$qU z4!c!Q$ADP>Z#6P>YtH|gvQR0Csj}NcFFEb5e;b)Tii8x9WBgZdNq3A;CnB^q(!l)! zk?rYFd)GY$0K=6MmJ8n9?571xtTi!YQ}Jv=T-t3HCA!kE`B_(+$8IW*3t8-6%5QyQ z)IMyn2Rh{g{?4hXaf|;473aJ`+-y7N3{ukHsjiwy5BOTLC}^Q?75L$$SeROu$@>AGm6EmDe@OjRi>Aq>f)u>QN$ljM0_m?>w*1q?+BG+%_#%h61{tA7W z_))x<7{G-k#34h#@xVU|ZP6VtqCy|;Dlm=Ybp&Q#mWi2HYBE(~-TQ;e$g;E2w~r0; zSOHQs8&FsjtEl1Fd1=l4%r;o&uA?}_HP}vcGP}Q`f&mHj98W9BeUK8 zf7N^WJ+aS97g%|ON75&nkD=`3_`lHcQK>^I*N#+$KjSfzds=4J_@p=VHn|d%{{6GN z!JQH>I$Wl?KK4wP6He?3-0*(nsnF1mJ%#PZGL-?JkABuu3EYFNToZ{?QE^~aMAg_& zr00O diff --git a/public/images/items/pb_silver.png b/public/images/items/pb_silver.png new file mode 100644 index 0000000000000000000000000000000000000000..f60a8348a94de2e587a94a66e2d0b4336b25763d GIT binary patch literal 556 zcmV+{0@MA8P)Px$=1D|BR9J=Wmd{G#Kpe+E{iBGD(%MtqOM6a2)4_jWw)gr4>f6Ol_aguOTIA7OlIcy{p6R)B;d-G zt0BwN=Q{CT@tiIY09vK(U-z{yK0N*bAUPd!9M=he(2b)ZC{#NGbzcYK_E(v;WZZUa zZ$IYdT9*_E0`MU+&2c6NZ$Bo-03uVazH0gwgsQ5xA#7zp4{T1m-q-g*zp?@_?%r%_ zu1A}_ixw9WwzFV&54;Ky4Yi9IqFSjKZB$amH3g=GcpxCq4On!Csb0>foV~!fNOt%2uJLj)H z7V`c9cAihl`lomP@z86!>@@t)3%E1ZZ$Xq}(@q~tZb!%L{a6Pv74CM0a}_L8or}6S zR5KZdt6hxImEv~SXcV_9sEwLssV*q=9$Dv{RdLb(Fzpv}GoeqE?kk_+TiBkx-&UIb&JYavlC?UY0EQ#Z8z3`e9qoXIwH>x=pqd z%35Sq>qmzv^CTe%oUfV}4yqwAZUt~i>7)61sNM}8P;br=s$(ZrlZK(vjAsUWM38U+ zr&)QP->kiGTmg*mJkJS+I`+I}KRnc`C{xKEdPLC}N;Oxf^~q{$aPvAo1Q<7u@*{@)RdMQ0{*wIZpce-YC+`B66=Hm#rS9 z%i`PcuPXOyFj)A1^nUO+-SD3k=fyMMs?i6of6#jWc(z?l<@v@Jr5k>0zjZ1vyuJtA y#ewheyXsUbi|VYIH!R!g?3LP&(Df*KfBywl*SaLA^$+XOa?;Me&zBUz_3=2Cwf6C~&~@LKwZ?SJ zE(wE97~16d?B;4H%K^`)c55OY#&D;a_3RAJ4PYgU)7YFM01p}4=;Tsp4yDdsA_GOq zHj$VF9zG>>tstHXDJ3?7#^!Pg8o5$8Hjza&V;TW-x*eovM&aRY_k_*ADM*R)`H4*PKWf!0(48sQn?===)i~112hN$mX3T1 zNb3q!MRVcoU^0@=a@~8`qj7$9tK=(GH*ofDuGS$J!DH*KY9U?MJ4kNh9R#?q2=(Ox zNzfGNF1y`8%XW2_-5!@i-9byY%H4I)C89N=CYraJyuF3a)e5zv?x3~#Z`4wE(AL~V zXGd>`^r#Le$9#<-=Y5X`@BKC?AC;htZMXHmsR`xnlLY!?+^B&cr~V77S8({jvAJ@F d&VQ%0%pa{9Auo9qX&wLo002ovPDHLkV1n*=KDGb= literal 0 HcmV?d00001 diff --git a/public/locales b/public/locales index 3ccef8472dd..87615556d8a 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 3ccef8472dd7cc7c362538489954cb8fdad27e5f +Subproject commit 87615556d8a2bd7eef7abac818f84423a8a13b03 diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index c838f6b2c49..7d0d86fadbf 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -10,7 +10,7 @@ import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; -import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifier } from "#app/modifier/modifier"; +import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; import { SpeciesFormKey } from "#enums/species-form-key"; @@ -1652,11 +1652,11 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesFormEvolution(Species.GHOLDENGO, "chest", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier - || m instanceof ExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG), + || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG), new SpeciesFormEvolution(Species.GHOLDENGO, "roaming", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier - || m instanceof ExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG) + || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG) ] }; 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 8dc4eca25bf..1e20b73e351 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,4 @@ -import { leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { generateModifierType, leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; @@ -14,6 +14,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import i18next from "i18next"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/anOfferYouCantRefuse"; @@ -98,6 +99,8 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = } } + const shinyCharm = generateModifierType(scene, modifierTypes.SHINY_CHARM); + encounter.setDialogueToken("itemName", shinyCharm?.name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name")); encounter.setDialogueToken("liepardName", getPokemonSpecies(Species.LIEPARD).getName()); return true; @@ -123,7 +126,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = return true; }) .withOptionPhase(async (scene: BattleScene) => { - // Give the player a Shiny charm + // Give the player a Shiny Charm scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.SHINY_CHARM)); leaveEncounterWithoutBattle(scene, true); }) @@ -132,9 +135,11 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new CombinationPokemonRequirement( - new MoveRequirement(EXTORTION_MOVES, true), - new AbilityRequirement(EXTORTION_ABILITIES, true)) + .withPrimaryPokemonRequirement( + CombinationPokemonRequirement.Some( + new MoveRequirement(EXTORTION_MOVES, true), + new AbilityRequirement(EXTORTION_ABILITIES, true) + ) ) .withDialogue({ buttonLabel: `${namespace}:option.2.label`, 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 d316ab14cde..e24eadb56c7 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -193,12 +193,14 @@ const WAVE_LEVEL_BREAKPOINTS = [ 30, 50, 70, 100, 120, 140, 160 ]; export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BUG_TYPE_SUPERFAN) .withEncounterTier(MysteryEncounterTier.GREAT) - .withPrimaryPokemonRequirement(new CombinationPokemonRequirement( - // 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(Type.BUG, 1), - new TypeRequirement(Type.BUG, false, 1) - )) + .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(Type.BUG, 1), + new TypeRequirement(Type.BUG, false, 1) + ) + ) .withMaxAllowedEncounters(1) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withIntroSpriteConfigs([]) // These are set in onInit() @@ -405,11 +407,13 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = .build()) .withOption(MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) - .withPrimaryPokemonRequirement(new CombinationPokemonRequirement( - // Meets one or both of the below reqs - new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1), - new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1) - )) + .withPrimaryPokemonRequirement( + CombinationPokemonRequirement.Some( + // Meets one or both of the below reqs + new HeldItemRequirement([ "BypassSpeedChanceModifier", "ContactHeldItemTransferChanceModifier" ], 1), + new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1) + ) + ) .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 57c8aa7a561..c4b03660bde 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -11,7 +11,7 @@ import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Abilities } from "#enums/abilities"; -import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { applyAbilityOverrideToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { Type } from "#app/data/type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -425,17 +425,8 @@ function onYesAbilitySwap(scene: BattleScene, resolve) { const onPokemonSelected = (pokemon: PlayerPokemon) => { // Do ability swap const encounter = scene.currentBattle.mysteryEncounter!; - if (pokemon.isFusion()) { - if (!pokemon.fusionCustomPokemonData) { - pokemon.fusionCustomPokemonData = new CustomPokemonData(); - } - pokemon.fusionCustomPokemonData.ability = encounter.misc.ability; - } else { - if (!pokemon.customPokemonData) { - pokemon.customPokemonData = new CustomPokemonData(); - } - pokemon.customPokemonData.ability = encounter.misc.ability; - } + + applyAbilityOverrideToPokemon(pokemon, encounter.misc.ability); encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender()); scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true)); }; diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 2c13086ccb8..8a814b58248 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -14,6 +14,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { Challenges } from "#enums/challenges"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/darkDeal"; @@ -141,6 +142,7 @@ export const DarkDealEncounter: MysteryEncounter = // Removes random pokemon (including fainted) from party and adds name to dialogue data tokens // Will never return last battle able mon and instead pick fainted/unable to battle const removedPokemon = getRandomPlayerPokemon(scene, true, false, true); + // Get all the pokemon's held items const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier)); scene.removePokemonFromPlayerParty(removedPokemon); @@ -160,7 +162,13 @@ export const DarkDealEncounter: MysteryEncounter = scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ROGUE_BALL)); // Start encounter with random legendary (7-10 starter strength) that has level additive - const bossTypes: Type[] = encounter.misc.removedTypes; + // If this is a mono-type challenge, always ensure the required type is filtered for + let bossTypes: Type[] = encounter.misc.removedTypes; + const singleTypeChallenges = scene.gameMode.challenges.filter(c => c.value && c.id === Challenges.SINGLE_TYPE); + if (scene.gameMode.isChallenge && singleTypeChallenges.length > 0) { + bossTypes = singleTypeChallenges.map(c => (c.value - 1) as Type); + } + const bossModifiers: PokemonHeldItemModifier[] = encounter.misc.modifiers; // Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+ const roll = randSeedInt(100); diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 5686d0f6ce5..d5f9388b56c 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -45,10 +45,13 @@ export const DelibirdyEncounter: MysteryEncounter = .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneRequirement(new MoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER)) // Must have enough money for it to spawn at the very least - .withPrimaryPokemonRequirement(new CombinationPokemonRequirement( // 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) - )) + .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) + ) + ) .withIntroSpriteConfigs([ { spriteKey: "", @@ -196,7 +199,7 @@ export const DelibirdyEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; const modifier: BerryModifier | HealingBoosterModifier = encounter.misc.chosenModifier; - // Give the player a Candy Jar if they gave a Berry, and a Healing Charm for Reviver Seed + // Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed if (modifier instanceof BerryModifier) { // Check if the player has max stacks of that Candy Jar already const existing = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier; @@ -211,8 +214,8 @@ export const DelibirdyEncounter: MysteryEncounter = scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.CANDY_JAR)); } } else { - // Check if the player has max stacks of that Healing Charm already - const existing = scene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier; + // Check if the player has max stacks of that Berry Pouch already + const existing = scene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier; if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { // At max stacks, give the first party pokemon a Shell Bell instead @@ -221,7 +224,7 @@ export const DelibirdyEncounter: MysteryEncounter = scene.playSound("item_fanfare"); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); } else { - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM)); + scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH)); } } @@ -290,8 +293,8 @@ export const DelibirdyEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; const modifier = encounter.misc.chosenModifier; - // Check if the player has max stacks of Berry Pouch already - const existing = scene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier; + // Check if the player has max stacks of Healing Charm already + const existing = scene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier; if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { // At max stacks, give the first party pokemon a Shell Bell instead @@ -300,7 +303,7 @@ export const DelibirdyEncounter: MysteryEncounter = scene.playSound("item_fanfare"); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); } else { - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH)); + scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM)); } // Remove the modifier if its stacks go to 0 diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index d306206159a..5c16e5d8564 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -4,24 +4,30 @@ import { AttackTypeBoosterModifierType, modifierTypes, } from "#app/modifier/mod import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; -import { TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; +import { AbilityRequirement, CombinationPokemonRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { Species } from "#enums/species"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Gender } from "#app/data/gender"; import { Type } from "#app/data/type"; import { BattlerIndex } from "#app/battle"; -import { PokemonMove } from "#app/field/pokemon"; +import Pokemon, { PokemonMove } from "#app/field/pokemon"; import { Moves } from "#enums/moves"; import { EncounterBattleAnim } from "#app/data/battle-anims"; import { WeatherType } from "#app/data/weather"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { StatusEffect } from "#app/data/status-effect"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +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"; import { EncounterAnim } from "#enums/encounter-anims"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; +import { Stat } from "#enums/stat"; +import { Ability } from "#app/data/ability"; +import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/fieryFallout"; @@ -62,16 +68,24 @@ export const FieryFalloutEncounter: MysteryEncounter = { species: volcaronaSpecies, isBoss: false, - gender: Gender.MALE + gender: Gender.MALE, + tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], + mysteryEncounterBattleEffects: (pokemon: Pokemon) => { + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1)); + } }, { species: volcaronaSpecies, isBoss: false, - gender: Gender.FEMALE + gender: Gender.FEMALE, + tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], + mysteryEncounterBattleEffects: (pokemon: Pokemon) => { + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1)); + } } ], doubleBattle: true, - disableSwitch: true + disableSwitch: true, }; encounter.enemyPartyConfigs = [ config ]; @@ -139,7 +153,7 @@ export const FieryFalloutEncounter: MysteryEncounter = async (scene: BattleScene) => { // Pick battle const encounter = scene.currentBattle.mysteryEncounter!; - setEncounterRewards(scene, { fillRemaining: true }, undefined, () => giveLeadPokemonCharcoal(scene)); + setEncounterRewards(scene, { fillRemaining: true }, undefined, () => giveLeadPokemonAttackTypeBoostItem(scene)); encounter.startOfBattleEffects.push( { @@ -153,18 +167,6 @@ export const FieryFalloutEncounter: MysteryEncounter = targets: [ BattlerIndex.PLAYER_2 ], move: new PokemonMove(Moves.FIRE_SPIN), ignorePp: true - }, - { - sourceBattlerIndex: BattlerIndex.ENEMY, - targets: [ BattlerIndex.ENEMY ], - move: new PokemonMove(Moves.QUIVER_DANCE), - ignorePp: true - }, - { - sourceBattlerIndex: BattlerIndex.ENEMY_2, - targets: [ BattlerIndex.ENEMY_2 ], - move: new PokemonMove(Moves.QUIVER_DANCE), - ignorePp: true }); await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); } @@ -180,7 +182,7 @@ export const FieryFalloutEncounter: MysteryEncounter = ], }, async (scene: BattleScene) => { - // Damage non-fire types and burn 1 random non-fire type member + // Damage non-fire types and burn 1 random non-fire type member + give it Heatproof const encounter = scene.currentBattle.mysteryEncounter!; const nonFireTypes = scene.getParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(Type.FIRE)); @@ -198,7 +200,11 @@ export const FieryFalloutEncounter: MysteryEncounter = if (chosenPokemon.trySetStatus(StatusEffect.BURN)) { // Burn applied encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender()); + encounter.setDialogueToken("abilityName", new Ability(Abilities.HEATPROOF, 3).name); queueEncounterMessage(scene, `${namespace}:option.2.target_burned`); + + // Also permanently change the burned Pokemon's ability to Heatproof + applyAbilityOverrideToPokemon(chosenPokemon, Abilities.HEATPROOF); } } @@ -209,8 +215,12 @@ export const FieryFalloutEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3PrimaryName dialogue token automatically - .withSecondaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3SecondaryName dialogue token automatically + .withPrimaryPokemonRequirement( + CombinationPokemonRequirement.Some( + new TypeRequirement(Type.FIRE, true, 1), + new AbilityRequirement(FIRE_RESISTANT_ABILITIES, true) + ) + ) // Will set option3PrimaryName dialogue token automatically .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, @@ -233,26 +243,32 @@ export const FieryFalloutEncounter: MysteryEncounter = { fillRemaining: true }, undefined, () => { - giveLeadPokemonCharcoal(scene); + giveLeadPokemonAttackTypeBoostItem(scene); }); const primary = encounter.options[2].primaryPokemon!; - const secondary = encounter.options[2].secondaryPokemon![0]; - setEncounterExp(scene, [ primary.id, secondary.id ], getPokemonSpecies(Species.VOLCARONA).baseExp * 2); + setEncounterExp(scene, [ primary.id ], getPokemonSpecies(Species.VOLCARONA).baseExp * 2); leaveEncounterWithoutBattle(scene); }) .build() ) .build(); -function giveLeadPokemonCharcoal(scene: BattleScene) { - // Give first party pokemon Charcoal for free at end of battle +function giveLeadPokemonAttackTypeBoostItem(scene: BattleScene) { + // Give first party pokemon attack type boost item for free at end of battle const leadPokemon = scene.getParty()?.[0]; if (leadPokemon) { - const charcoal = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FIRE ]) as AttackTypeBoosterModifierType; - applyModifierTypeToPlayerPokemon(scene, leadPokemon, charcoal); - scene.currentBattle.mysteryEncounter!.setDialogueToken("leadPokemon", leadPokemon.getNameToRender()); - queueEncounterMessage(scene, `${namespace}:found_charcoal`); + // Generate type booster held item, default to Charcoal if item fails to generate + let boosterModifierType = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType; + if (!boosterModifierType) { + boosterModifierType = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FIRE ]) as AttackTypeBoosterModifierType; + } + applyModifierTypeToPlayerPokemon(scene, leadPokemon, boosterModifierType); + + const encounter = scene.currentBattle.mysteryEncounter!; + encounter.setDialogueToken("itemName", boosterModifierType.name); + encounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender()); + queueEncounterMessage(scene, `${namespace}:found_item`); } } diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index f282064bb94..7fdd29d36a2 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -56,7 +56,13 @@ export const MysteriousChallengersEncounter: MysteryEncounter = // Hard difficulty trainer is another random trainer, but with AVERAGE_BALANCED config // Number of mons is based off wave: 1-20 is 2, 20-40 is 3, etc. capping at 6 after wave 100 - const hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex); + let retries = 0; + let hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex); + while (retries < 5 && hardTrainerType === normalTrainerType) { + // Will try to use a different trainer from the normal trainer type + hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex); + retries++; + } const hardTemplate = new TrainerPartyCompoundTemplate( new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER, false, true), new TrainerPartyTemplate( 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 d30c97b27de..8dd730492b1 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -21,7 +21,7 @@ import i18next from "i18next"; const namespace = "mysteryEncounters/shadyVitaminDealer"; const VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER = 1.5; -const VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER = 3.5; +const VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER = 5; /** * Shady Vitamin Dealer encounter. 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 9c10d33d019..610209f8aad 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 @@ -222,7 +222,10 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.misc.chosenPokemon = pokemon1; encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender()); const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs); - setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene)); + setEncounterRewards(scene, + { guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true }, + eggOptions, + () => doPostEncounterCleanup(scene)); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1); @@ -271,7 +274,10 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.misc.chosenPokemon = pokemon2; encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender()); const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs); - setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene)); + setEncounterRewards(scene, + { guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true }, + eggOptions, + () => doPostEncounterCleanup(scene)); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2); @@ -320,7 +326,10 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.misc.chosenPokemon = pokemon3; encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender()); const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs); - setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene)); + setEncounterRewards(scene, + { guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true }, + eggOptions, + () => doPostEncounterCleanup(scene)); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3); @@ -454,12 +463,16 @@ function calculateEggRewardsForPokemon(pokemon: PlayerPokemon): [number, number] } // Maximum of 30 points - const totalPoints = Math.min(pointsFromStarterTier + pointsFromBst, 30); + let totalPoints = Math.min(pointsFromStarterTier + pointsFromBst, 30); - // 1 Rare egg for every 6 points - const numRares = Math.floor(totalPoints / 6); + // First 5 points go to Common eggs + let numCommons = Math.min(totalPoints, 5); + totalPoints -= numCommons; + + // Then, 1 Rare egg for every 4 points + const numRares = Math.floor(totalPoints / 4); // 1 Common egg for every point leftover - const numCommons = totalPoints % 6; + numCommons += totalPoints % 4; return [ numCommons, numRares ]; } diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 9f80bbbffde..03341a713f2 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -37,6 +37,7 @@ export const TrainingSessionEncounter: MysteryEncounter = .withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party .withFleeAllowed(false) .withHideWildIntroMessage(true) + .withPreventGameStatsUpdates(true) // Do not count the Pokemon as seen or defeated since it is ours .withIntroSpriteConfigs([ { spriteKey: "training_session_gear", 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 2b3b38b2164..d3c16ce2122 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -71,7 +71,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ] }; const config: EnemyPartyConfig = { - levelAdditiveModifier: 1, + levelAdditiveModifier: 0.5, pokemonConfigs: [ pokemonConfig ], disableSwitch: true }; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index b97a22dbe51..2ecba6ce658 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -4,23 +4,30 @@ import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; -import { leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils"; +import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils"; import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { achvs } from "#app/system/achv"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence"; import { getLevelTotalExp } from "#app/data/exp"; import { Stat } from "#enums/stat"; -import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { Challenges } from "#enums/challenges"; +import { ModifierTier } from "#app/modifier/modifier-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 HeldModifierConfig from "#app/interfaces/held-modifier-config"; +import { trainerConfigs, TrainerPartyTemplate } from "#app/data/trainer-config"; +import { PartyMemberStrength } from "#enums/party-member-strength"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/weirdDream"; @@ -80,10 +87,11 @@ const EXCLUDED_TRANSFORMATION_SPECIES = [ const SUPER_LEGENDARY_BST_THRESHOLD = 600; const NON_LEGENDARY_BST_THRESHOLD = 570; -const GAIN_OLD_GATEAU_ITEM_BST_THRESHOLD = 450; + +const OLD_GATEAU_STATS_UP = 20; /** 0-100 */ -const PERCENT_LEVEL_LOSS_ON_REFUSE = 12.5; +const PERCENT_LEVEL_LOSS_ON_REFUSE = 10; /** * Value ranges of the resulting species BST transformations after adding values to original species @@ -105,7 +113,8 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM) .withEncounterTier(MysteryEncounterTier.ROGUE) .withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION) - .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) + // TODO: should reset minimum wave to 10 when there are more Rogue tiers in pool. Matching Dark Deal minimum for now. + .withSceneWaveRangeRequirement(30, 140) .withIntroSpriteConfigs([ { spriteKey: "weird_dream_woman", @@ -131,6 +140,15 @@ export const WeirdDreamEncounter: MysteryEncounter = .withQuery(`${namespace}:query`) .withOnInit((scene: BattleScene) => { scene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3"); + + // Calculate all the newly transformed Pokemon and begin asset load + const teamTransformations = getTeamTransformations(scene); + const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets()); + scene.currentBattle.mysteryEncounter!.misc = { + teamTransformations, + loadAssets + }; + return true; }) .withOnVisualsStart((scene: BattleScene) => { @@ -156,13 +174,10 @@ export const WeirdDreamEncounter: MysteryEncounter = doShowDreamBackground(scene); }); - // Calculate all the newly transformed Pokemon and begin asset load - const teamTransformations = getTeamTransformations(scene); - const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets()); - scene.currentBattle.mysteryEncounter!.misc = { - teamTransformations, - loadAssets - }; + for (const transformation of scene.currentBattle.mysteryEncounter!.misc.teamTransformations) { + scene.removePokemonFromPlayerParty(transformation.previousPokemon, false); + scene.getParty().push(transformation.newPokemon); + } }) .withOptionPhase(async (scene: BattleScene) => { // Starts cutscene dialogue, but does not await so that cutscene plays as player goes through dialogue @@ -193,7 +208,7 @@ export const WeirdDreamEncounter: MysteryEncounter = await showEncounterText(scene, `${namespace}:option.1.dream_complete`); await doNewTeamPostProcess(scene, transformations); - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT ]}); + setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT, modifierTypes.MINT ], fillRemaining: false }); leaveEncounterWithoutBattle(scene, true); }) .build() @@ -209,7 +224,88 @@ export const WeirdDreamEncounter: MysteryEncounter = ], }, async (scene: BattleScene) => { - // Reduce party levels by 20% + // Battle your "future" team for some item rewards + const transformations: PokemonTransformation[] = scene.currentBattle.mysteryEncounter!.misc.teamTransformations; + + // Uses the pokemon that player's party would have transformed into + const enemyPokemonConfigs: EnemyPokemonConfig[] = []; + for (const transformation of transformations) { + const newPokemon = transformation.newPokemon; + const previousPokemon = transformation.previousPokemon; + + await postProcessTransformedPokemon(scene, previousPokemon, newPokemon, newPokemon.species.getRootSpeciesId(), true); + + const dataSource = new PokemonData(newPokemon); + 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 + }); + } + // Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats + if (shouldGetOldGateau(newPokemon)) { + const stats = getOldGateauBoostedStats(newPokemon); + newPokemonHeldItemConfigs.push({ + modifier: generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [ OLD_GATEAU_STATS_UP, stats ]) as PokemonHeldItemModifierType, + stackCount: 1, + isTransferable: false + }); + } + + const enemyConfig: EnemyPokemonConfig = { + species: transformation.newSpecies, + isBoss: newPokemon.getSpeciesForm().getBaseStatTotal() > NON_LEGENDARY_BST_THRESHOLD, + level: previousPokemon.level, + dataSource: dataSource, + modifierConfigs: newPokemonHeldItemConfigs + }; + + enemyPokemonConfigs.push(enemyConfig); + } + + const genderIndex = scene.gameData.gender ?? PlayerGender.UNSET; + const trainerConfig = trainerConfigs[genderIndex === PlayerGender.FEMALE ? TrainerType.FUTURE_SELF_F : TrainerType.FUTURE_SELF_M].clone(); + trainerConfig.setPartyTemplates(new TrainerPartyTemplate(transformations.length, PartyMemberStrength.STRONG)); + const enemyPartyConfig: EnemyPartyConfig = { + trainerConfig: trainerConfig, + pokemonConfigs: enemyPokemonConfigs, + female: genderIndex === PlayerGender.FEMALE + }; + + const onBeforeRewards = () => { + // Before battle rewards, 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 = scene.getParty().filter(p => !p.passive); + if (passiveDisabledPokemon?.length > 0) { + const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)]; + enablePassiveMon.passive = true; + enablePassiveMon.updateInfo(true); + } + }; + + setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: false }, undefined, onBeforeRewards); + + await showEncounterText(scene, `${namespace}:option.2.selected_2`, null, undefined, true); + await initBattleWithEnemyConfig(scene, enemyPartyConfig); + } + ) + .withSimpleOption( + { + buttonLabel: `${namespace}:option.3.label`, + buttonTooltip: `${namespace}:option.3.tooltip`, + selected: [ + { + text: `${namespace}:option.3.selected`, + }, + ], + }, + async (scene: BattleScene) => { + // Leave, reduce party levels by 10% for (const pokemon of scene.getParty()) { pokemon.level = Math.max(Math.ceil((100 - PERCENT_LEVEL_LOSS_ON_REFUSE) / 100 * pokemon.level), 1); pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate); @@ -235,7 +331,7 @@ interface PokemonTransformation { function getTeamTransformations(scene: BattleScene): PokemonTransformation[] { const party = scene.getParty(); // Removes all pokemon from the party - const alreadyUsedSpecies: PokemonSpecies[] = []; + const alreadyUsedSpecies: PokemonSpecies[] = party.map(p => p.species); const pokemonTransformations: PokemonTransformation[] = party.map(p => { return { previousPokemon: p @@ -250,11 +346,11 @@ function getTeamTransformations(scene: BattleScene): PokemonTransformation[] { // First, roll 2 of the party members to new Pokemon at a +90 to +110 BST difference // Then, roll the remainder of the party members at a +40 to +50 BST difference const numPokemon = party.length; + const removedPokemon = randSeedShuffle(party.slice(0)); for (let i = 0; i < numPokemon; i++) { - const removed = party[randSeedInt(party.length)]; + const removed = removedPokemon[i]; const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id); pokemonTransformations[index].heldItems = removed.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier)); - scene.removePokemonFromPlayerParty(removed, false); const bst = removed.calculateBaseStats().reduce((a, b) => a + b, 0); let newBstRange: [number, number]; @@ -276,14 +372,13 @@ function getTeamTransformations(scene: BattleScene): PokemonTransformation[] { pokemonTransformations[index].newSpecies = newSpecies; + console.log("New species: " + JSON.stringify(newSpecies)); alreadyUsedSpecies.push(newSpecies); } for (const transformation of pokemonTransformations) { const newAbilityIndex = randSeedInt(transformation.newSpecies.getAbilityCount()); - const newPlayerPokemon = scene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined); - transformation.newPokemon = newPlayerPokemon; - scene.getParty().push(newPlayerPokemon); + transformation.newPokemon = scene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined); } return pokemonTransformations; @@ -296,109 +391,20 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon const newPokemon = transformation.newPokemon; const speciesRootForm = newPokemon.species.getRootSpeciesId(); - // Roll HA a second time - if (newPokemon.species.abilityHidden) { - const hiddenIndex = newPokemon.species.ability2 ? 2 : 1; - if (newPokemon.abilityIndex < hiddenIndex) { - const hiddenAbilityChance = new IntegerHolder(256); - scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); - - const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); - - if (hasHiddenAbility) { - newPokemon.abilityIndex = hiddenIndex; - } - } + if (await postProcessTransformedPokemon(scene, previousPokemon, newPokemon, speciesRootForm)) { + atLeastOneNewStarter = true; } - // Roll IVs a second time - newPokemon.ivs = newPokemon.ivs.map(iv => { - const newValue = randSeedInt(31); - return newValue > iv ? newValue : iv; - }); - - // For pokemon at/below 570 BST or any shiny pokemon, unlock it permanently as if you had caught it - if (newPokemon.getSpeciesForm().getBaseStatTotal() <= NON_LEGENDARY_BST_THRESHOLD || newPokemon.isShiny()) { - if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) { - scene.validateAchv(achvs.HIDDEN_ABILITY); - } - - if (newPokemon.species.subLegendary) { - scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); - } - - if (newPokemon.species.legendary) { - scene.validateAchv(achvs.CATCH_LEGENDARY); - } - - if (newPokemon.species.mythical) { - scene.validateAchv(achvs.CATCH_MYTHICAL); - } - - scene.gameData.updateSpeciesDexIvs(newPokemon.species.getRootSpeciesId(true), newPokemon.ivs); - const newStarterUnlocked = await scene.gameData.setPokemonCaught(newPokemon, true, false, false); - if (newStarterUnlocked) { - atLeastOneNewStarter = true; - await showEncounterText(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() })); - } - } - - // If the previous pokemon had pokerus, transfer to new pokemon - newPokemon.pokerus = previousPokemon.pokerus; - - // Transfer previous Pokemon's luck value - newPokemon.luck = previousPokemon.getLuck(); - - // If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock) - newPokemon.ivs = newPokemon.ivs.map((iv, index) => { - return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv; - }); - - // For pokemon that the player owns (including ones just caught), gain a candy - if (!!scene.gameData.dexData[speciesRootForm].caughtAttr) { - scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1); - } - - // Set the moveset of the new pokemon to be the same as previous, but with 1 egg move and 1 (attempted) STAB move of the new species - newPokemon.generateAndPopulateMoveset(); - // Store a copy of a "standard" generated moveset for the new pokemon, will be used later for finding a favored move - const newPokemonGeneratedMoveset = newPokemon.moveset; - - newPokemon.moveset = previousPokemon.moveset; - - const newEggMoveIndex = await addEggMoveToNewPokemonMoveset(scene, newPokemon, speciesRootForm); - - // Try to add a favored STAB move (might fail if Pokemon already knows a bunch of moves from newPokemonGeneratedMoveset) - addFavoredMoveToNewPokemonMoveset(newPokemon, newPokemonGeneratedMoveset, newEggMoveIndex); - - // Randomize the second type of the pokemon - // If the pokemon does not normally have a second type, it will gain 1 - const newTypes = [ newPokemon.getTypes()[0] ]; - let newType = randSeedInt(18) as Type; - while (newType === newTypes[0]) { - newType = randSeedInt(18) as Type; - } - newTypes.push(newType); - if (!newPokemon.customPokemonData) { - newPokemon.customPokemonData = new CustomPokemonData(); - } - newPokemon.customPokemonData.types = newTypes; - + // Copy old items to new pokemon for (const item of transformation.heldItems) { item.pokemonId = newPokemon.id; await scene.addModifier(item, false, false, false, true); } - - // Any pokemon that is at or below 450 BST gets +20 permanent BST to 3 stats: HP (halved, +10), lowest of Atk/SpAtk, and lowest of Def/SpDef - if (newPokemon.getSpeciesForm().getBaseStatTotal() <= GAIN_OLD_GATEAU_ITEM_BST_THRESHOLD) { - const stats: Stat[] = [ Stat.HP ]; - const baseStats = newPokemon.getSpeciesForm().baseStats.slice(0); - // 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); + // Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats + if (shouldGetOldGateau(newPokemon)) { + const stats = getOldGateauBoostedStats(newPokemon); const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU() - .generateType(scene.getParty(), [ 20, stats ]) + .generateType(scene.getParty(), [ OLD_GATEAU_STATS_UP, stats ]) ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU); const modifier = modType?.newModifier(newPokemon); if (modifier) { @@ -406,9 +412,6 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon } } - // Enable passive if previous had it - newPokemon.passive = previousPokemon.passive; - newPokemon.calculateStats(); await newPokemon.updateInfo(); } @@ -427,6 +430,138 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon } } +/** + * Applies special changes to the newly transformed pokemon, such as passing previous moves, gaining egg moves, etc. + * Returns whether the transformed pokemon unlocks a new starter for the player. + * @param scene + * @param previousPokemon + * @param newPokemon + * @param speciesRootForm + * @param forBattle Default `false`. If false, will perform achievements and dex unlocks for the player. + */ +async function postProcessTransformedPokemon(scene: BattleScene, previousPokemon: PlayerPokemon, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise { + let isNewStarter = false; + // Roll HA a second time + if (newPokemon.species.abilityHidden) { + const hiddenIndex = newPokemon.species.ability2 ? 2 : 1; + if (newPokemon.abilityIndex < hiddenIndex) { + const hiddenAbilityChance = new IntegerHolder(256); + scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); + + const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); + + if (hasHiddenAbility) { + newPokemon.abilityIndex = hiddenIndex; + } + } + } + + // Roll IVs a second time + newPokemon.ivs = newPokemon.ivs.map(iv => { + const newValue = randSeedInt(31); + return newValue > iv ? newValue : iv; + }); + + // Roll a neutral nature + newPokemon.nature = [ Nature.HARDY, Nature.DOCILE, Nature.BASHFUL, Nature.QUIRKY, Nature.SERIOUS ][randSeedInt(5)]; + + // For pokemon at/below 570 BST or any shiny pokemon, unlock it permanently as if you had caught it + if (!forBattle && (newPokemon.getSpeciesForm().getBaseStatTotal() <= NON_LEGENDARY_BST_THRESHOLD || newPokemon.isShiny())) { + if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) { + scene.validateAchv(achvs.HIDDEN_ABILITY); + } + + if (newPokemon.species.subLegendary) { + scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); + } + + if (newPokemon.species.legendary) { + scene.validateAchv(achvs.CATCH_LEGENDARY); + } + + if (newPokemon.species.mythical) { + scene.validateAchv(achvs.CATCH_MYTHICAL); + } + + scene.gameData.updateSpeciesDexIvs(newPokemon.species.getRootSpeciesId(true), newPokemon.ivs); + const newStarterUnlocked = await scene.gameData.setPokemonCaught(newPokemon, true, false, false); + if (newStarterUnlocked) { + isNewStarter = true; + await showEncounterText(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() })); + } + } + + // If the previous pokemon had pokerus, transfer to new pokemon + newPokemon.pokerus = previousPokemon.pokerus; + + // Transfer previous Pokemon's luck value + newPokemon.luck = previousPokemon.getLuck(); + + // If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock) + newPokemon.ivs = newPokemon.ivs.map((iv, index) => { + return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv; + }); + + // For pokemon that the player owns (including ones just caught), gain a candy + if (!forBattle && !!scene.gameData.dexData[speciesRootForm].caughtAttr) { + scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1); + } + + // Set the moveset of the new pokemon to be the same as previous, but with 1 egg move and 1 (attempted) STAB move of the new species + newPokemon.generateAndPopulateMoveset(); + // Store a copy of a "standard" generated moveset for the new pokemon, will be used later for finding a favored move + const newPokemonGeneratedMoveset = newPokemon.moveset; + + newPokemon.moveset = previousPokemon.moveset.slice(0); + + const newEggMoveIndex = await addEggMoveToNewPokemonMoveset(scene, newPokemon, speciesRootForm, forBattle); + + // Try to add a favored STAB move (might fail if Pokemon already knows a bunch of moves from newPokemonGeneratedMoveset) + addFavoredMoveToNewPokemonMoveset(newPokemon, newPokemonGeneratedMoveset, newEggMoveIndex); + + // Randomize the second type of the pokemon + // If the pokemon does not normally have a second type, it will gain 1 + const newTypes = [ newPokemon.getTypes()[0] ]; + let newType = randSeedInt(18) as Type; + while (newType === newTypes[0]) { + newType = randSeedInt(18) as Type; + } + newTypes.push(newType); + if (!newPokemon.customPokemonData) { + newPokemon.customPokemonData = new CustomPokemonData(); + } + newPokemon.customPokemonData.types = newTypes; + + // Enable passive if previous had it + newPokemon.passive = previousPokemon.passive; + + return isNewStarter; +} + +/** + * @returns `true` if a given Pokemon has valid BST to be given an Old Gateau + */ +function shouldGetOldGateau(pokemon: Pokemon): boolean { + return pokemon.getSpeciesForm().getBaseStatTotal() < NON_LEGENDARY_BST_THRESHOLD; +} + +/** + * Get the lowest of HP/Spd, lowest of Atk/SpAtk, and lowest of Def/SpDef + * @returns Array of 3 {@linkcode Stat}s to boost + */ +function getOldGateauBoostedStats(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; +} + + function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies { let newSpecies: PokemonSpecies | undefined; while (isNullOrUndefined(newSpecies)) { @@ -550,7 +685,7 @@ function doSideBySideTransformations(scene: BattleScene, transformations: Pokemo * @param newPokemon * @param speciesRootForm */ -async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, speciesRootForm: Species): Promise { +async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise { let eggMoveIndex: null | number = null; const eggMoves = newPokemon.getEggMoves()?.slice(0); if (eggMoves) { @@ -576,7 +711,7 @@ async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: Pla } // For pokemon that the player owns (including ones just caught), unlock the egg move - if (!isNullOrUndefined(randomEggMoveIndex) && !!scene.gameData.dexData[speciesRootForm].caughtAttr) { + if (!forBattle && !isNullOrUndefined(randomEggMoveIndex) && !!scene.gameData.dexData[speciesRootForm].caughtAttr) { await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true); } } diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index a57cedc8fa3..91ea0c5be19 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -37,31 +37,58 @@ export abstract class EncounterSceneRequirement implements EncounterRequirement abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string]; } +/** + * Combination of multiple {@linkcode EncounterSceneRequirement | EncounterSceneRequirements} (OR/AND possible. See {@linkcode isAnd}) + */ export class CombinationSceneRequirement extends EncounterSceneRequirement { - orRequirements: EncounterSceneRequirement[]; + /** If `true`, all requirements must be met (AND). If `false`, any requirement must be met (OR) */ + private isAnd: boolean; + requirements: EncounterSceneRequirement[]; - constructor(... orRequirements: EncounterSceneRequirement[]) { + public static Some(...requirements: EncounterSceneRequirement[]): CombinationSceneRequirement { + return new CombinationSceneRequirement(false, ...requirements); + } + + public static Every(...requirements: EncounterSceneRequirement[]): CombinationSceneRequirement { + return new CombinationSceneRequirement(true, ...requirements); + } + + private constructor(isAnd: boolean, ...requirements: EncounterSceneRequirement[]) { super(); - this.orRequirements = orRequirements; + this.isAnd = isAnd; + this.requirements = requirements; } + /** + * Checks if all/any requirements are met (depends on {@linkcode isAnd}) + * @param scene The {@linkcode BattleScene} to check against + * @returns true if all/any requirements are met (depends on {@linkcode isAnd}) + */ override meetsRequirement(scene: BattleScene): boolean { - for (const req of this.orRequirements) { - if (req.meetsRequirement(scene)) { - return true; - } - } - return false; + return this.isAnd + ? this.requirements.every(req => req.meetsRequirement(scene)) + : this.requirements.some(req => req.meetsRequirement(scene)); } + /** + * Retrieves a dialogue token key/value pair for the given {@linkcode EncounterSceneRequirement | requirements}. + * @param scene The {@linkcode BattleScene} to check against + * @param pokemon The {@linkcode PlayerPokemon} to check against + * @returns A dialogue token key/value pair + * @throws An {@linkcode Error} if {@linkcode isAnd} is `true` (not supported) + */ override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - for (const req of this.orRequirements) { - if (req.meetsRequirement(scene)) { - return req.getDialogueToken(scene, pokemon); + if (this.isAnd) { + throw new Error("Not implemented (Sorry)"); + } else { + for (const req of this.requirements) { + if (req.meetsRequirement(scene)) { + return req.getDialogueToken(scene, pokemon); + } } - } - return this.orRequirements[0].getDialogueToken(scene, pokemon); + return this.requirements[0].getDialogueToken(scene, pokemon); + } } } @@ -90,44 +117,74 @@ export abstract class EncounterPokemonRequirement implements EncounterRequiremen abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string]; } +/** + * Combination of multiple {@linkcode EncounterPokemonRequirement | EncounterPokemonRequirements} (OR/AND possible. See {@linkcode isAnd}) + */ export class CombinationPokemonRequirement extends EncounterPokemonRequirement { - orRequirements: EncounterPokemonRequirement[]; + /** If `true`, all requirements must be met (AND). If `false`, any requirement must be met (OR) */ + private isAnd: boolean; + private requirements: EncounterPokemonRequirement[]; - constructor(...orRequirements: EncounterPokemonRequirement[]) { + public static Some(...requirements: EncounterPokemonRequirement[]): CombinationPokemonRequirement { + return new CombinationPokemonRequirement(false, ...requirements); + } + + public static Every(...requirements: EncounterPokemonRequirement[]): CombinationPokemonRequirement { + return new CombinationPokemonRequirement(true, ...requirements); + } + + private constructor(isAnd: boolean, ...requirements: EncounterPokemonRequirement[]) { super(); + this.isAnd = isAnd; this.invertQuery = false; this.minNumberOfPokemon = 1; - this.orRequirements = orRequirements; + this.requirements = requirements; } + /** + * Checks if all/any requirements are met (depends on {@linkcode isAnd}) + * @param scene The {@linkcode BattleScene} to check against + * @returns true if all/any requirements are met (depends on {@linkcode isAnd}) + */ override meetsRequirement(scene: BattleScene): boolean { - for (const req of this.orRequirements) { - if (req.meetsRequirement(scene)) { - return true; - } - } - return false; + return this.isAnd + ? this.requirements.every(req => req.meetsRequirement(scene)) + : this.requirements.some(req => req.meetsRequirement(scene)); } + /** + * Queries the players party for all party members that are compatible with all/any requirements (depends on {@linkcode isAnd}) + * @param partyPokemon The party of {@linkcode PlayerPokemon} + * @returns All party members that are compatible with all/any requirements (depends on {@linkcode isAnd}) + */ override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { - for (const req of this.orRequirements) { - const result = req.queryParty(partyPokemon); - if (result?.length > 0) { - return result; - } + if (this.isAnd) { + return this.requirements.reduce((relevantPokemon, req) => req.queryParty(relevantPokemon), partyPokemon); + } else { + const matchingRequirement = this.requirements.find(req => req.queryParty(partyPokemon).length > 0); + return matchingRequirement ? matchingRequirement.queryParty(partyPokemon) : []; } - - return []; } + /** + * Retrieves a dialogue token key/value pair for the given {@linkcode EncounterPokemonRequirement | requirements}. + * @param scene The {@linkcode BattleScene} to check against + * @param pokemon The {@linkcode PlayerPokemon} to check against + * @returns A dialogue token key/value pair + * @throws An {@linkcode Error} if {@linkcode isAnd} is `true` (not supported) + */ override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - for (const req of this.orRequirements) { - if (req.meetsRequirement(scene)) { - return req.getDialogueToken(scene, pokemon); + if (this.isAnd) { + throw new Error("Not implemented (Sorry)"); + } else { + for (const req of this.requirements) { + if (req.meetsRequirement(scene)) { + return req.getDialogueToken(scene, pokemon); + } } - } - return this.orRequirements[0].getDialogueToken(scene, pokemon); + return this.requirements[0].getDialogueToken(scene, pokemon); + } } } diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index ee9eb159e10..c045ee51bd7 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -53,6 +53,7 @@ export interface IMysteryEncounter { hasBattleAnimationsWithoutTargets: boolean; skipEnemyBattleTurns: boolean; skipToFightInput: boolean; + preventGameStatsUpdates: boolean; onInit?: (scene: BattleScene) => boolean; onVisualsStart?: (scene: BattleScene) => boolean; @@ -150,6 +151,10 @@ export default class MysteryEncounter implements IMysteryEncounter { * If true, will skip COMMAND input and go straight to FIGHT (move select) input menu */ skipToFightInput: boolean; + /** + * If true, will prevent updating {@linkcode GameStats} for encountering and/or defeating Pokemon + */ + preventGameStatsUpdates: boolean; // #region Event callback functions @@ -548,6 +553,7 @@ export class MysteryEncounterBuilder implements Partial { hasBattleAnimationsWithoutTargets: boolean = false; skipEnemyBattleTurns: boolean = false; skipToFightInput: boolean = false; + preventGameStatsUpdates: boolean = false; maxAllowedEncounters: number = 3; expMultiplier: number = 1; @@ -735,6 +741,14 @@ export class MysteryEncounterBuilder implements Partial { return Object.assign(this, { skipToFightInput }); } + /** + * If true, will prevent updating {@linkcode GameStats} for encountering and/or defeating Pokemon + * Default `false` + */ + withPreventGameStatsUpdates(preventGameStatsUpdates: boolean): this & Required> { + return Object.assign(this, { preventGameStatsUpdates }); + } + /** * Sets the maximum number of times that an encounter can spawn in a given Classic run * @param maxAllowedEncounters diff --git a/src/data/mystery-encounters/requirements/requirement-groups.ts b/src/data/mystery-encounters/requirements/requirement-groups.ts index 63c899fc5e9..76bbb8f03a7 100644 --- a/src/data/mystery-encounters/requirements/requirement-groups.ts +++ b/src/data/mystery-encounters/requirements/requirement-groups.ts @@ -118,3 +118,20 @@ export const EXTORTION_ABILITIES = [ Abilities.SUCTION_CUPS, Abilities.STICKY_HOLD ]; + +/** + * Abilities that signify resistance to fire + */ +export const FIRE_RESISTANT_ABILITIES = [ + Abilities.FLAME_BODY, + Abilities.FLASH_FIRE, + Abilities.WELL_BAKED_BODY, + Abilities.HEATPROOF, + Abilities.THERMAL_EXCHANGE, + Abilities.THICK_FAT, + Abilities.WATER_BUBBLE, + Abilities.MAGMA_ARMOR, + Abilities.WATER_VEIL, + Abilities.STEAM_ENGINE, + Abilities.PRIMORDIAL_SEA +]; diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index 5fa8af95f4d..b1adc478ab0 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -21,6 +21,8 @@ import { Gender } from "#app/data/gender"; import { PermanentStat } from "#enums/stat"; import { VictoryPhase } from "#app/phases/victory-phase"; import { SummaryUiMode } from "#app/ui/summary-ui-handler"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import { Abilities } from "#enums/abilities"; /** Will give +1 level every 10 waves */ export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1; @@ -833,3 +835,21 @@ export function isPokemonValidForEncounterOptionSelection(pokemon: Pokemon, scen return null; } + +/** + * Permanently overrides the ability (not passive) of a pokemon. + * If the pokemon is a fusion, instead overrides the fused pokemon's ability. + */ +export function applyAbilityOverrideToPokemon(pokemon: Pokemon, ability: Abilities) { + if (pokemon.isFusion()) { + if (!pokemon.fusionCustomPokemonData) { + pokemon.fusionCustomPokemonData = new CustomPokemonData(); + } + pokemon.fusionCustomPokemonData.ability = ability; + } else { + if (!pokemon.customPokemonData) { + pokemon.customPokemonData = new CustomPokemonData(); + } + pokemon.customPokemonData.ability = ability; + } +} diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index fcc13975270..bc69b611075 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -2500,6 +2500,22 @@ export const trainerConfigs: TrainerConfigs = { [TrainerType.BUG_TYPE_SUPERFAN]: new TrainerConfig(++t).setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.ACE_TRAINER) .setPartyTemplates(new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE)), [TrainerType.EXPERT_POKEMON_BREEDER]: new TrainerConfig(++t).setMoneyMultiplier(3).setEncounterBgm(TrainerType.ACE_TRAINER).setLocalizedName("Expert Pokemon Breeder") - .setPartyTemplates(new TrainerPartyTemplate(3, PartyMemberStrength.STRONG)) + .setPartyTemplates(new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE)), + [TrainerType.FUTURE_SELF_M]: new TrainerConfig(++t) + .setMoneyMultiplier(0) + .setEncounterBgm("mystery_encounter_weird_dream") + .setBattleBgm("mystery_encounter_weird_dream") + .setMixedBattleBgm("mystery_encounter_weird_dream") + .setVictoryBgm("mystery_encounter_weird_dream") + .setLocalizedName("Future Self M") + .setPartyTemplates(new TrainerPartyTemplate(6, PartyMemberStrength.STRONG)), + [TrainerType.FUTURE_SELF_F]: new TrainerConfig(++t) + .setMoneyMultiplier(0) + .setEncounterBgm("mystery_encounter_weird_dream") + .setBattleBgm("mystery_encounter_weird_dream") + .setMixedBattleBgm("mystery_encounter_weird_dream") + .setVictoryBgm("mystery_encounter_weird_dream") + .setLocalizedName("Future Self F") + .setPartyTemplates(new TrainerPartyTemplate(6, PartyMemberStrength.STRONG)) }; diff --git a/src/enums/trainer-type.ts b/src/enums/trainer-type.ts index cb7509067b5..708faf69196 100644 --- a/src/enums/trainer-type.ts +++ b/src/enums/trainer-type.ts @@ -116,6 +116,8 @@ export enum TrainerType { VITO, BUG_TYPE_SUPERFAN, EXPERT_POKEMON_BREEDER, + FUTURE_SELF_M, + FUTURE_SELF_F, BROCK = 200, MISTY, diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6c4ae3b7ff9..d41c1f9eefa 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -93,7 +93,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public stats: integer[]; public ivs: integer[]; public nature: Nature; - public natureOverride: Nature | -1; public moveset: (PokemonMove | null)[]; public status: Status | null; public friendship: integer; @@ -4283,7 +4282,6 @@ export class PlayerPokemon extends Pokemon { if (newEvolution.condition?.predicate(this)) { const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, undefined, this.shiny, this.variant, this.ivs, this.nature); - newPokemon.natureOverride = this.natureOverride; newPokemon.passive = this.passive; newPokemon.moveset = this.moveset.slice(); newPokemon.moveset = this.copyMoveset(); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 8e7853a41bb..3e475c62590 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -10,7 +10,9 @@ import { getStatusEffectDescriptor, StatusEffect } from "#app/data/status-effect import { Type } from "#app/data/type"; import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerastallizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier } from "#app/modifier/modifier"; +import { + AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerastallizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier, TempExtraModifierModifier +} from "#app/modifier/modifier"; import { ModifierTier } from "#app/modifier/modifier-tier"; import Overrides from "#app/overrides"; import { Unlockables } from "#app/system/unlockables"; @@ -1561,6 +1563,7 @@ export const modifierTypes = { 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"), 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)), @@ -1577,13 +1580,13 @@ export const modifierTypes = { if (pregenArgs) { return new PokemonBaseStatTotalModifierType(pregenArgs[0] as number); } - return new PokemonBaseStatTotalModifierType(randSeedInt(20)); + return new PokemonBaseStatTotalModifierType(randSeedInt(20, 1)); }), MYSTERY_ENCOUNTER_OLD_GATEAU: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs) { return new PokemonBaseStatFlatModifierType(pregenArgs[0] as number, pregenArgs[1] as Stat[]); } - return new PokemonBaseStatFlatModifierType(randSeedInt(20), [ Stat.HP, Stat.ATK, Stat.DEF ]); + return new PokemonBaseStatFlatModifierType(randSeedInt(20, 1), [ Stat.HP, Stat.ATK, Stat.DEF ]); }), MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs) { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index b699c2483c9..11f16f103a5 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -404,6 +404,14 @@ export abstract class LapsingPersistentModifier extends PersistentModifier { 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; } @@ -960,7 +968,7 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { this.stackCount = pokemon ? pokemon.evoCounter + pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length - + pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier).length + + pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length : this.stackCount; const text = scene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11); @@ -975,7 +983,7 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { getMaxHeldItemCount(pokemon: Pokemon): number { this.stackCount = pokemon.evoCounter + pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length - + pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier).length; + + pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length; return 999; } } @@ -3288,6 +3296,60 @@ export class ExtraModifierModifier extends PersistentModifier { } } +/** + * Modifier used for timed boosts to the player's shop item rewards. + * @extends LapsingPersistentModifier + * @see {@linkcode apply} + */ +export class TempExtraModifierModifier extends LapsingPersistentModifier { + constructor(type: ModifierType, maxBattles: number, battleCount?: number, stackCount?: number) { + super(type, maxBattles, battleCount, stackCount); + } + + /** + * 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 + * @param scene + * @returns true if the modifier was successfully added or applied, false otherwise + */ + add(modifiers: PersistentModifier[], _virtual: boolean, scene: BattleScene): boolean { + for (const modifier of modifiers) { + if (this.match(modifier)) { + const modifierInstance = modifier as TempExtraModifierModifier; + const newBattleCount = this.getMaxBattles() + modifierInstance.getBattleCount(); + + modifierInstance.setNewBattleCount(newBattleCount); + scene.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 { constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 38d5cfb4a10..e5a60692bb4 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -1,7 +1,7 @@ import BattleScene from "#app/battle-scene"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { regenerateModifierPoolThresholds, ModifierTypeOption, ModifierType, getPlayerShopModifierTypeOptionsForWave, PokemonModifierType, FusePokemonModifierType, PokemonMoveModifierType, TmModifierType, RememberMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, ModifierPoolType, getPlayerModifierTypeOptions } from "#app/modifier/modifier-type"; -import { ExtraModifierModifier, HealShopCostModifier, Modifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { ExtraModifierModifier, HealShopCostModifier, Modifier, PokemonHeldItemModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler"; import { Mode } from "#app/ui/ui"; @@ -45,6 +45,7 @@ export class SelectModifierPhase extends BattlePhase { const modifierCount = new Utils.IntegerHolder(3); if (this.isPlayer()) { this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount); + this.scene.applyModifiers(TempExtraModifierModifier, true, modifierCount); } // If custom modifiers are specified, overrides default item count @@ -274,7 +275,13 @@ export class SelectModifierPhase extends BattlePhase { // Otherwise, continue with custom multiplier multiplier = this.customModifierSettings.rerollMultiplier; } - return Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * Math.pow(2, this.rerollCount) * multiplier, Number.MAX_SAFE_INTEGER); + + const baseMultiplier = Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * (2 ** this.rerollCount) * multiplier, Number.MAX_SAFE_INTEGER); + + // Apply Black Sludge to reroll cost + const modifiedRerollCost = new NumberHolder(baseMultiplier); + this.scene.applyModifier(HealShopCostModifier, true, modifiedRerollCost); + return modifiedRerollCost.value; } getPoolType(): ModifierPoolType { diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index e900ff97fc6..1faa31655df 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -25,12 +25,17 @@ export class VictoryPhase extends PokemonPhase { start() { super.start(); - this.scene.gameData.gameStats.pokemonDefeated++; + const isMysteryEncounter = this.scene.currentBattle.isBattleMysteryEncounter(); + + // update Pokemon defeated count except for MEs that disable it + if (!isMysteryEncounter || !this.scene.currentBattle.mysteryEncounter?.preventGameStatsUpdates) { + this.scene.gameData.gameStats.pokemonDefeated++; + } const expValue = this.getPokemon().getExpValue(); this.scene.applyPartyExp(expValue, true); - if (this.scene.currentBattle.isBattleMysteryEncounter()) { + if (isMysteryEncounter) { handleMysteryEncounterVictory(this.scene, false, this.isExpOnly); return this.end(); } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 5f9aad63408..c00159a7fd7 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1569,6 +1569,10 @@ export class GameData { } setPokemonSeen(pokemon: Pokemon, incrementCount: boolean = true, trainer: boolean = false): void { + // Some Mystery Encounters block updates to these stats + if (this.scene.currentBattle?.isBattleMysteryEncounter() && this.scene.currentBattle.mysteryEncounter?.preventGameStatsUpdates) { + return; + } const dexEntry = this.dexData[pokemon.species.speciesId]; dexEntry.seenAttr |= pokemon.getDexAttr(); if (incrementCount) { diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index e681c995b26..421739a9da1 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -92,7 +92,6 @@ export default class PokemonData { this.stats = source.stats; this.ivs = source.ivs; this.nature = source.nature !== undefined ? source.nature : 0 as Nature; - this.natureOverride = source.natureOverride !== undefined ? source.natureOverride : -1; this.friendship = source.friendship !== undefined ? source.friendship : getPokemonSpecies(this.species).baseFriendship; this.metLevel = source.metLevel || 5; this.metBiome = source.metBiome !== undefined ? source.metBiome : -1; @@ -117,6 +116,8 @@ export default class PokemonData { this.customPokemonData = new CustomPokemonData(source.customPokemonData); + // Deprecated, but needed for session data migration + this.natureOverride = source.natureOverride; this.mysteryEncounterPokemonData = new CustomPokemonData(source.mysteryEncounterPokemonData); this.fusionMysteryEncounterPokemonData = new CustomPokemonData(source.fusionMysteryEncounterPokemonData); diff --git a/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index 69c0a114645..66d628ef82f 100644 --- a/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -206,7 +206,7 @@ describe("Delibird-y - Mystery Encounter", () => { expect(candyJarAfter?.stackCount).toBe(1); }); - it("Should remove Reviver Seed and give the player a Healing Charm", async () => { + 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 @@ -220,11 +220,11 @@ describe("Delibird-y - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); const reviverSeedAfter = scene.findModifier(m => m instanceof PokemonInstantReviveModifier); - const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier); + const berryPouchAfter = scene.findModifier(m => m instanceof PreserveBerryModifier); expect(reviverSeedAfter).toBeUndefined(); - expect(healingCharmAfter).toBeDefined(); - expect(healingCharmAfter?.stackCount).toBe(1); + expect(berryPouchAfter).toBeDefined(); + expect(berryPouchAfter?.stackCount).toBe(1); }); it("Should give the player a Shell Bell if they have max stacks of Candy Jars", async () => { @@ -256,13 +256,13 @@ describe("Delibird-y - Mystery Encounter", () => { expect(shellBellAfter?.stackCount).toBe(1); }); - it("Should give the player a Shell Bell if they have max stacks of Healing Charms", async () => { + it("Should give the player a Shell Bell if they have max stacks of Berry Pouches", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); - // 5 Healing Charms + // 3 Berry Pouches scene.modifiers = []; - const healingCharm = generateModifierType(scene, modifierTypes.HEALING_CHARM)!.newModifier() as HealingBoosterModifier; - healingCharm.stackCount = 5; + const healingCharm = generateModifierType(scene, modifierTypes.BERRY_POUCH)!.newModifier() as PreserveBerryModifier; + healingCharm.stackCount = 3; await scene.addModifier(healingCharm, true, false, false, true); // Set 1 Reviver Seed on party lead @@ -275,12 +275,12 @@ describe("Delibird-y - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1 }); const reviverSeedAfter = scene.findModifier(m => m instanceof PokemonInstantReviveModifier); - const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier); + 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(5); + expect(healingCharmAfter?.stackCount).toBe(3); expect(shellBellAfter).toBeDefined(); expect(shellBellAfter?.stackCount).toBe(1); }); @@ -347,7 +347,7 @@ describe("Delibird-y - Mystery Encounter", () => { }); }); - it("Should decrease held item stacks and give the player a Berry Pouch", async () => { + it("Should decrease held item stacks and give the player a Healing Charm", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 2 Soul Dew on party lead @@ -361,14 +361,14 @@ describe("Delibird-y - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier); - const berryPouchAfter = scene.findModifier(m => m instanceof PreserveBerryModifier); + const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier); expect(soulDewAfter?.stackCount).toBe(1); - expect(berryPouchAfter).toBeDefined(); - expect(berryPouchAfter?.stackCount).toBe(1); + expect(healingCharmAfter).toBeDefined(); + expect(healingCharmAfter?.stackCount).toBe(1); }); - it("Should remove held item and give the player a Berry Pouch", async () => { + 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 @@ -382,20 +382,20 @@ describe("Delibird-y - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier); - const berryPouchAfter = scene.findModifier(m => m instanceof PreserveBerryModifier); + const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier); expect(soulDewAfter).toBeUndefined(); - expect(berryPouchAfter).toBeDefined(); - expect(berryPouchAfter?.stackCount).toBe(1); + expect(healingCharmAfter).toBeDefined(); + expect(healingCharmAfter?.stackCount).toBe(1); }); - it("Should give the player a Shell Bell if they have max stacks of Berry Pouches", async () => { + 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(scene, modifierTypes.BERRY_POUCH)!.newModifier() as PreserveBerryModifier; - healingCharm.stackCount = 3; + const healingCharm = generateModifierType(scene, modifierTypes.HEALING_CHARM)!.newModifier() as HealingBoosterModifier; + healingCharm.stackCount = 5; await scene.addModifier(healingCharm, true, false, false, true); // Set 1 Soul Dew on party lead @@ -408,12 +408,12 @@ describe("Delibird-y - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier); - const berryPouchAfter = scene.findModifier(m => m instanceof PreserveBerryModifier); + const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier); const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier); expect(soulDewAfter).toBeUndefined(); - expect(berryPouchAfter).toBeDefined(); - expect(berryPouchAfter?.stackCount).toBe(3); + expect(healingCharmAfter).toBeDefined(); + expect(healingCharmAfter?.stackCount).toBe(5); expect(shellBellAfter).toBeDefined(); expect(shellBellAfter?.stackCount).toBe(1); }); diff --git a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index a4f303d121f..5a270f1cbec 100644 --- a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -12,7 +12,7 @@ import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encount import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; import BattleScene from "#app/battle-scene"; -import { PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { AttackTypeBoosterModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { Type } from "#app/data/type"; import { Status, StatusEffect } from "#app/data/status-effect"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; @@ -22,6 +22,8 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Abilities } from "#enums/abilities"; import i18next from "i18next"; const namespace = "mysteryEncounters/fieryFallout"; @@ -42,10 +44,11 @@ describe("Fiery Fallout - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override.mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves() + .moveset([ Moves.PAYBACK, Moves.THUNDERBOLT ]); // Required for attack type booster item generation vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([ @@ -109,12 +112,16 @@ describe("Fiery Fallout - Mystery Encounter", () => { { species: getPokemonSpecies(Species.VOLCARONA), isBoss: false, - gender: Gender.MALE + gender: Gender.MALE, + tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], + mysteryEncounterBattleEffects: expect.any(Function) }, { species: getPokemonSpecies(Species.VOLCARONA), isBoss: false, - gender: Gender.FEMALE + gender: Gender.FEMALE, + tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], + mysteryEncounterBattleEffects: expect.any(Function) } ], doubleBattle: true, @@ -157,12 +164,11 @@ describe("Fiery Fallout - Mystery Encounter", () => { expect(enemyField[0].gender).not.toEqual(enemyField[1].gender); // Should be opposite gender const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]); - expect(movePhases.length).toBe(4); + expect(movePhases.length).toBe(2); expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.FIRE_SPIN).length).toBe(2); // Fire spin used twice before battle - expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.QUIVER_DANCE).length).toBe(2); // Quiver Dance used twice before battle }); - it("should give charcoal to lead pokemon", async () => { + it("should give attack type boosting item to lead pokemon", async () => { await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); @@ -172,8 +178,8 @@ describe("Fiery Fallout - Mystery Encounter", () => { const leadPokemonId = scene.getParty()?.[0].id; const leadPokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === leadPokemonId, true) as PokemonHeldItemModifier[]; - const charcoal = leadPokemonItems.find(i => i.type.name === "Charcoal"); - expect(charcoal).toBeDefined; + const item = leadPokemonItems.find(i => i instanceof AttackTypeBoosterModifier); + expect(item).toBeDefined; }); }); @@ -193,7 +199,7 @@ describe("Fiery Fallout - Mystery Encounter", () => { }); }); - it("should damage all non-fire party PKM by 20% and randomly burn 1", async () => { + it("should damage all non-fire party PKM by 20%, and burn + give Heatproof to a random Pokemon", async () => { await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); const party = scene.getParty(); @@ -210,7 +216,8 @@ describe("Fiery Fallout - Mystery Encounter", () => { burnablePokemon.forEach((pkm) => { expect(pkm.hp, `${pkm.name} should have received 20% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.2)); }); - expect(burnablePokemon.some(pkm => pkm?.status?.effect === StatusEffect.BURN)).toBeTruthy(); + expect(burnablePokemon.some(pkm => pkm.status?.effect === StatusEffect.BURN)).toBeTruthy(); + expect(burnablePokemon.some(pkm => pkm.customPokemonData.ability === Abilities.HEATPROOF)); notBurnablePokemon.forEach((pkm) => expect(pkm.hp, `${pkm.name} should be full hp: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp())); }); @@ -241,17 +248,15 @@ describe("Fiery Fallout - Mystery Encounter", () => { }); }); - it("should give charcoal to lead pokemon", async () => { + 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.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - const leadPokemonId = scene.getParty()?.[0].id; - const leadPokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier - && (m as PokemonHeldItemModifier).pokemonId === leadPokemonId, true) as PokemonHeldItemModifier[]; - const charcoal = leadPokemonItems.find(i => i.type.name === "Charcoal"); - expect(charcoal).toBeDefined; + const leadPokemonItems = scene.getParty()?.[0].getHeldItems() as PokemonHeldItemModifier[]; + const item = leadPokemonItems.find(i => i instanceof AttackTypeBoosterModifier); + expect(item).toBeDefined; }); it("should leave encounter without battle", async () => { @@ -264,7 +269,7 @@ describe("Fiery Fallout - Mystery Encounter", () => { }); it("should be disabled if not enough FIRE types are in party", async () => { - await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, [ Species.MAGIKARP, Species.ARCANINE ]); + await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, [ Species.MAGIKARP ]); await game.phaseInterceptor.to(MysteryEncounterPhase, false); const encounterPhase = scene.getCurrentPhase(); diff --git a/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index d761f2d1b21..8286c6a694b 100644 --- a/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -86,7 +86,7 @@ describe("Trash to Treasure - Mystery Encounter", () => { expect(TrashToTreasureEncounter.enemyPartyConfigs).toEqual([ { - levelAdditiveModifier: 1, + levelAdditiveModifier: 0.5, disableSwitch: true, pokemonConfigs: [ { diff --git a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index 0d463655a52..c1fa6d83a18 100644 --- a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -5,7 +5,7 @@ import { Species } from "#app/enums/species"; import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; +import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import BattleScene from "#app/battle-scene"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; @@ -15,6 +15,8 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/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 { CommandPhase } from "#app/phases/command-phase"; +import { ModifierTier } from "#app/modifier/modifier-tier"; const namespace = "mysteryEncounters/weirdDream"; const defaultParty = [ Species.MAGBY, Species.HAUNTER, Species.ABRA ]; @@ -70,7 +72,7 @@ describe("Weird Dream - Mystery Encounter", () => { expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`); expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}:description`); expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}:query`); - expect(WeirdDreamEncounter.options.length).toBe(2); + expect(WeirdDreamEncounter.options.length).toBe(3); }); it("should initialize fully", async () => { @@ -132,7 +134,7 @@ describe("Weird Dream - Mystery Encounter", () => { expect(plus40To50.length).toBe(1); }); - it("should have 1 Memory Mushroom, 5 Rogue Balls, and 2 Mints in rewards", async () => { + 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); @@ -141,11 +143,12 @@ describe("Weird Dream - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(4); + 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"); }); it("should leave encounter without battle", async () => { @@ -158,7 +161,7 @@ describe("Weird Dream - Mystery Encounter", () => { }); }); - describe("Option 2 - Leave", () => { + describe("Option 2 - Battle Future Self", () => { it("should have the correct properties", () => { const option = WeirdDreamEncounter.options[1]; expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); @@ -174,17 +177,63 @@ describe("Weird Dream - Mystery Encounter", () => { }); }); - it("should reduce party levels by 12.5%", async () => { + it("should start a battle against the player's transformation team", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); + await runMysteryEncounterToEnd(game, 2, undefined, true); + + const enemyField = scene.getEnemyField(); + expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(enemyField.length).toBe(1); + expect(scene.getEnemyParty().length).toBe(scene.getParty().length); + }); + + it("should have 2 Rogue/2 Ultra/2 Great items in rewards", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); + await runMysteryEncounterToEnd(game, 2, undefined, true); + await skipBattleRunMysteryEncounterRewardsPhase(game); + await game.phaseInterceptor.to(SelectModifierPhase, false); + expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + await game.phaseInterceptor.run(SelectModifierPhase); + + expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); + const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; + expect(modifierSelectHandler.options.length).toEqual(6); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount).toEqual(ModifierTier.ROGUE); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount).toEqual(ModifierTier.ROGUE); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount).toEqual(ModifierTier.ULTRA); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount).toEqual(ModifierTier.ULTRA); + expect(modifierSelectHandler.options[4].modifierTypeOption.type.tier - modifierSelectHandler.options[4].modifierTypeOption.upgradeCount).toEqual(ModifierTier.GREAT); + expect(modifierSelectHandler.options[5].modifierTypeOption.type.tier - modifierSelectHandler.options[5].modifierTypeOption.upgradeCount).toEqual(ModifierTier.GREAT); + }); + }); + + describe("Option 3 - Leave", () => { + it("should have the correct properties", () => { + const option = WeirdDreamEncounter.options[2]; + expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); + expect(option.dialogue).toBeDefined(); + expect(option.dialogue).toStrictEqual({ + buttonLabel: `${namespace}:option.3.label`, + buttonTooltip: `${namespace}:option.3.tooltip`, + selected: [ + { + text: `${namespace}:option.3.selected`, + }, + ], + }); + }); + + it("should reduce party levels by 10%", async () => { const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); const levelsPrior = scene.getParty().map(p => p.level); - await runMysteryEncounterToEnd(game, 2); + await runMysteryEncounterToEnd(game, 3); const levelsAfter = scene.getParty().map(p => p.level); for (let i = 0; i < levelsPrior.length; i++) { - expect(Math.max(Math.ceil(0.8875 * levelsPrior[i]), 1)).toBe(levelsAfter[i]); + expect(Math.max(Math.ceil(0.9 * levelsPrior[i]), 1)).toBe(levelsAfter[i]); expect(scene.getParty()[i].levelExp).toBe(0); } @@ -195,7 +244,7 @@ describe("Weird Dream - Mystery Encounter", () => { const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); - await runMysteryEncounterToEnd(game, 2); + await runMysteryEncounterToEnd(game, 3); expect(leaveEncounterWithoutBattleSpy).toBeCalled(); }); From a2419c4fc3d8556cc359edba4d7eb4761e7877a3 Mon Sep 17 00:00:00 2001 From: Opaque02 <66582645+Opaque02@users.noreply.github.com> Date: Thu, 24 Oct 2024 06:00:07 +1000 Subject: [PATCH 23/24] [Misc] Add admin for (un)linking 3rd party accounts (#4198) * Updated admin panel to allow the concept of unlinking accounts * Don't look too hard at this commit, nothing to see here * Admin stuff * Fixed linking and unlinking and updated menu options * Undid some changes and cleaned up some code * Updated some logic and added some comments * Updates to admin panel logic * Stupid promises everyone hates them and they deserver to die * Promise stuff still * Promises working thanks to Ydarissep on discord - pushing with debug code before it decides to stop working again * Removed debugging code * All discord functionality seems to be working here?? Not sure what happened but yay * Fixed up some bugs and code * Added registered date to the panel * Fixed and updated some minor error message related stuff * Minor changes * Fixed some minor bugs, made the save related errors have error codes, and added updated icons * Updated search field error * Missed a couple of things to push * Fixed linting and doc errors * Revert dev related code and clean up dev comments * Reverting utils * Updating front end to match back end from Pancakes' comments * make getFields and getInputFieldConfigs a single function of FormUiHandler * remove outdated doc * Apply suggestions from code review Moka review changes Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> * Added docs * eslint fixes * Fixed error not showing up in certain conditions --------- Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: MokaStitcher Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Co-authored-by: innerthunder --- public/images/ui/legacy/link_icon.png | Bin 0 -> 209 bytes public/images/ui/legacy/unlink_icon.png | Bin 0 -> 219 bytes public/images/ui/link_icon.png | Bin 0 -> 209 bytes public/images/ui/unlink_icon.png | Bin 0 -> 219 bytes src/loading-scene.ts | 2 + src/ui/admin-ui-handler.ts | 357 +++++++++++++++++++++--- src/ui/form-modal-ui-handler.ts | 57 ++-- src/ui/login-form-ui-handler.ts | 25 +- src/ui/menu-ui-handler.ts | 44 ++- src/ui/modal-ui-handler.ts | 3 + src/ui/registration-form-ui-handler.ts | 14 +- src/ui/rename-form-ui-handler.ts | 10 +- src/ui/test-dialogue-ui-handler.ts | 13 +- 13 files changed, 437 insertions(+), 88 deletions(-) create mode 100644 public/images/ui/legacy/link_icon.png create mode 100644 public/images/ui/legacy/unlink_icon.png create mode 100644 public/images/ui/link_icon.png create mode 100644 public/images/ui/unlink_icon.png diff --git a/public/images/ui/legacy/link_icon.png b/public/images/ui/legacy/link_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..56081261b9c699571cd9faf1e242b708031a2188 GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~(Ey(iS0F7S zBBGF@8~)z!6O!-hwX9tpn8k_Jk!mjw9*GyDgGrS;2x1BJppT^vIsrl$4>@*Pm% zaninU@BfKwmd1~MJyrHlcqDW3Lvre_D{FF2N-+OwVOYVqdCPK>IBtJ6t?$y}>T-NB yd;YT(r&d;#F$)^F-d(vS#eCYwSud6APOEbkF@Ah05PJ(~1%s!npUXO@geCwUuu3cd literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/unlink_icon.png b/public/images/ui/legacy/unlink_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f0da5f8e3eda4fdf27c6e07b11fffa17af715976 GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~p#Yx{S0JsU zsp;?U-__N%VZ(+;j~-2EH(3D`Wh)8t3ugEa0#%g{{sBc&JzX3_B&O!}ALKn?z`@LW zk*)tNn$K+b3IboFyt I=akR{02#wdng9R* literal 0 HcmV?d00001 diff --git a/public/images/ui/link_icon.png b/public/images/ui/link_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..56081261b9c699571cd9faf1e242b708031a2188 GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~(Ey(iS0F7S zBBGF@8~)z!6O!-hwX9tpn8k_Jk!mjw9*GyDgGrS;2x1BJppT^vIsrl$4>@*Pm% zaninU@BfKwmd1~MJyrHlcqDW3Lvre_D{FF2N-+OwVOYVqdCPK>IBtJ6t?$y}>T-NB yd;YT(r&d;#F$)^F-d(vS#eCYwSud6APOEbkF@Ah05PJ(~1%s!npUXO@geCwUuu3cd literal 0 HcmV?d00001 diff --git a/public/images/ui/unlink_icon.png b/public/images/ui/unlink_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f0da5f8e3eda4fdf27c6e07b11fffa17af715976 GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~p#Yx{S0JsU zsp;?U-__N%VZ(+;j~-2EH(3D`Wh)8t3ugEa0#%g{{sBc&JzX3_B&O!}ALKn?z`@LW zk*)tNn$K+b3IboFyt I=akR{02#wdng9R* literal 0 HcmV?d00001 diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 4f673fd2cfc..26936bcdaad 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -165,6 +165,8 @@ export class LoadingScene extends SceneBase { this.loadImage("discord", "ui"); this.loadImage("google", "ui"); this.loadImage("settings_icon", "ui"); + this.loadImage("link_icon", "ui"); + this.loadImage("unlink_icon", "ui"); this.loadImage("default_bg", "arenas"); // Load arena images diff --git a/src/ui/admin-ui-handler.ts b/src/ui/admin-ui-handler.ts index c73c02e66c2..6249e54d8c3 100644 --- a/src/ui/admin-ui-handler.ts +++ b/src/ui/admin-ui-handler.ts @@ -2,37 +2,83 @@ import BattleScene from "#app/battle-scene"; import { ModalConfig } from "./modal-ui-handler"; import { Mode } from "./ui"; import * as Utils from "../utils"; -import { FormModalUiHandler } from "./form-modal-ui-handler"; +import { FormModalUiHandler, InputFieldConfig } from "./form-modal-ui-handler"; import { Button } from "#app/enums/buttons"; +import { TextStyle } from "./text"; export default class AdminUiHandler extends FormModalUiHandler { + private adminMode: AdminMode; + private adminResult: AdminSearchInfo; + private config: ModalConfig; + + private readonly buttonGap = 10; + // http response from the server when a username isn't found in the server + private readonly httpUserNotFoundErrorCode: number = 404; + private readonly ERR_REQUIRED_FIELD = (field: string) => { + if (field === "username") { + return `${Utils.formatText(field)} is required`; + } else { + return `${Utils.formatText(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) => { + return `Username and ${service} successfully ${mode.toLowerCase()}ed`; + }; + private readonly ERR_USERNAME_NOT_FOUND: string = "Username not found!"; + private readonly ERR_GENERIC_ERROR: string = "There was an error"; + constructor(scene: BattleScene, mode: Mode | null = null) { super(scene, mode); } - setup(): void { - super.setup(); - } - - getModalTitle(config?: ModalConfig): string { + override getModalTitle(): string { return "Admin panel"; } - getFields(config?: ModalConfig): string[] { - return [ "Username", "Discord ID" ]; + override getWidth(): number { + return this.adminMode === AdminMode.ADMIN ? 180 : 160; } - getWidth(config?: ModalConfig): number { - return 160; + override getMargin(): [number, number, number, number] { + return [ 0, 0, 0, 0 ]; } - getMargin(config?: ModalConfig): [number, number, number, number] { - return [ 0, 0, 48, 0 ]; + override getButtonLabels(): string[] { + switch (this.adminMode) { + case AdminMode.LINK: + return [ "Link Account", "Cancel" ]; + case AdminMode.SEARCH: + return [ "Find account", "Cancel" ]; + case AdminMode.ADMIN: + return [ "Back to search", "Cancel" ]; + default: + return [ "Activate ADMIN", "Cancel" ]; + } } - getButtonLabels(config?: ModalConfig): string[] { - return [ "Link account", "Cancel" ]; + override getInputFieldConfigs(): InputFieldConfig[] { + const inputFieldConfigs: InputFieldConfig[] = []; + switch (this.adminMode) { + case AdminMode.LINK: + inputFieldConfigs.push( { label: "Username" }); + inputFieldConfigs.push( { label: "Discord ID" }); + break; + case AdminMode.SEARCH: + inputFieldConfigs.push( { label: "Username" }); + break; + case AdminMode.ADMIN: + const adminResult = this.adminResult ?? { username: "", discordId: "", googleId: "", lastLoggedIn: "", registered: "" }; + // Discord and Google ID fields that are not empty get locked, other fields are all locked + inputFieldConfigs.push( { label: "Username", isReadOnly: true }); + inputFieldConfigs.push( { label: "Discord ID", isReadOnly: adminResult.discordId !== "" }); + inputFieldConfigs.push( { label: "Google ID", isReadOnly: adminResult.googleId !== "" }); + inputFieldConfigs.push( { label: "Last played", isReadOnly: true }); + inputFieldConfigs.push( { label: "Registered", isReadOnly: true }); + break; + } + return inputFieldConfigs; } processInput(button: Button): boolean { @@ -45,44 +91,281 @@ export default class AdminUiHandler extends FormModalUiHandler { } show(args: any[]): boolean { + this.config = args[0] as ModalConfig; // config + this.adminMode = args[1] as AdminMode; // admin mode + this.adminResult = args[2] ?? { username: "", discordId: "", googleId: "", lastLoggedIn: "", registered: "" }; // admin result, if any + const isMessageError = args[3]; // is the message shown a success or error + + const fields = this.getInputFieldConfigs(); + const hasTitle = !!this.getModalTitle(); + + this.updateFields(fields, hasTitle); + this.updateContainer(this.config); + + const labels = this.getButtonLabels(); + for (let i = 0; i < labels.length; i++) { + this.buttonLabels[i].setText(labels[i]); // sets the label text + } + + this.errorMessage.setPosition(10, (hasTitle ? 31 : 5) + 20 * (fields.length - 1) + 16 + this.getButtonTopMargin()); // sets the position of the message dynamically + if (isMessageError) { + this.errorMessage.setColor(this.getTextColor(TextStyle.SUMMARY_PINK)); + this.errorMessage.setShadowColor(this.getTextColor(TextStyle.SUMMARY_PINK, true)); + } else { + this.errorMessage.setColor(this.getTextColor(TextStyle.SUMMARY_GREEN)); + this.errorMessage.setShadowColor(this.getTextColor(TextStyle.SUMMARY_GREEN, true)); + } + if (super.show(args)) { - const config = args[0] as ModalConfig; + this.populateFields(this.adminMode, this.adminResult); const originalSubmitAction = this.submitAction; this.submitAction = (_) => { this.submitAction = originalSubmitAction; + const adminSearchResult: AdminSearchInfo = this.convertInputsToAdmin(); // this converts the input texts into a single object for use later + const validFields = this.areFieldsValid(this.adminMode); + if (validFields.error) { + this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); // this is here to force a loading screen to allow the admin tool to reopen again if there's an error + return this.showMessage(validFields.errorMessage ?? "", adminSearchResult, true); + } this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); - const onFail = error => { - this.scene.ui.setMode(Mode.ADMIN, Object.assign(config, { errorMessage: error?.trim() })); - this.scene.ui.playError(); - }; - if (!this.inputs[0].text) { - return onFail("Username is required"); + if (this.adminMode === AdminMode.LINK) { + this.adminLinkUnlink(adminSearchResult, "discord", "Link") // calls server to link discord + .then(response => { + if (response.error) { + return this.showMessage(response.errorType, adminSearchResult, true); // error or some kind + } else { + return this.showMessage(this.SUCCESS_SERVICE_MODE("discord", "link"), adminSearchResult, false); // success + } + }); + } else if (this.adminMode === AdminMode.SEARCH) { + this.adminSearch(adminSearchResult) // admin search for username + .then(response => { + if (response.error) { + return this.showMessage(response.errorType, adminSearchResult, true); // failure + } + this.updateAdminPanelInfo(response.adminSearchResult ?? adminSearchResult); // success + }); + } else if (this.adminMode === AdminMode.ADMIN) { + this.updateAdminPanelInfo(adminSearchResult, AdminMode.SEARCH); } - if (!this.inputs[1].text) { - return onFail("Discord Id is required"); - } - Utils.apiPost("admin/account/discord-link", `username=${encodeURIComponent(this.inputs[0].text)}&discordId=${encodeURIComponent(this.inputs[1].text)}`, "application/x-www-form-urlencoded", true) - .then(response => { - if (!response.ok) { - console.error(response); - } - this.inputs[0].setText(""); - this.inputs[1].setText(""); - this.scene.ui.revertMode(); - }) - .catch((err) => { - console.error(err); - this.scene.ui.revertMode(); - }); return false; }; return true; } return false; + } + showMessage(message: string, adminResult: AdminSearchInfo, isError: boolean) { + this.scene.ui.setMode(Mode.ADMIN, Object.assign(this.config, { errorMessage: message?.trim() }), this.adminMode, adminResult, isError); + if (isError) { + this.scene.ui.playError(); + } else { + this.scene.ui.playSelect(); + } + } + + /** + * This is used to update the fields' text when loading in a new admin ui handler. It uses the {@linkcode adminResult} + * to update the input text based on the {@linkcode adminMode}. For a linking adminMode, it sets the username and discord. + * For a search adminMode, it sets the username. For an admin adminMode, it sets all the info from adminResult in the + * appropriate text boxes, and also sets the link/unlink icons for discord/google depending on the result + */ + private populateFields(adminMode: AdminMode, adminResult: AdminSearchInfo) { + switch (adminMode) { + case AdminMode.LINK: + this.inputs[0].setText(adminResult.username); + this.inputs[1].setText(adminResult.discordId); + break; + case AdminMode.SEARCH: + this.inputs[0].setText(adminResult.username); + break; + case AdminMode.ADMIN: + Object.keys(adminResult).forEach((aR, i) => { + this.inputs[i].setText(adminResult[aR]); + if (aR === "discordId" || aR === "googleId") { // this is here to add the icons for linking/unlinking of google/discord IDs + const nineSlice = this.inputContainers[i].list.find(iC => iC.type === "NineSlice"); + const img = this.scene.add.image(this.inputContainers[i].x + nineSlice!.width + this.buttonGap, this.inputContainers[i].y + (Math.floor(nineSlice!.height / 2)), adminResult[aR] === "" ? "link_icon" : "unlink_icon"); + img.setName(`adminBtn_${aR}`); + img.setOrigin(0.5, 0.5); + img.setInteractive(); + img.on("pointerdown", () => { + const service = aR.toLowerCase().replace("id", ""); // this takes our key (discordId or googleId) and removes the "Id" at the end to make it more url friendly + const mode = adminResult[aR] === "" ? "Link" : "Unlink"; // this figures out if we're linking or unlinking a service + const validFields = this.areFieldsValid(this.adminMode, service); + if (validFields.error) { + this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); // this is here to force a loading screen to allow the admin tool to reopen again if there's an error + return this.showMessage(validFields.errorMessage ?? "", adminResult, true); + } + this.adminLinkUnlink(this.convertInputsToAdmin(), service, mode).then(response => { // attempts to link/unlink depending on the service + if (response.error) { + this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); + return this.showMessage(response.errorType, adminResult, true); // fail + } else { // success, reload panel with new results + this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); + this.adminSearch(adminResult) + .then(response => { + if (response.error) { + return this.showMessage(response.errorType, adminResult, true); + } + return this.showMessage(this.SUCCESS_SERVICE_MODE(service, mode), response.adminSearchResult ?? adminResult, false); + }); + } + }); + }); + this.addInteractionHoverEffect(img); + this.modalContainer.add(img); + } + }); + break; + } + } + + private areFieldsValid(adminMode: AdminMode, service?: string): { error: boolean; errorMessage?: string; } { + switch (adminMode) { + case AdminMode.LINK: + if (!this.inputs[0].text) { // username missing from link panel + return { + error: true, + errorMessage: this.ERR_REQUIRED_FIELD("username") + }; + } + if (!this.inputs[1].text) { // discordId missing from linking panel + return { + error: true, + errorMessage: this.ERR_REQUIRED_FIELD("discord") + }; + } + break; + case AdminMode.SEARCH: + if (!this.inputs[0].text) { // username missing from search panel + return { + error: true, + errorMessage: this.ERR_REQUIRED_FIELD("username") + }; + } + break; + case AdminMode.ADMIN: + if (!this.inputs[1].text && service === "discord") { // discordId missing from admin panel + return { + error: true, + errorMessage: this.ERR_REQUIRED_FIELD(service) + }; + } + if (!this.inputs[2].text && service === "google") { // googleId missing from admin panel + return { + error: true, + errorMessage: this.ERR_REQUIRED_FIELD(service) + }; + } + break; + } + return { + error: false + }; + } + + private convertInputsToAdmin(): AdminSearchInfo { + return { + username: this.inputs[0]?.node ? this.inputs[0].text : "", + discordId: this.inputs[1]?.node ? this.inputs[1]?.text : "", + googleId: this.inputs[2]?.node ? this.inputs[2]?.text : "", + lastLoggedIn: this.inputs[3]?.node ? this.inputs[3]?.text : "", + registered: this.inputs[4]?.node ? this.inputs[4]?.text : "" + }; + } + + private async adminSearch(adminSearchResult: AdminSearchInfo) { + try { + const adminInfo = await Utils.apiFetch(`admin/account/adminSearch?username=${encodeURIComponent(adminSearchResult.username)}`, true); + if (!adminInfo.ok) { // error - if adminInfo.status === this.httpUserNotFoundErrorCode that means the username can't be found in the db + return { adminSearchResult: adminSearchResult, error: true, errorType: adminInfo.status === this.httpUserNotFoundErrorCode ? this.ERR_USERNAME_NOT_FOUND : this.ERR_GENERIC_ERROR }; + } else { // success + const adminInfoJson: AdminSearchInfo = await adminInfo.json(); + return { adminSearchResult: adminInfoJson, error: false }; + } + } catch (err) { + console.error(err); + return { error: true, errorType: err }; + } + } + + private async adminLinkUnlink(adminSearchResult: AdminSearchInfo, service: string, mode: string) { + try { + const response = await Utils.apiPost(`admin/account/${service}${mode}`, `username=${encodeURIComponent(adminSearchResult.username)}&${service}Id=${encodeURIComponent(service === "discord" ? adminSearchResult.discordId : adminSearchResult.googleId)}`, "application/x-www-form-urlencoded", true); + if (!response.ok) { // error - if response.status === this.httpUserNotFoundErrorCode that means the username can't be found in the db + return { adminSearchResult: adminSearchResult, error: true, errorType: response.status === this.httpUserNotFoundErrorCode ? this.ERR_USERNAME_NOT_FOUND : this.ERR_GENERIC_ERROR }; + } else { // success! + return { adminSearchResult: adminSearchResult, error: false }; + } + } catch (err) { + console.error(err); + return { error: true, errorType: err }; + } + } + + private updateAdminPanelInfo(adminSearchResult: AdminSearchInfo, mode?: AdminMode) { + mode = mode ?? AdminMode.ADMIN; + this.scene.ui.setMode(Mode.ADMIN, { + buttonActions: [ + // we double revert here and below to go back 2 layers of menus + () => { + this.scene.ui.revertMode(); + this.scene.ui.revertMode(); + }, + () => { + this.scene.ui.revertMode(); + this.scene.ui.revertMode(); + } + ] + }, mode, adminSearchResult); } clear(): void { super.clear(); + + // this is used to remove the existing fields on the admin panel so they can be updated + + const itemsToRemove: string[] = [ "formLabel", "adminBtn" ]; // this is the start of the names for each element we want to remove + const removeArray: any[] = []; + const mC = this.modalContainer.list; + for (let i = mC.length - 1; i >= 0; i--) { + /* This code looks for a few things before destroying the specific field; first it looks to see if the name of the element is %like% the itemsToRemove labels + * this means that anything with, for example, "formLabel", will be true. + * It then also checks for any containers that are within this.modalContainer, and checks if any of its child elements are of type rexInputText + * and if either of these conditions are met, the element is destroyed. + */ + if (itemsToRemove.some(iTR => mC[i].name.includes(iTR)) || (mC[i].type === "Container" && (mC[i] as Phaser.GameObjects.Container).list.find(m => m.type === "rexInputText"))) { + removeArray.push(mC[i]); + } + } + + while (removeArray.length > 0) { + this.modalContainer.remove(removeArray.pop(), true); + } } } + +export enum AdminMode { + LINK, + SEARCH, + ADMIN +} + +export function getAdminModeName(adminMode: AdminMode): string { + switch (adminMode) { + case AdminMode.LINK: + return "Link"; + case AdminMode.SEARCH: + return "Search"; + default: + return ""; + } +} + +interface AdminSearchInfo { + username: string; + discordId: string; + googleId: string; + lastLoggedIn: string; + registered: string; +} diff --git a/src/ui/form-modal-ui-handler.ts b/src/ui/form-modal-ui-handler.ts index 331154263ad..65ee9f2db10 100644 --- a/src/ui/form-modal-ui-handler.ts +++ b/src/ui/form-modal-ui-handler.ts @@ -5,7 +5,6 @@ import { TextStyle, addTextInputObject, addTextObject } from "./text"; import { WindowVariant, addWindow } from "./ui-theme"; import InputText from "phaser3-rex-plugins/plugins/inputtext"; import * as Utils from "../utils"; -import i18next from "i18next"; import { Button } from "#enums/buttons"; export interface FormModalConfig extends ModalConfig { @@ -19,6 +18,7 @@ export abstract class FormModalUiHandler extends ModalUiHandler { protected errorMessage: Phaser.GameObjects.Text; protected submitAction: Function | null; protected tween: Phaser.Tweens.Tween; + protected formLabels: Phaser.GameObjects.Text[]; constructor(scene: BattleScene, mode: Mode | null = null) { super(scene, mode); @@ -26,12 +26,18 @@ export abstract class FormModalUiHandler extends ModalUiHandler { this.editing = false; this.inputContainers = []; this.inputs = []; + this.formLabels = []; } - abstract getFields(): string[]; + /** + * Get configuration for all fields that should be part of the modal + * Gets used by {@linkcode updateFields} to add the proper text inputs and labels to the view + * @returns array of {@linkcode InputFieldConfig} + */ + abstract getInputFieldConfigs(): InputFieldConfig[]; getHeight(config?: ModalConfig): number { - return 20 * this.getFields().length + (this.getModalTitle() ? 26 : 0) + ((config as FormModalConfig)?.errorMessage ? 12 : 0) + this.getButtonTopMargin() + 28; + return 20 * this.getInputFieldConfigs().length + (this.getModalTitle() ? 26 : 0) + ((config as FormModalConfig)?.errorMessage ? 12 : 0) + this.getButtonTopMargin() + 28; } getReadableErrorMessage(error: string): string { @@ -45,37 +51,50 @@ export abstract class FormModalUiHandler extends ModalUiHandler { setup(): void { super.setup(); - const fields = this.getFields(); + const config = this.getInputFieldConfigs(); const hasTitle = !!this.getModalTitle(); - fields.forEach((field, f) => { - const label = addTextObject(this.scene, 10, (hasTitle ? 31 : 5) + 20 * f, field, TextStyle.TOOLTIP_CONTENT); + if (config.length >= 1) { + this.updateFields(config, hasTitle); + } - this.modalContainer.add(label); + this.errorMessage = addTextObject(this.scene, 10, (hasTitle ? 31 : 5) + 20 * (config.length - 1) + 16 + this.getButtonTopMargin(), "", TextStyle.TOOLTIP_CONTENT); + this.errorMessage.setColor(this.getTextColor(TextStyle.SUMMARY_PINK)); + this.errorMessage.setShadowColor(this.getTextColor(TextStyle.SUMMARY_PINK, true)); + this.errorMessage.setVisible(false); + this.modalContainer.add(this.errorMessage); + } + + protected updateFields(fieldsConfig: InputFieldConfig[], hasTitle: boolean) { + this.inputContainers = []; + this.inputs = []; + this.formLabels = []; + fieldsConfig.forEach((config, f) => { + const label = addTextObject(this.scene, 10, (hasTitle ? 31 : 5) + 20 * f, config.label, TextStyle.TOOLTIP_CONTENT); + label.name = "formLabel" + f; + + this.formLabels.push(label); + this.modalContainer.add(this.formLabels[this.formLabels.length - 1]); const inputContainer = this.scene.add.container(70, (hasTitle ? 28 : 2) + 20 * f); inputContainer.setVisible(false); const inputBg = addWindow(this.scene, 0, 0, 80, 16, false, false, 0, 0, WindowVariant.XTHIN); - const isPassword = field.includes(i18next.t("menu:password")) || field.includes(i18next.t("menu:confirmPassword")); - const input = addTextInputObject(this.scene, 4, -2, 440, 116, TextStyle.TOOLTIP_CONTENT, { type: isPassword ? "password" : "text", maxLength: isPassword ? 64 : 20 }); + const isPassword = config?.isPassword; + const isReadOnly = config?.isReadOnly; + const input = addTextInputObject(this.scene, 4, -2, 440, 116, TextStyle.TOOLTIP_CONTENT, { type: isPassword ? "password" : "text", maxLength: isPassword ? 64 : 20, readOnly: isReadOnly }); input.setOrigin(0, 0); inputContainer.add(inputBg); inputContainer.add(input); - this.modalContainer.add(inputContainer); this.inputContainers.push(inputContainer); + this.modalContainer.add(inputContainer); + this.inputs.push(input); }); - - this.errorMessage = addTextObject(this.scene, 10, (hasTitle ? 31 : 5) + 20 * (fields.length - 1) + 16 + this.getButtonTopMargin(), "", TextStyle.TOOLTIP_CONTENT); - this.errorMessage.setColor(this.getTextColor(TextStyle.SUMMARY_PINK)); - this.errorMessage.setShadowColor(this.getTextColor(TextStyle.SUMMARY_PINK, true)); - this.errorMessage.setVisible(false); - this.modalContainer.add(this.errorMessage); } show(args: any[]): boolean { @@ -149,3 +168,9 @@ export abstract class FormModalUiHandler extends ModalUiHandler { } } } + +export interface InputFieldConfig { + label: string, + isPassword?: boolean, + isReadOnly?: boolean +} diff --git a/src/ui/login-form-ui-handler.ts b/src/ui/login-form-ui-handler.ts index 8be432ad6c1..26a2a225ec6 100644 --- a/src/ui/login-form-ui-handler.ts +++ b/src/ui/login-form-ui-handler.ts @@ -1,4 +1,4 @@ -import { FormModalUiHandler } from "./form-modal-ui-handler"; +import { FormModalUiHandler, InputFieldConfig } from "./form-modal-ui-handler"; import { ModalConfig } from "./modal-ui-handler"; import * as Utils from "../utils"; import { Mode } from "./ui"; @@ -17,9 +17,9 @@ interface BuildInteractableImageOpts { export default class LoginFormUiHandler extends FormModalUiHandler { private readonly ERR_USERNAME: string = "invalid username"; - private readonly ERR_PASSWORD: string = "invalid password"; - private readonly ERR_ACCOUNT_EXIST: string = "account doesn't exist"; - private readonly ERR_PASSWORD_MATCH: string = "password doesn't match"; + private readonly ERR_PASSWORD: string = "invalid password"; + private readonly ERR_ACCOUNT_EXIST: string = "account doesn't exist"; + private readonly ERR_PASSWORD_MATCH: string = "password doesn't match"; private readonly ERR_NO_SAVES: string = "No save files found"; private readonly ERR_TOO_MANY_SAVES: string = "Too many save files found"; @@ -75,10 +75,6 @@ export default class LoginFormUiHandler extends FormModalUiHandler { return i18next.t("menu:login"); } - override getFields(_config?: ModalConfig): string[] { - return [ i18next.t("menu:username"), i18next.t("menu:password") ]; - } - override getWidth(_config?: ModalConfig): number { return 160; } @@ -106,14 +102,21 @@ export default class LoginFormUiHandler extends FormModalUiHandler { case this.ERR_PASSWORD_MATCH: return i18next.t("menu:unmatchingPassword"); case this.ERR_NO_SAVES: - return i18next.t("menu:noSaves"); + return "P01: " + i18next.t("menu:noSaves"); case this.ERR_TOO_MANY_SAVES: - return i18next.t("menu:tooManySaves"); + return "P02: " + i18next.t("menu:tooManySaves"); } return super.getReadableErrorMessage(error); } + override getInputFieldConfigs(): InputFieldConfig[] { + const inputFieldConfigs: InputFieldConfig[] = []; + inputFieldConfigs.push({ label: i18next.t("menu:username") }); + inputFieldConfigs.push({ label: i18next.t("menu:password"), isPassword: true }); + return inputFieldConfigs; + } + override show(args: any[]): boolean { if (super.show(args)) { @@ -164,7 +167,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler { [ this.discordImage, this.googleImage, this.usernameInfoImage ].forEach((img) => img.off("pointerdown")); } - private processExternalProvider(config: ModalConfig) : void { + private processExternalProvider(config: ModalConfig): void { this.externalPartyTitle.setText(i18next.t("menu:orUse") ?? ""); this.externalPartyTitle.setX(20 + this.externalPartyTitle.text.length); this.externalPartyTitle.setVisible(true); diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index 0f4c2d2f53e..301d54daa3a 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -13,6 +13,7 @@ import { GameDataType } from "#enums/game-data-type"; import BgmBar from "#app/ui/bgm-bar"; import AwaitableUiHandler from "./awaitable-ui-handler"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { AdminMode, getAdminModeName } from "./admin-ui-handler"; enum MenuOptions { GAME_SETTINGS, @@ -387,16 +388,41 @@ export default class MenuUiHandler extends MessageUiHandler { communityOptions.push({ label: "Admin", handler: () => { - ui.playSelect(); - ui.setOverlayMode(Mode.ADMIN, { - buttonActions: [ - () => { - ui.revertMode(); - }, - () => { - ui.revertMode(); + + const skippedAdminModes: AdminMode[] = [ AdminMode.ADMIN ]; // this is here so that we can skip the menu populating enums that aren't meant for the menu, such as the AdminMode.ADMIN + const options: OptionSelectItem[] = []; + Object.values(AdminMode).filter((v) => !isNaN(Number(v)) && !skippedAdminModes.includes(v as AdminMode)).forEach((mode) => { // this gets all the enums in a way we can use + options.push({ + label: getAdminModeName(mode as AdminMode), + handler: () => { + ui.playSelect(); + ui.setOverlayMode(Mode.ADMIN, { + buttonActions: [ + // we double revert here and below to go back 2 layers of menus + () => { + ui.revertMode(); + ui.revertMode(); + }, + () => { + ui.revertMode(); + ui.revertMode(); + } + ] + }, mode); // mode is our AdminMode enum + return true; } - ] + }); + }); + options.push({ + label: "Cancel", + handler: () => { + ui.revertMode(); + return true; + } + }); + this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + options: options, + delay: 0 }); return true; }, diff --git a/src/ui/modal-ui-handler.ts b/src/ui/modal-ui-handler.ts index 60204f18c4a..79f1e8afeed 100644 --- a/src/ui/modal-ui-handler.ts +++ b/src/ui/modal-ui-handler.ts @@ -15,12 +15,14 @@ export abstract class ModalUiHandler extends UiHandler { protected titleText: Phaser.GameObjects.Text; protected buttonContainers: Phaser.GameObjects.Container[]; protected buttonBgs: Phaser.GameObjects.NineSlice[]; + protected buttonLabels: Phaser.GameObjects.Text[]; constructor(scene: BattleScene, mode: Mode | null = null) { super(scene, mode); this.buttonContainers = []; this.buttonBgs = []; + this.buttonLabels = []; } abstract getModalTitle(config?: ModalConfig): string; @@ -75,6 +77,7 @@ export abstract class ModalUiHandler extends UiHandler { const buttonContainer = this.scene.add.container(0, buttonTopMargin); + this.buttonLabels.push(buttonLabel); this.buttonBgs.push(buttonBg); this.buttonContainers.push(buttonContainer); diff --git a/src/ui/registration-form-ui-handler.ts b/src/ui/registration-form-ui-handler.ts index 2f8486bcdb3..fc9eb85cbaf 100644 --- a/src/ui/registration-form-ui-handler.ts +++ b/src/ui/registration-form-ui-handler.ts @@ -1,4 +1,4 @@ -import { FormModalUiHandler } from "./form-modal-ui-handler"; +import { FormModalUiHandler, InputFieldConfig } from "./form-modal-ui-handler"; import { ModalConfig } from "./modal-ui-handler"; import * as Utils from "../utils"; import { Mode } from "./ui"; @@ -24,10 +24,6 @@ export default class RegistrationFormUiHandler extends FormModalUiHandler { return i18next.t("menu:register"); } - getFields(config?: ModalConfig): string[] { - return [ i18next.t("menu:username"), i18next.t("menu:password"), i18next.t("menu:confirmPassword") ]; - } - getWidth(config?: ModalConfig): number { return 160; } @@ -61,6 +57,14 @@ export default class RegistrationFormUiHandler extends FormModalUiHandler { return super.getReadableErrorMessage(error); } + override getInputFieldConfigs(): InputFieldConfig[] { + const inputFieldConfigs: InputFieldConfig[] = []; + inputFieldConfigs.push({ label: i18next.t("menu:username") }); + inputFieldConfigs.push({ label: i18next.t("menu:password"), isPassword: true }); + inputFieldConfigs.push({ label: i18next.t("menu:confirmPassword"), isPassword: true }); + return inputFieldConfigs; + } + setup(): void { super.setup(); diff --git a/src/ui/rename-form-ui-handler.ts b/src/ui/rename-form-ui-handler.ts index 078177cafb1..6e4c4c6809d 100644 --- a/src/ui/rename-form-ui-handler.ts +++ b/src/ui/rename-form-ui-handler.ts @@ -1,4 +1,4 @@ -import { FormModalUiHandler } from "./form-modal-ui-handler"; +import { FormModalUiHandler, InputFieldConfig } from "./form-modal-ui-handler"; import { ModalConfig } from "./modal-ui-handler"; import i18next from "i18next"; import { PlayerPokemon } from "#app/field/pokemon"; @@ -8,10 +8,6 @@ export default class RenameFormUiHandler extends FormModalUiHandler { return i18next.t("menu:renamePokemon"); } - getFields(config?: ModalConfig): string[] { - return [ i18next.t("menu:nickname") ]; - } - getWidth(config?: ModalConfig): number { return 160; } @@ -33,6 +29,10 @@ export default class RenameFormUiHandler extends FormModalUiHandler { return super.getReadableErrorMessage(error); } + override getInputFieldConfigs(): InputFieldConfig[] { + return [{ label: i18next.t("menu:nickname") }]; + } + show(args: any[]): boolean { if (super.show(args)) { const config = args[0] as ModalConfig; diff --git a/src/ui/test-dialogue-ui-handler.ts b/src/ui/test-dialogue-ui-handler.ts index 34fb80ecc89..bf0e7f6723f 100644 --- a/src/ui/test-dialogue-ui-handler.ts +++ b/src/ui/test-dialogue-ui-handler.ts @@ -1,4 +1,4 @@ -import { FormModalUiHandler } from "./form-modal-ui-handler"; +import { FormModalUiHandler, InputFieldConfig } from "./form-modal-ui-handler"; import { ModalConfig } from "./modal-ui-handler"; import i18next from "i18next"; import { PlayerPokemon } from "#app/field/pokemon"; @@ -43,10 +43,6 @@ export default class TestDialogueUiHandler extends FormModalUiHandler { return "Test Dialogue"; } - getFields(config?: ModalConfig): string[] { - return [ "Dialogue" ]; - } - getWidth(config?: ModalConfig): number { return 300; } @@ -68,8 +64,15 @@ export default class TestDialogueUiHandler extends FormModalUiHandler { return super.getReadableErrorMessage(error); } + override getInputFieldConfigs(): InputFieldConfig[] { + return [{ label: "Dialogue" }]; + } + show(args: any[]): boolean { const ui = this.getUi(); + const hasTitle = !!this.getModalTitle(); + this.updateFields(this.getInputFieldConfigs(), hasTitle); + this.updateContainer(args[0] as ModalConfig); const input = this.inputs[0]; input.setMaxLength(255); From 414e0a5447dfe1ef119291a57a1fc53cc45ec29b Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Wed, 23 Oct 2024 23:17:55 -0700 Subject: [PATCH 24/24] [Balance] Change Tyrogue to move-based evolutions (#4694) --- src/data/balance/pokemon-evolutions.ts | 19 +++++++++++++++---- src/data/balance/pokemon-level-moves.ts | 9 +++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index 7d0d86fadbf..4a6e44e0d51 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -1,7 +1,6 @@ import { Gender } from "#app/data/gender"; import { PokeballType } from "#app/data/pokeball"; import Pokemon from "#app/field/pokemon"; -import { Stat } from "#enums/stat"; import { Type } from "#app/data/type"; import * as Utils from "#app/utils"; import { WeatherType } from "#app/data/weather"; @@ -271,9 +270,21 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.MAROWAK, 28, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)) ], [Species.TYROGUE]: [ - new SpeciesEvolution(Species.HITMONLEE, 20, null, new SpeciesEvolutionCondition(p => p.stats[Stat.ATK] > p.stats[Stat.DEF])), - new SpeciesEvolution(Species.HITMONCHAN, 20, null, new SpeciesEvolutionCondition(p => p.stats[Stat.ATK] < p.stats[Stat.DEF])), - new SpeciesEvolution(Species.HITMONTOP, 20, null, new SpeciesEvolutionCondition(p => p.stats[Stat.ATK] === p.stats[Stat.DEF])) + /** + * Custom: Evolves into Hitmonlee, Hitmonchan or Hitmontop at level 20 + * if it knows Low Sweep, Mach Punch, or Rapid Spin, respectively. + * If Tyrogue knows multiple of these moves, its evolution is based on + * the first qualifying move in its moveset. + */ + new SpeciesEvolution(Species.HITMONLEE, 20, null, new SpeciesEvolutionCondition(p => + p.getMoveset(true).find(move => move && [ Moves.LOW_SWEEP, Moves.MACH_PUNCH, Moves.RAPID_SPIN ].includes(move?.moveId))?.moveId === Moves.LOW_SWEEP + )), + new SpeciesEvolution(Species.HITMONCHAN, 20, null, new SpeciesEvolutionCondition(p => + p.getMoveset(true).find(move => move && [ Moves.LOW_SWEEP, Moves.MACH_PUNCH, Moves.RAPID_SPIN ].includes(move?.moveId))?.moveId === Moves.MACH_PUNCH + )), + new SpeciesEvolution(Species.HITMONTOP, 20, null, new SpeciesEvolutionCondition(p => + p.getMoveset(true).find(move => move && [ Moves.LOW_SWEEP, Moves.MACH_PUNCH, Moves.RAPID_SPIN ].includes(move?.moveId))?.moveId === Moves.RAPID_SPIN + )), ], [Species.KOFFING]: [ new SpeciesEvolution(Species.GALAR_WEEZING, 35, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), diff --git a/src/data/balance/pokemon-level-moves.ts b/src/data/balance/pokemon-level-moves.ts index 53f547c4504..71d98fb4fc2 100644 --- a/src/data/balance/pokemon-level-moves.ts +++ b/src/data/balance/pokemon-level-moves.ts @@ -1835,6 +1835,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.LOW_SWEEP ], [ 1, Moves.JUMP_KICK ], [ 1, Moves.ROLLING_KICK ], + [ 1, Moves.MACH_PUNCH ], // Previous Stage Move, Custom + [ 1, Moves.RAPID_SPIN ], // Previous Stage Move, Custom [ 4, Moves.DOUBLE_KICK ], [ 8, Moves.LOW_KICK ], [ 12, Moves.ENDURE ], @@ -1857,6 +1859,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.FEINT ], [ 1, Moves.PURSUIT ], [ 1, Moves.COMET_PUNCH ], + [ 1, Moves.LOW_SWEEP ], // Previous Stage Move, Custom + [ 1, Moves.RAPID_SPIN ], // Previous Stage Move, Custom [ 4, Moves.MACH_PUNCH ], [ 8, Moves.VACUUM_WAVE ], [ 12, Moves.DETECT ], @@ -4148,6 +4152,9 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.FOCUS_ENERGY ], [ 1, Moves.FAKE_OUT ], [ 1, Moves.HELPING_HAND ], + [ 10, Moves.LOW_SWEEP ], // Custom + [ 10, Moves.MACH_PUNCH ], // Custom + [ 10, Moves.RAPID_SPIN ], // Custom ], [Species.HITMONTOP]: [ [ EVOLVE_MOVE, Moves.TRIPLE_KICK ], @@ -4159,6 +4166,8 @@ export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [ 1, Moves.FEINT ], [ 1, Moves.PURSUIT ], [ 1, Moves.ROLLING_KICK ], + [ 1, Moves.LOW_SWEEP ], // Previous Stage Move, Custom + [ 1, Moves.MACH_PUNCH ], // Previous Stage Move, Custom [ 4, Moves.QUICK_ATTACK ], [ 8, Moves.GYRO_BALL ], [ 12, Moves.DETECT ],