From d7194accdd182ac5082c07291b00fb47ccccb18c Mon Sep 17 00:00:00 2001 From: damocleas Date: Thu, 7 Aug 2025 17:42:58 -0400 Subject: [PATCH 01/18] [Balance] Minor ME Adjustments and Fixes 1.10 (#6166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adjusted A Trainer's Test - Adjusted The Expert Pokémon Breeder - Adjusted Trash to Treasure - Adjusted Weird Dream - Reorganized Dark Deal - Adjusted Weird Dream Test --- .../encounters/a-trainers-test-encounter.ts | 8 +- .../encounters/dark-deal-encounter.ts | 27 +++--- .../the-expert-pokemon-breeder-encounter.ts | 84 ++++++++++--------- .../encounters/trash-to-treasure-encounter.ts | 9 +- .../encounters/weird-dream-encounter.ts | 13 +-- .../encounters/weird-dream-encounter.test.ts | 5 +- 6 files changed, 80 insertions(+), 66 deletions(-) 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 5b2805f9310..ac3d4def654 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -63,12 +63,12 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder. break; case 3: trainerType = TrainerType.MIRA; - spriteKeys = getSpriteKeysFromSpecies(SpeciesId.ALAKAZAM, false, 1); + spriteKeys = getSpriteKeysFromSpecies(SpeciesId.ALAKAZAM); trainerNameKey = "mira"; break; case 4: trainerType = TrainerType.RILEY; - spriteKeys = getSpriteKeysFromSpecies(SpeciesId.LUCARIO, false, 1); + spriteKeys = getSpriteKeysFromSpecies(SpeciesId.LUCARIO); trainerNameKey = "riley"; break; default: @@ -164,8 +164,8 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder. encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`)); setEncounterRewards( { - guaranteedModifierTypeFuncs: [modifierTypes.SACRED_ASH], - guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA], + guaranteedModifierTypeFuncs: [modifierTypes.RELIC_GOLD], + guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE], fillRemaining: true, }, [eggOptions], diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 29517ac2531..d90e207cc9a 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -23,12 +23,8 @@ const namespace = "mysteryEncounters/darkDeal"; /** Exclude Ultra Beasts (inludes Cosmog/Solgaleo/Lunala/Necrozma), Paradox (includes Miraidon/Koraidon), Eternatus, and Mythicals */ const excludedBosses = [ - SpeciesId.NECROZMA, - SpeciesId.COSMOG, - SpeciesId.COSMOEM, - SpeciesId.SOLGALEO, - SpeciesId.LUNALA, SpeciesId.ETERNATUS, + /** UBs */ SpeciesId.NIHILEGO, SpeciesId.BUZZWOLE, SpeciesId.PHEROMOSA, @@ -40,6 +36,12 @@ const excludedBosses = [ SpeciesId.NAGANADEL, SpeciesId.STAKATAKA, SpeciesId.BLACEPHALON, + SpeciesId.COSMOG, + SpeciesId.COSMOEM, + SpeciesId.SOLGALEO, + SpeciesId.LUNALA, + SpeciesId.NECROZMA, + /** Paradox */ SpeciesId.GREAT_TUSK, SpeciesId.SCREAM_TAIL, SpeciesId.BRUTE_BONNET, @@ -47,10 +49,10 @@ const excludedBosses = [ SpeciesId.SLITHER_WING, SpeciesId.SANDY_SHOCKS, SpeciesId.ROARING_MOON, - SpeciesId.KORAIDON, SpeciesId.WALKING_WAKE, SpeciesId.GOUGING_FIRE, SpeciesId.RAGING_BOLT, + SpeciesId.KORAIDON, SpeciesId.IRON_TREADS, SpeciesId.IRON_BUNDLE, SpeciesId.IRON_HANDS, @@ -58,22 +60,23 @@ const excludedBosses = [ SpeciesId.IRON_MOTH, SpeciesId.IRON_THORNS, SpeciesId.IRON_VALIANT, - SpeciesId.MIRAIDON, SpeciesId.IRON_LEAVES, SpeciesId.IRON_BOULDER, SpeciesId.IRON_CROWN, + SpeciesId.MIRAIDON, + /** Mythical */ SpeciesId.MEW, SpeciesId.CELEBI, - SpeciesId.DEOXYS, SpeciesId.JIRACHI, - SpeciesId.DARKRAI, + SpeciesId.DEOXYS, SpeciesId.PHIONE, SpeciesId.MANAPHY, - SpeciesId.ARCEUS, + SpeciesId.DARKRAI, SpeciesId.SHAYMIN, + SpeciesId.ARCEUS, SpeciesId.VICTINI, - SpeciesId.MELOETTA, SpeciesId.KELDEO, + SpeciesId.MELOETTA, SpeciesId.GENESECT, SpeciesId.DIANCIE, SpeciesId.HOOPA, @@ -81,9 +84,9 @@ const excludedBosses = [ SpeciesId.MAGEARNA, SpeciesId.MARSHADOW, SpeciesId.ZERAORA, - SpeciesId.ZARUDE, SpeciesId.MELTAN, SpeciesId.MELMETAL, + SpeciesId.ZARUDE, SpeciesId.PECHARUNT, ]; 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 235bd322ef8..7c528e2305b 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 @@ -52,37 +52,6 @@ class BreederSpeciesEvolution { } const POOL_1_POKEMON: (SpeciesId | BreederSpeciesEvolution)[][] = [ - [SpeciesId.MUNCHLAX, new BreederSpeciesEvolution(SpeciesId.SNORLAX, SECOND_STAGE_EVOLUTION_WAVE)], - [ - SpeciesId.HAPPINY, - new BreederSpeciesEvolution(SpeciesId.CHANSEY, FIRST_STAGE_EVOLUTION_WAVE), - new BreederSpeciesEvolution(SpeciesId.BLISSEY, FINAL_STAGE_EVOLUTION_WAVE), - ], - [ - SpeciesId.MAGBY, - new BreederSpeciesEvolution(SpeciesId.MAGMAR, FIRST_STAGE_EVOLUTION_WAVE), - new BreederSpeciesEvolution(SpeciesId.MAGMORTAR, FINAL_STAGE_EVOLUTION_WAVE), - ], - [ - SpeciesId.ELEKID, - new BreederSpeciesEvolution(SpeciesId.ELECTABUZZ, FIRST_STAGE_EVOLUTION_WAVE), - new BreederSpeciesEvolution(SpeciesId.ELECTIVIRE, FINAL_STAGE_EVOLUTION_WAVE), - ], - [SpeciesId.RIOLU, new BreederSpeciesEvolution(SpeciesId.LUCARIO, SECOND_STAGE_EVOLUTION_WAVE)], - [ - SpeciesId.BUDEW, - new BreederSpeciesEvolution(SpeciesId.ROSELIA, FIRST_STAGE_EVOLUTION_WAVE), - new BreederSpeciesEvolution(SpeciesId.ROSERADE, FINAL_STAGE_EVOLUTION_WAVE), - ], - [SpeciesId.TOXEL, new BreederSpeciesEvolution(SpeciesId.TOXTRICITY, SECOND_STAGE_EVOLUTION_WAVE)], - [ - SpeciesId.MIME_JR, - new BreederSpeciesEvolution(SpeciesId.GALAR_MR_MIME, FIRST_STAGE_EVOLUTION_WAVE), - new BreederSpeciesEvolution(SpeciesId.MR_RIME, FINAL_STAGE_EVOLUTION_WAVE), - ], -]; - -const POOL_2_POKEMON: (SpeciesId | BreederSpeciesEvolution)[][] = [ [ SpeciesId.PICHU, new BreederSpeciesEvolution(SpeciesId.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), @@ -93,24 +62,63 @@ const POOL_2_POKEMON: (SpeciesId | BreederSpeciesEvolution)[][] = [ new BreederSpeciesEvolution(SpeciesId.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(SpeciesId.ALOLA_RAICHU, FINAL_STAGE_EVOLUTION_WAVE), ], - [SpeciesId.SMOOCHUM, new BreederSpeciesEvolution(SpeciesId.JYNX, SECOND_STAGE_EVOLUTION_WAVE)], - [SpeciesId.TYROGUE, new BreederSpeciesEvolution(SpeciesId.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE)], - [SpeciesId.TYROGUE, new BreederSpeciesEvolution(SpeciesId.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE)], - [SpeciesId.TYROGUE, new BreederSpeciesEvolution(SpeciesId.HITMONTOP, SECOND_STAGE_EVOLUTION_WAVE)], [ SpeciesId.IGGLYBUFF, new BreederSpeciesEvolution(SpeciesId.JIGGLYPUFF, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(SpeciesId.WIGGLYTUFF, FINAL_STAGE_EVOLUTION_WAVE), ], + [ + SpeciesId.TOGEPI, + new BreederSpeciesEvolution(SpeciesId.TOGETIC, FIRST_STAGE_EVOLUTION_WAVE), + new BreederSpeciesEvolution(SpeciesId.TOGEKISS, FINAL_STAGE_EVOLUTION_WAVE), + ], + [SpeciesId.TYROGUE, new BreederSpeciesEvolution(SpeciesId.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE)], + [SpeciesId.TYROGUE, new BreederSpeciesEvolution(SpeciesId.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE)], + [SpeciesId.TYROGUE, new BreederSpeciesEvolution(SpeciesId.HITMONTOP, SECOND_STAGE_EVOLUTION_WAVE)], + [SpeciesId.SMOOCHUM, new BreederSpeciesEvolution(SpeciesId.JYNX, FIRST_STAGE_EVOLUTION_WAVE)], [ SpeciesId.AZURILL, new BreederSpeciesEvolution(SpeciesId.MARILL, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(SpeciesId.AZUMARILL, FINAL_STAGE_EVOLUTION_WAVE), ], - [SpeciesId.WYNAUT, new BreederSpeciesEvolution(SpeciesId.WOBBUFFET, SECOND_STAGE_EVOLUTION_WAVE)], + [ + SpeciesId.BUDEW, + new BreederSpeciesEvolution(SpeciesId.ROSELIA, FIRST_STAGE_EVOLUTION_WAVE), + new BreederSpeciesEvolution(SpeciesId.ROSERADE, FINAL_STAGE_EVOLUTION_WAVE), + ], [SpeciesId.CHINGLING, new BreederSpeciesEvolution(SpeciesId.CHIMECHO, SECOND_STAGE_EVOLUTION_WAVE)], [SpeciesId.BONSLY, new BreederSpeciesEvolution(SpeciesId.SUDOWOODO, SECOND_STAGE_EVOLUTION_WAVE)], + [SpeciesId.MIME_JR, new BreederSpeciesEvolution(SpeciesId.MR_MIME, SECOND_STAGE_EVOLUTION_WAVE)], + [ + SpeciesId.MIME_JR, + new BreederSpeciesEvolution(SpeciesId.GALAR_MR_MIME, SECOND_STAGE_EVOLUTION_WAVE), + new BreederSpeciesEvolution(SpeciesId.MR_RIME, FINAL_STAGE_EVOLUTION_WAVE), + ], + [ + SpeciesId.HAPPINY, + new BreederSpeciesEvolution(SpeciesId.CHANSEY, FIRST_STAGE_EVOLUTION_WAVE), + new BreederSpeciesEvolution(SpeciesId.BLISSEY, FINAL_STAGE_EVOLUTION_WAVE), + ], [SpeciesId.MANTYKE, new BreederSpeciesEvolution(SpeciesId.MANTINE, SECOND_STAGE_EVOLUTION_WAVE)], + [SpeciesId.TOXEL, new BreederSpeciesEvolution(SpeciesId.TOXTRICITY, SECOND_STAGE_EVOLUTION_WAVE)], +]; + +const POOL_2_POKEMON: (SpeciesId | BreederSpeciesEvolution)[][] = [ + [SpeciesId.DITTO], + [ + SpeciesId.ELEKID, + new BreederSpeciesEvolution(SpeciesId.ELECTABUZZ, FIRST_STAGE_EVOLUTION_WAVE), + new BreederSpeciesEvolution(SpeciesId.ELECTIVIRE, FINAL_STAGE_EVOLUTION_WAVE), + ], + [ + SpeciesId.MAGBY, + new BreederSpeciesEvolution(SpeciesId.MAGMAR, FIRST_STAGE_EVOLUTION_WAVE), + new BreederSpeciesEvolution(SpeciesId.MAGMORTAR, FINAL_STAGE_EVOLUTION_WAVE), + ], + [SpeciesId.WYNAUT, new BreederSpeciesEvolution(SpeciesId.WOBBUFFET, SECOND_STAGE_EVOLUTION_WAVE)], + [SpeciesId.MUNCHLAX, new BreederSpeciesEvolution(SpeciesId.SNORLAX, SECOND_STAGE_EVOLUTION_WAVE)], + [SpeciesId.RIOLU, new BreederSpeciesEvolution(SpeciesId.LUCARIO, SECOND_STAGE_EVOLUTION_WAVE)], + [SpeciesId.AUDINO], ]; /** @@ -502,7 +510,7 @@ function getPartyConfig(): EnemyPartyConfig { shiny: true, variant: 1, nature: Nature.MODEST, - moveSet: [MoveId.MOONBLAST, MoveId.MYSTICAL_FIRE, MoveId.ICE_BEAM, MoveId.THUNDERBOLT], + moveSet: [MoveId.DAZZLING_GLEAM, MoveId.MYSTICAL_FIRE, MoveId.ICE_BEAM, MoveId.THUNDERBOLT], // Make this one have an item gimmick when we have more items/finish implementations ivs: [31, 31, 31, 31, 31, 31], }, { @@ -515,7 +523,7 @@ function getPartyConfig(): EnemyPartyConfig { shiny: true, variant: 2, nature: Nature.BOLD, - moveSet: [MoveId.TRI_ATTACK, MoveId.STORED_POWER, MoveId.TAKE_HEART, MoveId.MOONLIGHT], + moveSet: [MoveId.TRI_ATTACK, MoveId.STORED_POWER, MoveId.CALM_MIND, MoveId.MOONLIGHT], ivs: [31, 31, 31, 31, 31, 31], }, ); 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 452a9a8bb4b..74a36a280d3 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -13,8 +13,9 @@ import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#mod import type { PokemonHeldItemModifierType } from "#modifiers/modifier-type"; import { PokemonMove } from "#moves/pokemon-move"; import { showEncounterText } from "#mystery-encounters/encounter-dialogue-utils"; -import type { EnemyPartyConfig, EnemyPokemonConfig } from "#mystery-encounters/encounter-phase-utils"; import { + type EnemyPartyConfig, + type EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, @@ -23,8 +24,7 @@ import { transitionMysteryEncounterIntroVisuals, } from "#mystery-encounters/encounter-phase-utils"; import { applyModifierTypeToPlayerPokemon } from "#mystery-encounters/encounter-pokemon-utils"; -import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; -import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; +import { type MysteryEncounter, MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; import i18next from "#plugins/i18n"; import { randSeedInt } from "#utils/common"; @@ -200,7 +200,8 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde const encounter = globalScene.currentBattle.mysteryEncounter!; setEncounterRewards({ - guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], + guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS], + guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true, }); encounter.startOfBattleEffects.push( diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 1164d2ca7ca..b4a2ba4abd5 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -47,6 +47,7 @@ const namespace = "mysteryEncounters/weirdDream"; /** Exclude Ultra Beasts, Paradox, Eternatus, and all legendary/mythical/trio pokemon that are below 570 BST */ const EXCLUDED_TRANSFORMATION_SPECIES = [ + SpeciesId.ARCEUS, SpeciesId.ETERNATUS, /** UBs */ SpeciesId.NIHILEGO, @@ -82,20 +83,19 @@ const EXCLUDED_TRANSFORMATION_SPECIES = [ SpeciesId.IRON_BOULDER, SpeciesId.IRON_CROWN, /** These are banned so they don't appear in the < 570 BST pool */ + SpeciesId.PHIONE, + SpeciesId.TYPE_NULL, SpeciesId.COSMOG, + SpeciesId.COSMOEM, SpeciesId.MELTAN, SpeciesId.KUBFU, - SpeciesId.COSMOEM, - SpeciesId.POIPOLE, - SpeciesId.TERAPAGOS, - SpeciesId.TYPE_NULL, - SpeciesId.CALYREX, - SpeciesId.NAGANADEL, SpeciesId.URSHIFU, + SpeciesId.CALYREX, SpeciesId.OGERPON, SpeciesId.OKIDOGI, SpeciesId.MUNKIDORI, SpeciesId.FEZANDIPITI, + SpeciesId.TERAPAGOS, ]; const SUPER_LEGENDARY_BST_THRESHOLD = 600; @@ -226,6 +226,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit modifierTypes.MINT, modifierTypes.MINT, modifierTypes.MINT, + modifierTypes.MINT, ], fillRemaining: false, }); diff --git a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index e9fcc9797d1..ed0d612e967 100644 --- a/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -147,12 +147,13 @@ describe("Weird Dream - Mystery Encounter", () => { const modifierSelectHandler = scene.ui.handlers.find( h => h instanceof ModifierSelectUiHandler, ) as ModifierSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(5); + expect(modifierSelectHandler.options.length).toEqual(6); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("ROGUE_BALL"); expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toEqual("MINT"); expect(modifierSelectHandler.options[3].modifierTypeOption.type.id).toEqual("MINT"); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.id).toEqual("MINT"); + expect(modifierSelectHandler.options[4].modifierTypeOption.type.id).toEqual("MINT"); + expect(modifierSelectHandler.options[5].modifierTypeOption.type.id).toEqual("MINT"); }); it("should leave encounter without battle", async () => { From 94650670fd287737785d4da11beb0d514a573d0e Mon Sep 17 00:00:00 2001 From: "Amani H." <109637146+xsn34kzx@users.noreply.github.com> Date: Thu, 7 Aug 2025 20:47:28 -0400 Subject: [PATCH 02/18] [Challenge] Add Nuzlocke-related Challenges (#6186) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Challenge] Add Nuzlocke-related Challenges Co-authored-by: Matilde Simões Co-authored-by: Fuad Ali Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Sirzento * Add Sacred Ash to `revive` Group * Separate Challenge Utility Functions * Misc. Changes * Transition to `BooleanHolder` * Add "Nuzlocke" Achievement * Change Challenge Order * Adjust Nuzlocke Achievement to Include Fresh Start * Fix Infinite Reward Reroll Bug * Fix Party Heal * Minor Change * Adjust TODOs * Add Unit Tests * Tweak Faint Cry in Permanent Faint * Resolve rebase issue * Apply Matthew's Suggestions Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Apply Matthew's Suggestions Pt. 2 Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Fix and Lint Suggestions * Revert Accidental Overrides * Fix and Lint Suggestions Pt. 2 * Rename Challenges * Prevent `RandomMoveAttr` from Using Banned Moves * Update Locales --------- Co-authored-by: Matilde Simões Co-authored-by: Fuad Ali Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Sirzento Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --- src/data/challenge.ts | 474 +++++++----------- src/data/moves/move.ts | 14 +- src/data/moves/pokemon-move.ts | 24 +- .../utils/encounter-pokemon-utils.ts | 11 +- src/enums/challenge-type.ts | 42 +- src/enums/challenges.ts | 3 + src/field/pokemon.ts | 20 +- src/game-mode.ts | 16 +- src/modifier/modifier-type.ts | 42 +- src/phases/attempt-capture-phase.ts | 10 + src/phases/command-phase.ts | 28 +- src/phases/party-heal-phase.ts | 11 +- src/phases/select-biome-phase.ts | 10 +- src/phases/select-starter-phase.ts | 2 +- src/phases/victory-phase.ts | 7 +- src/system/achv.ts | 14 + src/system/game-data.ts | 2 +- src/ui/modifier-select-ui-handler.ts | 6 +- src/ui/party-ui-handler.ts | 2 +- src/ui/starter-select-ui-handler.ts | 2 +- src/utils/challenge-utils.ts | 409 +++++++++++++++ test/challenges/hardcore.test.ts | 169 +++++++ test/challenges/limited-catch.test.ts | 55 ++ test/challenges/limited-support.test.ts | 90 ++++ 24 files changed, 1129 insertions(+), 334 deletions(-) create mode 100644 src/utils/challenge-utils.ts create mode 100644 test/challenges/hardcore.test.ts create mode 100644 test/challenges/limited-catch.test.ts create mode 100644 test/challenges/limited-support.test.ts diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 1a1a3774f8f..724d1f302da 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1,29 +1,26 @@ import type { FixedBattleConfig } from "#app/battle"; import { getRandomTrainerFunc } from "#app/battle"; import { defaultStarterSpecies } from "#app/constants"; -import { globalScene } from "#app/global-scene"; -import { pokemonEvolutions } from "#balance/pokemon-evolutions"; import { speciesStarterCosts } from "#balance/starters"; -import { pokemonFormChanges } from "#data/pokemon-forms"; import type { PokemonSpecies } from "#data/pokemon-species"; import { BattleType } from "#enums/battle-type"; -import { ChallengeType } from "#enums/challenge-type"; import { Challenges } from "#enums/challenges"; import { TypeColor, TypeShadow } from "#enums/color"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import { ModifierTier } from "#enums/modifier-tier"; -import type { MoveId } from "#enums/move-id"; +import { MoveId } from "#enums/move-id"; import type { MoveSourceType } from "#enums/move-source-type"; import { Nature } from "#enums/nature"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import { TrainerVariant } from "#enums/trainer-variant"; -import type { Pokemon } from "#field/pokemon"; +import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; import { Trainer } from "#field/trainer"; +import type { ModifierTypeOption } from "#modifiers/modifier-type"; import { PokemonMove } from "#moves/pokemon-move"; import type { DexAttrProps, GameData } from "#system/game-data"; -import { BooleanHolder, isBetween, type NumberHolder, randSeedItem } from "#utils/common"; +import { type BooleanHolder, isBetween, type NumberHolder, randSeedItem } from "#utils/common"; import { deepCopy } from "#utils/data"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; import { toCamelCase, toSnakeCase } from "#utils/strings"; @@ -341,6 +338,83 @@ export abstract class Challenge { applyFlipStat(_pokemon: Pokemon, _baseStats: number[]) { return false; } + + /** + * An apply function for PARTY_HEAL. Derived classes should alter this. + * @param _status - Whether party healing is enabled or not + * @returns Whether this function did anything + */ + applyPartyHeal(_status: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for SHOP. Derived classes should alter this. + * @param _status - Whether the shop is or is not available after a wave + * @returns Whether this function did anything + */ + applyShop(_status: BooleanHolder) { + return false; + } + + /** + * An apply function for POKEMON_ADD_TO_PARTY. Derived classes should alter this. + * @param _pokemon - The pokemon being caught + * @param _status - Whether the pokemon can be added to the party or not + * @return Whether this function did anything + */ + applyPokemonAddToParty(_pokemon: EnemyPokemon, _status: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for POKEMON_FUSION. Derived classes should alter this. + * @param _pokemon - The pokemon being checked + * @param _status - Whether the selected pokemon is allowed to fuse or not + * @returns Whether this function did anything + */ + applyPokemonFusion(_pokemon: PlayerPokemon, _status: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for POKEMON_MOVE. Derived classes should alter this. + * @param _moveId - The {@linkcode MoveId} being checked + * @param _status - A {@linkcode BooleanHolder} containing the move's usability status + * @returns Whether this function did anything + */ + applyPokemonMove(_moveId: MoveId, _status: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for SHOP_ITEM. Derived classes should alter this. + * @param _shopItem - The item being checked + * @param _status - Whether the item should be added to the shop or not + * @returns Whether this function did anything + */ + applyShopItem(_shopItem: ModifierTypeOption | null, _status: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for WAVE_REWARD. Derived classes should alter this. + * @param _reward - The reward being checked + * @param _status - Whether the reward should be added to the reward options or not + * @returns Whether this function did anything + */ + applyWaveReward(_reward: ModifierTypeOption | null, _status: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for PREVENT_REVIVE. Derived classes should alter this. + * @param _status - Whether fainting is a permanent status or not + * @returns Whether this function did anything + */ + applyPreventRevive(_status: BooleanHolder): boolean { + return false; + } } type ChallengeCondition = (data: GameData) => boolean; @@ -864,208 +938,108 @@ export class LowerStarterPointsChallenge extends Challenge { } /** - * Apply all challenges that modify starter choice. - * @param challengeType {@link ChallengeType} ChallengeType.STARTER_CHOICE - * @param pokemon {@link PokemonSpecies} The pokemon to check the validity of. - * @param valid {@link BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. - * @param dexAttr {@link DexAttrProps} The dex attributes of the pokemon. - * @returns True if any challenge was successfully applied. + * Implements a No Support challenge */ -export function applyChallenges( - challengeType: ChallengeType.STARTER_CHOICE, - pokemon: PokemonSpecies, - valid: BooleanHolder, - dexAttr: DexAttrProps, -): boolean; -/** - * Apply all challenges that modify available total starter points. - * @param challengeType {@link ChallengeType} ChallengeType.STARTER_POINTS - * @param points {@link NumberHolder} The amount of points you have available. - * @returns True if any challenge was successfully applied. - */ -export function applyChallenges(challengeType: ChallengeType.STARTER_POINTS, points: NumberHolder): boolean; -/** - * Apply all challenges that modify the cost of a starter. - * @param challengeType {@link ChallengeType} ChallengeType.STARTER_COST - * @param species {@link SpeciesId} The pokemon to change the cost of. - * @param points {@link NumberHolder} The cost of the pokemon. - * @returns True if any challenge was successfully applied. - */ -export function applyChallenges( - challengeType: ChallengeType.STARTER_COST, - species: SpeciesId, - cost: NumberHolder, -): boolean; -/** - * Apply all challenges that modify a starter after selection. - * @param challengeType {@link ChallengeType} ChallengeType.STARTER_MODIFY - * @param pokemon {@link Pokemon} The starter pokemon to modify. - * @returns True if any challenge was successfully applied. - */ -export function applyChallenges(challengeType: ChallengeType.STARTER_MODIFY, pokemon: Pokemon): boolean; -/** - * Apply all challenges that what pokemon you can have in battle. - * @param challengeType {@link ChallengeType} ChallengeType.POKEMON_IN_BATTLE - * @param pokemon {@link Pokemon} The pokemon to check the validity of. - * @param valid {@link BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. - * @returns True if any challenge was successfully applied. - */ -export function applyChallenges( - challengeType: ChallengeType.POKEMON_IN_BATTLE, - pokemon: Pokemon, - valid: BooleanHolder, -): boolean; -/** - * Apply all challenges that modify what fixed battles there are. - * @param challengeType {@link ChallengeType} ChallengeType.FIXED_BATTLES - * @param waveIndex {@link Number} The current wave index. - * @param battleConfig {@link FixedBattleConfig} The battle config to modify. - * @returns True if any challenge was successfully applied. - */ -export function applyChallenges( - challengeType: ChallengeType.FIXED_BATTLES, - waveIndex: number, - battleConfig: FixedBattleConfig, -): boolean; -/** - * Apply all challenges that modify type effectiveness. - * @param challengeType {@linkcode ChallengeType} ChallengeType.TYPE_EFFECTIVENESS - * @param effectiveness {@linkcode NumberHolder} The current effectiveness of the move. - * @returns True if any challenge was successfully applied. - */ -export function applyChallenges(challengeType: ChallengeType.TYPE_EFFECTIVENESS, effectiveness: NumberHolder): boolean; -/** - * Apply all challenges that modify what level AI are. - * @param challengeType {@link ChallengeType} ChallengeType.AI_LEVEL - * @param level {@link NumberHolder} The generated level of the pokemon. - * @param levelCap {@link Number} The maximum level cap for the current wave. - * @param isTrainer {@link Boolean} Whether this is a trainer pokemon. - * @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon. - * @returns True if any challenge was successfully applied. - */ -export function applyChallenges( - challengeType: ChallengeType.AI_LEVEL, - level: NumberHolder, - levelCap: number, - isTrainer: boolean, - isBoss: boolean, -): boolean; -/** - * Apply all challenges that modify how many move slots the AI has. - * @param challengeType {@link ChallengeType} ChallengeType.AI_MOVE_SLOTS - * @param pokemon {@link Pokemon} The pokemon being considered. - * @param moveSlots {@link NumberHolder} The amount of move slots. - * @returns True if any challenge was successfully applied. - */ -export function applyChallenges( - challengeType: ChallengeType.AI_MOVE_SLOTS, - pokemon: Pokemon, - moveSlots: NumberHolder, -): boolean; -/** - * Apply all challenges that modify whether a pokemon has its passive. - * @param challengeType {@link ChallengeType} ChallengeType.PASSIVE_ACCESS - * @param pokemon {@link Pokemon} The pokemon to modify. - * @param hasPassive {@link BooleanHolder} Whether it has its passive. - * @returns True if any challenge was successfully applied. - */ -export function applyChallenges( - challengeType: ChallengeType.PASSIVE_ACCESS, - pokemon: Pokemon, - hasPassive: BooleanHolder, -): boolean; -/** - * Apply all challenges that modify the game modes settings. - * @param challengeType {@link ChallengeType} ChallengeType.GAME_MODE_MODIFY - * @returns True if any challenge was successfully applied. - */ -export function applyChallenges(challengeType: ChallengeType.GAME_MODE_MODIFY): boolean; -/** - * Apply all challenges that modify what level a pokemon can access a move. - * @param challengeType {@link ChallengeType} ChallengeType.MOVE_ACCESS - * @param pokemon {@link Pokemon} What pokemon would learn the move. - * @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. - * @param move {@link MoveId} The move in question. - * @param level {@link NumberHolder} The level threshold for access. - * @returns True if any challenge was successfully applied. - */ -export function applyChallenges( - challengeType: ChallengeType.MOVE_ACCESS, - pokemon: Pokemon, - moveSource: MoveSourceType, - move: MoveId, - level: NumberHolder, -): boolean; -/** - * Apply all challenges that modify what weight a pokemon gives to move generation - * @param challengeType {@link ChallengeType} ChallengeType.MOVE_WEIGHT - * @param pokemon {@link Pokemon} What pokemon would learn the move. - * @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. - * @param move {@link MoveId} The move in question. - * @param weight {@link NumberHolder} The weight of the move. - * @returns True if any challenge was successfully applied. - */ -export function applyChallenges( - challengeType: ChallengeType.MOVE_WEIGHT, - pokemon: Pokemon, - moveSource: MoveSourceType, - move: MoveId, - weight: NumberHolder, -): boolean; +export class LimitedSupportChallenge extends Challenge { + constructor() { + super(Challenges.LIMITED_SUPPORT, 3); + } -export function applyChallenges(challengeType: ChallengeType.FLIP_STAT, pokemon: Pokemon, baseStats: number[]): boolean; - -export function applyChallenges(challengeType: ChallengeType, ...args: any[]): boolean { - let ret = false; - globalScene.gameMode.challenges.forEach(c => { - if (c.value !== 0) { - switch (challengeType) { - case ChallengeType.STARTER_CHOICE: - ret ||= c.applyStarterChoice(args[0], args[1], args[2]); - 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(); - 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.FLIP_STAT: - ret ||= c.applyFlipStat(args[0], args[1]); - break; - } + override applyPartyHeal(status: BooleanHolder): boolean { + if (status.value) { + status.value = this.value === 2; + return true; } - }); - return ret; + return false; + } + + override applyShop(status: BooleanHolder): boolean { + if (status.value) { + status.value = this.value === 1; + return true; + } + return false; + } + + static override loadChallenge(source: LimitedSupportChallenge | any): LimitedSupportChallenge { + const newChallenge = new LimitedSupportChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + +/** + * Implements a Limited Catch challenge + */ +export class LimitedCatchChallenge extends Challenge { + constructor() { + super(Challenges.LIMITED_CATCH, 1); + } + + override applyPokemonAddToParty(pokemon: EnemyPokemon, status: BooleanHolder): boolean { + if (status.value) { + status.value = pokemon.metWave % 10 === 1; + return true; + } + return false; + } + + static override loadChallenge(source: LimitedCatchChallenge | any): LimitedCatchChallenge { + const newChallenge = new LimitedCatchChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + +/** + * Implements a Permanent Faint challenge + */ +export class HardcoreChallenge extends Challenge { + constructor() { + super(Challenges.HARDCORE, 1); + } + + override applyPokemonFusion(pokemon: PlayerPokemon, status: BooleanHolder): boolean { + if (!status.value) { + status.value = pokemon.isFainted(); + return true; + } + return false; + } + + override applyShopItem(shopItem: ModifierTypeOption | null, status: BooleanHolder): boolean { + status.value = shopItem?.type.group !== "revive"; + return true; + } + + override applyWaveReward(reward: ModifierTypeOption | null, status: BooleanHolder): boolean { + return this.applyShopItem(reward, status); + } + + override applyPokemonMove(moveId: MoveId, status: BooleanHolder) { + if (status.value) { + status.value = moveId !== MoveId.REVIVAL_BLESSING; + return true; + } + return false; + } + + override applyPreventRevive(status: BooleanHolder): boolean { + if (!status.value) { + status.value = true; + return true; + } + return false; + } + + static override loadChallenge(source: HardcoreChallenge | any): HardcoreChallenge { + const newChallenge = new HardcoreChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } } /** @@ -1089,6 +1063,12 @@ export function copyChallenge(source: Challenge | any): Challenge { return InverseBattleChallenge.loadChallenge(source); case Challenges.FLIP_STAT: return FlipStatChallenge.loadChallenge(source); + case Challenges.LIMITED_CATCH: + return LimitedCatchChallenge.loadChallenge(source); + case Challenges.LIMITED_SUPPORT: + return LimitedSupportChallenge.loadChallenge(source); + case Challenges.HARDCORE: + return HardcoreChallenge.loadChallenge(source); } throw new Error("Unknown challenge copied"); } @@ -1097,87 +1077,13 @@ export const allChallenges: Challenge[] = []; export function initChallenges() { allChallenges.push( + new FreshStartChallenge(), + new HardcoreChallenge(), + new LimitedCatchChallenge(), + new LimitedSupportChallenge(), new SingleGenerationChallenge(), new SingleTypeChallenge(), - new FreshStartChallenge(), new InverseBattleChallenge(), new FlipStatChallenge(), ); } - -/** - * Apply all challenges to the given starter (and form) to check its validity. - * Differs from {@linkcode checkSpeciesValidForChallenge} which only checks form changes. - * @param species - The {@linkcode PokemonSpecies} to check the validity of. - * @param dexAttr - The {@linkcode DexAttrProps | dex attributes} of the species, including its form index. - * @param soft - If `true`, allow it if it could become valid through evolution or form change. - * @returns `true` if the species is considered valid. - */ -export function checkStarterValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) { - if (!soft) { - const isValidForChallenge = new BooleanHolder(true); - applyChallenges(ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); - return isValidForChallenge.value; - } - // We check the validity of every evolution and form change, and require that at least one is valid - const speciesToCheck = [species.speciesId]; - while (speciesToCheck.length) { - const checking = speciesToCheck.pop(); - // Linter complains if we don't handle this - if (!checking) { - return false; - } - const checkingSpecies = getPokemonSpecies(checking); - if (checkSpeciesValidForChallenge(checkingSpecies, props, true)) { - return true; - } - if (checking && pokemonEvolutions.hasOwnProperty(checking)) { - pokemonEvolutions[checking].forEach(e => { - // Form check to deal with cases such as Basculin -> Basculegion - // TODO: does this miss anything if checking forms of a stage 2 Pokémon? - if (!e?.preFormKey || e.preFormKey === species.forms[props.formIndex].formKey) { - speciesToCheck.push(e.speciesId); - } - }); - } - } - return false; -} - -/** - * Apply all challenges to the given species (and form) to check its validity. - * Differs from {@linkcode checkStarterValidForChallenge} which also checks evolutions. - * @param species - The {@linkcode PokemonSpecies} to check the validity of. - * @param dexAttr - The {@linkcode DexAttrProps | dex attributes} of the species, including its form index. - * @param soft - If `true`, allow it if it could become valid through a form change. - * @returns `true` if the species is considered valid. - */ -function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) { - const isValidForChallenge = new BooleanHolder(true); - applyChallenges(ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); - if (!soft || !pokemonFormChanges.hasOwnProperty(species.speciesId)) { - return isValidForChallenge.value; - } - // If the form in props is valid, return true before checking other form changes - if (soft && isValidForChallenge.value) { - return true; - } - - const result = pokemonFormChanges[species.speciesId].some(f1 => { - // Exclude form changes that require the mon to be on the field to begin with - if (!("item" in f1.trigger)) { - return false; - } - - return species.forms.some((f2, formIndex) => { - if (f1.formKey === f2.formKey) { - const formProps = { ...props, formIndex }; - const isFormValidForChallenge = new BooleanHolder(true); - applyChallenges(ChallengeType.STARTER_CHOICE, species, isFormValidForChallenge, formProps); - return isFormValidForChallenge.value; - } - return false; - }); - }); - return result; -} diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 757f57e0b1f..067bd05c2ae 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -22,7 +22,6 @@ import { TypeBoostTag, } from "#data/battler-tags"; import { getBerryEffectFunc } from "#data/berry"; -import { applyChallenges } from "#data/challenge"; import { allAbilities, allMoves } from "#data/data-lists"; import { SpeciesFormChangeRevertWeatherFormTrigger } from "#data/form-change-triggers"; import { DelayedAttackTag } from "#data/positional-tags/positional-tag"; @@ -93,6 +92,7 @@ import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randS import { getEnumValues } from "#utils/enums"; import { toTitleCase } from "#utils/strings"; import i18next from "i18next"; +import { applyChallenges } from "#utils/challenge-utils"; /** * A function used to conditionally determine execution of a given {@linkcode MoveAttr}. @@ -124,7 +124,7 @@ export abstract class Move implements Localizable { /** * Check if the move is of the given subclass without requiring `instanceof`. * - * ⚠️ Does _not_ work for {@linkcode ChargingAttackMove} and {@linkcode ChargingSelfStatusMove} subclasses. For those, + * ! Does _not_ work for {@linkcode ChargingAttackMove} and {@linkcode ChargingSelfStatusMove} subclasses. For those, * use {@linkcode isChargingMove} instead. * * @param moveKind - The string name of the move to check against @@ -6906,13 +6906,19 @@ export class RandomMoveAttr extends CallMoveAttr { * @param move Move being used * @param args Unused */ - override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + override apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { + // TODO: Move this into the constructor to avoid constructing this every call const moveIds = getEnumValues(MoveId).map(m => !this.invalidMoves.has(m) && !allMoves[m].name.endsWith(" (N)") ? m : MoveId.NONE); let moveId: MoveId = MoveId.NONE; + const moveStatus = new BooleanHolder(true); do { moveId = this.getMoveOverride() ?? moveIds[user.randBattleSeedInt(moveIds.length)]; + moveStatus.value = moveId !== MoveId.NONE; + if (user.isPlayer()) { + applyChallenges(ChallengeType.POKEMON_MOVE, moveId, moveStatus); + } } - while (moveId === MoveId.NONE); + while (!moveStatus.value); return super.apply(user, target, allMoves[moveId], args); } } diff --git a/src/data/moves/pokemon-move.ts b/src/data/moves/pokemon-move.ts index d3f68fe9db4..3c96cbea598 100644 --- a/src/data/moves/pokemon-move.ts +++ b/src/data/moves/pokemon-move.ts @@ -1,8 +1,10 @@ import { allMoves } from "#data/data-lists"; -import type { MoveId } from "#enums/move-id"; +import { ChallengeType } from "#enums/challenge-type"; +import { MoveId } from "#enums/move-id"; import type { Pokemon } from "#field/pokemon"; import type { Move } from "#moves/move"; -import { toDmgValue } from "#utils/common"; +import { applyChallenges } from "#utils/challenge-utils"; +import { BooleanHolder, toDmgValue } from "#utils/common"; /** * Wrapper class for the {@linkcode Move} class for Pokemon to interact with. @@ -45,16 +47,18 @@ export class PokemonMove { * @returns Whether this {@linkcode PokemonMove} can be selected by this Pokemon. */ isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean { + const move = this.getMove(); // TODO: Add Sky Drop's 1 turn stall - if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) { - return false; + const usability = new BooleanHolder( + !move.name.endsWith(" (N)") && + (ignorePp || this.ppUsed < this.getMovePp() || move.pp === -1) && + // TODO: Review if the `MoveId.NONE` check is even necessary anymore + !(this.moveId !== MoveId.NONE && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)), + ); + if (pokemon.isPlayer()) { + applyChallenges(ChallengeType.POKEMON_MOVE, move.id, usability); } - - if (this.getMove().name.endsWith(" (N)")) { - return false; - } - - return ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1; + return usability.value; } getMove(): Move { diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index 19f06707257..7617fb5a89e 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -13,6 +13,7 @@ import { CustomPokemonData } from "#data/pokemon-data"; import type { PokemonSpecies } from "#data/pokemon-species"; import { getStatusEffectCatchRateMultiplier } from "#data/status-effect"; import type { AbilityId } from "#enums/ability-id"; +import { ChallengeType } from "#enums/challenge-type"; import { PlayerGender } from "#enums/player-gender"; import type { PokeballType } from "#enums/pokeball"; import type { PokemonType } from "#enums/pokemon-type"; @@ -33,7 +34,8 @@ import { achvs } from "#system/achv"; import type { PartyOption } from "#ui/party-ui-handler"; import { PartyUiMode } from "#ui/party-ui-handler"; import { SummaryUiMode } from "#ui/summary-ui-handler"; -import { isNullOrUndefined, randSeedInt } from "#utils/common"; +import { applyChallenges } from "#utils/challenge-utils"; +import { BooleanHolder, isNullOrUndefined, randSeedInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; @@ -706,6 +708,13 @@ export async function catchPokemon( }); }; Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => { + const addStatus = new BooleanHolder(true); + applyChallenges(ChallengeType.POKEMON_ADD_TO_PARTY, pokemon, addStatus); + if (!addStatus.value) { + removePokemon(); + end(); + return; + } if (globalScene.getPlayerParty().length === 6) { const promptRelease = () => { globalScene.ui.showText( diff --git a/src/enums/challenge-type.ts b/src/enums/challenge-type.ts index d9b1fce3e6e..053bcf92011 100644 --- a/src/enums/challenge-type.ts +++ b/src/enums/challenge-type.ts @@ -65,5 +65,45 @@ export enum ChallengeType { /** * Modifies what the pokemon stats for Flip Stat Mode. */ - FLIP_STAT + FLIP_STAT, + /** + * Challenges which conditionally enable or disable automatic party healing during biome transitions + * @see {@linkcode Challenge.applyPartyHealAvailability} + */ + PARTY_HEAL, + /** + * Challenges which conditionally enable or disable the shop + * @see {@linkcode Challenge.applyShopAvailability} + */ + SHOP, + /** + * Challenges which validate whether a pokemon can be added to the player's party or not + * @see {@linkcode Challenge.applyCatchAvailability} + */ + POKEMON_ADD_TO_PARTY, + /** + * Challenges which validate whether a pokemon is allowed to fuse or not + * @see {@linkcode Challenge.applyFusionAvailability} + */ + POKEMON_FUSION, + /** + * Challenges which validate whether particular moves can or cannot be used + * @see {@linkcode Challenge.applyMoveAvailability} + */ + POKEMON_MOVE, + /** + * Challenges which validate whether particular items are or are not sold in the shop + * @see {@linkcode Challenge.applyShopItems} + */ + SHOP_ITEM, + /** + * Challenges which validate whether particular items will be given as a reward after a wave + * @see {@linkcode Challenge.applyWaveRewards} + */ + WAVE_REWARD, + /** + * Challenges which prevent recovery from fainting + * @see {@linkcode Challenge.applyPermanentFaint} + */ + PREVENT_REVIVE, } diff --git a/src/enums/challenges.ts b/src/enums/challenges.ts index 7b506a61a2f..8d4f4c7a22a 100644 --- a/src/enums/challenges.ts +++ b/src/enums/challenges.ts @@ -6,4 +6,7 @@ export enum Challenges { FRESH_START, INVERSE_BATTLE, FLIP_STAT, + LIMITED_CATCH, + LIMITED_SUPPORT, + HARDCORE, } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index d4f332d887c..bc069aa1fc2 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -39,7 +39,6 @@ import { TrappedTag, TypeImmuneTag, } from "#data/battler-tags"; -import { applyChallenges } from "#data/challenge"; import { allAbilities, allMoves } from "#data/data-lists"; import { getLevelTotalExp } from "#data/exp"; import { @@ -148,6 +147,7 @@ import { EnemyBattleInfo } from "#ui/enemy-battle-info"; import type { PartyOption } from "#ui/party-ui-handler"; import { PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler"; import { PlayerBattleInfo } from "#ui/player-battle-info"; +import { applyChallenges } from "#utils/challenge-utils"; import { BooleanHolder, type Constructor, @@ -4558,8 +4558,17 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { } const key = this.species.getCryKey(this.formIndex); - let rate = 0.85; - const cry = globalScene.playSound(key, { rate: rate }) as AnySound; + const crySoundConfig = { rate: 0.85, detune: 0 }; + if (this.isPlayer()) { + // If fainting is permanent, emphasize impact + const preventRevive = new BooleanHolder(false); + applyChallenges(ChallengeType.PREVENT_REVIVE, preventRevive); + if (preventRevive.value) { + crySoundConfig.detune = -100; + crySoundConfig.rate = 0.7; + } + } + const cry = globalScene.playSound(key, crySoundConfig) as AnySound; if (!cry || globalScene.fieldVolume === 0) { callback(); return; @@ -4578,7 +4587,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { delay: fixedInt(delay), repeat: -1, callback: () => { - frameThreshold = sprite.anims.msPerFrame / rate; + frameThreshold = sprite.anims.msPerFrame / crySoundConfig.rate; frameProgress += delay; while (frameProgress > frameThreshold) { if (sprite.anims.duration) { @@ -4588,8 +4597,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { frameProgress -= frameThreshold; } if (cry && !cry.pendingRemove) { - rate *= 0.99; - cry.setRate(rate); + cry.setRate(crySoundConfig.rate * 0.99); } else { faintCryTimer?.destroy(); faintCryTimer = null; diff --git a/src/game-mode.ts b/src/game-mode.ts index c5ab120e218..82f7b4fa77f 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -2,8 +2,7 @@ import { FixedBattleConfig } from "#app/battle"; import { CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; -import type { Challenge } from "#data/challenge"; -import { allChallenges, applyChallenges, copyChallenge } from "#data/challenge"; +import { allChallenges, type Challenge, copyChallenge } from "#data/challenge"; import { getDailyStartingBiome } from "#data/daily-run"; import { allSpecies } from "#data/data-lists"; import type { PokemonSpecies } from "#data/pokemon-species"; @@ -14,7 +13,8 @@ import { GameModes } from "#enums/game-modes"; import { SpeciesId } from "#enums/species-id"; import type { Arena } from "#field/arena"; import { classicFixedBattles, type FixedBattleConfigs } from "#trainers/fixed-battle-configs"; -import { isNullOrUndefined, randSeedInt, randSeedItem } from "#utils/common"; +import { applyChallenges } from "#utils/challenge-utils"; +import { BooleanHolder, isNullOrUndefined, randSeedInt, randSeedItem } from "#utils/common"; import i18next from "i18next"; interface GameModeConfig { @@ -311,6 +311,16 @@ export class GameMode implements GameModeConfig { return this.battleConfig[waveIndex]; } + /** + * Check if the current game mode has the shop enabled or not + * @returns Whether the shop is available in the current mode + */ + public getShopStatus(): boolean { + const status = new BooleanHolder(!this.hasNoShop); + applyChallenges(ChallengeType.SHOP, status); + return status.value; + } + getClearScoreBonus(): number { switch (this.modeId) { case GameModes.CLASSIC: diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index b359ec756e6..9ec9ee0ad48 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -14,6 +14,7 @@ import { pokemonFormChanges, SpeciesFormChangeCondition } from "#data/pokemon-fo import { getStatusEffectDescriptor } from "#data/status-effect"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; +import { ChallengeType } from "#enums/challenge-type"; import { FormChangeItem } from "#enums/form-change-item"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import { ModifierTier } from "#enums/modifier-tier"; @@ -116,7 +117,16 @@ import type { ModifierTypeFunc, WeightedModifierTypeWeightFunc } from "#types/mo import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#ui/party-ui-handler"; import { PartyUiHandler } from "#ui/party-ui-handler"; import { getModifierTierTextTint } from "#ui/text"; -import { formatMoney, isNullOrUndefined, NumberHolder, padInt, randSeedInt, randSeedItem } from "#utils/common"; +import { applyChallenges } from "#utils/challenge-utils"; +import { + BooleanHolder, + formatMoney, + isNullOrUndefined, + NumberHolder, + padInt, + randSeedInt, + randSeedItem, +} from "#utils/common"; import { getEnumKeys, getEnumValues } from "#utils/enums"; import { getModifierPoolForType, getModifierType } from "#utils/modifier-utils"; import i18next from "i18next"; @@ -533,7 +543,9 @@ export class PokemonReviveModifierType extends PokemonHpRestoreModifierType { ); this.selectFilter = (pokemon: PlayerPokemon) => { - if (pokemon.hp) { + const selectStatus = new BooleanHolder(pokemon.hp !== 0); + applyChallenges(ChallengeType.PREVENT_REVIVE, selectStatus); + if (selectStatus.value) { return PartyUiHandler.NoEffectMessage; } return null; @@ -1011,6 +1023,7 @@ class AllPokemonFullReviveModifierType extends AllPokemonFullHpRestoreModifierTy "modifierType:ModifierType.AllPokemonFullReviveModifierType", (_type, _args) => new PokemonHpRestoreModifier(this, -1, 0, 100, false, true), ); + this.group = "revive"; } } @@ -1262,7 +1275,9 @@ export class FusePokemonModifierType extends PokemonModifierType { iconImage, (_type, args) => new FusePokemonModifier(this, (args[0] as PlayerPokemon).id, (args[1] as PlayerPokemon).id), (pokemon: PlayerPokemon) => { - if (pokemon.isFusion()) { + const selectStatus = new BooleanHolder(pokemon.isFusion()); + applyChallenges(ChallengeType.POKEMON_FUSION, pokemon, selectStatus); + if (selectStatus.value) { return PartyUiHandler.NoEffectMessage; } return null; @@ -2574,11 +2589,15 @@ function getModifierTypeOptionWithRetry( ): ModifierTypeOption { allowLuckUpgrades = allowLuckUpgrades ?? true; let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, tier, undefined, 0, allowLuckUpgrades); + const candidateValidity = new BooleanHolder(true); + applyChallenges(ChallengeType.WAVE_REWARD, candidate, candidateValidity); let r = 0; while ( - existingOptions.length && - ++r < retryCount && - existingOptions.filter(o => o.type.name === candidate?.type.name || o.type.group === candidate?.type.group).length + (existingOptions.length && + ++r < retryCount && + existingOptions.filter(o => o.type.name === candidate?.type.name || o.type.group === candidate?.type.group) + .length) || + !candidateValidity.value ) { candidate = getNewModifierTypeOption( party, @@ -2588,6 +2607,7 @@ function getModifierTypeOptionWithRetry( 0, allowLuckUpgrades, ); + applyChallenges(ChallengeType.WAVE_REWARD, candidate, candidateValidity); } return candidate!; } @@ -2648,7 +2668,15 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: number, baseC [new ModifierTypeOption(modifierTypeInitObj.FULL_RESTORE(), 0, baseCost * 2.25)], [new ModifierTypeOption(modifierTypeInitObj.SACRED_ASH(), 0, baseCost * 10)], ]; - return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat(); + + return options + .slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)) + .flat() + .filter(shopItem => { + const status = new BooleanHolder(true); + applyChallenges(ChallengeType.SHOP_ITEM, shopItem, status); + return status.value; + }); } export function getEnemyBuffModifierForWave( diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index fcddd23dd20..aea39cff294 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -12,6 +12,7 @@ import { } from "#data/pokeball"; import { getStatusEffectCatchRateMultiplier } from "#data/status-effect"; import { BattlerIndex } from "#enums/battler-index"; +import { ChallengeType } from "#enums/challenge-type"; import type { PokeballType } from "#enums/pokeball"; import { StatusEffect } from "#enums/status-effect"; import { UiMode } from "#enums/ui-mode"; @@ -23,6 +24,8 @@ import { achvs } from "#system/achv"; import type { PartyOption } from "#ui/party-ui-handler"; import { PartyUiMode } from "#ui/party-ui-handler"; import { SummaryUiMode } from "#ui/summary-ui-handler"; +import { applyChallenges } from "#utils/challenge-utils"; +import { BooleanHolder } from "#utils/common"; import i18next from "i18next"; // TODO: Refactor and split up to allow for overriding capture chance @@ -287,6 +290,13 @@ export class AttemptCapturePhase extends PokemonPhase { }); }; Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => { + const addStatus = new BooleanHolder(true); + applyChallenges(ChallengeType.POKEMON_ADD_TO_PARTY, pokemon, addStatus); + if (!addStatus.value) { + removePokemon(); + end(); + return; + } if (globalScene.getPlayerParty().length === PLAYER_PARTY_MAX_SIZE) { const promptRelease = () => { globalScene.ui.showText( diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 016d4ff5d3b..c7eb466157d 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -9,6 +9,7 @@ import { ArenaTagType } from "#enums/arena-tag-type"; import { BattleType } from "#enums/battle-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BiomeId } from "#enums/biome-id"; +import { ChallengeType } from "#enums/challenge-type"; import { Command } from "#enums/command"; import { FieldPosition } from "#enums/field-position"; import { MoveId } from "#enums/move-id"; @@ -21,6 +22,8 @@ import type { MoveTargetSet } from "#moves/move"; import { getMoveTargets } from "#moves/move-utils"; import { FieldPhase } from "#phases/field-phase"; import type { TurnMove } from "#types/turn-move"; +import { applyChallenges } from "#utils/challenge-utils"; +import { BooleanHolder } from "#utils/common"; import i18next from "i18next"; export class CommandPhase extends FieldPhase { @@ -210,16 +213,27 @@ export class CommandPhase extends FieldPhase { const move = user.getMoveset()[cursor]; globalScene.ui.setMode(UiMode.MESSAGE); - // Decides between a Disabled, Not Implemented, or No PP translation message - const errorMessage = user.isMoveRestricted(move.moveId, user) - ? user.getRestrictingTag(move.moveId, user)!.selectionDeniedText(user, move.moveId) - : move.getName().endsWith(" (N)") - ? "battle:moveNotImplemented" - : "battle:moveNoPP"; + // Set the translation key for why the move cannot be selected + let cannotSelectKey: string; + const moveStatus = new BooleanHolder(true); + applyChallenges(ChallengeType.POKEMON_MOVE, move.moveId, moveStatus); + if (!moveStatus.value) { + cannotSelectKey = "battle:moveCannotUseChallenge"; + } else if (move.getPpRatio() === 0) { + cannotSelectKey = "battle:moveNoPP"; + } else if (move.getName().endsWith(" (N)")) { + cannotSelectKey = "battle:moveNotImplemented"; + } else if (user.isMoveRestricted(move.moveId, user)) { + cannotSelectKey = user.getRestrictingTag(move.moveId, user)!.selectionDeniedText(user, move.moveId); + } else { + // TODO: Consider a message that signals a being unusable for an unknown reason + cannotSelectKey = ""; + } + const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator globalScene.ui.showText( - i18next.t(errorMessage, { moveName: moveName }), + i18next.t(cannotSelectKey, { moveName: moveName }), null, () => { globalScene.ui.clearText(); diff --git a/src/phases/party-heal-phase.ts b/src/phases/party-heal-phase.ts index 80d8b315102..1030d5eb9d9 100644 --- a/src/phases/party-heal-phase.ts +++ b/src/phases/party-heal-phase.ts @@ -1,6 +1,8 @@ import { globalScene } from "#app/global-scene"; +import { ChallengeType } from "#enums/challenge-type"; import { BattlePhase } from "#phases/battle-phase"; -import { fixedInt } from "#utils/common"; +import { applyChallenges } from "#utils/challenge-utils"; +import { BooleanHolder, fixedInt } from "#utils/common"; export class PartyHealPhase extends BattlePhase { public readonly phaseName = "PartyHealPhase"; @@ -20,7 +22,14 @@ export class PartyHealPhase extends BattlePhase { globalScene.fadeOutBgm(1000, false); } globalScene.ui.fadeOut(1000).then(() => { + const preventRevive = new BooleanHolder(false); + applyChallenges(ChallengeType.PREVENT_REVIVE, preventRevive); for (const pokemon of globalScene.getPlayerParty()) { + // Prevent reviving fainted pokemon during certain challenges + if (pokemon.isFainted() && preventRevive.value) { + continue; + } + pokemon.hp = pokemon.getMaxHp(); pokemon.resetStatus(true, false, false, true); for (const move of pokemon.moveset) { diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index ab96bf5c45e..f4fd11636fc 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -1,11 +1,13 @@ import { globalScene } from "#app/global-scene"; import { biomeLinks, getBiomeName } from "#balance/biomes"; import { BiomeId } from "#enums/biome-id"; +import { ChallengeType } from "#enums/challenge-type"; import { UiMode } from "#enums/ui-mode"; import { MapModifier, MoneyInterestModifier } from "#modifiers/modifier"; import { BattlePhase } from "#phases/battle-phase"; import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; -import { randSeedInt } from "#utils/common"; +import { applyChallenges } from "#utils/challenge-utils"; +import { BooleanHolder, randSeedInt } from "#utils/common"; export class SelectBiomePhase extends BattlePhase { public readonly phaseName = "SelectBiomePhase"; @@ -20,7 +22,11 @@ export class SelectBiomePhase extends BattlePhase { const setNextBiome = (nextBiome: BiomeId) => { if (nextWaveIndex % 10 === 1) { globalScene.applyModifiers(MoneyInterestModifier, true); - globalScene.phaseManager.unshiftNew("PartyHealPhase", false); + const healStatus = new BooleanHolder(true); + applyChallenges(ChallengeType.PARTY_HEAL, healStatus); + if (healStatus.value) { + globalScene.phaseManager.unshiftNew("PartyHealPhase", false); + } } globalScene.phaseManager.unshiftNew("SwitchBiomePhase", nextBiome); this.end(); diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index d6bd252c77d..ef3fa74bd44 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -1,7 +1,6 @@ import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; import { Phase } from "#app/phase"; -import { applyChallenges } from "#data/challenge"; import { SpeciesFormChangeMoveLearnedTrigger } from "#data/form-change-triggers"; import { Gender } from "#data/gender"; import { ChallengeType } from "#enums/challenge-type"; @@ -10,6 +9,7 @@ import { UiMode } from "#enums/ui-mode"; import { overrideHeldItems, overrideModifiers } from "#modifiers/modifier"; import { SaveSlotUiMode } from "#ui/save-slot-select-ui-handler"; import type { Starter } from "#ui/starter-select-ui-handler"; +import { applyChallenges } from "#utils/challenge-utils"; import { isNullOrUndefined } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index 4b1a79d7443..c0f4a32d7e1 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -3,10 +3,13 @@ import { globalScene } from "#app/global-scene"; import { modifierTypes } from "#data/data-lists"; import { BattleType } from "#enums/battle-type"; import type { BattlerIndex } from "#enums/battler-index"; +import { ChallengeType } from "#enums/challenge-type"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import type { CustomModifierSettings } from "#modifiers/modifier-type"; import { handleMysteryEncounterVictory } from "#mystery-encounters/encounter-phase-utils"; import { PokemonPhase } from "#phases/pokemon-phase"; +import { applyChallenges } from "#utils/challenge-utils"; +import { BooleanHolder } from "#utils/common"; export class VictoryPhase extends PokemonPhase { public readonly phaseName = "VictoryPhase"; @@ -63,7 +66,9 @@ export class VictoryPhase extends PokemonPhase { break; } } - if (globalScene.currentBattle.waveIndex % 10) { + const healStatus = new BooleanHolder(globalScene.currentBattle.waveIndex % 10 === 0); + applyChallenges(ChallengeType.PARTY_HEAL, healStatus); + if (!healStatus.value) { globalScene.phaseManager.pushNew( "SelectModifierPhase", undefined, diff --git a/src/system/achv.ts b/src/system/achv.ts index 69eade02e35..383d07252e6 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -5,6 +5,7 @@ import { FlipStatChallenge, FreshStartChallenge, InverseBattleChallenge, + LimitedCatchChallenge, SingleGenerationChallenge, SingleTypeChallenge, } from "#data/challenge"; @@ -922,6 +923,19 @@ export const achvs = { c.value > 0 && globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0), ).setSecret(), + // TODO: Decide on icon + NUZLOCKE: new ChallengeAchv( + "NUZLOCKE", + "", + "NUZLOCKE.description", + "leaf_stone", + 100, + c => + c instanceof LimitedCatchChallenge && + c.value > 0 && + globalScene.gameMode.challenges.some(c => c.id === Challenges.HARDCORE && c.value > 0) && + globalScene.gameMode.challenges.some(c => c.id === Challenges.FRESH_START && c.value > 0), + ), BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 50).setSecret(), }; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index d899afa19ef..ae559072e35 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -11,7 +11,6 @@ import { speciesEggMoves } from "#balance/egg-moves"; import { pokemonPrevolutions } from "#balance/pokemon-evolutions"; import { speciesStarterCosts } from "#balance/starters"; import { ArenaTrapTag } from "#data/arena-tag"; -import { applyChallenges } from "#data/challenge"; import { allMoves, allSpecies } from "#data/data-lists"; import type { Egg } from "#data/egg"; import { pokemonFormChanges } from "#data/pokemon-forms"; @@ -63,6 +62,7 @@ import { VoucherType, vouchers } from "#system/voucher"; import { trainerConfigs } from "#trainers/trainer-config"; import type { DexData, DexEntry } from "#types/dex-data"; import { RUN_HISTORY_LIMIT } from "#ui/run-history-ui-handler"; +import { applyChallenges } from "#utils/challenge-utils"; import { executeIf, fixedInt, isLocal, NumberHolder, randInt, randSeedItem } from "#utils/common"; import { decrypt, encrypt } from "#utils/data"; import { getEnumKeys } from "#utils/enums"; diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 16eecf6993d..d90b3310fb0 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -209,10 +209,10 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler { this.updateRerollCostText(); const typeOptions = args[1] as ModifierTypeOption[]; - const removeHealShop = globalScene.gameMode.hasNoShop; + const hasShop = globalScene.gameMode.getShopStatus(); const baseShopCost = new NumberHolder(globalScene.getWaveMoneyAmount(1)); globalScene.applyModifier(HealShopCostModifier, true, baseShopCost); - const shopTypeOptions = !removeHealShop + const shopTypeOptions = hasShop ? getPlayerShopModifierTypeOptionsForWave(globalScene.currentBattle.waveIndex, baseShopCost.value) : []; const optionsYOffset = @@ -370,7 +370,7 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler { if (globalScene.shopCursorTarget === ShopCursorTarget.CHECK_TEAM) { this.setRowCursor(0); this.setCursor(2); - } else if (globalScene.shopCursorTarget === ShopCursorTarget.SHOP && globalScene.gameMode.hasNoShop) { + } else if (globalScene.shopCursorTarget === ShopCursorTarget.SHOP && !hasShop) { this.setRowCursor(ShopCursorTarget.REWARDS); this.setCursor(0); } else { diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 0337e487200..c50065e5812 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1,7 +1,6 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { pokemonEvolutions } from "#balance/pokemon-evolutions"; -import { applyChallenges } from "#data/challenge"; import { allMoves } from "#data/data-lists"; import { SpeciesFormChangeItemTrigger } from "#data/form-change-triggers"; import { Gender, getGenderColor, getGenderSymbol } from "#data/gender"; @@ -26,6 +25,7 @@ import { MoveInfoOverlay } from "#ui/move-info-overlay"; import { PokemonIconAnimHandler, PokemonIconAnimMode } from "#ui/pokemon-icon-anim-handler"; import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; +import { applyChallenges } from "#utils/challenge-utils"; import { BooleanHolder, getLocalizedSpriteKey, randInt } from "#utils/common"; import { toTitleCase } from "#utils/strings"; import i18next from "i18next"; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 5a8e6b803a8..0827d5dd697 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -16,7 +16,6 @@ import { POKERUS_STARTER_COUNT, speciesStarterCosts, } from "#balance/starters"; -import { applyChallenges, checkStarterValidForChallenge } from "#data/challenge"; import { allAbilities, allMoves, allSpecies } from "#data/data-lists"; import { Egg, getEggTierForSpecies } from "#data/egg"; import { GrowthRate, getGrowthRateColor } from "#data/exp"; @@ -59,6 +58,7 @@ import { StarterContainer } from "#ui/starter-container"; import { StatsContainer } from "#ui/stats-container"; import { addBBCodeTextObject, addTextObject } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; +import { applyChallenges, checkStarterValidForChallenge } from "#utils/challenge-utils"; import { BooleanHolder, fixedInt, diff --git a/src/utils/challenge-utils.ts b/src/utils/challenge-utils.ts new file mode 100644 index 00000000000..43297027e04 --- /dev/null +++ b/src/utils/challenge-utils.ts @@ -0,0 +1,409 @@ +import type { FixedBattleConfig } from "#app/battle"; +import { globalScene } from "#app/global-scene"; +import { pokemonEvolutions } from "#balance/pokemon-evolutions"; +import { pokemonFormChanges } from "#data/pokemon-forms"; +import type { PokemonSpecies } from "#data/pokemon-species"; +import { ChallengeType } from "#enums/challenge-type"; +import type { MoveId } from "#enums/move-id"; +import type { MoveSourceType } from "#enums/move-source-type"; +import type { SpeciesId } from "#enums/species-id"; +import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; +import type { ModifierTypeOption } from "#modifiers/modifier-type"; +import type { DexAttrProps } from "#system/game-data"; +import { BooleanHolder, type NumberHolder } from "./common"; +import { getPokemonSpecies } from "./pokemon-utils"; + +/** + * @param challengeType - {@linkcode ChallengeType.STARTER_CHOICE} + * @param pokemon - The {@linkcode PokemonSpecies} to check the validity of + * @param valid - A {@linkcode BooleanHolder} holding the checked species' validity; + * will be set to `false` if the species is disallowed + * @param dexAttr - The {@linkcode DexAttrProps | Dex attributes} of the species + * @returns `true` if any challenge was successfully applied + */ +export function applyChallenges( + challengeType: ChallengeType.STARTER_CHOICE, + pokemon: PokemonSpecies, + valid: BooleanHolder, + dexAttr: DexAttrProps, +): boolean; +/** + * Apply all challenges that modify available total starter points. + * @param challengeType {@link ChallengeType} ChallengeType.STARTER_POINTS + * @param points {@link NumberHolder} The amount of points you have available. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges(challengeType: ChallengeType.STARTER_POINTS, points: NumberHolder): boolean; +/** + * Apply all challenges that modify the cost of a starter. + * @param challengeType {@link ChallengeType} ChallengeType.STARTER_COST + * @param species {@link SpeciesId} The pokemon to change the cost of. + * @param points {@link NumberHolder} The cost of the pokemon. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.STARTER_COST, + species: SpeciesId, + cost: NumberHolder, +): boolean; +/** + * Apply all challenges that modify a starter after selection. + * @param challengeType {@link ChallengeType} ChallengeType.STARTER_MODIFY + * @param pokemon {@link Pokemon} The starter pokemon to modify. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges(challengeType: ChallengeType.STARTER_MODIFY, pokemon: Pokemon): boolean; +/** + * Apply all challenges that what pokemon you can have in battle. + * @param challengeType {@link ChallengeType} ChallengeType.POKEMON_IN_BATTLE + * @param pokemon {@link Pokemon} The pokemon to check the validity of. + * @param valid {@link BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.POKEMON_IN_BATTLE, + pokemon: Pokemon, + valid: BooleanHolder, +): boolean; +/** + * Apply all challenges that modify what fixed battles there are. + * @param challengeType {@link ChallengeType} ChallengeType.FIXED_BATTLES + * @param waveIndex {@link Number} The current wave index. + * @param battleConfig {@link FixedBattleConfig} The battle config to modify. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.FIXED_BATTLES, + waveIndex: number, + battleConfig: FixedBattleConfig, +): boolean; +/** + * Apply all challenges that modify type effectiveness. + * @param challengeType {@linkcode ChallengeType} ChallengeType.TYPE_EFFECTIVENESS + * @param effectiveness {@linkcode NumberHolder} The current effectiveness of the move. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges(challengeType: ChallengeType.TYPE_EFFECTIVENESS, effectiveness: NumberHolder): boolean; +/** + * Apply all challenges that modify what level AI are. + * @param challengeType {@link ChallengeType} ChallengeType.AI_LEVEL + * @param level {@link NumberHolder} The generated level of the pokemon. + * @param levelCap {@link Number} The maximum level cap for the current wave. + * @param isTrainer {@link Boolean} Whether this is a trainer pokemon. + * @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.AI_LEVEL, + level: NumberHolder, + levelCap: number, + isTrainer: boolean, + isBoss: boolean, +): boolean; +/** + * Apply all challenges that modify how many move slots the AI has. + * @param challengeType {@link ChallengeType} ChallengeType.AI_MOVE_SLOTS + * @param pokemon {@link Pokemon} The pokemon being considered. + * @param moveSlots {@link NumberHolder} The amount of move slots. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.AI_MOVE_SLOTS, + pokemon: Pokemon, + moveSlots: NumberHolder, +): boolean; +/** + * Apply all challenges that modify whether a pokemon has its passive. + * @param challengeType {@link ChallengeType} ChallengeType.PASSIVE_ACCESS + * @param pokemon {@link Pokemon} The pokemon to modify. + * @param hasPassive {@link BooleanHolder} Whether it has its passive. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.PASSIVE_ACCESS, + pokemon: Pokemon, + hasPassive: BooleanHolder, +): boolean; +/** + * Apply all challenges that modify the game modes settings. + * @param challengeType {@link ChallengeType} ChallengeType.GAME_MODE_MODIFY + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges(challengeType: ChallengeType.GAME_MODE_MODIFY): boolean; +/** + * Apply all challenges that modify what level a pokemon can access a move. + * @param challengeType {@link ChallengeType} ChallengeType.MOVE_ACCESS + * @param pokemon {@link Pokemon} What pokemon would learn the move. + * @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. + * @param move {@link MoveId} The move in question. + * @param level {@link NumberHolder} The level threshold for access. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.MOVE_ACCESS, + pokemon: Pokemon, + moveSource: MoveSourceType, + move: MoveId, + level: NumberHolder, +): boolean; +/** + * Apply all challenges that modify what weight a pokemon gives to move generation + * @param challengeType {@link ChallengeType} ChallengeType.MOVE_WEIGHT + * @param pokemon {@link Pokemon} What pokemon would learn the move. + * @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. + * @param move {@link MoveId} The move in question. + * @param weight {@link NumberHolder} The weight of the move. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.MOVE_WEIGHT, + pokemon: Pokemon, + moveSource: MoveSourceType, + move: MoveId, + weight: NumberHolder, +): boolean; + +export function applyChallenges(challengeType: ChallengeType.FLIP_STAT, pokemon: Pokemon, baseStats: number[]): boolean; + +/** + * Apply all challenges that conditionally enable or disable automatic party healing during biome transitions + * @param challengeType - {@linkcode ChallengeType.PARTY_HEAL} + * @param status - Whether party healing is enabled or not + * @returns `true` if any challenge was successfully applied, `false` otherwise + */ +export function applyChallenges(challengeType: ChallengeType.PARTY_HEAL, status: BooleanHolder): boolean; + +/** + * Apply all challenges that conditionally enable or disable the shop + * @param challengeType - {@linkcode ChallengeType.SHOP} + * @param status - Whether party healing is enabled or not + * @returns `true` if any challenge was successfully applied, `false` otherwise + */ +export function applyChallenges(challengeType: ChallengeType.SHOP, status: BooleanHolder): boolean; + +/** + * Apply all challenges that validate whether a pokemon can be added to the player's party or not + * @param challengeType - {@linkcode ChallengeType.POKEMON_ADD_TO_PARTY} + * @param pokemon - The pokemon being caught + * @param status - Whether the pokemon can be added to the party or not + * @return `true` if any challenge was sucessfully applied, `false` otherwise + */ +export function applyChallenges( + challengeType: ChallengeType.POKEMON_ADD_TO_PARTY, + pokemon: EnemyPokemon, + status: BooleanHolder, +): boolean; + +/** + * Apply all challenges that validate whether a pokemon is allowed to fuse or not + * @param challengeType - {@linkcode ChallengeType.POKEMON_FUSION} + * @param pokemon - The pokemon being checked + * @param status - Whether the selected pokemon is allowed to fuse or not + * @return `true` if any challenge was sucessfully applied, `false` otherwise + */ +export function applyChallenges( + challengeType: ChallengeType.POKEMON_FUSION, + pokemon: PlayerPokemon, + status: BooleanHolder, +): boolean; + +/** + * Apply all challenges that validate whether particular moves can or cannot be used + * @param challengeType - {@linkcode ChallengeType.POKEMON_MOVE} + * @param moveId - The move being checked + * @param status - Whether the move can be used or not + * @return `true` if any challenge was sucessfully applied, `false` otherwise + */ +export function applyChallenges( + challengeType: ChallengeType.POKEMON_MOVE, + moveId: MoveId, + status: BooleanHolder, +): boolean; + +/** + * Apply all challenges that validate whether particular items are or are not sold in the shop + * @param challengeType - {@linkcode ChallengeType.SHOP_ITEM} + * @param shopItem - The item being checked + * @param status - Whether the item should be added to the shop or not + * @return `true` if any challenge was sucessfully applied, `false` otherwise + */ +export function applyChallenges( + challengeType: ChallengeType.SHOP_ITEM, + shopItem: ModifierTypeOption | null, + status: BooleanHolder, +): boolean; + +/** + * Apply all challenges that validate whether particular items will be given as a reward after a wave + * @param challengeType - {@linkcode ChallengeType.WAVE_REWARD} + * @param reward - The reward being checked + * @param status - Whether the reward should be added to the reward options or not + * @return `true` if any challenge was sucessfully applied, `false` otherwise + */ +export function applyChallenges( + challengeType: ChallengeType.WAVE_REWARD, + reward: ModifierTypeOption | null, + status: BooleanHolder, +): boolean; + +/** + * Apply all challenges that prevent recovery from fainting + * @param challengeType - {@linkcode ChallengeType.PREVENT_REVIVE} + * @param status - Whether fainting is a permanent status or not + * @return `true` if any challenge was sucessfully applied, `false` otherwise + */ +export function applyChallenges(challengeType: ChallengeType.PREVENT_REVIVE, status: BooleanHolder): boolean; + +export function applyChallenges(challengeType: ChallengeType, ...args: any[]): boolean { + let ret = false; + globalScene.gameMode.challenges.forEach(c => { + if (c.value !== 0) { + switch (challengeType) { + case ChallengeType.STARTER_CHOICE: + ret ||= c.applyStarterChoice(args[0], args[1], args[2]); + 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(); + 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.FLIP_STAT: + ret ||= c.applyFlipStat(args[0], args[1]); + break; + case ChallengeType.PARTY_HEAL: + ret ||= c.applyPartyHeal(args[0]); + break; + case ChallengeType.SHOP: + ret ||= c.applyShop(args[0]); + break; + case ChallengeType.POKEMON_ADD_TO_PARTY: + ret ||= c.applyPokemonAddToParty(args[0], args[1]); + break; + case ChallengeType.POKEMON_FUSION: + ret ||= c.applyPokemonFusion(args[0], args[1]); + break; + case ChallengeType.POKEMON_MOVE: + ret ||= c.applyPokemonMove(args[0], args[1]); + break; + case ChallengeType.SHOP_ITEM: + ret ||= c.applyShopItem(args[0], args[1]); + break; + case ChallengeType.WAVE_REWARD: + ret ||= c.applyWaveReward(args[0], args[1]); + break; + case ChallengeType.PREVENT_REVIVE: + ret ||= c.applyPreventRevive(args[0]); + break; + } + } + }); + return ret; +} + +/** + * Apply all challenges to the given starter (and form) to check its validity. + * Differs from {@linkcode checkSpeciesValidForChallenge} which only checks form changes. + * @param species - The {@linkcode PokemonSpecies} to check the validity of. + * @param dexAttr - The {@linkcode DexAttrProps | dex attributes} of the species, including its form index. + * @param soft - If `true`, allow it if it could become valid through evolution or form change. + * @returns `true` if the species is considered valid. + */ +export function checkStarterValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) { + if (!soft) { + const isValidForChallenge = new BooleanHolder(true); + applyChallenges(ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); + return isValidForChallenge.value; + } + // We check the validity of every evolution and form change, and require that at least one is valid + const speciesToCheck = [species.speciesId]; + while (speciesToCheck.length) { + const checking = speciesToCheck.pop(); + // Linter complains if we don't handle this + if (!checking) { + return false; + } + const checkingSpecies = getPokemonSpecies(checking); + if (checkSpeciesValidForChallenge(checkingSpecies, props, true)) { + return true; + } + if (checking && pokemonEvolutions.hasOwnProperty(checking)) { + pokemonEvolutions[checking].forEach(e => { + // Form check to deal with cases such as Basculin -> Basculegion + // TODO: does this miss anything if checking forms of a stage 2 Pokémon? + if (!e?.preFormKey || e.preFormKey === species.forms[props.formIndex].formKey) { + speciesToCheck.push(e.speciesId); + } + }); + } + } + return false; +} + +/** + * Apply all challenges to the given species (and form) to check its validity. + * Differs from {@linkcode checkStarterValidForChallenge} which also checks evolutions. + * @param species - The {@linkcode PokemonSpecies} to check the validity of. + * @param dexAttr - The {@linkcode DexAttrProps | dex attributes} of the species, including its form index. + * @param soft - If `true`, allow it if it could become valid through a form change. + * @returns `true` if the species is considered valid. + */ +function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) { + const isValidForChallenge = new BooleanHolder(true); + applyChallenges(ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); + if (!soft || !pokemonFormChanges.hasOwnProperty(species.speciesId)) { + return isValidForChallenge.value; + } + // If the form in props is valid, return true before checking other form changes + if (soft && isValidForChallenge.value) { + return true; + } + + const result = pokemonFormChanges[species.speciesId].some(f1 => { + // Exclude form changes that require the mon to be on the field to begin with + if (!("item" in f1.trigger)) { + return false; + } + + return species.forms.some((f2, formIndex) => { + if (f1.formKey === f2.formKey) { + const formProps = { ...props, formIndex }; + const isFormValidForChallenge = new BooleanHolder(true); + applyChallenges(ChallengeType.STARTER_CHOICE, species, isFormValidForChallenge, formProps); + return isFormValidForChallenge.value; + } + return false; + }); + }); + return result; +} diff --git a/test/challenges/hardcore.test.ts b/test/challenges/hardcore.test.ts new file mode 100644 index 00000000000..0f4ab1b9f02 --- /dev/null +++ b/test/challenges/hardcore.test.ts @@ -0,0 +1,169 @@ +import { Status } from "#data/status-effect"; +import { AbilityId } from "#enums/ability-id"; +import { Button } from "#enums/buttons"; +import { Challenges } from "#enums/challenges"; +import { MoveId } from "#enums/move-id"; +import { ShopCursorTarget } from "#enums/shop-cursor-target"; +import { SpeciesId } from "#enums/species-id"; +import { StatusEffect } from "#enums/status-effect"; +import { UiMode } from "#enums/ui-mode"; +import { GameManager } from "#test/test-utils/game-manager"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Challenges - Hardcore", () => { + 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.challengeMode.addChallenge(Challenges.HARDCORE, 1, 1); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.VOLTORB) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH) + .moveset(MoveId.SPLASH); + }); + + it("should render Revival Blessing unusable by players only", async () => { + game.override.enemyMoveset(MoveId.REVIVAL_BLESSING).moveset(MoveId.REVIVAL_BLESSING); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF]); + + const player = game.field.getPlayerPokemon(); + const revBlessing = player.getMoveset()[0]; + expect(revBlessing.isUsable(player)).toBe(false); + + game.move.select(MoveId.REVIVAL_BLESSING); + await game.toEndOfTurn(); + + // Player struggled due to only move being the unusable Revival Blessing + expect(player).toHaveUsedMove(MoveId.STRUGGLE); + expect(game.field.getEnemyPokemon()).toHaveUsedMove(MoveId.REVIVAL_BLESSING); + }); + + it("prevents REVIVE items in shop and in wave rewards", async () => { + game.override.startingWave(181).startingLevel(200); + await game.challengeMode.startBattle(); + + game.move.select(MoveId.SPLASH); + await game.doKillOpponents(); + + await game.phaseInterceptor.to("SelectModifierPhase"); + expect(game.scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); + const modifierSelectHandler = game.scene.ui.handlers.find( + h => h instanceof ModifierSelectUiHandler, + ) as ModifierSelectUiHandler; + expect( + modifierSelectHandler.options.find(reward => reward.modifierTypeOption.type.group === "revive"), + ).toBeUndefined(); + expect( + modifierSelectHandler.shopOptionsRows.find(row => + row.find(item => item.modifierTypeOption.type.group === "revive"), + ), + ).toBeUndefined(); + }); + + it("prevents the automatic party heal from reviving fainted Pokémon", async () => { + game.override.startingWave(10).startingLevel(200); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF, SpeciesId.WHISMUR]); + + const faintedPokemon = game.scene.getPlayerParty()[1]; + faintedPokemon.hp = 0; + faintedPokemon.status = new Status(StatusEffect.FAINT); + expect(faintedPokemon.isFainted()).toBe(true); + + game.move.select(MoveId.SPLASH); + await game.doKillOpponents(); + + await game.toNextWave(); + + expect(faintedPokemon.isFainted()).toBe(true); + }); + + // TODO: Couldn't figure out how to select party Pokémon + it.skip("prevents fusion with a fainted Pokémon", async () => { + game.override.itemRewards([{ name: "DNA_SPLICERS" }]); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF, SpeciesId.WHISMUR]); + + const faintedPokemon = game.scene.getPlayerParty()[1]; + faintedPokemon.hp = 0; + faintedPokemon.status = new Status(StatusEffect.FAINT); + expect(faintedPokemon.isFainted()).toBe(true); + + game.move.select(MoveId.RAZOR_LEAF); + await game.doKillOpponents(); + + await game.phaseInterceptor.to("SelectModifierPhase"); + game.onNextPrompt( + "SelectModifierPhase", + UiMode.MODIFIER_SELECT, + () => { + const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler; + // Traverse to and select first modifier + handler.setCursor(0); + handler.setRowCursor(ShopCursorTarget.REWARDS); + handler.processInput(Button.ACTION); + + // Go to fainted Pokémon and try to select it + handler.processInput(Button.RIGHT); + handler.processInput(Button.ACTION); + handler.processInput(Button.ACTION); + handler.processInput(Button.ACTION); + + expect(game.scene.getPlayerParty().length).toBe(2); + }, + () => game.isCurrentPhase("CommandPhase") || game.isCurrentPhase("NewBattlePhase"), + true, + ); + }); + + // TODO: Couldn't figure out how to select party Pokémon + it.skip("prevents fainted Pokémon from being revived", async () => { + game.override.itemRewards([{ name: "MAX_REVIVE" }]); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF, SpeciesId.WHISMUR]); + + const faintedPokemon = game.scene.getPlayerParty()[1]; + faintedPokemon.hp = 0; + faintedPokemon.status = new Status(StatusEffect.FAINT); + expect(faintedPokemon.isFainted()).toBe(true); + + game.move.select(MoveId.RAZOR_LEAF); + await game.doKillOpponents(); + + await game.phaseInterceptor.to("SelectModifierPhase"); + game.onNextPrompt( + "SelectModifierPhase", + UiMode.MODIFIER_SELECT, + () => { + const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler; + // Traverse to and select first modifier + handler.setCursor(0); + handler.setRowCursor(ShopCursorTarget.REWARDS); + handler.processInput(Button.ACTION); + + // Go to fainted Pokémon and try to select it + handler.processInput(Button.RIGHT); + handler.processInput(Button.ACTION); + handler.processInput(Button.ACTION); + handler.processInput(Button.ACTION); + + expect(faintedPokemon.isFainted()).toBe(true); + }, + () => game.isCurrentPhase("CommandPhase") || game.isCurrentPhase("NewBattlePhase"), + true, + ); + }); +}); diff --git a/test/challenges/limited-catch.test.ts b/test/challenges/limited-catch.test.ts new file mode 100644 index 00000000000..80be52df2fb --- /dev/null +++ b/test/challenges/limited-catch.test.ts @@ -0,0 +1,55 @@ +import { AbilityId } from "#enums/ability-id"; +import { Challenges } from "#enums/challenges"; +import { MoveId } from "#enums/move-id"; +import { PokeballType } from "#enums/pokeball"; +import { SpeciesId } from "#enums/species-id"; +import { GameManager } from "#test/test-utils/game-manager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Challenges - Limited Catch", () => { + 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.challengeMode.addChallenge(Challenges.LIMITED_CATCH, 1, 1); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.VOLTORB) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH) + .startingModifier([{ name: "MASTER_BALL", count: 1 }]); + }); + + it("should allow wild Pokémon to be caught on X1 waves", async () => { + game.override.startingWave(31); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF]); + + game.doThrowPokeball(PokeballType.MASTER_BALL); + await game.toEndOfTurn(); + + expect(game.scene.getPlayerParty()).toHaveLength(2); + }); + + it("should prevent Pokémon from being caught on non-X1 waves", async () => { + game.override.startingWave(53); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF]); + + game.doThrowPokeball(PokeballType.MASTER_BALL); + await game.toEndOfTurn(); + + expect(game.scene.getPlayerParty()).toHaveLength(1); + }); +}); diff --git a/test/challenges/limited-support.test.ts b/test/challenges/limited-support.test.ts new file mode 100644 index 00000000000..5c0eb2bd420 --- /dev/null +++ b/test/challenges/limited-support.test.ts @@ -0,0 +1,90 @@ +import { AbilityId } from "#enums/ability-id"; +import { Challenges } from "#enums/challenges"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import { UiMode } from "#enums/ui-mode"; +import { GameManager } from "#test/test-utils/game-manager"; +import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Challenges - Limited Support", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.VOLTORB) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH); + }); + + it('should disable the shop in "No Shop"', async () => { + game.override.startingWave(181); + game.challengeMode.addChallenge(Challenges.LIMITED_SUPPORT, 2, 1); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF]); + + game.move.use(MoveId.SPLASH); + await game.doKillOpponents(); + await game.phaseInterceptor.to("SelectModifierPhase"); + + expect(game.scene.ui.getMode()).toBe(UiMode.MODIFIER_SELECT); + const modifierSelectHandler = game.scene.ui.handlers.find( + h => h instanceof ModifierSelectUiHandler, + ) as ModifierSelectUiHandler; + expect(modifierSelectHandler.shopOptionsRows).toHaveLength(0); + }); + + it('should disable the automatic party heal in "No Heal"', async () => { + game.override.startingWave(10); + game.challengeMode.addChallenge(Challenges.LIMITED_SUPPORT, 1, 1); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF]); + + const playerPokemon = game.field.getPlayerPokemon(); + playerPokemon.hp /= 2; + + game.move.use(MoveId.SPLASH); + await game.doKillOpponents(); + await game.toNextWave(); + + expect(playerPokemon).not.toHaveFullHp(); + }); + + it('should disable both automatic party healing and shop in "Both"', async () => { + game.override.startingWave(10); + game.challengeMode.addChallenge(Challenges.LIMITED_SUPPORT, 3, 1); + await game.challengeMode.startBattle([SpeciesId.NUZLEAF]); + + const playerPokemon = game.field.getPlayerPokemon(); + playerPokemon.hp /= 2; + + game.move.use(MoveId.SPLASH); + await game.doKillOpponents(); + await game.toNextWave(); + + expect(playerPokemon).not.toHaveFullHp(); + + game.move.use(MoveId.SPLASH); + await game.doKillOpponents(); + await game.phaseInterceptor.to("SelectModifierPhase"); + + expect(game.scene.ui.getMode()).toBe(UiMode.MODIFIER_SELECT); + const modifierSelectHandler = game.scene.ui.handlers.find( + h => h instanceof ModifierSelectUiHandler, + ) as ModifierSelectUiHandler; + expect(modifierSelectHandler.shopOptionsRows).toHaveLength(0); + }); +}); From ac8ef62290c032804247915da16ddbc8ac3e619e Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Fri, 8 Aug 2025 02:49:32 +0200 Subject: [PATCH 03/18] [UI/UX] Clean up some magic numbers (#6165) * Removed unused scale parameter from InfoOverlay s * Using `globalScene.scaledCanvas.width` * Using `scaledCanvas` everywhere * Replaced some sneaky instances of `scaledCanvas / 2` * Convert comments to TSDocs * Fix typo * Caught a few more instances of `width / 6` etc * Fixed unused scale parameter --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> --- src/battle-scene.ts | 30 +++++------- .../encounters/bug-type-superfan-encounter.ts | 6 +-- .../global-trade-system-encounter.ts | 6 +-- .../encounters/weird-dream-encounter.ts | 6 +-- src/field/damage-number-handler.ts | 2 +- src/phases/egg-hatch-phase.ts | 6 +-- src/phases/end-card-phase.ts | 4 +- src/phases/evolution-phase.ts | 8 ++-- src/timed-event-manager.ts | 2 +- src/ui/abstact-option-select-ui-handler.ts | 4 +- src/ui/achv-bar.ts | 6 +-- src/ui/achvs-ui-handler.ts | 4 +- src/ui/ball-ui-handler.ts | 2 +- src/ui/base-stats-overlay.ts | 3 +- src/ui/battle-info/player-battle-info.ts | 2 +- src/ui/battle-message-ui-handler.ts | 8 ++-- src/ui/candy-bar.ts | 6 +-- src/ui/challenges-select-ui-handler.ts | 6 +-- src/ui/char-sprite.ts | 6 +-- src/ui/confirm-ui-handler.ts | 4 +- src/ui/egg-gacha-ui-handler.ts | 6 +-- src/ui/egg-hatch-scene-handler.ts | 2 +- src/ui/egg-list-ui-handler.ts | 8 ++-- src/ui/egg-summary-ui-handler.ts | 6 +-- src/ui/evolution-scene-handler.ts | 2 +- src/ui/fight-ui-handler.ts | 6 +-- src/ui/filter-text.ts | 2 +- src/ui/login-form-ui-handler.ts | 2 +- src/ui/menu-ui-handler.ts | 10 ++-- src/ui/modal-ui-handler.ts | 10 ++-- src/ui/modifier-select-ui-handler.ts | 33 ++++++------- src/ui/move-info-overlay.ts | 48 +++++++++++-------- src/ui/mystery-encounter-ui-handler.ts | 2 +- src/ui/party-exp-bar.ts | 6 +-- src/ui/party-ui-handler.ts | 10 ++-- src/ui/pokeball-tray.ts | 8 ++-- src/ui/pokedex-info-overlay.ts | 35 +++++++------- src/ui/pokedex-page-ui-handler.ts | 23 ++++----- src/ui/pokedex-ui-handler.ts | 10 ++-- src/ui/run-history-ui-handler.ts | 6 +-- src/ui/run-info-ui-handler.ts | 14 +++--- src/ui/save-slot-select-ui-handler.ts | 6 +-- src/ui/saving-icon-handler.ts | 2 +- .../settings/abstract-binding-ui-handler.ts | 12 ++--- .../abstract-control-settings-ui-handler.ts | 14 +++--- .../settings/abstract-settings-ui-handler.ts | 14 +++--- src/ui/settings/navigation-menu.ts | 2 +- src/ui/starter-select-ui-handler.ts | 6 +-- src/ui/title-ui-handler.ts | 8 ++-- src/ui/ui.ts | 16 ++----- 50 files changed, 210 insertions(+), 240 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 8c8906be2b0..ea15969ce61 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -478,8 +478,8 @@ export class BattleScene extends SceneBase { this.uiContainer = uiContainer; - const overlayWidth = this.game.canvas.width / 6; - const overlayHeight = this.game.canvas.height / 6 - 48; + const overlayWidth = this.scaledCanvas.width; + const overlayHeight = this.scaledCanvas.height - 48; this.fieldOverlay = this.add.rectangle(0, overlayHeight * -1 - 48, overlayWidth, overlayHeight, 0x424242); this.fieldOverlay.setName("rect-field-overlay"); this.fieldOverlay.setOrigin(0, 0); @@ -537,34 +537,29 @@ export class BattleScene extends SceneBase { this.candyBar.setup(); this.fieldUI.add(this.candyBar); - this.biomeWaveText = addTextObject( - this.game.canvas.width / 6 - 2, - 0, - startingWave.toString(), - TextStyle.BATTLE_INFO, - ); + this.biomeWaveText = addTextObject(this.scaledCanvas.width - 2, 0, startingWave.toString(), TextStyle.BATTLE_INFO); this.biomeWaveText.setName("text-biome-wave"); this.biomeWaveText.setOrigin(1, 0.5); this.fieldUI.add(this.biomeWaveText); - this.moneyText = addTextObject(this.game.canvas.width / 6 - 2, 0, "", TextStyle.MONEY); + this.moneyText = addTextObject(this.scaledCanvas.width - 2, 0, "", TextStyle.MONEY); this.moneyText.setName("text-money"); this.moneyText.setOrigin(1, 0.5); this.fieldUI.add(this.moneyText); - this.scoreText = addTextObject(this.game.canvas.width / 6 - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" }); + this.scoreText = addTextObject(this.scaledCanvas.width - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" }); this.scoreText.setName("text-score"); this.scoreText.setOrigin(1, 0.5); this.fieldUI.add(this.scoreText); - this.luckText = addTextObject(this.game.canvas.width / 6 - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" }); + this.luckText = addTextObject(this.scaledCanvas.width - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" }); this.luckText.setName("text-luck"); this.luckText.setOrigin(1, 0.5); this.luckText.setVisible(false); this.fieldUI.add(this.luckText); this.luckLabelText = addTextObject( - this.game.canvas.width / 6 - 2, + this.scaledCanvas.width - 2, 0, i18next.t("common:luckIndicator"), TextStyle.PARTY, @@ -586,10 +581,7 @@ export class BattleScene extends SceneBase { this.spriteSparkleHandler = new PokemonSpriteSparkleHandler(); this.spriteSparkleHandler.setup(); - this.pokemonInfoContainer = new PokemonInfoContainer( - this.game.canvas.width / 6 + 52, - -(this.game.canvas.height / 6) + 66, - ); + this.pokemonInfoContainer = new PokemonInfoContainer(this.scaledCanvas.width + 52, -this.scaledCanvas.height + 66); this.pokemonInfoContainer.setup(); this.fieldUI.add(this.pokemonInfoContainer); @@ -2054,7 +2046,7 @@ export class BattleScene extends SceneBase { } else { this.luckText.setTint(0xffef5c, 0x47ff69, 0x6b6bff, 0xff6969); } - this.luckLabelText.setX(this.game.canvas.width / 6 - 2 - (this.luckText.displayWidth + 2)); + this.luckLabelText.setX(this.scaledCanvas.width - 2 - (this.luckText.displayWidth + 2)); this.tweens.add({ targets: labels, duration: duration, @@ -2088,7 +2080,7 @@ export class BattleScene extends SceneBase { const enemyModifierCount = this.enemyModifiers.filter(m => m.isIconVisible()).length; const biomeWaveTextHeight = this.biomeWaveText.getBottomLeft().y - this.biomeWaveText.getTopLeft().y; this.biomeWaveText.setY( - -(this.game.canvas.height / 6) + + -this.scaledCanvas.height + (enemyModifierCount ? (enemyModifierCount <= 12 ? 15 : 24) : 0) + biomeWaveTextHeight / 2, ); @@ -2100,7 +2092,7 @@ export class BattleScene extends SceneBase { const offsetY = (this.scoreText.visible ? this.scoreText : this.moneyText).y + 15; this.partyExpBar.setY(offsetY); this.candyBar.setY(offsetY + 15); - this.ui?.achvBar.setY(this.game.canvas.height / 6 + offsetY); + this.ui?.achvBar.setY(this.scaledCanvas.height + offsetY); } /** 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 d60ebe690ac..d86f1511207 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -715,15 +715,13 @@ function doBugTypeMoveTutor(): Promise { const moveOptions = globalScene.currentBattle.mysteryEncounter!.misc.moveTutorOptions; await showEncounterDialogue(`${namespace}:battle_won`, `${namespace}:speaker`); - const overlayScale = 1; const moveInfoOverlay = new MoveInfoOverlay({ delayVisibility: false, - scale: overlayScale, onSide: true, right: true, x: 1, - y: -MoveInfoOverlay.getHeight(overlayScale, true) - 1, - width: globalScene.game.canvas.width / 6 - 2, + y: -MoveInfoOverlay.getHeight(true) - 1, + width: globalScene.scaledCanvas.width - 2, }); globalScene.ui.add(moveInfoOverlay); diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 347092fe0b4..083a6d06801 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -564,14 +564,14 @@ function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?: function showTradeBackground() { return new Promise(resolve => { - const tradeContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); + const tradeContainer = globalScene.add.container(0, -globalScene.scaledCanvas.height); tradeContainer.setName("Trade Background"); const flyByStaticBg = globalScene.add.rectangle( 0, 0, - globalScene.game.canvas.width / 6, - globalScene.game.canvas.height / 6, + globalScene.scaledCanvas.width, + globalScene.scaledCanvas.height, 0, ); flyByStaticBg.setName("Black Background"); diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index b4a2ba4abd5..57b066e2ba2 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -649,15 +649,15 @@ function getTransformedSpecies( } function doShowDreamBackground() { - const transformationContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); + const transformationContainer = globalScene.add.container(0, -globalScene.scaledCanvas.height); transformationContainer.name = "Dream Background"; // In case it takes a bit for video to load const transformationStaticBg = globalScene.add.rectangle( 0, 0, - globalScene.game.canvas.width / 6, - globalScene.game.canvas.height / 6, + globalScene.scaledCanvas.width, + globalScene.scaledCanvas.height, 0, ); transformationStaticBg.setName("Black Background"); diff --git a/src/field/damage-number-handler.ts b/src/field/damage-number-handler.ts index 1bbacc19566..112b08631b0 100644 --- a/src/field/damage-number-handler.ts +++ b/src/field/damage-number-handler.ts @@ -30,7 +30,7 @@ export class DamageNumberHandler { const baseScale = target.getSpriteScale() / 6; const damageNumber = addTextObject( target.x, - -(globalScene.game.canvas.height / 6) + target.y - target.getSprite().height / 2, + -globalScene.scaledCanvas.height + target.y - target.getSprite().height / 2, formatStat(amount, true), TextStyle.SUMMARY, ); diff --git a/src/phases/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts index 94923ae8c1f..e9d28e0fe2a 100644 --- a/src/phases/egg-hatch-phase.ts +++ b/src/phases/egg-hatch-phase.ts @@ -148,9 +148,9 @@ export class EggHatchPhase extends Phase { this.eggHatchOverlay = globalScene.add.rectangle( 0, - -globalScene.game.canvas.height / 6, - globalScene.game.canvas.width / 6, - globalScene.game.canvas.height / 6, + -globalScene.scaledCanvas.height, + globalScene.scaledCanvas.width, + globalScene.scaledCanvas.height, 0xffffff, ); this.eggHatchOverlay.setOrigin(0, 0); diff --git a/src/phases/end-card-phase.ts b/src/phases/end-card-phase.ts index b9b383db13d..5ce0ca47b99 100644 --- a/src/phases/end-card-phase.ts +++ b/src/phases/end-card-phase.ts @@ -25,8 +25,8 @@ export class EndCardPhase extends Phase { globalScene.field.add(this.endCard); this.text = addTextObject( - globalScene.game.canvas.width / 12, - globalScene.game.canvas.height / 6 - 16, + globalScene.scaledCanvas.width / 2, + globalScene.scaledCanvas.height - 16, i18next.t("battle:congratulations"), TextStyle.SUMMARY, { fontSize: "128px" }, diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index cad79455af3..ad3db97d520 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -90,16 +90,16 @@ export class EvolutionPhase extends Phase { .setVisible(false); this.evolutionBgOverlay = globalScene.add - .rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x262626) + .rectangle(0, 0, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height, 0x262626) .setOrigin(0) .setAlpha(0); this.evolutionContainer.add([this.evolutionBaseBg, this.evolutionBgOverlay, this.evolutionBg]); this.evolutionOverlay = globalScene.add.rectangle( 0, - -globalScene.game.canvas.height / 6, - globalScene.game.canvas.width / 6, - globalScene.game.canvas.height / 6 - 48, + -globalScene.scaledCanvas.height, + globalScene.scaledCanvas.width, + globalScene.scaledCanvas.height - 48, 0xffffff, ); this.evolutionOverlay.setOrigin(0).setAlpha(0); diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index 9877f298404..9e711735e7f 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -653,7 +653,7 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container { console.log(this.event.bannerKey); const padding = 5; const showTimer = this.event.eventType !== EventType.NO_TIMER_DISPLAY; - const yPosition = globalScene.game.canvas.height / 6 - padding - (showTimer ? 10 : 0) - (this.event.yOffset ?? 0); + const yPosition = globalScene.scaledCanvas.height - padding - (showTimer ? 10 : 0) - (this.event.yOffset ?? 0); this.banner = new Phaser.GameObjects.Image(globalScene, this.availableWidth / 2, yPosition - padding, key); this.banner.setName("img-event-banner"); this.banner.setOrigin(0.5, 1); diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index 2fb0159b6ef..b7279bc2d30 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -66,7 +66,7 @@ export abstract class AbstractOptionSelectUiHandler extends UiHandler { setup() { const ui = this.getUi(); - this.optionSelectContainer = globalScene.add.container(globalScene.game.canvas.width / 6 - 1, -48); + this.optionSelectContainer = globalScene.add.container(globalScene.scaledCanvas.width - 1, -48); this.optionSelectContainer.setName(`option-select-${this.mode ? UiMode[this.mode] : "UNKNOWN"}`); this.optionSelectContainer.setVisible(false); ui.add(this.optionSelectContainer); @@ -135,7 +135,7 @@ export abstract class AbstractOptionSelectUiHandler extends UiHandler { this.optionSelectText.setName("text-option-select"); this.optionSelectTextContainer.add(this.optionSelectText); this.optionSelectContainer.setPosition( - globalScene.game.canvas.width / 6 - 1 - (this.config?.xOffset || 0), + globalScene.scaledCanvas.width - 1 - (this.config?.xOffset || 0), -48 + (this.config?.yOffset || 0), ); this.optionSelectBg.width = Math.max(this.optionSelectText.displayWidth + 24, this.getWindowWidth()); diff --git a/src/ui/achv-bar.ts b/src/ui/achv-bar.ts index bb1ef95c9de..0f71bcbfde0 100644 --- a/src/ui/achv-bar.ts +++ b/src/ui/achv-bar.ts @@ -21,7 +21,7 @@ export class AchvBar extends Phaser.GameObjects.Container { public shown: boolean; constructor() { - super(globalScene, globalScene.game.canvas.width / 6, 0); + super(globalScene, globalScene.scaledCanvas.width, 0); this.playerGender = globalScene.gameData.gender; } @@ -118,7 +118,7 @@ export class AchvBar extends Phaser.GameObjects.Container { globalScene.tweens.add({ targets: this, - x: globalScene.game.canvas.width / 6 - this.bg.width / 2, + x: globalScene.scaledCanvas.width - this.bg.width / 2, duration: 500, ease: "Sine.easeOut", }); @@ -136,7 +136,7 @@ export class AchvBar extends Phaser.GameObjects.Container { globalScene.tweens.add({ targets: this, - x: globalScene.game.canvas.width / 6, + x: globalScene.scaledCanvas.width, duration: 500, ease: "Sine.easeIn", onComplete: () => { diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts index 01fd1d45a61..2c04e24e0f2 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -72,9 +72,9 @@ export class AchvsUiHandler extends MessageUiHandler { const ui = this.getUi(); /** Width of the global canvas / 6 */ - const WIDTH = globalScene.game.canvas.width / 6; + const WIDTH = globalScene.scaledCanvas.width; /** Height of the global canvas / 6 */ - const HEIGHT = globalScene.game.canvas.height / 6; + const HEIGHT = globalScene.scaledCanvas.height; this.mainContainer = globalScene.add.container(1, -HEIGHT + 1); diff --git a/src/ui/ball-ui-handler.ts b/src/ui/ball-ui-handler.ts index 67beb0eba84..99dabd893df 100644 --- a/src/ui/ball-ui-handler.ts +++ b/src/ui/ball-ui-handler.ts @@ -37,7 +37,7 @@ export class BallUiHandler extends UiHandler { const optionsText = addTextObject(0, 0, optionsTextContent, TextStyle.WINDOW, { align: "right", maxLines: 6 }); const optionsTextWidth = optionsText.displayWidth; this.pokeballSelectContainer = globalScene.add.container( - globalScene.game.canvas.width / 6 - 51 - Math.max(64, optionsTextWidth), + globalScene.scaledCanvas.width - 51 - Math.max(64, optionsTextWidth), -49, ); this.pokeballSelectContainer.setVisible(false); diff --git a/src/ui/base-stats-overlay.ts b/src/ui/base-stats-overlay.ts index e3ba472475a..3b432e13096 100644 --- a/src/ui/base-stats-overlay.ts +++ b/src/ui/base-stats-overlay.ts @@ -16,7 +16,6 @@ interface BaseStatsOverlaySettings { const HEIGHT = 120; const BORDER = 8; -const GLOBAL_SCALE = 6; const shortStats = ["HP", "ATK", "DEF", "SPATK", "SPDEF", "SPD"]; export class BaseStatsOverlay extends Phaser.GameObjects.Container implements InfoToggle { @@ -109,7 +108,7 @@ export class BaseStatsOverlay extends Phaser.GameObjects.Container implements In // width of this element static getWidth(_scale: number): number { - return globalScene.game.canvas.width / GLOBAL_SCALE / 2; + return globalScene.scaledCanvas.width / 2; } // height of this element diff --git a/src/ui/battle-info/player-battle-info.ts b/src/ui/battle-info/player-battle-info.ts index 62a2eddecb9..8784495e6d2 100644 --- a/src/ui/battle-info/player-battle-info.ts +++ b/src/ui/battle-info/player-battle-info.ts @@ -39,7 +39,7 @@ export class PlayerBattleInfo extends BattleInfo { statOverflow: 1, }, }; - super(Math.floor(globalScene.game.canvas.width / 6) - 10, -72, true, posParams); + super(Math.floor(globalScene.scaledCanvas.width) - 10, -72, true, posParams); this.hpNumbersContainer = globalScene.add.container(-15, 10).setName("container_hp"); diff --git a/src/ui/battle-message-ui-handler.ts b/src/ui/battle-message-ui-handler.ts index b58897b9022..2ecca06172b 100644 --- a/src/ui/battle-message-ui-handler.ts +++ b/src/ui/battle-message-ui-handler.ts @@ -94,7 +94,7 @@ export class BattleMessageUiHandler extends MessageUiHandler { this.levelUpStatsContainer = levelUpStatsContainer; - const levelUpStatsLabelsContent = addTextObject(globalScene.game.canvas.width / 6 - 73, -94, "", TextStyle.WINDOW, { + const levelUpStatsLabelsContent = addTextObject(globalScene.scaledCanvas.width - 73, -94, "", TextStyle.WINDOW, { maxLines: 6, }); let levelUpStatsLabelText = ""; @@ -106,7 +106,7 @@ export class BattleMessageUiHandler extends MessageUiHandler { levelUpStatsLabelsContent.x -= levelUpStatsLabelsContent.displayWidth; const levelUpStatsBg = addWindow( - globalScene.game.canvas.width / 6, + globalScene.scaledCanvas.width, -100, 80 + levelUpStatsLabelsContent.displayWidth, 100, @@ -117,7 +117,7 @@ export class BattleMessageUiHandler extends MessageUiHandler { levelUpStatsContainer.add(levelUpStatsLabelsContent); const levelUpStatsIncrContent = addTextObject( - globalScene.game.canvas.width / 6 - 50, + globalScene.scaledCanvas.width - 50, -94, "+\n+\n+\n+\n+\n+", TextStyle.WINDOW, @@ -128,7 +128,7 @@ export class BattleMessageUiHandler extends MessageUiHandler { this.levelUpStatsIncrContent = levelUpStatsIncrContent; const levelUpStatsValuesContent = addBBCodeTextObject( - globalScene.game.canvas.width / 6 - 7, + globalScene.scaledCanvas.width - 7, -94, "", TextStyle.WINDOW, diff --git a/src/ui/candy-bar.ts b/src/ui/candy-bar.ts index 239b963227b..b29ac3ec520 100644 --- a/src/ui/candy-bar.ts +++ b/src/ui/candy-bar.ts @@ -19,7 +19,7 @@ export class CandyBar extends Phaser.GameObjects.Container { public shown: boolean; constructor() { - super(globalScene, globalScene.game.canvas.width / 6, -(globalScene.game.canvas.height / 6) + 15); + super(globalScene, globalScene.scaledCanvas.width, -globalScene.scaledCanvas.height + 15); } setup(): void { @@ -80,7 +80,7 @@ export class CandyBar extends Phaser.GameObjects.Container { this.tween = globalScene.tweens.add({ targets: this, - x: globalScene.game.canvas.width / 6 - (this.bg.width - 5), + x: globalScene.scaledCanvas.width - (this.bg.width - 5), duration: 500, ease: "Sine.easeOut", onComplete: () => { @@ -111,7 +111,7 @@ export class CandyBar extends Phaser.GameObjects.Container { this.tween = globalScene.tweens.add({ targets: this, - x: globalScene.game.canvas.width / 6, + x: globalScene.scaledCanvas.width, duration: 500, ease: "Sine.easeIn", onComplete: () => { diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts index 4a7ab7641a3..b37ddcc6a5f 100644 --- a/src/ui/challenges-select-ui-handler.ts +++ b/src/ui/challenges-select-ui-handler.ts @@ -58,11 +58,11 @@ export class GameChallengesUiHandler extends UiHandler { this.widestTextBox = 0; - this.challengesContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); + this.challengesContainer = globalScene.add.container(1, -globalScene.scaledCanvas.height + 1); this.challengesContainer.setName("challenges"); this.challengesContainer.setInteractive( - new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), + new Phaser.Geom.Rectangle(0, 0, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height), Phaser.Geom.Rectangle.Contains, ); @@ -79,7 +79,7 @@ export class GameChallengesUiHandler extends UiHandler { this.challengesContainer.add(bgOverlay); // TODO: Change this back to /9 when adding in difficulty - const headerBg = addWindow(0, 0, globalScene.game.canvas.width / 6, 24); + const headerBg = addWindow(0, 0, globalScene.scaledCanvas.width, 24); headerBg.setName("window-header-bg"); headerBg.setOrigin(0, 0); diff --git a/src/ui/char-sprite.ts b/src/ui/char-sprite.ts index 381421086ff..1ab00291ff6 100644 --- a/src/ui/char-sprite.ts +++ b/src/ui/char-sprite.ts @@ -10,7 +10,7 @@ export class CharSprite extends Phaser.GameObjects.Container { public shown: boolean; constructor() { - super(globalScene, globalScene.game.canvas.width / 6 + 32, -42); + super(globalScene, globalScene.scaledCanvas.width + 32, -42); } setup(): void { @@ -49,7 +49,7 @@ export class CharSprite extends Phaser.GameObjects.Container { globalScene.tweens.add({ targets: this, - x: globalScene.game.canvas.width / 6 - 102, + x: globalScene.scaledCanvas.width - 102, duration: 750, ease: "Cubic.easeOut", onComplete: () => { @@ -95,7 +95,7 @@ export class CharSprite extends Phaser.GameObjects.Container { globalScene.tweens.add({ targets: this, - x: globalScene.game.canvas.width / 6 + 32, + x: globalScene.scaledCanvas.width + 32, duration: 750, ease: "Cubic.easeIn", onComplete: () => { diff --git a/src/ui/confirm-ui-handler.ts b/src/ui/confirm-ui-handler.ts index b2f35931278..aeef7fc1c91 100644 --- a/src/ui/confirm-ui-handler.ts +++ b/src/ui/confirm-ui-handler.ts @@ -69,7 +69,7 @@ export class ConfirmUiHandler extends AbstractOptionSelectUiHandler { const xOffset = args.length >= 7 && args[6] !== null ? (args[6] as number) : 0; const yOffset = args.length >= 8 && args[7] !== null ? (args[7] as number) : 0; - this.optionSelectContainer.setPosition(globalScene.game.canvas.width / 6 - 1 + xOffset, -48 + yOffset); + this.optionSelectContainer.setPosition(globalScene.scaledCanvas.width - 1 + xOffset, -48 + yOffset); this.setCursor(this.switchCheck ? this.switchCheckCursor : 0); return true; @@ -103,7 +103,7 @@ export class ConfirmUiHandler extends AbstractOptionSelectUiHandler { const xOffset = args.length >= 4 && args[3] !== null ? (args[3] as number) : 0; const yOffset = args.length >= 5 && args[4] !== null ? (args[4] as number) : 0; - this.optionSelectContainer.setPosition(globalScene.game.canvas.width / 6 - 1 + xOffset, -48 + yOffset); + this.optionSelectContainer.setPosition(globalScene.scaledCanvas.width - 1 + xOffset, -48 + yOffset); this.setCursor(this.switchCheck ? this.switchCheckCursor : 0); diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 5dcf05e2606..0287e0ee7a5 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -174,7 +174,7 @@ export class EggGachaUiHandler extends MessageUiHandler { const ui = this.getUi(); - this.eggGachaContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6).setVisible(false); + this.eggGachaContainer = globalScene.add.container(0, -globalScene.scaledCanvas.height).setVisible(false); ui.add(this.eggGachaContainer); const bg = globalScene.add.nineslice(0, 0, "default_bg", undefined, 320, 180, 0, 0, 16, 0).setOrigin(0); @@ -212,7 +212,7 @@ export class EggGachaUiHandler extends MessageUiHandler { this.eggGachaOptionSelectBg = addWindow(0, 0, eggGachaOptionSelectWidth, 16 + 576 * this.scale).setOrigin(1); this.eggGachaOptionsContainer = globalScene.add - .container(globalScene.game.canvas.width / 6, 148) + .container(globalScene.scaledCanvas.width, 148) .add(this.eggGachaOptionSelectBg); this.eggGachaContainer.add(this.eggGachaOptionsContainer); @@ -278,7 +278,7 @@ export class EggGachaUiHandler extends MessageUiHandler { this.eggGachaContainer.add(this.eggGachaOptionsContainer); for (const voucher of getEnumValues(VoucherType)) { - const container = globalScene.add.container(globalScene.game.canvas.width / 6 - 56 * voucher, 0); + const container = globalScene.add.container(globalScene.scaledCanvas.width - 56 * voucher, 0); const bg = addWindow(0, 0, 56, 22).setOrigin(1, 0); container.add(bg); diff --git a/src/ui/egg-hatch-scene-handler.ts b/src/ui/egg-hatch-scene-handler.ts index 5b2c9d40cfa..6536ef2af80 100644 --- a/src/ui/egg-hatch-scene-handler.ts +++ b/src/ui/egg-hatch-scene-handler.ts @@ -19,7 +19,7 @@ export class EggHatchSceneHandler extends UiHandler { } setup() { - this.eggHatchContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); + this.eggHatchContainer = globalScene.add.container(0, -globalScene.scaledCanvas.height); globalScene.fieldUI.add(this.eggHatchContainer); const eggLightraysAnimFrames = globalScene.anims.generateFrameNames("egg_lightrays", { start: 0, end: 3 }); diff --git a/src/ui/egg-list-ui-handler.ts b/src/ui/egg-list-ui-handler.ts index 42f969b9d38..2516903f631 100644 --- a/src/ui/egg-list-ui-handler.ts +++ b/src/ui/egg-list-ui-handler.ts @@ -36,11 +36,11 @@ export class EggListUiHandler extends MessageUiHandler { setup() { const ui = this.getUi(); - this.eggListContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6).setVisible(false); + this.eggListContainer = globalScene.add.container(0, -globalScene.scaledCanvas.height).setVisible(false); ui.add(this.eggListContainer); const bgColor = globalScene.add - .rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x006860) + .rectangle(0, 0, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height, 0x006860) .setOrigin(0); const eggListBg = globalScene.add.image(0, 0, "egg_list_bg").setOrigin(0); @@ -69,9 +69,7 @@ export class EggListUiHandler extends MessageUiHandler { .withUpdateGridCallBack(() => this.updateEggIcons()) .withUpdateSingleElementCallback((i: number) => this.setEggDetails(i)); - this.eggListMessageBoxContainer = globalScene.add - .container(0, globalScene.game.canvas.height / 6) - .setVisible(false); + this.eggListMessageBoxContainer = globalScene.add.container(0, globalScene.scaledCanvas.height).setVisible(false); const eggListMessageBox = addWindow(1, -1, 318, 28).setOrigin(0, 1); this.eggListMessageBoxContainer.add(eggListMessageBox); diff --git a/src/ui/egg-summary-ui-handler.ts b/src/ui/egg-summary-ui-handler.ts index aa4e8974318..c66075dd910 100644 --- a/src/ui/egg-summary-ui-handler.ts +++ b/src/ui/egg-summary-ui-handler.ts @@ -59,11 +59,11 @@ export class EggSummaryUiHandler extends MessageUiHandler { setup() { const ui = this.getUi(); - this.summaryContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); + this.summaryContainer = globalScene.add.container(0, -globalScene.scaledCanvas.height); this.summaryContainer.setVisible(false); ui.add(this.summaryContainer); - this.eggHatchContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); + this.eggHatchContainer = globalScene.add.container(0, -globalScene.scaledCanvas.height); this.eggHatchContainer.setVisible(false); ui.add(this.eggHatchContainer); @@ -92,7 +92,7 @@ export class EggSummaryUiHandler extends MessageUiHandler { iconContainerX + numCols * iconSize, iconContainerY + 3, 4, - globalScene.game.canvas.height / 6 - 20, + globalScene.scaledCanvas.height - 20, numRows, ); this.summaryContainer.add(scrollBar); diff --git a/src/ui/evolution-scene-handler.ts b/src/ui/evolution-scene-handler.ts index c22cf31faaa..0333594dc36 100644 --- a/src/ui/evolution-scene-handler.ts +++ b/src/ui/evolution-scene-handler.ts @@ -22,7 +22,7 @@ export class EvolutionSceneHandler extends MessageUiHandler { const ui = this.getUi(); - this.evolutionContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); + this.evolutionContainer = globalScene.add.container(0, -globalScene.scaledCanvas.height); const messageBg = globalScene.add.sprite(0, 0, "bg", globalScene.windowType).setOrigin(0, 1).setVisible(false); diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 42f8cba5df4..1d856079939 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -105,15 +105,13 @@ export class FightUiHandler extends UiHandler implements InfoToggle { ]); // prepare move overlay - const overlayScale = 1; this.moveInfoOverlay = new MoveInfoOverlay({ delayVisibility: true, - scale: overlayScale, onSide: true, right: true, x: 0, - y: -MoveInfoOverlay.getHeight(overlayScale, true), - width: globalScene.game.canvas.width / 6 + 4, + y: -MoveInfoOverlay.getHeight(true), + width: globalScene.scaledCanvas.width + 4, hideEffectBox: true, hideBg: true, }); diff --git a/src/ui/filter-text.ts b/src/ui/filter-text.ts index ff7119dd778..d5809292c25 100644 --- a/src/ui/filter-text.ts +++ b/src/ui/filter-text.ts @@ -62,7 +62,7 @@ export class FilterText extends Phaser.GameObjects.Container { this.dialogueMessageBox = addWindow( -this.textPadding, 0, - globalScene.game.canvas.width / 6 + this.textPadding * 2, + globalScene.scaledCanvas.width + this.textPadding * 2, 49, false, false, diff --git a/src/ui/login-form-ui-handler.ts b/src/ui/login-form-ui-handler.ts index 524eaeece86..67b2c81b94c 100644 --- a/src/ui/login-form-ui-handler.ts +++ b/src/ui/login-form-ui-handler.ts @@ -49,7 +49,7 @@ export class LoginFormUiHandler extends FormModalUiHandler { private buildExternalPartyContainer() { this.externalPartyContainer = globalScene.add.container(0, 0); this.externalPartyContainer.setInteractive( - new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 12, globalScene.game.canvas.height / 12), + new Phaser.Geom.Rectangle(0, 0, globalScene.scaledCanvas.width / 2, globalScene.scaledCanvas.height / 2), Phaser.Geom.Rectangle.Contains, ); this.externalPartyTitle = addTextObject(0, 4, "", TextStyle.SETTINGS_LABEL); diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index 30bf9eeff75..e419997456b 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -96,10 +96,10 @@ export class MenuUiHandler extends MessageUiHandler { ui.bgmBar = this.bgmBar; - this.menuContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); + this.menuContainer = globalScene.add.container(1, -globalScene.scaledCanvas.height + 1); this.menuContainer.setName("menu"); this.menuContainer.setInteractive( - new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), + new Phaser.Geom.Rectangle(0, 0, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height), Phaser.Geom.Rectangle.Contains, ); @@ -146,10 +146,10 @@ export class MenuUiHandler extends MessageUiHandler { this.scale = getTextStyleOptions(TextStyle.WINDOW, globalScene.uiTheme).scale; this.menuBg = addWindow( - globalScene.game.canvas.width / 6 - (this.optionSelectText.displayWidth + 25), + globalScene.scaledCanvas.width - (this.optionSelectText.displayWidth + 25), 0, this.optionSelectText.displayWidth + 19 + 24 * this.scale, - globalScene.game.canvas.height / 6 - 2, + globalScene.scaledCanvas.height - 2, ); this.menuBg.setOrigin(0, 0); @@ -174,7 +174,7 @@ export class MenuUiHandler extends MessageUiHandler { this.dialogueMessageBox = addWindow( -this.textPadding, 0, - globalScene.game.canvas.width / 6 + this.textPadding * 2, + globalScene.scaledCanvas.width + this.textPadding * 2, 49, false, false, diff --git a/src/ui/modal-ui-handler.ts b/src/ui/modal-ui-handler.ts index 4d1456eec51..51a6a21a29c 100644 --- a/src/ui/modal-ui-handler.ts +++ b/src/ui/modal-ui-handler.ts @@ -46,7 +46,7 @@ export abstract class ModalUiHandler extends UiHandler { this.modalContainer = globalScene.add.container(0, 0); this.modalContainer.setInteractive( - new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), + new Phaser.Geom.Rectangle(0, 0, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height), Phaser.Geom.Rectangle.Contains, ); @@ -105,8 +105,8 @@ export abstract class ModalUiHandler extends UiHandler { const overlay = globalScene.add.rectangle( (this.getWidth() + marginLeft + marginRight) / 2, (this.getHeight() + marginTop + marginBottom) / 2, - globalScene.game.canvas.width / 6, - globalScene.game.canvas.height / 6, + globalScene.scaledCanvas.width, + globalScene.scaledCanvas.height, 0, ); overlay.setOrigin(0.5, 0.5); @@ -159,8 +159,8 @@ export abstract class ModalUiHandler extends UiHandler { const width = this.getWidth(config); const height = this.getHeight(config); this.modalContainer.setPosition( - (globalScene.game.canvas.width / 6 - (width + (marginRight - marginLeft))) / 2, - (-globalScene.game.canvas.height / 6 - (height + (marginBottom - marginTop))) / 2, + (globalScene.scaledCanvas.width - (width + (marginRight - marginLeft))) / 2, + (-globalScene.scaledCanvas.height - (height + (marginBottom - marginTop))) / 2, ); this.modalBg.setSize(width, height); diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index d90b3310fb0..6edd7d5d70d 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -86,10 +86,7 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler { transferButtonText.setOrigin(1, 0); this.transferButtonContainer.add(transferButtonText); - this.checkButtonContainer = globalScene.add.container( - globalScene.game.canvas.width / 6 - 1, - OPTION_BUTTON_YPOSITION, - ); + this.checkButtonContainer = globalScene.add.container(globalScene.scaledCanvas.width - 1, OPTION_BUTTON_YPOSITION); this.checkButtonContainer.setName("use-btn"); this.checkButtonContainer.setVisible(false); ui.add(this.checkButtonContainer); @@ -129,8 +126,8 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler { this.lockRarityButtonContainer.add(this.lockRarityButtonText); this.continueButtonContainer = globalScene.add.container( - globalScene.game.canvas.width / 12, - -(globalScene.game.canvas.height / 12), + globalScene.scaledCanvas.width / 2, + -(globalScene.scaledCanvas.height / 2), ); this.continueButtonContainer.setVisible(false); ui.add(this.continueButtonContainer); @@ -146,15 +143,13 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler { this.continueButtonContainer.add(continueButtonText); // prepare move overlay - const overlayScale = 1; this.moveInfoOverlay = new MoveInfoOverlay({ delayVisibility: true, - scale: overlayScale, onSide: true, right: true, x: 1, - y: -MoveInfoOverlay.getHeight(overlayScale, true) - 1, - width: globalScene.game.canvas.width / 6 - 2, + y: -MoveInfoOverlay.getHeight(true) - 1, + width: globalScene.scaledCanvas.width - 2, }); ui.add(this.moveInfoOverlay); // register the overlay to receive toggle events @@ -219,10 +214,10 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler { shopTypeOptions.length > SHOP_OPTIONS_ROW_LIMIT ? -SINGLE_SHOP_ROW_YOFFSET : -DOUBLE_SHOP_ROW_YOFFSET; for (let m = 0; m < typeOptions.length; m++) { - const sliceWidth = globalScene.game.canvas.width / 6 / (typeOptions.length + 2); + const sliceWidth = globalScene.scaledCanvas.width / (typeOptions.length + 2); const option = new ModifierOption( sliceWidth * (m + 1) + sliceWidth * 0.5, - -globalScene.game.canvas.height / 12 + optionsYOffset, + -globalScene.scaledCanvas.height / 2 + optionsYOffset, typeOptions[m], ); option.setScale(0.5); @@ -243,10 +238,10 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler { row ? SHOP_OPTIONS_ROW_LIMIT : 0, row ? undefined : SHOP_OPTIONS_ROW_LIMIT, ); - const sliceWidth = globalScene.game.canvas.width / 6 / (rowOptions.length + 2); + const sliceWidth = globalScene.scaledCanvas.width / (rowOptions.length + 2); const option = new ModifierOption( sliceWidth * (col + 1) + sliceWidth * 0.5, - -globalScene.game.canvas.height / 12 - globalScene.game.canvas.height / 32 - (42 - (28 * row - 1)), + -globalScene.scaledCanvas.height / 2 - globalScene.game.canvas.height / 32 - (42 - (28 * row - 1)), shopTypeOptions[m], ); option.setScale(0.375); @@ -558,27 +553,27 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler { // Continue button when no shop items this.cursorObj.setScale(1.25); this.cursorObj.setPosition( - globalScene.game.canvas.width / 18 + 23, - -globalScene.game.canvas.height / 12 - + globalScene.scaledCanvas.width / 3 + 23, + -globalScene.scaledCanvas.height / 2 - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2), ); ui.showText(i18next.t("modifierSelectUiHandler:continueNextWaveDescription")); return ret; } - const sliceWidth = globalScene.game.canvas.width / 6 / (options.length + 2); + const sliceWidth = globalScene.scaledCanvas.width / (options.length + 2); if (this.rowCursor < 2) { // Cursor on free items this.cursorObj.setPosition( sliceWidth * (cursor + 1) + sliceWidth * 0.5 - 20, - -globalScene.game.canvas.height / 12 - + -globalScene.scaledCanvas.height / 2 - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2), ); } else { // Cursor on paying items this.cursorObj.setPosition( sliceWidth * (cursor + 1) + sliceWidth * 0.5 - 16, - -globalScene.game.canvas.height / 12 - + -globalScene.scaledCanvas.height / 2 - globalScene.game.canvas.height / 32 - (-14 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1))), ); diff --git a/src/ui/move-info-overlay.ts b/src/ui/move-info-overlay.ts index f8632eb244e..f98630260db 100644 --- a/src/ui/move-info-overlay.ts +++ b/src/ui/move-info-overlay.ts @@ -10,17 +10,24 @@ import { fixedInt, getLocalizedSpriteKey } from "#utils/common"; import i18next from "i18next"; export interface MoveInfoOverlaySettings { - delayVisibility?: boolean; // if true, showing the overlay will only set it to active and populate the fields and the handler using this field has to manually call setVisible later. - scale?: number; // scale the box? A scale of 0.5 is recommended - top?: boolean; // should the effect box be on top? - right?: boolean; // should the effect box be on the right? - onSide?: boolean; // should the effect be on the side? ignores top argument if true - //location and width of the component; unaffected by scaling + /** + * If true, showing the overlay will only set it to active and populate the fields + * and the handler using this field has to manually call `setVisible` later. + */ + delayVisibility?: boolean; + /** Whether the effect box should be on top */ + top?: boolean; + /** Whether the effect box should be on the right */ + right?: boolean; + /** Whether the effect box should be on the side. Overrides the `top` param if `true`. */ + onSide?: boolean; + /** `x` position of the component, unaffected by scaling */ x?: number; + /** `y` position of the component, unaffected by scaling */ y?: number; - /** Default is always half the screen, regardless of scale */ + /** Width of the component, unaffected by scaling. Defaults to half the screen width. */ width?: number; - /** Determines whether to display the small secondary box */ + /** Whether to display the small secondary box */ hideEffectBox?: boolean; hideBg?: boolean; } @@ -54,12 +61,11 @@ export class MoveInfoOverlay extends Phaser.GameObjects.Container implements Inf options.top = false; } super(globalScene, options?.x, options?.y); - const scale = options?.scale || 1; // set up the scale - this.setScale(scale); + this.setScale(1); this.options = options || {}; // prepare the description box - const width = (options?.width || MoveInfoOverlay.getWidth(scale)) / scale; // divide by scale as we always want this to be half a window wide + const width = options?.width || MoveInfoOverlay.getWidth(); // we always want this to be half a window wide this.descBg = addWindow( options?.onSide && !options?.right ? EFF_WIDTH : 0, options?.top ? EFF_HEIGHT : 0, @@ -88,19 +94,19 @@ export class MoveInfoOverlay extends Phaser.GameObjects.Container implements Inf y: options?.y || 0, }; if (maskPointOrigin.x < 0) { - maskPointOrigin.x += globalScene.game.canvas.width / GLOBAL_SCALE; + maskPointOrigin.x += globalScene.scaledCanvas.width; } if (maskPointOrigin.y < 0) { - maskPointOrigin.y += globalScene.game.canvas.height / GLOBAL_SCALE; + maskPointOrigin.y += globalScene.scaledCanvas.height; } const moveDescriptionTextMaskRect = globalScene.make.graphics(); moveDescriptionTextMaskRect.fillStyle(0xff0000); moveDescriptionTextMaskRect.fillRect( - maskPointOrigin.x + ((options?.onSide && !options?.right ? EFF_WIDTH : 0) + BORDER) * scale, - maskPointOrigin.y + ((options?.top ? EFF_HEIGHT : 0) + BORDER - 2) * scale, - width - ((options?.onSide ? EFF_WIDTH : 0) - BORDER * 2) * scale, - (DESC_HEIGHT - (BORDER - 2) * 2) * scale, + maskPointOrigin.x + ((options?.onSide && !options?.right ? EFF_WIDTH : 0) + BORDER), + maskPointOrigin.y + ((options?.top ? EFF_HEIGHT : 0) + BORDER - 2), + width - ((options?.onSide ? EFF_WIDTH : 0) - BORDER * 2), + DESC_HEIGHT - (BORDER - 2) * 2, ); moveDescriptionTextMaskRect.setScale(6); const moveDescriptionTextMask = this.createGeometryMask(moveDescriptionTextMaskRect); @@ -233,12 +239,12 @@ export class MoveInfoOverlay extends Phaser.GameObjects.Container implements Inf } // width of this element - static getWidth(_scale: number): number { - return globalScene.game.canvas.width / GLOBAL_SCALE / 2; + static getWidth(): number { + return globalScene.scaledCanvas.width / 2; } // height of this element - static getHeight(scale: number, onSide?: boolean): number { - return (onSide ? Math.max(EFF_HEIGHT, DESC_HEIGHT) : EFF_HEIGHT + DESC_HEIGHT) * scale; + static getHeight(onSide?: boolean): number { + return onSide ? Math.max(EFF_HEIGHT, DESC_HEIGHT) : EFF_HEIGHT + DESC_HEIGHT; } } diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index b6bc464855c..881c375fa8a 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -471,7 +471,7 @@ export class MysteryEncounterUiHandler extends UiHandler { // View Party Button const viewPartyText = addBBCodeTextObject( - globalScene.game.canvas.width / 6, + globalScene.scaledCanvas.width, -24, getBBCodeFrag(i18next.t("mysteryEncounterMessages:view_party_button"), TextStyle.PARTY), TextStyle.PARTY, diff --git a/src/ui/party-exp-bar.ts b/src/ui/party-exp-bar.ts index 952a1f8227a..c9567ceb042 100644 --- a/src/ui/party-exp-bar.ts +++ b/src/ui/party-exp-bar.ts @@ -14,7 +14,7 @@ export class PartyExpBar extends Phaser.GameObjects.Container { public shown: boolean; constructor() { - super(globalScene, globalScene.game.canvas.width / 6, -(globalScene.game.canvas.height / 6) + 15); + super(globalScene, globalScene.scaledCanvas.width, -globalScene.scaledCanvas.height + 15); } setup(): void { @@ -66,7 +66,7 @@ export class PartyExpBar extends Phaser.GameObjects.Container { this.tween = globalScene.tweens.add({ targets: this, - x: globalScene.game.canvas.width / 6 - (this.bg.width - 5), + x: globalScene.scaledCanvas.width - (this.bg.width - 5), duration: 500 / Math.pow(2, globalScene.expGainsSpeed), ease: "Sine.easeOut", onComplete: () => { @@ -92,7 +92,7 @@ export class PartyExpBar extends Phaser.GameObjects.Container { this.tween = globalScene.tweens.add({ targets: this, - x: globalScene.game.canvas.width / 6, + x: globalScene.scaledCanvas.width, duration: 500, ease: "Sine.easeIn", onComplete: () => { diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index c50065e5812..ff5e7246a6f 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -311,7 +311,7 @@ export class PartyUiHandler extends MessageUiHandler { this.partyCancelButton = partyCancelButton; - this.optionsContainer = globalScene.add.container(globalScene.game.canvas.width / 6 - 1, -1); + this.optionsContainer = globalScene.add.container(globalScene.scaledCanvas.width - 1, -1); partyContainer.add(this.optionsContainer); this.iconAnimHandler = new PokemonIconAnimHandler(); @@ -323,14 +323,12 @@ export class PartyUiHandler extends MessageUiHandler { this.partyDiscardModeButton = partyDiscardModeButton; - // prepare move overlay. in case it appears to be too big, set the overlayScale to .5 - const overlayScale = 1; + // prepare move overlay this.moveInfoOverlay = new MoveInfoOverlay({ - scale: overlayScale, top: true, x: 1, - y: -MoveInfoOverlay.getHeight(overlayScale) - 1, - width: globalScene.game.canvas.width / 12 - 30, + y: -MoveInfoOverlay.getHeight() - 1, + width: globalScene.scaledCanvas.width / 2 - 30, }); ui.add(this.moveInfoOverlay); diff --git a/src/ui/pokeball-tray.ts b/src/ui/pokeball-tray.ts index 9720aa42090..b1522af0e27 100644 --- a/src/ui/pokeball-tray.ts +++ b/src/ui/pokeball-tray.ts @@ -10,7 +10,7 @@ export class PokeballTray extends Phaser.GameObjects.Container { public shown: boolean; constructor(player: boolean) { - super(globalScene, player ? globalScene.game.canvas.width / 6 : 0, player ? -72 : -144); + super(globalScene, player ? globalScene.scaledCanvas.width : 0, player ? -72 : -144); this.player = player; } @@ -36,7 +36,7 @@ export class PokeballTray extends Phaser.GameObjects.Container { .map((_, i) => globalScene.add.sprite( (this.player ? -83 : 76) + - (globalScene.game.canvas.width / 6) * (this.player ? -1 : 1) + + globalScene.scaledCanvas.width * (this.player ? -1 : 1) + 10 * i * (this.player ? 1 : -1), -8, "pb_tray_ball", @@ -67,7 +67,7 @@ export class PokeballTray extends Phaser.GameObjects.Container { this.bg.alpha = 1; this.balls.forEach((ball, b) => { - ball.x += (globalScene.game.canvas.width / 6 + 104) * (this.player ? 1 : -1); + ball.x += (globalScene.scaledCanvas.width + 104) * (this.player ? 1 : -1); let ballFrame = "ball"; if (b >= party.length) { ballFrame = "empty"; @@ -115,7 +115,7 @@ export class PokeballTray extends Phaser.GameObjects.Container { this.balls.forEach((ball, b) => { globalScene.tweens.add({ targets: ball, - x: `${this.player ? "-" : "+"}=${globalScene.game.canvas.width / 6}`, + x: `${this.player ? "-" : "+"}=${globalScene.scaledCanvas.width}`, duration: 250, delay: b * 100, ease: "Sine.easeIn", diff --git a/src/ui/pokedex-info-overlay.ts b/src/ui/pokedex-info-overlay.ts index 0f2f5fa3dde..9c5876318ec 100644 --- a/src/ui/pokedex-info-overlay.ts +++ b/src/ui/pokedex-info-overlay.ts @@ -7,7 +7,6 @@ import { fixedInt } from "#utils/common"; export interface PokedexInfoOverlaySettings { delayVisibility?: boolean; // if true, showing the overlay will only set it to active and populate the fields and the handler using this field has to manually call setVisible later. - scale?: number; // scale the box? A scale of 0.5 is recommended //location and width of the component; unaffected by scaling x?: number; y?: number; @@ -36,17 +35,15 @@ export class PokedexInfoOverlay extends Phaser.GameObjects.Container implements private maskPointOriginX: number; private maskPointOriginY: number; - public scale: number; public width: number; constructor(options?: PokedexInfoOverlaySettings) { super(globalScene, options?.x, options?.y); - this.scale = options?.scale || 1; // set up the scale - this.setScale(this.scale); + this.setScale(1); this.options = options || {}; // prepare the description box - this.width = (options?.width || PokedexInfoOverlay.getWidth(this.scale)) / this.scale; // divide by scale as we always want this to be half a window wide + this.width = options?.width || PokedexInfoOverlay.getWidth(); // we always want this to be half a window wide this.descBg = addWindow(0, 0, this.width, DESC_HEIGHT); this.descBg.setOrigin(0, 0); this.add(this.descBg); @@ -61,19 +58,19 @@ export class PokedexInfoOverlay extends Phaser.GameObjects.Container implements this.maskPointOriginY = options?.y || 0; if (this.maskPointOriginX < 0) { - this.maskPointOriginX += globalScene.game.canvas.width / GLOBAL_SCALE; + this.maskPointOriginX += globalScene.scaledCanvas.width; } if (this.maskPointOriginY < 0) { - this.maskPointOriginY += globalScene.game.canvas.height / GLOBAL_SCALE; + this.maskPointOriginY += globalScene.scaledCanvas.height; } this.textMaskRect = globalScene.make.graphics(); this.textMaskRect.fillStyle(0xff0000); this.textMaskRect.fillRect( - this.maskPointOriginX + BORDER * this.scale, - this.maskPointOriginY + (BORDER - 2) * this.scale, - this.width - BORDER * 2 * this.scale, - (DESC_HEIGHT - (BORDER - 2) * 2) * this.scale, + this.maskPointOriginX + BORDER, + this.maskPointOriginY + (BORDER - 2), + this.width - BORDER * 2, + DESC_HEIGHT - (BORDER - 2) * 2, ); this.textMaskRect.setScale(6); const textMask = this.createGeometryMask(this.textMaskRect); @@ -111,10 +108,10 @@ export class PokedexInfoOverlay extends Phaser.GameObjects.Container implements this.textMaskRect.clear(); this.textMaskRect.fillStyle(0xff0000); this.textMaskRect.fillRect( - this.maskPointOriginX + BORDER * this.scale, - this.maskPointOriginY + (BORDER - 2) * this.scale + (48 - newHeight), - this.width - BORDER * 2 * this.scale, - (newHeight - (BORDER - 2) * 2) * this.scale, + this.maskPointOriginX + BORDER, + this.maskPointOriginY + (BORDER - 2) + (48 - newHeight), + this.width - BORDER * 2, + newHeight - (BORDER - 2) * 2, ); const updatedMask = this.createGeometryMask(this.textMaskRect); this.desc.setMask(updatedMask); @@ -167,12 +164,12 @@ export class PokedexInfoOverlay extends Phaser.GameObjects.Container implements } // width of this element - static getWidth(_scale: number): number { - return globalScene.game.canvas.width / GLOBAL_SCALE / 2; + static getWidth(): number { + return globalScene.scaledCanvas.width / 2; } // height of this element - static getHeight(scale: number, _onSide?: boolean): number { - return DESC_HEIGHT * scale; + static getHeight(): number { + return DESC_HEIGHT; } } diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index 227b86c4d4d..fbfb6b615a4 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -299,15 +299,15 @@ export class PokedexPageUiHandler extends MessageUiHandler { const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)) ?? "en"; const textSettings = languageSettings[langSettingKey]; - this.starterSelectContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); + this.starterSelectContainer = globalScene.add.container(0, -globalScene.scaledCanvas.height); this.starterSelectContainer.setVisible(false); ui.add(this.starterSelectContainer); const bgColor = globalScene.add.rectangle( 0, 0, - globalScene.game.canvas.width / 6, - globalScene.game.canvas.height / 6, + globalScene.scaledCanvas.width, + globalScene.scaledCanvas.height, 0x006860, ); bgColor.setOrigin(0, 0); @@ -602,7 +602,7 @@ export class PokedexPageUiHandler extends MessageUiHandler { this.filterInstructionsContainer.setVisible(true); this.starterSelectContainer.add(this.filterInstructionsContainer); - this.starterSelectMessageBoxContainer = globalScene.add.container(0, globalScene.game.canvas.height / 6); + this.starterSelectMessageBoxContainer = globalScene.add.container(0, globalScene.scaledCanvas.height); this.starterSelectMessageBoxContainer.setVisible(false); this.starterSelectContainer.add(this.starterSelectMessageBoxContainer); @@ -629,7 +629,7 @@ export class PokedexPageUiHandler extends MessageUiHandler { this.menuContainer = globalScene.add.container(-130, 0); this.menuContainer.setName("menu"); this.menuContainer.setInteractive( - new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), + new Phaser.Geom.Rectangle(0, 0, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height), Phaser.Geom.Rectangle.Contains, ); @@ -659,10 +659,10 @@ export class PokedexPageUiHandler extends MessageUiHandler { this.scale = getTextStyleOptions(TextStyle.WINDOW, globalScene.uiTheme).scale; this.menuBg = addWindow( - globalScene.game.canvas.width / 6 - 83, + globalScene.scaledCanvas.width - 83, 0, this.optionSelectText.displayWidth + 19 + 24 * this.scale, - globalScene.game.canvas.height / 6 - 2, + globalScene.scaledCanvas.height - 2, ); this.menuBg.setOrigin(0, 0); @@ -682,19 +682,16 @@ export class PokedexPageUiHandler extends MessageUiHandler { this.menuContainer.bringToTop(this.baseStatsOverlay); // add the info overlay last to be the top most ui element and prevent the IVs from overlaying this - const overlayScale = 1; this.moveInfoOverlay = new MoveInfoOverlay({ - scale: overlayScale, top: true, x: 1, - y: globalScene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29, + y: globalScene.scaledCanvas.height - MoveInfoOverlay.getHeight() - 29, }); this.starterSelectContainer.add(this.moveInfoOverlay); this.infoOverlay = new PokedexInfoOverlay({ - scale: overlayScale, x: 1, - y: globalScene.game.canvas.height / 6 - PokedexInfoOverlay.getHeight(overlayScale) - 29, + y: globalScene.scaledCanvas.height - PokedexInfoOverlay.getHeight() - 29, }); this.starterSelectContainer.add(this.infoOverlay); @@ -1103,7 +1100,7 @@ export class PokedexPageUiHandler extends MessageUiHandler { this.starterSelectMessageBoxContainer.setY(0); this.message.setY(4); } else { - this.starterSelectMessageBoxContainer.setY(globalScene.game.canvas.height / 6); + this.starterSelectMessageBoxContainer.setY(globalScene.scaledCanvas.height); this.starterSelectMessageBox.setOrigin(0, 1); this.message.setY(singleLine ? -22 : -37); } diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index cd1dc312f4d..75a3e5162e3 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -245,15 +245,15 @@ export class PokedexUiHandler extends MessageUiHandler { const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)) ?? "en"; const textSettings = languageSettings[langSettingKey]; - this.starterSelectContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); + this.starterSelectContainer = globalScene.add.container(0, -globalScene.scaledCanvas.height); this.starterSelectContainer.setVisible(false); ui.add(this.starterSelectContainer); const bgColor = globalScene.add.rectangle( 0, 0, - globalScene.game.canvas.width / 6, - globalScene.game.canvas.height / 6, + globalScene.scaledCanvas.width, + globalScene.scaledCanvas.height, 0x006860, ); bgColor.setOrigin(0, 0); @@ -544,7 +544,7 @@ export class PokedexUiHandler extends MessageUiHandler { this.type2Icon.setOrigin(0, 0); this.starterSelectContainer.add(this.type2Icon); - this.starterSelectMessageBoxContainer = globalScene.add.container(0, globalScene.game.canvas.height / 6); + this.starterSelectMessageBoxContainer = globalScene.add.container(0, globalScene.scaledCanvas.height); this.starterSelectMessageBoxContainer.setVisible(false); this.starterSelectContainer.add(this.starterSelectMessageBoxContainer); @@ -784,7 +784,7 @@ export class PokedexUiHandler extends MessageUiHandler { this.starterSelectMessageBoxContainer.setY(0); this.message.setY(4); } else { - this.starterSelectMessageBoxContainer.setY(globalScene.game.canvas.height / 6); + this.starterSelectMessageBoxContainer.setY(globalScene.scaledCanvas.height); this.starterSelectMessageBox.setOrigin(0, 1); this.message.setY(singleLine ? -22 : -37); } diff --git a/src/ui/run-history-ui-handler.ts b/src/ui/run-history-ui-handler.ts index 00aa47ae65d..457c48654a3 100644 --- a/src/ui/run-history-ui-handler.ts +++ b/src/ui/run-history-ui-handler.ts @@ -54,14 +54,14 @@ export class RunHistoryUiHandler extends MessageUiHandler { const loadSessionBg = globalScene.add.rectangle( 0, 0, - globalScene.game.canvas.width / 6, - -globalScene.game.canvas.height / 6, + globalScene.scaledCanvas.width, + -globalScene.scaledCanvas.height, 0x006860, ); loadSessionBg.setOrigin(0, 0); this.runSelectContainer.add(loadSessionBg); - this.runContainerInitialY = -globalScene.game.canvas.height / 6 + 8; + this.runContainerInitialY = -globalScene.scaledCanvas.height + 8; this.runsContainer = globalScene.add.container(8, this.runContainerInitialY); this.runSelectContainer.add(this.runsContainer); diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 465e48a45ad..667a22a001a 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -74,7 +74,7 @@ export class RunInfoUiHandler extends UiHandler { } override async setup() { - this.runContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); + this.runContainer = globalScene.add.container(1, -globalScene.scaledCanvas.height + 1); // The import of the modifiersModule is loaded here to sidestep async/await issues. this.modifiersModule = Modifier; this.runContainer.setVisible(false); @@ -120,7 +120,7 @@ export class RunInfoUiHandler extends UiHandler { // Creates Header and adds to this.runContainer this.addHeader(); - this.statsBgWidth = (globalScene.game.canvas.width / 6 - 2) / 3; + this.statsBgWidth = (globalScene.scaledCanvas.width - 2) / 3; // Creates Run Result Container this.runResultContainer = globalScene.add.container(0, 24); @@ -147,7 +147,7 @@ export class RunInfoUiHandler extends UiHandler { this.showParty(true); this.runContainer.setInteractive( - new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), + new Phaser.Geom.Rectangle(0, 0, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height), Phaser.Geom.Rectangle.Contains, ); this.getUi().bringToTop(this.runContainer); @@ -174,7 +174,7 @@ export class RunInfoUiHandler extends UiHandler { * It does not check if the run has any PokemonHeldItemModifiers though. */ private addHeader() { - const headerBg = addWindow(0, 0, globalScene.game.canvas.width / 6 - 2, 24); + const headerBg = addWindow(0, 0, globalScene.scaledCanvas.width - 2, 24); headerBg.setOrigin(0, 0); this.runContainer.add(headerBg); if (this.runInfo.modifiers.length !== 0) { @@ -723,7 +723,7 @@ export class RunInfoUiHandler extends UiHandler { private parsePartyInfo(): void { const party = this.runInfo.party; const currentLanguage = i18next.resolvedLanguage ?? "en"; - const windowHeight = (globalScene.game.canvas.height / 6 - 23) / 6; + const windowHeight = (globalScene.scaledCanvas.height - 23) / 6; party.forEach((p: PokemonData, i: number) => { const pokemonInfoWindow = new RoundRectangle(globalScene, 0, 14, this.statsBgWidth * 2 + 10, windowHeight - 2, 3); @@ -971,8 +971,8 @@ export class RunInfoUiHandler extends UiHandler { endCard.setOrigin(0); endCard.setScale(0.5); const text = addTextObject( - globalScene.game.canvas.width / 12, - globalScene.game.canvas.height / 6 - 16, + globalScene.scaledCanvas.width / 2, + globalScene.scaledCanvas.height - 16, i18next.t("battle:congratulations"), TextStyle.SUMMARY, { fontSize: "128px" }, diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 9da34e672f1..5b1566b4913 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -54,14 +54,14 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { const loadSessionBg = globalScene.add.rectangle( 0, 0, - globalScene.game.canvas.width / 6, - -globalScene.game.canvas.height / 6, + globalScene.scaledCanvas.width, + -globalScene.scaledCanvas.height, 0x006860, ); loadSessionBg.setOrigin(0, 0); this.saveSlotSelectContainer.add(loadSessionBg); - this.sessionSlotsContainerInitialY = -globalScene.game.canvas.height / 6 + 8; + this.sessionSlotsContainerInitialY = -globalScene.scaledCanvas.height + 8; this.sessionSlotsContainer = globalScene.add.container(8, this.sessionSlotsContainerInitialY); this.saveSlotSelectContainer.add(this.sessionSlotsContainer); diff --git a/src/ui/saving-icon-handler.ts b/src/ui/saving-icon-handler.ts index 6923017218f..00c8b8b526c 100644 --- a/src/ui/saving-icon-handler.ts +++ b/src/ui/saving-icon-handler.ts @@ -8,7 +8,7 @@ export class SavingIconHandler extends Phaser.GameObjects.Container { private shown: boolean; constructor() { - super(globalScene, globalScene.game.canvas.width / 6 - 4, globalScene.game.canvas.height / 6 - 4); + super(globalScene, globalScene.scaledCanvas.width - 4, globalScene.scaledCanvas.height - 4); } setup(): void { diff --git a/src/ui/settings/abstract-binding-ui-handler.ts b/src/ui/settings/abstract-binding-ui-handler.ts index eb68456a69d..b2bda2455bb 100644 --- a/src/ui/settings/abstract-binding-ui-handler.ts +++ b/src/ui/settings/abstract-binding-ui-handler.ts @@ -73,8 +73,8 @@ export abstract class AbstractBindingUiHandler extends UiHandler { // Setup backgrounds and text objects for UI. this.titleBg = addWindow( - globalScene.game.canvas.width / 6 - this.getWindowWidth(), - -(globalScene.game.canvas.height / 6) + 28 + 21, + globalScene.scaledCanvas.width - this.getWindowWidth(), + -globalScene.scaledCanvas.height + 28 + 21, this.getWindowWidth(), 24, ); @@ -82,8 +82,8 @@ export abstract class AbstractBindingUiHandler extends UiHandler { this.optionSelectContainer.add(this.titleBg); this.actionBg = addWindow( - globalScene.game.canvas.width / 6 - this.getWindowWidth(), - -(globalScene.game.canvas.height / 6) + this.getWindowHeight() + 28 + 21 + 21, + globalScene.scaledCanvas.width - this.getWindowWidth(), + -globalScene.scaledCanvas.height + this.getWindowHeight() + 28 + 21 + 21, this.getWindowWidth(), 24, ); @@ -102,8 +102,8 @@ export abstract class AbstractBindingUiHandler extends UiHandler { this.optionSelectContainer.add(this.timerText); this.optionSelectBg = addWindow( - globalScene.game.canvas.width / 6 - this.getWindowWidth(), - -(globalScene.game.canvas.height / 6) + this.getWindowHeight() + 28, + globalScene.scaledCanvas.width - this.getWindowWidth(), + -globalScene.scaledCanvas.height + this.getWindowHeight() + 28, this.getWindowWidth(), this.getWindowHeight(), ); diff --git a/src/ui/settings/abstract-control-settings-ui-handler.ts b/src/ui/settings/abstract-control-settings-ui-handler.ts index ee9e990ee2a..b40676fc97c 100644 --- a/src/ui/settings/abstract-control-settings-ui-handler.ts +++ b/src/ui/settings/abstract-control-settings-ui-handler.ts @@ -96,11 +96,11 @@ export abstract class AbstractControlSettingsUiHandler extends UiHandler { const ui = this.getUi(); this.navigationIcons = {}; - this.settingsContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); + this.settingsContainer = globalScene.add.container(1, -globalScene.scaledCanvas.height + 1); this.settingsContainer.setName(`settings-${this.titleSelected}`); this.settingsContainer.setInteractive( - new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), + new Phaser.Geom.Rectangle(0, 0, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height), Phaser.Geom.Rectangle.Contains, ); @@ -109,15 +109,15 @@ export abstract class AbstractControlSettingsUiHandler extends UiHandler { this.optionsBg = addWindow( 0, this.navigationContainer.height, - globalScene.game.canvas.width / 6 - 2, - globalScene.game.canvas.height / 6 - 16 - this.navigationContainer.height - 2, + globalScene.scaledCanvas.width - 2, + globalScene.scaledCanvas.height - 16 - this.navigationContainer.height - 2, ); this.optionsBg.setOrigin(0, 0); this.actionsBg = addWindow( 0, - globalScene.game.canvas.height / 6 - this.navigationContainer.height, - globalScene.game.canvas.width / 6 - 2, + globalScene.scaledCanvas.height - this.navigationContainer.height, + globalScene.scaledCanvas.width - 2, 22, ); this.actionsBg.setOrigin(0, 0); @@ -597,7 +597,7 @@ export abstract class AbstractControlSettingsUiHandler extends UiHandler { // Check if the cursor object exists, if not, create it. if (!this.cursorObj) { - const cursorWidth = globalScene.game.canvas.width / 6 - (this.scrollBar.visible ? 16 : 10); + const cursorWidth = globalScene.scaledCanvas.width - (this.scrollBar.visible ? 16 : 10); this.cursorObj = globalScene.add.nineslice(0, 0, "summary_moves_cursor", undefined, cursorWidth, 16, 1, 1, 1, 1); this.cursorObj.setOrigin(0, 0); // Set the origin to the top-left corner. this.optionsContainer.add(this.cursorObj); // Add the cursor to the options container. diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index 81d733220fc..91d5aec984a 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -56,10 +56,10 @@ export class AbstractSettingsUiHandler extends MessageUiHandler { setup() { const ui = this.getUi(); - this.settingsContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); + this.settingsContainer = globalScene.add.container(1, -globalScene.scaledCanvas.height + 1); this.settingsContainer.setName(`settings-${this.title}`); this.settingsContainer.setInteractive( - new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6 - 20), + new Phaser.Geom.Rectangle(0, 0, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height - 20), Phaser.Geom.Rectangle.Contains, ); @@ -70,16 +70,16 @@ export class AbstractSettingsUiHandler extends MessageUiHandler { this.optionsBg = addWindow( 0, this.navigationContainer.height, - globalScene.game.canvas.width / 6 - 2, - globalScene.game.canvas.height / 6 - 16 - this.navigationContainer.height - 2, + globalScene.scaledCanvas.width - 2, + globalScene.scaledCanvas.height - 16 - this.navigationContainer.height - 2, ); this.optionsBg.setName("window-options-bg"); this.optionsBg.setOrigin(0, 0); const actionsBg = addWindow( 0, - globalScene.game.canvas.height / 6 - this.navigationContainer.height, - globalScene.game.canvas.width / 6 - 2, + globalScene.scaledCanvas.height - this.navigationContainer.height, + globalScene.scaledCanvas.width - 2, 22, ); actionsBg.setOrigin(0, 0); @@ -375,7 +375,7 @@ export class AbstractSettingsUiHandler extends MessageUiHandler { const ret = super.setCursor(cursor); if (!this.cursorObj) { - const cursorWidth = globalScene.game.canvas.width / 6 - (this.scrollBar.visible ? 16 : 10); + const cursorWidth = globalScene.scaledCanvas.width - (this.scrollBar.visible ? 16 : 10); this.cursorObj = globalScene.add.nineslice(0, 0, "summary_moves_cursor", undefined, cursorWidth, 16, 1, 1, 1, 1); this.cursorObj.setOrigin(0, 0); this.optionsContainer.add(this.cursorObj); diff --git a/src/ui/settings/navigation-menu.ts b/src/ui/settings/navigation-menu.ts index 2f3aa50f7f3..b889ce57b61 100644 --- a/src/ui/settings/navigation-menu.ts +++ b/src/ui/settings/navigation-menu.ts @@ -124,7 +124,7 @@ export class NavigationMenu extends Phaser.GameObjects.Container { */ setup() { const navigationManager = NavigationManager.getInstance(); - const headerBg = addWindow(0, 0, globalScene.game.canvas.width / 6 - 2, 24); + const headerBg = addWindow(0, 0, globalScene.scaledCanvas.width - 2, 24); headerBg.setOrigin(0, 0); this.add(headerBg); this.width = headerBg.width; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 0827d5dd697..d4690005c5b 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1050,12 +1050,10 @@ export class StarterSelectUiHandler extends MessageUiHandler { globalScene.add.existing(this.statsContainer); // add the info overlay last to be the top most ui element and prevent the IVs from overlaying this - const overlayScale = 1; this.moveInfoOverlay = new MoveInfoOverlay({ - scale: overlayScale, top: true, x: 1, - y: globalScene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29, + y: globalScene.scaledCanvas.height / 6 - MoveInfoOverlay.getHeight() - 29, }); this.starterSelectContainer.add([ @@ -1311,7 +1309,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.starterSelectMessageBoxContainer.setY(0); this.message.setY(4); } else { - this.starterSelectMessageBoxContainer.setY(globalScene.game.canvas.height / 6); + this.starterSelectMessageBoxContainer.setY(globalScene.scaledCanvas.height); this.starterSelectMessageBox.setOrigin(0, 1); this.message.setY(singleLine ? -22 : -37); } diff --git a/src/ui/title-ui-handler.ts b/src/ui/title-ui-handler.ts index 66cb69f6a26..ab7a4d020fa 100644 --- a/src/ui/title-ui-handler.ts +++ b/src/ui/title-ui-handler.ts @@ -36,12 +36,12 @@ export class TitleUiHandler extends OptionSelectUiHandler { const ui = this.getUi(); - this.titleContainer = globalScene.add.container(0, -(globalScene.game.canvas.height / 6)); + this.titleContainer = globalScene.add.container(0, -globalScene.scaledCanvas.height); this.titleContainer.setName("title"); this.titleContainer.setAlpha(0); ui.add(this.titleContainer); - const logo = globalScene.add.image(globalScene.game.canvas.width / 6 / 2, 8, "logo"); + const logo = globalScene.add.image(globalScene.scaledCanvas.width / 2, 8, "logo"); logo.setOrigin(0.5, 0); this.titleContainer.add(logo); @@ -53,7 +53,7 @@ export class TitleUiHandler extends OptionSelectUiHandler { this.playerCountLabel = addTextObject( // Actual y position will be determined after the title menu has been populated with options - globalScene.game.canvas.width / 6 - 2, + globalScene.scaledCanvas.width - 2, 0, `? ${i18next.t("menu:playersOnline")}`, TextStyle.MESSAGE, @@ -131,7 +131,7 @@ export class TitleUiHandler extends OptionSelectUiHandler { if (ret) { // Moving player count to top of the menu - this.playerCountLabel.setY(globalScene.game.canvas.height / 6 - 13 - this.getWindowHeight()); + this.playerCountLabel.setY(globalScene.scaledCanvas.height - 13 - this.getWindowHeight()); this.splashMessage = randItem(getSplashMessages()); this.splashMessageText.setText( diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 847294dcfc6..d5baea07ed5 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -123,7 +123,7 @@ export class UI extends Phaser.GameObjects.Container { private overlayActive: boolean; constructor() { - super(globalScene, 0, globalScene.game.canvas.height / 6); + super(globalScene, 0, globalScene.scaledCanvas.height); this.mode = UiMode.MESSAGE; this.modeChain = []; @@ -183,13 +183,7 @@ export class UI extends Phaser.GameObjects.Container { for (const handler of this.handlers) { handler.setup(); } - this.overlay = globalScene.add.rectangle( - 0, - 0, - globalScene.game.canvas.width / 6, - globalScene.game.canvas.height / 6, - 0, - ); + this.overlay = globalScene.add.rectangle(0, 0, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height, 0); this.overlay.setName("rect-ui-overlay"); this.overlay.setOrigin(0, 0); globalScene.uiContainer.add(this.overlay); @@ -440,15 +434,15 @@ export class UI extends Phaser.GameObjects.Container { if (isTouch) { // If we are in the top left quadrant on mobile, move the tooltip to the top right corner if (pointerX <= globalScene.game.canvas.width / 2 && pointerY <= globalScene.game.canvas.height / 2) { - x = globalScene.game.canvas.width / 6 - tooltipWidth - padding; + x = globalScene.scaledCanvas.width - tooltipWidth - padding; } } else { // If the tooltip would go offscreen on the right, or is close to it, move to the left of the cursor - if (x + tooltipWidth + padding > globalScene.game.canvas.width / 6) { + if (x + tooltipWidth + padding > globalScene.scaledCanvas.width) { x = Math.max(padding, pointerX / 6 - tooltipWidth - padding); } // If the tooltip would go offscreen at the bottom, or is close to it, move above the cursor - if (y + tooltipHeight + padding > globalScene.game.canvas.height / 6) { + if (y + tooltipHeight + padding > globalScene.scaledCanvas.height) { y = Math.max(padding, pointerY / 6 - tooltipHeight - padding); } } From 6e4dd36d5acb46135a483f53f32d59a956c268cf Mon Sep 17 00:00:00 2001 From: fabske0 <192151969+fabske0@users.noreply.github.com> Date: Fri, 8 Aug 2025 02:56:50 +0200 Subject: [PATCH 04/18] [Docs/Misc/i18n] Fix typos (#6231) * fix comment typos * fix typos in variable/parameter/class names * fix typos in object names * fix typos in `console.log()` and i18n key use * fix typo in filename `abstact-option-select-ui-handler.ts` * fix casing of `@privateremarks` to `@privateRemarks` * fix use of `cancelControllerChoice` --- src/@types/move-types.ts | 2 +- src/battle-scene.ts | 4 +-- src/data/abilities/ability.ts | 12 ++++----- .../encounters/bug-type-superfan-encounter.ts | 2 +- .../encounters/clowning-around-encounter.ts | 2 +- .../encounters/dancing-lessons-encounter.ts | 2 +- .../encounters/delibirdy-encounter.ts | 2 +- .../encounters/field-trip-encounter.ts | 2 +- .../global-trade-system-encounter.ts | 2 +- .../encounters/training-session-encounter.ts | 2 +- .../mystery-encounter-option.ts | 2 +- .../mystery-encounters/mystery-encounter.ts | 6 ++--- .../utils/encounter-dialogue-utils.ts | 2 +- .../utils/encounter-phase-utils.ts | 2 +- src/field/pokemon.ts | 26 +++++++++---------- src/modifier/modifier-type.ts | 4 +-- src/modifier/modifier.ts | 10 +++---- src/phase.ts | 2 +- src/phases/command-phase.ts | 4 +-- src/phases/select-biome-phase.ts | 2 +- src/phases/title-phase.ts | 2 +- src/system/settings/settings-gamepad.ts | 2 +- ...s => abstract-option-select-ui-handler.ts} | 0 src/ui/arena-flyout.ts | 10 +++---- src/ui/autocomplete-ui-handler.ts | 2 +- src/ui/battle-info/player-battle-info.ts | 4 +-- src/ui/command-ui-handler.ts | 2 +- src/ui/confirm-ui-handler.ts | 4 +-- src/ui/login-form-ui-handler.ts | 2 +- src/ui/menu-ui-handler.ts | 2 +- src/ui/modifier-select-ui-handler.ts | 2 +- src/ui/pokedex-page-ui-handler.ts | 2 +- src/ui/pokedex-scan-ui-handler.ts | 2 +- src/ui/pokedex-ui-handler.ts | 2 +- src/ui/run-info-ui-handler.ts | 4 +-- src/ui/save-slot-select-ui-handler.ts | 2 +- .../settings/abstract-binding-ui-handler.ts | 2 +- src/ui/settings/option-select-ui-handler.ts | 2 +- src/ui/starter-select-ui-handler.ts | 4 +-- src/ui/summary-ui-handler.ts | 2 +- src/ui/test-dialogue-ui-handler.ts | 8 +++--- test/ui/starter-select.test.ts | 2 +- 42 files changed, 78 insertions(+), 78 deletions(-) rename src/ui/{abstact-option-select-ui-handler.ts => abstract-option-select-ui-handler.ts} (100%) diff --git a/src/@types/move-types.ts b/src/@types/move-types.ts index 5f8d7e8777e..ff44c665e48 100644 --- a/src/@types/move-types.ts +++ b/src/@types/move-types.ts @@ -16,7 +16,7 @@ export type * from "#moves/move"; * Map of move subclass names to their respective classes. * Does not include the ChargeMove subclasses. For that, use `ChargingMoveClassMap`. * - * @privateremarks + * @privateRemarks * The `never` field (`declare private _: never`) in some classes is necessary * to ensure typescript does not improperly narrow a failed `is` guard to `never`. * diff --git a/src/battle-scene.ts b/src/battle-scene.ts index ea15969ce61..271cde1aaa9 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -242,8 +242,8 @@ export class BattleScene extends SceneBase { public battleStyle: BattleStyle = BattleStyle.SWITCH; /** * Defines whether or not to show type effectiveness hints - * - true: No hints - * - false: Show hints for moves + * - true: Show hints for moves + * - false: No hints */ public typeHints = false; diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 2f57df4a551..336d45fed66 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -1284,7 +1284,7 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { /** * Set stat stages when the user gets hit by a critical hit * - * @privateremarks + * @privateRemarks * It is the responsibility of the caller to ensure that this ability attribute is only applied * when the user has been hit by a critical hit; such an event is not checked here. * @@ -2043,7 +2043,7 @@ export class AllyStatMultiplierAbAttr extends AbAttr { /** * @param stat - The stat being modified - * @param multipler - The multiplier to apply to the stat + * @param multiplier - The multiplier to apply to the stat * @param ignorable - Whether the multiplier can be ignored by mold breaker-like moves and abilities */ constructor(stat: BattleStat, multiplier: number, ignorable = true) { @@ -6444,23 +6444,23 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { public override canApply({ pokemon, source, damage }: PostDamageAbAttrParams): boolean { const moveHistory = pokemon.getMoveHistory(); // Will not activate when the Pokémon's HP is lowered by cutting its own HP - const fordbiddenAttackingMoves = [MoveId.BELLY_DRUM, MoveId.SUBSTITUTE, MoveId.CURSE, MoveId.PAIN_SPLIT]; + const forbiddenAttackingMoves = [MoveId.BELLY_DRUM, MoveId.SUBSTITUTE, MoveId.CURSE, MoveId.PAIN_SPLIT]; if (moveHistory.length > 0) { const lastMoveUsed = moveHistory[moveHistory.length - 1]; - if (fordbiddenAttackingMoves.includes(lastMoveUsed.move)) { + if (forbiddenAttackingMoves.includes(lastMoveUsed.move)) { return false; } } // Dragon Tail and Circle Throw switch out Pokémon before the Ability activates. - const fordbiddenDefendingMoves = [MoveId.DRAGON_TAIL, MoveId.CIRCLE_THROW]; + const forbiddenDefendingMoves = [MoveId.DRAGON_TAIL, MoveId.CIRCLE_THROW]; if (source) { const enemyMoveHistory = source.getMoveHistory(); if (enemyMoveHistory.length > 0) { const enemyLastMoveUsed = enemyMoveHistory[enemyMoveHistory.length - 1]; // Will not activate if the Pokémon's HP falls below half while it is in the air during Sky Drop. if ( - fordbiddenDefendingMoves.includes(enemyLastMoveUsed.move) || + forbiddenDefendingMoves.includes(enemyLastMoveUsed.move) || (enemyLastMoveUsed.move === MoveId.SKY_DROP && enemyLastMoveUsed.result === MoveResult.OTHER) ) { return false; 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 d86f1511207..c5553e9bb95 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -46,7 +46,7 @@ import { } from "#mystery-encounters/mystery-encounter-requirements"; import { getRandomPartyMemberFunc, trainerConfigs } from "#trainers/trainer-config"; import { TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#trainers/trainer-party-template"; -import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { MoveInfoOverlay } from "#ui/move-info-overlay"; import { isNullOrUndefined, randSeedInt, randSeedShuffle } from "#utils/common"; import i18next from "i18next"; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 1c85cb7595c..092cc4931af 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -45,7 +45,7 @@ import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; import { trainerConfigs } from "#trainers/trainer-config"; import { TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#trainers/trainer-party-template"; -import type { OptionSelectConfig } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectConfig } from "#ui/abstract-option-select-ui-handler"; import { randSeedInt, randSeedShuffle } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 94006a43837..8dae0eaee3a 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -37,7 +37,7 @@ import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encou import { MoveRequirement } from "#mystery-encounters/mystery-encounter-requirements"; import { DANCING_MOVES } from "#mystery-encounters/requirement-groups"; import { PokemonData } from "#system/pokemon-data"; -import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 66f50f134dd..85102a01ce1 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -33,7 +33,7 @@ import { MoneyRequirement, } from "#mystery-encounters/mystery-encounter-requirements"; import i18next from "#plugins/i18n"; -import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { randSeedItem } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index 9c655e70b8c..0413c3d0e1d 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -18,7 +18,7 @@ import { import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; -import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import i18next from "i18next"; /** i18n namespace for the encounter */ diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 083a6d06801..ed49fccf190 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -42,7 +42,7 @@ import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encou import { PartySizeRequirement } from "#mystery-encounters/mystery-encounter-requirements"; import { PokemonData } from "#system/pokemon-data"; import { MusicPreference } from "#system/settings"; -import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { isNullOrUndefined, NumberHolder, randInt, randSeedInt, randSeedItem, randSeedShuffle } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; import i18next from "i18next"; diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 393f8a24e51..e56c42a3ee5 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -27,7 +27,7 @@ import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; import { PokemonData } from "#system/pokemon-data"; import type { HeldModifierConfig } from "#types/held-modifier-config"; -import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { isNullOrUndefined, randSeedShuffle } from "#utils/common"; import { getEnumValues } from "#utils/enums"; import i18next from "i18next"; diff --git a/src/data/mystery-encounters/mystery-encounter-option.ts b/src/data/mystery-encounters/mystery-encounter-option.ts index 504310eeabd..6ab2f8dae00 100644 --- a/src/data/mystery-encounters/mystery-encounter-option.ts +++ b/src/data/mystery-encounters/mystery-encounter-option.ts @@ -156,7 +156,7 @@ export class MysteryEncounterOption implements IMysteryEncounterOption { return true; } console.log( - "Mystery Encounter Edge Case: Requirement not met due to primay pokemon overlapping with support pokemon. There's no valid primary pokemon left.", + "Mystery Encounter Edge Case: Requirement not met due to primary pokemon overlapping with support pokemon. There's no valid primary pokemon left.", ); return false; } diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index 47dfe58cace..580fdc2ca38 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -576,7 +576,7 @@ export class MysteryEncounterBuilder implements Partial { */ /** - * @statif Defines the type of encounter which is used as an identifier, should be tied to a unique MysteryEncounterType + * @static Defines the type of encounter which is used as an identifier, should be tied to a unique MysteryEncounterType * NOTE: if new functions are added to {@linkcode MysteryEncounter} class * @param encounterType * @returns this @@ -605,7 +605,7 @@ export class MysteryEncounterBuilder implements Partial { } /** - * Defines an option + phasefor the encounter. + * Defines an option + phase for the encounter. * Use for easy/streamlined options. * There should be at least 2 options defined and no more than 4. * If complex use {@linkcode MysteryEncounterBuilder.withOption} @@ -627,7 +627,7 @@ export class MysteryEncounterBuilder implements Partial { } /** - * Defines an option + phasefor the encounter. + * Defines an option + phase for the encounter. * Use for easy/streamlined options. * There should be at least 2 options defined and no more than 4. * If complex use {@linkcode MysteryEncounterBuilder.withOption} diff --git a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts index 1ae0659b29e..54179ee2604 100644 --- a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts @@ -6,7 +6,7 @@ import { isNullOrUndefined } from "#utils/common"; import i18next from "i18next"; /** - * Will inject all relevant dialogue tokens that exist in the {@linkcode BattlegScene.currentBattle.mysteryEncounter.dialogueTokens}, into i18n text. + * Will inject all relevant dialogue tokens that exist in the {@linkcode globalScene.currentBattle.mysteryEncounter.dialogueTokens}, into i18n text. * Also adds BBCodeText fragments for colored text, if applicable * @param keyOrString * @param primaryStyle Can define a text style to be applied to the entire string. Must be defined for BBCodeText styles to be applied correctly diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 6b085978b27..b599f923534 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -46,7 +46,7 @@ import type { PokemonData } from "#system/pokemon-data"; import type { TrainerConfig } from "#trainers/trainer-config"; import { trainerConfigs } from "#trainers/trainer-config"; import type { HeldModifierConfig } from "#types/held-modifier-config"; -import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import type { PartyOption, PokemonSelectFilter } from "#ui/party-ui-handler"; import { PartyUiMode } from "#ui/party-ui-handler"; import { coerceArray, isNullOrUndefined, randomString, randSeedInt, randSeedItem } from "#utils/common"; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index bc069aa1fc2..fe85e92772c 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4045,7 +4045,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * @param damage integer * @param ignoreSegments boolean, not currently used * @param preventEndure used to update damage if endure or sturdy - * @param ignoreFaintPhas flag on whether to add FaintPhase if pokemon after applying damage faints + * @param ignoreFaintPhase flag on whether to add FaintPhase if pokemon after applying damage faints * @returns integer representing damage dealt */ damage(damage: number, _ignoreSegments = false, preventEndure = false, ignoreFaintPhase = false): number { @@ -5206,38 +5206,38 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { } } - updateFusionPalette(ignoreOveride?: boolean): void { - if (!this.getFusionSpeciesForm(ignoreOveride)) { + updateFusionPalette(ignoreOverride?: boolean): void { + if (!this.getFusionSpeciesForm(ignoreOverride)) { [this.getSprite(), this.getTintSprite()] .filter(s => !!s) .map(s => { - s.pipelineData[`spriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}`] = []; - s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}`] = []; + s.pipelineData[`spriteColors${ignoreOverride && this.summonData.speciesForm ? "Base" : ""}`] = []; + s.pipelineData[`fusionSpriteColors${ignoreOverride && this.summonData.speciesForm ? "Base" : ""}`] = []; }); return; } - const speciesForm = this.getSpeciesForm(ignoreOveride); - const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOveride); + const speciesForm = this.getSpeciesForm(ignoreOverride); + const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride); const spriteKey = speciesForm.getSpriteKey( - this.getGender(ignoreOveride) === Gender.FEMALE, + this.getGender(ignoreOverride) === Gender.FEMALE, speciesForm.formIndex, this.shiny, this.variant, ); const backSpriteKey = speciesForm - .getSpriteKey(this.getGender(ignoreOveride) === Gender.FEMALE, speciesForm.formIndex, this.shiny, this.variant) + .getSpriteKey(this.getGender(ignoreOverride) === Gender.FEMALE, speciesForm.formIndex, this.shiny, this.variant) .replace("pkmn__", "pkmn__back__"); const fusionSpriteKey = fusionSpeciesForm.getSpriteKey( - this.getFusionGender(ignoreOveride) === Gender.FEMALE, + this.getFusionGender(ignoreOverride) === Gender.FEMALE, fusionSpeciesForm.formIndex, this.fusionShiny, this.fusionVariant, ); const fusionBackSpriteKey = fusionSpeciesForm .getSpriteKey( - this.getFusionGender(ignoreOveride) === Gender.FEMALE, + this.getFusionGender(ignoreOverride) === Gender.FEMALE, fusionSpeciesForm.formIndex, this.fusionShiny, this.fusionVariant, @@ -5522,8 +5522,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { [this.getSprite(), this.getTintSprite()] .filter(s => !!s) .map(s => { - s.pipelineData[`spriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}`] = spriteColors; - s.pipelineData[`fusionSpriteColors${ignoreOveride && this.summonData.speciesForm ? "Base" : ""}`] = + s.pipelineData[`spriteColors${ignoreOverride && this.summonData.speciesForm ? "Base" : ""}`] = spriteColors; + s.pipelineData[`fusionSpriteColors${ignoreOverride && this.summonData.speciesForm ? "Base" : ""}`] = fusionSpriteColors; }); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 9ec9ee0ad48..46ed7e1e4b5 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -105,7 +105,7 @@ import { TempExtraModifierModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, - TerrastalizeModifier, + TerastallizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, @@ -431,7 +431,7 @@ export class TerastallizeModifierType extends PokemonModifierType { super( "", `${PokemonType[teraType].toLowerCase()}_tera_shard`, - (type, args) => new TerrastalizeModifier(type as TerastallizeModifierType, (args[0] as Pokemon).id, teraType), + (type, args) => new TerastallizeModifier(type as TerastallizeModifierType, (args[0] as Pokemon).id, teraType), (pokemon: PlayerPokemon) => { if ( [pokemon.species.speciesId, pokemon.fusionSpecies?.speciesId].filter( diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index b31bee7fc69..6907b6907ca 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2073,7 +2073,7 @@ export abstract class ConsumablePokemonModifier extends ConsumableModifier { } } -export class TerrastalizeModifier extends ConsumablePokemonModifier { +export class TerastallizeModifier extends ConsumablePokemonModifier { public declare type: TerastallizeModifierType; public teraType: PokemonType; @@ -2084,9 +2084,9 @@ export class TerrastalizeModifier extends ConsumablePokemonModifier { } /** - * Checks if {@linkcode TerrastalizeModifier} should be applied + * Checks if {@linkcode TerastallizeModifier} should be applied * @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item - * @returns `true` if the {@linkcode TerrastalizeModifier} should be applied + * @returns `true` if the {@linkcode TerastallizeModifier} should be applied */ override shouldApply(playerPokemon?: PlayerPokemon): boolean { return ( @@ -2098,7 +2098,7 @@ export class TerrastalizeModifier extends ConsumablePokemonModifier { } /** - * Applies {@linkcode TerrastalizeModifier} + * Applies {@linkcode TerastallizeModifier} * @param pokemon The {@linkcode PlayerPokemon} that consumes the item * @returns `true` if hp was restored */ @@ -3875,7 +3875,7 @@ const ModifierClassMap = Object.freeze({ ResetNegativeStatStageModifier, FieldEffectModifier, ConsumablePokemonModifier, - TerrastalizeModifier, + TerastallizeModifier, PokemonHpRestoreModifier, PokemonStatusHealModifier, ConsumablePokemonMoveModifier, diff --git a/src/phase.ts b/src/phase.ts index 46a81dddb6f..eccbf3127e6 100644 --- a/src/phase.ts +++ b/src/phase.ts @@ -11,7 +11,7 @@ export abstract class Phase { /** * The string name of the phase, used to identify the phase type for {@linkcode is} * - * @privateremarks + * @privateRemarks * * When implementing a phase, you must set the `phaseName` property to the name of the phase. */ diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index c7eb466157d..ff9ee7cc197 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -112,7 +112,7 @@ export class CommandPhase extends FieldPhase { * Clear out all unusable moves in front of the currently acting pokemon's move queue. */ // TODO: Refactor move queue handling to ensure that this method is not necessary. - private clearUnusuableMoves(): void { + private clearUnusableMoves(): void { const playerPokemon = this.getPokemon(); const moveQueue = playerPokemon.getMoveQueue(); if (moveQueue.length === 0) { @@ -143,7 +143,7 @@ export class CommandPhase extends FieldPhase { * @returns Whether a queued move was successfully set to be executed. */ private tryExecuteQueuedMove(): boolean { - this.clearUnusuableMoves(); + this.clearUnusableMoves(); const playerPokemon = globalScene.getPlayerField()[this.fieldIndex]; const moveQueue = playerPokemon.getMoveQueue(); diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index f4fd11636fc..4089f0c2852 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -5,7 +5,7 @@ import { ChallengeType } from "#enums/challenge-type"; import { UiMode } from "#enums/ui-mode"; import { MapModifier, MoneyInterestModifier } from "#modifiers/modifier"; import { BattlePhase } from "#phases/battle-phase"; -import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { applyChallenges } from "#utils/challenge-utils"; import { BooleanHolder, randSeedInt } from "#utils/common"; diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 6f0493f707d..15d92ba2812 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -16,7 +16,7 @@ import type { Modifier } from "#modifiers/modifier"; import { getDailyRunStarterModifiers, regenerateModifierPoolThresholds } from "#modifiers/modifier-type"; import type { SessionSaveData } from "#system/game-data"; import { vouchers } from "#system/voucher"; -import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { SaveSlotUiMode } from "#ui/save-slot-select-ui-handler"; import { isLocal, isLocalServerConnected, isNullOrUndefined } from "#utils/common"; import i18next from "i18next"; diff --git a/src/system/settings/settings-gamepad.ts b/src/system/settings/settings-gamepad.ts index 2e25eda7300..c334159cc3c 100644 --- a/src/system/settings/settings-gamepad.ts +++ b/src/system/settings/settings-gamepad.ts @@ -144,7 +144,7 @@ export function setSettingGamepad(setting: SettingGamepad, value: number): boole handler: () => changeGamepadHandler(g), })), { - label: i18next.t("settings:cancelContollerChoice"), + label: i18next.t("settings:cancelControllerChoice"), handler: cancelHandler, }, ], diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstract-option-select-ui-handler.ts similarity index 100% rename from src/ui/abstact-option-select-ui-handler.ts rename to src/ui/abstract-option-select-ui-handler.ts diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index d2a45646690..e243bef342e 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -36,7 +36,7 @@ interface ArenaEffectInfo { /** The enum string representation of the effect */ name: string; /** {@linkcode ArenaEffectType} type of effect */ - effecType: ArenaEffectType; + effectType: ArenaEffectType; /** The maximum duration set by the effect */ maxDuration: number; @@ -246,7 +246,7 @@ 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) { + switch (fieldEffectInfo.effectType) { case ArenaEffectType.PLAYER: textObject = this.flyoutTextPlayer; break; @@ -300,7 +300,7 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { const existingTrapTagIndex = isArenaTrapTag ? this.fieldEffectInfo.findIndex( - e => tagAddedEvent.arenaTagType === e.tagType && arenaEffectType === e.effecType, + e => tagAddedEvent.arenaTagType === e.tagType && arenaEffectType === e.effectType, ) : -1; let name: string = getFieldEffectText(ArenaTagType[tagAddedEvent.arenaTagType]); @@ -318,7 +318,7 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { this.fieldEffectInfo.push({ name, - effecType: arenaEffectType, + effectType: arenaEffectType, maxDuration: tagAddedEvent.duration, duration: tagAddedEvent.duration, tagType: tagAddedEvent.arenaTagType, @@ -353,7 +353,7 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { ? WeatherType[fieldEffectChangedEvent.newWeatherType] : TerrainType[fieldEffectChangedEvent.newTerrainType], ), - effecType: + effectType: fieldEffectChangedEvent instanceof WeatherChangedEvent ? ArenaEffectType.WEATHER : ArenaEffectType.TERRAIN, maxDuration: fieldEffectChangedEvent.duration, duration: fieldEffectChangedEvent.duration, diff --git a/src/ui/autocomplete-ui-handler.ts b/src/ui/autocomplete-ui-handler.ts index 6016245c38d..337b17048dc 100644 --- a/src/ui/autocomplete-ui-handler.ts +++ b/src/ui/autocomplete-ui-handler.ts @@ -1,6 +1,6 @@ import { Button } from "#enums/buttons"; import { UiMode } from "#enums/ui-mode"; -import { AbstractOptionSelectUiHandler } from "#ui/abstact-option-select-ui-handler"; +import { AbstractOptionSelectUiHandler } from "#ui/abstract-option-select-ui-handler"; export class AutoCompleteUiHandler extends AbstractOptionSelectUiHandler { modalContainer: Phaser.GameObjects.Container; diff --git a/src/ui/battle-info/player-battle-info.ts b/src/ui/battle-info/player-battle-info.ts index 8784495e6d2..998f7cbb41f 100644 --- a/src/ui/battle-info/player-battle-info.ts +++ b/src/ui/battle-info/player-battle-info.ts @@ -198,11 +198,11 @@ export class PlayerBattleInfo extends BattleInfo { this.lastLevelCapped = isLevelCapped; if (this.lastExp !== pokemon.exp || this.lastLevel !== pokemon.level) { - const durationMultipler = Math.max( + const durationMultiplier = Math.max( Phaser.Tweens.Builders.GetEaseFunction("Cubic.easeIn")(1 - Math.min(pokemon.level - this.lastLevel, 10) / 10), 0.1, ); - await this.updatePokemonExp(pokemon, false, durationMultipler); + await this.updatePokemonExp(pokemon, false, durationMultiplier); } else if (isLevelCapped !== oldLevelCapped) { this.setLevel(pokemon.level); } diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index 41ff559062a..b702bcd0803 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -42,7 +42,7 @@ export class CommandUiHandler extends UiHandler { ui.add(this.commandsContainer); this.teraButton = globalScene.add.sprite(-32, 15, "button_tera"); - this.teraButton.setName("terrastallize-button"); + this.teraButton.setName("terastallize-button"); this.teraButton.setScale(1.3); this.teraButton.setFrame("fire"); this.teraButton.setPipeline(globalScene.spritePipeline, { diff --git a/src/ui/confirm-ui-handler.ts b/src/ui/confirm-ui-handler.ts index aeef7fc1c91..49e88556f1b 100644 --- a/src/ui/confirm-ui-handler.ts +++ b/src/ui/confirm-ui-handler.ts @@ -1,8 +1,8 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { UiMode } from "#enums/ui-mode"; -import type { OptionSelectConfig } from "#ui/abstact-option-select-ui-handler"; -import { AbstractOptionSelectUiHandler } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectConfig } from "#ui/abstract-option-select-ui-handler"; +import { AbstractOptionSelectUiHandler } from "#ui/abstract-option-select-ui-handler"; import i18next from "i18next"; export class ConfirmUiHandler extends AbstractOptionSelectUiHandler { diff --git a/src/ui/login-form-ui-handler.ts b/src/ui/login-form-ui-handler.ts index 67b2c81b94c..0f55faba5c4 100644 --- a/src/ui/login-form-ui-handler.ts +++ b/src/ui/login-form-ui-handler.ts @@ -2,7 +2,7 @@ import { pokerogueApi } from "#api/pokerogue-api"; import { globalScene } from "#app/global-scene"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; -import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; import type { ModalConfig } from "#ui/modal-ui-handler"; diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index e419997456b..da6bc9ced78 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -7,7 +7,7 @@ import { Button } from "#enums/buttons"; import { GameDataType } from "#enums/game-data-type"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; -import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { AdminMode, getAdminModeName } from "#ui/admin-ui-handler"; import type { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; import { BgmBar } from "#ui/bgm-bar"; diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 6edd7d5d70d..a070b522050 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -843,7 +843,7 @@ class ModifierOption extends Phaser.GameObjects.Container { /** * Start the tweens responsible for animating the option's appearance * - * @privateremarks + * @privateRemarks * This method is unusual. It "returns" (one via the actual return, one by via appending to the `promiseHolder` * parameter) two promises. The promise returned by the method resolves once the option's appearance animations have * completed, and is meant to allow callers to synchronize with the completion of the option's appearance animations. diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index fbfb6b615a4..49658d9cfc9 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -46,7 +46,7 @@ import { getVariantIcon, getVariantTint } from "#sprites/variant"; import type { StarterAttributes } from "#system/game-data"; import { SettingKeyboard } from "#system/settings-keyboard"; import type { DexEntry } from "#types/dex-data"; -import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { BaseStatsOverlay } from "#ui/base-stats-overlay"; import { MessageUiHandler } from "#ui/message-ui-handler"; import { MoveInfoOverlay } from "#ui/move-info-overlay"; diff --git a/src/ui/pokedex-scan-ui-handler.ts b/src/ui/pokedex-scan-ui-handler.ts index ab3258a03de..4f606cbcbb0 100644 --- a/src/ui/pokedex-scan-ui-handler.ts +++ b/src/ui/pokedex-scan-ui-handler.ts @@ -1,7 +1,7 @@ import { allAbilities, allMoves, allSpecies } from "#data/data-lists"; import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon } from "#field/pokemon"; -import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { FilterTextRow } from "#ui/filter-text"; import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index 75a3e5162e3..aa2a5cda459 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -33,7 +33,7 @@ import { getVariantIcon, getVariantTint } from "#sprites/variant"; import type { DexAttrProps, StarterAttributes } from "#system/game-data"; import { SettingKeyboard } from "#system/settings-keyboard"; import type { DexEntry } from "#types/dex-data"; -import type { OptionSelectConfig } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectConfig } from "#ui/abstract-option-select-ui-handler"; import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#ui/dropdown"; import { FilterBar } from "#ui/filter-bar"; import { FilterText, FilterTextRow } from "#ui/filter-text"; diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 667a22a001a..2def302c1d5 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -702,11 +702,11 @@ export class RunInfoUiHandler extends UiHandler { rules.push(i18next.t("challenges:inverseBattle.shortName")); break; default: { - const localisationKey = Challenges[this.runInfo.challenges[i].id] + const localizationKey = 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`)); + rules.push(i18next.t(`challenges:${localizationKey}.name`)); break; } } diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 5b1566b4913..9c2f8488b22 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -401,7 +401,7 @@ class SessionSlot extends Phaser.GameObjects.Container { const gameModeLabel = addTextObject( 8, 5, - `${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unkown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}`, + `${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unknown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}`, TextStyle.WINDOW, ); this.add(gameModeLabel); diff --git a/src/ui/settings/abstract-binding-ui-handler.ts b/src/ui/settings/abstract-binding-ui-handler.ts index b2bda2455bb..2c8d0eb63ba 100644 --- a/src/ui/settings/abstract-binding-ui-handler.ts +++ b/src/ui/settings/abstract-binding-ui-handler.ts @@ -8,7 +8,7 @@ import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; import i18next from "i18next"; -type CancelFn = (succes?: boolean) => boolean; +type CancelFn = (success?: boolean) => boolean; /** * Abstract class for handling UI elements related to button bindings. diff --git a/src/ui/settings/option-select-ui-handler.ts b/src/ui/settings/option-select-ui-handler.ts index d7b699e6e50..c989c768244 100644 --- a/src/ui/settings/option-select-ui-handler.ts +++ b/src/ui/settings/option-select-ui-handler.ts @@ -1,5 +1,5 @@ import { UiMode } from "#enums/ui-mode"; -import { AbstractOptionSelectUiHandler } from "#ui/abstact-option-select-ui-handler"; +import { AbstractOptionSelectUiHandler } from "#ui/abstract-option-select-ui-handler"; export class OptionSelectUiHandler extends AbstractOptionSelectUiHandler { constructor(mode: UiMode = UiMode.OPTION_SELECT) { diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index d4690005c5b..15a08e9d0f4 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -47,7 +47,7 @@ import { achvs } from "#system/achv"; import type { DexAttrProps, StarterAttributes, StarterMoveset } from "#system/game-data"; import { SettingKeyboard } from "#system/settings-keyboard"; import type { DexEntry } from "#types/dex-data"; -import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#ui/dropdown"; import { FilterBar } from "#ui/filter-bar"; import { MessageUiHandler } from "#ui/message-ui-handler"; @@ -901,7 +901,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.pokemonEggMovesContainer.add(eggMoveContainer); } - this.teraIcon = globalScene.add.sprite(85, 63, "button_tera").setName("terrastallize-icon").setFrame("fire"); + this.teraIcon = globalScene.add.sprite(85, 63, "button_tera").setName("terastallize-icon").setFrame("fire"); // The font size should be set per language const instructionTextSize = textSettings.instructionTextSize; diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index b51bdfdb157..b6ce0a706f8 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -879,7 +879,7 @@ export class SummaryUiHandler extends UiHandler { !isNullOrUndefined(this.pokemon) ) { const teraIcon = globalScene.add.sprite(123, 26, "button_tera"); - teraIcon.setName("terrastallize-icon"); + teraIcon.setName("terastallize-icon"); teraIcon.setFrame(PokemonType[this.pokemon.getTeraType()].toLowerCase()); profileContainer.add(teraIcon); } diff --git a/src/ui/test-dialogue-ui-handler.ts b/src/ui/test-dialogue-ui-handler.ts index c57c73ca777..4f825ed95ea 100644 --- a/src/ui/test-dialogue-ui-handler.ts +++ b/src/ui/test-dialogue-ui-handler.ts @@ -1,6 +1,6 @@ import { UiMode } from "#enums/ui-mode"; import type { PlayerPokemon } from "#field/pokemon"; -import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; import type { ModalConfig } from "#ui/modal-ui-handler"; @@ -13,7 +13,7 @@ export class TestDialogueUiHandler extends FormModalUiHandler { setup() { super.setup(); - const flattenKeys = (object?: any, topKey?: string, midleKey?: string[]): Array => { + const flattenKeys = (object?: any, topKey?: string, middleKey?: string[]): Array => { return Object.keys(object ?? {}) .map((t, i) => { const value = Object.values(object)[i]; @@ -23,7 +23,7 @@ export class TestDialogueUiHandler extends FormModalUiHandler { // If the value is an object, execute the same process // si el valor es un objeto ejecuta el mismo proceso - return flattenKeys(value, topKey ?? t, topKey ? (midleKey ? [...midleKey, t] : [t]) : undefined).filter( + return flattenKeys(value, topKey ?? t, topKey ? (middleKey ? [...middleKey, t] : [t]) : undefined).filter( t => t.length > 0, ); } @@ -31,7 +31,7 @@ export class TestDialogueUiHandler extends FormModalUiHandler { // we check for null or undefined here as per above - the typeof is still an object but the value is null so we need to exit out of this and pass the null key // Return in the format expected by i18next - return midleKey ? `${topKey}:${midleKey.map(m => m).join(".")}.${t}` : `${topKey}:${t}`; + return middleKey ? `${topKey}:${middleKey.map(m => m).join(".")}.${t}` : `${topKey}:${t}`; } }) .filter(t => t); diff --git a/test/ui/starter-select.test.ts b/test/ui/starter-select.test.ts index a8c6284cf3f..6dc9603c8b3 100644 --- a/test/ui/starter-select.test.ts +++ b/test/ui/starter-select.test.ts @@ -10,7 +10,7 @@ import { EncounterPhase } from "#phases/encounter-phase"; import { SelectStarterPhase } from "#phases/select-starter-phase"; import type { TitlePhase } from "#phases/title-phase"; import { GameManager } from "#test/test-utils/game-manager"; -import type { OptionSelectItem } from "#ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import type { OptionSelectUiHandler } from "#ui/option-select-ui-handler"; import type { SaveSlotSelectUiHandler } from "#ui/save-slot-select-ui-handler"; import type { StarterSelectUiHandler } from "#ui/starter-select-ui-handler"; From 8e261512fc6f62c24f0e399129e2eb985191075c Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:00:34 -0400 Subject: [PATCH 05/18] [Test] Improved tests for arena trap (#6209) * Added arena trap tests and such * hopefullt fixed tests * Marked test as todo since me smol brain --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> --- test/abilities/arena-trap.test.ts | 85 ++++++++++++++++--------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/test/abilities/arena-trap.test.ts b/test/abilities/arena-trap.test.ts index f85fae5b259..0090487f49c 100644 --- a/test/abilities/arena-trap.test.ts +++ b/test/abilities/arena-trap.test.ts @@ -1,10 +1,15 @@ +import { getPokemonNameWithAffix } from "#app/messages"; import { allAbilities } from "#data/data-lists"; import { AbilityId } from "#enums/ability-id"; +import { Button } from "#enums/buttons"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; +import { UiMode } from "#enums/ui-mode"; import { GameManager } from "#test/test-utils/game-manager"; +import type { PartyUiHandler } from "#ui/party-ui-handler"; +import i18next from "i18next"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; describe("Abilities - Arena Trap", () => { let phaserGame: Phaser.Game; @@ -23,68 +28,64 @@ describe("Abilities - Arena Trap", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .moveset(MoveId.SPLASH) .ability(AbilityId.ARENA_TRAP) + .enemyAbility(AbilityId.ARENA_TRAP) .enemySpecies(SpeciesId.RALTS) - .enemyAbility(AbilityId.BALL_FETCH) - .enemyMoveset(MoveId.TELEPORT); + .enemyMoveset(MoveId.SPLASH); }); - // TODO: Enable test when Issue #935 is addressed - it.todo("should not allow grounded Pokémon to flee", async () => { + // NB: Since switching moves bypass trapping, the only way fleeing can occur in PKR is from the player + // TODO: Implement once forced flee helper exists + it.todo("should interrupt player flee attempt and display message, unless user has Run Away"); + + // TODO: Figure out how to wrangle the UI into not timing out + it.todo("should interrupt player switch attempt and display message", async () => { game.override.battleStyle("single"); + await game.classicMode.startBattle([SpeciesId.DUGTRIO, SpeciesId.GOTHITELLE]); - await game.classicMode.startBattle(); + const enemy = game.field.getEnemyPokemon(); - const enemy = game.scene.getEnemyPokemon(); + game.doSwitchPokemon(1); + game.onNextPrompt("CommandPhase", UiMode.PARTY, () => { + // no switch out command should be queued due to arena trap + expect(game.scene.currentBattle.turnCommands[0]).toBeNull(); - game.move.select(MoveId.SPLASH); + // back out and end the phase to avoid timeout + console.log(game.scene.ui.getHandler().constructor.name); + (game.scene.ui.getHandler() as PartyUiHandler).processInput(Button.CANCEL); + }); - await game.toNextTurn(); + await game.phaseInterceptor.to("CommandPhase"); - expect(enemy).toBe(game.scene.getEnemyPokemon()); + expect(game.textInterceptor.logs).toContain( + i18next.t("abilityTriggers:arenaTrap", { + pokemonNameWithAffix: getPokemonNameWithAffix(enemy), + abilityName: allAbilities[AbilityId.ARENA_TRAP].name, + }), + ); }); it("should guarantee double battle with any one LURE", async () => { game.override.startingModifier([{ name: "LURE" }]).startingWave(2); + await game.classicMode.startBattle([SpeciesId.DUGTRIO]); - await game.classicMode.startBattle(); - - expect(game.scene.getEnemyField().length).toBe(2); + expect(game.scene.getEnemyField()).toHaveLength(2); }); - /** - * This checks if the Player Pokemon is able to switch out/run away after the Enemy Pokemon with {@linkcode AbilityId.ARENA_TRAP} - * is forcefully moved out of the field from moves such as Roar {@linkcode MoveId.ROAR} - * - * Note: It should be able to switch out/run away - */ it("should lift if pokemon with this ability leaves the field", async () => { - game.override - .battleStyle("double") - .enemyMoveset(MoveId.SPLASH) - .moveset([MoveId.ROAR, MoveId.SPLASH]) - .ability(AbilityId.BALL_FETCH); - await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.SUDOWOODO, SpeciesId.LUNATONE]); + game.override.battleStyle("single"); + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - const [enemy1, enemy2] = game.scene.getEnemyField(); - const [player1, player2] = game.scene.getPlayerField(); + const player = game.field.getPlayerPokemon(); + const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy1, "getAbility").mockReturnValue(allAbilities[AbilityId.ARENA_TRAP]); + expect(player.isTrapped()).toBe(true); + expect(enemy.isOnField()).toBe(true); - game.move.select(MoveId.ROAR); - game.move.select(MoveId.SPLASH, 1); + game.move.use(MoveId.ROAR); + await game.toEndOfTurn(); - // This runs the fist command phase where the moves are selected - await game.toNextTurn(); - // During the next command phase the player pokemons should not be trapped anymore - game.move.select(MoveId.SPLASH); - game.move.select(MoveId.SPLASH, 1); - await game.toNextTurn(); - - expect(player1.isTrapped()).toBe(false); - expect(player2.isTrapped()).toBe(false); - expect(enemy1.isOnField()).toBe(false); - expect(enemy2.isOnField()).toBe(true); + expect(player.isTrapped()).toBe(false); + expect(enemy.isOnField()).toBe(false); }); }); From 89871b10c6c04b724066cda05f64a501ebd098f4 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 7 Aug 2025 18:11:47 -0700 Subject: [PATCH 06/18] [i18n] Update locales submodule --- public/locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales b/public/locales index fa35780fed7..ab2716d5440 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit fa35780fed762017c89d1e9ece8a2779dff56c4d +Subproject commit ab2716d5440c25f73986664aa3f3131821c3c392 From 4bb3e11c746e8e969f8d1605795b62d193189a59 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:24:33 -0400 Subject: [PATCH 07/18] [Misc] Added chance for alt title logo (#6182) * [Misc] Added 0.1% chance for fake login screen * Actually added logo file * Update title-ui-handler.ts Co-authored-by: damocleas Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> --- public/images/logo_fake.png | Bin 0 -> 1628 bytes src/constants.ts | 7 +++++++ src/loading-scene.ts | 1 + src/timed-event-manager.ts | 10 ++++++++++ src/ui/title-ui-handler.ts | 13 ++++++++++++- src/utils/common.ts | 12 ++++++++---- 6 files changed, 38 insertions(+), 5 deletions(-) create mode 100755 public/images/logo_fake.png diff --git a/public/images/logo_fake.png b/public/images/logo_fake.png new file mode 100755 index 0000000000000000000000000000000000000000..9fdb87240252709715af48d90f62b7a313d6dde3 GIT binary patch literal 1628 zcmV-i2BZ0jP)Px){7FPXRCr$Pn>%h5K@dgljF5yCARz)0GB!a*#0FRZ>tF$FfQXDukdY7|1Q{6# zAtC{_MOjzHz4httv7aqo@~k&A-Ss*3>7Mra+27CSCr^Ic-!tD2-oHPec@Ce6WLO>l z6!crQ&&=~o?^Rn?RdbSGoX|`SACVqcnOxlHNIB?n}J{euHaq)%L3=R^~)Tw>v{x~pX112xhP*~ z{2dPHf{QT}H$bVt7+?^;9Ra2cO0SlQ}H8QrAL_EI`vp)px` ziyCsjypFaw(Bf67R`-t3aoFpy-?QzRN=8<2d9Y^mS_UhEDg$yjZ#dhGknLkdPz^^^ z`VE^Cvs)ym(r?K8`unF#`*r7LSNM2u2sN7`P8C}Wi142R<9+nR-rQE8e9=r z*m$Z;RRP(r45K#=yP-mTeMUmr5iAB!#a=4oRm*TEP?=uSV6mnzDt$^JbhKKv3`4SJ z6ZfM!P?;>_`N~QOWoKogGPjN4w_;dEz^pW$W2@3gc9!u}KgIkKja~8MfN}~f+w;Il zD&@?B=QEFTvUiFt3_5eFl+Wfw{boxATjH~2eFvvIC{>8)x)o8LJ|@FG=iGT?H=X2MiXQOUc#BSlK2c zbFzoZ1Y#=^rBJjFm7U69eUdM2S()vCwL{-sU=2NI;EDnqZKrHIo-c!?oNFtOX#dFX z8(9>ydaYg$`Fkt9C@&WjtH8X7j5I?-u|lXS9cfHc|c5_&u_1 z8LX_&i1wj;BZ>F=aebs@!rO~vX50!?X2kK$WWgS;T8dWmRi!N2Z(=>7Io^6D4=K}E zWpM^qX4@+P3nS1{b7Ux0mOAB#EiP2_8JxSon$_3Khy6^nL(ag8R_wcbES0fKrLdZ@ zEN!!LX3?Xf1y>@7cSwrxecWt1@nu5iHZPRdMCnsKj9wpVh0pDqI1p zs;Ge#Wu`1^Rp&CpM)1snRh2>Yh~4K+m#lBfVDhEVXTpwLC9qWUh#Q4$<)L2OG6m7d zQD#vqNmb4wVC>TB0#?d8XgPWwi7P$hI# zue20;SkSNxtOza!18%Oe>qq>CM@b^M#x=FkiRH3#?#bq1b9at_E7`ktXLj|zUDYei zSSn%_SXCvnvqGr6Djn#sii0|i@%OUK&B~<;hR)nk5N5&FwZl<*wZOu99Dx<3`fk)_ zSxo`p!OR@u10ovNqPx;(MnF|cTIYF{fvXvFr(PwnSk|h5RdMF2-D43P1%PtEsp(Rc z$7ih_zHNhLSqpoRsscCtKHucu78@L)R~f9VRNQoEs`8@~X3JD{uB*Inw_g9jYI<+C z1DewxcKdFTF0000 this.isActive(te)); } + /** + * Check whether the current event is active and for April Fools. + * @returns Whether the April Fools event is currently active. + */ + isAprilFoolsActive(): boolean { + return timedEvents.some( + te => this.isActive(te) && te.hasOwnProperty("bannerKey") && te.bannerKey!.startsWith("aprf"), + ); + } + activeEventHasBanner(): boolean { const activeEvents = timedEvents.filter(te => this.isActive(te) && te.hasOwnProperty("bannerKey")); return activeEvents.length > 0; diff --git a/src/ui/title-ui-handler.ts b/src/ui/title-ui-handler.ts index ab7a4d020fa..36e37500a64 100644 --- a/src/ui/title-ui-handler.ts +++ b/src/ui/title-ui-handler.ts @@ -1,4 +1,5 @@ import { pokerogueApi } from "#api/pokerogue-api"; +import { FAKE_TITLE_LOGO_CHANCE } from "#app/constants"; import { timedEventManager } from "#app/global-event-manager"; import { globalScene } from "#app/global-scene"; import { TimedEventDisplay } from "#app/timed-event-manager"; @@ -41,7 +42,7 @@ export class TitleUiHandler extends OptionSelectUiHandler { this.titleContainer.setAlpha(0); ui.add(this.titleContainer); - const logo = globalScene.add.image(globalScene.scaledCanvas.width / 2, 8, "logo"); + const logo = globalScene.add.image(globalScene.scaledCanvas.width / 2, 8, this.getLogo()); logo.setOrigin(0.5, 0); this.titleContainer.add(logo); @@ -186,4 +187,14 @@ export class TitleUiHandler extends OptionSelectUiHandler { ease: "Sine.easeInOut", }); } + + /** + * Get the logo file path to load, with a 0.1% chance to use the fake logo instead. + * @returns The path to the image. + */ + private getLogo(): string { + // Invert spawn chances on april fools + const aprilFools = timedEventManager.isAprilFoolsActive(); + return aprilFools === !!randInt(FAKE_TITLE_LOGO_CHANCE) ? "logo_fake" : "logo"; + } } diff --git a/src/utils/common.ts b/src/utils/common.ts index 1c75dac93b4..aac1ef359e6 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -70,12 +70,16 @@ export function padInt(value: number, length: number, padWith?: string): string } /** - * Returns a random integer between min and min + range - * @param range The amount of possible numbers - * @param min The starting number + * Returns a **completely unseeded** random integer between `min` and `min + range`. + * @param range - The amount of possible numbers to pick + * @param min - The minimum number to pick; default `0` + * @returns A psuedo-random, unseeded integer within the interval [min, min+range]. + * @remarks + * This should not be used for battles or other outwards-facing randomness; + * battles are intended to be seeded and deterministic. */ export function randInt(range: number, min = 0): number { - if (range === 1) { + if (range <= 1) { return min; } return Math.floor(Math.random() * range) + min; From ae9f03e06056a2ada91739f572e6918d0f07483c Mon Sep 17 00:00:00 2001 From: Blitzy <118096277+Blitz425@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:45:37 -0500 Subject: [PATCH 08/18] [Balance] Evil Leader Adjustments (#6133) * Update trainer-config.ts * Update Starmobiles to use their mainline movesets * Update Starmobile forms and Trainer Config --------- Co-authored-by: damocleas --- src/data/balance/passives.ts | 2 +- src/data/balance/pokemon-species.ts | 10 +- src/data/trainers/trainer-config.ts | 234 ++++++++++++++-------------- 3 files changed, 121 insertions(+), 125 deletions(-) diff --git a/src/data/balance/passives.ts b/src/data/balance/passives.ts index 0a76b3036b9..c0f84d90cf0 100644 --- a/src/data/balance/passives.ts +++ b/src/data/balance/passives.ts @@ -1044,7 +1044,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [SpeciesId.FINIZEN]: { 0: AbilityId.FRIEND_GUARD }, [SpeciesId.PALAFIN]: { 0: AbilityId.EMERGENCY_EXIT, 1: AbilityId.IRON_FIST }, [SpeciesId.VAROOM]: { 0: AbilityId.LEVITATE }, - [SpeciesId.REVAVROOM]: { 0: AbilityId.LEVITATE, 1: AbilityId.DARK_AURA, 2: AbilityId.FLASH_FIRE, 3: AbilityId.MERCILESS, 4: AbilityId.FILTER, 5: AbilityId.SCRAPPY }, + [SpeciesId.REVAVROOM]: { 0: AbilityId.LEVITATE, 1: AbilityId.INTIMIDATE, 2: AbilityId.SPEED_BOOST, 3: AbilityId.TOXIC_DEBRIS, 4: AbilityId.MISTY_SURGE, 5: AbilityId.STAMINA }, [SpeciesId.CYCLIZAR]: { 0: AbilityId.PROTEAN }, [SpeciesId.ORTHWORM]: { 0: AbilityId.REGENERATOR }, [SpeciesId.GLIMMET]: { 0: AbilityId.STURDY }, diff --git a/src/data/balance/pokemon-species.ts b/src/data/balance/pokemon-species.ts index 5e9d352f437..13269308958 100644 --- a/src/data/balance/pokemon-species.ts +++ b/src/data/balance/pokemon-species.ts @@ -1618,11 +1618,11 @@ export function initSpecies() { new PokemonSpecies(SpeciesId.VAROOM, 9, false, false, false, "Single-Cyl Pokémon", PokemonType.STEEL, PokemonType.POISON, 1, 35, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.SLOW_START, 300, 45, 70, 63, 30, 45, 47, 190, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(SpeciesId.REVAVROOM, 9, false, false, false, "Multi-Cyl Pokémon", PokemonType.STEEL, PokemonType.POISON, 1.8, 120, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, GrowthRate.MEDIUM_FAST, 50, false, false, new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.POISON, 1.8, 120, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, false, null, true), - new PokemonForm("Segin Starmobile", "segin-starmobile", PokemonType.STEEL, PokemonType.DARK, 1.8, 240, AbilityId.INTIMIDATE, AbilityId.NONE, AbilityId.INTIMIDATE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), - new PokemonForm("Schedar Starmobile", "schedar-starmobile", PokemonType.STEEL, PokemonType.FIRE, 1.8, 240, AbilityId.SPEED_BOOST, AbilityId.NONE, AbilityId.SPEED_BOOST, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), - new PokemonForm("Navi Starmobile", "navi-starmobile", PokemonType.STEEL, PokemonType.POISON, 1.8, 240, AbilityId.TOXIC_DEBRIS, AbilityId.NONE, AbilityId.TOXIC_DEBRIS, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), - new PokemonForm("Ruchbah Starmobile", "ruchbah-starmobile", PokemonType.STEEL, PokemonType.FAIRY, 1.8, 240, AbilityId.MISTY_SURGE, AbilityId.NONE, AbilityId.MISTY_SURGE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), - new PokemonForm("Caph Starmobile", "caph-starmobile", PokemonType.STEEL, PokemonType.FIGHTING, 1.8, 240, AbilityId.STAMINA, AbilityId.NONE, AbilityId.STAMINA, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true) + new PokemonForm("Segin Starmobile", "segin-starmobile", PokemonType.STEEL, PokemonType.DARK, 1.8, 240, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.OVERCOAT, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), + new PokemonForm("Schedar Starmobile", "schedar-starmobile", PokemonType.STEEL, PokemonType.FIRE, 1.8, 240, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.OVERCOAT, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), + new PokemonForm("Navi Starmobile", "navi-starmobile", PokemonType.STEEL, PokemonType.POISON, 1.8, 240, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.OVERCOAT, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), + new PokemonForm("Ruchbah Starmobile", "ruchbah-starmobile", PokemonType.STEEL, PokemonType.FAIRY, 1.8, 240, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.OVERCOAT, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), + new PokemonForm("Caph Starmobile", "caph-starmobile", PokemonType.STEEL, PokemonType.FIGHTING, 1.8, 240, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.OVERCOAT, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true) ), new PokemonSpecies(SpeciesId.CYCLIZAR, 9, false, false, false, "Mount Pokémon", PokemonType.DRAGON, PokemonType.NORMAL, 1.6, 63, AbilityId.SHED_SKIN, AbilityId.NONE, AbilityId.REGENERATOR, 501, 70, 95, 65, 85, 65, 121, 190, 50, 175, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(SpeciesId.ORTHWORM, 9, false, false, false, "Earthworm Pokémon", PokemonType.STEEL, null, 2.5, 310, AbilityId.EARTH_EATER, AbilityId.NONE, AbilityId.SAND_VEIL, 480, 70, 85, 145, 60, 55, 65, 25, 50, 240, GrowthRate.SLOW, 50, false), diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 5739492f96e..6c643737778 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -2562,7 +2562,7 @@ export const trainerConfigs: TrainerConfigs = { p.moveset = [ new PokemonMove(MoveId.WICKED_TORQUE), new PokemonMove(MoveId.SPIN_OUT), - new PokemonMove(MoveId.SHIFT_GEAR), + new PokemonMove(MoveId.PARTING_SHOT), new PokemonMove(MoveId.HIGH_HORSEPOWER), ]; }), @@ -2582,7 +2582,7 @@ export const trainerConfigs: TrainerConfigs = { p.moveset = [ new PokemonMove(MoveId.BLAZING_TORQUE), new PokemonMove(MoveId.SPIN_OUT), - new PokemonMove(MoveId.SHIFT_GEAR), + new PokemonMove(MoveId.FLAME_CHARGE), new PokemonMove(MoveId.HIGH_HORSEPOWER), ]; }), @@ -2602,7 +2602,7 @@ export const trainerConfigs: TrainerConfigs = { p.moveset = [ new PokemonMove(MoveId.NOXIOUS_TORQUE), new PokemonMove(MoveId.SPIN_OUT), - new PokemonMove(MoveId.SHIFT_GEAR), + new PokemonMove(MoveId.TOXIC_SPIKES), new PokemonMove(MoveId.HIGH_HORSEPOWER), ]; }), @@ -2622,7 +2622,7 @@ export const trainerConfigs: TrainerConfigs = { p.moveset = [ new PokemonMove(MoveId.MAGICAL_TORQUE), new PokemonMove(MoveId.SPIN_OUT), - new PokemonMove(MoveId.SHIFT_GEAR), + new PokemonMove(MoveId.MISTY_TERRAIN), new PokemonMove(MoveId.HIGH_HORSEPOWER), ]; }), @@ -2642,7 +2642,7 @@ export const trainerConfigs: TrainerConfigs = { p.moveset = [ new PokemonMove(MoveId.COMBAT_TORQUE), new PokemonMove(MoveId.SPIN_OUT), - new PokemonMove(MoveId.SHIFT_GEAR), + new PokemonMove(MoveId.IRON_DEFENSE), new PokemonMove(MoveId.HIGH_HORSEPOWER), ]; }), @@ -4974,21 +4974,21 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.NIDOQUEEN, SpeciesId.NIDOKING])) .setPartyMemberFunc( 4, - getRandomPartyMemberFunc([SpeciesId.RHYPERIOR], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ULTRA_BALL; - p.abilityIndex = 1; // Solid Rock - }), - ) - .setPartyMemberFunc( - 5, getRandomPartyMemberFunc([SpeciesId.KANGASKHAN], TrainerSlot.TRAINER, true, p => { - p.setBoss(true, 2); p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; p.formIndex = 1; // Mega Kangaskhan p.generateName(); }), + ) + .setPartyMemberFunc( + 5, + getRandomPartyMemberFunc([SpeciesId.RHYPERIOR], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.pokeball = PokeballType.ULTRA_BALL; + p.abilityIndex = 1; // Solid Rock + p.setBoss(true, 2); + }), ), [TrainerType.ROCKET_BOSS_GIOVANNI_2]: new TrainerConfig(++t) .setName("Giovanni") @@ -4997,52 +4997,53 @@ export const trainerConfigs: TrainerConfigs = { .setVictoryBgm("victory_team_plasma") .setPartyMemberFunc( 0, - getRandomPartyMemberFunc([SpeciesId.TYRANITAR], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.RHYPERIOR], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); p.generateAndPopulateMoveset(); + p.abilityIndex = 1; // Solid Rock p.pokeball = PokeballType.ULTRA_BALL; }), ) .setPartyMemberFunc( 1, - getRandomPartyMemberFunc([SpeciesId.GASTRODON, SpeciesId.SEISMITOAD], TrainerSlot.TRAINER, true, p => { - if (p.species.speciesId === SpeciesId.GASTRODON) { - p.abilityIndex = 0; // Storm Drain - } else if (p.species.speciesId === SpeciesId.SEISMITOAD) { - p.abilityIndex = 2; // Water Absorb - } + getRandomPartyMemberFunc([SpeciesId.NIDOKING, SpeciesId.NIDOQUEEN], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.abilityIndex = 2; // Sheer Force }), ) .setPartyMemberFunc( 2, - getRandomPartyMemberFunc([SpeciesId.GARCHOMP, SpeciesId.EXCADRILL], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.HONCHKROW], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ULTRA_BALL; - if (p.species.speciesId === SpeciesId.GARCHOMP) { - p.abilityIndex = 2; // Rough Skin - } else if (p.species.speciesId === SpeciesId.EXCADRILL) { - p.abilityIndex = 0; // Sand Rush + if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.SUCKER_PUNCH)) { + // Check if Sucker Punch is in the moveset, if not, replace the third move with Sucker Punch. + p.moveset[2] = new PokemonMove(MoveId.SUCKER_PUNCH); } }), ) .setPartyMemberFunc( 3, - getRandomPartyMemberFunc([SpeciesId.RHYPERIOR], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ULTRA_BALL; - p.abilityIndex = 1; // Solid Rock - }), - ) - .setPartyMemberFunc( - 4, getRandomPartyMemberFunc([SpeciesId.KANGASKHAN], TrainerSlot.TRAINER, true, p => { - p.setBoss(true, 2); p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; p.formIndex = 1; // Mega Kangaskhan p.generateName(); }), ) + .setPartyMemberFunc( + 4, + getRandomPartyMemberFunc( + [SpeciesId.ARTICUNO, SpeciesId.ZAPDOS, SpeciesId.MOLTRES], + TrainerSlot.TRAINER, + true, + p => { + p.generateAndPopulateMoveset(); + p.pokeball = PokeballType.ULTRA_BALL; + p.abilityIndex = 2; // Snow Cloak Articuno, Static Zapdos, Flame Body Moltres + p.setBoss(true, 2); + }, + ), + ) .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.MEWTWO], TrainerSlot.TRAINER, true, p => { @@ -5056,16 +5057,22 @@ export const trainerConfigs: TrainerConfigs = { .initForEvilTeamLeader("Magma Boss", []) .setMixedBattleBgm("battle_aqua_magma_boss") .setVictoryBgm("victory_team_plasma") - .setPartyMemberFunc(0, getRandomPartyMemberFunc([SpeciesId.SOLROCK])) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.TALONFLAME])) - .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.WEEZING, SpeciesId.GALAR_WEEZING])) .setPartyMemberFunc( - 3, + 0, getRandomPartyMemberFunc([SpeciesId.TORKOAL], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.abilityIndex = 1; // Drought }), ) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.SOLROCK])) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.WEEZING, SpeciesId.GALAR_WEEZING])) + .setPartyMemberFunc( + 3, + getRandomPartyMemberFunc([SpeciesId.SCOVILLAIN], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.abilityIndex = 0; // Chlorophyll + }), + ) .setPartyMemberFunc(4, getRandomPartyMemberFunc([SpeciesId.DONPHAN])) .setPartyMemberFunc( 5, @@ -5140,16 +5147,16 @@ export const trainerConfigs: TrainerConfigs = { .initForEvilTeamLeader("Aqua Boss", []) .setMixedBattleBgm("battle_aqua_magma_boss") .setVictoryBgm("victory_team_plasma") - .setPartyMemberFunc(0, getRandomPartyMemberFunc([SpeciesId.LUDICOLO])) .setPartyMemberFunc( - 1, + 0, getRandomPartyMemberFunc([SpeciesId.PELIPPER], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.abilityIndex = 1; // Drizzle }), ) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.WAILORD])) .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.MUK, SpeciesId.ALOLA_MUK])) - .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.WAILORD])) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.LUDICOLO])) .setPartyMemberFunc( 4, getRandomPartyMemberFunc([SpeciesId.QWILFISH], TrainerSlot.TRAINER, true, p => { @@ -5225,7 +5232,7 @@ export const trainerConfigs: TrainerConfigs = { .setMixedBattleBgm("battle_galactic_boss") .setVictoryBgm("victory_team_plasma") .setPartyMemberFunc(0, getRandomPartyMemberFunc([SpeciesId.GYARADOS])) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.HONCHKROW, SpeciesId.HISUI_BRAVIARY])) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.CROBAT, SpeciesId.HONCHKROW])) .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.MAGNEZONE])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.UXIE, SpeciesId.MESPRIT, SpeciesId.AZELF])) .setPartyMemberFunc( @@ -5233,8 +5240,6 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.HOUNDOOM], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; - p.formIndex = 1; // Mega Houndoom - p.generateName(); }), ) .setPartyMemberFunc( @@ -5253,7 +5258,7 @@ export const trainerConfigs: TrainerConfigs = { .setVictoryBgm("victory_team_plasma") .setPartyMemberFunc( 0, - getRandomPartyMemberFunc([SpeciesId.CROBAT], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.CROBAT, SpeciesId.HONCHKROW], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); p.generateAndPopulateMoveset(); }), @@ -5374,24 +5379,8 @@ export const trainerConfigs: TrainerConfigs = { p.gender = Gender.MALE; }), ) - .setPartyMemberFunc( - 3, - getRandomPartyMemberFunc([SpeciesId.DRAGALGE, SpeciesId.CLAWITZER], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); - if (p.species.speciesId === SpeciesId.DRAGALGE) { - p.abilityIndex = 2; // Adaptability - } else if (p.species.speciesId === SpeciesId.CLAWITZER) { - p.abilityIndex = 0; // Mega Launcher - } - }), - ) - .setPartyMemberFunc( - 4, - getRandomPartyMemberFunc([SpeciesId.GALLADE], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); - p.abilityIndex = 1; // Sharpness - }), - ) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.MALAMAR])) + .setPartyMemberFunc(4, getRandomPartyMemberFunc([SpeciesId.AEGISLASH, SpeciesId.HISUI_GOODRA])) .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.GYARADOS], TrainerSlot.TRAINER, true, p => { @@ -5416,21 +5405,11 @@ export const trainerConfigs: TrainerConfigs = { p.gender = Gender.MALE; }), ) - .setPartyMemberFunc( - 1, - getRandomPartyMemberFunc([SpeciesId.DRAGALGE, SpeciesId.CLAWITZER], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); - if (p.species.speciesId === SpeciesId.DRAGALGE) { - p.abilityIndex = 2; // Adaptability - } else if (p.species.speciesId === SpeciesId.CLAWITZER) { - p.abilityIndex = 0; // Mega Launcher - } - }), - ) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.MIENSHAO])) .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.AEGISLASH, SpeciesId.HISUI_GOODRA])) .setPartyMemberFunc( 3, - getRandomPartyMemberFunc([SpeciesId.IRON_VALIANT], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.VOLCANION], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ROGUE_BALL; }), @@ -5467,10 +5446,10 @@ export const trainerConfigs: TrainerConfigs = { p.gender = Gender.FEMALE; }), ) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.LILLIGANT, SpeciesId.HISUI_LILLIGANT])) - .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.MILOTIC, SpeciesId.PRIMARINA])) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.LILLIGANT])) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.MILOTIC])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.GALAR_SLOWBRO, SpeciesId.GALAR_SLOWKING])) - .setPartyMemberFunc(4, getRandomPartyMemberFunc([SpeciesId.BEWEAR])) + .setPartyMemberFunc(4, getRandomPartyMemberFunc([SpeciesId.BEWEAR, SpeciesId.LOPUNNY])) .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.NIHILEGO], TrainerSlot.TRAINER, true, p => { @@ -5492,7 +5471,7 @@ export const trainerConfigs: TrainerConfigs = { p.gender = Gender.FEMALE; }), ) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.MILOTIC, SpeciesId.PRIMARINA])) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.MILOTIC, SpeciesId.LILLIGANT])) .setPartyMemberFunc( 2, getRandomPartyMemberFunc([SpeciesId.SILVALLY], TrainerSlot.TRAINER, true, p => { @@ -5572,7 +5551,11 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.GOLISOPOD], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); p.generateAndPopulateMoveset(); - p.gender = Gender.MALE; + if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.FIRST_IMPRESSION)) { + // Check if First Impression is in the moveset, if not, replace the third move with First Impression. + p.moveset[2] = new PokemonMove(MoveId.FIRST_IMPRESSION); + p.gender = Gender.MALE; + } }), ), [TrainerType.GUZMA_2]: new TrainerConfig(++t) @@ -5585,8 +5568,12 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.GOLISOPOD], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); p.generateAndPopulateMoveset(); - p.abilityIndex = 2; // Anticipation - p.gender = Gender.MALE; + if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.FIRST_IMPRESSION)) { + // Check if First Impression is in the moveset, if not, replace the third move with First Impression. + p.moveset[2] = new PokemonMove(MoveId.FIRST_IMPRESSION); + p.abilityIndex = 2; // Anticipation + p.gender = Gender.MALE; + } }), ) .setPartyMemberFunc( @@ -5615,7 +5602,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.GENESECT], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ULTRA_BALL; + p.pokeball = PokeballType.ROGUE_BALL; p.formIndex = randSeedInt(4, 1); // Shock, Burn, Chill, or Douse Drive if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TECHNO_BLAST)) { // Check if Techno Blast is in the moveset, if not, replace the third move with Techno Blast. @@ -5697,13 +5684,7 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.ULTRA_BALL; }), ) - .setPartyMemberFunc( - 1, - getRandomPartyMemberFunc([SpeciesId.AEGISLASH, SpeciesId.GHOLDENGO], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ULTRA_BALL; - }), - ) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.CORVIKNIGHT])) .setPartyMemberFunc( 2, getRandomPartyMemberFunc([SpeciesId.DRACOZOLT, SpeciesId.DRACOVISH], TrainerSlot.TRAINER, true, p => { @@ -5721,6 +5702,17 @@ export const trainerConfigs: TrainerConfigs = { ) .setPartyMemberFunc( 4, + getRandomPartyMemberFunc([SpeciesId.COPPERAJAH], TrainerSlot.TRAINER, true, p => { + p.setBoss(true, 2); + p.generateAndPopulateMoveset(); + p.formIndex = 1; // G-Max Copperajah + p.generateName(); + p.pokeball = PokeballType.ULTRA_BALL; + p.gender = Gender.FEMALE; + }), + ) + .setPartyMemberFunc( + 5, getRandomPartyMemberFunc( [SpeciesId.GALAR_ARTICUNO, SpeciesId.GALAR_ZAPDOS, SpeciesId.GALAR_MOLTRES], TrainerSlot.TRAINER, @@ -5731,33 +5723,27 @@ export const trainerConfigs: TrainerConfigs = { p.pokeball = PokeballType.ULTRA_BALL; }, ), - ) - .setPartyMemberFunc( - 5, - getRandomPartyMemberFunc([SpeciesId.COPPERAJAH], TrainerSlot.TRAINER, true, p => { - p.setBoss(true, 2); - p.generateAndPopulateMoveset(); - p.formIndex = 1; // G-Max Copperajah - p.generateName(); - p.pokeball = PokeballType.ULTRA_BALL; - p.gender = Gender.FEMALE; - }), ), [TrainerType.PENNY]: new TrainerConfig(++t) .setName("Cassiopeia") .initForEvilTeamLeader("Star Boss", []) .setMixedBattleBgm("battle_star_boss") .setVictoryBgm("victory_team_plasma") - .setPartyMemberFunc(0, getRandomPartyMemberFunc([SpeciesId.JOLTEON, SpeciesId.LEAFEON])) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.VAPOREON, SpeciesId.UMBREON])) - .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.ESPEON, SpeciesId.GLACEON])) - .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.FLAREON])) + .setPartyMemberFunc(0, getRandomPartyMemberFunc([SpeciesId.ESPEON])) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.UMBREON])) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.LEAFEON, SpeciesId.GLACEON])) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.VAPOREON, SpeciesId.FLAREON, SpeciesId.JOLTEON])) .setPartyMemberFunc( 4, getRandomPartyMemberFunc([SpeciesId.SYLVEON], TrainerSlot.TRAINER, true, p => { + p.setBoss(true, 2); p.abilityIndex = 2; // Pixilate p.generateAndPopulateMoveset(); - p.gender = Gender.FEMALE; + if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.HYPER_VOICE)) { + // Check if Hyper Voice is in the moveset, if not, replace the second move with Hyper Voice. + p.moveset[1] = new PokemonMove(MoveId.HYPER_VOICE); + p.gender = Gender.FEMALE; + } }), ) .setPartyMemberFunc( @@ -5770,7 +5756,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateName(); }), ) - .setInstantTera(4), // Tera Fairy Sylveon + .setInstantTera(3), // Tera Fairy Sylveon [TrainerType.PENNY_2]: new TrainerConfig(++t) .setName("Cassiopeia") .initForEvilTeamLeader("Star Boss", [], true) @@ -5782,7 +5768,11 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); p.abilityIndex = 2; // Pixilate p.generateAndPopulateMoveset(); - p.gender = Gender.FEMALE; + if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.HYPER_VOICE)) { + // Check if Hyper Voice is in the moveset, if not, replace the second move with Hyper Voice. + p.moveset[1] = new PokemonMove(MoveId.HYPER_VOICE); + p.gender = Gender.FEMALE; + } }), ) .setPartyMemberFunc( @@ -5794,25 +5784,30 @@ export const trainerConfigs: TrainerConfigs = { ) .setPartyMemberFunc( 2, - getRandomPartyMemberFunc([SpeciesId.RAIKOU, SpeciesId.ENTEI, SpeciesId.SUICUNE], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.ESPEON, SpeciesId.UMBREON], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ULTRA_BALL; + p.abilityIndex = p.species.speciesId === SpeciesId.UMBREON ? 0 : 2; // Synchronize Umbreon, Magic Bounce Espeon }), ) .setPartyMemberFunc( 3, + getRandomPartyMemberFunc( + [SpeciesId.WALKING_WAKE, SpeciesId.GOUGING_FIRE, SpeciesId.RAGING_BOLT], + TrainerSlot.TRAINER, + true, + p => { + p.generateAndPopulateMoveset(); + p.pokeball = PokeballType.ROGUE_BALL; + }, + ), + ) + .setPartyMemberFunc( + 4, getRandomPartyMemberFunc([SpeciesId.REVAVROOM], TrainerSlot.TRAINER, true, p => { p.formIndex = randSeedInt(5, 1); // Random Starmobile form p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ROGUE_BALL; - }), - ) - .setPartyMemberFunc( - 4, - getRandomPartyMemberFunc([SpeciesId.ZAMAZENTA], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); - p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.MASTER_BALL; }), ) .setPartyMemberFunc( @@ -5826,6 +5821,7 @@ export const trainerConfigs: TrainerConfigs = { }), ) .setInstantTera(0), // Tera Fairy Sylveon + [TrainerType.BUCK]: new TrainerConfig(++t) .setName("Buck") .initForStatTrainer(true) From 98d957de75408c4c8ffbde71be1db6f50f5ee512 Mon Sep 17 00:00:00 2001 From: Blitzy <118096277+Blitz425@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:58:40 -0500 Subject: [PATCH 09/18] [Balance] Champion Team Adjustments (#6132) * Update Morty * Update trainer-config.ts --------- Co-authored-by: damocleas --- src/data/balance/signature-species.ts | 2 +- src/data/trainers/trainer-config.ts | 396 ++++++++++++++------------ 2 files changed, 219 insertions(+), 179 deletions(-) diff --git a/src/data/balance/signature-species.ts b/src/data/balance/signature-species.ts index fba91f6fe3d..557bcdfed16 100644 --- a/src/data/balance/signature-species.ts +++ b/src/data/balance/signature-species.ts @@ -30,7 +30,7 @@ export const signatureSpecies: SignatureSpecies = new Proxy({ FALKNER: [SpeciesId.PIDGEY, SpeciesId.HOOTHOOT, SpeciesId.NATU, SpeciesId.MURKROW], BUGSY: [SpeciesId.SCYTHER, SpeciesId.SHUCKLE, SpeciesId.YANMA, [SpeciesId.PINSIR, SpeciesId.HERACROSS]], WHITNEY: [SpeciesId.MILTANK, SpeciesId.AIPOM, SpeciesId.IGGLYBUFF, [SpeciesId.GIRAFARIG, SpeciesId.STANTLER]], - MORTY: [SpeciesId.GASTLY, SpeciesId.MISDREAVUS, SpeciesId.DUSKULL, SpeciesId.SABLEYE], + MORTY: [SpeciesId.GASTLY, SpeciesId.MISDREAVUS, SpeciesId.DUSKULL, SpeciesId.HISUI_TYPHLOSION], CHUCK: [SpeciesId.POLIWRATH, SpeciesId.MANKEY, SpeciesId.TYROGUE, SpeciesId.MACHOP], JASMINE: [SpeciesId.STEELIX, SpeciesId.MAGNEMITE, SpeciesId.PINECO, SpeciesId.SKARMORY], PRYCE: [SpeciesId.SWINUB, SpeciesId.SEEL, SpeciesId.SHELLDER, SpeciesId.SNEASEL], diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 6c643737778..7ae157ded96 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -3791,27 +3791,28 @@ export const trainerConfigs: TrainerConfigs = { .setDoubleTrainerType(TrainerType.RED) .setDoubleTitle("champion_double") .setPartyMemberFunc(0, getRandomPartyMemberFunc([SpeciesId.ALAKAZAM])) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.MACHAMP])) .setPartyMemberFunc( - 2, - getRandomPartyMemberFunc([SpeciesId.HO_OH], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.MASTER_BALL; - }), - ) - .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.RHYPERIOR, SpeciesId.ELECTIVIRE])) - .setPartyMemberFunc( - 4, + 1, getRandomPartyMemberFunc( [SpeciesId.ARCANINE, SpeciesId.EXEGGUTOR, SpeciesId.GYARADOS], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - p.setBoss(true, 2); + p.teraType = p.species.type1; }, ), ) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.RHYPERIOR, SpeciesId.ELECTIVIRE])) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.MACHAMP])) + .setPartyMemberFunc( + 4, + getRandomPartyMemberFunc([SpeciesId.HO_OH], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.pokeball = PokeballType.MASTER_BALL; + p.abilityIndex = 2; // Regenerator + }), + ) .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.PIDGEOT], TrainerSlot.TRAINER, true, p => { @@ -3819,9 +3820,10 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.generateName(); p.gender = Gender.MALE; + p.setBoss(true, 2); }), ) - .setInstantTera(3), // Tera Ground or Rock Rhyperior / Electric Electivire / Fire Magmortar + .setInstantTera(2), // Tera Fire Arcanine, Tera Grass Exeggutor, Tera Water Gyarados [TrainerType.RED]: new TrainerConfig(++t) .initForChampion(true) .setBattleBgm("battle_johto_champion") @@ -3832,26 +3834,24 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 0, getRandomPartyMemberFunc([SpeciesId.PIKACHU], TrainerSlot.TRAINER, true, p => { - p.formIndex = 8; // G-Max Pikachu - p.generateAndPopulateMoveset(); - p.generateName(); + p.formIndex = 1; // Partner Pikachu p.gender = Gender.MALE; + p.generateAndPopulateMoveset(); + if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.VOLT_TACKLE)) { + // Check if Volt Tackle is in the moveset, if not, replace the first move with Volt Tackle. + p.moveset[0] = new PokemonMove(MoveId.VOLT_TACKLE); + } }), ) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.ESPEON, SpeciesId.UMBREON, SpeciesId.SYLVEON])) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.MEGANIUM, SpeciesId.TYPHLOSION, SpeciesId.FERALIGATR])) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.ESPEON, SpeciesId.UMBREON, SpeciesId.SYLVEON])) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.SNORLAX])) .setPartyMemberFunc( - 2, + 4, getRandomPartyMemberFunc([SpeciesId.LUGIA], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.MASTER_BALL; - }), - ) - .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.MEGANIUM, SpeciesId.TYPHLOSION, SpeciesId.FERALIGATR])) - .setPartyMemberFunc( - 4, - getRandomPartyMemberFunc([SpeciesId.SNORLAX], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); - p.setBoss(true, 2); + p.abilityIndex = 2; // Multiscale }), ) .setPartyMemberFunc( @@ -3865,10 +3865,11 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.generateName(); p.gender = Gender.MALE; + p.setBoss(true, 2); }, ), ) - .setInstantTera(3), // Tera Grass Meganium / Fire Typhlosion / Water Feraligatr + .setInstantTera(0), // Tera Electric Pikachu [TrainerType.LANCE_CHAMPION]: new TrainerConfig(++t) .setName("Lance") .initForChampion(true) @@ -3876,37 +3877,38 @@ export const trainerConfigs: TrainerConfigs = { .setMixedBattleBgm("battle_johto_champion") .setPartyMemberFunc(0, getRandomPartyMemberFunc([SpeciesId.GYARADOS, SpeciesId.KINGDRA])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.AERODACTYL])) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.CHARIZARD])) .setPartyMemberFunc( - 2, + 3, + getRandomPartyMemberFunc( + [SpeciesId.TYRANITAR, SpeciesId.GARCHOMP, SpeciesId.HYDREIGON], + TrainerSlot.TRAINER, + true, + p => { + p.abilityIndex = 2; // Unnerve Tyranitar, Rough Skin Garchomp, Levitate Hydreigon + p.generateAndPopulateMoveset(); + }, + ), + ) + .setPartyMemberFunc( + 4, getRandomPartyMemberFunc([SpeciesId.SALAMENCE], TrainerSlot.TRAINER, true, p => { p.formIndex = 1; // Mega Salamence p.generateAndPopulateMoveset(); p.generateName(); }), ) - .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.CHARIZARD])) - .setPartyMemberFunc( - 4, - getRandomPartyMemberFunc( - [SpeciesId.TYRANITAR, SpeciesId.GARCHOMP, SpeciesId.KOMMO_O], - TrainerSlot.TRAINER, - true, - p => { - p.teraType = PokemonType.DRAGON; - p.generateAndPopulateMoveset(); - p.abilityIndex = p.species.speciesId === SpeciesId.KOMMO_O ? 1 : 2; // Soundproof Kommo-o, Unnerve Tyranitar, Rough Skin Garchomp - }, - ), - ) .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.DRAGONITE], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); + p.abilityIndex = 2; // Multiscale p.gender = Gender.MALE; p.setBoss(true, 2); + p.teraType = PokemonType.DRAGON; }), ) - .setInstantTera(4), // Tera Dragon Tyranitar / Garchomp / Kommo-o + .setInstantTera(5), // Tera Dragon Dragonite [TrainerType.STEVEN]: new TrainerConfig(++t) .initForChampion(true) .setBattleBgm("battle_hoenn_champion_g5") @@ -3914,16 +3916,22 @@ export const trainerConfigs: TrainerConfigs = { .setHasDouble("steven_wallace_double") .setDoubleTrainerType(TrainerType.WALLACE) .setDoubleTitle("champion_double") - .setPartyMemberFunc(0, getRandomPartyMemberFunc([SpeciesId.SKARMORY])) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.CRADILY, SpeciesId.ARMALDO])) .setPartyMemberFunc( - 2, - getRandomPartyMemberFunc([SpeciesId.AGGRON], TrainerSlot.TRAINER, true, p => { + 0, + getRandomPartyMemberFunc([SpeciesId.GIGALITH], TrainerSlot.TRAINER, true, p => { + p.abilityIndex = 1; // Sand Stream + p.generateAndPopulateMoveset(); + }), + ) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.SKARMORY, SpeciesId.CLAYDOL])) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.AGGRON])) + .setPartyMemberFunc( + 3, + getRandomPartyMemberFunc([SpeciesId.GOLURK, SpeciesId.RUNERIGUS], TrainerSlot.TRAINER, true, p => { + p.abilityIndex = 0; // Iron Fist Golurk, Wandering Spirit Runerigus p.generateAndPopulateMoveset(); - p.setBoss(true, 2); }), ) - .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.GOLURK, SpeciesId.RUNERIGUS])) .setPartyMemberFunc( 4, getRandomPartyMemberFunc( @@ -3942,6 +3950,7 @@ export const trainerConfigs: TrainerConfigs = { p.formIndex = 1; // Mega Metagross p.generateAndPopulateMoveset(); p.generateName(); + p.setBoss(true, 2); }), ) .setInstantTera(4), // Tera Rock Regirock / Ice Regice / Steel Registeel @@ -3959,22 +3968,34 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); }), ) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.LUDICOLO])) .setPartyMemberFunc( - 2, - getRandomPartyMemberFunc([SpeciesId.LATIAS, SpeciesId.LATIOS], TrainerSlot.TRAINER, true, p => { - p.formIndex = 1; // Mega Latios or Mega Latias + 1, + getRandomPartyMemberFunc([SpeciesId.LUDICOLO], TrainerSlot.TRAINER, true, p => { + p.abilityIndex = 0; // Swift Swim p.generateAndPopulateMoveset(); - p.generateName(); - p.pokeball = PokeballType.MASTER_BALL; }), ) - .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.SWAMPERT, SpeciesId.GASTRODON, SpeciesId.SEISMITOAD])) + .setPartyMemberFunc( + 2, + getRandomPartyMemberFunc([SpeciesId.TENTACRUEL, SpeciesId.WALREIN], TrainerSlot.TRAINER, true, p => { + p.abilityIndex = p.species.speciesId === SpeciesId.TENTACRUEL ? 2 : 0; // Rain Dish Tentacruel, Thick Fat Walrein + p.generateAndPopulateMoveset(); + }), + ) + .setPartyMemberFunc( + 3, + getRandomPartyMemberFunc([SpeciesId.LATIAS, SpeciesId.LATIOS], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.generateName(); + p.pokeball = PokeballType.ULTRA_BALL; + }), + ) .setPartyMemberFunc( 4, - getRandomPartyMemberFunc([SpeciesId.REGIELEKI, SpeciesId.REGIDRAGO], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.SWAMPERT], TrainerSlot.TRAINER, true, p => { + p.formIndex = 1; // Mega Swampert p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ULTRA_BALL; + p.generateName(); }), ) .setPartyMemberFunc( @@ -3985,22 +4006,14 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); }), ) - .setInstantTera(4), // Tera Electric Regieleki / Dragon Regidrago + .setInstantTera(5), // Tera Water Milotic [TrainerType.CYNTHIA]: new TrainerConfig(++t) .initForChampion(false) .setBattleBgm("battle_sinnoh_champion") .setMixedBattleBgm("battle_sinnoh_champion") .setPartyMemberFunc(0, getRandomPartyMemberFunc([SpeciesId.SPIRITOMB])) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.LUCARIO])) .setPartyMemberFunc( - 2, - getRandomPartyMemberFunc([SpeciesId.GIRATINA], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.MASTER_BALL; - }), - ) - .setPartyMemberFunc( - 3, + 1, getRandomPartyMemberFunc( [SpeciesId.MILOTIC, SpeciesId.ROSERADE, SpeciesId.HISUI_ARCANINE], TrainerSlot.TRAINER, @@ -4011,11 +4024,13 @@ export const trainerConfigs: TrainerConfigs = { }, ), ) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.TOGEKISS])) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.LUCARIO])) .setPartyMemberFunc( 4, - getRandomPartyMemberFunc([SpeciesId.TOGEKISS], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.GIRATINA], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - p.setBoss(true, 2); + p.pokeball = PokeballType.MASTER_BALL; }), ) .setPartyMemberFunc( @@ -4025,9 +4040,10 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.generateName(); p.gender = Gender.FEMALE; + p.setBoss(true, 2); }), ) - .setInstantTera(3), // Tera Water Milotic / Grass Roserade / Fire Hisuian Arcanine + .setInstantTera(1), // Tera Water Milotic / Grass Roserade / Fire Hisuian Arcanine [TrainerType.ALDER]: new TrainerConfig(++t) .initForChampion(true) .setHasDouble("alder_iris_double") @@ -4050,29 +4066,26 @@ export const trainerConfigs: TrainerConfigs = { ) .setPartyMemberFunc( 2, - getRandomPartyMemberFunc([SpeciesId.ZEKROM], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.MASTER_BALL; - }), + getRandomPartyMemberFunc([SpeciesId.CHANDELURE, SpeciesId.KROOKODILE, SpeciesId.REUNICLUS, SpeciesId.CONKELDURR]), ) .setPartyMemberFunc( 3, getRandomPartyMemberFunc([SpeciesId.KELDEO], TrainerSlot.TRAINER, true, p => { + p.pokeball = PokeballType.ROGUE_BALL; p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ULTRA_BALL; + if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.SECRET_SWORD)) { + // Check if Secret Sword is in the moveset, if not, replace the third move with Secret Sword. + p.moveset[2] = new PokemonMove(MoveId.SECRET_SWORD); + } + p.formIndex = 1; // Resolute Form }), ) .setPartyMemberFunc( 4, - getRandomPartyMemberFunc( - [SpeciesId.CHANDELURE, SpeciesId.KROOKODILE, SpeciesId.REUNICLUS, SpeciesId.CONKELDURR], - TrainerSlot.TRAINER, - true, - p => { - p.generateAndPopulateMoveset(); - p.teraType = p.species.speciesId === SpeciesId.KROOKODILE ? PokemonType.DARK : p.species.type1; - }, - ), + getRandomPartyMemberFunc([SpeciesId.ZEKROM], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.pokeball = PokeballType.MASTER_BALL; + }), ) .setPartyMemberFunc( 5, @@ -4080,9 +4093,10 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.gender = Gender.MALE; p.setBoss(true, 2); + p.teraType = PokemonType.FIRE; }), ) - .setInstantTera(4), // Tera Ghost Chandelure / Dark Krookodile / Psychic Reuniclus / Fighting Conkeldurr + .setInstantTera(5), // Tera Fire Volcarona [TrainerType.IRIS]: new TrainerConfig(++t) .initForChampion(false) .setBattleBgm("battle_champion_iris") @@ -4091,34 +4105,29 @@ export const trainerConfigs: TrainerConfigs = { .setDoubleTrainerType(TrainerType.ALDER) .setDoubleTitle("champion_double") .setPartyMemberFunc(0, getRandomPartyMemberFunc([SpeciesId.DRUDDIGON])) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.ARCHEOPS])) .setPartyMemberFunc( - 2, - getRandomPartyMemberFunc([SpeciesId.RESHIRAM], TrainerSlot.TRAINER, true, p => { + 1, + getRandomPartyMemberFunc([SpeciesId.ARCHEOPS], TrainerSlot.TRAINER, true, p => { + p.abilityIndex = 2; // Emergency Exit p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.MASTER_BALL; }), ) .setPartyMemberFunc( - 3, - getRandomPartyMemberFunc( - [SpeciesId.SALAMENCE, SpeciesId.HYDREIGON, SpeciesId.ARCHALUDON], - TrainerSlot.TRAINER, - true, - p => { - p.generateAndPopulateMoveset(); - p.teraType = PokemonType.DRAGON; - }, - ), - ) - .setPartyMemberFunc( - 4, + 2, getRandomPartyMemberFunc([SpeciesId.LAPRAS], TrainerSlot.TRAINER, true, p => { p.formIndex = 1; // G-Max Lapras p.generateAndPopulateMoveset(); p.generateName(); }), ) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.AGGRON, SpeciesId.HYDREIGON, SpeciesId.ARCHALUDON])) + .setPartyMemberFunc( + 4, + getRandomPartyMemberFunc([SpeciesId.RESHIRAM], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.pokeball = PokeballType.MASTER_BALL; + }), + ) .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.HAXORUS], TrainerSlot.TRAINER, true, p => { @@ -4128,37 +4137,32 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); }), ) - .setInstantTera(3), // Tera Dragon Salamence / Hydreigon / Archaludon + .setInstantTera(5), // Tera Dragon Haxorus [TrainerType.DIANTHA]: new TrainerConfig(++t) .initForChampion(false) .setMixedBattleBgm("battle_kalos_champion") + .setPartyMemberFunc(0, getRandomPartyMemberFunc([SpeciesId.HAWLUCHA])) .setPartyMemberFunc( - 0, - getRandomPartyMemberFunc([SpeciesId.HAWLUCHA], TrainerSlot.TRAINER, true, p => { + 1, + getRandomPartyMemberFunc([SpeciesId.TREVENANT, SpeciesId.GOURGEIST], TrainerSlot.TRAINER, true, p => { + p.abilityIndex = 2; // Harvest Trevenant, Insomnia Gourgeist p.generateAndPopulateMoveset(); }), ) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.TREVENANT, SpeciesId.GOURGEIST])) .setPartyMemberFunc( 2, - getRandomPartyMemberFunc([SpeciesId.XERNEAS], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.MASTER_BALL; - }), - ) - .setPartyMemberFunc( - 3, getRandomPartyMemberFunc([SpeciesId.TYRANTRUM, SpeciesId.AURORUS], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.abilityIndex = 2; // Rock Head Tyrantrum, Snow Warning Aurorus p.teraType = p.species.type2!; }), ) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.GOODRA])) .setPartyMemberFunc( 4, - getRandomPartyMemberFunc([SpeciesId.GOODRA], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.XERNEAS], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - p.setBoss(true, 2); + p.pokeball = PokeballType.MASTER_BALL; }), ) .setPartyMemberFunc( @@ -4168,9 +4172,10 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.generateName(); p.gender = Gender.FEMALE; + p.setBoss(true, 2); }), ) - .setInstantTera(3), // Tera Dragon Tyrantrum / Ice Aurorus + .setInstantTera(2), // Tera Dragon Tyrantrum / Ice Aurorus [TrainerType.KUKUI]: new TrainerConfig(++t) .initForChampion(true) .setMixedBattleBgm("battle_champion_kukui") @@ -4181,7 +4186,13 @@ export const trainerConfigs: TrainerConfigs = { p.formIndex = 2; // Dusk Lycanroc }), ) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.MAGNEZONE, SpeciesId.ALOLA_NINETALES])) + .setPartyMemberFunc( + 1, + getRandomPartyMemberFunc([SpeciesId.MAGNEZONE, SpeciesId.ALOLA_NINETALES], TrainerSlot.TRAINER, true, p => { + p.generateAndPopulateMoveset(); + p.abilityIndex = p.species.speciesId === SpeciesId.MAGNEZONE ? 1 : 2; // Sturdy Magnezone, Snow Warning Ninetales + }), + ) .setPartyMemberFunc( 2, getRandomPartyMemberFunc( @@ -4191,16 +4202,16 @@ export const trainerConfigs: TrainerConfigs = { p => { p.formIndex = 1; // Therian Formes p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ULTRA_BALL; + p.pokeball = PokeballType.ROGUE_BALL; }, ), ) .setPartyMemberFunc( 3, - getRandomPartyMemberFunc([SpeciesId.TAPU_KOKO, SpeciesId.TAPU_FINI], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.TAPU_LELE, SpeciesId.TAPU_FINI], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - p.setBoss(true, 2); p.pokeball = PokeballType.ULTRA_BALL; + p.abilityIndex = 0; // Psychic / Misty Surge }), ) .setPartyMemberFunc( @@ -4216,6 +4227,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.gender = Gender.MALE; p.teraType = p.species.type2!; + p.setBoss(true, 2); }), ) .setInstantTera(5), // Tera Dark Incineroar / Fighting Hisuian Decidueye @@ -4223,28 +4235,33 @@ export const trainerConfigs: TrainerConfigs = { .initForChampion(true) .setMixedBattleBgm("battle_alola_champion") .setPartyMemberFunc(0, getRandomPartyMemberFunc([SpeciesId.ALOLA_RAICHU])) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.NOIVERN])) + .setPartyMemberFunc( + 1, + getRandomPartyMemberFunc([SpeciesId.NOIVERN], TrainerSlot.TRAINER, true, p => { + p.abilityIndex = 1; // Infiltrator + p.generateAndPopulateMoveset(); + }), + ) .setPartyMemberFunc( 2, - getRandomPartyMemberFunc([SpeciesId.SOLGALEO], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.BLACEPHALON, SpeciesId.STAKATAKA], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.MASTER_BALL; + p.pokeball = PokeballType.ROGUE_BALL; }), ) .setPartyMemberFunc( 3, - getRandomPartyMemberFunc([SpeciesId.TAPU_LELE, SpeciesId.TAPU_BULU], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.TAPU_KOKO, SpeciesId.TAPU_BULU], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; - p.teraType = p.species.type1; + p.abilityIndex = 0; // Electric / Grassy Surge }), ) .setPartyMemberFunc( 4, - getRandomPartyMemberFunc([SpeciesId.ZYGARDE], TrainerSlot.TRAINER, true, p => { - p.formIndex = 1; // Zygarde 10% forme, Aura Break + getRandomPartyMemberFunc([SpeciesId.SOLGALEO], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ROGUE_BALL; + p.pokeball = PokeballType.MASTER_BALL; }), ) .setPartyMemberFunc( @@ -4253,34 +4270,35 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.setBoss(true, 2); p.gender = p.species.speciesId === SpeciesId.PRIMARINA ? Gender.FEMALE : Gender.MALE; + p.teraType = p.species.speciesId === SpeciesId.PRIMARINA ? PokemonType.WATER : PokemonType.GHOST; }), ) - .setInstantTera(3), // Tera Psychic Tapu Lele / Grass Tapu Bulu + .setInstantTera(5), // Tera Ghost Decidueye, Water Primarina [TrainerType.LEON]: new TrainerConfig(++t) .initForChampion(true) .setMixedBattleBgm("battle_galar_champion") .setPartyMemberFunc(0, getRandomPartyMemberFunc([SpeciesId.AEGISLASH])) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.RHYPERIOR, SpeciesId.SEISMITOAD, SpeciesId.MR_RIME])) .setPartyMemberFunc( - 2, + 1, + getRandomPartyMemberFunc( + [SpeciesId.RHYPERIOR, SpeciesId.SEISMITOAD, SpeciesId.MR_RIME], + TrainerSlot.TRAINER, + true, + p => { + p.abilityIndex = 1; // Solid Rock Rhyperior, Poison Touch Seismitoad, Screen Cleaner Mr. Rime + p.generateAndPopulateMoveset(); + }, + ), + ) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.DRAGAPULT])) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.RILLABOOM, SpeciesId.CINDERACE, SpeciesId.INTELEON])) + .setPartyMemberFunc( + 4, getRandomPartyMemberFunc([SpeciesId.ZACIAN], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.MASTER_BALL; }), ) - .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.DRAGAPULT])) - .setPartyMemberFunc( - 4, - getRandomPartyMemberFunc( - [SpeciesId.RILLABOOM, SpeciesId.CINDERACE, SpeciesId.INTELEON], - TrainerSlot.TRAINER, - true, - p => { - p.generateAndPopulateMoveset(); - p.setBoss(true, 2); - }, - ), - ) .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.CHARIZARD], TrainerSlot.TRAINER, true, p => { @@ -4288,22 +4306,23 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.generateName(); p.gender = Gender.MALE; + p.setBoss(true, 2); }), ) - .setInstantTera(3), // Tera Dragapult to Ghost or Dragon + .setInstantTera(3), // Tera Grass Rillaboom, Fire Cinderace, Water Inteleon [TrainerType.MUSTARD]: new TrainerConfig(++t) .initForChampion(true) .setMixedBattleBgm("battle_mustard") .setPartyMemberFunc( 0, - getRandomPartyMemberFunc([SpeciesId.CORVIKNIGHT], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.MIENSHAO], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; }), ) .setPartyMemberFunc( 1, - getRandomPartyMemberFunc([SpeciesId.KOMMO_O], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.CORVIKNIGHT], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; }), @@ -4312,36 +4331,46 @@ export const trainerConfigs: TrainerConfigs = { 2, getRandomPartyMemberFunc([SpeciesId.GALAR_SLOWBRO, SpeciesId.GALAR_SLOWKING], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); + p.abilityIndex = p.species.speciesId === SpeciesId.GALAR_SLOWBRO ? 0 : 2; // Quick Draw Galar Slowbro, Regenerator Galar Slowking p.pokeball = PokeballType.ULTRA_BALL; - p.teraType = p.species.type1; }), ) .setPartyMemberFunc( 3, - getRandomPartyMemberFunc([SpeciesId.GALAR_DARMANITAN], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); + getRandomPartyMemberFunc([SpeciesId.VENUSAUR, SpeciesId.BLASTOISE], TrainerSlot.TRAINER, true, p => { p.pokeball = PokeballType.ULTRA_BALL; }), ) .setPartyMemberFunc( 4, - getRandomPartyMemberFunc([SpeciesId.BLASTOISE, SpeciesId.VENUSAUR], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.KOMMO_O], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - p.setBoss(true, 2); p.pokeball = PokeballType.ULTRA_BALL; + p.generateAndPopulateMoveset(); }), ) .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.URSHIFU], TrainerSlot.TRAINER, true, p => { - p.formIndex = randSeedIntRange(2, 3); // Random G-Max Urshifu - p.generateAndPopulateMoveset(); + p.formIndex = randSeedIntRange(2, 3); // Random G-Max Urshifu form p.generateName(); p.gender = Gender.MALE; p.pokeball = PokeballType.ULTRA_BALL; + p.setBoss(true, 2); + if (p.formIndex === 2) { + p.moveset[0] = new PokemonMove(MoveId.WICKED_BLOW); + p.moveset[1] = new PokemonMove(MoveId.BRICK_BREAK); + p.moveset[2] = new PokemonMove(randSeedItem([MoveId.FIRE_PUNCH, MoveId.THUNDER_PUNCH, MoveId.ICE_PUNCH])); + p.moveset[3] = new PokemonMove(MoveId.FOCUS_ENERGY); + } else if (p.formIndex === 3) { + p.moveset[0] = new PokemonMove(MoveId.SURGING_STRIKES); + p.moveset[1] = new PokemonMove(MoveId.BRICK_BREAK); + p.moveset[2] = new PokemonMove(randSeedItem([MoveId.FIRE_PUNCH, MoveId.THUNDER_PUNCH, MoveId.ICE_PUNCH])); + p.moveset[3] = new PokemonMove(MoveId.FOCUS_ENERGY); + } }), ) - .setInstantTera(2), // Tera Poison Galar-Slowbro / Galar-Slowking + .setInstantTera(4), // Tera Fighting Kommo-o [TrainerType.GEETA]: new TrainerConfig(++t) .initForChampion(false) .setMixedBattleBgm("battle_champion_geeta") @@ -4353,16 +4382,22 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(true, 2); }), ) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.ESPATHRA, SpeciesId.VELUZA])) .setPartyMemberFunc( - 2, + 1, + getRandomPartyMemberFunc([SpeciesId.ESPATHRA], TrainerSlot.TRAINER, true, p => { + p.abilityIndex = 0; // Opportunist + p.generateAndPopulateMoveset(); + }), + ) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.BAXCALIBUR])) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.CHESNAUGHT, SpeciesId.DELPHOX, SpeciesId.GRENINJA])) + .setPartyMemberFunc( + 4, getRandomPartyMemberFunc([SpeciesId.MIRAIDON], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.MASTER_BALL; }), ) - .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.BAXCALIBUR])) - .setPartyMemberFunc(4, getRandomPartyMemberFunc([SpeciesId.CHESNAUGHT, SpeciesId.DELPHOX, SpeciesId.GRENINJA])) .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.KINGAMBIT], TrainerSlot.TRAINER, true, p => { @@ -4389,19 +4424,19 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.PAWMOT])) .setPartyMemberFunc( 2, + getRandomPartyMemberFunc([SpeciesId.DUDUNSPARCE], TrainerSlot.TRAINER, true, p => { + p.abilityIndex = 0; // Serene Grace + p.generateAndPopulateMoveset(); + }), + ) + .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.ARMAROUGE, SpeciesId.CERULEDGE])) + .setPartyMemberFunc( + 4, getRandomPartyMemberFunc([SpeciesId.KORAIDON], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); p.pokeball = PokeballType.MASTER_BALL; }), ) - .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.GHOLDENGO])) - .setPartyMemberFunc( - 4, - getRandomPartyMemberFunc([SpeciesId.ARMAROUGE, SpeciesId.CERULEDGE], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); - p.teraType = p.species.type2!; - }), - ) .setPartyMemberFunc( 5, getRandomPartyMemberFunc( @@ -4412,10 +4447,11 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); p.gender = Gender.MALE; p.setBoss(true, 2); + p.teraType = p.species.type2!; }, ), ) - .setInstantTera(4), // Tera Psychic Armarouge / Ghost Ceruledge + .setInstantTera(5), // Tera Dark Meowscarada, Ghost Skeledirge, Fighting Quaquaval [TrainerType.KIERAN]: new TrainerConfig(++t) .initForChampion(true) .setMixedBattleBgm("battle_champion_kieran") @@ -4429,9 +4465,9 @@ export const trainerConfigs: TrainerConfigs = { ) .setPartyMemberFunc( 2, - getRandomPartyMemberFunc([SpeciesId.TERAPAGOS], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.DRAGONITE], TrainerSlot.TRAINER, true, p => { + p.abilityIndex = 2; // Multiscale p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.MASTER_BALL; }), ) .setPartyMemberFunc( @@ -4443,25 +4479,29 @@ export const trainerConfigs: TrainerConfigs = { ) .setPartyMemberFunc( 4, - getRandomPartyMemberFunc([SpeciesId.OGERPON], TrainerSlot.TRAINER, true, p => { - p.formIndex = randSeedInt(4); // Random Ogerpon Tera Mask + getRandomPartyMemberFunc([SpeciesId.TERAPAGOS], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ULTRA_BALL; - if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.IVY_CUDGEL)) { - // Check if Ivy Cudgel is in the moveset, if not, replace the first move with Ivy Cudgel. - p.moveset[0] = new PokemonMove(MoveId.IVY_CUDGEL); + if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TERA_STARSTORM)) { + // Check if Tera Starstorm is in the moveset, if not, replace the first move with Tera Starstorm. + p.moveset[0] = new PokemonMove(MoveId.TERA_STARSTORM); } + p.pokeball = PokeballType.MASTER_BALL; }), ) .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.HYDRAPPLE], TrainerSlot.TRAINER, true, p => { - p.generateAndPopulateMoveset(); p.gender = Gender.MALE; p.setBoss(true, 2); + p.teraType = PokemonType.FIGHTING; + p.generateAndPopulateMoveset(); + if (!p.moveset.some(move => !isNullOrUndefined(move) && move.moveId === MoveId.TERA_BLAST)) { + // Check if Tera Blast is in the moveset, if not, replace the third move with Tera Blast. + p.moveset[2] = new PokemonMove(MoveId.TERA_BLAST); + } }), ) - .setInstantTera(4), // Tera Ogerpon + .setInstantTera(5), // Tera Fighting Hydrapple [TrainerType.RIVAL]: new TrainerConfig((t = TrainerType.RIVAL)) .setName("Finn") From a289206a96aae81ef07c7cea50afb2eafebc03d7 Mon Sep 17 00:00:00 2001 From: damocleas Date: Thu, 7 Aug 2025 23:33:16 -0400 Subject: [PATCH 10/18] [Balance] More Passive/Egg Move/Cost changes for 1.10 (#6234) * Update passives.ts * Update egg-moves.ts * Update starters.ts --- src/data/balance/egg-moves.ts | 10 +++++----- src/data/balance/passives.ts | 20 ++++++++++---------- src/data/balance/starters.ts | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/data/balance/egg-moves.ts b/src/data/balance/egg-moves.ts index 3475fe4fdea..e936afcdc08 100644 --- a/src/data/balance/egg-moves.ts +++ b/src/data/balance/egg-moves.ts @@ -15,7 +15,7 @@ export const speciesEggMoves = { [SpeciesId.SPEAROW]: [ MoveId.FLOATY_FALL, MoveId.EXTREME_SPEED, MoveId.KNOCK_OFF, MoveId.TRIPLE_ARROWS ], [SpeciesId.EKANS]: [ MoveId.NOXIOUS_TORQUE, MoveId.DRAGON_DANCE, MoveId.SLACK_OFF, MoveId.SHED_TAIL ], [SpeciesId.SANDSHREW]: [ MoveId.HIGH_HORSEPOWER, MoveId.DIRE_CLAW, MoveId.SHORE_UP, MoveId.MIGHTY_CLEAVE ], - [SpeciesId.NIDORAN_F]: [ MoveId.CALM_MIND, MoveId.MOONLIGHT, MoveId.MALIGNANT_CHAIN, MoveId.SANDSEAR_STORM ], + [SpeciesId.NIDORAN_F]: [ MoveId.BANEFUL_BUNKER, MoveId.MOONLIGHT, MoveId.BARB_BARRAGE, MoveId.THOUSAND_WAVES ], [SpeciesId.NIDORAN_M]: [ MoveId.DRAGON_DANCE, MoveId.MOUNTAIN_GALE, MoveId.NOXIOUS_TORQUE, MoveId.PRECIPICE_BLADES ], [SpeciesId.VULPIX]: [ MoveId.MOONBLAST, MoveId.INFERNAL_PARADE, MoveId.MORNING_SUN, MoveId.TAIL_GLOW ], [SpeciesId.ZUBAT]: [ MoveId.FLOATY_FALL, MoveId.DIRE_CLAW, MoveId.SWORDS_DANCE, MoveId.COLLISION_COURSE ], @@ -293,7 +293,7 @@ export const speciesEggMoves = { [SpeciesId.ARCHEN]: [ MoveId.ROOST, MoveId.EARTHQUAKE, MoveId.FLOATY_FALL, MoveId.MIGHTY_CLEAVE ], [SpeciesId.TRUBBISH]: [ MoveId.COIL, MoveId.RECOVER, MoveId.DIRE_CLAW, MoveId.GIGATON_HAMMER ], [SpeciesId.ZORUA]: [ MoveId.MALIGNANT_CHAIN, MoveId.MOONBLAST, MoveId.SECRET_SWORD, MoveId.FIERY_WRATH ], - [SpeciesId.MINCCINO]: [ MoveId.ICICLE_SPEAR, MoveId.TIDY_UP, MoveId.KNOCK_OFF, MoveId.POPULATION_BOMB ], + [SpeciesId.MINCCINO]: [ MoveId.ICICLE_SPEAR, MoveId.TIDY_UP, MoveId.LOW_KICK, MoveId.POPULATION_BOMB ], [SpeciesId.GOTHITA]: [ MoveId.RECOVER, MoveId.MOONBLAST, MoveId.AURA_SPHERE, MoveId.LUMINA_CRASH ], [SpeciesId.SOLOSIS]: [ MoveId.MIST_BALL, MoveId.SPEED_SWAP, MoveId.FLAMETHROWER, MoveId.LIGHT_OF_RUIN ], [SpeciesId.DUCKLETT]: [ MoveId.SPLISHY_SPLASH, MoveId.SANDSEAR_STORM, MoveId.WILDBOLT_STORM, MoveId.QUIVER_DANCE ], @@ -310,7 +310,7 @@ export const speciesEggMoves = { [SpeciesId.TYNAMO]: [ MoveId.SCALD, MoveId.STRENGTH_SAP, MoveId.FIRE_LASH, MoveId.AURA_WHEEL ], [SpeciesId.ELGYEM]: [ MoveId.THUNDERCLAP, MoveId.BADDY_BAD, MoveId.AURA_SPHERE, MoveId.PHOTON_GEYSER ], [SpeciesId.LITWICK]: [ MoveId.GIGA_DRAIN, MoveId.EARTH_POWER, MoveId.MOONBLAST, MoveId.TORCH_SONG ], - [SpeciesId.AXEW]: [ MoveId.STONE_AXE, MoveId.DIRE_CLAW, MoveId.BITTER_BLADE, MoveId.GLAIVE_RUSH ], + [SpeciesId.AXEW]: [ MoveId.STONE_AXE, MoveId.DIRE_CLAW, MoveId.RAGING_FURY, MoveId.BITTER_BLADE ], [SpeciesId.CUBCHOO]: [ MoveId.MOUNTAIN_GALE, MoveId.AQUA_STEP, MoveId.ICE_SHARD, MoveId.COLLISION_COURSE ], [SpeciesId.CRYOGONAL]: [ MoveId.FREEZING_GLARE, MoveId.AURORA_VEIL, MoveId.NASTY_PLOT, MoveId.ORIGIN_PULSE ], [SpeciesId.SHELMET]: [ MoveId.POWER_GEM, MoveId.NASTY_PLOT, MoveId.EARTH_POWER, MoveId.STEAM_ERUPTION ], @@ -448,7 +448,7 @@ export const speciesEggMoves = { [SpeciesId.ROOKIDEE]: [ MoveId.ROOST, MoveId.BODY_PRESS, MoveId.KINGS_SHIELD, MoveId.BEHEMOTH_BASH ], [SpeciesId.BLIPBUG]: [ MoveId.HEAL_ORDER, MoveId.LUSTER_PURGE, MoveId.SLEEP_POWDER, MoveId.TAIL_GLOW ], [SpeciesId.NICKIT]: [ MoveId.BADDY_BAD, MoveId.MYSTICAL_FIRE, MoveId.SPARKLY_SWIRL, MoveId.MAKE_IT_RAIN ], - [SpeciesId.GOSSIFLEUR]: [ MoveId.PARTING_SHOT, MoveId.STRENGTH_SAP, MoveId.SAPPY_SEED, MoveId.SEED_FLARE ], + [SpeciesId.GOSSIFLEUR]: [ MoveId.BATON_PASS, MoveId.TAILWIND, MoveId.SAPPY_SEED, MoveId.SPORE ], [SpeciesId.WOOLOO]: [ MoveId.NUZZLE, MoveId.MILK_DRINK, MoveId.BODY_PRESS, MoveId.MULTI_ATTACK ], [SpeciesId.CHEWTLE]: [ MoveId.ICE_FANG, MoveId.PSYCHIC_FANGS, MoveId.SHELL_SMASH, MoveId.MIGHTY_CLEAVE ], [SpeciesId.YAMPER]: [ MoveId.ICE_FANG, MoveId.SWORDS_DANCE, MoveId.THUNDER_FANG, MoveId.BOLT_STRIKE ], @@ -514,7 +514,7 @@ export const speciesEggMoves = { [SpeciesId.TAROUNTULA]: [ MoveId.STONE_AXE, MoveId.LEECH_LIFE, MoveId.FAKE_OUT, MoveId.SPORE ], [SpeciesId.NYMBLE]: [ MoveId.KNOCK_OFF, MoveId.FELL_STINGER, MoveId.ATTACK_ORDER, MoveId.WICKED_BLOW ], [SpeciesId.PAWMI]: [ MoveId.DRAIN_PUNCH, MoveId.METEOR_MASH, MoveId.JET_PUNCH, MoveId.PLASMA_FISTS ], - [SpeciesId.TANDEMAUS]: [ MoveId.BATON_PASS, MoveId.COVET, MoveId.SIZZLY_SLIDE, MoveId.REVIVAL_BLESSING ], + [SpeciesId.TANDEMAUS]: [ MoveId.BATON_PASS, MoveId.FAKE_OUT, MoveId.POWER_UP_PUNCH, MoveId.REVIVAL_BLESSING ], [SpeciesId.FIDOUGH]: [ MoveId.SOFT_BOILED, MoveId.HIGH_HORSEPOWER, MoveId.SIZZLY_SLIDE, MoveId.TIDY_UP ], [SpeciesId.SMOLIV]: [ MoveId.STRENGTH_SAP, MoveId.EARTH_POWER, MoveId.CALM_MIND, MoveId.BOOMBURST ], [SpeciesId.SQUAWKABILLY]: [ MoveId.PARTING_SHOT, MoveId.EARTHQUAKE, MoveId.FLARE_BLITZ, MoveId.EXTREME_SPEED ], diff --git a/src/data/balance/passives.ts b/src/data/balance/passives.ts index c0f84d90cf0..64fa9b87138 100644 --- a/src/data/balance/passives.ts +++ b/src/data/balance/passives.ts @@ -36,9 +36,9 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [SpeciesId.ARBOK]: { 0: AbilityId.REGENERATOR }, [SpeciesId.SANDSHREW]: { 0: AbilityId.TOUGH_CLAWS }, [SpeciesId.SANDSLASH]: { 0: AbilityId.TOUGH_CLAWS }, - [SpeciesId.NIDORAN_F]: { 0: AbilityId.FLARE_BOOST }, - [SpeciesId.NIDORINA]: { 0: AbilityId.FLARE_BOOST }, - [SpeciesId.NIDOQUEEN]: { 0: AbilityId.FLARE_BOOST }, + [SpeciesId.NIDORAN_F]: { 0: AbilityId.TOXIC_DEBRIS }, + [SpeciesId.NIDORINA]: { 0: AbilityId.TOXIC_DEBRIS }, + [SpeciesId.NIDOQUEEN]: { 0: AbilityId.TOXIC_DEBRIS }, [SpeciesId.NIDORAN_M]: { 0: AbilityId.GUTS }, [SpeciesId.NIDORINO]: { 0: AbilityId.GUTS }, [SpeciesId.NIDOKING]: { 0: AbilityId.GUTS }, @@ -220,8 +220,8 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [SpeciesId.YANMEGA]: { 0: AbilityId.SHEER_FORCE }, [SpeciesId.WOOPER]: { 0: AbilityId.WATER_VEIL }, [SpeciesId.QUAGSIRE]: { 0: AbilityId.COMATOSE }, - [SpeciesId.MURKROW]: { 0: AbilityId.DARK_AURA }, - [SpeciesId.HONCHKROW]: { 0: AbilityId.DARK_AURA }, + [SpeciesId.MURKROW]: { 0: AbilityId.UNNERVE }, + [SpeciesId.HONCHKROW]: { 0: AbilityId.INTIMIDATE }, [SpeciesId.MISDREAVUS]: { 0: AbilityId.BEADS_OF_RUIN }, [SpeciesId.MISMAGIUS]: { 0: AbilityId.BEADS_OF_RUIN }, [SpeciesId.UNOWN]: { 0: AbilityId.ADAPTABILITY, 1: AbilityId.BEAST_BOOST, 2: AbilityId.CONTRARY, 3: AbilityId.DAZZLING, 4: AbilityId.EMERGENCY_EXIT, 5: AbilityId.FRIEND_GUARD, 6: AbilityId.GOOD_AS_GOLD, 7: AbilityId.HONEY_GATHER, 8: AbilityId.IMPOSTER, 9: AbilityId.JUSTIFIED, 10: AbilityId.KLUTZ, 11: AbilityId.LIBERO, 12: AbilityId.MOODY, 13: AbilityId.NEUTRALIZING_GAS, 14: AbilityId.OPPORTUNIST, 15: AbilityId.PICKUP, 16: AbilityId.QUICK_DRAW, 17: AbilityId.RUN_AWAY, 18: AbilityId.SIMPLE, 19: AbilityId.TRACE, 20: AbilityId.UNNERVE, 21: AbilityId.VICTORY_STAR, 22: AbilityId.WANDERING_SPIRIT, 23: AbilityId.FAIRY_AURA, 24: AbilityId.DARK_AURA, 25: AbilityId.AURA_BREAK, 26: AbilityId.PURE_POWER, 27: AbilityId.UNAWARE }, @@ -391,7 +391,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [SpeciesId.BANETTE]: { 0: AbilityId.SHADOW_SHIELD, 1: AbilityId.SHADOW_SHIELD }, [SpeciesId.DUSKULL]: { 0: AbilityId.UNNERVE }, [SpeciesId.DUSCLOPS]: { 0: AbilityId.UNNERVE }, - [SpeciesId.DUSKNOIR]: { 0: AbilityId.UNNERVE }, + [SpeciesId.DUSKNOIR]: { 0: AbilityId.LEVITATE }, [SpeciesId.TROPIUS]: { 0: AbilityId.RIPEN }, [SpeciesId.ABSOL]: { 0: AbilityId.SHARPNESS, 1: AbilityId.SHARPNESS }, [SpeciesId.WYNAUT]: { 0: AbilityId.STURDY }, @@ -640,9 +640,9 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [SpeciesId.LITWICK]: { 0: AbilityId.SHADOW_TAG }, [SpeciesId.LAMPENT]: { 0: AbilityId.SHADOW_TAG }, [SpeciesId.CHANDELURE]: { 0: AbilityId.SHADOW_TAG }, - [SpeciesId.AXEW]: { 0: AbilityId.DRAGONS_MAW }, - [SpeciesId.FRAXURE]: { 0: AbilityId.DRAGONS_MAW }, - [SpeciesId.HAXORUS]: { 0: AbilityId.DRAGONS_MAW }, + [SpeciesId.AXEW]: { 0: AbilityId.OWN_TEMPO }, + [SpeciesId.FRAXURE]: { 0: AbilityId.OWN_TEMPO }, + [SpeciesId.HAXORUS]: { 0: AbilityId.OWN_TEMPO }, [SpeciesId.CUBCHOO]: { 0: AbilityId.FUR_COAT }, [SpeciesId.BEARTIC]: { 0: AbilityId.FUR_COAT }, [SpeciesId.CRYOGONAL]: { 0: AbilityId.SNOW_WARNING }, @@ -827,7 +827,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [SpeciesId.TAPU_LELE]: { 0: AbilityId.BERSERK }, [SpeciesId.TAPU_BULU]: { 0: AbilityId.FLOWER_VEIL }, [SpeciesId.TAPU_FINI]: { 0: AbilityId.FAIRY_AURA }, - [SpeciesId.COSMOG]: { 0: AbilityId.PICKUP }, + [SpeciesId.COSMOG]: { 0: AbilityId.POWER_SPOT }, [SpeciesId.COSMOEM]: { 0: AbilityId.POWER_SPOT }, [SpeciesId.SOLGALEO]: { 0: AbilityId.BEAST_BOOST }, [SpeciesId.LUNALA]: { 0: AbilityId.BEAST_BOOST }, diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts index 8b91c12ae2d..99d5ad62e47 100644 --- a/src/data/balance/starters.ts +++ b/src/data/balance/starters.ts @@ -566,7 +566,7 @@ export const speciesStarterCosts = { [SpeciesId.FLITTLE]: 3, [SpeciesId.TINKATINK]: 4, [SpeciesId.WIGLETT]: 2, - [SpeciesId.BOMBIRDIER]: 3, + [SpeciesId.BOMBIRDIER]: 4, [SpeciesId.FINIZEN]: 3, [SpeciesId.VAROOM]: 4, [SpeciesId.CYCLIZAR]: 4, From 7873181726be36cd3993e16712d4a5b2cd3bb124 Mon Sep 17 00:00:00 2001 From: Desroi <96223572+Desroi@users.noreply.github.com> Date: Thu, 7 Aug 2025 22:46:19 -0500 Subject: [PATCH 11/18] [Bug] Fix Recoil & Shell Bell not applying when hitting Substitute https://github.com/pagefaultgames/pokerogue/pull/5712 * Created Double Edge testing for recoil damage on substitutes * Changed the logic for substitute damage * Made testing for the updated substitute logic * Update test formatting * Fixed error in damage logic * Apply Biome * Fix test file * Apply suggestion and update test * Remove obsolete workaround and console logs --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/phases/move-effect-phase.ts | 1 + test/moves/recoil-moves.test.ts | 84 +++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 test/moves/recoil-moves.test.ts diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index c57e0f6cead..767d7a79968 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -829,6 +829,7 @@ export class MoveEffectPhase extends PokemonPhase { const substitute = target.getTag(SubstituteTag); const isBlockedBySubstitute = substitute && this.move.hitsSubstitute(user, target); if (isBlockedBySubstitute) { + user.turnData.totalDamageDealt += Math.min(dmg, substitute.hp); substitute.hp -= dmg; } else if (!target.isPlayer() && dmg >= target.hp) { globalScene.applyModifiers(EnemyEndureChanceModifier, false, target); diff --git a/test/moves/recoil-moves.test.ts b/test/moves/recoil-moves.test.ts new file mode 100644 index 00000000000..6fc69c932ac --- /dev/null +++ b/test/moves/recoil-moves.test.ts @@ -0,0 +1,84 @@ +import { AbilityId } from "#enums/ability-id"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import { GameManager } from "#test/test-utils/game-manager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Recoil Moves", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleStyle("single") + .enemySpecies(SpeciesId.PIDOVE) + .startingLevel(1) + .enemyLevel(100) + .enemyMoveset(MoveId.SUBSTITUTE) + .criticalHits(false) + .ability(AbilityId.NO_GUARD) + .enemyAbility(AbilityId.BALL_FETCH); + }); + + it.each([ + { moveName: "Double Edge", moveId: MoveId.DOUBLE_EDGE }, + { moveName: "Brave Bird", moveId: MoveId.BRAVE_BIRD }, + { moveName: "Flare Blitz", moveId: MoveId.FLARE_BLITZ }, + { moveName: "Head Charge", moveId: MoveId.HEAD_CHARGE }, + { moveName: "Head Smash", moveId: MoveId.HEAD_SMASH }, + { moveName: "Light of Ruin", moveId: MoveId.LIGHT_OF_RUIN }, + { moveName: "Struggle", moveId: MoveId.STRUGGLE }, + { moveName: "Submission", moveId: MoveId.SUBMISSION }, + { moveName: "Take Down", moveId: MoveId.TAKE_DOWN }, + { moveName: "Volt Tackle", moveId: MoveId.VOLT_TACKLE }, + { moveName: "Wave Crash", moveId: MoveId.WAVE_CRASH }, + { moveName: "Wild Charge", moveId: MoveId.WILD_CHARGE }, + { moveName: "Wood Hammer", moveId: MoveId.WOOD_HAMMER }, + ])("$moveName causes recoil damage when hitting a substitute", async ({ moveId }) => { + await game.classicMode.startBattle([SpeciesId.TOGEPI]); + + game.move.use(moveId); + await game.phaseInterceptor.to("MoveEndPhase"); // Pidove substitute + + const pidove = game.field.getEnemyPokemon(); + const subTag = pidove.getTag(BattlerTagType.SUBSTITUTE)!; + expect(subTag).toBeDefined(); + const subInitialHp = subTag.hp; + + await game.phaseInterceptor.to("MoveEndPhase"); // player attack + + expect(subTag.hp).toBeLessThan(subInitialHp); + + const playerPokemon = game.field.getPlayerPokemon(); + expect(playerPokemon.hp).toBeLessThan(playerPokemon.getMaxHp()); + }); + + it("causes recoil damage when hitting a substitute in a double battle", async () => { + game.override.battleStyle("double"); + + await game.classicMode.startBattle([SpeciesId.TOGEPI, SpeciesId.TOGEPI]); + + const [playerPokemon1, playerPokemon2] = game.scene.getPlayerField(); + + game.move.use(MoveId.DOUBLE_EDGE, 0); + game.move.use(MoveId.DOUBLE_EDGE, 1); + + await game.toNextTurn(); + + expect(playerPokemon1.hp).toBeLessThan(playerPokemon1.getMaxHp()); + expect(playerPokemon2.hp).toBeLessThan(playerPokemon2.getMaxHp()); + }); +}); From 32f5421b329110244e14c321147f203e5f7edf50 Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Thu, 7 Aug 2025 23:11:03 -0500 Subject: [PATCH 12/18] [Misc] Replace `Abilities.ABILITY_NAME` with `AbilityId.ABILITY_NAME` in comments This commit mirrors the comment changes made in 375587213e344ab3531391b2c96ceb75bac08e5d. --- src/data/abilities/ability.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 336d45fed66..f5fd9b19f72 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -5491,7 +5491,7 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { * Attribute used for abilities that damage opponents causing the user to faint * equal to the amount of damage the last attack inflicted. * - * Used for {@linkcode Abilities.INNARDS_OUT}. + * Used for {@linkcode AbilityId.INNARDS_OUT | Innards Out}. * @sealed */ export class PostFaintHPDamageAbAttr extends PostFaintAbAttr { @@ -7307,7 +7307,7 @@ export function initAbilities() { .attr(HealFromBerryUseAbAttr, 1 / 3), new Ability(AbilityId.PROTEAN, 6) .attr(PokemonTypeChangeAbAttr) - // .condition((p) => !p.summonData.abilitiesApplied.includes(Abilities.PROTEAN)) //Gen 9 Implementation + // .condition((p) => !p.summonData.abilitiesApplied.includes(AbilityId.PROTEAN)) //Gen 9 Implementation // TODO: needs testing on interaction with weather blockage .edgeCase(), new Ability(AbilityId.FUR_COAT, 6) @@ -7566,7 +7566,7 @@ export function initAbilities() { .attr(PostSummonStatStageChangeAbAttr, [ Stat.DEF ], 1, true), new Ability(AbilityId.LIBERO, 8) .attr(PokemonTypeChangeAbAttr) - //.condition((p) => !p.summonData.abilitiesApplied.includes(Abilities.LIBERO)), //Gen 9 Implementation + //.condition((p) => !p.summonData.abilitiesApplied.includes(AbilityId.LIBERO)), //Gen 9 Implementation // TODO: needs testing on interaction with weather blockage .edgeCase(), new Ability(AbilityId.BALL_FETCH, 8) From 2dfe7232aa3e01989421d139c23e8462b80c37e0 Mon Sep 17 00:00:00 2001 From: damocleas Date: Fri, 8 Aug 2025 00:27:59 -0400 Subject: [PATCH 13/18] Add 1/16 chance for Trainers in Laboratory --- src/field/arena.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/field/arena.ts b/src/field/arena.ts index ed2f1df9b12..06ba6fdd334 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -536,6 +536,7 @@ export class Arena { case BiomeId.ABYSS: case BiomeId.SPACE: case BiomeId.TEMPLE: + case BiomeId.LABORATORY: return 16; default: return 0; From 8a66cfe40b51fdca48441b4f003fd9f40c04f017 Mon Sep 17 00:00:00 2001 From: Blitzy <118096277+Blitz425@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:28:33 -0500 Subject: [PATCH 14/18] =?UTF-8?q?[Bug]=20Fix=20Pok=C3=A9=20Fan=20in=20Trai?= =?UTF-8?q?ner=20Config=20(#6238)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix Poke Fan in Trainer Config --- src/data/trainers/trainer-config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 7ae157ded96..0f047157174 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -1593,9 +1593,9 @@ export const trainerConfigs: TrainerConfigs = { .setSpeciesFilter(s => tmSpecies[MoveId.FLY].indexOf(s.speciesId) > -1), [TrainerType.POKEFAN]: new TrainerConfig(++t) .setMoneyMultiplier(1.4) - .setName("PokéFan") - .setHasGenders("PokéFan Female") - .setHasDouble("PokéFan Family") + .setName("Pokéfan") + .setHasGenders("Pokéfan Female") + .setHasDouble("Pokéfan Family") .setEncounterBgm(TrainerType.POKEFAN) .setPartyTemplates( trainerPartyTemplates.SIX_WEAKER, From 7316628448079c5f8b4c7a1d4f1c808fd0b20bb8 Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Fri, 8 Aug 2025 20:31:51 -0500 Subject: [PATCH 15/18] [Misc] Fix comment formatting nitpick in ability-id Adds a space between every `/**` and `{@link`. --- src/enums/ability-id.ts | 622 ++++++++++++++++++++-------------------- 1 file changed, 311 insertions(+), 311 deletions(-) diff --git a/src/enums/ability-id.ts b/src/enums/ability-id.ts index c9681fb1109..f054c7b574e 100644 --- a/src/enums/ability-id.ts +++ b/src/enums/ability-id.ts @@ -1,624 +1,624 @@ export enum AbilityId { - /**{@link https://bulbapedia.bulbagarden.net/wiki/None_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/None_(ability) | Source} */ NONE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Stench_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Stench_(ability) | Source} */ STENCH, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Drizzle_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Drizzle_(ability) | Source} */ DRIZZLE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Speed_Boost_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Speed_Boost_(ability) | Source} */ SPEED_BOOST, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Battle_Armor_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Battle_Armor_(ability) | Source} */ BATTLE_ARMOR, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Sturdy_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Sturdy_(ability) | Source} */ STURDY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Damp_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Damp_(ability) | Source} */ DAMP, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Limber_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Limber_(ability) | Source} */ LIMBER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Sand_Veil_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Sand_Veil_(ability) | Source} */ SAND_VEIL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Static_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Static_(ability) | Source} */ STATIC, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Volt_Absorb_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Volt_Absorb_(ability) | Source} */ VOLT_ABSORB, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Water_Absorb_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Water_Absorb_(ability) | Source} */ WATER_ABSORB, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Oblivious_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Oblivious_(ability) | Source} */ OBLIVIOUS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Cloud_Nine_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Cloud_Nine_(ability) | Source} */ CLOUD_NINE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Compound_Eyes_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Compound_Eyes_(ability) | Source} */ COMPOUND_EYES, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Insomnia_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Insomnia_(ability) | Source} */ INSOMNIA, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Color_Change_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Color_Change_(ability) | Source} */ COLOR_CHANGE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Immunity_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Immunity_(ability) | Source} */ IMMUNITY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Flash_Fire_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Flash_Fire_(ability) | Source} */ FLASH_FIRE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Shield_Dust_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Shield_Dust_(ability) | Source} */ SHIELD_DUST, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Own_Tempo_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Own_Tempo_(ability) | Source} */ OWN_TEMPO, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Suction_Cups_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Suction_Cups_(ability) | Source} */ SUCTION_CUPS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Intimidate_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Intimidate_(ability) | Source} */ INTIMIDATE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Shadow_Tag_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Shadow_Tag_(ability) | Source} */ SHADOW_TAG, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Rough_Skin_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Rough_Skin_(ability) | Source} */ ROUGH_SKIN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Wonder_Guard_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Wonder_Guard_(ability) | Source} */ WONDER_GUARD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Levitate_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Levitate_(ability) | Source} */ LEVITATE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Effect_Spore_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Effect_Spore_(ability) | Source} */ EFFECT_SPORE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Synchronize_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Synchronize_(ability) | Source} */ SYNCHRONIZE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Clear_Body_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Clear_Body_(ability) | Source} */ CLEAR_BODY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Natural_Cure_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Natural_Cure_(ability) | Source} */ NATURAL_CURE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Lightning_Rod_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Lightning_Rod_(ability) | Source} */ LIGHTNING_ROD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Serene_Grace_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Serene_Grace_(ability) | Source} */ SERENE_GRACE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Swift_Swim_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Swift_Swim_(ability) | Source} */ SWIFT_SWIM, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Chlorophyll_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Chlorophyll_(ability) | Source} */ CHLOROPHYLL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Illuminate_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Illuminate_(ability) | Source} */ ILLUMINATE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Trace_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Trace_(ability) | Source} */ TRACE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Huge_Power_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Huge_Power_(ability) | Source} */ HUGE_POWER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Poison_Point_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Poison_Point_(ability) | Source} */ POISON_POINT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Inner_Focus_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Inner_Focus_(ability) | Source} */ INNER_FOCUS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Magma_Armor_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Magma_Armor_(ability) | Source} */ MAGMA_ARMOR, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Water_Veil_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Water_Veil_(ability) | Source} */ WATER_VEIL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Magnet_Pull_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Magnet_Pull_(ability) | Source} */ MAGNET_PULL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Soundproof_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Soundproof_(ability) | Source} */ SOUNDPROOF, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Rain_Dish_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Rain_Dish_(ability) | Source} */ RAIN_DISH, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Sand_Stream_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Sand_Stream_(ability) | Source} */ SAND_STREAM, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Pressure_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Pressure_(ability) | Source} */ PRESSURE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Thick_Fat_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Thick_Fat_(ability) | Source} */ THICK_FAT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Early_Bird_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Early_Bird_(ability) | Source} */ EARLY_BIRD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Flame_Body_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Flame_Body_(ability) | Source} */ FLAME_BODY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Run_Away_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Run_Away_(ability) | Source} */ RUN_AWAY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Keen_Eye_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Keen_Eye_(ability) | Source} */ KEEN_EYE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Hyper_Cutter_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Hyper_Cutter_(ability) | Source} */ HYPER_CUTTER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Pickup_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Pickup_(ability) | Source} */ PICKUP, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Truant_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Truant_(ability) | Source} */ TRUANT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Hustle_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Hustle_(ability) | Source} */ HUSTLE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Cute_Charm_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Cute_Charm_(ability) | Source} */ CUTE_CHARM, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Plus_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Plus_(ability) | Source} */ PLUS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Minus_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Minus_(ability) | Source} */ MINUS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Forecast_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Forecast_(ability) | Source} */ FORECAST, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Sticky_Hold_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Sticky_Hold_(ability) | Source} */ STICKY_HOLD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Shed_Skin_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Shed_Skin_(ability) | Source} */ SHED_SKIN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Guts_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Guts_(ability) | Source} */ GUTS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Marvel_Scale_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Marvel_Scale_(ability) | Source} */ MARVEL_SCALE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Liquid_Ooze_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Liquid_Ooze_(ability) | Source} */ LIQUID_OOZE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Overgrow_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Overgrow_(ability) | Source} */ OVERGROW, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Blaze_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Blaze_(ability) | Source} */ BLAZE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Torrent_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Torrent_(ability) | Source} */ TORRENT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Swarm_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Swarm_(ability) | Source} */ SWARM, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Rock_Head_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Rock_Head_(ability) | Source} */ ROCK_HEAD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Drought_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Drought_(ability) | Source} */ DROUGHT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Arena_Trap_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Arena_Trap_(ability) | Source} */ ARENA_TRAP, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Vital_Spirit_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Vital_Spirit_(ability) | Source} */ VITAL_SPIRIT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/White_Smoke_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/White_Smoke_(ability) | Source} */ WHITE_SMOKE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Pure_Power_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Pure_Power_(ability) | Source} */ PURE_POWER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Shell_Armor_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Shell_Armor_(ability) | Source} */ SHELL_ARMOR, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Air_Lock_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Air_Lock_(ability) | Source} */ AIR_LOCK, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Tangled_Feet_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Tangled_Feet_(ability) | Source} */ TANGLED_FEET, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Motor_Drive_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Motor_Drive_(ability) | Source} */ MOTOR_DRIVE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Rivalry_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Rivalry_(ability) | Source} */ RIVALRY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Steadfast_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Steadfast_(ability) | Source} */ STEADFAST, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Snow_Cloak_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Snow_Cloak_(ability) | Source} */ SNOW_CLOAK, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Gluttony_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Gluttony_(ability) | Source} */ GLUTTONY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Anger_Point_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Anger_Point_(ability) | Source} */ ANGER_POINT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Unburden_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Unburden_(ability) | Source} */ UNBURDEN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Heatproof_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Heatproof_(ability) | Source} */ HEATPROOF, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Simple_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Simple_(ability) | Source} */ SIMPLE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Dry_Skin_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Dry_Skin_(ability) | Source} */ DRY_SKIN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Download_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Download_(ability) | Source} */ DOWNLOAD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Iron_Fist_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Iron_Fist_(ability) | Source} */ IRON_FIST, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Poison_Heal_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Poison_Heal_(ability) | Source} */ POISON_HEAL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Adaptability_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Adaptability_(ability) | Source} */ ADAPTABILITY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Skill_Link_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Skill_Link_(ability) | Source} */ SKILL_LINK, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Hydration_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Hydration_(ability) | Source} */ HYDRATION, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Solar_Power_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Solar_Power_(ability) | Source} */ SOLAR_POWER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Quick_Feet_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Quick_Feet_(ability) | Source} */ QUICK_FEET, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Normalize_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Normalize_(ability) | Source} */ NORMALIZE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Sniper_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Sniper_(ability) | Source} */ SNIPER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Magic_Guard_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Magic_Guard_(ability) | Source} */ MAGIC_GUARD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/No_Guard_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/No_Guard_(ability) | Source} */ NO_GUARD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Stall_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Stall_(ability) | Source} */ STALL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Technician_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Technician_(ability) | Source} */ TECHNICIAN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Leaf_Guard_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Leaf_Guard_(ability) | Source} */ LEAF_GUARD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Klutz_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Klutz_(ability) | Source} */ KLUTZ, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Mold_Breaker_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Mold_Breaker_(ability) | Source} */ MOLD_BREAKER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Super_Luck_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Super_Luck_(ability) | Source} */ SUPER_LUCK, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Aftermath_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Aftermath_(ability) | Source} */ AFTERMATH, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Anticipation_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Anticipation_(ability) | Source} */ ANTICIPATION, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Forewarn_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Forewarn_(ability) | Source} */ FOREWARN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Unaware_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Unaware_(ability) | Source} */ UNAWARE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Tinted_Lens_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Tinted_Lens_(ability) | Source} */ TINTED_LENS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Filter_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Filter_(ability) | Source} */ FILTER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Slow_Start_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Slow_Start_(ability) | Source} */ SLOW_START, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Scrappy_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Scrappy_(ability) | Source} */ SCRAPPY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Storm_Drain_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Storm_Drain_(ability) | Source} */ STORM_DRAIN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Ice_Body_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Ice_Body_(ability) | Source} */ ICE_BODY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Solid_Rock_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Solid_Rock_(ability) | Source} */ SOLID_ROCK, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Snow_Warning_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Snow_Warning_(ability) | Source} */ SNOW_WARNING, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Honey_Gather_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Honey_Gather_(ability) | Source} */ HONEY_GATHER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Frisk_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Frisk_(ability) | Source} */ FRISK, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Reckless_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Reckless_(ability) | Source} */ RECKLESS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Multitype_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Multitype_(ability) | Source} */ MULTITYPE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Flower_Gift_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Flower_Gift_(ability) | Source} */ FLOWER_GIFT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Bad_Dreams_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Bad_Dreams_(ability) | Source} */ BAD_DREAMS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Pickpocket_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Pickpocket_(ability) | Source} */ PICKPOCKET, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Sheer_Force_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Sheer_Force_(ability) | Source} */ SHEER_FORCE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Contrary_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Contrary_(ability) | Source} */ CONTRARY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Unnerve_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Unnerve_(ability) | Source} */ UNNERVE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Defiant_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Defiant_(ability) | Source} */ DEFIANT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Defeatist_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Defeatist_(ability) | Source} */ DEFEATIST, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Cursed_Body_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Cursed_Body_(ability) | Source} */ CURSED_BODY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Healer_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Healer_(ability) | Source} */ HEALER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Friend_Guard_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Friend_Guard_(ability) | Source} */ FRIEND_GUARD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Weak_Armor_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Weak_Armor_(ability) | Source} */ WEAK_ARMOR, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Heavy_Metal_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Heavy_Metal_(ability) | Source} */ HEAVY_METAL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Light_Metal_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Light_Metal_(ability) | Source} */ LIGHT_METAL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Multiscale_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Multiscale_(ability) | Source} */ MULTISCALE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Toxic_Boost_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Toxic_Boost_(ability) | Source} */ TOXIC_BOOST, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Flare_Boost_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Flare_Boost_(ability) | Source} */ FLARE_BOOST, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Harvest_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Harvest_(ability) | Source} */ HARVEST, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Telepathy_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Telepathy_(ability) | Source} */ TELEPATHY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Moody_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Moody_(ability) | Source} */ MOODY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Overcoat_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Overcoat_(ability) | Source} */ OVERCOAT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Poison_Touch_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Poison_Touch_(ability) | Source} */ POISON_TOUCH, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Regenerator_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Regenerator_(ability) | Source} */ REGENERATOR, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Big_Pecks_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Big_Pecks_(ability) | Source} */ BIG_PECKS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Sand_Rush_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Sand_Rush_(ability) | Source} */ SAND_RUSH, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Wonder_Skin_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Wonder_Skin_(ability) | Source} */ WONDER_SKIN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Analytic_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Analytic_(ability) | Source} */ ANALYTIC, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Illusion_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Illusion_(ability) | Source} */ ILLUSION, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Imposter_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Imposter_(ability) | Source} */ IMPOSTER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Infiltrator_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Infiltrator_(ability) | Source} */ INFILTRATOR, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Mummy_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Mummy_(ability) | Source} */ MUMMY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Moxie_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Moxie_(ability) | Source} */ MOXIE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Justified_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Justified_(ability) | Source} */ JUSTIFIED, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Rattled_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Rattled_(ability) | Source} */ RATTLED, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Magic_Bounce_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Magic_Bounce_(ability) | Source} */ MAGIC_BOUNCE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Sap_Sipper_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Sap_Sipper_(ability) | Source} */ SAP_SIPPER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Prankster_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Prankster_(ability) | Source} */ PRANKSTER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Sand_Force_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Sand_Force_(ability) | Source} */ SAND_FORCE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Iron_Barbs_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Iron_Barbs_(ability) | Source} */ IRON_BARBS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Zen_Mode_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Zen_Mode_(ability) | Source} */ ZEN_MODE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Victory_Star_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Victory_Star_(ability) | Source} */ VICTORY_STAR, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Turboblaze_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Turboblaze_(ability) | Source} */ TURBOBLAZE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Teravolt_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Teravolt_(ability) | Source} */ TERAVOLT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Aroma_Veil_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Aroma_Veil_(ability) | Source} */ AROMA_VEIL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Flower_Veil_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Flower_Veil_(ability) | Source} */ FLOWER_VEIL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Cheek_Pouch_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Cheek_Pouch_(ability) | Source} */ CHEEK_POUCH, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Protean_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Protean_(ability) | Source} */ PROTEAN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Fur_Coat_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Fur_Coat_(ability) | Source} */ FUR_COAT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Magician_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Magician_(ability) | Source} */ MAGICIAN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Bulletproof_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Bulletproof_(ability) | Source} */ BULLETPROOF, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Competitive_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Competitive_(ability) | Source} */ COMPETITIVE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Strong_Jaw_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Strong_Jaw_(ability) | Source} */ STRONG_JAW, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Refrigerate_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Refrigerate_(ability) | Source} */ REFRIGERATE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Sweet_Veil_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Sweet_Veil_(ability) | Source} */ SWEET_VEIL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Stance_Change_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Stance_Change_(ability) | Source} */ STANCE_CHANGE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Gale_Wings_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Gale_Wings_(ability) | Source} */ GALE_WINGS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Mega_Launcher_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Mega_Launcher_(ability) | Source} */ MEGA_LAUNCHER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Grass_Pelt_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Grass_Pelt_(ability) | Source} */ GRASS_PELT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Symbiosis_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Symbiosis_(ability) | Source} */ SYMBIOSIS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Tough_Claws_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Tough_Claws_(ability) | Source} */ TOUGH_CLAWS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Pixilate_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Pixilate_(ability) | Source} */ PIXILATE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Gooey_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Gooey_(ability) | Source} */ GOOEY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Aerilate_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Aerilate_(ability) | Source} */ AERILATE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Parental_Bond_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Parental_Bond_(ability) | Source} */ PARENTAL_BOND, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Dark_Aura_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Dark_Aura_(ability) | Source} */ DARK_AURA, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Fairy_Aura_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Fairy_Aura_(ability) | Source} */ FAIRY_AURA, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Aura_Break_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Aura_Break_(ability) | Source} */ AURA_BREAK, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Primordial_Sea_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Primordial_Sea_(ability) | Source} */ PRIMORDIAL_SEA, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Desolate_Land_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Desolate_Land_(ability) | Source} */ DESOLATE_LAND, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Delta_Stream_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Delta_Stream_(ability) | Source} */ DELTA_STREAM, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Stamina_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Stamina_(ability) | Source} */ STAMINA, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Wimp_Out_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Wimp_Out_(ability) | Source} */ WIMP_OUT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Emergency_Exit_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Emergency_Exit_(ability) | Source} */ EMERGENCY_EXIT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Water_Compaction_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Water_Compaction_(ability) | Source} */ WATER_COMPACTION, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Merciless_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Merciless_(ability) | Source} */ MERCILESS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Shields_Down_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Shields_Down_(ability) | Source} */ SHIELDS_DOWN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Stakeout_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Stakeout_(ability) | Source} */ STAKEOUT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Water_Bubble_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Water_Bubble_(ability) | Source} */ WATER_BUBBLE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Steelworker_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Steelworker_(ability) | Source} */ STEELWORKER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Berserk_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Berserk_(ability) | Source} */ BERSERK, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Slush_Rush_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Slush_Rush_(ability) | Source} */ SLUSH_RUSH, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Long_Reach_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Long_Reach_(ability) | Source} */ LONG_REACH, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Liquid_Voice_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Liquid_Voice_(ability) | Source} */ LIQUID_VOICE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Triage_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Triage_(ability) | Source} */ TRIAGE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Galvanize_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Galvanize_(ability) | Source} */ GALVANIZE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Surge_Surfer_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Surge_Surfer_(ability) | Source} */ SURGE_SURFER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Schooling_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Schooling_(ability) | Source} */ SCHOOLING, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Disguise_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Disguise_(ability) | Source} */ DISGUISE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Battle_Bond_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Battle_Bond_(ability) | Source} */ BATTLE_BOND, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Power_Construct_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Power_Construct_(ability) | Source} */ POWER_CONSTRUCT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Corrosion_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Corrosion_(ability) | Source} */ CORROSION, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Comatose_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Comatose_(ability) | Source} */ COMATOSE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Queenly_Majesty_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Queenly_Majesty_(ability) | Source} */ QUEENLY_MAJESTY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Innards_Out_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Innards_Out_(ability) | Source} */ INNARDS_OUT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Dancer_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Dancer_(ability) | Source} */ DANCER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Battery_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Battery_(ability) | Source} */ BATTERY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Fluffy_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Fluffy_(ability) | Source} */ FLUFFY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Dazzling_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Dazzling_(ability) | Source} */ DAZZLING, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Soul_Heart_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Soul_Heart_(ability) | Source} */ SOUL_HEART, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Tangling_Hair_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Tangling_Hair_(ability) | Source} */ TANGLING_HAIR, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Receiver_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Receiver_(ability) | Source} */ RECEIVER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Power_Of_Alchemy_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Power_Of_Alchemy_(ability) | Source} */ POWER_OF_ALCHEMY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Beast_Boost_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Beast_Boost_(ability) | Source} */ BEAST_BOOST, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Rks_System_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Rks_System_(ability) | Source} */ RKS_SYSTEM, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Electric_Surge_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Electric_Surge_(ability) | Source} */ ELECTRIC_SURGE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Psychic_Surge_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Psychic_Surge_(ability) | Source} */ PSYCHIC_SURGE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Misty_Surge_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Misty_Surge_(ability) | Source} */ MISTY_SURGE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Grassy_Surge_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Grassy_Surge_(ability) | Source} */ GRASSY_SURGE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Full_Metal_Body_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Full_Metal_Body_(ability) | Source} */ FULL_METAL_BODY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Shadow_Shield_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Shadow_Shield_(ability) | Source} */ SHADOW_SHIELD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Prism_Armor_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Prism_Armor_(ability) | Source} */ PRISM_ARMOR, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Neuroforce_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Neuroforce_(ability) | Source} */ NEUROFORCE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Intrepid_Sword_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Intrepid_Sword_(ability) | Source} */ INTREPID_SWORD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Dauntless_Shield_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Dauntless_Shield_(ability) | Source} */ DAUNTLESS_SHIELD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Libero_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Libero_(ability) | Source} */ LIBERO, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Ball_Fetch_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Ball_Fetch_(ability) | Source} */ BALL_FETCH, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Cotton_Down_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Cotton_Down_(ability) | Source} */ COTTON_DOWN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Propeller_Tail_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Propeller_Tail_(ability) | Source} */ PROPELLER_TAIL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Mirror_Armor_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Mirror_Armor_(ability) | Source} */ MIRROR_ARMOR, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Gulp_Missile_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Gulp_Missile_(ability) | Source} */ GULP_MISSILE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Stalwart_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Stalwart_(ability) | Source} */ STALWART, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Steam_Engine_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Steam_Engine_(ability) | Source} */ STEAM_ENGINE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Punk_Rock_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Punk_Rock_(ability) | Source} */ PUNK_ROCK, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Sand_Spit_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Sand_Spit_(ability) | Source} */ SAND_SPIT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Ice_Scales_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Ice_Scales_(ability) | Source} */ ICE_SCALES, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Ripen_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Ripen_(ability) | Source} */ RIPEN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Ice_Face_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Ice_Face_(ability) | Source} */ ICE_FACE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Power_Spot_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Power_Spot_(ability) | Source} */ POWER_SPOT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Mimicry_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Mimicry_(ability) | Source} */ MIMICRY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Screen_Cleaner_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Screen_Cleaner_(ability) | Source} */ SCREEN_CLEANER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Steely_Spirit_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Steely_Spirit_(ability) | Source} */ STEELY_SPIRIT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Perish_Body_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Perish_Body_(ability) | Source} */ PERISH_BODY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Wandering_Spirit_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Wandering_Spirit_(ability) | Source} */ WANDERING_SPIRIT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Gorilla_Tactics_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Gorilla_Tactics_(ability) | Source} */ GORILLA_TACTICS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Neutralizing_Gas_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Neutralizing_Gas_(ability) | Source} */ NEUTRALIZING_GAS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Pastel_Veil_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Pastel_Veil_(ability) | Source} */ PASTEL_VEIL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Hunger_Switch_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Hunger_Switch_(ability) | Source} */ HUNGER_SWITCH, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Quick_Draw_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Quick_Draw_(ability) | Source} */ QUICK_DRAW, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Unseen_Fist_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Unseen_Fist_(ability) | Source} */ UNSEEN_FIST, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Curious_Medicine_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Curious_Medicine_(ability) | Source} */ CURIOUS_MEDICINE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Transistor_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Transistor_(ability) | Source} */ TRANSISTOR, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Dragons_Maw_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Dragons_Maw_(ability) | Source} */ DRAGONS_MAW, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Chilling_Neigh_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Chilling_Neigh_(ability) | Source} */ CHILLING_NEIGH, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Grim_Neigh_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Grim_Neigh_(ability) | Source} */ GRIM_NEIGH, - /**{@link https://bulbapedia.bulbagarden.net/wiki/As_One_Glastrier_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/As_One_Glastrier_(ability) | Source} */ AS_ONE_GLASTRIER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/As_One_Spectrier_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/As_One_Spectrier_(ability) | Source} */ AS_ONE_SPECTRIER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Lingering_Aroma_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Lingering_Aroma_(ability) | Source} */ LINGERING_AROMA, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Seed_Sower_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Seed_Sower_(ability) | Source} */ SEED_SOWER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Thermal_Exchange_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Thermal_Exchange_(ability) | Source} */ THERMAL_EXCHANGE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Anger_Shell_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Anger_Shell_(ability) | Source} */ ANGER_SHELL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Purifying_Salt_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Purifying_Salt_(ability) | Source} */ PURIFYING_SALT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Well_Baked_Body_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Well_Baked_Body_(ability) | Source} */ WELL_BAKED_BODY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Wind_Rider_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Wind_Rider_(ability) | Source} */ WIND_RIDER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Guard_Dog_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Guard_Dog_(ability) | Source} */ GUARD_DOG, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Rocky_Payload_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Rocky_Payload_(ability) | Source} */ ROCKY_PAYLOAD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Wind_Power_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Wind_Power_(ability) | Source} */ WIND_POWER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Zero_To_Hero_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Zero_To_Hero_(ability) | Source} */ ZERO_TO_HERO, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Commander_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Commander_(ability) | Source} */ COMMANDER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Electromorphosis_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Electromorphosis_(ability) | Source} */ ELECTROMORPHOSIS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Protosynthesis_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Protosynthesis_(ability) | Source} */ PROTOSYNTHESIS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Quark_Drive_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Quark_Drive_(ability) | Source} */ QUARK_DRIVE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Good_As_Gold_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Good_As_Gold_(ability) | Source} */ GOOD_AS_GOLD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Vessel_Of_Ruin_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Vessel_Of_Ruin_(ability) | Source} */ VESSEL_OF_RUIN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Sword_Of_Ruin_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Sword_Of_Ruin_(ability) | Source} */ SWORD_OF_RUIN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Tablets_Of_Ruin_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Tablets_Of_Ruin_(ability) | Source} */ TABLETS_OF_RUIN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Beads_Of_Ruin_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Beads_Of_Ruin_(ability) | Source} */ BEADS_OF_RUIN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Orichalcum_Pulse_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Orichalcum_Pulse_(ability) | Source} */ ORICHALCUM_PULSE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Hadron_Engine_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Hadron_Engine_(ability) | Source} */ HADRON_ENGINE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Opportunist_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Opportunist_(ability) | Source} */ OPPORTUNIST, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Cud_Chew_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Cud_Chew_(ability) | Source} */ CUD_CHEW, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Sharpness_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Sharpness_(ability) | Source} */ SHARPNESS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Supreme_Overlord_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Supreme_Overlord_(ability) | Source} */ SUPREME_OVERLORD, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Costar_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Costar_(ability) | Source} */ COSTAR, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Toxic_Debris_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Toxic_Debris_(ability) | Source} */ TOXIC_DEBRIS, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Armor_Tail_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Armor_Tail_(ability) | Source} */ ARMOR_TAIL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Earth_Eater_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Earth_Eater_(ability) | Source} */ EARTH_EATER, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Mycelium_Might_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Mycelium_Might_(ability) | Source} */ MYCELIUM_MIGHT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Minds_Eye_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Minds_Eye_(ability) | Source} */ MINDS_EYE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Supersweet_Syrup_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Supersweet_Syrup_(ability) | Source} */ SUPERSWEET_SYRUP, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Hospitality_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Hospitality_(ability) | Source} */ HOSPITALITY, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Toxic_Chain_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Toxic_Chain_(ability) | Source} */ TOXIC_CHAIN, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Teal_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Teal_(ability) | Source} */ EMBODY_ASPECT_TEAL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Wellspring_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Wellspring_(ability) | Source} */ EMBODY_ASPECT_WELLSPRING, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Hearthflame_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Hearthflame_(ability) | Source} */ EMBODY_ASPECT_HEARTHFLAME, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Cornerstone_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Cornerstone_(ability) | Source} */ EMBODY_ASPECT_CORNERSTONE, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Tera_Shift_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Tera_Shift_(ability) | Source} */ TERA_SHIFT, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Tera_Shell_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Tera_Shell_(ability) | Source} */ TERA_SHELL, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Teraform_Zero_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Teraform_Zero_(ability) | Source} */ TERAFORM_ZERO, - /**{@link https://bulbapedia.bulbagarden.net/wiki/Poison_Puppeteer_(ability) | Source} */ + /** {@link https://bulbapedia.bulbagarden.net/wiki/Poison_Puppeteer_(ability) | Source} */ POISON_PUPPETEER, } From f0a56a30490d2d91f9a90b20acd8693ca21649ec Mon Sep 17 00:00:00 2001 From: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> Date: Fri, 8 Aug 2025 21:15:33 -0500 Subject: [PATCH 16/18] [Sprite] Add unique trainer sprite for Rocket Boss Giovanni (#6235) --- .../trainer/rocket_boss_giovanni_1.json | 41 ++++++++++++++++++ .../images/trainer/rocket_boss_giovanni_1.png | Bin 0 -> 698 bytes src/data/trainers/trainer-config.ts | 5 +-- 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 public/images/trainer/rocket_boss_giovanni_1.json create mode 100644 public/images/trainer/rocket_boss_giovanni_1.png diff --git a/public/images/trainer/rocket_boss_giovanni_1.json b/public/images/trainer/rocket_boss_giovanni_1.json new file mode 100644 index 00000000000..a53be1a20e8 --- /dev/null +++ b/public/images/trainer/rocket_boss_giovanni_1.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "rocket_boss_giovanni_1.png", + "format": "RGBA8888", + "size": { + "w": 79, + "h": 79 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 39, + "h": 79 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 39, + "h": 79 + }, + "frame": { + "x": 0, + "y": 0, + "w": 39, + "h": 79 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:d6c5e1804414106d43a7c46f83468d39:1f3f7898a58950988acac6ee7167e012:5f742cbdaafcd5ae864f18ec2af7512a$" + } +} diff --git a/public/images/trainer/rocket_boss_giovanni_1.png b/public/images/trainer/rocket_boss_giovanni_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8adab2d057575f8d7a6620fc5808f5bc646e53ed GIT binary patch literal 698 zcmV;r0!96aP)iD;n6Kc;2OZ_|Ulj|Nr)u zbou}Q00DGTPE!Ct=GbNc000SaNLh0L01FcU01FcV0GgZ_0006VNkl9H$7>SEkL|e`TY}ryG z3y(`7(1UW$^dL0|VkyawoHCI?fGID-#>47Km})N|hLj|@7&*SE1w)EY#FQdFn1q4% z9#hN=a+EPdeZrisR4Rk7+){D4hVjQ$&cOy zYg==fKv;(K#6^5mboLOMKn5dMpI0IWSV_zyffc&Zh6 z{JH|^0z7@K=@LHA-t`+BPZ1!1YY)9%x%J`jFBIBob)b8_fa>?)!Ez>%07*qoM6N<$f)eOAlK=n! literal 0 HcmV?d00001 diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 0f047157174..d29b40e0972 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -223,9 +223,8 @@ export class TrainerConfig { case TrainerType.LARRY_ELITE: trainerType = TrainerType.LARRY; break; - case TrainerType.ROCKET_BOSS_GIOVANNI_1: case TrainerType.ROCKET_BOSS_GIOVANNI_2: - trainerType = TrainerType.GIOVANNI; + trainerType = TrainerType.ROCKET_BOSS_GIOVANNI_1; break; case TrainerType.MAXIE_2: trainerType = TrainerType.MAXIE; @@ -895,7 +894,7 @@ export class TrainerConfig { /** * Helper function to check if a specialty type is set - * @returns true if specialtyType is defined and not Type.UNKNOWN + * @returns `true` if `specialtyType` is defined and not {@link PokemonType.UNKNOWN} */ hasSpecialtyType(): boolean { return !isNullOrUndefined(this.specialtyType) && this.specialtyType !== PokemonType.UNKNOWN; From 79576ad117a9674a5ab47be2306a1e67ce267e5a Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Fri, 8 Aug 2025 20:18:40 -0700 Subject: [PATCH 17/18] [GitHub] Update `.github/CODEOWNERS` file (#6240) * [GitHub] Update `.github/CODEOWNERS` file `@pagefaultgames/senior-dev-team` added to `package.json` and `pnpm-lock.yaml` `@pagefaultgames/balance-team` added to `/src/data/trainers` * Move senior dev team entries to the bottom of the file --- .github/CODEOWNERS | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 979b94f84d6..a97457a818b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,9 +3,6 @@ # everything (whole code-base) - Junior Devs * @pagefaultgames/junior-dev-team -# github actions/templates etc. - Dev Leads -/.github @pagefaultgames/senior-dev-team - # Art Team /public/**/*.png @pagefaultgames/art-team /public/**/*.json @pagefaultgames/art-team @@ -19,4 +16,11 @@ /public/audio @pagefaultgames/composer-team # Balance Files; contain actual code logic and must also be owned by dev team -/src/data/balance @pagefaultgames/balance-team @pagefaultgames/junior-dev-team \ No newline at end of file +/src/data/balance @pagefaultgames/balance-team @pagefaultgames/junior-dev-team +/src/data/trainers @pagefaultgames/balance-team @pagefaultgames/junior-dev-team + +# GitHub actions/templates etc. - Senior Devs +# Should be defined last in the file to make sure these always override all other definitions +/.github @pagefaultgames/senior-dev-team +package.json @pagefaultgames/senior-dev-team +pnpm-lock.yaml @pagefaultgames/senior-dev-team \ No newline at end of file From e862334819bcc37fc4e0ba9cd18ae858785e84b0 Mon Sep 17 00:00:00 2001 From: Lugiad Date: Sat, 9 Aug 2025 17:11:03 +0200 Subject: [PATCH 18/18] [UI/UX] Summary Move Effect UI adjustments (#6223) Summary Effect tab UI image adjustments --- .../images/ui/legacy/summary_moves_effect.png | Bin 290 -> 289 bytes public/images/ui/summary_moves_effect.png | Bin 283 -> 292 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/ui/legacy/summary_moves_effect.png b/public/images/ui/legacy/summary_moves_effect.png index 34d59970c9962d869ecb71dad386bdd26801bf36..61567c9749bbc77e85df7f319413f716f2d2dc2d 100644 GIT binary patch delta 253 zcmVpHQAS4d(X1R*kvVw5~ zR$ztKTM;*~z4CVPq`*-4fJ^M|=ZDaI3FToOnD$bLu=PoS&r-9e zxcwjq@NHdV(iM0Q2~lWeHA4_W>n!Pta}UE0sh2Ck3;_o*c`0uNR=5>z1(FTy z35aJ&Mqq_B8Nj>5p&&Y)!hxl9{(KP4lPHZl@n{??$jcALv3K`#Sk@Q;u=WB;&RDW8 zVg5mo=-aZ6$)+UxkN~7wQ9T4H)%KE2vF~B{A?0!d>LDbNvs|u}A~nc#Zzb;{ z?&5gL!{pooMvNFSV#J6MBmW;r6l0HKxKfSQ!%=(_-WHQ014b`gu>b%707*qoM6N<$ Ef*RLyvH$=8 diff --git a/public/images/ui/summary_moves_effect.png b/public/images/ui/summary_moves_effect.png index 5b1cef9c02e9ddfd669ec7646446d839189597a7..7993cd0ad577267d47a9c39807d9c51f440c3f2c 100644 GIT binary patch delta 241 zcmVOjJbx00960|B);ke?V309RL6T0(4SNQvd*Vlg&5)006K_ zL_t(|Uc8Sn4un7mMJ;W;ftkvBy%QSCE#wNsgHUivXUOiKFc!wf#}A?Td7&E~^HL?# zOTkQ$!`8gz&)BuFqeL}6{NpHp7GF>a@j9dr-kC~`oP-+>~GO8t%5QrE} r{G(b%wT$K$inWYt8P!4<&_@dZtzs+bCtm}E00000NkvXXu0mjfzs_QK delta 232 zcmVClr9hXYWhfE%jj>^+q-7{k!@d$$N-~s)rHr8@Ly5ZE_#%#ngH@JR zDl$Q*?>JuA1XEHfw|0R3a#*?6%4dqkc!7Bb1{joDMzxG;als4R%SRLcsFqPJqxpp_ iEu&gSwU7$vqXht@)Qs+oPRCFH0000