diff --git a/README.md b/README.md index 9e88163f2d7..839bfa3ae45 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,14 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to - Keisuke Ito - Arata Iiyoshi - Atsuhiro Ishizuna + - Pokémon HeartGold/SoulSilver - Pokémon Black/White 2 + - Pokémon X/Y + - Pokémon Omega Ruby/Alpha Sapphire + - Pokémon Sun/Moon + - Pokémon Ultra Sun/Ultra Moon + - Pokémon Sword/Shield + - Pokémon Scarlet/Violet - Firel (Custom Metropolis and Laboratory biome music) - Lmz (Custom Jungle biome music) diff --git a/package.json b/package.json index 2e87525aeaf..f100b3865d2 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "test": "vitest run", "test:cov": "vitest run --coverage", "test:watch": "vitest watch --coverage", + "test:silent": "vitest run --silent", "eslint": "eslint --fix .", "eslint-ci": "eslint .", "docs": "typedoc" diff --git a/public/audio/bgm/battle_alola_champion.mp3 b/public/audio/bgm/battle_alola_champion.mp3 new file mode 100644 index 00000000000..94173ca69fd Binary files /dev/null and b/public/audio/bgm/battle_alola_champion.mp3 differ diff --git a/public/audio/bgm/battle_alola_elite.mp3 b/public/audio/bgm/battle_alola_elite.mp3 new file mode 100644 index 00000000000..d87a4a89968 Binary files /dev/null and b/public/audio/bgm/battle_alola_elite.mp3 differ diff --git a/public/audio/bgm/battle_bb_elite.mp3 b/public/audio/bgm/battle_bb_elite.mp3 new file mode 100644 index 00000000000..e0ec0dbd518 Binary files /dev/null and b/public/audio/bgm/battle_bb_elite.mp3 differ diff --git a/public/audio/bgm/battle_champion_geeta.mp3 b/public/audio/bgm/battle_champion_geeta.mp3 new file mode 100644 index 00000000000..1b654e33ee2 Binary files /dev/null and b/public/audio/bgm/battle_champion_geeta.mp3 differ diff --git a/public/audio/bgm/battle_champion_kieran.mp3 b/public/audio/bgm/battle_champion_kieran.mp3 new file mode 100644 index 00000000000..e43a1c904cd Binary files /dev/null and b/public/audio/bgm/battle_champion_kieran.mp3 differ diff --git a/public/audio/bgm/battle_champion_nemona.mp3 b/public/audio/bgm/battle_champion_nemona.mp3 new file mode 100644 index 00000000000..9d835030e49 Binary files /dev/null and b/public/audio/bgm/battle_champion_nemona.mp3 differ diff --git a/public/audio/bgm/battle_galar_champion.mp3 b/public/audio/bgm/battle_galar_champion.mp3 new file mode 100644 index 00000000000..9dd43f9bbc8 Binary files /dev/null and b/public/audio/bgm/battle_galar_champion.mp3 differ diff --git a/public/audio/bgm/battle_galar_elite.mp3 b/public/audio/bgm/battle_galar_elite.mp3 new file mode 100644 index 00000000000..34587edcbd1 Binary files /dev/null and b/public/audio/bgm/battle_galar_elite.mp3 differ diff --git a/public/audio/bgm/battle_galar_gym.mp3 b/public/audio/bgm/battle_galar_gym.mp3 new file mode 100644 index 00000000000..a10b284cf4b Binary files /dev/null and b/public/audio/bgm/battle_galar_gym.mp3 differ diff --git a/public/audio/bgm/battle_hoenn_elite.mp3 b/public/audio/bgm/battle_hoenn_elite.mp3 new file mode 100644 index 00000000000..7d352d4332a Binary files /dev/null and b/public/audio/bgm/battle_hoenn_elite.mp3 differ diff --git a/public/audio/bgm/battle_kalos_champion.mp3 b/public/audio/bgm/battle_kalos_champion.mp3 new file mode 100644 index 00000000000..c9dd6b80133 Binary files /dev/null and b/public/audio/bgm/battle_kalos_champion.mp3 differ diff --git a/public/audio/bgm/battle_kalos_elite.mp3 b/public/audio/bgm/battle_kalos_elite.mp3 new file mode 100644 index 00000000000..f4a0181544d Binary files /dev/null and b/public/audio/bgm/battle_kalos_elite.mp3 differ diff --git a/public/audio/bgm/battle_kalos_gym.mp3 b/public/audio/bgm/battle_kalos_gym.mp3 new file mode 100644 index 00000000000..9031274519f Binary files /dev/null and b/public/audio/bgm/battle_kalos_gym.mp3 differ diff --git a/public/audio/bgm/battle_legendary_arceus.mp3 b/public/audio/bgm/battle_legendary_arceus.mp3 new file mode 100644 index 00000000000..03dab3878f2 Binary files /dev/null and b/public/audio/bgm/battle_legendary_arceus.mp3 differ diff --git a/public/audio/bgm/battle_legendary_birds_galar.mp3 b/public/audio/bgm/battle_legendary_birds_galar.mp3 new file mode 100644 index 00000000000..79bedb5c500 Binary files /dev/null and b/public/audio/bgm/battle_legendary_birds_galar.mp3 differ diff --git a/public/audio/bgm/battle_legendary_calyrex.mp3 b/public/audio/bgm/battle_legendary_calyrex.mp3 new file mode 100644 index 00000000000..8d22a5df963 Binary files /dev/null and b/public/audio/bgm/battle_legendary_calyrex.mp3 differ diff --git a/public/audio/bgm/battle_legendary_deoxys.mp3 b/public/audio/bgm/battle_legendary_deoxys.mp3 new file mode 100644 index 00000000000..da91a5f76bc Binary files /dev/null and b/public/audio/bgm/battle_legendary_deoxys.mp3 differ diff --git a/public/audio/bgm/battle_legendary_dia_pal.mp3 b/public/audio/bgm/battle_legendary_dia_pal.mp3 new file mode 100644 index 00000000000..07998098afe Binary files /dev/null and b/public/audio/bgm/battle_legendary_dia_pal.mp3 differ diff --git a/public/audio/bgm/battle_legendary_dusk_dawn.mp3 b/public/audio/bgm/battle_legendary_dusk_dawn.mp3 new file mode 100644 index 00000000000..20b883a4ca7 Binary files /dev/null and b/public/audio/bgm/battle_legendary_dusk_dawn.mp3 differ diff --git a/public/audio/bgm/battle_legendary_entei.mp3 b/public/audio/bgm/battle_legendary_entei.mp3 new file mode 100644 index 00000000000..30d1f87748c Binary files /dev/null and b/public/audio/bgm/battle_legendary_entei.mp3 differ diff --git a/public/audio/bgm/battle_legendary_giratina.mp3 b/public/audio/bgm/battle_legendary_giratina.mp3 new file mode 100644 index 00000000000..87674e003ca Binary files /dev/null and b/public/audio/bgm/battle_legendary_giratina.mp3 differ diff --git a/public/audio/bgm/battle_legendary_glas_spec.mp3 b/public/audio/bgm/battle_legendary_glas_spec.mp3 new file mode 100644 index 00000000000..52988510ea6 Binary files /dev/null and b/public/audio/bgm/battle_legendary_glas_spec.mp3 differ diff --git a/public/audio/bgm/battle_legendary_gro_kyo.mp3 b/public/audio/bgm/battle_legendary_gro_kyo.mp3 new file mode 100644 index 00000000000..b3401da3f6e Binary files /dev/null and b/public/audio/bgm/battle_legendary_gro_kyo.mp3 differ diff --git a/public/audio/bgm/battle_legendary_ho_oh.mp3 b/public/audio/bgm/battle_legendary_ho_oh.mp3 new file mode 100644 index 00000000000..46740297f77 Binary files /dev/null and b/public/audio/bgm/battle_legendary_ho_oh.mp3 differ diff --git a/public/audio/bgm/battle_legendary_kanto.mp3 b/public/audio/bgm/battle_legendary_kanto.mp3 new file mode 100644 index 00000000000..0b9946ec9fe Binary files /dev/null and b/public/audio/bgm/battle_legendary_kanto.mp3 differ diff --git a/public/audio/bgm/battle_legendary_lake_trio.mp3 b/public/audio/bgm/battle_legendary_lake_trio.mp3 new file mode 100644 index 00000000000..dc6bb08d13f Binary files /dev/null and b/public/audio/bgm/battle_legendary_lake_trio.mp3 differ diff --git a/public/audio/bgm/battle_legendary_loyal_three.mp3 b/public/audio/bgm/battle_legendary_loyal_three.mp3 new file mode 100644 index 00000000000..d753e58a0a7 Binary files /dev/null and b/public/audio/bgm/battle_legendary_loyal_three.mp3 differ diff --git a/public/audio/bgm/battle_legendary_lugia.mp3 b/public/audio/bgm/battle_legendary_lugia.mp3 new file mode 100644 index 00000000000..37031c8a642 Binary files /dev/null and b/public/audio/bgm/battle_legendary_lugia.mp3 differ diff --git a/public/audio/bgm/battle_legendary_ogerpon.mp3 b/public/audio/bgm/battle_legendary_ogerpon.mp3 new file mode 100644 index 00000000000..2ac0d9756a8 Binary files /dev/null and b/public/audio/bgm/battle_legendary_ogerpon.mp3 differ diff --git a/public/audio/bgm/battle_legendary_pecharunt.mp3 b/public/audio/bgm/battle_legendary_pecharunt.mp3 new file mode 100644 index 00000000000..48f205d97d1 Binary files /dev/null and b/public/audio/bgm/battle_legendary_pecharunt.mp3 differ diff --git a/public/audio/bgm/battle_legendary_raikou.mp3 b/public/audio/bgm/battle_legendary_raikou.mp3 new file mode 100644 index 00000000000..8daa7083faa Binary files /dev/null and b/public/audio/bgm/battle_legendary_raikou.mp3 differ diff --git a/public/audio/bgm/battle_legendary_rayquaza.mp3 b/public/audio/bgm/battle_legendary_rayquaza.mp3 new file mode 100644 index 00000000000..843596779bd Binary files /dev/null and b/public/audio/bgm/battle_legendary_rayquaza.mp3 differ diff --git a/public/audio/bgm/battle_legendary_regis.mp3 b/public/audio/bgm/battle_legendary_regis_g5.mp3 similarity index 100% rename from public/audio/bgm/battle_legendary_regis.mp3 rename to public/audio/bgm/battle_legendary_regis_g5.mp3 diff --git a/public/audio/bgm/battle_legendary_regis_g6.mp3 b/public/audio/bgm/battle_legendary_regis_g6.mp3 new file mode 100644 index 00000000000..ebe1a44d18a Binary files /dev/null and b/public/audio/bgm/battle_legendary_regis_g6.mp3 differ diff --git a/public/audio/bgm/battle_legendary_ruinous.mp3 b/public/audio/bgm/battle_legendary_ruinous.mp3 new file mode 100644 index 00000000000..dc1aeeaee26 Binary files /dev/null and b/public/audio/bgm/battle_legendary_ruinous.mp3 differ diff --git a/public/audio/bgm/battle_legendary_sinnoh.mp3 b/public/audio/bgm/battle_legendary_sinnoh.mp3 new file mode 100644 index 00000000000..6332a3e1cc2 Binary files /dev/null and b/public/audio/bgm/battle_legendary_sinnoh.mp3 differ diff --git a/public/audio/bgm/battle_legendary_sol_lun.mp3 b/public/audio/bgm/battle_legendary_sol_lun.mp3 new file mode 100644 index 00000000000..4aa9d5111b8 Binary files /dev/null and b/public/audio/bgm/battle_legendary_sol_lun.mp3 differ diff --git a/public/audio/bgm/battle_legendary_suicune.mp3 b/public/audio/bgm/battle_legendary_suicune.mp3 new file mode 100644 index 00000000000..7e7e4901386 Binary files /dev/null and b/public/audio/bgm/battle_legendary_suicune.mp3 differ diff --git a/public/audio/bgm/battle_legendary_tapu.mp3 b/public/audio/bgm/battle_legendary_tapu.mp3 new file mode 100644 index 00000000000..7f251a387a2 Binary files /dev/null and b/public/audio/bgm/battle_legendary_tapu.mp3 differ diff --git a/public/audio/bgm/battle_legendary_terapagos.mp3 b/public/audio/bgm/battle_legendary_terapagos.mp3 new file mode 100644 index 00000000000..b820d7dba6a Binary files /dev/null and b/public/audio/bgm/battle_legendary_terapagos.mp3 differ diff --git a/public/audio/bgm/battle_legendary_ub.mp3 b/public/audio/bgm/battle_legendary_ub.mp3 new file mode 100644 index 00000000000..7a6bdb3a678 Binary files /dev/null and b/public/audio/bgm/battle_legendary_ub.mp3 differ diff --git a/public/audio/bgm/battle_legendary_ultra_nec.mp3 b/public/audio/bgm/battle_legendary_ultra_nec.mp3 new file mode 100644 index 00000000000..63d8b0fa505 Binary files /dev/null and b/public/audio/bgm/battle_legendary_ultra_nec.mp3 differ diff --git a/public/audio/bgm/battle_legendary_xern_yvel.mp3 b/public/audio/bgm/battle_legendary_xern_yvel.mp3 new file mode 100644 index 00000000000..856bebb0f61 Binary files /dev/null and b/public/audio/bgm/battle_legendary_xern_yvel.mp3 differ diff --git a/public/audio/bgm/battle_legendary_zac_zam.mp3 b/public/audio/bgm/battle_legendary_zac_zam.mp3 new file mode 100644 index 00000000000..6725625bedf Binary files /dev/null and b/public/audio/bgm/battle_legendary_zac_zam.mp3 differ diff --git a/public/audio/bgm/battle_paldea_elite.mp3 b/public/audio/bgm/battle_paldea_elite.mp3 new file mode 100644 index 00000000000..9a598dfaf64 Binary files /dev/null and b/public/audio/bgm/battle_paldea_elite.mp3 differ diff --git a/public/audio/bgm/battle_paldea_gym.mp3 b/public/audio/bgm/battle_paldea_gym.mp3 new file mode 100644 index 00000000000..eb9a19bacfe Binary files /dev/null and b/public/audio/bgm/battle_paldea_gym.mp3 differ diff --git a/public/audio/bgm/battle_elite.mp3 b/public/audio/bgm/battle_unova_elite.mp3 similarity index 100% rename from public/audio/bgm/battle_elite.mp3 rename to public/audio/bgm/battle_unova_elite.mp3 diff --git a/src/battle-scene.ts b/src/battle-scene.ts index df827f5201c..c2097391894 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -20,8 +20,8 @@ import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesF import AbilityBar from "./ui/ability-bar"; import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, PostBattleInitAbAttr, applyAbAttrs, applyPostBattleInitAbAttrs } from "./data/ability"; import { allAbilities } from "./data/ability"; -import Battle, { BattleType, FixedBattleConfig, fixedBattles } from "./battle"; -import { GameMode, GameModes, gameModes } from "./game-mode"; +import Battle, { BattleType, FixedBattleConfig } from "./battle"; +import { GameMode, GameModes, getGameMode } from "./game-mode"; import FieldSpritePipeline from "./pipelines/field-sprite"; import SpritePipeline from "./pipelines/sprite"; import PartyExpBar from "./ui/party-exp-bar"; @@ -126,6 +126,7 @@ export default class BattleScene extends SceneBase { public uiTheme: UiTheme = UiTheme.DEFAULT; public windowType: integer = 0; public experimentalSprites: boolean = false; + public musicPreference: integer = 0; public moveAnimations: boolean = true; public expGainsSpeed: integer = 0; public skipSeenDialogues: boolean = false; @@ -154,6 +155,13 @@ export default class BattleScene extends SceneBase { */ public battleStyle: integer = 0; + /** + * Defines whether or not to show type effectiveness hints + * - true: No hints + * - false: Show hints for moves + */ + public typeHints: boolean = false; + public disableMenu: boolean = false; public gameData: GameData; @@ -226,6 +234,7 @@ export default class BattleScene extends SceneBase { public rngSeedOverride: string = ""; public rngOffset: integer = 0; + public inputMethod: string; private infoToggles: InfoToggle[] = []; /** @@ -852,7 +861,7 @@ export default class BattleScene extends SceneBase { this.gameData = new GameData(this); } - this.gameMode = gameModes[GameModes.CLASSIC]; + this.gameMode = getGameMode(GameModes.CLASSIC); this.setSeed(Overrides.SEED_OVERRIDE || Utils.randomString(24)); console.log("Seed:", this.seed); @@ -960,8 +969,8 @@ export default class BattleScene extends SceneBase { const playerField = this.getPlayerField(); - if (this.gameMode.hasFixedBattles && fixedBattles.hasOwnProperty(newWaveIndex) && trainerData === undefined) { - battleConfig = fixedBattles[newWaveIndex]; + if (this.gameMode.isFixedBattle(newWaveIndex) && trainerData === undefined) { + battleConfig = this.gameMode.getFixedBattle(newWaveIndex); newDouble = battleConfig.double; newBattleType = battleConfig.battleType; this.executeWithSeedOffset(() => newTrainer = battleConfig.getTrainer(this), (battleConfig.seedOffsetWaveIndex || newWaveIndex) << 8); @@ -1009,7 +1018,8 @@ export default class BattleScene extends SceneBase { if (Overrides.DOUBLE_BATTLE_OVERRIDE) { newDouble = true; } - if (Overrides.SINGLE_BATTLE_OVERRIDE) { + /* Override battles into single only if not fighting with trainers */ + if (newBattleType !== BattleType.TRAINER && Overrides.SINGLE_BATTLE_OVERRIDE) { newDouble = false; } @@ -1381,8 +1391,7 @@ export default class BattleScene extends SceneBase { if (this.money === undefined) { return; } - const formattedMoney = - this.moneyFormat === MoneyFormat.ABBREVIATED ? Utils.formatFancyLargeNumber(this.money, 3) : this.money.toLocaleString(); + const formattedMoney = Utils.formatMoney(this.moneyFormat, this.money); this.moneyText.setText(`₽${formattedMoney}`); this.fieldUI.moveAbove(this.moneyText, this.luckText); if (forceVisible) { @@ -1671,55 +1680,145 @@ export default class BattleScene extends SceneBase { getBgmLoopPoint(bgmName: string): number { switch (bgmName) { - case "battle_kanto_champion": + case "battle_kanto_champion": //B2W2 Kanto Champion Battle return 13.950; - case "battle_johto_champion": + case "battle_johto_champion": //B2W2 Johto Champion Battle return 23.498; - case "battle_hoenn_champion": + case "battle_hoenn_champion": //B2W2 Hoenn Champion Battle return 11.328; - case "battle_sinnoh_champion": + case "battle_sinnoh_champion": //B2W2 Sinnoh Champion Battle return 12.235; - case "battle_champion_alder": + case "battle_champion_alder": //BW Unova Champion Battle return 27.653; - case "battle_champion_iris": + case "battle_champion_iris": //B2W2 Unova Champion Battle return 10.145; - case "battle_elite": + case "battle_kalos_champion": //XY Kalos Champion Battle + return 10.380; + case "battle_alola_champion": //USUM Alola Champion Battle + return 13.025; + case "battle_galar_champion": //SWSH Galar Champion Battle + return 61.635; + case "battle_champion_geeta": //SV Champion Geeta Battle + return 37.447; + case "battle_champion_nemona": //SV Champion Nemona Battle + return 14.914; + case "battle_champion_kieran": //SV Champion Kieran Battle + return 7.206; + case "battle_hoenn_elite": //ORAS Elite Four Battle + return 11.350; + case "battle_unova_elite": //BW Elite Four Battle return 17.730; - case "battle_final_encounter": + case "battle_kalos_elite": //XY Elite Four Battle + return 12.340; + case "battle_alola_elite": //SM Elite Four Battle + return 19.212; + case "battle_galar_elite": //SWSH League Tournament Battle + return 164.069; + case "battle_paldea_elite": //SV Elite Four Battle + return 12.770; + case "battle_bb_elite": //SV BB League Elite Four Battle + return 19.434; + case "battle_final_encounter": //PMD RTDX Rayquaza's Domain return 19.159; - case "battle_final": + case "battle_final": //BW Ghetsis Battle return 16.453; - case "battle_kanto_gym": + case "battle_kanto_gym": //B2W2 Kanto Gym Battle return 13.857; - case "battle_johto_gym": + case "battle_johto_gym": //B2W2 Johto Gym Battle return 12.911; - case "battle_hoenn_gym": + case "battle_hoenn_gym": //B2W2 Hoenn Gym Battle return 12.379; - case "battle_sinnoh_gym": + case "battle_sinnoh_gym": //B2W2 Sinnoh Gym Battle return 13.122; - case "battle_unova_gym": + case "battle_unova_gym": //BW Unova Gym Battle return 19.145; - case "battle_legendary_regis": //B2W2 Legendary Titan Battle + case "battle_kalos_gym": //XY Kalos Gym Battle + return 44.810; + case "battle_galar_gym": //SWSH Galar Gym Battle + return 171.262; + case "battle_paldea_gym": //SV Paldea Gym Battle + return 127.489; + case "battle_legendary_kanto": //XY Kanto Legendary Battle + return 32.966; + case "battle_legendary_raikou": //HGSS Raikou Battle + return 12.632; + case "battle_legendary_entei": //HGSS Entei Battle + return 2.905; + case "battle_legendary_suicune": //HGSS Suicune Battle + return 12.636; + case "battle_legendary_lugia": //HGSS Lugia Battle + return 19.770; + case "battle_legendary_ho_oh": //HGSS Ho-oh Battle + return 17.668; + case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle return 49.500; + case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle + return 21.130; + case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle + return 10.547; + case "battle_legendary_rayquaza": //ORAS Rayquaza Battle + return 10.495; + case "battle_legendary_deoxys": //ORAS Deoxys Battle + return 13.333; + case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle + return 16.887; + case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle + return 22.770; + case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle + return 16.009; + case "battle_legendary_giratina": //ORAS Giratina Battle + return 10.451; + case "battle_legendary_arceus": //HGSS Arceus Battle + return 9.595; case "battle_legendary_unova": //BW Unova Legendary Battle return 13.855; case "battle_legendary_kyurem": //BW Kyurem Battle return 18.314; case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle return 18.329; - case "battle_rival": + case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle + return 26.468; + case "battle_legendary_tapu": //SM Tapu Battle + return 0.000; + case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle + return 6.525; + case "battle_legendary_ub": //SM Ultra Beast Battle + return 9.818; + case "battle_legendary_dusk_dawn": //USUM Dusk Mane & Dawn Wings Necrozma Battle + return 5.211; + case "battle_legendary_ultra_nec": //USUM Ultra Necrozma Battle + return 10.344; + case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle + return 11.424; + case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle + return 12.503; + case "battle_legendary_calyrex": //SWSH Calyrex Battle + return 50.641; + case "battle_legendary_birds_galar": //SWSH Galarian Legendary Birds Battle + return 0.175; + case "battle_legendary_ruinous": //SV Treasures of Ruin Battle + return 6.333; + case "battle_legendary_loyal_three": //SV Loyal Three Battle + return 6.500; + case "battle_legendary_ogerpon": //SV Ogerpon Battle + return 14.335; + case "battle_legendary_terapagos": //SV Terapagos Battle + return 24.377; + case "battle_legendary_pecharunt": //SV Pecharunt Battle + return 6.508; + case "battle_rival": //BW Rival Battle return 13.689; - case "battle_rival_2": + case "battle_rival_2": //BW N Battle return 17.714; - case "battle_rival_3": + case "battle_rival_3": //BW Final N Battle return 17.586; - case "battle_trainer": + case "battle_trainer": //BW Trainer Battle return 13.686; - case "battle_wild": + case "battle_wild": //BW Wild Battle return 12.703; - case "battle_wild_strong": + case "battle_wild_strong": //BW Strong Wild Battle return 13.940; - case "end_summit": + case "end_summit": //PMD RTDX Sky Tower Summit return 30.025; } diff --git a/src/battle.ts b/src/battle.ts index c857a8766ed..104c35a5e9f 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -197,7 +197,11 @@ export default class Battle { if (!this.started && this.trainer.config.encounterBgm && this.trainer.getEncounterMessages()?.length) { return `encounter_${this.trainer.getEncounterBgm()}`; } - return this.trainer.getBattleBgm(); + if (scene.musicPreference === 0) { + return this.trainer.getBattleBgm(); + } else { + return this.trainer.getMixedBattleBgm(); + } } else if (this.gameMode.isClassic && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS) { return "end_summit"; } @@ -209,22 +213,116 @@ export default class Battle { return "battle_final_encounter"; } if (pokemon.species.legendary || pokemon.species.subLegendary || pokemon.species.mythical) { - if (pokemon.species.speciesId === Species.REGIROCK || pokemon.species.speciesId === Species.REGICE || pokemon.species.speciesId === Species.REGISTEEL || pokemon.species.speciesId === Species.REGIGIGAS || pokemon.species.speciesId === Species.REGIELEKI || pokemon.species.speciesId === Species.REGIDRAGO) { - return "battle_legendary_regis"; - } - if (pokemon.species.speciesId === Species.COBALION || pokemon.species.speciesId === Species.TERRAKION || pokemon.species.speciesId === Species.VIRIZION || pokemon.species.speciesId === Species.TORNADUS || pokemon.species.speciesId === Species.THUNDURUS || pokemon.species.speciesId === Species.LANDORUS || pokemon.species.speciesId === Species.KELDEO || pokemon.species.speciesId === Species.MELOETTA || pokemon.species.speciesId === Species.GENESECT) { + if (scene.musicPreference === 0) { + if (pokemon.species.speciesId === Species.REGIROCK || pokemon.species.speciesId === Species.REGICE || pokemon.species.speciesId === Species.REGISTEEL || pokemon.species.speciesId === Species.REGIGIGAS || pokemon.species.speciesId === Species.REGIELEKI || pokemon.species.speciesId === Species.REGIDRAGO) { + return "battle_legendary_regis_g5"; + } + if (pokemon.species.speciesId === Species.COBALION || pokemon.species.speciesId === Species.TERRAKION || pokemon.species.speciesId === Species.VIRIZION || pokemon.species.speciesId === Species.TORNADUS || pokemon.species.speciesId === Species.THUNDURUS || pokemon.species.speciesId === Species.LANDORUS || pokemon.species.speciesId === Species.KELDEO || pokemon.species.speciesId === Species.MELOETTA || pokemon.species.speciesId === Species.GENESECT) { + return "battle_legendary_unova"; + } + if (pokemon.species.speciesId === Species.KYUREM) { + return "battle_legendary_kyurem"; + } + if (pokemon.species.legendary) { + return "battle_legendary_res_zek"; + } + return "battle_legendary_unova"; + } else { + if (pokemon.species.speciesId === Species.ARTICUNO || pokemon.species.speciesId === Species.ZAPDOS || pokemon.species.speciesId === Species.MOLTRES || pokemon.species.speciesId === Species.MEWTWO || pokemon.species.speciesId === Species.MEW) { + return "battle_legendary_kanto"; + } + if (pokemon.species.speciesId === Species.RAIKOU) { + return "battle_legendary_raikou"; + } + if (pokemon.species.speciesId === Species.ENTEI) { + return "battle_legendary_entei"; + } + if (pokemon.species.speciesId === Species.SUICUNE) { + return "battle_legendary_suicune"; + } + if (pokemon.species.speciesId === Species.LUGIA) { + return "battle_legendary_lugia"; + } + if (pokemon.species.speciesId === Species.HO_OH) { + return "battle_legendary_ho_oh"; + } + if (pokemon.species.speciesId === Species.REGIROCK || pokemon.species.speciesId === Species.REGICE || pokemon.species.speciesId === Species.REGISTEEL || pokemon.species.speciesId === Species.REGIGIGAS || pokemon.species.speciesId === Species.REGIELEKI || pokemon.species.speciesId === Species.REGIDRAGO) { + return "battle_legendary_regis_g6"; + } + if (pokemon.species.speciesId === Species.GROUDON || pokemon.species.speciesId === Species.KYOGRE) { + return "battle_legendary_gro_kyo"; + } + if (pokemon.species.speciesId === Species.RAYQUAZA) { + return "battle_legendary_rayquaza"; + } + if (pokemon.species.speciesId === Species.DEOXYS) { + return "battle_legendary_deoxys"; + } + if (pokemon.species.speciesId === Species.UXIE || pokemon.species.speciesId === Species.MESPRIT || pokemon.species.speciesId === Species.AZELF) { + return "battle_legendary_lake_trio"; + } + if (pokemon.species.speciesId === Species.HEATRAN || pokemon.species.speciesId === Species.CRESSELIA || pokemon.species.speciesId === Species.DARKRAI || pokemon.species.speciesId === Species.SHAYMIN) { + return "battle_legendary_sinnoh"; + } + if (pokemon.species.speciesId === Species.DIALGA || pokemon.species.speciesId === Species.PALKIA) { + return "battle_legendary_dia_pal"; + } + if (pokemon.species.speciesId === Species.GIRATINA) { + return "battle_legendary_giratina"; + } + if (pokemon.species.speciesId === Species.ARCEUS) { + return "battle_legendary_arceus"; + } + if (pokemon.species.speciesId === Species.COBALION || pokemon.species.speciesId === Species.TERRAKION || pokemon.species.speciesId === Species.VIRIZION || pokemon.species.speciesId === Species.TORNADUS || pokemon.species.speciesId === Species.THUNDURUS || pokemon.species.speciesId === Species.LANDORUS || pokemon.species.speciesId === Species.KELDEO || pokemon.species.speciesId === Species.MELOETTA || pokemon.species.speciesId === Species.GENESECT) { + return "battle_legendary_unova"; + } + if (pokemon.species.speciesId === Species.KYUREM) { + return "battle_legendary_kyurem"; + } + if (pokemon.species.speciesId === Species.XERNEAS || pokemon.species.speciesId === Species.YVELTAL || pokemon.species.speciesId === Species.ZYGARDE) { + return "battle_legendary_xern_yvel"; + } + if (pokemon.species.speciesId === Species.TAPU_KOKO || pokemon.species.speciesId === Species.TAPU_LELE || pokemon.species.speciesId === Species.TAPU_BULU || pokemon.species.speciesId === Species.TAPU_FINI) { + return "battle_legendary_tapu"; + } + if (pokemon.species.speciesId === Species.COSMOG || pokemon.species.speciesId === Species.COSMOEM || pokemon.species.speciesId === Species.SOLGALEO || pokemon.species.speciesId === Species.LUNALA || pokemon.species.speciesId === Species.NECROZMA) { + return "battle_legendary_sol_lun"; + } + if (pokemon.species.speciesId === Species.NIHILEGO || pokemon.species.speciesId === Species.BUZZWOLE || pokemon.species.speciesId === Species.PHEROMOSA || pokemon.species.speciesId === Species.XURKITREE || pokemon.species.speciesId === Species.CELESTEELA || pokemon.species.speciesId === Species.KARTANA || pokemon.species.speciesId === Species.GUZZLORD || pokemon.species.speciesId === Species.POIPOLE || pokemon.species.speciesId === Species.NAGANADEL || pokemon.species.speciesId === Species.STAKATAKA || pokemon.species.speciesId === Species.BLACEPHALON) { + return "battle_legendary_ub"; + } + if (pokemon.species.speciesId === Species.ZACIAN || pokemon.species.speciesId === Species.ZAMAZENTA) { + return "battle_legendary_zac_zam"; + } + if (pokemon.species.speciesId === Species.GLASTRIER || pokemon.species.speciesId === Species.SPECTRIER) { + return "battle_legendary_glas_spec"; + } + if (pokemon.species.speciesId === Species.CALYREX) { + return "battle_legendary_calyrex"; + } + if (pokemon.species.speciesId === Species.GALAR_ARTICUNO || pokemon.species.speciesId === Species.GALAR_ZAPDOS || pokemon.species.speciesId === Species.GALAR_MOLTRES) { + return "battle_legendary_birds_galar"; + } + if (pokemon.species.speciesId === Species.WO_CHIEN || pokemon.species.speciesId === Species.CHIEN_PAO || pokemon.species.speciesId === Species.TING_LU || pokemon.species.speciesId === Species.CHI_YU) { + return "battle_legendary_ruinous"; + } + if (pokemon.species.speciesId === Species.OKIDOGI || pokemon.species.speciesId === Species.MUNKIDORI || pokemon.species.speciesId === Species.FEZANDIPITI) { + return "battle_legendary_loyal_three"; + } + if (pokemon.species.speciesId === Species.OGERPON) { + return "battle_legendary_ogerpon"; + } + if (pokemon.species.speciesId === Species.TERAPAGOS) { + return "battle_legendary_terapagos"; + } + if (pokemon.species.speciesId === Species.PECHARUNT) { + return "battle_legendary_pecharunt"; + } + if (pokemon.species.legendary) { + return "battle_legendary_res_zek"; + } return "battle_legendary_unova"; } - if (pokemon.species.speciesId === Species.RESHIRAM || pokemon.species.speciesId === Species.ZEKROM) { - return "battle_legendary_res_zek"; - } - if (pokemon.species.speciesId === Species.KYUREM) { - return "battle_legendary_kyurem"; - } - if (pokemon.species.legendary) { - return "battle_legendary_res_zek"; - } - return "battle_legendary_unova"; } } @@ -322,11 +420,11 @@ function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[]): Get }; } -interface FixedBattleConfigs { +export interface FixedBattleConfigs { [key: integer]: FixedBattleConfig } -export const fixedBattles: FixedBattleConfigs = { +export const classicFixedBattles: FixedBattleConfigs = { [5]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), [8]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) @@ -340,13 +438,13 @@ export const fixedBattles: FixedBattleConfigs = { [145]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_5, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), [182]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, [ TrainerType.HALA, TrainerType.MOLAYNE ],TrainerType.MARNIE_ELITE, TrainerType.RIKA, TrainerType.CRISPIN ])), + .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, [ TrainerType.HALA, TrainerType.MOLAYNE ], TrainerType.MARNIE_ELITE, TrainerType.RIKA, TrainerType.CRISPIN ])), [184]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY, TrainerType.AMARYS ])), [186]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182) - .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, [TrainerType.BEA_ELITE,TrainerType.ALLISTER_ELITE], TrainerType.LARRY_ELITE, TrainerType.LACEY ])), + .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, [ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ], TrainerType.LARRY_ELITE, TrainerType.LACEY ])), [188]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182) - .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI,TrainerType.RAIHAN_ELITE, TrainerType.HASSEL, TrainerType.DRAYTON ])), + .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL, TrainerType.DRAYTON ])), [190]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BLUE, [ TrainerType.RED, TrainerType.LANCE_CHAMPION ], [ TrainerType.STEVEN, TrainerType.WALLACE ], TrainerType.CYNTHIA, [ TrainerType.ALDER, TrainerType.IRIS ], TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, [ TrainerType.GEETA, TrainerType.NEMONA ], TrainerType.KIERAN ])), [195]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) diff --git a/src/data/ability.ts b/src/data/ability.ts index 7162bab52bc..ae607d253c1 100755 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2949,6 +2949,7 @@ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { /** * Checks if enemy Pokemon is trapped by an Arena Trap-esque ability * If the enemy is a Ghost type, it is not trapped + * If the enemy has the ability Run Away, it is not trapped. * If the user has Magnet Pull and the enemy is not a Steel type, it is not trapped. * If the user has Arena Trap and the enemy is not grounded, it is not trapped. * @param pokemon The {@link Pokemon} with this {@link AbAttr} @@ -2963,6 +2964,9 @@ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { if (otherPokemon.getTypes(true).includes(Type.GHOST) || (otherPokemon.getTypes(true).includes(Type.STELLAR) && otherPokemon.getTypes().includes(Type.GHOST))) { trapped.value = false; return false; + } else if (otherPokemon.hasAbility(Abilities.RUN_AWAY)) { + trapped.value = false; + return false; } trapped.value = true; return true; diff --git a/src/data/challenge.ts b/src/data/challenge.ts new file mode 100644 index 00000000000..916f59ab2c2 --- /dev/null +++ b/src/data/challenge.ts @@ -0,0 +1,577 @@ +import * as Utils from "../utils"; +import { Challenges } from "./enums/challenges"; +import i18next from "#app/plugins/i18n.js"; +import { GameData } from "#app/system/game-data.js"; +import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./pokemon-species"; +import Pokemon from "#app/field/pokemon.js"; +import { BattleType, FixedBattleConfig } from "#app/battle.js"; +import { TrainerType } from "./enums/trainer-type"; +import Trainer, { TrainerVariant } from "#app/field/trainer.js"; +import { GameMode } from "#app/game-mode.js"; +import { Species } from "./enums/species"; +import { Type } from "./type"; + +/** + * An enum for all the challenge types. The parameter entries on these describe the + * parameters to use when calling the applyChallenges function. + */ +export enum ChallengeType { + /** + * Challenges which modify what starters you can choose + * @param args [0] {@link PokemonSpecies} The species to check + * [1] {@link Utils.BooleanHolder} Sets to false if illegal, pass in true. + */ + STARTER_CHOICE, + /** + * Challenges which modify how many starter points you have + * @param args [0] {@link Utils.NumberHolder} The amount of starter points you have + */ + STARTER_POINTS, + /** + * Challenges which modify your starters in some way + * Not Fully Implemented + */ + STARTER_MODIFY, + /** + * Challenges which limit which pokemon you can have in battle. + * @param args [0] {@link Pokemon} The pokemon to check + * [1] {@link Utils.BooleanHolder} Sets to false if illegal, pass in true. + */ + POKEMON_IN_BATTLE, + /** + * Adds or modifies the fixed battles in a run + * @param args [0] integer The wave to get a battle for + * [1] {@link FixedBattleConfig} A new fixed battle. It'll be modified if a battle exists. + */ + FIXED_BATTLES, +} + +/** + * A challenge object. Exists only to serve as a base class. + */ +export abstract class Challenge { + public id: Challenges; // The id of the challenge + + public value: integer; // The "strength" of the challenge, all challenges have a numerical value. + public maxValue: integer; // The maximum strength of the challenge. + public severity: integer; // The current severity of the challenge. Some challenges have multiple severities in addition to strength. + public maxSeverity: integer; // The maximum severity of the challenge. + + public conditions: ChallengeCondition[]; + public challengeTypes: ChallengeType[]; + + /** + * @param {Challenges} id The enum value for the challenge + */ + constructor(id: Challenges, maxValue: integer = Number.MAX_SAFE_INTEGER) { + this.id = id; + + this.value = 0; + this.maxValue = maxValue; + this.severity = 0; + this.maxSeverity = 0; + this.conditions = []; + this.challengeTypes = []; + } + + /** + * Reset the challenge to a base state. + */ + reset(): void { + this.value = 0; + this.severity = 0; + } + + /** + * Gets the localisation key for the challenge + * @returns The i18n key for this challenge + */ + geti18nKey(): string { + return Challenges[this.id].split("_").map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); + } + + /** + * Used for unlockable challenges to check if they're unlocked. + * @param {GameData} data The save data. + * @returns {boolean} Whether this challenge is unlocked. + */ + isUnlocked(data: GameData): boolean { + return this.conditions.every(f => f(data)); + } + + /** + * Adds an unlock condition to this challenge. + * @param {ChallengeCondition} condition The condition to add. + * @returns {Challenge} This challenge + */ + condition(condition: ChallengeCondition): Challenge { + this.conditions.push(condition); + + return this; + } + + /** + * If this challenge is of a particular type + * @param {ChallengeType} challengeType The challenge type to check. + * @returns {Challenge} This challenge + */ + isOfType(challengeType: ChallengeType): boolean { + return this.challengeTypes.some(c => c === challengeType); + } + + /** + * Adds a challenge type to this challenge. + * @param {ChallengeType} challengeType The challenge type to add. + * @returns {Challenge} This challenge + */ + addChallengeType(challengeType: ChallengeType): Challenge { + this.challengeTypes.push(challengeType); + + return this; + } + + /** + * @returns {string} The localised name of this challenge. + */ + getName(): string { + return i18next.t(`challenges:${this.geti18nKey()}.name`); + } + + /** + * Returns the textual representation of a challenge's current value. + * @param {value} overrideValue The value to check for. If undefined, gets the current value. + * @returns {string} The localised name for the current value. + */ + getValue(overrideValue?: integer): string { + if (overrideValue === undefined) { + overrideValue = this.value; + } + return i18next.t(`challenges:${this.geti18nKey()}.value.${this.value}`); + } + + /** + * Returns the description of a challenge's current value. + * @param {value} overrideValue The value to check for. If undefined, gets the current value. + * @returns {string} The localised description for the current value. + */ + getDescription(overrideValue?: integer): string { + if (overrideValue === undefined) { + overrideValue = this.value; + } + return i18next.t(`challenges:${this.geti18nKey()}.desc.${this.value}`); + } + + /** + * Increase the value of the challenge + * @returns {boolean} Returns true if the value changed + */ + increaseValue(): boolean { + if (this.value < this.maxValue) { + this.value = Math.min(this.value + 1, this.maxValue); + return true; + } + return false; + } + + /** + * Decrease the value of the challenge + * @returns {boolean} Returns true if the value changed + */ + decreaseValue(): boolean { + if (this.value > 0) { + this.value = Math.max(this.value - 1, 0); + return true; + } + return false; + } + + /** + * Whether to allow choosing this challenge's severity. + */ + hasSeverity(): boolean { + return this.value !== 0 && this.maxSeverity > 0; + } + + /** + * Decrease the severity of the challenge + * @returns {boolean} Returns true if the value changed + */ + decreaseSeverity(): boolean { + if (this.severity > 0) { + this.severity = Math.max(this.severity - 1, 0); + return true; + } + return false; + } + + /** + * Increase the severity of the challenge + * @returns {boolean} Returns true if the value changed + */ + increaseSeverity(): boolean { + if (this.severity < this.maxSeverity) { + this.severity = Math.min(this.severity + 1, this.maxSeverity); + return true; + } + return false; + } + + /** + * Gets the "difficulty" value of this challenge. + * @returns {integer} The difficulty value. + */ + getDifficulty(): integer { + return this.value; + } + + /** + * Gets the minimum difficulty added by this challenge. + * @returns {integer} The difficulty value. + */ + getMinDifficulty(): integer { + return 0; + } + + /** + * Modifies the data or game state in some way to apply the challenge. + * @param {ChallengeType} challengeType Which challenge type this is being applied for. + * @param args Irrelevant. See the specific challenge's apply function for additional information. + */ + abstract apply(challengeType: ChallengeType, args: any[]): boolean; + + /** + * Clones a challenge, either from another challenge or json. Chainable. + * @param {Challenge | any} source The source challenge of json. + * @returns {Challenge} This challenge. + */ + static loadChallenge(source: Challenge | any): Challenge { + throw new Error("Method not implemented! Use derived class"); + } +} + +type ChallengeCondition = (data: GameData) => boolean; + +/** + * Implements a mono generation challenge. + */ +export class SingleGenerationChallenge extends Challenge { + constructor() { + super(Challenges.SINGLE_GENERATION, 9); + this.addChallengeType(ChallengeType.STARTER_CHOICE); + this.addChallengeType(ChallengeType.POKEMON_IN_BATTLE); + this.addChallengeType(ChallengeType.FIXED_BATTLES); + } + + apply(challengeType: ChallengeType, args: any[]): boolean { + if (this.value === 0) { + return false; + } + + /** + * We have special code below for victini because it is classed as a generation 4 pokemon in the code + * despite being a generation 5 pokemon. This is due to UI constraints, the starter select screen has + * no more room for pokemon so victini is put in the gen 4 section instead. This code just overrides the + * normal generation check to correctly treat victini as gen 5. + */ + switch (challengeType) { + case ChallengeType.STARTER_CHOICE: + const species = args[0] as PokemonSpecies; + const isValidStarter = args[1] as Utils.BooleanHolder; + const starterGeneration = species.speciesId === Species.VICTINI ? 5 : species.generation; + if (starterGeneration !== this.value) { + isValidStarter.value = false; + return true; + } + break; + case ChallengeType.POKEMON_IN_BATTLE: + const pokemon = args[0] as Pokemon; + const isValidPokemon = args[1] as Utils.BooleanHolder; + const baseGeneration = pokemon.species.speciesId === Species.VICTINI ? 5 : getPokemonSpecies(pokemon.species.speciesId).generation; + const fusionGeneration = pokemon.isFusion() ? pokemon.fusionSpecies.speciesId === Species.VICTINI ? 5 : getPokemonSpecies(pokemon.fusionSpecies.speciesId).generation : 0; + if (pokemon.isPlayer() && (baseGeneration !== this.value || (pokemon.isFusion() && fusionGeneration !== this.value))) { + isValidPokemon.value = false; + return true; + } + break; + case ChallengeType.FIXED_BATTLES: + const waveIndex = args[0] as integer; + const battleConfig = args[1] as FixedBattleConfig; + let trainerTypes: TrainerType[] = []; + switch (waveIndex) { + case 182: + trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]),TrainerType.MARNIE_ELITE, TrainerType.RIKA ]; + break; + case 184: + trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ]; + break; + case 186: + trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([TrainerType.BEA_ELITE,TrainerType.ALLISTER_ELITE]), TrainerType.LARRY_ELITE ]; + break; + case 188: + trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ]; + break; + case 190: + trainerTypes = [ TrainerType.BLUE, Utils.randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), Utils.randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, Utils.randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ]; + break; + } + if (trainerTypes.length === 0) { + return false; + } else { + battleConfig.setBattleType(BattleType.TRAINER).setGetTrainerFunc(scene => new Trainer(scene, trainerTypes[this.value - 1], TrainerVariant.DEFAULT)); + return true; + } + } + return false; + } + + /** + * @overrides + */ + getDifficulty(): number { + return this.value > 0 ? 1 : 0; + } + + static loadChallenge(source: SingleGenerationChallenge | any): SingleGenerationChallenge { + const newChallenge = new SingleGenerationChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + +interface monotypeOverride { + /** The species to override */ + species: Species; + /** The type to count as */ + type: Type; + /** If part of a fusion, should we check the fused species instead of the base species? */ + fusion: boolean; +} + +/** + * Implements a mono type challenge. + */ +export class SingleTypeChallenge extends Challenge { + private static TYPE_OVERRIDES: monotypeOverride[] = [ + {species: Species.MELOETTA, type: Type.PSYCHIC, fusion: true}, + {species: Species.CASTFORM, type: Type.NORMAL, fusion: false}, + ]; + + constructor() { + super(Challenges.SINGLE_TYPE, 18); + this.addChallengeType(ChallengeType.STARTER_CHOICE); + this.addChallengeType(ChallengeType.POKEMON_IN_BATTLE); + } + + apply(challengeType: ChallengeType, args: any[]): boolean { + if (this.value === 0) { + return false; + } + + switch (challengeType) { + case ChallengeType.STARTER_CHOICE: + const species = args[0] as PokemonSpecies; + const isValidStarter = args[1] as Utils.BooleanHolder; + if (!species.isOfType(this.value - 1)) { + isValidStarter.value = false; + return true; + } + break; + case ChallengeType.POKEMON_IN_BATTLE: + const pokemon = args[0] as Pokemon; + const isValidPokemon = args[1] as Utils.BooleanHolder; + if (pokemon.isPlayer() && !pokemon.isOfType(this.value - 1, false, false, true) + && !SingleTypeChallenge.TYPE_OVERRIDES.some(o => o.type === (this.value - 1) && (pokemon.isFusion() && o.fusion ? pokemon.fusionSpecies : pokemon.species).speciesId === o.species)) { + isValidPokemon.value = false; + return true; + } + break; + } + return false; + } + + /** + * @overrides + */ + getDifficulty(): number { + return this.value > 0 ? 1 : 0; + } + + static loadChallenge(source: SingleTypeChallenge | any): SingleTypeChallenge { + const newChallenge = new SingleTypeChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + +/** + * Implements a fresh start challenge. + */ +export class FreshStartChallenge extends Challenge { + constructor() { + super(Challenges.FRESH_START, 1); + this.addChallengeType(ChallengeType.STARTER_CHOICE); + this.addChallengeType(ChallengeType.STARTER_MODIFY); + } + + apply(challengeType: ChallengeType, args: any[]): boolean { + if (this.value === 0) { + return false; + } + + switch (challengeType) { + case ChallengeType.STARTER_CHOICE: + const species = args[0] as PokemonSpecies; + const isValidStarter = args[1] as Utils.BooleanHolder; + if (species) { + isValidStarter.value = false; + return true; + } + break; + } + return false; + } + + /** + * @overrides + */ + getDifficulty(): number { + return 0; + } + + static loadChallenge(source: FreshStartChallenge | any): FreshStartChallenge { + const newChallenge = new FreshStartChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + +/** + * Lowers the amount of starter points available. + */ +export class LowerStarterMaxCostChallenge extends Challenge { + constructor() { + super(Challenges.LOWER_MAX_STARTER_COST, 9); + this.addChallengeType(ChallengeType.STARTER_CHOICE); + } + + /** + * @override + */ + getValue(overrideValue?: integer): string { + if (overrideValue === undefined) { + overrideValue = this.value; + } + return (10 - overrideValue).toString(); + } + + apply(challengeType: ChallengeType, args: any[]): boolean { + if (this.value === 0) { + return false; + } + + switch (challengeType) { + case ChallengeType.STARTER_CHOICE: + const species = args[0] as PokemonSpecies; + const isValid = args[1] as Utils.BooleanHolder; + if (speciesStarters[species.speciesId] > 10 - this.value) { + isValid.value = false; + return true; + } + } + return false; + } + + static loadChallenge(source: LowerStarterMaxCostChallenge | any): LowerStarterMaxCostChallenge { + const newChallenge = new LowerStarterMaxCostChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + +/** + * Lowers the maximum cost of starters available. + */ +export class LowerStarterPointsChallenge extends Challenge { + constructor() { + super(Challenges.LOWER_STARTER_POINTS, 9); + this.addChallengeType(ChallengeType.STARTER_POINTS); + } + + /** + * @override + */ + getValue(overrideValue?: integer): string { + if (overrideValue === undefined) { + overrideValue = this.value; + } + return (10 - overrideValue).toString(); + } + + apply(challengeType: ChallengeType, args: any[]): boolean { + if (this.value === 0) { + return false; + } + + switch (challengeType) { + case ChallengeType.STARTER_POINTS: + const points = args[0] as Utils.NumberHolder; + points.value -= this.value; + return true; + } + return false; + } + + static loadChallenge(source: LowerStarterPointsChallenge | any): LowerStarterPointsChallenge { + const newChallenge = new LowerStarterPointsChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + +/** + * Apply all challenges of a given challenge type. + * @param {BattleScene} scene The current scene + * @param {ChallengeType} challengeType What challenge type to apply + * @param {any[]} args Any args for that challenge type + * @returns {boolean} True if any challenge was successfully applied. + */ +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean { + let ret = false; + gameMode.challenges.forEach(v => { + if (v.isOfType(challengeType)) { + ret ||= v.apply(challengeType, args); + } + }); + return ret; +} + +export function copyChallenge(source: Challenge | any): Challenge { + switch (source.id) { + case Challenges.SINGLE_GENERATION: + return SingleGenerationChallenge.loadChallenge(source); + case Challenges.SINGLE_TYPE: + return SingleTypeChallenge.loadChallenge(source); + case Challenges.LOWER_MAX_STARTER_COST: + return LowerStarterMaxCostChallenge.loadChallenge(source); + case Challenges.LOWER_STARTER_POINTS: + return LowerStarterPointsChallenge.loadChallenge(source); + } + throw new Error("Unknown challenge copied"); +} + +export const allChallenges: Challenge[] = []; + +export function initChallenges() { + allChallenges.push( + new SingleGenerationChallenge(), + new SingleTypeChallenge(), + // new LowerStarterMaxCostChallenge(), + // new LowerStarterPointsChallenge(), + // new FreshStartChallenge() + ); +} diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index cb4bfddc685..c9b097bfcc0 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -1,6 +1,5 @@ import BattleScene from "../battle-scene"; import { PlayerPokemon } from "../field/pokemon"; -import { GameModes, gameModes } from "../game-mode"; import { Starter } from "../ui/starter-select-ui-handler"; import * as Utils from "../utils"; import { Species } from "./enums/species"; @@ -29,7 +28,7 @@ export function getDailyRunStarters(scene: BattleScene, seed: string): Starter[] const starters: Starter[] = []; scene.executeWithSeedOffset(() => { - const startingLevel = gameModes[GameModes.DAILY].getStartingLevel(); + const startingLevel = scene.gameMode.getStartingLevel(); if (/\d{18}$/.test(seed)) { for (let s = 0; s < 3; s++) { diff --git a/src/data/enums/challenges.ts b/src/data/enums/challenges.ts new file mode 100644 index 00000000000..690e1cdc32d --- /dev/null +++ b/src/data/enums/challenges.ts @@ -0,0 +1,7 @@ +export enum Challenges { + SINGLE_GENERATION, + SINGLE_TYPE, + LOWER_MAX_STARTER_COST, + LOWER_STARTER_POINTS, + FRESH_START +} diff --git a/src/data/move.ts b/src/data/move.ts index 868a75d9ea8..68083d163f1 100755 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4484,7 +4484,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { } const party = player ? user.scene.getParty() : user.scene.getEnemyParty(); - return (!player && !user.scene.currentBattle.battleType) || party.filter(p => !p.isFainted() && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > user.scene.currentBattle.getBattlerCount(); + return (!player && !user.scene.currentBattle.battleType) || party.filter(p => p.isAllowedInBattle() && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > user.scene.currentBattle.getBattlerCount(); }; } @@ -6073,7 +6073,7 @@ export function initMoves() { ], true) .attr(RemoveArenaTrapAttr), new StatusMove(Moves.SWEET_SCENT, Type.NORMAL, 100, 20, -1, 0, 2) - .attr(StatChangeAttr, BattleStat.EVA, -1) + .attr(StatChangeAttr, BattleStat.EVA, -2) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.IRON_TAIL, Type.STEEL, MoveCategory.PHYSICAL, 100, 75, 15, 30, 0, 2) .attr(StatChangeAttr, BattleStat.DEF, -1), diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index 8a955afa746..5d382595126 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -187,6 +187,7 @@ export class TrainerConfig { public isBoss: boolean = false; public hasStaticParty: boolean = false; public useSameSeedForAllMembers: boolean = false; + public mixedBattleBgm: string; public battleBgm: string; public encounterBgm: string; public femaleEncounterBgm: string; @@ -440,6 +441,11 @@ export class TrainerConfig { return this; } + setMixedBattleBgm(mixedBattleBgm: string): TrainerConfig { + this.mixedBattleBgm = mixedBattleBgm; + return this; + } + setBattleBgm(battleBgm: string): TrainerConfig { this.battleBgm = battleBgm; return this; @@ -605,7 +611,7 @@ export class TrainerConfig { this.setMoneyMultiplier(3.25); this.setBoss(); this.setStaticParty(); - this.setBattleBgm("battle_elite"); + this.setBattleBgm("battle_unova_elite"); this.setVictoryBgm("victory_gym"); this.setGenModifiersFunc(party => getRandomTeraModifiers(party, 2, specialtyTypes.length ? specialtyTypes : null)); @@ -1142,76 +1148,76 @@ export const trainerConfigs: TrainerConfigs = { [TrainerType.BRYCEN]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["BRYCEN"],true, Type.ICE), [TrainerType.DRAYDEN]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["DRAYDEN"],true, Type.DRAGON), [TrainerType.MARLON]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["MARLON"],true, Type.WATER), - [TrainerType.VIOLA]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["VIOLA"],false, Type.BUG), - [TrainerType.GRANT]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["GRANT"],true, Type.ROCK), - [TrainerType.KORRINA]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["KORRINA"],false, Type.FIGHTING), - [TrainerType.RAMOS]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["RAMOS"],true, Type.GRASS), - [TrainerType.CLEMONT]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["CLEMONT"],true, Type.ELECTRIC), - [TrainerType.VALERIE]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["VALERIE"],false, Type.FAIRY), - [TrainerType.OLYMPIA]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["OLYMPIA"],false, Type.PSYCHIC), - [TrainerType.WULFRIC]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["WULFRIC"],true, Type.ICE), - [TrainerType.MILO]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["MILO"],true, Type.GRASS), - [TrainerType.NESSA]: new TrainerConfig(++t).setName("Nessa").initForGymLeader(signatureSpecies["NESSA"],false, Type.WATER), - [TrainerType.KABU]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["KABU"],true, Type.FIRE), - [TrainerType.BEA]: new TrainerConfig(++t).setName("Bea").initForGymLeader(signatureSpecies["BEA"],false, Type.FIGHTING), - [TrainerType.ALLISTER]: new TrainerConfig(++t).setName("Allister").initForGymLeader(signatureSpecies["ALLISTER"],true, Type.GHOST), - [TrainerType.OPAL]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["OPAL"],false, Type.FAIRY), - [TrainerType.BEDE]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["BEDE"],true, Type.FAIRY), - [TrainerType.GORDIE]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["GORDIE"],true, Type.ROCK), - [TrainerType.MELONY]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["MELONY"],false, Type.ICE), - [TrainerType.PIERS]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["PIERS"],true, Type.DARK).setHasDouble("piers_marnie_double").setDoubleTrainerType(TrainerType.MARNIE).setDoubleTitle("gym_leader_double"), - [TrainerType.MARNIE]: new TrainerConfig(++t).setName("Marnie").initForGymLeader(signatureSpecies["MARNIE"],false, Type.DARK).setHasDouble("marnie_piers_double").setDoubleTrainerType(TrainerType.PIERS).setDoubleTitle("gym_leader_double"), - [TrainerType.RAIHAN]: new TrainerConfig(++t).setName("Raihan").initForGymLeader(signatureSpecies["RAIHAN"],true, Type.DRAGON), - [TrainerType.KATY]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["KATY"],false, Type.BUG), - [TrainerType.BRASSIUS]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["BRASSIUS"],true, Type.GRASS), - [TrainerType.IONO]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["IONO"],false, Type.ELECTRIC), - [TrainerType.KOFU]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["KOFU"],true, Type.WATER), - [TrainerType.LARRY]: new TrainerConfig(++t).setName("Larry").initForGymLeader(signatureSpecies["LARRY"],true, Type.NORMAL), - [TrainerType.RYME]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["RYME"],false, Type.GHOST), - [TrainerType.TULIP]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["TULIP"],false, Type.PSYCHIC), - [TrainerType.GRUSHA]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["GRUSHA"],true, Type.ICE), + [TrainerType.VIOLA]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["VIOLA"],false, Type.BUG).setMixedBattleBgm("battle_kalos_gym"), + [TrainerType.GRANT]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["GRANT"],true, Type.ROCK).setMixedBattleBgm("battle_kalos_gym"), + [TrainerType.KORRINA]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["KORRINA"],false, Type.FIGHTING).setMixedBattleBgm("battle_kalos_gym"), + [TrainerType.RAMOS]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["RAMOS"],true, Type.GRASS).setMixedBattleBgm("battle_kalos_gym"), + [TrainerType.CLEMONT]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["CLEMONT"],true, Type.ELECTRIC).setMixedBattleBgm("battle_kalos_gym"), + [TrainerType.VALERIE]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["VALERIE"],false, Type.FAIRY).setMixedBattleBgm("battle_kalos_gym"), + [TrainerType.OLYMPIA]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["OLYMPIA"],false, Type.PSYCHIC).setMixedBattleBgm("battle_kalos_gym"), + [TrainerType.WULFRIC]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["WULFRIC"],true, Type.ICE).setMixedBattleBgm("battle_kalos_gym"), + [TrainerType.MILO]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["MILO"],true, Type.GRASS).setMixedBattleBgm("battle_galar_gym"), + [TrainerType.NESSA]: new TrainerConfig(++t).setName("Nessa").initForGymLeader(signatureSpecies["NESSA"],false, Type.WATER).setMixedBattleBgm("battle_galar_gym"), + [TrainerType.KABU]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["KABU"],true, Type.FIRE).setMixedBattleBgm("battle_galar_gym"), + [TrainerType.BEA]: new TrainerConfig(++t).setName("Bea").initForGymLeader(signatureSpecies["BEA"],false, Type.FIGHTING).setMixedBattleBgm("battle_galar_gym"), + [TrainerType.ALLISTER]: new TrainerConfig(++t).setName("Allister").initForGymLeader(signatureSpecies["ALLISTER"],true, Type.GHOST).setMixedBattleBgm("battle_galar_gym"), + [TrainerType.OPAL]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["OPAL"],false, Type.FAIRY).setMixedBattleBgm("battle_galar_gym"), + [TrainerType.BEDE]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["BEDE"],true, Type.FAIRY).setMixedBattleBgm("battle_galar_gym"), + [TrainerType.GORDIE]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["GORDIE"],true, Type.ROCK).setMixedBattleBgm("battle_galar_gym"), + [TrainerType.MELONY]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["MELONY"],false, Type.ICE).setMixedBattleBgm("battle_galar_gym"), + [TrainerType.PIERS]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["PIERS"],true, Type.DARK).setHasDouble("piers_marnie_double").setDoubleTrainerType(TrainerType.MARNIE).setDoubleTitle("gym_leader_double").setMixedBattleBgm("battle_galar_gym"), + [TrainerType.MARNIE]: new TrainerConfig(++t).setName("Marnie").initForGymLeader(signatureSpecies["MARNIE"],false, Type.DARK).setHasDouble("marnie_piers_double").setDoubleTrainerType(TrainerType.PIERS).setDoubleTitle("gym_leader_double").setMixedBattleBgm("battle_galar_gym"), + [TrainerType.RAIHAN]: new TrainerConfig(++t).setName("Raihan").initForGymLeader(signatureSpecies["RAIHAN"],true, Type.DRAGON).setMixedBattleBgm("battle_galar_gym"), + [TrainerType.KATY]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["KATY"],false, Type.BUG).setMixedBattleBgm("battle_paldea_gym"), + [TrainerType.BRASSIUS]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["BRASSIUS"],true, Type.GRASS).setMixedBattleBgm("battle_paldea_gym"), + [TrainerType.IONO]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["IONO"],false, Type.ELECTRIC).setMixedBattleBgm("battle_paldea_gym"), + [TrainerType.KOFU]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["KOFU"],true, Type.WATER).setMixedBattleBgm("battle_paldea_gym"), + [TrainerType.LARRY]: new TrainerConfig(++t).setName("Larry").initForGymLeader(signatureSpecies["LARRY"],true, Type.NORMAL).setMixedBattleBgm("battle_paldea_gym"), + [TrainerType.RYME]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["RYME"],false, Type.GHOST).setMixedBattleBgm("battle_paldea_gym"), + [TrainerType.TULIP]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["TULIP"],false, Type.PSYCHIC).setMixedBattleBgm("battle_paldea_gym"), + [TrainerType.GRUSHA]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["GRUSHA"],true, Type.ICE).setMixedBattleBgm("battle_paldea_gym"), - [TrainerType.LORELEI]: new TrainerConfig((t = TrainerType.LORELEI)).initForEliteFour(signatureSpecies["LORELEI"],false, Type.ICE), - [TrainerType.BRUNO]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["BRUNO"], true, Type.FIGHTING), - [TrainerType.AGATHA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["AGATHA"], false,Type.GHOST), - [TrainerType.LANCE]: new TrainerConfig(++t).setName("Lance").initForEliteFour(signatureSpecies["LANCE"],true, Type.DRAGON), - [TrainerType.WILL]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["WILL"],true, Type.PSYCHIC), - [TrainerType.KOGA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["KOGA"], true, Type.POISON), - [TrainerType.KAREN]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["KAREN"],false, Type.DARK), - [TrainerType.SIDNEY]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["SIDNEY"],true, Type.DARK), - [TrainerType.PHOEBE]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["PHOEBE"],false, Type.GHOST), - [TrainerType.GLACIA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["GLACIA"],false, Type.ICE), - [TrainerType.DRAKE]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["DRAKE"],true, Type.DRAGON), - [TrainerType.AARON]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["AARON"],true, Type.BUG), - [TrainerType.BERTHA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["BERTHA"],false, Type.GROUND), - [TrainerType.FLINT]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["FLINT"],true, Type.FIRE), - [TrainerType.LUCIAN]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["LUCIAN"], true,Type.PSYCHIC), + [TrainerType.LORELEI]: new TrainerConfig((t = TrainerType.LORELEI)).initForEliteFour(signatureSpecies["LORELEI"],false, Type.ICE).setBattleBgm("battle_kanto_gym"), + [TrainerType.BRUNO]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["BRUNO"], true, Type.FIGHTING).setBattleBgm("battle_kanto_gym"), + [TrainerType.AGATHA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["AGATHA"], false,Type.GHOST).setBattleBgm("battle_kanto_gym"), + [TrainerType.LANCE]: new TrainerConfig(++t).setName("Lance").initForEliteFour(signatureSpecies["LANCE"],true, Type.DRAGON).setBattleBgm("battle_kanto_gym"), + [TrainerType.WILL]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["WILL"],true, Type.PSYCHIC).setBattleBgm("battle_johto_gym"), + [TrainerType.KOGA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["KOGA"], true, Type.POISON).setBattleBgm("battle_johto_gym"), + [TrainerType.KAREN]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["KAREN"],false, Type.DARK).setBattleBgm("battle_johto_gym"), + [TrainerType.SIDNEY]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["SIDNEY"],true, Type.DARK).setMixedBattleBgm("battle_hoenn_elite"), + [TrainerType.PHOEBE]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["PHOEBE"],false, Type.GHOST).setMixedBattleBgm("battle_hoenn_elite"), + [TrainerType.GLACIA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["GLACIA"],false, Type.ICE).setMixedBattleBgm("battle_hoenn_elite"), + [TrainerType.DRAKE]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["DRAKE"],true, Type.DRAGON).setMixedBattleBgm("battle_hoenn_elite"), + [TrainerType.AARON]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["AARON"],true, Type.BUG).setBattleBgm("battle_sinnoh_gym"), + [TrainerType.BERTHA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["BERTHA"],false, Type.GROUND).setBattleBgm("battle_sinnoh_gym"), + [TrainerType.FLINT]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["FLINT"],true, Type.FIRE).setBattleBgm("battle_sinnoh_gym"), + [TrainerType.LUCIAN]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["LUCIAN"], true,Type.PSYCHIC).setBattleBgm("battle_sinnoh_gym"), [TrainerType.SHAUNTAL]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["SHAUNTAL"],false, Type.GHOST), [TrainerType.MARSHAL]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["MARSHAL"],true, Type.FIGHTING), [TrainerType.GRIMSLEY]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["GRIMSLEY"],true, Type.DARK), [TrainerType.CAITLIN]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["CAITLIN"],false, Type.PSYCHIC), - [TrainerType.MALVA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["MALVA"], false,Type.FIRE), - [TrainerType.SIEBOLD]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["SIEBOLD"], true,Type.WATER), - [TrainerType.WIKSTROM]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["WIKSTROM"],true, Type.STEEL), - [TrainerType.DRASNA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["DRASNA"],false, Type.DRAGON), - [TrainerType.HALA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["HALA"],true, Type.FIGHTING), - [TrainerType.MOLAYNE]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["MOLAYNE"],true, Type.STEEL), - [TrainerType.OLIVIA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["OLIVIA"],false, Type.ROCK), - [TrainerType.ACEROLA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["ACEROLA"],false, Type.GHOST), - [TrainerType.KAHILI]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["KAHILI"],false, Type.FLYING), - [TrainerType.MARNIE_ELITE]: new TrainerConfig(++t).setName("Marnie").initForEliteFour(signatureSpecies["MARNIE_ELITE"],false, Type.DARK), - [TrainerType.NESSA_ELITE]: new TrainerConfig(++t).setName("Nessa").initForEliteFour(signatureSpecies["NESSA_ELITE"],false, Type.WATER), - [TrainerType.BEA_ELITE]: new TrainerConfig(++t).setName("Bea").initForEliteFour(signatureSpecies["BEA_ELITE"],false, Type.FIGHTING), - [TrainerType.ALLISTER_ELITE]: new TrainerConfig(++t).setName("Allister").initForEliteFour(signatureSpecies["ALLISTER_ELITE"],true, Type.GHOST), - [TrainerType.RAIHAN_ELITE]: new TrainerConfig(++t).setName("Raihan").initForEliteFour(signatureSpecies["RAIHAN_ELITE"],true, Type.DRAGON), - [TrainerType.RIKA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["RIKA"],false, Type.GROUND), - [TrainerType.POPPY]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["POPPY"],false, Type.STEEL), - [TrainerType.LARRY_ELITE]: new TrainerConfig(++t).setName("Larry").initForEliteFour(signatureSpecies["LARRY_ELITE"],true, Type.NORMAL, Type.FLYING), - [TrainerType.HASSEL]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["HASSEL"],true, Type.DRAGON), - [TrainerType.CRISPIN]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["CRISPIN"],true, Type.FIRE), - [TrainerType.AMARYS]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["AMARYS"],false, Type.STEEL), - [TrainerType.LACEY]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["LACEY"],false, Type.FAIRY), - [TrainerType.DRAYTON]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["DRAYTON"],true, Type.DRAGON), + [TrainerType.MALVA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["MALVA"], false,Type.FIRE).setMixedBattleBgm("battle_kalos_elite"), + [TrainerType.SIEBOLD]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["SIEBOLD"], true,Type.WATER).setMixedBattleBgm("battle_kalos_elite"), + [TrainerType.WIKSTROM]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["WIKSTROM"],true, Type.STEEL).setMixedBattleBgm("battle_kalos_elite"), + [TrainerType.DRASNA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["DRASNA"],false, Type.DRAGON).setMixedBattleBgm("battle_kalos_elite"), + [TrainerType.HALA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["HALA"],true, Type.FIGHTING).setMixedBattleBgm("battle_alola_elite"), + [TrainerType.MOLAYNE]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["MOLAYNE"],true, Type.STEEL).setMixedBattleBgm("battle_alola_elite"), + [TrainerType.OLIVIA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["OLIVIA"],false, Type.ROCK).setMixedBattleBgm("battle_alola_elite"), + [TrainerType.ACEROLA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["ACEROLA"],false, Type.GHOST).setMixedBattleBgm("battle_alola_elite"), + [TrainerType.KAHILI]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["KAHILI"],false, Type.FLYING).setMixedBattleBgm("battle_alola_elite"), + [TrainerType.MARNIE_ELITE]: new TrainerConfig(++t).setName("Marnie").initForEliteFour(signatureSpecies["MARNIE_ELITE"],false, Type.DARK).setMixedBattleBgm("battle_galar_elite"), + [TrainerType.NESSA_ELITE]: new TrainerConfig(++t).setName("Nessa").initForEliteFour(signatureSpecies["NESSA_ELITE"],false, Type.WATER).setMixedBattleBgm("battle_galar_elite"), + [TrainerType.BEA_ELITE]: new TrainerConfig(++t).setName("Bea").initForEliteFour(signatureSpecies["BEA_ELITE"],false, Type.FIGHTING).setMixedBattleBgm("battle_galar_elite"), + [TrainerType.ALLISTER_ELITE]: new TrainerConfig(++t).setName("Allister").initForEliteFour(signatureSpecies["ALLISTER_ELITE"],true, Type.GHOST).setMixedBattleBgm("battle_galar_elite"), + [TrainerType.RAIHAN_ELITE]: new TrainerConfig(++t).setName("Raihan").initForEliteFour(signatureSpecies["RAIHAN_ELITE"],true, Type.DRAGON).setMixedBattleBgm("battle_galar_elite"), + [TrainerType.RIKA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["RIKA"],false, Type.GROUND).setMixedBattleBgm("battle_paldea_elite"), + [TrainerType.POPPY]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["POPPY"],false, Type.STEEL).setMixedBattleBgm("battle_paldea_elite"), + [TrainerType.LARRY_ELITE]: new TrainerConfig(++t).setName("Larry").initForEliteFour(signatureSpecies["LARRY_ELITE"],true, Type.NORMAL, Type.FLYING).setMixedBattleBgm("battle_paldea_elite"), + [TrainerType.HASSEL]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["HASSEL"],true, Type.DRAGON).setMixedBattleBgm("battle_paldea_elite"), + [TrainerType.CRISPIN]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["CRISPIN"],true, Type.FIRE).setMixedBattleBgm("battle_bb_elite"), + [TrainerType.AMARYS]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["AMARYS"],false, Type.STEEL).setMixedBattleBgm("battle_bb_elite"), + [TrainerType.LACEY]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["LACEY"],false, Type.FAIRY).setMixedBattleBgm("battle_bb_elite"), + [TrainerType.DRAYTON]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["DRAYTON"],true, Type.DRAGON).setMixedBattleBgm("battle_bb_elite"), [TrainerType.BLUE]: new TrainerConfig((t = TrainerType.BLUE)).initForChampion(signatureSpecies["BLUE"],true).setBattleBgm("battle_kanto_champion").setHasDouble("blue_red_double").setDoubleTrainerType(TrainerType.RED).setDoubleTitle("champion_double") .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.ALAKAZAM], TrainerSlot.TRAINER, true, p => { @@ -1275,7 +1281,7 @@ export const trainerConfigs: TrainerConfigs = { p.formIndex = 1; p.generateAndPopulateMoveset(); })), - [TrainerType.DIANTHA]: new TrainerConfig(++t).initForChampion(signatureSpecies["DIANTHA"],false) + [TrainerType.DIANTHA]: new TrainerConfig(++t).initForChampion(signatureSpecies["DIANTHA"],false).setMixedBattleBgm("battle_kalos_champion") .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.GOURGEIST], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); })) @@ -1283,11 +1289,11 @@ export const trainerConfigs: TrainerConfigs = { p.formIndex = 1; p.generateAndPopulateMoveset(); })), - [TrainerType.HAU]: new TrainerConfig(++t).initForChampion(signatureSpecies["HAU"],true) + [TrainerType.HAU]: new TrainerConfig(++t).initForChampion(signatureSpecies["HAU"],true).setMixedBattleBgm("battle_alola_champion") .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.ALOLA_RAICHU], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); })), - [TrainerType.LEON]: new TrainerConfig(++t).initForChampion(signatureSpecies["LEON"],true) + [TrainerType.LEON]: new TrainerConfig(++t).initForChampion(signatureSpecies["LEON"],true).setMixedBattleBgm("battle_galar_champion") .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.RILLABOOM, Species.CINDERACE, Species.INTELEON], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); })) @@ -1295,15 +1301,15 @@ export const trainerConfigs: TrainerConfigs = { p.formIndex = 3; p.generateAndPopulateMoveset(); })), - [TrainerType.GEETA]: new TrainerConfig(++t).initForChampion(signatureSpecies["GEETA"],false) + [TrainerType.GEETA]: new TrainerConfig(++t).initForChampion(signatureSpecies["GEETA"],false).setMixedBattleBgm("battle_champion_geeta") .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.GLIMMORA], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); })), - [TrainerType.NEMONA]: new TrainerConfig(++t).initForChampion(signatureSpecies["NEMONA"],false) + [TrainerType.NEMONA]: new TrainerConfig(++t).initForChampion(signatureSpecies["NEMONA"],false).setMixedBattleBgm("battle_champion_nemona") .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.LYCANROC], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); })), - [TrainerType.KIERAN]: new TrainerConfig(++t).initForChampion(signatureSpecies["KIERAN"],true) + [TrainerType.KIERAN]: new TrainerConfig(++t).initForChampion(signatureSpecies["KIERAN"],true).setMixedBattleBgm("battle_champion_kieran") .setPartyMemberFunc(0, getRandomPartyMemberFunc([Species.POLIWRATH, Species.POLITOED], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); })), diff --git a/src/data/type.ts b/src/data/type.ts index b2bf8117249..c92416afca9 100644 --- a/src/data/type.ts +++ b/src/data/type.ts @@ -501,6 +501,52 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer): } } +/** + * Retrieve the color corresponding to a specific damage multiplier + * @returns A color or undefined if the default color should be used + */ +export function getTypeDamageMultiplierColor(multiplier: TypeDamageMultiplier, side: "defense" | "offense"): string | undefined { + if (side === "offense") { + switch (multiplier) { + case 0: + return "#929292"; + case 0.125: + return "#FF5500"; + case 0.25: + return "#FF7400"; + case 0.5: + return "#FE8E00"; + case 1: + return undefined; + case 2: + return "#4AA500"; + case 4: + return "#4BB400"; + case 8: + return "#52C200"; + } + } else if (side === "defense") { + switch (multiplier) { + case 0: + return "#B1B100"; + case 0.125: + return "#2DB4FF"; + case 0.25: + return "#00A4FF"; + case 0.5: + return "#0093FF"; + case 1: + return undefined; + case 2: + return "#FE8E00"; + case 4: + return "#FF7400"; + case 8: + return "#FF5500"; + } + } +} + export function getTypeRgb(type: Type): [ integer, integer, integer ] { switch (type) { case Type.NORMAL: diff --git a/src/evolution-phase.ts b/src/evolution-phase.ts index 29382807ccb..c7986f6664f 100644 --- a/src/evolution-phase.ts +++ b/src/evolution-phase.ts @@ -531,6 +531,11 @@ export class EvolutionPhase extends Phase { } export class EndEvolutionPhase extends Phase { + + constructor(scene: BattleScene) { + super(scene); + } + start() { super.start(); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index ff277fc865b..580e873a410 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -48,6 +48,7 @@ import { BerryType } from "../data/enums/berry-type"; import i18next from "../plugins/i18n"; import { speciesEggMoves } from "../data/egg-moves"; import { ModifierTier } from "../modifier/modifier-tier"; +import { applyChallenges, ChallengeType } from "#app/data/challenge.js"; export enum FieldPosition { CENTER, @@ -266,11 +267,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return !this.hp && (!checkStatus || this.status?.effect === StatusEffect.FAINT); } + /** + * Check if this pokemon is both not fainted and allowed to be in battle. + * This is frequently a better alternative to {@link isFainted} + * @returns {boolean} True if pokemon is allowed in battle + */ + isAllowedInBattle(): boolean { + const challengeAllowed = new Utils.BooleanHolder(true); + applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed); + return !this.isFainted() && challengeAllowed.value; + } + isActive(onField?: boolean): boolean { if (!this.scene) { return false; } - return !this.isFainted() && !!this.scene && (!onField || this.isOnField()); + return this.isAllowedInBattle() && !!this.scene && (!onField || this.isOnField()); } getDexAttr(): bigint { @@ -845,11 +857,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!ignoreOverride && this.summonData?.types) { this.summonData.types.forEach(t => types.push(t)); } else { - const speciesForm = this.getSpeciesForm(); + const speciesForm = this.getSpeciesForm(ignoreOverride); types.push(speciesForm.type1); - const fusionSpeciesForm = this.getFusionSpeciesForm(); + const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride); if (fusionSpeciesForm) { if (fusionSpeciesForm.type2 !== null && fusionSpeciesForm.type2 !== speciesForm.type1) { types.push(fusionSpeciesForm.type2); @@ -885,8 +897,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return types; } - isOfType(type: Type, forDefend: boolean = false): boolean { - return !!this.getTypes(true, forDefend).find(t => t === type); + isOfType(type: Type, includeTeraType: boolean = true, forDefend: boolean = false, ignoreOverride?: boolean): boolean { + return !!this.getTypes(includeTeraType, forDefend, ignoreOverride).some(t => t === type); } /** @@ -1054,7 +1066,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } isGrounded(): boolean { - return !this.isOfType(Type.FLYING, true) && !this.hasAbility(Abilities.LEVITATE); + return !this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE); + } + + /** + * @returns The type damage multiplier or undefined if it's a status move + */ + getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier | undefined { + if (move.getMove().category === MoveCategory.STATUS) { + return undefined; + } + + return this.getAttackMoveEffectiveness(source, move); } getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove): TypeDamageMultiplier { @@ -1576,11 +1599,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.battleInfo.updateInfo(this, instant); } + /** + * Show or hide the type effectiveness multiplier window + * Passing undefined will hide the window + */ + updateEffectiveness(effectiveness?: string) { + this.battleInfo.updateEffectiveness(effectiveness); + } + toggleStats(visible: boolean): void { this.battleInfo.toggleStats(visible); } + toggleFlyout(visible: boolean): void { - this.battleInfo.flyoutMenu?.toggleFlyout(visible); + this.battleInfo.toggleFlyout(visible); } addExp(exp: integer) { diff --git a/src/field/trainer.ts b/src/field/trainer.ts index fb85bfbe8b7..5a86d5c4502 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -166,6 +166,10 @@ export default class Trainer extends Phaser.GameObjects.Container { return this.config.doubleOnly || this.variant === TrainerVariant.DOUBLE; } + getMixedBattleBgm(): string { + return this.config.mixedBattleBgm; + } + getBattleBgm(): string { return this.config.battleBgm; } diff --git a/src/game-mode.ts b/src/game-mode.ts index 4d46971dcb5..cb042b2757c 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -1,17 +1,20 @@ -import { fixedBattles } from "./battle"; +import i18next from "i18next"; +import { classicFixedBattles, FixedBattleConfig, FixedBattleConfigs } from "./battle"; import BattleScene from "./battle-scene"; +import { allChallenges, applyChallenges, Challenge, ChallengeType, copyChallenge } from "./data/challenge"; import { Biome } from "./data/enums/biome"; import { Species } from "./data/enums/species"; import PokemonSpecies, { allSpecies } from "./data/pokemon-species"; import { Arena } from "./field/arena"; -import * as Utils from "./utils"; import * as Overrides from "./overrides"; +import * as Utils from "./utils"; export enum GameModes { CLASSIC, ENDLESS, SPLICED_ENDLESS, - DAILY + DAILY, + CHALLENGE } interface GameModeConfig { @@ -19,12 +22,12 @@ interface GameModeConfig { isEndless?: boolean; isDaily?: boolean; hasTrainers?: boolean; - hasFixedBattles?: boolean; hasNoShop?: boolean; hasShortBiomes?: boolean; hasRandomBiomes?: boolean; hasRandomBosses?: boolean; isSplicedOnly?: boolean; + isChallenge?: boolean; } export class GameMode implements GameModeConfig { @@ -33,16 +36,23 @@ export class GameMode implements GameModeConfig { public isEndless: boolean; public isDaily: boolean; public hasTrainers: boolean; - public hasFixedBattles: boolean; public hasNoShop: boolean; public hasShortBiomes: boolean; public hasRandomBiomes: boolean; public hasRandomBosses: boolean; public isSplicedOnly: boolean; + public isChallenge: boolean; + public challenges: Challenge[]; + public battleConfig: FixedBattleConfigs; - constructor(modeId: GameModes, config: GameModeConfig) { + constructor(modeId: GameModes, config: GameModeConfig, battleConfig?: FixedBattleConfigs) { this.modeId = modeId; + this.challenges = []; Object.assign(this, config); + if (this.isChallenge) { + this.challenges = allChallenges.map(c => copyChallenge(c)); + } + this.battleConfig = battleConfig || {}; } /** @@ -112,7 +122,7 @@ export class GameMode implements GameModeConfig { if (w === waveIndex) { continue; } - if ((w % 30) === (arena.scene.offsetGym ? 0 : 20) || fixedBattles.hasOwnProperty(w)) { + if ((w % 30) === (arena.scene.offsetGym ? 0 : 20) || this.isFixedBattle(waveIndex)) { allowTrainerBattle = false; break; } else if (w < waveIndex) { @@ -161,6 +171,7 @@ export class GameMode implements GameModeConfig { isWaveFinal(waveIndex: integer, modeId: GameModes = this.modeId): boolean { switch (modeId) { case GameModes.CLASSIC: + case GameModes.CHALLENGE: return waveIndex === 200; case GameModes.ENDLESS: case GameModes.SPLICED_ENDLESS: @@ -208,10 +219,36 @@ export class GameMode implements GameModeConfig { (this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS); } + /** + * Checks whether there is a fixed battle on this gamemode on a given wave. + * @param {integer} waveIndex The wave to check. + * @returns {boolean} If this game mode has a fixed battle on this wave + */ + isFixedBattle(waveIndex: integer): boolean { + const dummyConfig = new FixedBattleConfig(); + return this.battleConfig.hasOwnProperty(waveIndex) || applyChallenges(this, ChallengeType.FIXED_BATTLES, waveIndex, dummyConfig); + + } + + /** + * Returns the config for the fixed battle for a particular wave. + * @param {integer} waveIndex The wave to check. + * @returns {boolean} The fixed battle for this wave. + */ + getFixedBattle(waveIndex: integer): FixedBattleConfig { + const challengeConfig = new FixedBattleConfig(); + if (applyChallenges(this, ChallengeType.FIXED_BATTLES, waveIndex, challengeConfig)) { + return challengeConfig; + } else { + return this.battleConfig[waveIndex]; + } + } + getClearScoreBonus(): integer { switch (this.modeId) { case GameModes.CLASSIC: + case GameModes.CHALLENGE: return 5000; case GameModes.DAILY: return 2500; @@ -221,6 +258,7 @@ export class GameMode implements GameModeConfig { getEnemyModifierChance(isBoss: boolean): integer { switch (this.modeId) { case GameModes.CLASSIC: + case GameModes.CHALLENGE: case GameModes.DAILY: return !isBoss ? 18 : 6; case GameModes.ENDLESS: @@ -232,20 +270,45 @@ export class GameMode implements GameModeConfig { getName(): string { switch (this.modeId) { case GameModes.CLASSIC: - return "Classic"; + return i18next.t("gameMode:classic"); case GameModes.ENDLESS: - return "Endless"; + return i18next.t("gameMode:endless"); case GameModes.SPLICED_ENDLESS: - return "Endless (Spliced)"; + return i18next.t("gameMode:endlessSpliced"); case GameModes.DAILY: - return "Daily Run"; + return i18next.t("gameMode:dailyRun"); + case GameModes.CHALLENGE: + return i18next.t("gameMode:challenge"); + } + } + + static getModeName(modeId: GameModes): string { + switch (modeId) { + case GameModes.CLASSIC: + return i18next.t("gameMode:classic"); + case GameModes.ENDLESS: + return i18next.t("gameMode:endless"); + case GameModes.SPLICED_ENDLESS: + return i18next.t("gameMode:endlessSpliced"); + case GameModes.DAILY: + return i18next.t("gameMode:dailyRun"); + case GameModes.CHALLENGE: + return i18next.t("gameMode:challenge"); } } } -export const gameModes = Object.freeze({ - [GameModes.CLASSIC]: new GameMode(GameModes.CLASSIC, { isClassic: true, hasTrainers: true, hasFixedBattles: true }), - [GameModes.ENDLESS]: new GameMode(GameModes.ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true }), - [GameModes.SPLICED_ENDLESS]: new GameMode(GameModes.SPLICED_ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true, isSplicedOnly: true }), - [GameModes.DAILY]: new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true }) -}); +export function getGameMode(gameMode: GameModes): GameMode { + switch (gameMode) { + case GameModes.CLASSIC: + return new GameMode(GameModes.CLASSIC, { isClassic: true, hasTrainers: true }, classicFixedBattles); + case GameModes.ENDLESS: + return new GameMode(GameModes.ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true }); + case GameModes.SPLICED_ENDLESS: + return new GameMode(GameModes.SPLICED_ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true, isSplicedOnly: true }); + case GameModes.DAILY: + return new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true }); + case GameModes.CHALLENGE: + return new GameMode(GameModes.CHALLENGE, { isClassic: true, hasTrainers: true, isChallenge: true }, classicFixedBattles); + } +} diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 8bb811e3d9b..7a097cc8b37 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -18,8 +18,10 @@ import {initMoves} from "#app/data/move"; import {initAbilities} from "#app/data/ability"; import {initAchievements} from "#app/system/achv"; import {initTrainerTypeDialogue} from "#app/data/dialogue"; +import { initChallenges } from "./data/challenge"; import i18next from "i18next"; import { initStatsKeys } from "./ui/game-stats-ui-handler"; +import { initVouchers } from "./system/voucher"; export class LoadingScene extends SceneBase { constructor() { @@ -329,6 +331,7 @@ export class LoadingScene extends SceneBase { this.loadLoadingScreen(); + initVouchers(); initAchievements(); initStatsKeys(); initPokemonPrevolutions(); @@ -339,6 +342,7 @@ export class LoadingScene extends SceneBase { initSpecies(); initMoves(); initAbilities(); + initChallenges(); } loadLoadingScreen() { @@ -432,7 +436,7 @@ export class LoadingScene extends SceneBase { }); this.load.on("fileprogress", file => { - assetText.setText(`Loading asset: ${file.key}`); + assetText.setText(i18next.t("menu:loadingAsset", { assetName: file.key })); }); loadingGraphics.push(bg, graphics, progressBar, progressBox, logo, percentText, assetText); diff --git a/src/locales/de/challenges.ts b/src/locales/de/challenges.ts new file mode 100644 index 00000000000..149037be740 --- /dev/null +++ b/src/locales/de/challenges.ts @@ -0,0 +1,67 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const challenges: SimpleTranslationEntries = { + "title": "Challenge Modifiers", + "points": "Bad Ideas", + "confirm_start": "Proceed with these challenges?", + "singleGeneration.name": "Mono Gen", + "singleGeneration.value.0": "Off", + "singleGeneration.desc.0": "You can only use pokemon from the chosen generation.", + "singleGeneration.value.1": "Gen 1", + "singleGeneration.desc.1": "You can only use pokemon from generation one.", + "singleGeneration.value.2": "Gen 2", + "singleGeneration.desc.2": "You can only use pokemon from generation two.", + "singleGeneration.value.3": "Gen 3", + "singleGeneration.desc.3": "You can only use pokemon from generation three.", + "singleGeneration.value.4": "Gen 4", + "singleGeneration.desc.4": "You can only use pokemon from generation four.", + "singleGeneration.value.5": "Gen 5", + "singleGeneration.desc.5": "You can only use pokemon from generation five.", + "singleGeneration.value.6": "Gen 6", + "singleGeneration.desc.6": "You can only use pokemon from generation six.", + "singleGeneration.value.7": "Gen 7", + "singleGeneration.desc.7": "You can only use pokemon from generation seven.", + "singleGeneration.value.8": "Gen 8", + "singleGeneration.desc.8": "You can only use pokemon from generation eight.", + "singleGeneration.value.9": "Gen 9", + "singleGeneration.desc.9": "You can only use pokemon from generation nine.", + "singleType.name": "Mono Type", + "singleType.value.0": "Off", + "singleType.desc.0": "You can only use pokemon of the chosen type.", + "singleType.value.1": "Normal", + "singleType.desc.1": "You can only use pokemon with the Normal type.", + "singleType.value.2": "Fighting", + "singleType.desc.2": "You can only use pokemon with the Fighting type.", + "singleType.value.3": "Flying", + "singleType.desc.3": "You can only use pokemon with the Flying type.", + "singleType.value.4": "Poison", + "singleType.desc.4": "You can only use pokemon with the Poison type.", + "singleType.value.5": "Ground", + "singleType.desc.5": "You can only use pokemon with the Ground type.", + "singleType.value.6": "Rock", + "singleType.desc.6": "You can only use pokemon with the Rock type.", + "singleType.value.7": "Bug", + "singleType.desc.7": "You can only use pokemon with the Bug type.", + "singleType.value.8": "Ghost", + "singleType.desc.8": "You can only use pokemon with the Ghost type.", + "singleType.value.9": "Steel", + "singleType.desc.9": "You can only use pokemon with the Steel type.", + "singleType.value.10": "Fire", + "singleType.desc.10": "You can only use pokemon with the Fire type.", + "singleType.value.11": "Water", + "singleType.desc.11": "You can only use pokemon with the Water type.", + "singleType.value.12": "Grass", + "singleType.desc.12": "You can only use pokemon with the Grass type.", + "singleType.value.13": "Electric", + "singleType.desc.13": "You can only use pokemon with the Electric type.", + "singleType.value.14": "Psychic", + "singleType.desc.14": "You can only use pokemon with the Psychic type.", + "singleType.value.15": "Ice", + "singleType.desc.15": "You can only use pokemon with the Ice type.", + "singleType.value.16": "Dragon", + "singleType.desc.16": "You can only use pokemon with the Dragon type.", + "singleType.value.17": "Dark", + "singleType.desc.17": "You can only use pokemon with the Dark type.", + "singleType.value.18": "Fairy", + "singleType.desc.18": "You can only use pokemon with the Fairy type.", +} as const; diff --git a/src/locales/de/config.ts b/src/locales/de/config.ts index afee437a652..42869e5719e 100644 --- a/src/locales/de/config.ts +++ b/src/locales/de/config.ts @@ -5,6 +5,7 @@ import { battle } from "./battle"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { berry } from "./berry"; import { biome } from "./biome"; +import { challenges } from "./challenges"; import { commandUiHandler } from "./command-ui-handler"; import { PGFbattleSpecDialogue, @@ -18,6 +19,7 @@ import { } from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameMode } from "./game-mode"; import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; @@ -29,6 +31,7 @@ import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; +import { saveSlotSelectUiHandler } from "./save-slot-select-ui-handler"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; @@ -45,6 +48,7 @@ export const deConfig = { battleMessageUiHandler: battleMessageUiHandler, berry: berry, biome: biome, + challenges: challenges, commandUiHandler: commandUiHandler, PGMdialogue: PGMdialogue, PGFdialogue: PGFdialogue, @@ -56,6 +60,7 @@ export const deConfig = { PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameMode: gameMode, gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, @@ -67,6 +72,7 @@ export const deConfig = { pokemon: pokemon, pokemonInfo: pokemonInfo, pokemonInfoContainer: pokemonInfoContainer, + saveSlotSelectUiHandler: saveSlotSelectUiHandler, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, diff --git a/src/locales/de/game-mode.ts b/src/locales/de/game-mode.ts new file mode 100644 index 00000000000..59058a6ab49 --- /dev/null +++ b/src/locales/de/game-mode.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const gameMode: SimpleTranslationEntries = { + "classic": "Klassik", + "endless": "Endlos", + "endlessSpliced": "Endlos (Fusion)", + "dailyRun": "Täglicher Run", + "unknown": "Unbekannt", + "challenge": "Challenge", +} as const; diff --git a/src/locales/de/menu.ts b/src/locales/de/menu.ts index e1e5db72b9c..975ec4aac80 100644 --- a/src/locales/de/menu.ts +++ b/src/locales/de/menu.ts @@ -45,8 +45,8 @@ export const menu: SimpleTranslationEntries = { "weeklyRankings": "Wöchentliche Rangliste", "noRankings": "Keine Rangliste", "loading": "Lade…", + "loadingAsset": "Loading asset: {{assetName}}", "playersOnline": "Spieler Online", - "empty":"Leer", "yes":"Ja", "no":"Nein", "disclaimer": "DISCLAIMER", diff --git a/src/locales/de/save-slot-select-ui-handler.ts b/src/locales/de/save-slot-select-ui-handler.ts new file mode 100644 index 00000000000..fbbfebae6ee --- /dev/null +++ b/src/locales/de/save-slot-select-ui-handler.ts @@ -0,0 +1,9 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const saveSlotSelectUiHandler: SimpleTranslationEntries = { + "overwriteData": "Den ausgewählten Speicherstand überschreiben?", + "loading": "Läd...", + "wave": "Welle", + "lv": "Lvl", + "empty": "Leer", +} as const; diff --git a/src/locales/en/achv.ts b/src/locales/en/achv.ts index 42b1995bcde..a545ab8a71e 100644 --- a/src/locales/en/achv.ts +++ b/src/locales/en/achv.ts @@ -168,4 +168,99 @@ export const achv: AchievementTranslationEntries = { name: "Undefeated", description: "Beat the game in classic mode", }, + + "MONO_GEN_ONE": { + name: "The Original Rival", + description: "Complete the generation one only challenge.", + }, + "MONO_GEN_TWO": { + name: "Generation 1.5", + description: "Complete the generation two only challenge.", + }, + "MONO_GEN_THREE": { + name: "Too much water?", + description: "Complete the generation three only challenge.", + }, + "MONO_GEN_FOUR": { + name: "Is she really the hardest?", + description: "Complete the generation four only challenge.", + }, + "MONO_GEN_FIVE": { + name: "All Original", + description: "Complete the generation five only challenge.", + }, + "MONO_GEN_SIX": { + name: "Almost Royalty", + description: "Complete the generation six only challenge.", + }, + "MONO_GEN_SEVEN": { + name: "Only Technically", + description: "Complete the generation seven only challenge.", + }, + "MONO_GEN_EIGHT": { + name: "A Champion Time!", + description: "Complete the generation eight only challenge.", + }, + "MONO_GEN_NINE": { + name: "She was going easy on you", + description: "Complete the generation nine only challenge.", + }, + + "MonoType": { + description: "Complete the {{type}} monotype challenge.", + }, + "MONO_NORMAL": { + name: "Mono NORMAL", + }, + "MONO_FIGHTING": { + name: "I Know Kung Fu", + }, + "MONO_FLYING": { + name: "Mono FLYING", + }, + "MONO_POISON": { + name: "Kanto's Favourite", + }, + "MONO_GROUND": { + name: "Mono GROUND", + }, + "MONO_ROCK": { + name: "Brock Hard", + }, + "MONO_BUG": { + name: "Sting Like A Beedrill", + }, + "MONO_GHOST": { + name: "Who you gonna call?", + }, + "MONO_STEEL": { + name: "Mono STEEL", + }, + "MONO_FIRE": { + name: "Mono FIRE", + }, + "MONO_WATER": { + name: "When It Rains, It Pours", + }, + "MONO_GRASS": { + name: "Mono GRASS", + }, + "MONO_ELECTRIC": { + name: "Mono ELECTRIC", + }, + "MONO_PSYCHIC": { + name: "Mono PSYCHIC", + }, + "MONO_ICE": { + name: "Mono ICE", + }, + "MONO_DRAGON": { + name: "Mono DRAGON", + }, + "MONO_DARK": { + name: "It's just a phase", + }, + "MONO_FAIRY": { + name: "Mono FAIRY", + }, } as const; diff --git a/src/locales/en/challenges.ts b/src/locales/en/challenges.ts new file mode 100644 index 00000000000..7401104e1a3 --- /dev/null +++ b/src/locales/en/challenges.ts @@ -0,0 +1,67 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const challenges: SimpleTranslationEntries = { + "title": "Challenge Modifiers", + "start": "Start", + "illegalEvolution": "{{pokemon}} changed into an ineligble pokemon\nfor this challenge!", + "singleGeneration.name": "Mono Gen", + "singleGeneration.value.0": "Off", + "singleGeneration.desc.0": "You can only use pokemon from the chosen generation.", + "singleGeneration.value.1": "Gen 1", + "singleGeneration.desc.1": "You can only use pokemon from generation one.", + "singleGeneration.value.2": "Gen 2", + "singleGeneration.desc.2": "You can only use pokemon from generation two.", + "singleGeneration.value.3": "Gen 3", + "singleGeneration.desc.3": "You can only use pokemon from generation three.", + "singleGeneration.value.4": "Gen 4", + "singleGeneration.desc.4": "You can only use pokemon from generation four.", + "singleGeneration.value.5": "Gen 5", + "singleGeneration.desc.5": "You can only use pokemon from generation five.", + "singleGeneration.value.6": "Gen 6", + "singleGeneration.desc.6": "You can only use pokemon from generation six.", + "singleGeneration.value.7": "Gen 7", + "singleGeneration.desc.7": "You can only use pokemon from generation seven.", + "singleGeneration.value.8": "Gen 8", + "singleGeneration.desc.8": "You can only use pokemon from generation eight.", + "singleGeneration.value.9": "Gen 9", + "singleGeneration.desc.9": "You can only use pokemon from generation nine.", + "singleType.name": "Mono Type", + "singleType.value.0": "Off", + "singleType.desc.0": "You can only use pokemon of the chosen type.", + "singleType.value.1": "Normal", + "singleType.desc.1": "You can only use pokemon with the Normal type.", + "singleType.value.2": "Fighting", + "singleType.desc.2": "You can only use pokemon with the Fighting type.", + "singleType.value.3": "Flying", + "singleType.desc.3": "You can only use pokemon with the Flying type.", + "singleType.value.4": "Poison", + "singleType.desc.4": "You can only use pokemon with the Poison type.", + "singleType.value.5": "Ground", + "singleType.desc.5": "You can only use pokemon with the Ground type.", + "singleType.value.6": "Rock", + "singleType.desc.6": "You can only use pokemon with the Rock type.", + "singleType.value.7": "Bug", + "singleType.desc.7": "You can only use pokemon with the Bug type.", + "singleType.value.8": "Ghost", + "singleType.desc.8": "You can only use pokemon with the Ghost type.", + "singleType.value.9": "Steel", + "singleType.desc.9": "You can only use pokemon with the Steel type.", + "singleType.value.10": "Fire", + "singleType.desc.10": "You can only use pokemon with the Fire type.", + "singleType.value.11": "Water", + "singleType.desc.11": "You can only use pokemon with the Water type.", + "singleType.value.12": "Grass", + "singleType.desc.12": "You can only use pokemon with the Grass type.", + "singleType.value.13": "Electric", + "singleType.desc.13": "You can only use pokemon with the Electric type.", + "singleType.value.14": "Psychic", + "singleType.desc.14": "You can only use pokemon with the Psychic type.", + "singleType.value.15": "Ice", + "singleType.desc.15": "You can only use pokemon with the Ice type.", + "singleType.value.16": "Dragon", + "singleType.desc.16": "You can only use pokemon with the Dragon type.", + "singleType.value.17": "Dark", + "singleType.desc.17": "You can only use pokemon with the Dark type.", + "singleType.value.18": "Fairy", + "singleType.desc.18": "You can only use pokemon with the Fairy type.", +} as const; diff --git a/src/locales/en/config.ts b/src/locales/en/config.ts index 383b52d4c19..49dcd9f594e 100644 --- a/src/locales/en/config.ts +++ b/src/locales/en/config.ts @@ -5,6 +5,7 @@ import { battle } from "./battle"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { berry } from "./berry"; import { biome } from "./biome"; +import { challenges } from "./challenges"; import { commandUiHandler } from "./command-ui-handler"; import { PGFbattleSpecDialogue, @@ -18,6 +19,7 @@ import { } from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameMode } from "./game-mode"; import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; @@ -29,6 +31,7 @@ import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; +import { saveSlotSelectUiHandler } from "./save-slot-select-ui-handler"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; @@ -45,6 +48,7 @@ export const enConfig = { battleMessageUiHandler: battleMessageUiHandler, berry: berry, biome: biome, + challenges: challenges, commandUiHandler: commandUiHandler, PGMdialogue: PGMdialogue, PGFdialogue: PGFdialogue, @@ -56,6 +60,7 @@ export const enConfig = { PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameMode: gameMode, gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, @@ -67,6 +72,7 @@ export const enConfig = { pokemon: pokemon, pokemonInfo: pokemonInfo, pokemonInfoContainer: pokemonInfoContainer, + saveSlotSelectUiHandler: saveSlotSelectUiHandler, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, diff --git a/src/locales/en/dialogue.ts b/src/locales/en/dialogue.ts index 601e6363f3a..86167da696c 100644 --- a/src/locales/en/dialogue.ts +++ b/src/locales/en/dialogue.ts @@ -2372,7 +2372,7 @@ export const PGMdoubleBattleDialogue: DialogueTranslationEntries = { }, "tate_liza_double": { "encounter": { - 1: `Tate: Are you suprised? + 1: `Tate: Are you surprised? $Liza: We are two gym leaders at once! $Tate: We are twins! $Liza: We dont need to talk to understand each other! @@ -2386,7 +2386,7 @@ export const PGMdoubleBattleDialogue: DialogueTranslationEntries = { }, "liza_tate_double": { "encounter": { - 1: `Liza: Hihihi... Are you suprised? + 1: `Liza: Hihihi... Are you surprised? $Tate: Yes, we are really two gym leaders at once! $Liza: This is my twin brother Tate! $Tate: And this is my twin sister Liza! diff --git a/src/locales/en/game-mode.ts b/src/locales/en/game-mode.ts new file mode 100644 index 00000000000..be342b4c390 --- /dev/null +++ b/src/locales/en/game-mode.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const gameMode: SimpleTranslationEntries = { + "classic": "Classic", + "endless": "Endless", + "endlessSpliced": "Endless (Spliced)", + "dailyRun": "Daily Run", + "unknown": "Unknown", + "challenge": "Challenge", +} as const; diff --git a/src/locales/en/menu.ts b/src/locales/en/menu.ts index d43ac0983f4..03b8f22332d 100644 --- a/src/locales/en/menu.ts +++ b/src/locales/en/menu.ts @@ -45,8 +45,8 @@ export const menu: SimpleTranslationEntries = { "weeklyRankings": "Weekly Rankings", "noRankings": "No Rankings", "loading": "Loading…", + "loadingAsset": "Loading asset: {{assetName}}", "playersOnline": "Players Online", - "empty":"Empty", "yes":"Yes", "no":"No", "disclaimer": "DISCLAIMER", diff --git a/src/locales/en/save-slot-select-ui-handler.ts b/src/locales/en/save-slot-select-ui-handler.ts new file mode 100644 index 00000000000..5aaa675fc4d --- /dev/null +++ b/src/locales/en/save-slot-select-ui-handler.ts @@ -0,0 +1,9 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const saveSlotSelectUiHandler: SimpleTranslationEntries = { + "overwriteData": "Overwrite the data in the selected slot?", + "loading": "Loading...", + "wave": "Wave", + "lv": "Lv", + "empty": "Empty", +} as const; diff --git a/src/locales/en/starter-select-ui-handler.ts b/src/locales/en/starter-select-ui-handler.ts index 857ba4d805c..f001c621cc4 100644 --- a/src/locales/en/starter-select-ui-handler.ts +++ b/src/locales/en/starter-select-ui-handler.ts @@ -30,12 +30,12 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "selectMoveSwapWith": "Select a move to swap with", "unlockPassive": "Unlock Passive", "reduceCost": "Reduce Cost", - "cycleShiny": "R: Cycle Shiny", - "cycleForm": "F: Cycle Form", - "cycleGender": "G: Cycle Gender", - "cycleAbility": "E: Cycle Ability", - "cycleNature": "N: Cycle Nature", - "cycleVariant": "V: Cycle Variant", + "cycleShiny": ": Cycle Shiny", + "cycleForm": ": Cycle Form", + "cycleGender": ": Cycle Gender", + "cycleAbility": ": Cycle Ability", + "cycleNature": ": Cycle Nature", + "cycleVariant": ": Cycle Variant", "enablePassive": "Enable Passive", "disablePassive": "Disable Passive", "locked": "Locked", diff --git a/src/locales/es/battle.ts b/src/locales/es/battle.ts index c4d79cfdb93..4f60ab28d8a 100644 --- a/src/locales/es/battle.ts +++ b/src/locales/es/battle.ts @@ -18,11 +18,11 @@ export const battle: SimpleTranslationEntries = { "partyFull": "Tu equipo esta completo.\n¿Quieres liberar un Pokémon para meter a {{pokemonName}}?", "pokemon": "Pokémon", "sendOutPokemon": "¡Adelante, {{pokemonName}}!", - "hitResultCriticalHit": "!Un golpe crítico!", - "hitResultSuperEffective": "!Es supereficaz!", + "hitResultCriticalHit": "¡Un golpe crítico!", + "hitResultSuperEffective": "¡Es supereficaz!", "hitResultNotVeryEffective": "No es muy eficaz…", "hitResultNoEffect": "No afecta a {{pokemonName}}!", - "hitResultOneHitKO": "!KO en 1 golpe!", + "hitResultOneHitKO": "¡KO en 1 golpe!", "attackFailed": "¡Pero ha fallado!", "attackHitsCount": "N.º de golpes: {{count}}.", "expGain": "{{pokemonName}} ha ganado\n{{exp}} puntos de experiencia.", @@ -56,9 +56,9 @@ export const battle: SimpleTranslationEntries = { "skipItemQuestion": "¿Estás seguro de que no quieres coger un objeto?", "eggHatching": "¿Y esto?", "ivScannerUseQuestion": "¿Quieres usar el Escáner de IVs en {{pokemonName}}?", - "wildPokemonWithAffix": "Wild {{pokemonName}}", - "foePokemonWithAffix": "Foe {{pokemonName}}", - "useMove": "{{pokemonNameWithAffix}} used {{moveName}}!", - "drainMessage": "{{pokemonName}} had its\nenergy drained!", - "regainHealth": "{{pokemonName}} regained\nhealth!" + "wildPokemonWithAffix": "El {{pokemonName}} salvaje", + "foePokemonWithAffix": "El {{pokemonName}} enemigo", + "useMove": "¡{{pokemonNameWithAffix}} usó {{moveName}}!", + "drainMessage": "¡{{pokemonName}} tuvo su\nenergía absorbida!", + "regainHealth": "¡{{pokemonName}} recuperó\nPS!" } as const; diff --git a/src/locales/es/challenges.ts b/src/locales/es/challenges.ts new file mode 100644 index 00000000000..6c994e5f4b3 --- /dev/null +++ b/src/locales/es/challenges.ts @@ -0,0 +1,67 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const challenges: SimpleTranslationEntries = { + "title": "Parámetros de Desafíos", + "points": "Malas Ideas", + "confirm_start": "¿Continuar con estos desafíos?", + "singleGeneration.name": "Monogeneración", + "singleGeneration.value.0": "No", + "singleGeneration.desc.0": "Solo puedes usar Pokémon de la generación elegida.", + "singleGeneration.value.1": "Gen 1", + "singleGeneration.desc.1": "Solo puedes usar Pokémon de primera generación.", + "singleGeneration.value.2": "Gen 2", + "singleGeneration.desc.2": "Solo puedes usar Pokémon de segunda generación.", + "singleGeneration.value.3": "Gen 3", + "singleGeneration.desc.3": "Solo puedes usar Pokémon de tercera generación.", + "singleGeneration.value.4": "Gen 4", + "singleGeneration.desc.4": "Solo puedes usar Pokémon de cuarta generación.", + "singleGeneration.value.5": "Gen 5", + "singleGeneration.desc.5": "Solo puedes usar Pokémon de quinta generación.", + "singleGeneration.value.6": "Gen 6", + "singleGeneration.desc.6": "Solo puedes usar Pokémon de sexta generación.", + "singleGeneration.value.7": "Gen 7", + "singleGeneration.desc.7": "Solo puedes usar Pokémon de séptima generación.", + "singleGeneration.value.8": "Gen 8", + "singleGeneration.desc.8": "Solo puedes usar Pokémon de octava generación.", + "singleGeneration.value.9": "Gen 9", + "singleGeneration.desc.9": "Solo puedes usar Pokémon de novena generación.", + "singleType.name": "Monotipo", + "singleType.value.0": "No", + "singleType.desc.0": "Solo puedes usar Pokémon del tipo elegido", + "singleType.value.1": "Normal", + "singleType.desc.1": "Solo puedes usar Pokémon de tipo Normal.", + "singleType.value.2": "Lucha", + "singleType.desc.2": "Solo puedes usar Pokémon de tipo Lucha.", + "singleType.value.3": "Volador", + "singleType.desc.3": "Solo puedes usar Pokémon de tipo Volador.", + "singleType.value.4": "Veneno", + "singleType.desc.4": "Solo puedes usar Pokémon de tipo Veneno.", + "singleType.value.5": "Tierra", + "singleType.desc.5": "Solo puedes usar Pokémon de tipo Tierra.", + "singleType.value.6": "Roca", + "singleType.desc.6": "Solo puedes usar Pokémon de tipo Roca.", + "singleType.value.7": "Bicho", + "singleType.desc.7": "Solo puedes usar Pokémon de tipo Bicho.", + "singleType.value.8": "Fantasma", + "singleType.desc.8": "Solo puedes usar Pokémon de tipo Fantasma.", + "singleType.value.9": "Acero", + "singleType.desc.9": "Solo puedes usar Pokémon de tipo Acero.", + "singleType.value.10": "Fuego", + "singleType.desc.10": "Solo puedes usar Pokémon de tipo Fuego.", + "singleType.value.11": "Agua", + "singleType.desc.11": "Solo puedes usar Pokémon de tipo Agua.", + "singleType.value.12": "Planta", + "singleType.desc.12": "Solo puedes usar Pokémon de tipo Planta.", + "singleType.value.13": "Eléctrico", + "singleType.desc.13": "Solo puedes usar Pokémon de tipo Eléctrico.", + "singleType.value.14": "Psíquico", + "singleType.desc.14": "Solo puedes usar Pokémon de tipo Psíquico.", + "singleType.value.15": "Hielo", + "singleType.desc.15": "Solo puedes usar Pokémon de tipo Hielo.", + "singleType.value.16": "Dragón", + "singleType.desc.16": "Solo puedes usar Pokémon de tipo Dragón.", + "singleType.value.17": "Siniestro", + "singleType.desc.17": "Solo puedes usar Pokémon de tipo Siniestro.", + "singleType.value.18": "Hada", + "singleType.desc.18": "Solo puedes usar Pokémon de tipo Hada.", +} as const; diff --git a/src/locales/es/config.ts b/src/locales/es/config.ts index f6d1ac0f1c1..e31cd494c2f 100644 --- a/src/locales/es/config.ts +++ b/src/locales/es/config.ts @@ -5,6 +5,7 @@ import { battle } from "./battle"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { berry } from "./berry"; import { biome } from "./biome"; +import { challenges } from "./challenges"; import { commandUiHandler } from "./command-ui-handler"; import { PGFbattleSpecDialogue, @@ -18,6 +19,7 @@ import { } from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameMode } from "./game-mode"; import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; @@ -29,6 +31,7 @@ import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; +import { saveSlotSelectUiHandler } from "./save-slot-select-ui-handler"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; @@ -45,6 +48,7 @@ export const esConfig = { battleMessageUiHandler: battleMessageUiHandler, berry: berry, biome: biome, + challenges: challenges, commandUiHandler: commandUiHandler, PGMdialogue: PGMdialogue, PGFdialogue: PGFdialogue, @@ -56,6 +60,7 @@ export const esConfig = { PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameMode: gameMode, gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, @@ -67,6 +72,7 @@ export const esConfig = { pokemon: pokemon, pokemonInfo: pokemonInfo, pokemonInfoContainer: pokemonInfoContainer, + saveSlotSelectUiHandler: saveSlotSelectUiHandler, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, diff --git a/src/locales/es/game-mode.ts b/src/locales/es/game-mode.ts new file mode 100644 index 00000000000..dcff983791f --- /dev/null +++ b/src/locales/es/game-mode.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const gameMode: SimpleTranslationEntries = { + "classic": "Clásica", + "endless": "Infinita", + "endlessSpliced": "Infinita (Fusión)", + "dailyRun": "Diaria", + "unknown": "Desconicido", + "challenge": "Desafío", +} as const; diff --git a/src/locales/es/menu.ts b/src/locales/es/menu.ts index 517569ff40b..4bd6d750d69 100644 --- a/src/locales/es/menu.ts +++ b/src/locales/es/menu.ts @@ -45,10 +45,10 @@ export const menu: SimpleTranslationEntries = { "weeklyRankings": "Rankings Semanales", "noRankings": "Sin Rankings", "loading": "Cargando…", + "loadingAsset": "Cargando recurso: {{assetName}}", "playersOnline": "Jugadores en Línea", - "empty":"Vacío", "yes":"Sí", "no":"No", - "disclaimer": "DISCLAIMER", - "disclaimerDescription": "This game is an unfinished product; it might have playability issues (including the potential loss of save data),\n change without notice, and may or may not be updated further or completed." + "disclaimer": "AVISO", + "disclaimerDescription": "Este juego es un producto inacabado; puede tener problemas de jugabilidad (incluyendo la posible pérdida de datos de guardado),\ncambiar sin avisar, y puede o no puede ser actualizado hasta ser completado." } as const; diff --git a/src/locales/es/modifier-type.ts b/src/locales/es/modifier-type.ts index bca61e01d87..df975bfed2a 100644 --- a/src/locales/es/modifier-type.ts +++ b/src/locales/es/modifier-type.ts @@ -130,7 +130,7 @@ export const modifierType: ModifierTypeTranslationEntries = { }, "RARE_CANDY": { name: "Carameloraro" }, - "RARER_CANDY": { name: "Rarer Candy" }, + "RARER_CANDY": { name: "Caramelorarísimo" }, "MEGA_BRACELET": { name: "Mega-aro", description: "Las Megapiedras están disponibles" }, "DYNAMAX_BAND": { name: "Maximuñequera", description: "Las Maxisetas están disponibles" }, @@ -162,14 +162,14 @@ export const modifierType: ModifierTypeTranslationEntries = { "PP_UP": { name: "Más PP" }, "PP_MAX": { name: "Máx PP" }, - "LURE": { name: "Lure" }, - "SUPER_LURE": { name: "Super Lure" }, - "MAX_LURE": { name: "Max Lure" }, + "LURE": { name: "Incienso" }, + "SUPER_LURE": { name: "Superincienso" }, + "MAX_LURE": { name: "Incienso Máximo" }, - "MEMORY_MUSHROOM": { name: "Memory Mushroom", description: "Recall one Pokémon's forgotten move" }, + "MEMORY_MUSHROOM": { name: "Seta Recuerdo", description: "Recuerda un movimiento olvidado de un Pokémon." }, "EXP_SHARE": { name: "Repartir EXP", description: "Los que no combatan reciben el 20% de la EXP" }, - "EXP_BALANCE": { name: "EXP. Balance", description: "Da mayor parte de la EXP recibida a los miembros del equipo que tengan menos nivel" }, + "EXP_BALANCE": { name: "Equilibrar EXP", description: "Da mayor parte de la EXP recibida a los miembros del equipo que tengan menos nivel" }, "OVAL_CHARM": { name: "Amuleto Oval", description: "Cada Pokémon combatiente recibe un 10% adicional de la EXP total" }, @@ -197,7 +197,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "GRIP_CLAW": { name: "Garra Garfio" }, "WIDE_LENS": { name: "Lupa" }, - "MULTI_LENS": { name: "Multi Lens" }, + "MULTI_LENS": { name: "Multilupa" }, "HEALING_CHARM": { name: "Amuleto curación", description: "Aumenta la efectividad de los movimientos y objetos de curacion de PS en un 10% (excepto revivir)" }, "CANDY_JAR": { name: "Candy Jar", description: "Aumenta en 1 el número de niveles añadidos por los carameloraros" }, @@ -216,7 +216,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "TOXIC_ORB": { name: "Toxiesfera", description: "Extraña esfera que envenena gravemente a quien la usa en combate" }, "FLAME_ORB": { name: "Llamasfera", description: "Extraña esfera que causa quemaduras a quien la usa en combate" }, - "BATON": { name: "Baton", description: "Permite pasar los efectos al cambiar de Pokémon, también evita las trampas" }, + "BATON": { name: "Relevo", description: "Permite pasar los efectos al cambiar de Pokémon, también evita las trampas" }, "SHINY_CHARM": { name: "Amuleto Iris", description: "Aumenta drásticamente la posibilidad de que un Pokémon salvaje sea Shiny" }, "ABILITY_CHARM": { name: "Amuleto Habilidad", description: "Aumenta drásticamente la posibilidad de que un Pokémon salvaje tenga una habilidad oculta" }, @@ -229,15 +229,15 @@ export const modifierType: ModifierTypeTranslationEntries = { "GOLDEN_POKEBALL": { name: "Poké Ball Dorada", description: "Agrega 1 opción de objeto extra al final de cada combate" }, - "ENEMY_DAMAGE_BOOSTER": { name: "Damage Token", description: "Aumenta el daño en un 5%" }, - "ENEMY_DAMAGE_REDUCTION": { name: "Protection Token", description: "Reduce el daño recibido en un 2,5%" }, - "ENEMY_HEAL": { name: "Recovery Token", description: "Cura el 2% de los PS máximo en cada turno" }, - "ENEMY_ATTACK_POISON_CHANCE": { name: "Poison Token" }, - "ENEMY_ATTACK_PARALYZE_CHANCE": { name: "Paralyze Token" }, - "ENEMY_ATTACK_BURN_CHANCE": { name: "Burn Token" }, - "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Full Heal Token", description: "Agrega un 2.5% de probabilidad cada turno de curar un problema de estado" }, - "ENEMY_ENDURE_CHANCE": { name: "Endure Token" }, - "ENEMY_FUSED_CHANCE": { name: "Fusion Token", description: "Agrega un 1% de probabilidad de que un Pokémon salvaje sea una fusión" }, + "ENEMY_DAMAGE_BOOSTER": { name: "Ficha Daño", description: "Aumenta el daño en un 5%" }, + "ENEMY_DAMAGE_REDUCTION": { name: "Ficha Protección", description: "Reduce el daño recibido en un 2,5%" }, + "ENEMY_HEAL": { name: "Ficha Curación", description: "Cura el 2% de los PS máximo en cada turno" }, + "ENEMY_ATTACK_POISON_CHANCE": { name: "Ficha Veneno" }, + "ENEMY_ATTACK_PARALYZE_CHANCE": { name: "Ficha Parálisis" }, + "ENEMY_ATTACK_BURN_CHANCE": { name: "Ficha Quemadura" }, + "ENEMY_STATUS_EFFECT_HEAL_CHANCE": { name: "Ficha Cura Total", description: "Agrega un 2.5% de probabilidad cada turno de curar un problema de estado" }, + "ENEMY_ENDURE_CHANCE": { name: "Ficha Aguante" }, + "ENEMY_FUSED_CHANCE": { name: "Ficha Fusión", description: "Agrega un 1% de probabilidad de que un Pokémon salvaje sea una fusión" }, }, TempBattleStatBoosterItem: { "x_attack": "Ataque X", diff --git a/src/locales/es/save-slot-select-ui-handler.ts b/src/locales/es/save-slot-select-ui-handler.ts new file mode 100644 index 00000000000..7939518ceed --- /dev/null +++ b/src/locales/es/save-slot-select-ui-handler.ts @@ -0,0 +1,9 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const saveSlotSelectUiHandler: SimpleTranslationEntries = { + "overwriteData": "¿Sobrescribir los datos en la ranura seleccionada?", + "loading": "Cargando...", + "wave": "Oleada", + "lv": "Nv", + "empty": "Vacío", +} as const; diff --git a/src/locales/es/starter-select-ui-handler.ts b/src/locales/es/starter-select-ui-handler.ts index 81455418c7d..e41f151965a 100644 --- a/src/locales/es/starter-select-ui-handler.ts +++ b/src/locales/es/starter-select-ui-handler.ts @@ -30,12 +30,12 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "selectMoveSwapWith": "Elige el movimiento que sustituirá a", "unlockPassive": "Añadir Pasiva", "reduceCost": "Reducir Coste", - "cycleShiny": "R: Cambiar Shiny", - "cycleForm": "F: Cambiar Forma", - "cycleGender": "G: Cambiar Género", - "cycleAbility": "E: Cambiar Habilidad", - "cycleNature": "N: Cambiar Naturaleza", - "cycleVariant": "V: Cambiar Variante", + "cycleShiny": ": Cambiar Shiny", + "cycleForm": ": Cambiar Forma", + "cycleGender": ": Cambiar Género", + "cycleAbility": ": Cambiar Habilidad", + "cycleNature": ": Cambiar Naturaleza", + "cycleVariant": ": Cambiar Variante", "enablePassive": "Activar Pasiva", "disablePassive": "Desactivar Pasiva", "locked": "Bloqueado", diff --git a/src/locales/fr/challenges.ts b/src/locales/fr/challenges.ts new file mode 100644 index 00000000000..f655caf4807 --- /dev/null +++ b/src/locales/fr/challenges.ts @@ -0,0 +1,67 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const challenges: SimpleTranslationEntries = { + "title": "Paramètres du Challenge", + "points": "Bad Ideas", + "confirm_start": "Continuer avec ces paramètres ?", + "singleGeneration.name": "Mono-génération", + "singleGeneration.value.0": "Désactivé", + "singleGeneration.desc.0": "Vous ne pouvez choisir que des Pokémon de la génération sélectionnée.", + "singleGeneration.value.1": "1G", + "singleGeneration.desc.1": "Vous ne pouvez choisir que des Pokémon de 1re génération.", + "singleGeneration.value.2": "2G", + "singleGeneration.desc.2": "Vous ne pouvez choisir que des Pokémon de 2e génération.", + "singleGeneration.value.3": "3G", + "singleGeneration.desc.3": "Vous ne pouvez choisir que des Pokémon de 3e génération.", + "singleGeneration.value.4": "4G", + "singleGeneration.desc.4": "Vous ne pouvez choisir que des Pokémon de 4e génération.", + "singleGeneration.value.5": "5G", + "singleGeneration.desc.5": "Vous ne pouvez choisir que des Pokémon de 5e génération.", + "singleGeneration.value.6": "6G", + "singleGeneration.desc.6": "Vous ne pouvez choisir que des Pokémon de 6e génération.", + "singleGeneration.value.7": "7G", + "singleGeneration.desc.7": "Vous ne pouvez choisir que des Pokémon de 7e génération.", + "singleGeneration.value.8": "8G", + "singleGeneration.desc.8": "Vous ne pouvez choisir que des Pokémon de 8e génération.", + "singleGeneration.value.9": "9G", + "singleGeneration.desc.9": "Vous ne pouvez choisir que des Pokémon de 9e génération.", + "singleType.name": "Mono-type", + "singleType.value.0": "Désactivé", + "singleType.desc.0": "Vous ne pouvez choisir que des Pokémon du type sélectionné.", + "singleType.value.1": "Normal", + "singleType.desc.1": "Vous ne pouvez choisir que des Pokémon de type Normal.", + "singleType.value.2": "Combat", + "singleType.desc.2": "Vous ne pouvez choisir que des Pokémon de type Combat.", + "singleType.value.3": "Vol", + "singleType.desc.3": "Vous ne pouvez choisir que des Pokémon de type Vol.", + "singleType.value.4": "Poison", + "singleType.desc.4": "Vous ne pouvez choisir que des Pokémon de type Poison.", + "singleType.value.5": "Sol", + "singleType.desc.5": "Vous ne pouvez choisir que des Pokémon de type Sol.", + "singleType.value.6": "Roche", + "singleType.desc.6": "Vous ne pouvez choisir que des Pokémon de type Roche.", + "singleType.value.7": "Insecte", + "singleType.desc.7": "Vous ne pouvez choisir que des Pokémon de type Insecte.", + "singleType.value.8": "Spectre", + "singleType.desc.8": "Vous ne pouvez choisir que des Pokémon de type Spectre.", + "singleType.value.9": "Acier", + "singleType.desc.9": "Vous ne pouvez choisir que des Pokémon de type Acier.", + "singleType.value.10": "Feu", + "singleType.desc.10": "Vous ne pouvez choisir que des Pokémon de type Feu.", + "singleType.value.11": "Eau", + "singleType.desc.11": "Vous ne pouvez choisir que des Pokémon de type Eau.", + "singleType.value.12": "Plante", + "singleType.desc.12": "Vous ne pouvez choisir que des Pokémon de type Plante.", + "singleType.value.13": "Électrik", + "singleType.desc.13": "Vous ne pouvez choisir que des Pokémon de type Électrik.", + "singleType.value.14": "Psy", + "singleType.desc.14": "Vous ne pouvez choisir que des Pokémon de type Psy.", + "singleType.value.15": "Glace", + "singleType.desc.15": "Vous ne pouvez choisir que des Pokémon de type Glace.", + "singleType.value.16": "Dragon", + "singleType.desc.16": "Vous ne pouvez choisir que des Pokémon de type Dragon.", + "singleType.value.17": "Ténèbres", + "singleType.desc.17": "Vous ne pouvez choisir que des Pokémon de type Ténèbres.", + "singleType.value.18": "Fée", + "singleType.desc.18": "Vous ne pouvez choisir que des Pokémon de type Fée.", +} as const; diff --git a/src/locales/fr/config.ts b/src/locales/fr/config.ts index d523d35bb87..058efe11dfe 100644 --- a/src/locales/fr/config.ts +++ b/src/locales/fr/config.ts @@ -5,6 +5,7 @@ import { battle } from "./battle"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { berry } from "./berry"; import { biome } from "./biome"; +import { challenges } from "./challenges"; import { commandUiHandler } from "./command-ui-handler"; import { PGFbattleSpecDialogue, @@ -18,6 +19,7 @@ import { } from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameMode } from "./game-mode"; import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; @@ -29,6 +31,7 @@ import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; +import { saveSlotSelectUiHandler } from "./save-slot-select-ui-handler"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; @@ -45,6 +48,7 @@ export const frConfig = { battleMessageUiHandler: battleMessageUiHandler, berry: berry, biome: biome, + challenges: challenges, commandUiHandler: commandUiHandler, PGMdialogue: PGMdialogue, PGFdialogue: PGFdialogue, @@ -56,6 +60,7 @@ export const frConfig = { PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameMode: gameMode, gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, @@ -67,6 +72,7 @@ export const frConfig = { pokemon: pokemon, pokemonInfo: pokemonInfo, pokemonInfoContainer: pokemonInfoContainer, + saveSlotSelectUiHandler: saveSlotSelectUiHandler, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, diff --git a/src/locales/fr/game-mode.ts b/src/locales/fr/game-mode.ts new file mode 100644 index 00000000000..28858e46bfa --- /dev/null +++ b/src/locales/fr/game-mode.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const gameMode: SimpleTranslationEntries = { + "classic": "Classique", + "endless": "Infini", + "endlessSpliced": "Infini (Fusions)", + "dailyRun": "Défi du jour", + "unknown": "Inconnu", + "challenge": "Challenge", +} as const; diff --git a/src/locales/fr/menu.ts b/src/locales/fr/menu.ts index e955d4970c0..f9538e9d26c 100644 --- a/src/locales/fr/menu.ts +++ b/src/locales/fr/menu.ts @@ -40,10 +40,10 @@ export const menu: SimpleTranslationEntries = { "weeklyRankings": "Classement de la Semaine", "noRankings": "Pas de Classement", "loading": "Chargement…", + "loadingAsset": "Loading asset: {{assetName}}", "playersOnline": "Joueurs Connectés", - "empty":"Vide", "yes":"Oui", "no":"Non", - "disclaimer": "DISCLAIMER", - "disclaimerDescription": "This game is an unfinished product; it might have playability issues (including the potential loss of save data),\n change without notice, and may or may not be updated further or completed." + "disclaimer": "AVERTISSEMENT", + "disclaimerDescription": "Ce jeu n’est pas un produit fini et peut contenir des problèmes de jouabilité, dont de possibles pertes de sauvegardes,\ndes modifications sans avertissement et pourrait ou non encore être mis à jour ou terminé." } as const; diff --git a/src/locales/fr/save-slot-select-ui-handler.ts b/src/locales/fr/save-slot-select-ui-handler.ts new file mode 100644 index 00000000000..53b68191b9f --- /dev/null +++ b/src/locales/fr/save-slot-select-ui-handler.ts @@ -0,0 +1,9 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const saveSlotSelectUiHandler: SimpleTranslationEntries = { + "overwriteData": "Effacer les données de l’emplacement sélectionné ?", + "loading": "Chargement…", + "wave": "Vague", + "lv": "N.", + "empty": "Vide", +} as const; diff --git a/src/locales/fr/starter-select-ui-handler.ts b/src/locales/fr/starter-select-ui-handler.ts index 4aa6185b439..9f504cab11e 100644 --- a/src/locales/fr/starter-select-ui-handler.ts +++ b/src/locales/fr/starter-select-ui-handler.ts @@ -30,12 +30,12 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "selectMoveSwapWith": "Sélectionnez laquelle échanger avec", "unlockPassive": "Débloquer Passif", "reduceCost": "Diminuer le cout", - "cycleShiny": "R: » Chromatiques", - "cycleForm": "F: » Formes", - "cycleGender": "G: » Sexes", - "cycleAbility": "E: » Talents", - "cycleNature": "N: » Natures", - "cycleVariant": "V: » Variants", + "cycleShiny": ": » Chromatiques", + "cycleForm": ": » Formes", + "cycleGender": ": » Sexes", + "cycleAbility": ": » Talents", + "cycleNature": ": » Natures", + "cycleVariant": ": » Variants", "enablePassive": "Activer Passif", "disablePassive": "Désactiver Passif", "locked": "Verrouillé", diff --git a/src/locales/it/challenges.ts b/src/locales/it/challenges.ts new file mode 100644 index 00000000000..149037be740 --- /dev/null +++ b/src/locales/it/challenges.ts @@ -0,0 +1,67 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const challenges: SimpleTranslationEntries = { + "title": "Challenge Modifiers", + "points": "Bad Ideas", + "confirm_start": "Proceed with these challenges?", + "singleGeneration.name": "Mono Gen", + "singleGeneration.value.0": "Off", + "singleGeneration.desc.0": "You can only use pokemon from the chosen generation.", + "singleGeneration.value.1": "Gen 1", + "singleGeneration.desc.1": "You can only use pokemon from generation one.", + "singleGeneration.value.2": "Gen 2", + "singleGeneration.desc.2": "You can only use pokemon from generation two.", + "singleGeneration.value.3": "Gen 3", + "singleGeneration.desc.3": "You can only use pokemon from generation three.", + "singleGeneration.value.4": "Gen 4", + "singleGeneration.desc.4": "You can only use pokemon from generation four.", + "singleGeneration.value.5": "Gen 5", + "singleGeneration.desc.5": "You can only use pokemon from generation five.", + "singleGeneration.value.6": "Gen 6", + "singleGeneration.desc.6": "You can only use pokemon from generation six.", + "singleGeneration.value.7": "Gen 7", + "singleGeneration.desc.7": "You can only use pokemon from generation seven.", + "singleGeneration.value.8": "Gen 8", + "singleGeneration.desc.8": "You can only use pokemon from generation eight.", + "singleGeneration.value.9": "Gen 9", + "singleGeneration.desc.9": "You can only use pokemon from generation nine.", + "singleType.name": "Mono Type", + "singleType.value.0": "Off", + "singleType.desc.0": "You can only use pokemon of the chosen type.", + "singleType.value.1": "Normal", + "singleType.desc.1": "You can only use pokemon with the Normal type.", + "singleType.value.2": "Fighting", + "singleType.desc.2": "You can only use pokemon with the Fighting type.", + "singleType.value.3": "Flying", + "singleType.desc.3": "You can only use pokemon with the Flying type.", + "singleType.value.4": "Poison", + "singleType.desc.4": "You can only use pokemon with the Poison type.", + "singleType.value.5": "Ground", + "singleType.desc.5": "You can only use pokemon with the Ground type.", + "singleType.value.6": "Rock", + "singleType.desc.6": "You can only use pokemon with the Rock type.", + "singleType.value.7": "Bug", + "singleType.desc.7": "You can only use pokemon with the Bug type.", + "singleType.value.8": "Ghost", + "singleType.desc.8": "You can only use pokemon with the Ghost type.", + "singleType.value.9": "Steel", + "singleType.desc.9": "You can only use pokemon with the Steel type.", + "singleType.value.10": "Fire", + "singleType.desc.10": "You can only use pokemon with the Fire type.", + "singleType.value.11": "Water", + "singleType.desc.11": "You can only use pokemon with the Water type.", + "singleType.value.12": "Grass", + "singleType.desc.12": "You can only use pokemon with the Grass type.", + "singleType.value.13": "Electric", + "singleType.desc.13": "You can only use pokemon with the Electric type.", + "singleType.value.14": "Psychic", + "singleType.desc.14": "You can only use pokemon with the Psychic type.", + "singleType.value.15": "Ice", + "singleType.desc.15": "You can only use pokemon with the Ice type.", + "singleType.value.16": "Dragon", + "singleType.desc.16": "You can only use pokemon with the Dragon type.", + "singleType.value.17": "Dark", + "singleType.desc.17": "You can only use pokemon with the Dark type.", + "singleType.value.18": "Fairy", + "singleType.desc.18": "You can only use pokemon with the Fairy type.", +} as const; diff --git a/src/locales/it/config.ts b/src/locales/it/config.ts index 3f53c8fca01..47926c05f7f 100644 --- a/src/locales/it/config.ts +++ b/src/locales/it/config.ts @@ -5,6 +5,7 @@ import { battle } from "./battle"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { berry } from "./berry"; import { biome } from "./biome"; +import { challenges } from "./challenges"; import { commandUiHandler } from "./command-ui-handler"; import { PGFbattleSpecDialogue, @@ -18,6 +19,7 @@ import { } from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameMode } from "./game-mode"; import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; @@ -29,6 +31,7 @@ import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; +import { saveSlotSelectUiHandler } from "./save-slot-select-ui-handler"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; @@ -45,6 +48,7 @@ export const itConfig = { battleMessageUiHandler: battleMessageUiHandler, berry: berry, biome: biome, + challenges: challenges, commandUiHandler: commandUiHandler, PGMdialogue: PGMdialogue, PGFdialogue: PGFdialogue, @@ -56,6 +60,7 @@ export const itConfig = { PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameMode: gameMode, gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, @@ -67,6 +72,7 @@ export const itConfig = { pokemon: pokemon, pokemonInfo: pokemonInfo, pokemonInfoContainer: pokemonInfoContainer, + saveSlotSelectUiHandler: saveSlotSelectUiHandler, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, diff --git a/src/locales/it/game-mode.ts b/src/locales/it/game-mode.ts new file mode 100644 index 00000000000..be342b4c390 --- /dev/null +++ b/src/locales/it/game-mode.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const gameMode: SimpleTranslationEntries = { + "classic": "Classic", + "endless": "Endless", + "endlessSpliced": "Endless (Spliced)", + "dailyRun": "Daily Run", + "unknown": "Unknown", + "challenge": "Challenge", +} as const; diff --git a/src/locales/it/menu.ts b/src/locales/it/menu.ts index e891146f754..4e3da7ca992 100644 --- a/src/locales/it/menu.ts +++ b/src/locales/it/menu.ts @@ -40,13 +40,13 @@ export const menu: SimpleTranslationEntries = { "weeklyRankings": "Classifica Settimanale", "noRankings": "Nessuna Classifica", "loading": "Caricamento…", + "loadingAsset": "Loading asset: {{assetName}}", "playersOnline": "Giocatori Online", "evolving": "Cosa?\n{{pokemonName}} si evolvendo!", "stoppedEvolving": "{{pokemonName}} ha smesso di evolversi.", "pauseEvolutionsQuestion": "Vuoi sospendere le evoluzioni per {{pokemonName}}?\nLe evoluzioni possono essere riattivate dalla schermata del party.", "evolutionsPaused": "Le evoluzioni sono state sospese per {{pokemonName}}.", "evolutionDone": "Congratulazioni!\n{{pokemonName}} si è evoluto in {{evolvedPokemonName}}!", - "empty":"Vuoto", "yes":"Si", "no":"No", "disclaimer": "DISCLAIMER", diff --git a/src/locales/it/save-slot-select-ui-handler.ts b/src/locales/it/save-slot-select-ui-handler.ts new file mode 100644 index 00000000000..16e36e471a5 --- /dev/null +++ b/src/locales/it/save-slot-select-ui-handler.ts @@ -0,0 +1,9 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const saveSlotSelectUiHandler: SimpleTranslationEntries = { + "overwriteData": "Overwrite the data in the selected slot?", + "loading": "Loading...", + "wave": "Wave", + "lv": "Lv", + "empty": "Vuoto", +} as const; diff --git a/src/locales/it/starter-select-ui-handler.ts b/src/locales/it/starter-select-ui-handler.ts index 0f3f9df421a..ae406183a90 100644 --- a/src/locales/it/starter-select-ui-handler.ts +++ b/src/locales/it/starter-select-ui-handler.ts @@ -30,12 +30,12 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "selectMoveSwapWith": "Seleziona una mossa da scambiare con", "unlockPassive": "Sblocca Passiva", "reduceCost": "Riduci Costo", - "cycleShiny": "R: Alterna Shiny", - "cycleForm": "F: Alterna Forma", - "cycleGender": "G: Alterna Sesso", - "cycleAbility": "E: Alterna Abilità", - "cycleNature": "N: Alterna Natura", - "cycleVariant": "V: Alterna Variante", + "cycleShiny": ": Alterna Shiny", + "cycleForm": ": Alterna Forma", + "cycleGender": ": Alterna Sesso", + "cycleAbility": ": Alterna Abilità", + "cycleNature": ": Alterna Natura", + "cycleVariant": ": Alterna Variante", "enablePassive": "Attiva Passiva", "disablePassive": "Disattiva Passiva", "locked": "Bloccato", diff --git a/src/locales/ko/challenges.ts b/src/locales/ko/challenges.ts new file mode 100644 index 00000000000..7c7f6199b1d --- /dev/null +++ b/src/locales/ko/challenges.ts @@ -0,0 +1,67 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const challenges: SimpleTranslationEntries = { + "title": "챌린지 조건 설정", + "points": "Bad Ideas", + "confirm_start": "이 조건으로 챌린지를 진행하시겠습니까?", + "singleGeneration.name": "단일 세대", + "singleGeneration.value.0": "설정 안함", + "singleGeneration.desc.0": "선택한 세대의 포켓몬만 사용할 수 있습니다.", + "singleGeneration.value.1": "1세대", + "singleGeneration.desc.1": "1세대의 포켓몬만 사용할 수 있습니다.", + "singleGeneration.value.2": "2세대", + "singleGeneration.desc.2": "2세대의 포켓몬만 사용할 수 있습니다.", + "singleGeneration.value.3": "3세대", + "singleGeneration.desc.3": "3세대의 포켓몬만 사용할 수 있습니다.", + "singleGeneration.value.4": "4세대", + "singleGeneration.desc.4": "4세대의 포켓몬만 사용할 수 있습니다r", + "singleGeneration.value.5": "5세대", + "singleGeneration.desc.5": "5세대의 포켓몬만 사용할 수 있습니다.", + "singleGeneration.value.6": "6세대", + "singleGeneration.desc.6": "6세대의 포켓몬만 사용할 수 있습니다.", + "singleGeneration.value.7": "7세대", + "singleGeneration.desc.7": "7세대의 포켓몬만 사용할 수 있습니다.", + "singleGeneration.value.8": "8세대", + "singleGeneration.desc.8": "8세대의 포켓몬만 사용할 수 있습니다.", + "singleGeneration.value.9": "9세대", + "singleGeneration.desc.9": "9세대의 포켓몬만 사용할 수 있습니다.", + "singleType.name": "단일 타입", + "singleType.value.0": "설정 안함", + "singleType.desc.0": "선택한 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.1": "노말", + "singleType.desc.1": "노말 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.2": "격투", + "singleType.desc.2": "격투 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.3": "비행", + "singleType.desc.3": "비행 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.4": "독", + "singleType.desc.4": "독 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.5": "땅", + "singleType.desc.5": "땅 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.6": "바위 ", + "singleType.desc.6": "바위 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.7": "벌레", + "singleType.desc.7": "벌레 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.8": "고스트", + "singleType.desc.8": "고스트 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.9": "강철", + "singleType.desc.9": "강철 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.10": "불꽃", + "singleType.desc.10": "불꽃 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.11": "물", + "singleType.desc.11": "물 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.12": "풀", + "singleType.desc.12": "풀 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.13": "전기", + "singleType.desc.13": "전기 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.14": "에스퍼", + "singleType.desc.14": "에스퍼 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.15": "얼음", + "singleType.desc.15": "얼음 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.16": "드래곤", + "singleType.desc.16": "드래곤 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.17": "악", + "singleType.desc.17": "악 타입의 포켓몬만 사용할 수 있습니다.", + "singleType.value.18": "페어리", + "singleType.desc.18": "페어리 타입의 포켓몬만 사용할 수 있습니다.", +} as const; diff --git a/src/locales/ko/config.ts b/src/locales/ko/config.ts index 936154153be..e79afbd8212 100644 --- a/src/locales/ko/config.ts +++ b/src/locales/ko/config.ts @@ -5,6 +5,7 @@ import { battle } from "./battle"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { berry } from "./berry"; import { biome } from "./biome"; +import { challenges } from "./challenges"; import { commandUiHandler } from "./command-ui-handler"; import { PGFbattleSpecDialogue, @@ -18,6 +19,7 @@ import { } from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameMode } from "./game-mode"; import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; @@ -29,6 +31,7 @@ import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; +import { saveSlotSelectUiHandler } from "./save-slot-select-ui-handler"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; @@ -45,6 +48,7 @@ export const koConfig = { battleMessageUiHandler: battleMessageUiHandler, berry: berry, biome: biome, + challenges: challenges, commandUiHandler: commandUiHandler, PGMdialogue: PGMdialogue, PGFdialogue: PGFdialogue, @@ -56,6 +60,7 @@ export const koConfig = { PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameMode: gameMode, gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, @@ -67,6 +72,7 @@ export const koConfig = { pokemon: pokemon, pokemonInfo: pokemonInfo, pokemonInfoContainer: pokemonInfoContainer, + saveSlotSelectUiHandler: saveSlotSelectUiHandler, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, diff --git a/src/locales/ko/dialogue.ts b/src/locales/ko/dialogue.ts index cdb6670650f..13919ef244b 100644 --- a/src/locales/ko/dialogue.ts +++ b/src/locales/ko/dialogue.ts @@ -697,19 +697,19 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "falkner": { "encounter": { - 1: "I'll show you the real power of the magnificent bird Pokémon!", - 2: "Winds, stay with me!", - 3: "Dad! I hope you're watching me battle from above!" + 1: "넓은 하늘을 화려하게 나는 새 포켓몬의 진정한 강함을 알게 해주겠다!", + 2: "바람이여, 나에게 오라!", + 3: "아버지, 내 시합을 하늘에서도 봐줘!" }, "victory": { - 1: "I understand… I'll bow out gracefully.", - 2: "A defeat is a defeat. You are strong indeed.", - 3: "…Shoot! Yeah, I lost." + 1: "알았다… 미련없이 땅에 내려가지.", + 2: "패배는 패배니까. 넌 정말 강하군.", + 3: "…큭! 그래, 내가 졌다." }, "defeat": { - 1: "Dad! I won with your cherished bird Pokémon…", - 2: "Bird Pokémon are the best after all!", - 3: "Feels like I'm catching up to my dad!" + 1: "아버지! 소중히 여기던 새 포켓몬으로 이겼어…", + 2: "언제나 새 포켓몬이 최강이다!", + 3: "아버지를 따라 잡은 기분이군!" } }, "nessa": { @@ -874,83 +874,83 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "morty": { "encounter": { - 1: `With a little more, I could see a future in which I meet the legendary Pokémon. - $You're going to help me reach that level!`, - 2: `It's said that a rainbow-hued Pokémon will come down to appear before a truly powerful Trainer. - $I believed that tale, so I have secretly trained here all my life. As a result, I can now see what others cannot. - $I see a shadow of the person who will make the Pokémon appear. - $I believe that person is me! You're going to help me reach that level!`, - 3: "Whether you choose to believe or not, mystic power does exist.", - 4: "You can bear witness to the fruits of my training.", - 5: "You must make your soul one with that of Pokémon. Can you do this?", - 6: "Say, do you want to be part of my training?" + 1: `조금만 더 노력하면, 내가 전설의 포켓몬을 만나는 미래가 보여. + $내가 그 수준에 도달할 수 있게 도와줘!`, + 2: `커다란 무지개색 포켓몬은 진정한 강함을 가진 트레이너 앞에 나타난다는 이야기가 있어. + $난 그 이야기를 믿고, 줄곧 이 곳에서 몰래 수행하고 있어. 그 결과로, 다른 사람들은 볼 수 없는 것을 볼 수 있게 됐지. + $내겐 그 포켓몬을 나타나게 할 사람의 그림자가 보이거든. + $난 그게 나라고 믿어! 넌 내가 그 수준에 올라갈 수 있도록 도와줘야겠어!`, + 3: "네가 믿든 믿지 않든, 불가사의한 힘은 존재해.", + 4: "넌 내 수련의 결실을 보게 될 거야.", + 5: "포켓몬과 너의 영혼을 하나로 만들어야 해. 가능하겠어?", + 6: "저기, 너 내 수행의 일부분이 되고 싶은거지?" }, "victory": { - 1: "I'm not good enough yet…", - 2: `I see… Your journey has taken you to far-away places and you have witnessed much more than I. - $I envy you for that…`, - 3: "How is this possible…", - 4: `I don't think our potentials are so different. - $But you seem to have something more than that… So be it.`, - 5: "Guess I need more training.", - 6: "That's a shame." + 1: "나는 아직 멀었구나…", + 2: `그래… 여행으로 먼 곳을 돌아다니면서, 나보다 훨씬 많은 것을 봐왔구나. + $네가 조금 부럽네…`, + 3: "이게 어떻게 가능한 거지…", + 4: `우리의 잠재력은 그렇게 다르진 않은 것 같아. + $그치만 넌 그것과 다른 무언가를 많이 갖고 있는 것 같네… 흐음.`, + 5: "수련이 더 필요하겠군.", + 6: "안타깝게 됐네." }, "defeat": { - 1: "I moved… one step ahead again.", - 2: "Fufufu…", - 3: "Wh-what?! It can't be! Even that wasn't enough?", - 4: "I feel like I just smashed through a really stubborn boulder!", - 5: "Ahahahah!", - 6: "I knew I would win!" + 1: "내가…다시 한 발짝 앞섰어.", + 2: "후후훗…", + 3: "뭐-뭐야?! 이럴 수가! 그것도 부족해?", + 4: "정말 단단한 바위를 뚫고 나온 기분인데!", + 5: "아하하하하!", + 6: "내가 이길 줄 알았어!" } }, "crispin": { "encounter": { - 1: "I wanna win, so that's exactly what I'll do!", - 2: "I battle because I wanna battle! And you know what? That's how it should be!" + 1: "이기고 싶으니까 이기고 말겠어!", + 2: "싸우고 싶으면 싸운다! 이거면 충분하지 않아!?" }, "victory": { - 1: "I wanted to win…but I lost!", - 2: "I lost…'cause I couldn't win!" + 1: "이기고 싶었는데…졌잖아!", + 2: "이기지 못해서…지고 말았어!" }, "defeat": { - 1: "Hey, wait a sec. Did I just win? I think I just won! Talk about satisfying!", - 2: "Wooo! That was amazing!" + 1: "잠시만. 나 지금 이긴거지? 이긴 거 맞지! 기분 좋은데!", + 2: "우와아! 이거 굉장한데!" } }, "amarys": { "encounter": { - 1: `I want to be the one to help a certain person. That being the case, I cannot afford to lose. - $… Our battle starts now.`, + 1: `네리네는 그 사람을 구원하고 싶습니다. 그렇기에 패배는 용납되지 않습니다. + $… 승부를 시작합니다.`, }, "victory": { - 1: "I am… not enough, I see." + 1: "네리네는… 안 된다는 건가요." }, "defeat": { - 1: "Victory belongs to me. Well fought." + 1: "네리네가 승리했습니다. 수고하셨습니다." } }, "lacey": { "encounter": { - 1: "I'll be facing you with my usual party as a member of the Elite Four." + 1: "이번에는 사천왕으로서 승부하는 거니까 평소 사용하는 아이들로 상대해 드릴게요!" }, "victory": { - 1: "That was a great battle!" + 1: "멋진 포켓몬 배틀이었어요!" }, "defeat": { - 1: "Let's give your Pokémon a nice round of applause for their efforts!" + 1: "당신의 포켓몬의 노력에 박수를 보내주세요!" } }, "drayton": { "encounter": { - 1: `Man, I love chairs. Don't you love chairs? What lifesavers. - $I don't get why everyone doesn't just sit all the time. Standing up's tiring work!`, + 1: `의자는 좋은 거야. 너도 그렇게 생각해? 정말 고마운 물건이지. + $왜 다들 앉지 않는 걸까. 서 있는 건 힘들잖아!`, }, "victory": { - 1: "Guess I should've expected that!" + 1: "전보다 더 강해질 줄이야!" }, "defeat": { - 1: "Heh heh! Don't mind me, just scooping up a W over here. I get it if you're upset, but don't go full Kieran on me, OK?" + 1: "헤헤헷! 내 승리야. 분한 건 알겠지만 카지처럼 나가떨어지지마, 응?" } }, "ramos": { @@ -1146,26 +1146,26 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "lorelei": { "encounter": { - 1: `No one can best me when it comes to icy Pokémon! Freezing moves are powerful! - $Your Pokémon will be at my mercy when they are frozen solid! Hahaha! Are you ready?`, + 1: `얼음포켓몬을 내보내면 대적할 사람이 없지! 상대를 얼린다는 건 매우 강력한 공격이야. + $네 포켓몬이 꽁꽁 얼면 그때부턴 내 손바닥 위거든! 아하하! 준비됐어?`, }, "victory": { - 1: "How dare you!" + 1: "어떻게 감히!" }, "defeat": { - 1: "There's nothing you can do once you're frozen." + 1: "얼어붙은 넌 아무것도 할 수 없어." } }, "will": { "encounter": { - 1: `I have trained all around the world, making my psychic Pokémon powerful. - $I can only keep getting better! Losing is not an option!`, + 1: `나는 전세계를 돌아다니며, 강한 에스퍼 포켓몬을 만들도록 수행해왔다. + $계속 더 정진하겠다! 패배는 선택지에 없어!`, }, "victory": { - 1: "I… I can't… believe it…" + 1: "이… 내가… 믿을수 없어…" }, "defeat": { - 1: "That was close. I wonder what it is that you lack." + 1: "근소한 차이였다. 네게 부족한 것이 무엇인지 궁금하군." } }, "malva": { @@ -1216,35 +1216,35 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "bruno": { "encounter": { - 1: "We will grind you down with our superior power! Hoo hah!" + 1: "우월한 힘으로 너를 부숴주지! 우! 하~앗!" }, "victory": { - 1: "Why? How could I lose?" + 1: "하? 어떻게 내가 진 거지?" }, "defeat": { - 1: "You can challenge me all you like, but the results will never change!" + 1: "얼마든지 내게 도전 할 수 있지만, 결과는 절대 바뀌지 않을 거다!" } }, "bugsy": { "encounter": { - 1: "I'm Bugsy! I never lose when it comes to bug Pokémon!" + 1: "내 이름은 호일! 벌레 포켓몬에 대해서라면 누구에게도 지지 않아!" }, "victory": { - 1: "Whoa, amazing! You're an expert on Pokémon!\nMy research isn't complete yet. OK, you win." + 1: "우와, 대단해! 넌 포켓몬 전문가구나!\n내 연구는 아직 안 끝났네. 응, 네가 이겼어." }, "defeat": { - 1: "Thanks! Thanks to our battle, I was also able to make progress in my research!" + 1: "고마워! 방금 승부 덕분에, 내 연구도 진전을 이룬 것 같아!" } }, "koga": { "encounter": { - 1: "Fwahahahaha! Pokémon are not merely about brute force--you shall see soon enough!" + 1: "후하하하! 포켓몬은 딘순히 강한 것만이 아니다--곧 알려주지!" }, "victory": { - 1: "Ah! You've proven your worth!" + 1: "하! 스스로 증명해냈군!" }, "defeat": { - 1: "Have you learned to fear the techniques of the ninja?" + 1: "인술을 피하는 방법을 배워보겠나?" } }, "bertha": { @@ -1319,13 +1319,13 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "agatha": { "encounter": { - 1: "Pokémon are for battling! I'll show you how a real Trainer battles!" + 1: "포켓몬은 싸우게 하려고 있는 것이야! 진정한 싸움이라는 것을 보여주겠다!" }, "victory": { - 1: "Oh my! You're something special, child!" + 1: "이런! 넌 무언가 특별하구나, 꼬마야!" }, "defeat": { - 1: "Bahaha. That's how a proper battle's done!" + 1: "바하하하. 제대로 된 승부는 이렇게 하는거다!" } }, "flint": { @@ -1414,33 +1414,33 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "lance": { "encounter": { - 1: "I've been waiting for you. Allow me to test your skill.", - 2: "I thought that you would be able to get this far. Let's get this started." + 1: "널 기다리고 있었다. 그 실력을 시험해보겠어.", + 2: "여기까지 올 수 있을거라고 생각했다. 슬슬 시작해볼까." }, "victory": { - 1: "You got me. You are magnificent!", - 2: "I never expected another trainer to beat me… I'm surprised." + 1: "날 따라잡았군. 훌륭해!", + 2: "다른 트레이너가 날 이길 거라곤 생각 못했는데… 놀랍군." }, "defeat": { - 1: "That was close. Want to try again?", - 2: "It's not that you are weak. Don't let it bother you." + 1: "근소하군. 다시 해볼까?", + 2: "네가 약해서가 아니다. 신경쓰지 말도록." } }, "karen": { "encounter": { - 1: "I am Karen. Would you care for a showdown with my Dark-type Pokémon?", - 2: "I am unlike those you've already met.", - 3: "You've assembled a charming team. Our battle should be a good one." + 1: "난 카렌! 내 악 타입 포켓몬과의 승부를 원하니?", + 2: "난 네가 이전에 만났던 트레이너들과는 달라.", + 3: "강한 포켓몬, 약한 포켓몬, 그런 건 사람이 멋대로 정하는 것." }, "victory": { - 1: "No! I can't win. How did you become so strong?", - 2: "I will not stray from my chosen path.", - 3: "The Champion is looking forward to meeting you." + 1: "좋아하는 마음이 전해진다면 포켓몬도 답할거야. 그렇게 강해지는 거지", + 2: "난 내가 선택한 길을 걸어갈거야.", + 3: "챔피언이 너를 기다리고 있어." }, "defeat": { - 1: "That's about what I expected.", - 2: "Well, that was relatively entertaining.", - 3: "Come visit me anytime." + 1: "정말 강한 트레이너라면 좋아하는 포켓몬으로 이길 수 있도록 열심히 해야 해.", + 2: "뭐, 비교적 재밌었어.", + 3: "언제라도 다시 찾아와, 상대해줄게." } }, "milo": { @@ -1507,13 +1507,13 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "blue": { "encounter": { - 1: "You must be pretty good to get this far." + 1: "여기까지 왔다니, 실력이 꽤 봐줄만 할 것 같은데." }, "victory": { - 1: "I've only lost to him and now to you… Him? Hee, hee…" + 1: "그 녀석한테만 지는 줄 알았는데… 누구냐고? 하, 하…" }, "defeat": { - 1: "See? My power is what got me here." + 1: "봤지? 여기까지 온 내 실력." } }, "piers": { @@ -1540,24 +1540,24 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "jasmine": { "encounter": { - 1: "Oh… Your Pokémon are impressive. I think I will enjoy this." + 1: "와… 당신의 포켓몬은 인상적이네요. 재미있을 것 같아요." }, "victory": { - 1: "You are truly strong. I'll have to try much harder, too." + 1: "당신은 정말 강하네요. 저도 더 열심히 노력해야겠어요." }, "defeat": { - 1: "I never expected to win." + 1: "이길 줄은 몰랐어요." } }, "lance_champion": { "encounter": { - 1: "I am still the Champion. I won't hold anything back." + 1: "여전히 난 챔피언이다. 더이상 주저할 게 없군." }, "victory": { - 1: "This is the emergence of a new Champion." + 1: "새로운 챔피언의 등장이군." }, "defeat": { - 1: "I successfully defended my Championship." + 1: "성공적으로 챔피언 자리를 지켜냈다." } }, "steven": { @@ -1652,24 +1652,24 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "whitney": { "encounter": { - 1: "Hey! Don't you think Pokémon are, like, super cute?" + 1: "있지! 포켓몬들 말이야, 정말 너무 귀엽지?" }, "victory": { - 1: "Waaah! Waaah! You're so mean!" + 1: "흑! 으아앙! 너무해!" }, "defeat": { - 1: "And that's that!" + 1: "이걸로 끝!" } }, "chuck": { "encounter": { - 1: "Hah! You want to challenge me? Are you brave or just ignorant?" + 1: "하! 나에게 도전하겠다고? 용감한 거냐, 아니면 그냥 무모한 거냐?" }, "victory": { - 1: "You're strong! Would you please make me your apprentice?" + 1: "자네 강하군! 나를 제자로 삼아주겠나?" }, "defeat": { - 1: "There. Do you realize how much more powerful I am than you?" + 1: "자. 내가 자네보다 얼마나 더 강력한지 깨달았겠지?" } }, "katy": { @@ -1685,24 +1685,24 @@ export const PGMdialogue: DialogueTranslationEntries = { }, "pryce": { "encounter": { - 1: "Youth alone does not ensure victory! Experience is what counts." + 1: "젊음만으로는 승리를 보장할 수 없다! 중요한 것은 경험이다." }, "victory": { - 1: "Outstanding! That was perfect. Try not to forget what you feel now." + 1: "특출하군! 완벽해. 지금 이 느낌을 잊지 말도록." }, "defeat": { - 1: "Just as I envisioned." + 1: "내가 예상했던 그대로군." } }, "clair": { "encounter": { - 1: "Do you know who I am? And you still dare to challenge me?" + 1: "내가 누군지 알지? 그런데도 감히 내게 도전해?" }, "victory": { - 1: "I wonder how far you can get with your skill level. This should be fascinating." + 1: "네 실력이 어디까지 올라갈 수 있는지 궁금하네. 아주 흥미진진하겠어." }, "defeat": { - 1: "That's that." + 1: "끝이다." } }, "maylene": { diff --git a/src/locales/ko/game-mode.ts b/src/locales/ko/game-mode.ts new file mode 100644 index 00000000000..ad387010a8f --- /dev/null +++ b/src/locales/ko/game-mode.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const gameMode: SimpleTranslationEntries = { + "classic": "클래식", + "endless": "엔들리스", + "endlessSpliced": "엔들리스(융합체)", + "dailyRun": "데일리 런", + "unknown": "언노운", + "challenge": "챌린지", +} as const; diff --git a/src/locales/ko/menu.ts b/src/locales/ko/menu.ts index 3bd52540f94..9245d67533a 100644 --- a/src/locales/ko/menu.ts +++ b/src/locales/ko/menu.ts @@ -45,8 +45,8 @@ export const menu: SimpleTranslationEntries = { "weeklyRankings": "주간 랭킹", "noRankings": "랭킹 정보 없음", "loading": "로딩 중…", + "loadingAsset": "Loading asset: {{assetName}}", "playersOnline": "플레이어 온라인", - "empty":"빈 슬롯", "yes":"예", "no":"아니오", "disclaimer": "면책 조항", diff --git a/src/locales/ko/move.ts b/src/locales/ko/move.ts index 60a5a5eaafc..700a623c3cc 100644 --- a/src/locales/ko/move.ts +++ b/src/locales/ko/move.ts @@ -3393,7 +3393,7 @@ export const move: MoveTranslationEntries = { effect: "무수히 많은 불덩이로 공격한다. 화상 상태로 만들 때가 있다. 상대가 상태 이상인 경우 위력이 2배가 된다." }, ceaselessEdge: { - name: "비검천충파", + name: "비검천중파", effect: "조개껍질 검으로 공격한다. 조개껍질 파편은 압정이 되어 상대의 발밑에 흩어진다." }, bleakwindStorm: { diff --git a/src/locales/ko/save-slot-select-ui-handler.ts b/src/locales/ko/save-slot-select-ui-handler.ts new file mode 100644 index 00000000000..213da34bda5 --- /dev/null +++ b/src/locales/ko/save-slot-select-ui-handler.ts @@ -0,0 +1,9 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const saveSlotSelectUiHandler: SimpleTranslationEntries = { + "overwriteData": "선택한 슬롯에 데이터를 덮어쓰시겠습니까?", + "loading": "로딩 중...", + "wave": "웨이브", + "lv": "Lv", + "empty": "빈 슬롯", +} as const; diff --git a/src/locales/ko/starter-select-ui-handler.ts b/src/locales/ko/starter-select-ui-handler.ts index 9a27824e541..41001488458 100644 --- a/src/locales/ko/starter-select-ui-handler.ts +++ b/src/locales/ko/starter-select-ui-handler.ts @@ -30,12 +30,12 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "selectMoveSwapWith": "교체될 기술을 선택해주세요. 대상:", "unlockPassive": "패시브 해금", "reduceCost": "코스트 줄이기", - "cycleShiny": "R: 특별한 색", - "cycleForm": "F: 폼 체인지", - "cycleGender": "G: 암수 전환", - "cycleAbility": "E: 특성 전환", - "cycleNature": "N: 성격 전환", - "cycleVariant": "V: 색상 전환", + "cycleShiny": ": 특별한 색", + "cycleForm": ": 폼 체인지", + "cycleGender": ": 암수 전환", + "cycleAbility": ": 특성 전환", + "cycleNature": ": 성격 전환", + "cycleVariant": ": 색상 전환", "enablePassive": "패시브 활성화", "disablePassive": "패시브 비활성화", "locked": "잠김", diff --git a/src/locales/pt_BR/battle.ts b/src/locales/pt_BR/battle.ts index 47a74fe17b1..ebe0a090473 100644 --- a/src/locales/pt_BR/battle.ts +++ b/src/locales/pt_BR/battle.ts @@ -5,7 +5,7 @@ export const battle: SimpleTranslationEntries = { "trainerAppeared": "{{trainerName}}\nquer batalhar!", "trainerAppearedDouble": "{{trainerName}}\nquerem batalhar!", "singleWildAppeared": "Um {{pokemonName}} selvagem apareceu!", - "trainerSendOut": "{{trainerName}} sent out\n{{pokemonName}}!", + "trainerSendOut": "{{trainerName}} escolheu\n{{pokemonName}}!", "multiWildAppeared": "Um {{pokemonName1}} e um {{pokemonName2}} selvagens\napareceram!", "playerComeBack": "{{pokemonName}}, retorne!", "trainerComeBack": "{{trainerName}} retirou {{pokemonName}} da batalha!", @@ -13,9 +13,9 @@ export const battle: SimpleTranslationEntries = { "trainerGo": "{{trainerName}} escolheu {{pokemonName}}!", "switchQuestion": "Quer trocar\nde {{pokemonName}}?", "trainerDefeated": "Você derrotou\n{{trainerName}}!", - "moneyWon": "You got\n₽{{moneyAmount}} for winning!", + "moneyWon": "Você ganhou\n₽{{moneyAmount}} por ganhar!", "pokemonCaught": "{{pokemonName}} foi capturado!", - "partyFull": "Your party is full.\nRelease a Pokémon to make room for {{pokemonName}}?", + "partyFull": "Sua equipe está cheia.\nSolte um Pokémon para ter espaço para {{pokemonName}}?", "pokemon": "Pokémon", "sendOutPokemon": "{{pokemonName}}, eu escolho você!!", "hitResultCriticalHit": "Um golpe crítico!", diff --git a/src/locales/pt_BR/challenges.ts b/src/locales/pt_BR/challenges.ts new file mode 100644 index 00000000000..9b7bfe1973c --- /dev/null +++ b/src/locales/pt_BR/challenges.ts @@ -0,0 +1,67 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const challenges: SimpleTranslationEntries = { + "title": "Desafios", + "start": "Iniciar", + "illegalEvolution": "{{pokemon}} não pode ser escolhido\nnesse desafio!", + "singleGeneration.name": "Geração Única", + "singleGeneration.value.0": "Desligado", + "singleGeneration.desc.0": "Você só pode user Pokémon de uma única geração.", + "singleGeneration.value.1": "Geração 1", + "singleGeneration.desc.1": "Você só pode user Pokémon da primeira geração.", + "singleGeneration.value.2": "Geração 2", + "singleGeneration.desc.2": "Você só pode user Pokémon da segunda geração.", + "singleGeneration.value.3": "Geração 3", + "singleGeneration.desc.3": "Você só pode user Pokémon da terceira geração.", + "singleGeneration.value.4": "Geração 4", + "singleGeneration.desc.4": "Você só pode user Pokémon da quarta geração.", + "singleGeneration.value.5": "Geração 5", + "singleGeneration.desc.5": "Você só pode user Pokémon da quinta geração.", + "singleGeneration.value.6": "Geração 6", + "singleGeneration.desc.6": "Você só pode user Pokémon da sexta geração.", + "singleGeneration.value.7": "Geração 7", + "singleGeneration.desc.7": "Você só pode user Pokémon da sétima geração.", + "singleGeneration.value.8": "Geração 8", + "singleGeneration.desc.8": "Você só pode user Pokémon da oitava geração.", + "singleGeneration.value.9": "Geração 9", + "singleGeneration.desc.9": "Você só pode user Pokémon da nona geração.", + "singleType.name": "Tipo Único", + "singleType.value.0": "Desligado", + "singleType.desc.0": "Você só pode user Pokémon de um único tipo.", + "singleType.value.1": "Normal", + "singleType.desc.1": "Você só pode user Pokémon do tipo Normal.", + "singleType.value.2": "Lutador", + "singleType.desc.2": "Você só pode user Pokémon do tipo Lutador.", + "singleType.value.3": "Voador", + "singleType.desc.3": "Você só pode user Pokémon do tipo Voador.", + "singleType.value.4": "Veneno", + "singleType.desc.4": "Você só pode user Pokémon do tipo Veneno.", + "singleType.value.5": "Terra", + "singleType.desc.5": "Você só pode user Pokémon do tipo Terra.", + "singleType.value.6": "Pedra", + "singleType.desc.6": "Você só pode user Pokémon do tipo Pedra.", + "singleType.value.7": "Inseto", + "singleType.desc.7": "Você só pode user Pokémon do tipo Inseto.", + "singleType.value.8": "Fantasma", + "singleType.desc.8": "Você só pode user Pokémon do tipo Fantasma.", + "singleType.value.9": "Aço", + "singleType.desc.9": "Você só pode user Pokémon do tipo Aço.", + "singleType.value.10": "Fogo", + "singleType.desc.10": "Você só pode user Pokémon do tipo Fogo.", + "singleType.value.11": "Água", + "singleType.desc.11": "Você só pode user Pokémon do tipo Água.", + "singleType.value.12": "Grama", + "singleType.desc.12": "Você só pode user Pokémon do tipo Grama.", + "singleType.value.13": "Elétrico", + "singleType.desc.13": "Você só pode user Pokémon do tipo Elétrico.", + "singleType.value.14": "Psíquico", + "singleType.desc.14": "Você só pode user Pokémon do tipo Psíquico.", + "singleType.value.15": "Gelo", + "singleType.desc.15": "Você só pode user Pokémon do tipo Gelo.", + "singleType.value.16": "Dragão", + "singleType.desc.16": "Você só pode user Pokémon do tipo Dragão.", + "singleType.value.17": "Sombrio", + "singleType.desc.17": "Você só pode user Pokémon do tipo Sombrio.", + "singleType.value.18": "Fada", + "singleType.desc.18": "Você só pode user Pokémon do tipo Fada.", +} as const; diff --git a/src/locales/pt_BR/config.ts b/src/locales/pt_BR/config.ts index 3baca7df382..b7c5e3a2403 100644 --- a/src/locales/pt_BR/config.ts +++ b/src/locales/pt_BR/config.ts @@ -5,6 +5,7 @@ import { battle } from "./battle"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { berry } from "./berry"; import { biome } from "./biome"; +import { challenges } from "./challenges"; import { commandUiHandler } from "./command-ui-handler"; import { PGFbattleSpecDialogue, @@ -18,6 +19,7 @@ import { } from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameMode } from "./game-mode"; import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; @@ -25,17 +27,18 @@ import { menuUiHandler } from "./menu-ui-handler"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; +import { partyUiHandler } from "./party-ui-handler"; import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; +import { saveSlotSelectUiHandler } from "./save-slot-select-ui-handler"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; import { weather } from "./weather"; -import { partyUiHandler } from "./party-ui-handler"; export const ptBrConfig = { ability: ability, @@ -45,6 +48,7 @@ export const ptBrConfig = { battleMessageUiHandler: battleMessageUiHandler, berry: berry, biome: biome, + challenges: challenges, commandUiHandler: commandUiHandler, PGMdialogue: PGMdialogue, PGFdialogue: PGFdialogue, @@ -56,6 +60,7 @@ export const ptBrConfig = { PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameMode: gameMode, gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, @@ -63,10 +68,12 @@ export const ptBrConfig = { modifierType: modifierType, move: move, nature: nature, + partyUiHandler: partyUiHandler, pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, pokemonInfoContainer: pokemonInfoContainer, + saveSlotSelectUiHandler: saveSlotSelectUiHandler, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, @@ -75,5 +82,4 @@ export const ptBrConfig = { tutorial: tutorial, voucher: voucher, weather: weather, - partyUiHandler: partyUiHandler }; diff --git a/src/locales/pt_BR/game-mode.ts b/src/locales/pt_BR/game-mode.ts new file mode 100644 index 00000000000..5f0e930703b --- /dev/null +++ b/src/locales/pt_BR/game-mode.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const gameMode: SimpleTranslationEntries = { + "classic": "Clássico", + "endless": "Infinito", + "endlessSpliced": "Infinito (Fusões)", + "dailyRun": "Desafio Diário", + "unknown": "Desconhecido", + "challenge": "Desafio", +} as const; diff --git a/src/locales/pt_BR/growth.ts b/src/locales/pt_BR/growth.ts index 50762e5ad94..945520c91d7 100644 --- a/src/locales/pt_BR/growth.ts +++ b/src/locales/pt_BR/growth.ts @@ -1,10 +1,10 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const growth: SimpleTranslationEntries = { - "Erratic": "Instável", + "Erratic": "Muito Rápido", "Fast": "Rápido", "Medium_Fast": "Meio Rápido", "Medium_Slow": "Meio Lento", "Slow": "Lento", - "Fluctuating": "Flutuante" + "Fluctuating": "Muito Lento", } as const; diff --git a/src/locales/pt_BR/menu.ts b/src/locales/pt_BR/menu.ts index 000ffb1e397..f4fc8cc3a72 100644 --- a/src/locales/pt_BR/menu.ts +++ b/src/locales/pt_BR/menu.ts @@ -45,8 +45,8 @@ export const menu: SimpleTranslationEntries = { "weeklyRankings": "Classificação Semanal", "noRankings": "Sem Classificação", "loading": "Carregando…", + "loadingAsset": "Carregando recurso: {{assetName}}", "playersOnline": "Jogadores Ativos", - "empty": "Vazio", "yes": "Sim", "no": "Não", "disclaimer": "AVISO", diff --git a/src/locales/pt_BR/party-ui-handler.ts b/src/locales/pt_BR/party-ui-handler.ts index 9d3c7baa9ae..763d733f3e8 100644 --- a/src/locales/pt_BR/party-ui-handler.ts +++ b/src/locales/pt_BR/party-ui-handler.ts @@ -1,10 +1,10 @@ import { SimpleTranslationEntries } from "#app/plugins/i18n"; export const partyUiHandler: SimpleTranslationEntries = { - "SEND_OUT": "Send Out", - "SUMMARY": "Summary", - "CANCEL": "Cancel", - "RELEASE": "Release", - "APPLY": "Apply", - "TEACH": "Teach" + "SEND_OUT": "Trocar", + "SUMMARY": "Sumário", + "CANCEL": "Cancelar", + "RELEASE": "Soltar", + "APPLY": "Aplicar", + "TEACH": "Ensinar", } as const; diff --git a/src/locales/pt_BR/save-slot-select-ui-handler.ts b/src/locales/pt_BR/save-slot-select-ui-handler.ts new file mode 100644 index 00000000000..23aeed7c5c7 --- /dev/null +++ b/src/locales/pt_BR/save-slot-select-ui-handler.ts @@ -0,0 +1,9 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const saveSlotSelectUiHandler: SimpleTranslationEntries = { + "overwriteData": "Substituir os dados desse slot?", + "loading": "Carregando...", + "wave": "Onda", + "lv": "Nv", + "empty": "Vazio", +} as const; diff --git a/src/locales/pt_BR/starter-select-ui-handler.ts b/src/locales/pt_BR/starter-select-ui-handler.ts index 4d4ee94505b..fc98e72c614 100644 --- a/src/locales/pt_BR/starter-select-ui-handler.ts +++ b/src/locales/pt_BR/starter-select-ui-handler.ts @@ -30,12 +30,12 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "selectMoveSwapWith": "Escolha o movimento que substituirá", "unlockPassive": "Aprender Passiva", "reduceCost": "Reduzir Custo", - "cycleShiny": "R: » Shiny", - "cycleForm": "F: » Forma", - "cycleGender": "G: » Gênero", - "cycleAbility": "E: » Habilidade", - "cycleNature": "N: » Natureza", - "cycleVariant": "V: » Variante", + "cycleShiny": ": » Shiny", + "cycleForm": ": » Forma", + "cycleGender": ": » Gênero", + "cycleAbility": ": » Habilidade", + "cycleNature": ": » Natureza", + "cycleVariant": ": » Variante", "enablePassive": "Ativar Passiva", "disablePassive": "Desativar Passiva", "locked": "Bloqueada", diff --git a/src/locales/zh_CN/challenges.ts b/src/locales/zh_CN/challenges.ts new file mode 100644 index 00000000000..0810b6f1eeb --- /dev/null +++ b/src/locales/zh_CN/challenges.ts @@ -0,0 +1,67 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const challenges: SimpleTranslationEntries = { + "title": "适用挑战条件", + "points": "Bad Ideas", + "confirm_start": "要执行这些挑战吗?", + "singleGeneration.name": "单一世代", + "singleGeneration.value.0": "关闭", + "singleGeneration.desc.0": "你只能使用所选世代的宝可梦", + "singleGeneration.value.1": "第一世代", + "singleGeneration.desc.1": "你只能使用第一世代的宝可梦", + "singleGeneration.value.2": "第二世代", + "singleGeneration.desc.2": "你只能使用第二世代的宝可梦", + "singleGeneration.value.3": "第三世代", + "singleGeneration.desc.3": "你只能使用第三世代的宝可梦", + "singleGeneration.value.4": "第四世代", + "singleGeneration.desc.4": "你只能使用第四世代的宝可梦", + "singleGeneration.value.5": "第五世代", + "singleGeneration.desc.5": "你只能使用第五世代的宝可梦", + "singleGeneration.value.6": "第六世代", + "singleGeneration.desc.6": "你只能使用第六世代的宝可梦", + "singleGeneration.value.7": "第七世代", + "singleGeneration.desc.7": "你只能使用第七世代的宝可梦", + "singleGeneration.value.8": "第八世代", + "singleGeneration.desc.8": "你只能使用第八世代的宝可梦", + "singleGeneration.value.9": "第久世代", + "singleGeneration.desc.9": "你只能使用第九世代的宝可梦", + "singleType.name": "单属性", + "singleType.value.0": "关闭", + "singleType.desc.0": "你只能使用所选属性的宝可梦", + "singleType.value.1": "普通", + "singleType.desc.1": "你只能使用普通属性的宝可梦", + "singleType.value.2": "格斗", + "singleType.desc.2": "你只能使用格斗属性的宝可梦", + "singleType.value.3": "飞行", + "singleType.desc.3": "你只能使用飞行属性的宝可梦", + "singleType.value.4": "毒", + "singleType.desc.4": "你只能使用毒属性的宝可梦", + "singleType.value.5": "地面", + "singleType.desc.5": "你只能使用地面属性的宝可梦", + "singleType.value.6": "岩石", + "singleType.desc.6": "你只能使用所选属性的宝可梦", + "singleType.value.7": "虫", + "singleType.desc.7": "你只能使用虫属性的宝可梦", + "singleType.value.8": "幽灵", + "singleType.desc.8": "你只能使用幽灵属性的宝可梦", + "singleType.value.9": "钢", + "singleType.desc.9": "你只能使用钢属性的宝可梦", + "singleType.value.10": "火", + "singleType.desc.10": "你只能使用火属性的宝可梦", + "singleType.value.11": "水", + "singleType.desc.11": "你只能使用水属性的宝可梦", + "singleType.value.12": "草", + "singleType.desc.12": "你只能使用草属性的宝可梦", + "singleType.value.13": "电", + "singleType.desc.13": "你只能使用电属性的宝可梦", + "singleType.value.14": "超能", + "singleType.desc.14": "你只能使用超能属性的宝可梦", + "singleType.value.15": "冰", + "singleType.desc.15": "你只能使用冰属性的宝可梦", + "singleType.value.16": "龙", + "singleType.desc.16": "你只能使用龙属性的宝可梦", + "singleType.value.17": "恶", + "singleType.desc.17": "你只能使用恶属性的宝可梦", + "singleType.value.18": "妖精", + "singleType.desc.18": "你只能使用妖精属性的宝可梦", +} as const; diff --git a/src/locales/zh_CN/config.ts b/src/locales/zh_CN/config.ts index 0560b829dea..6b2132b6406 100644 --- a/src/locales/zh_CN/config.ts +++ b/src/locales/zh_CN/config.ts @@ -5,6 +5,7 @@ import { battle } from "./battle"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { berry } from "./berry"; import { biome } from "./biome"; +import { challenges } from "./challenges"; import { commandUiHandler } from "./command-ui-handler"; import { PGFbattleSpecDialogue, @@ -18,6 +19,7 @@ import { } from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameMode } from "./game-mode"; import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; @@ -29,6 +31,7 @@ import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; +import { saveSlotSelectUiHandler } from "./save-slot-select-ui-handler"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; @@ -45,6 +48,7 @@ export const zhCnConfig = { battleMessageUiHandler: battleMessageUiHandler, berry: berry, biome: biome, + challenges: challenges, commandUiHandler: commandUiHandler, PGMdialogue: PGMdialogue, PGFdialogue: PGFdialogue, @@ -56,6 +60,7 @@ export const zhCnConfig = { PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameMode: gameMode, gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, @@ -67,6 +72,7 @@ export const zhCnConfig = { pokemon: pokemon, pokemonInfo: pokemonInfo, pokemonInfoContainer: pokemonInfoContainer, + saveSlotSelectUiHandler: saveSlotSelectUiHandler, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, diff --git a/src/locales/zh_CN/game-mode.ts b/src/locales/zh_CN/game-mode.ts new file mode 100644 index 00000000000..be342b4c390 --- /dev/null +++ b/src/locales/zh_CN/game-mode.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const gameMode: SimpleTranslationEntries = { + "classic": "Classic", + "endless": "Endless", + "endlessSpliced": "Endless (Spliced)", + "dailyRun": "Daily Run", + "unknown": "Unknown", + "challenge": "Challenge", +} as const; diff --git a/src/locales/zh_CN/menu.ts b/src/locales/zh_CN/menu.ts index d8cad6b05af..39d8e5e3a04 100644 --- a/src/locales/zh_CN/menu.ts +++ b/src/locales/zh_CN/menu.ts @@ -45,8 +45,8 @@ export const menu: SimpleTranslationEntries = { "weeklyRankings": "每周排名", "noRankings": "无排名", "loading": "加载中...", + "loadingAsset": "Loading asset: {{assetName}}", "playersOnline": "在线玩家", - "empty": "空", "yes": "是", "no": "否", "disclaimer": "DISCLAIMER", diff --git a/src/locales/zh_CN/save-slot-select-ui-handler.ts b/src/locales/zh_CN/save-slot-select-ui-handler.ts new file mode 100644 index 00000000000..98b7764aee2 --- /dev/null +++ b/src/locales/zh_CN/save-slot-select-ui-handler.ts @@ -0,0 +1,9 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const saveSlotSelectUiHandler: SimpleTranslationEntries = { + "overwriteData": "Overwrite the data in the selected slot?", + "loading": "Loading...", + "wave": "Wave", + "lv": "Lv", + "empty": "空", +} as const; diff --git a/src/locales/zh_CN/starter-select-ui-handler.ts b/src/locales/zh_CN/starter-select-ui-handler.ts index 9491438bb13..a05c7348ab5 100644 --- a/src/locales/zh_CN/starter-select-ui-handler.ts +++ b/src/locales/zh_CN/starter-select-ui-handler.ts @@ -30,12 +30,12 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "selectMoveSwapWith": "选择要替换成的招式", "unlockPassive": "解锁被动", "reduceCost": "降低花费", - "cycleShiny": "R: 切换闪光", - "cycleForm": "F: 切换形态", - "cycleGender": "G: 切换性别", - "cycleAbility": "E: 切换特性", - "cycleNature": "N: 切换性格", - "cycleVariant": "V: 切换变种", + "cycleShiny": ": 切换闪光", + "cycleForm": ": 切换形态", + "cycleGender": ": 切换性别", + "cycleAbility": ": 切换特性", + "cycleNature": ": 切换性格", + "cycleVariant": ": 切换变种", "enablePassive": "启用被动", "disablePassive": "禁用被动", "locked": "未解锁", diff --git a/src/locales/zh_TW/challenges.ts b/src/locales/zh_TW/challenges.ts new file mode 100644 index 00000000000..149037be740 --- /dev/null +++ b/src/locales/zh_TW/challenges.ts @@ -0,0 +1,67 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const challenges: SimpleTranslationEntries = { + "title": "Challenge Modifiers", + "points": "Bad Ideas", + "confirm_start": "Proceed with these challenges?", + "singleGeneration.name": "Mono Gen", + "singleGeneration.value.0": "Off", + "singleGeneration.desc.0": "You can only use pokemon from the chosen generation.", + "singleGeneration.value.1": "Gen 1", + "singleGeneration.desc.1": "You can only use pokemon from generation one.", + "singleGeneration.value.2": "Gen 2", + "singleGeneration.desc.2": "You can only use pokemon from generation two.", + "singleGeneration.value.3": "Gen 3", + "singleGeneration.desc.3": "You can only use pokemon from generation three.", + "singleGeneration.value.4": "Gen 4", + "singleGeneration.desc.4": "You can only use pokemon from generation four.", + "singleGeneration.value.5": "Gen 5", + "singleGeneration.desc.5": "You can only use pokemon from generation five.", + "singleGeneration.value.6": "Gen 6", + "singleGeneration.desc.6": "You can only use pokemon from generation six.", + "singleGeneration.value.7": "Gen 7", + "singleGeneration.desc.7": "You can only use pokemon from generation seven.", + "singleGeneration.value.8": "Gen 8", + "singleGeneration.desc.8": "You can only use pokemon from generation eight.", + "singleGeneration.value.9": "Gen 9", + "singleGeneration.desc.9": "You can only use pokemon from generation nine.", + "singleType.name": "Mono Type", + "singleType.value.0": "Off", + "singleType.desc.0": "You can only use pokemon of the chosen type.", + "singleType.value.1": "Normal", + "singleType.desc.1": "You can only use pokemon with the Normal type.", + "singleType.value.2": "Fighting", + "singleType.desc.2": "You can only use pokemon with the Fighting type.", + "singleType.value.3": "Flying", + "singleType.desc.3": "You can only use pokemon with the Flying type.", + "singleType.value.4": "Poison", + "singleType.desc.4": "You can only use pokemon with the Poison type.", + "singleType.value.5": "Ground", + "singleType.desc.5": "You can only use pokemon with the Ground type.", + "singleType.value.6": "Rock", + "singleType.desc.6": "You can only use pokemon with the Rock type.", + "singleType.value.7": "Bug", + "singleType.desc.7": "You can only use pokemon with the Bug type.", + "singleType.value.8": "Ghost", + "singleType.desc.8": "You can only use pokemon with the Ghost type.", + "singleType.value.9": "Steel", + "singleType.desc.9": "You can only use pokemon with the Steel type.", + "singleType.value.10": "Fire", + "singleType.desc.10": "You can only use pokemon with the Fire type.", + "singleType.value.11": "Water", + "singleType.desc.11": "You can only use pokemon with the Water type.", + "singleType.value.12": "Grass", + "singleType.desc.12": "You can only use pokemon with the Grass type.", + "singleType.value.13": "Electric", + "singleType.desc.13": "You can only use pokemon with the Electric type.", + "singleType.value.14": "Psychic", + "singleType.desc.14": "You can only use pokemon with the Psychic type.", + "singleType.value.15": "Ice", + "singleType.desc.15": "You can only use pokemon with the Ice type.", + "singleType.value.16": "Dragon", + "singleType.desc.16": "You can only use pokemon with the Dragon type.", + "singleType.value.17": "Dark", + "singleType.desc.17": "You can only use pokemon with the Dark type.", + "singleType.value.18": "Fairy", + "singleType.desc.18": "You can only use pokemon with the Fairy type.", +} as const; diff --git a/src/locales/zh_TW/config.ts b/src/locales/zh_TW/config.ts index c7045da3d97..098b1507eb4 100644 --- a/src/locales/zh_TW/config.ts +++ b/src/locales/zh_TW/config.ts @@ -5,6 +5,7 @@ import { battle } from "./battle"; import { battleMessageUiHandler } from "./battle-message-ui-handler"; import { berry } from "./berry"; import { biome } from "./biome"; +import { challenges } from "./challenges"; import { commandUiHandler } from "./command-ui-handler"; import { PGFbattleSpecDialogue, @@ -18,6 +19,7 @@ import { } from "./dialogue"; import { egg } from "./egg"; import { fightUiHandler } from "./fight-ui-handler"; +import { gameMode } from "./game-mode"; import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; @@ -29,6 +31,7 @@ import { pokeball } from "./pokeball"; import { pokemon } from "./pokemon"; import { pokemonInfo } from "./pokemon-info"; import { pokemonInfoContainer } from "./pokemon-info-container"; +import { saveSlotSelectUiHandler } from "./save-slot-select-ui-handler"; import { splashMessages } from "./splash-messages"; import { starterSelectUiHandler } from "./starter-select-ui-handler"; import { titles, trainerClasses, trainerNames } from "./trainers"; @@ -45,6 +48,7 @@ export const zhTwConfig = { battleMessageUiHandler: battleMessageUiHandler, berry: berry, biome: biome, + challenges: challenges, commandUiHandler: commandUiHandler, PGMdialogue: PGMdialogue, PGFdialogue: PGFdialogue, @@ -56,6 +60,7 @@ export const zhTwConfig = { PGFdoubleBattleDialogue: PGFdoubleBattleDialogue, egg: egg, fightUiHandler: fightUiHandler, + gameMode: gameMode, gameStatsUiHandler: gameStatsUiHandler, growth: growth, menu: menu, @@ -67,6 +72,7 @@ export const zhTwConfig = { pokemon: pokemon, pokemonInfo: pokemonInfo, pokemonInfoContainer: pokemonInfoContainer, + saveSlotSelectUiHandler: saveSlotSelectUiHandler, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, titles: titles, diff --git a/src/locales/zh_TW/game-mode.ts b/src/locales/zh_TW/game-mode.ts new file mode 100644 index 00000000000..be342b4c390 --- /dev/null +++ b/src/locales/zh_TW/game-mode.ts @@ -0,0 +1,10 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const gameMode: SimpleTranslationEntries = { + "classic": "Classic", + "endless": "Endless", + "endlessSpliced": "Endless (Spliced)", + "dailyRun": "Daily Run", + "unknown": "Unknown", + "challenge": "Challenge", +} as const; diff --git a/src/locales/zh_TW/menu.ts b/src/locales/zh_TW/menu.ts index 680db51e8ac..d16052f2ac7 100644 --- a/src/locales/zh_TW/menu.ts +++ b/src/locales/zh_TW/menu.ts @@ -45,8 +45,8 @@ export const menu: SimpleTranslationEntries = { "weeklyRankings": "每週排名", "noRankings": "無排名", "loading": "加載中…", + "loadingAsset": "Loading asset: {{assetName}}", "playersOnline": "在線玩家", - "empty":"空", "yes":"是", "no":"否", "disclaimer": "DISCLAIMER", diff --git a/src/locales/zh_TW/save-slot-select-ui-handler.ts b/src/locales/zh_TW/save-slot-select-ui-handler.ts new file mode 100644 index 00000000000..98b7764aee2 --- /dev/null +++ b/src/locales/zh_TW/save-slot-select-ui-handler.ts @@ -0,0 +1,9 @@ +import { SimpleTranslationEntries } from "#app/plugins/i18n"; + +export const saveSlotSelectUiHandler: SimpleTranslationEntries = { + "overwriteData": "Overwrite the data in the selected slot?", + "loading": "Loading...", + "wave": "Wave", + "lv": "Lv", + "empty": "空", +} as const; diff --git a/src/locales/zh_TW/starter-select-ui-handler.ts b/src/locales/zh_TW/starter-select-ui-handler.ts index f7139a54189..89642588ee4 100644 --- a/src/locales/zh_TW/starter-select-ui-handler.ts +++ b/src/locales/zh_TW/starter-select-ui-handler.ts @@ -30,12 +30,12 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "selectMoveSwapWith": "選擇想要替換成的招式", "unlockPassive": "解鎖被動", "reduceCost": "降低花費", - "cycleShiny": "R: 切換閃光", - "cycleForm": "F: 切換形態", - "cycleGender": "G: 切換性別", - "cycleAbility": "E: 切換特性", - "cycleNature": "N: 切換性格", - "cycleVariant": "V: 切換變種", + "cycleShiny": ": 切換閃光", + "cycleForm": ": 切換形態", + "cycleGender": ": 切換性別", + "cycleAbility": ": 切換特性", + "cycleNature": ": 切換性格", + "cycleVariant": ": 切換變種", "enablePassive": "啟用被動", "disablePassive": "禁用被動", "locked": "未解鎖", diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 46019281d4b..6901290a306 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -25,6 +25,7 @@ import i18next from "#app/plugins/i18n"; import { getModifierTierTextTint } from "#app/ui/text"; import { BattlerTagType } from "#app/data/enums/battler-tag-type.js"; import * as Overrides from "../overrides"; +import { MoneyMultiplierModifier } from "./modifier"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -631,9 +632,13 @@ export class MoneyRewardModifierType extends ModifierType { } getDescription(scene: BattleScene): string { + const moneyAmount = new Utils.IntegerHolder(scene.getWaveMoneyAmount(this.moneyMultiplier)); + scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + const formattedMoney = Utils.formatMoney(scene.moneyFormat, moneyAmount.value); + return i18next.t("modifierType:ModifierType.MoneyRewardModifierType.description", { moneyMultiplier: i18next.t(this.moneyMultiplierDescriptorKey as any), - moneyAmount: scene.getWaveMoneyAmount(this.moneyMultiplier).toLocaleString("en-US"), + moneyAmount: formattedMoney, }); } } diff --git a/src/phases.ts b/src/phases.ts index a235349cd6f..f83f90a626b 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -36,7 +36,7 @@ import { getBiomeKey } from "./field/arena"; import { BattleType, BattlerIndex, TurnCommand } from "./battle"; import { BattleSpec } from "./enums/battle-spec"; import { Species } from "./data/enums/species"; -import { HealAchv, LevelAchv, achvs } from "./system/achv"; +import { ChallengeAchv, HealAchv, LevelAchv, achvs } from "./system/achv"; import { TrainerSlot, trainerConfigs } from "./data/trainer-config"; import { TrainerType } from "./data/enums/trainer-type"; import { EggHatchPhase } from "./egg-hatch-phase"; @@ -55,7 +55,7 @@ import { TerrainType } from "./data/terrain"; import { OptionSelectConfig, OptionSelectItem } from "./ui/abstact-option-select-ui-handler"; import { SaveSlotUiMode } from "./ui/save-slot-select-ui-handler"; import { fetchDailyRunSeed, getDailyRunStarters } from "./data/daily-run"; -import { GameModes, gameModes } from "./game-mode"; +import { GameMode, GameModes, getGameMode } from "./game-mode"; import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./data/pokemon-species"; import i18next from "./plugins/i18n"; import { Abilities } from "./data/enums/abilities"; @@ -202,14 +202,21 @@ export class TitlePhase extends Phase { if (this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { const options: OptionSelectItem[] = [ { - label: gameModes[GameModes.CLASSIC].getName(), + label: GameMode.getModeName(GameModes.CLASSIC), handler: () => { setModeAndEnd(GameModes.CLASSIC); return true; } }, { - label: gameModes[GameModes.ENDLESS].getName(), + label: GameMode.getModeName(GameModes.CHALLENGE), + handler: () => { + setModeAndEnd(GameModes.CHALLENGE); + return true; + } + }, + { + label: GameMode.getModeName(GameModes.ENDLESS), handler: () => { setModeAndEnd(GameModes.ENDLESS); return true; @@ -218,7 +225,7 @@ export class TitlePhase extends Phase { ]; if (this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { options.push({ - label: gameModes[GameModes.SPLICED_ENDLESS].getName(), + label: GameMode.getModeName(GameModes.SPLICED_ENDLESS), handler: () => { setModeAndEnd(GameModes.SPLICED_ENDLESS); return true; @@ -307,7 +314,7 @@ export class TitlePhase extends Phase { this.scene.sessionSlotId = slotId; const generateDaily = (seed: string) => { - this.scene.gameMode = gameModes[GameModes.DAILY]; + this.scene.gameMode = getGameMode(GameModes.DAILY); this.scene.setSeed(seed); this.scene.resetSeed(1); @@ -369,7 +376,12 @@ export class TitlePhase extends Phase { end(): void { if (!this.loaded && !this.scene.gameMode.isDaily) { this.scene.arena.preloadBgm(); - this.scene.pushPhase(new SelectStarterPhase(this.scene, this.gameMode)); + this.scene.gameMode = getGameMode(this.gameMode); + if (this.gameMode === GameModes.CHALLENGE) { + this.scene.pushPhase(new SelectChallengePhase(this.scene)); + } else { + this.scene.pushPhase(new SelectStarterPhase(this.scene)); + } this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene)); } else { this.scene.playBgm(); @@ -378,7 +390,7 @@ export class TitlePhase extends Phase { this.scene.pushPhase(new EncounterPhase(this.scene, this.loaded)); if (this.loaded) { - const availablePartyMembers = this.scene.getParty().filter(p => !p.isFainted()).length; + const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()).length; this.scene.pushPhase(new SummonPhase(this.scene, 0, true, true)); if (this.scene.currentBattle.double && availablePartyMembers > 1) { @@ -504,13 +516,24 @@ export class SelectGenderPhase extends Phase { } } -export class SelectStarterPhase extends Phase { - private gameMode: GameModes; - - constructor(scene: BattleScene, gameMode: GameModes) { +export class SelectChallengePhase extends Phase { + constructor(scene: BattleScene) { super(scene); + } - this.gameMode = gameMode; + start() { + super.start(); + + this.scene.playBgm("menu"); + + this.scene.ui.setMode(Mode.CHALLENGE_SELECT); + } +} + +export class SelectStarterPhase extends Phase { + + constructor(scene: BattleScene) { + super(scene); } start() { @@ -529,7 +552,7 @@ export class SelectStarterPhase extends Phase { this.scene.sessionSlotId = slotId; this.initBattle(starters); }); - }, this.gameMode); + }); } initBattle(starters: Starter[]) { @@ -1002,7 +1025,7 @@ export class EncounterPhase extends BattlePhase { } if (!this.loaded) { - const availablePartyMembers = this.scene.getParty().filter(p => !p.isFainted()); + const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()); if (!availablePartyMembers[0].isOnField()) { this.scene.pushPhase(new SummonPhase(this.scene, 0)); @@ -1295,20 +1318,29 @@ export class SummonPhase extends PartyMemberPokemonPhase { */ preSummon(): void { const partyMember = this.getPokemon(); - // If the Pokemon about to be sent out is fainted, switch to the first non-fainted Pokemon - if (partyMember.isFainted()) { - console.warn("The Pokemon about to be sent out is fainted. Attempting to resolve..."); + // If the Pokemon about to be sent out is fainted or illegal under a challenge, switch to the first non-fainted legal Pokemon + if (!partyMember.isAllowedInBattle()) { + console.warn("The Pokemon about to be sent out is fainted or illegal under a challenge. Attempting to resolve..."); + + // First check if they're somehow still in play, if so remove them. + if (partyMember.isOnField()) { + partyMember.hideInfo(); + partyMember.setVisible(false); + this.scene.field.remove(partyMember); + this.scene.triggerPokemonFormChange(partyMember, SpeciesFormChangeActiveTrigger, true); + } + const party = this.getParty(); // Find the first non-fainted Pokemon index above the current one - const nonFaintedIndex = party.findIndex((p, i) => i > this.partyMemberIndex && !p.isFainted()); - if (nonFaintedIndex === -1) { + const legalIndex = party.findIndex((p, i) => i > this.partyMemberIndex && p.isAllowedInBattle()); + if (legalIndex === -1) { console.error("Party Details:\n", party); - throw new Error("All available Pokemon were fainted!"); + throw new Error("All available Pokemon were fainted or illegal!"); } - // Swaps the fainted Pokemon and the first non-fainted Pokemon in the party - [party[this.partyMemberIndex], party[nonFaintedIndex]] = [party[nonFaintedIndex], party[this.partyMemberIndex]]; + // Swaps the fainted Pokemon and the first non-fainted legal Pokemon in the party + [party[this.partyMemberIndex], party[legalIndex]] = [party[legalIndex], party[this.partyMemberIndex]]; console.warn("Swapped %s %O with %s %O", partyMember?.name, partyMember, party[0]?.name, party[0]); } @@ -1352,7 +1384,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { if (this.fieldIndex === 1) { pokemon.setFieldPosition(FieldPosition.RIGHT, 0); } else { - const availablePartyMembers = this.getParty().filter(p => !p.isFainted()).length; + const availablePartyMembers = this.getParty().filter(p => p.isAllowedInBattle()).length; pokemon.setFieldPosition(!this.scene.currentBattle.double || availablePartyMembers === 1 ? FieldPosition.CENTER : FieldPosition.LEFT); } @@ -1642,7 +1674,7 @@ export class ToggleDoublePositionPhase extends BattlePhase { const playerPokemon = this.scene.getPlayerField().find(p => p.isActive(true)); if (playerPokemon) { - playerPokemon.setFieldPosition(this.double && this.scene.getParty().filter(p => !p.isFainted()).length > 1 ? FieldPosition.LEFT : FieldPosition.CENTER, 500).then(() => { + playerPokemon.setFieldPosition(this.double && this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? FieldPosition.LEFT : FieldPosition.CENTER, 500).then(() => { if (playerPokemon.getFieldIndex() === 1) { const party = this.scene.getParty(); party[1] = party[0]; @@ -1742,6 +1774,34 @@ export class TurnInitPhase extends FieldPhase { start() { super.start(); + this.scene.getPlayerField().forEach(p => { + // If this pokemon is in play and evolved into something illegal under the current challenge, force a switch + if (p.isOnField() && !p.isAllowedInBattle()) { + this.scene.queueMessage(i18next.t("challenges:illegalEvolution", {"pokemon": p.name}), null, true); + + const allowedPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle()); + + if (!allowedPokemon.length) { + // If there are no longer any legal pokemon in the party, game over. + this.scene.clearPhaseQueue(); + this.scene.unshiftPhase(new GameOverPhase(this.scene)); + } else if (allowedPokemon.length >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !allowedPokemon[0].isActive(true))) { + // If there is at least one pokemon in the back that is legal to switch in, force a switch. + p.switchOut(false, true); + } else { + // If there are no pokemon in the back but we're not game overing, just hide the pokemon. + // This should only happen in double battles. + p.hideInfo(); + p.setVisible(false); + this.scene.field.remove(p); + this.scene.triggerPokemonFormChange(p, SpeciesFormChangeActiveTrigger, true); + } + if (allowedPokemon.length === 1 && this.scene.currentBattle.double) { + this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); + } + } + }); + //this.scene.pushPhase(new MoveAnimTestPhase(this.scene)); this.scene.eventTarget.dispatchEvent(new TurnInitEvent()); @@ -1776,9 +1836,15 @@ export class CommandPhase extends FieldPhase { super.start(); if (this.fieldIndex) { - const allyCommand = this.scene.currentBattle.turnCommands[this.fieldIndex - 1]; - if (allyCommand.command === Command.BALL || allyCommand.command === Command.RUN) { - this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: allyCommand.command, skip: true }; + // If we somehow are attempting to check the right pokemon but there's only one pokemon out + // Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching + if (this.scene.getPlayerField().filter(p => p.isActive()).length === 1) { + this.fieldIndex = FieldPosition.CENTER; + } else { + const allyCommand = this.scene.currentBattle.turnCommands[this.fieldIndex - 1]; + if (allyCommand.command === Command.BALL || allyCommand.command === Command.RUN) { + this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: allyCommand.command, skip: true }; + } } } @@ -2353,7 +2419,7 @@ export class BattleEndPhase extends BattlePhase { } } - for (const pokemon of this.scene.getParty().filter(p => !p.isFainted())) { + for (const pokemon of this.scene.getParty().filter(p => p.isAllowedInBattle())) { applyPostBattleAbAttrs(PostBattleAbAttr, pokemon); } @@ -3486,7 +3552,7 @@ export class DamagePhase extends PokemonPhase { this.scene.setFieldScale(0.75); this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); this.scene.currentBattle.double = true; - const availablePartyMembers = this.scene.getParty().filter(p => !p.isFainted()); + const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()); if (availablePartyMembers.length > 1) { this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, true)); if (!availablePartyMembers[1].isOnField()) { @@ -3568,11 +3634,11 @@ export class FaintPhase extends PokemonPhase { } if (this.player) { - const nonFaintedPartyMembers = this.scene.getParty().filter(p => !p.isFainted()); - const nonFaintedPartyMemberCount = nonFaintedPartyMembers.length; + const nonFaintedLegalPartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()); + const nonFaintedPartyMemberCount = nonFaintedLegalPartyMembers.length; if (!nonFaintedPartyMemberCount) { this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !nonFaintedPartyMembers[0].isActive(true))) { + } else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !nonFaintedLegalPartyMembers[0].isActive(true))) { this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); } if (nonFaintedPartyMemberCount === 1 && this.scene.currentBattle.double) { @@ -3966,7 +4032,7 @@ export class GameOverPhase extends BattlePhase { this.scene.gameData.loadSession(this.scene, this.scene.sessionSlotId).then(() => { this.scene.pushPhase(new EncounterPhase(this.scene, true)); - const availablePartyMembers = this.scene.getParty().filter(p => !p.isFainted()).length; + const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()).length; this.scene.pushPhase(new SummonPhase(this.scene, 0)); if (this.scene.currentBattle.double && availablePartyMembers > 1) { @@ -4018,6 +4084,10 @@ export class GameOverPhase extends BattlePhase { this.scene.clearPhaseQueue(); this.scene.ui.clearText(); + if (this.victory && this.scene.gameMode.isChallenge) { + this.scene.gameMode.challenges.forEach(c => this.scene.validateAchvs(ChallengeAchv, c)); + } + const clear = (endCardPhase?: EndCardPhase) => { if (newClear) { this.handleUnlocks(); @@ -4221,17 +4291,17 @@ export class SwitchPhase extends BattlePhase { super.start(); // Skip modal switch if impossible - if (this.isModal && !this.scene.getParty().filter(p => !p.isFainted() && !p.isActive(true)).length) { + if (this.isModal && !this.scene.getParty().filter(p => !p.isAllowedInBattle() && !p.isActive(true)).length) { return super.end(); } // Check if there is any space still in field - if (this.isModal && this.scene.getPlayerField().filter(p => !p.isFainted() && p.isActive(true)).length >= this.scene.currentBattle.getBattlerCount()) { + if (this.isModal && this.scene.getPlayerField().filter(p => !p.isAllowedInBattle() && p.isActive(true)).length >= this.scene.currentBattle.getBattlerCount()) { return super.end(); } - // Override field index to 0 in case of double battle where 2/3 remaining party members fainted at once - const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || this.scene.getParty().filter(p => !p.isFainted()).length > 1 ? this.fieldIndex : 0; + // Override field index to 0 in case of double battle where 2/3 remaining legal party members fainted at once + const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? this.fieldIndex : 0; this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) { @@ -4917,7 +4987,8 @@ export class SelectModifierPhase extends BattlePhase { let cost: integer; switch (rowCursor) { case 0: - if (!cursor) { + switch (cursor) { + case 0: const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers); if (this.scene.money < rerollCost) { this.scene.ui.playError(); @@ -4932,18 +5003,25 @@ export class SelectModifierPhase extends BattlePhase { this.scene.animateMoneyChanged(false); this.scene.playSound("buy"); } - } else if (cursor === 1) { + break; + case 1: this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier - && (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; + && (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; const itemModifier = itemModifiers[itemIndex]; this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); } else { this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); } }, PartyUiHandler.FilterItemMaxStacks); - } else { + break; + case 2: + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + }); + break; + case 3: this.scene.lockModifierTiers = !this.scene.lockModifierTiers; const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; uiHandler.setRerollCost(this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 5356c94fe7f..f7b3f6bbae8 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -196,35 +196,15 @@ declare module "i18next" { interface CustomTypeOptions { defaultNS: "menu"; // Even if we don't use it, i18next requires a valid default namespace resources: { - menu: SimpleTranslationEntries; - menuUiHandler: SimpleTranslationEntries; - move: MoveTranslationEntries; - battle: SimpleTranslationEntries; - abilityTriggers: SimpleTranslationEntries; ability: AbilityTranslationEntries; - pokeball: SimpleTranslationEntries; - pokemon: SimpleTranslationEntries; - pokemonInfo: PokemonInfoTranslationEntries; - commandUiHandler: SimpleTranslationEntries; - fightUiHandler: SimpleTranslationEntries; - titles: SimpleTranslationEntries; - trainerClasses: SimpleTranslationEntries; - trainerNames: SimpleTranslationEntries; - tutorial: SimpleTranslationEntries; - starterSelectUiHandler: SimpleTranslationEntries; - splashMessages: SimpleTranslationEntries; - nature: SimpleTranslationEntries; - growth: SimpleTranslationEntries; - egg: SimpleTranslationEntries; - weather: SimpleTranslationEntries; - modifierType: ModifierTypeTranslationEntries; + abilityTriggers: SimpleTranslationEntries; + achv: AchievementTranslationEntries; + battle: SimpleTranslationEntries; battleMessageUiHandler: SimpleTranslationEntries; berry: BerryTranslationEntries; - achv: AchievementTranslationEntries; - gameStatsUiHandler: SimpleTranslationEntries; - voucher: SimpleTranslationEntries; biome: SimpleTranslationEntries; - pokemonInfoContainer: SimpleTranslationEntries; + challenges: SimpleTranslationEntries; + commandUiHandler: SimpleTranslationEntries; PGMdialogue: DialogueTranslationEntries; PGMbattleSpecDialogue: SimpleTranslationEntries; PGMmiscDialogue: SimpleTranslationEntries; @@ -233,7 +213,30 @@ declare module "i18next" { PGFbattleSpecDialogue: SimpleTranslationEntries; PGFmiscDialogue: SimpleTranslationEntries; PGFdoubleBattleDialogue: DialogueTranslationEntries; + egg: SimpleTranslationEntries; + fightUiHandler: SimpleTranslationEntries; + gameMode: SimpleTranslationEntries; + gameStatsUiHandler: SimpleTranslationEntries; + growth: SimpleTranslationEntries; + menu: SimpleTranslationEntries; + menuUiHandler: SimpleTranslationEntries; + modifierType: ModifierTypeTranslationEntries; + move: MoveTranslationEntries; + nature: SimpleTranslationEntries; partyUiHandler: SimpleTranslationEntries; + pokeball: SimpleTranslationEntries; + pokemon: SimpleTranslationEntries; + pokemonInfo: PokemonInfoTranslationEntries; + pokemonInfoContainer: SimpleTranslationEntries; + saveSlotSelectUiHandler: SimpleTranslationEntries; + splashMessages: SimpleTranslationEntries; + starterSelectUiHandler: SimpleTranslationEntries; + titles: SimpleTranslationEntries; + trainerClasses: SimpleTranslationEntries; + trainerNames: SimpleTranslationEntries; + tutorial: SimpleTranslationEntries; + voucher: SimpleTranslationEntries; + weather: SimpleTranslationEntries; }; } } diff --git a/src/system/achv.ts b/src/system/achv.ts index 364b7e0c579..c63e2101c18 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -3,6 +3,7 @@ import BattleScene from "../battle-scene"; import { TurnHeldItemTransferModifier } from "../modifier/modifier"; import i18next from "../plugins/i18n"; import * as Utils from "../utils"; +import { Challenge, SingleGenerationChallenge, SingleTypeChallenge } from "#app/data/challenge.js"; export enum AchvTier { COMMON, @@ -126,6 +127,12 @@ export class ModifierAchv extends Achv { } } +export class ChallengeAchv extends Achv { + constructor(localizationKey: string, name: string, description: string, iconImage: string, score: integer, challengeFunc: (challenge: Challenge) => boolean) { + super(localizationKey, name, description, iconImage, score, (_scene: BattleScene, args: any[]) => challengeFunc((args[0] as Challenge))); + } +} + /** * Get the description of an achievement from the localization file with all the necessary variables filled in @@ -214,6 +221,43 @@ export function getAchievementDescription(localizationKey: string): string { return i18next.t("achv:PERFECT_IVS.description"); case "CLASSIC_VICTORY": return i18next.t("achv:CLASSIC_VICTORY.description"); + case "MONO_GEN_ONE": + return i18next.t("achv:MONO_GEN_ONE.description"); + case "MONO_GEN_TWO": + return i18next.t("achv:MONO_GEN_TWO.description"); + case "MONO_GEN_THREE": + return i18next.t("achv:MONO_GEN_THREE.description"); + case "MONO_GEN_FOUR": + return i18next.t("achv:MONO_GEN_FOUR.description"); + case "MONO_GEN_FIVE": + return i18next.t("achv:MONO_GEN_FIVE.description"); + case "MONO_GEN_SIX": + return i18next.t("achv:MONO_GEN_SIX.description"); + case "MONO_GEN_SEVEN": + return i18next.t("achv:MONO_GEN_SEVEN.description"); + case "MONO_GEN_EIGHT": + return i18next.t("achv:MONO_GEN_EIGHT.description"); + case "MONO_GEN_NINE": + return i18next.t("achv:MONO_GEN_NINE.description"); + case "MONO_NORMAL": + case "MONO_FIGHTING": + case "MONO_FLYING": + case "MONO_POISON": + case "MONO_GROUND": + case "MONO_ROCK": + case "MONO_BUG": + case "MONO_GHOST": + case "MONO_STEEL": + case "MONO_FIRE": + case "MONO_WATER": + case "MONO_GRASS": + case "MONO_ELECTRIC": + case "MONO_PSYCHIC": + case "MONO_ICE": + case "MONO_DRAGON": + case "MONO_DARK": + case "MONO_FAIRY": + return i18next.t("achv:MonoType.description", {"type": i18next.t(`pokemonInfo:Type.${localizationKey.slice(5)}`)}); default: return ""; } @@ -261,6 +305,33 @@ export const achvs = { HIDDEN_ABILITY: new Achv("HIDDEN_ABILITY","", "HIDDEN_ABILITY.description", "ability_charm", 75), PERFECT_IVS: new Achv("PERFECT_IVS","", "PERFECT_IVS.description", "blunder_policy", 100), CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY","", "CLASSIC_VICTORY.description", "relic_crown", 150), + MONO_GEN_ONE_VICTORY: new ChallengeAchv("MONO_GEN_ONE","", "MONO_GEN_ONE.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 1), + MONO_GEN_TWO_VICTORY: new ChallengeAchv("MONO_GEN_TWO","", "MONO_GEN_TWO.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 2), + MONO_GEN_THREE_VICTORY: new ChallengeAchv("MONO_GEN_THREE","", "MONO_GEN_THREE.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 3), + MONO_GEN_FOUR_VICTORY: new ChallengeAchv("MONO_GEN_FOUR","", "MONO_GEN_FOUR.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 4), + MONO_GEN_FIVE_VICTORY: new ChallengeAchv("MONO_GEN_FIVE","", "MONO_GEN_FIVE.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 5), + MONO_GEN_SIX_VICTORY: new ChallengeAchv("MONO_GEN_SIX","", "MONO_GEN_SIX.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 6), + MONO_GEN_SEVEN_VICTORY: new ChallengeAchv("MONO_GEN_SEVEN","", "MONO_GEN_SEVEN.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 7), + MONO_GEN_EIGHT_VICTORY: new ChallengeAchv("MONO_GEN_EIGHT","", "MONO_GEN_EIGHT.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 8), + MONO_GEN_NINE_VICTORY: new ChallengeAchv("MONO_GEN_NINE","", "MONO_GEN_NINE.description", "mystic_ticket", 100, c => c instanceof SingleGenerationChallenge && c.value === 9), + MONO_NORMAL: new ChallengeAchv("MONO_NORMAL","", "MONO_NORMAL.description", "silk_scarf", 100, c => c instanceof SingleTypeChallenge && c.value === 1), + MONO_FIGHTING: new ChallengeAchv("MONO_FIGHTING","", "MONO_FIGHTING.description", "black_belt", 100, c => c instanceof SingleTypeChallenge && c.value === 2), + MONO_FLYING: new ChallengeAchv("MONO_FLYING","", "MONO_FLYING.description", "sharp_beak", 100, c => c instanceof SingleTypeChallenge && c.value === 3), + MONO_POISON: new ChallengeAchv("MONO_POISON","", "MONO_POISON.description", "poison_barb", 100, c => c instanceof SingleTypeChallenge && c.value === 4), + MONO_GROUND: new ChallengeAchv("MONO_GROUND","", "MONO_GROUND.description", "soft_sand", 100, c => c instanceof SingleTypeChallenge && c.value === 5), + MONO_ROCK: new ChallengeAchv("MONO_ROCK","", "MONO_ROCK.description", "hard_stone", 100, c => c instanceof SingleTypeChallenge && c.value === 6), + MONO_BUG: new ChallengeAchv("MONO_BUG","", "MONO_BUG.description", "silver_powder", 100, c => c instanceof SingleTypeChallenge && c.value === 7), + MONO_GHOST: new ChallengeAchv("MONO_GHOST","", "MONO_GHOST.description", "spell_tag", 100, c => c instanceof SingleTypeChallenge && c.value === 8), + MONO_STEEL: new ChallengeAchv("MONO_STEEL","", "MONO_STEEL.description", "metal_coat", 100, c => c instanceof SingleTypeChallenge && c.value === 9), + MONO_FIRE: new ChallengeAchv("MONO_FIRE","", "MONO_FIRE.description", "charcoal", 100, c => c instanceof SingleTypeChallenge && c.value === 10), + MONO_WATER: new ChallengeAchv("MONO_WATER","", "MONO_WATER.description", "mystic_water", 100, c => c instanceof SingleTypeChallenge && c.value === 11), + MONO_GRASS: new ChallengeAchv("MONO_GRASS","", "MONO_GRASS.description", "miracle_seed", 100, c => c instanceof SingleTypeChallenge && c.value === 12), + MONO_ELECTRIC: new ChallengeAchv("MONO_ELECTRIC","", "MONO_ELECTRIC.description", "magnet", 100, c => c instanceof SingleTypeChallenge && c.value === 13), + MONO_PSYCHIC: new ChallengeAchv("MONO_PSYCHIC","", "MONO_PSYCHIC.description", "twisted_spoon", 100, c => c instanceof SingleTypeChallenge && c.value === 14), + MONO_ICE: new ChallengeAchv("MONO_ICE","", "MONO_ICE.description", "never_melt_ice", 100, c => c instanceof SingleTypeChallenge && c.value === 15), + MONO_DRAGON: new ChallengeAchv("MONO_DRAGON","", "MONO_DRAGON.description", "dragon_fang", 100, c => c instanceof SingleTypeChallenge && c.value === 16), + MONO_DARK: new ChallengeAchv("MONO_DARK","", "MONO_DARK.description", "black_glasses", 100, c => c instanceof SingleTypeChallenge && c.value === 17), + MONO_FAIRY: new ChallengeAchv("MONO_FAIRY","", "MONO_FAIRY.description", "fairy_feather", 100, c => c instanceof SingleTypeChallenge && c.value === 18), }; export function initAchievements() { diff --git a/src/system/challenge-data.ts b/src/system/challenge-data.ts new file mode 100644 index 00000000000..69df11dd395 --- /dev/null +++ b/src/system/challenge-data.ts @@ -0,0 +1,17 @@ +import { Challenge, copyChallenge } from "#app/data/challenge.js"; + +export default class ChallengeData { + public id: integer; + public value: integer; + public severity: integer; + + constructor(source: Challenge | any) { + this.id = source.id; + this.value = source.value; + this.severity = source.severity; + } + + toChallenge(): Challenge { + return copyChallenge(this); + } +} diff --git a/src/system/game-data.ts b/src/system/game-data.ts index c5e2cd6eb91..a6070c6330f 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -9,7 +9,7 @@ import PokemonData from "./pokemon-data"; import PersistentModifierData from "./modifier-data"; import ArenaData from "./arena-data"; import { Unlockables } from "./unlockables"; -import { GameModes, gameModes } from "../game-mode"; +import { GameModes, getGameMode } from "../game-mode"; import { BattleType } from "../battle"; import TrainerData from "./trainer-data"; import { trainerConfigs } from "../data/trainer-config"; @@ -38,6 +38,7 @@ import { EnemyAttackStatusEffectChanceModifier } from "../modifier/modifier"; import { StatusEffect } from "#app/data/status-effect.js"; import { PlayerGender } from "#app/data/enums/player-gender"; import { GameDataType } from "#app/data/enums/game-data-type"; +import ChallengeData from "./challenge-data"; const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary @@ -107,6 +108,7 @@ export interface SessionSaveData { trainer: TrainerData; gameVersion: string; timestamp: integer; + challenges: ChallengeData[]; } interface Unlocks { @@ -780,7 +782,8 @@ export class GameData { battleType: scene.currentBattle.battleType, trainer: scene.currentBattle.battleType === BattleType.TRAINER ? new TrainerData(scene.currentBattle.trainer) : null, gameVersion: scene.game.config.gameVersion, - timestamp: new Date().getTime() + timestamp: new Date().getTime(), + challenges: scene.gameMode.challenges.map(c => new ChallengeData(c)) } as SessionSaveData; } @@ -829,7 +832,10 @@ export class GameData { const initSessionFromData = async sessionData => { console.debug(sessionData); - scene.gameMode = gameModes[sessionData.gameMode || GameModes.CLASSIC]; + scene.gameMode = getGameMode(sessionData.gameMode || GameModes.CLASSIC); + if (sessionData.challenges) { + scene.gameMode.challenges = sessionData.challenges.map(c => c.toChallenge()); + } scene.setSeed(sessionData.seed || scene.game.config.seed[0]); scene.resetSeed(); @@ -1075,6 +1081,17 @@ export class GameData { return new ArenaData(v); } + if (k === "challenges") { + const ret: ChallengeData[] = []; + if (v === null) { + v = []; + } + for (const c of v) { + ret.push(new ChallengeData(c)); + } + return ret; + } + return v; }) as SessionSaveData; } diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index f79393dac5c..eb2b016b61e 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -64,9 +64,11 @@ export const SettingKeys = { Sprite_Set: "SPRITE_SET", Fusion_Palette_Swaps: "FUSION_PALETTE_SWAPS", Player_Gender: "PLAYER_GENDER", + Type_Hints: "TYPE_HINTS", Master_Volume: "MASTER_VOLUME", BGM_Volume: "BGM_VOLUME", - SE_Volume: "SE_VOLUME" + SE_Volume: "SE_VOLUME", + Music_Preference: "MUSIC_PREFERENCE" }; /** @@ -267,6 +269,13 @@ export const Setting: Array = [ default: 0, type: SettingType.DISPLAY }, + { + key: SettingKeys.Type_Hints, + label: "Type hints", + options: OFF_ON, + default: 0, + type: SettingType.DISPLAY + }, { key: SettingKeys.Master_Volume, label: "Master Volume", @@ -287,6 +296,14 @@ export const Setting: Array = [ options: VOLUME_OPTIONS, default: 10, type: SettingType.AUDIO + }, + { + key: SettingKeys.Music_Preference, + label: "Music Preference", + options: ["Consistent", "Mixed"], + default: 0, + type: SettingType.AUDIO, + requireReload: true } ]; @@ -335,6 +352,9 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): scene.seVolume = value ? parseInt(Setting[index].options[value]) * 0.01 : 0; scene.updateSoundVolume(); break; + case SettingKeys.Music_Preference: + scene.musicPreference = value; + break; case SettingKeys.Damage_Numbers: scene.damageNumbersMode = value; break; @@ -435,6 +455,9 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): case SettingKeys.Vibration: scene.enableVibration = Setting[index].options[value] !== "Disabled" && hasTouchscreen(); break; + case SettingKeys.Type_Hints: + scene.typeHints = Setting[index].options[value] === "On"; + break; case SettingKeys.Language: if (value) { if (scene.ui) { diff --git a/src/system/unlockables.ts b/src/system/unlockables.ts index f94e29ff039..d8e7d97edf6 100644 --- a/src/system/unlockables.ts +++ b/src/system/unlockables.ts @@ -1,4 +1,4 @@ -import { GameModes, gameModes } from "../game-mode"; +import { GameMode, GameModes } from "../game-mode"; export enum Unlockables { ENDLESS_MODE, @@ -9,10 +9,10 @@ export enum Unlockables { export function getUnlockableName(unlockable: Unlockables) { switch (unlockable) { case Unlockables.ENDLESS_MODE: - return `${gameModes[GameModes.ENDLESS].getName()} Mode`; + return `${GameMode.getModeName(GameModes.ENDLESS)} Mode`; case Unlockables.MINI_BLACK_HOLE: return "Mini Black Hole"; case Unlockables.SPLICED_ENDLESS_MODE: - return `${gameModes[GameModes.SPLICED_ENDLESS].getName()} Mode`; + return `${GameMode.getModeName(GameModes.SPLICED_ENDLESS)} Mode`; } } diff --git a/src/system/voucher.ts b/src/system/voucher.ts index c11410400b5..ba42b99d294 100644 --- a/src/system/voucher.ts +++ b/src/system/voucher.ts @@ -83,42 +83,40 @@ export const vouchers: Vouchers = {}; const voucherAchvs: Achv[] = [ achvs.CLASSIC_VICTORY ]; -{ - (function() { - import("../data/trainer-config").then(tc => { - const trainerConfigs = tc.trainerConfigs; +export function initVouchers() { + import("../data/trainer-config").then(tc => { + const trainerConfigs = tc.trainerConfigs; - for (const achv of voucherAchvs) { - const voucherType = achv.score >= 150 - ? VoucherType.GOLDEN - : achv.score >= 100 - ? VoucherType.PREMIUM - : achv.score >= 75 - ? VoucherType.PLUS - : VoucherType.REGULAR; - vouchers[achv.id] = new Voucher(voucherType, getAchievementDescription(achv.localizationKey)); - } + for (const achv of voucherAchvs) { + const voucherType = achv.score >= 150 + ? VoucherType.GOLDEN + : achv.score >= 100 + ? VoucherType.PREMIUM + : achv.score >= 75 + ? VoucherType.PLUS + : VoucherType.REGULAR; + vouchers[achv.id] = new Voucher(voucherType, getAchievementDescription(achv.localizationKey)); + } - const bossTrainerTypes = Object.keys(trainerConfigs) - .filter(tt => trainerConfigs[tt].isBoss && trainerConfigs[tt].getDerivedType() !== TrainerType.RIVAL); + const bossTrainerTypes = Object.keys(trainerConfigs) + .filter(tt => trainerConfigs[tt].isBoss && trainerConfigs[tt].getDerivedType() !== TrainerType.RIVAL); - for (const trainerType of bossTrainerTypes) { - const voucherType = trainerConfigs[trainerType].moneyMultiplier < 10 - ? VoucherType.PLUS - : VoucherType.PREMIUM; - const key = TrainerType[trainerType]; - const trainerName = trainerConfigs[trainerType].name; - const trainer = trainerConfigs[trainerType]; - const title = trainer.title ? ` (${trainer.title})` : ""; - vouchers[key] = new Voucher( - voucherType, - `${i18next.t("voucher:defeatTrainer", { trainerName })} ${title}`, - ); - } - const voucherKeys = Object.keys(vouchers); - for (const k of voucherKeys) { - vouchers[k].id = k; - } - }); - })(); + for (const trainerType of bossTrainerTypes) { + const voucherType = trainerConfigs[trainerType].moneyMultiplier < 10 + ? VoucherType.PLUS + : VoucherType.PREMIUM; + const key = TrainerType[trainerType]; + const trainerName = trainerConfigs[trainerType].name; + const trainer = trainerConfigs[trainerType]; + const title = trainer.title ? ` (${trainer.title})` : ""; + vouchers[key] = new Voucher( + voucherType, + `${i18next.t("voucher:defeatTrainer", { trainerName })} ${title}`, + ); + } + const voucherKeys = Object.keys(vouchers); + for (const k of voucherKeys) { + vouchers[k].id = k; + } + }); } diff --git a/src/test/abilities/intrepid_sword.test.ts b/src/test/abilities/intrepid_sword.test.ts index f2e8eeb12cb..da2beb9c029 100644 --- a/src/test/abilities/intrepid_sword.test.ts +++ b/src/test/abilities/intrepid_sword.test.ts @@ -44,8 +44,8 @@ describe("Abilities - Intrepid Sword", () => { expect(game.scene.getParty()[0].summonData).not.toBeUndefined(); let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats; expect(battleStatsPokemon[BattleStat.ATK]).toBe(0); - await game.phaseInterceptor.mustRun(ShowAbilityPhase).catch((error) => expect(error).toBe(ShowAbilityPhase)); - await game.phaseInterceptor.mustRun(StatChangePhase).catch((error) => expect(error).toBe(StatChangePhase)); + await game.phaseInterceptor.run(ShowAbilityPhase); + await game.phaseInterceptor.run(StatChangePhase); battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats; expect(battleStatsPokemon[BattleStat.ATK]).toBe(1); }, 20000); @@ -57,8 +57,8 @@ describe("Abilities - Intrepid Sword", () => { let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats; expect(battleStatsOpponent[BattleStat.ATK]).toBe(0); await game.phaseInterceptor.runFrom(PostSummonPhase).to(ToggleDoublePositionPhase); - await game.phaseInterceptor.mustRun(StatChangePhase).catch((error) => expect(error).toBe(StatChangePhase)); - await game.phaseInterceptor.whenAboutToRun(MessagePhase); + await game.phaseInterceptor.run(StatChangePhase); + await game.phaseInterceptor.run(MessagePhase); battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats; expect(battleStatsOpponent[BattleStat.ATK]).toBe(1); }, 20000); diff --git a/src/test/battle/battle-order.test.ts b/src/test/battle/battle-order.test.ts index e481150fafe..7ace7dae224 100644 --- a/src/test/battle/battle-order.test.ts +++ b/src/test/battle/battle-order.test.ts @@ -5,7 +5,7 @@ import * as overrides from "#app/overrides"; import {Abilities} from "#app/data/enums/abilities"; import {Species} from "#app/data/enums/species"; import { - CommandPhase, EnemyCommandPhase, + CommandPhase, EnemyCommandPhase, SelectTargetPhase, TurnStartPhase } from "#app/phases"; import {Mode} from "#app/ui/ui"; @@ -55,7 +55,6 @@ describe("Battle order", () => { (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); }); await game.phaseInterceptor.run(EnemyCommandPhase); - await game.phaseInterceptor.whenAboutToRun(TurnStartPhase); const phase = game.scene.getCurrentPhase() as TurnStartPhase; const order = phase.getOrder(); expect(order[0]).toBe(2); @@ -77,7 +76,6 @@ describe("Battle order", () => { (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); }); await game.phaseInterceptor.run(EnemyCommandPhase); - await game.phaseInterceptor.whenAboutToRun(TurnStartPhase); const phase = game.scene.getCurrentPhase() as TurnStartPhase; const order = phase.getOrder(); expect(order[0]).toBe(0); @@ -118,9 +116,7 @@ describe("Battle order", () => { const handler = game.scene.ui.getHandler() as TargetSelectUiHandler; handler.processInput(Button.ACTION); }); - await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase); - await game.phaseInterceptor.run(EnemyCommandPhase); - await game.phaseInterceptor.whenAboutToRun(TurnStartPhase); + await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false); const phase = game.scene.getCurrentPhase() as TurnStartPhase; const order = phase.getOrder(); expect(order.indexOf(0)).toBeGreaterThan(order.indexOf(2)); @@ -163,9 +159,7 @@ describe("Battle order", () => { const handler = game.scene.ui.getHandler() as TargetSelectUiHandler; handler.processInput(Button.ACTION); }); - await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase); - await game.phaseInterceptor.run(EnemyCommandPhase); - await game.phaseInterceptor.whenAboutToRun(TurnStartPhase); + await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false); const phase = game.scene.getCurrentPhase() as TurnStartPhase; const order = phase.getOrder(); expect(order.indexOf(3)).toBeLessThan(order.indexOf(0)); @@ -207,9 +201,7 @@ describe("Battle order", () => { const handler = game.scene.ui.getHandler() as TargetSelectUiHandler; handler.processInput(Button.ACTION); }); - await game.phaseInterceptor.runFrom(CommandPhase).to(EnemyCommandPhase); - await game.phaseInterceptor.run(EnemyCommandPhase); - await game.phaseInterceptor.whenAboutToRun(TurnStartPhase); + await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false); const phase = game.scene.getCurrentPhase() as TurnStartPhase; const order = phase.getOrder(); expect(order.indexOf(1)).toBeLessThan(order.indexOf(0)); diff --git a/src/test/battle/battle.test.ts b/src/test/battle/battle.test.ts index 521b56a8db2..28f98d936ce 100644 --- a/src/test/battle/battle.test.ts +++ b/src/test/battle/battle.test.ts @@ -1,40 +1,29 @@ import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest"; -import {generateStarter, getMovePosition, waitUntil,} from "#app/test/utils/gameManagerUtils"; +import {generateStarter, getMovePosition,} from "#app/test/utils/gameManagerUtils"; import {Mode} from "#app/ui/ui"; import {GameModes} from "#app/game-mode"; import {Species} from "#app/data/enums/species"; import * as overrides from "../../overrides"; import {Command} from "#app/ui/command-ui-handler"; import { - BattleEndPhase, - BerryPhase, CommandPhase, - DamagePhase, - EggLapsePhase, EncounterPhase, EnemyCommandPhase, - FaintPhase, LoginPhase, - MessagePhase, - MoveEffectPhase, - MoveEndPhase, - MovePhase, - PostSummonPhase, SelectGenderPhase, SelectModifierPhase, SelectStarterPhase, - StatChangePhase, + SummonPhase, TitlePhase, - TurnEndPhase, TurnInitPhase, - TurnStartPhase, - VictoryPhase, } from "#app/phases"; import {Moves} from "#app/data/enums/moves"; import GameManager from "#app/test/utils/gameManager"; import Phaser from "phaser"; import {allSpecies} from "#app/data/pokemon-species"; import {PlayerGender} from "#app/data/enums/player-gender"; +import { getGameMode } from "#app/game-mode.js"; +import {Abilities} from "#app/data/enums/abilities"; describe("Test Battle Phase", () => { let phaserGame: Phaser.Game; @@ -54,22 +43,6 @@ describe("Test Battle Phase", () => { game = new GameManager(phaserGame); }); - it("test phase interceptor with remove", async() => { - await game.phaseInterceptor.run(LoginPhase); - - await game.phaseInterceptor.run(LoginPhase, () => { - return game.phaseInterceptor.log.includes("LoginPhase"); - }); - - game.scene.gameData.gender = PlayerGender.MALE; - await game.phaseInterceptor.remove(SelectGenderPhase, () => game.isCurrentPhase(TitlePhase)); - - await game.phaseInterceptor.run(TitlePhase); - await waitUntil(() => game.scene.ui?.getMode() === Mode.TITLE); - - expect(game.scene.ui?.getMode()).toBe(Mode.TITLE); - }, 100000); - it("test phase interceptor with prompt", async() => { await game.phaseInterceptor.run(LoginPhase); @@ -86,7 +59,7 @@ describe("Test Battle Phase", () => { expect(game.scene.ui?.getMode()).toBe(Mode.TITLE); expect(game.scene.gameData.gender).toBe(PlayerGender.MALE); - }, 100000); + }, 20000); it("test phase interceptor with prompt with preparation for a future prompt", async() => { await game.phaseInterceptor.run(LoginPhase); @@ -108,13 +81,13 @@ describe("Test Battle Phase", () => { expect(game.scene.ui?.getMode()).toBe(Mode.TITLE); expect(game.scene.gameData.gender).toBe(PlayerGender.MALE); - }, 100000); + }, 20000); it("newGame one-liner", async() => { await game.startBattle(); expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); - }, 100000); + }, 20000); it("do attack wave 3 - single battle - regular - OHKO", async() => { vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO); @@ -122,6 +95,8 @@ describe("Test Battle Phase", () => { vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000); vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3); vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION); + vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]); vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); await game.startBattle(); game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { @@ -131,28 +106,10 @@ describe("Test Battle Phase", () => { const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); }); - await game.phaseInterceptor.run(EnemyCommandPhase); - await game.phaseInterceptor.run(TurnStartPhase); - - await game.phaseInterceptor.run(MovePhase); - await game.phaseInterceptor.run(MessagePhase); - await game.phaseInterceptor.run(MoveEffectPhase); - await game.phaseInterceptor.run(DamagePhase); - await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(FaintPhase)); - await game.phaseInterceptor.run(FaintPhase); - await game.phaseInterceptor.run(MessagePhase); - - await game.phaseInterceptor.run(VictoryPhase); - await game.phaseInterceptor.run(MoveEndPhase); - await game.phaseInterceptor.run(MovePhase); - await game.phaseInterceptor.run(BerryPhase); - await game.phaseInterceptor.run(TurnEndPhase); - await game.phaseInterceptor.run(BattleEndPhase); - await game.phaseInterceptor.run(EggLapsePhase); - await game.phaseInterceptor.run(SelectModifierPhase); + await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(SelectModifierPhase); expect(game.scene.ui?.getMode()).toBe(Mode.MODIFIER_SELECT); expect(game.scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name); - }, 100000); + }, 20000); it("do attack wave 3 - single battle - regular - NO OHKO with opponent using non damage attack", async() => { vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO); @@ -160,7 +117,8 @@ describe("Test Battle Phase", () => { vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(5); vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3); vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]); - vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TAIL_WHIP]); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION); + vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TAIL_WHIP, Moves.TAIL_WHIP, Moves.TAIL_WHIP, Moves.TAIL_WHIP]); vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); await game.startBattle(); game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { @@ -170,35 +128,8 @@ describe("Test Battle Phase", () => { const movePosition = getMovePosition(game.scene, 0, Moves.TACKLE); (game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false); }); - await game.phaseInterceptor.run(EnemyCommandPhase); - await game.phaseInterceptor.run(TurnStartPhase); - - await game.phaseInterceptor.run(MovePhase); - await game.phaseInterceptor.run(MessagePhase); - await game.phaseInterceptor.run(MoveEffectPhase); - await game.phaseInterceptor.run(DamagePhase); - await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEndPhase)); - await game.phaseInterceptor.run(MoveEndPhase); - - await game.phaseInterceptor.run(MovePhase); - await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEffectPhase)); - await game.phaseInterceptor.run(MoveEffectPhase); - game.scene.moveAnimations = null; // Mandatory to avoid the crash - await game.phaseInterceptor.run(StatChangePhase, () => game.isCurrentPhase(MessagePhase) || game.isCurrentPhase(MoveEndPhase) || game.isCurrentPhase(DamagePhase)); - await game.phaseInterceptor.run(DamagePhase, () => game.isCurrentPhase(MessagePhase) || game.isCurrentPhase(MoveEndPhase)); - await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(MoveEndPhase)); - await game.phaseInterceptor.run(MoveEndPhase); - - await game.phaseInterceptor.run(BerryPhase); - await game.phaseInterceptor.run(MessagePhase, () => game.isCurrentPhase(TurnEndPhase)); - await game.phaseInterceptor.run(TurnEndPhase); - - await game.phaseInterceptor.run(TurnInitPhase); - await game.phaseInterceptor.run(CommandPhase); - await waitUntil(() => game.scene.ui?.getMode() === Mode.COMMAND); - expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); - expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); - }, 100000); + await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase); + }, 20000); it("load 100% data file", async() => { await game.importData("src/test/utils/saves/everything.prsv"); @@ -207,7 +138,7 @@ describe("Test Battle Phase", () => { return species.caughtAttr !== 0n; }).length; expect(caughtCount).toBe(Object.keys(allSpecies).length); - }, 50000); + }, 20000); it("start battle with selected team", async() => { await game.startBattle([ @@ -218,25 +149,7 @@ describe("Test Battle Phase", () => { expect(game.scene.getParty()[0].species.speciesId).toBe(Species.CHARIZARD); expect(game.scene.getParty()[1].species.speciesId).toBe(Species.CHANSEY); expect(game.scene.getParty()[2].species.speciesId).toBe(Species.MEW); - }, 50000); - - it("assert next phase", async() => { - await game.phaseInterceptor.run(LoginPhase); - game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => { - game.scene.gameData.gender = PlayerGender.MALE; - game.endPhase(); - }, () => game.isCurrentPhase(TitlePhase)); - await game.phaseInterceptor.mustRun(SelectGenderPhase).catch((error) => expect(error).toBe(SelectGenderPhase)); - await game.phaseInterceptor.mustRun(TitlePhase).catch((error) => expect(error).toBe(TitlePhase)); - game.onNextPrompt("TitlePhase", Mode.TITLE, () => { - const starters = generateStarter(game.scene); - const selectStarterPhase = new SelectStarterPhase(game.scene, GameModes.CLASSIC); - game.scene.pushPhase(new EncounterPhase(game.scene, false)); - selectStarterPhase.initBattle(starters); - }); - await game.phaseInterceptor.mustRun(EncounterPhase).catch((error) => expect(error).toBe(EncounterPhase)); - await game.phaseInterceptor.mustRun(PostSummonPhase).catch((error) => expect(error).toBe(PostSummonPhase)); - }, 50000); + }, 20000); it("test remove random battle seed int", async() => { for (let i=0; i<10; i++) { @@ -244,5 +157,107 @@ describe("Test Battle Phase", () => { expect(rand).toBe(14); } }); + + it("wrong phase", async() => { + await game.phaseInterceptor.run(LoginPhase); + await game.phaseInterceptor.run(LoginPhase).catch((e) => { + expect(e).toBe("Wrong phase: this is SelectGenderPhase and not LoginPhase"); + }); + }, 20000); + + it("wrong phase but skip", async() => { + await game.phaseInterceptor.run(LoginPhase); + await game.phaseInterceptor.run(LoginPhase, () => game.isCurrentPhase(SelectGenderPhase)); + }, 20000); + + it("good run", async() => { + await game.phaseInterceptor.run(LoginPhase); + game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => { + game.scene.gameData.gender = PlayerGender.MALE; + game.endPhase(); + }, () => game.isCurrentPhase(TitlePhase)); + await game.phaseInterceptor.run(SelectGenderPhase, () => game.isCurrentPhase(TitlePhase)); + await game.phaseInterceptor.run(TitlePhase); + }, 20000); + + it("good run from select gender to title", async() => { + await game.phaseInterceptor.run(LoginPhase); + game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => { + game.scene.gameData.gender = PlayerGender.MALE; + game.endPhase(); + }, () => game.isCurrentPhase(TitlePhase)); + await game.phaseInterceptor.runFrom(SelectGenderPhase).to(TitlePhase); + }, 20000); + + it("good run to SummonPhase phase", async() => { + await game.phaseInterceptor.run(LoginPhase); + game.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => { + game.scene.gameData.gender = PlayerGender.MALE; + game.endPhase(); + }, () => game.isCurrentPhase(TitlePhase)); + game.onNextPrompt("TitlePhase", Mode.TITLE, () => { + game.scene.gameMode = getGameMode(GameModes.CLASSIC); + const starters = generateStarter(game.scene); + const selectStarterPhase = new SelectStarterPhase(game.scene); + game.scene.pushPhase(new EncounterPhase(game.scene, false)); + selectStarterPhase.initBattle(starters); + }); + await game.phaseInterceptor.runFrom(SelectGenderPhase).to(SummonPhase); + }, 20000); + + it("2vs1", async() => { + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MIGHTYENA); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION); + await game.startBattle([ + Species.BLASTOISE, + Species.CHARIZARD, + ]); + expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); + expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); + }, 20000); + + it("1vs1", async() => { + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MIGHTYENA); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION); + await game.startBattle([ + Species.BLASTOISE, + ]); + expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); + expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); + }, 20000); + + it("2vs2", async() => { + vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MIGHTYENA); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION); + vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3); + await game.startBattle([ + Species.BLASTOISE, + Species.CHARIZARD, + ]); + expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); + expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); + }, 20000); + + it("4vs2", async() => { + vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MIGHTYENA); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION); + vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION); + vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3); + await game.startBattle([ + Species.BLASTOISE, + Species.CHARIZARD, + Species.DARKRAI, + Species.GABITE, + ]); + expect(game.scene.ui?.getMode()).toBe(Mode.COMMAND); + expect(game.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); + }, 20000); }); diff --git a/src/test/battle/error-handling.test.ts b/src/test/battle/error-handling.test.ts new file mode 100644 index 00000000000..2cb29aab1e7 --- /dev/null +++ b/src/test/battle/error-handling.test.ts @@ -0,0 +1,39 @@ +import {afterEach, beforeAll, beforeEach, describe, it, vi} from "vitest"; +import GameManager from "#app/test/utils/gameManager"; +import Phaser from "phaser"; +import * as overrides from "#app/overrides"; +import {Species} from "#app/data/enums/species"; +import {Moves} from "#app/data/enums/moves"; +import {Abilities} from "#app/data/enums/abilities"; + +describe("Test Battle Phase", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + }); + + it("should start phase", async() => { + vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MEWTWO); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA); + vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000); + vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION); + vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]); + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + await game.startBattle(); + }, 100000); +}); + diff --git a/src/test/phases/phases.test.ts b/src/test/phases/phases.test.ts index abb7ac2c975..009526ebe41 100644 --- a/src/test/phases/phases.test.ts +++ b/src/test/phases/phases.test.ts @@ -28,7 +28,8 @@ describe("Phases", () => { describe("LoginPhase", () => { it("should start the login phase", async () => { const loginPhase = new LoginPhase(scene); - loginPhase.start(); + scene.pushPhase(loginPhase); + await game.phaseInterceptor.run(LoginPhase); expect(scene.ui.getMode()).to.equal(Mode.MESSAGE); }); }); @@ -36,16 +37,18 @@ describe("Phases", () => { describe("TitlePhase", () => { it("should start the title phase", async () => { const titlePhase = new TitlePhase(scene); - titlePhase.start(); - expect(scene.ui.getMode()).to.equal(Mode.MESSAGE); + scene.pushPhase(titlePhase); + await game.phaseInterceptor.run(TitlePhase); + expect(scene.ui.getMode()).to.equal(Mode.TITLE); }); }); describe("UnavailablePhase", () => { it("should start the unavailable phase", async () => { const unavailablePhase = new UnavailablePhase(scene); - unavailablePhase.start(); + scene.pushPhase(unavailablePhase); + await game.phaseInterceptor.run(UnavailablePhase); expect(scene.ui.getMode()).to.equal(Mode.UNAVAILABLE); - }); + }, 20000); }); }); diff --git a/src/test/ui/starter-select.test.ts b/src/test/ui/starter-select.test.ts index 802542da259..9f323a2839c 100644 --- a/src/test/ui/starter-select.test.ts +++ b/src/test/ui/starter-select.test.ts @@ -15,9 +15,9 @@ import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; import SaveSlotSelectUiHandler from "#app/ui/save-slot-select-ui-handler"; import {OptionSelectItem} from "#app/ui/abstact-option-select-ui-handler"; import {Gender} from "#app/data/gender"; +import {allSpecies} from "#app/data/pokemon-species"; import {Nature} from "#app/data/nature"; import {Abilities} from "#app/data/enums/abilities"; -import {allSpecies} from "#app/data/pokemon-species"; describe("UI - Starter select", () => { @@ -51,12 +51,13 @@ describe("UI - Starter select", () => { currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); - await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); handler.processInput(Button.ACTION); + game.phaseInterceptor.unlock(); }); + await game.phaseInterceptor.run(SelectStarterPhase); let options: OptionSelectItem[]; let optionSelectUiHandler: OptionSelectUiHandler; await new Promise((resolve) => { @@ -73,21 +74,20 @@ describe("UI - Starter select", () => { expect(options.some(option => option.label === "Cancel")).toBe(true); optionSelectUiHandler.processInput(Button.ACTION); - game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.SUBMIT); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { - const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; - saveSlotSelectUiHandler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + resolve(); + }); }); await game.phaseInterceptor.whenAboutToRun(EncounterPhase); @@ -99,19 +99,25 @@ describe("UI - Starter select", () => { it("Bulbasaur - shiny - variant 2 female hardy overgrow", async() => { await game.importData("src/test/utils/saves/everything.prsv"); + const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => { + const species = game.scene.gameData.dexData[key]; + return species.caughtAttr !== 0n; + }).length; + expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", Mode.TITLE, () => { const currentPhase = game.scene.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); - await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); handler.processInput(Button.CYCLE_GENDER); handler.processInput(Button.ACTION); + game.phaseInterceptor.unlock(); }); + await game.phaseInterceptor.run(SelectStarterPhase); let options: OptionSelectItem[]; let optionSelectUiHandler: OptionSelectUiHandler; await new Promise((resolve) => { @@ -128,21 +134,20 @@ describe("UI - Starter select", () => { expect(options.some(option => option.label === "Cancel")).toBe(true); optionSelectUiHandler.processInput(Button.ACTION); - game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.SUBMIT); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { - const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; - saveSlotSelectUiHandler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + resolve(); + }); }); await game.phaseInterceptor.whenAboutToRun(EncounterPhase); @@ -153,15 +158,19 @@ describe("UI - Starter select", () => { expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.OVERGROW); }, 20000); - it("Bulbasaur - shiny - variant 2 female lonely cholorophyl", async() => { + it("Bulbasaur - shiny - variant 2 female lonely chlorophyl", async() => { await game.importData("src/test/utils/saves/everything.prsv"); + const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => { + const species = game.scene.gameData.dexData[key]; + return species.caughtAttr !== 0n; + }).length; + expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", Mode.TITLE, () => { const currentPhase = game.scene.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); - await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); @@ -169,7 +178,9 @@ describe("UI - Starter select", () => { handler.processInput(Button.CYCLE_NATURE); handler.processInput(Button.CYCLE_ABILITY); handler.processInput(Button.ACTION); + game.phaseInterceptor.unlock(); }); + await game.phaseInterceptor.run(SelectStarterPhase); let options: OptionSelectItem[]; let optionSelectUiHandler: OptionSelectUiHandler; await new Promise((resolve) => { @@ -186,21 +197,20 @@ describe("UI - Starter select", () => { expect(options.some(option => option.label === "Cancel")).toBe(true); optionSelectUiHandler.processInput(Button.ACTION); - game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.SUBMIT); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { - const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; - saveSlotSelectUiHandler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + resolve(); + }); }); await game.phaseInterceptor.whenAboutToRun(EncounterPhase); @@ -211,21 +221,27 @@ describe("UI - Starter select", () => { expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.CHLOROPHYLL); }, 20000); - it("Bulbasaur - shiny - variant 2 female", async() => { + it("Bulbasaur - shiny - variant 2 female lonely chlorophyl", async() => { await game.importData("src/test/utils/saves/everything.prsv"); + const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => { + const species = game.scene.gameData.dexData[key]; + return species.caughtAttr !== 0n; + }).length; + expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", Mode.TITLE, () => { const currentPhase = game.scene.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); - await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); handler.processInput(Button.CYCLE_GENDER); handler.processInput(Button.ACTION); + game.phaseInterceptor.unlock(); }); + await game.phaseInterceptor.run(SelectStarterPhase); let options: OptionSelectItem[]; let optionSelectUiHandler: OptionSelectUiHandler; await new Promise((resolve) => { @@ -242,21 +258,20 @@ describe("UI - Starter select", () => { expect(options.some(option => option.label === "Cancel")).toBe(true); optionSelectUiHandler.processInput(Button.ACTION); - game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.SUBMIT); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { - const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; - saveSlotSelectUiHandler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + resolve(); + }); }); await game.phaseInterceptor.whenAboutToRun(EncounterPhase); @@ -268,19 +283,25 @@ describe("UI - Starter select", () => { it("Bulbasaur - not shiny", async() => { await game.importData("src/test/utils/saves/everything.prsv"); + const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => { + const species = game.scene.gameData.dexData[key]; + return species.caughtAttr !== 0n; + }).length; + expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", Mode.TITLE, () => { const currentPhase = game.scene.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); - await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); handler.processInput(Button.CYCLE_SHINY); handler.processInput(Button.ACTION); + game.phaseInterceptor.unlock(); }); + await game.phaseInterceptor.run(SelectStarterPhase); let options: OptionSelectItem[]; let optionSelectUiHandler: OptionSelectUiHandler; await new Promise((resolve) => { @@ -297,21 +318,20 @@ describe("UI - Starter select", () => { expect(options.some(option => option.label === "Cancel")).toBe(true); optionSelectUiHandler.processInput(Button.ACTION); - game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.SUBMIT); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { - const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; - saveSlotSelectUiHandler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + resolve(); + }); }); await game.phaseInterceptor.whenAboutToRun(EncounterPhase); @@ -320,76 +340,28 @@ describe("UI - Starter select", () => { expect(game.scene.getParty()[0].variant).toBe(0); }, 20000); - it("Bulbasaur - shiny - variant 0", async() => { - await game.importData("src/test/utils/saves/everything.prsv"); - await game.runToTitle(); - game.onNextPrompt("TitlePhase", Mode.TITLE, () => { - const currentPhase = game.scene.getCurrentPhase() as TitlePhase; - currentPhase.gameMode = GameModes.CLASSIC; - currentPhase.end(); - }); - await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); - game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.RIGHT); - handler.processInput(Button.V); - handler.processInput(Button.ACTION); - }); - let options: OptionSelectItem[]; - let optionSelectUiHandler: OptionSelectUiHandler; - await new Promise((resolve) => { - game.onNextPrompt("SelectStarterPhase", Mode.OPTION_SELECT, () => { - optionSelectUiHandler = game.scene.ui.getHandler() as OptionSelectUiHandler; - options = optionSelectUiHandler.getOptionsWithScroll(); - resolve(); - }); - }); - expect(options.some(option => option.label === "Add to Party")).toBe(true); - expect(options.some(option => option.label === "Toggle IVs")).toBe(true); - expect(options.some(option => option.label === "Manage Moves")).toBe(true); - expect(options.some(option => option.label === "Use Candies")).toBe(true); - expect(options.some(option => option.label === "Cancel")).toBe(true); - optionSelectUiHandler.processInput(Button.ACTION); - - game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.SUBMIT); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { - const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; - saveSlotSelectUiHandler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); - }); - await game.phaseInterceptor.whenAboutToRun(EncounterPhase); - - expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR); - expect(game.scene.getParty()[0].shiny).toBe(true); - expect(game.scene.getParty()[0].variant).toBe(0); - }, 20000); - it("Bulbasaur - shiny - variant 1", async() => { await game.importData("src/test/utils/saves/everything.prsv"); + const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => { + const species = game.scene.gameData.dexData[key]; + return species.caughtAttr !== 0n; + }).length; + expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", Mode.TITLE, () => { const currentPhase = game.scene.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); - await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); handler.processInput(Button.V); handler.processInput(Button.V); handler.processInput(Button.ACTION); + game.phaseInterceptor.unlock(); }); + await game.phaseInterceptor.run(SelectStarterPhase); let options: OptionSelectItem[]; let optionSelectUiHandler: OptionSelectUiHandler; await new Promise((resolve) => { @@ -406,21 +378,20 @@ describe("UI - Starter select", () => { expect(options.some(option => option.label === "Cancel")).toBe(true); optionSelectUiHandler.processInput(Button.ACTION); - game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.SUBMIT); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { - const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; - saveSlotSelectUiHandler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + resolve(); + }); }); await game.phaseInterceptor.whenAboutToRun(EncounterPhase); @@ -429,15 +400,19 @@ describe("UI - Starter select", () => { expect(game.scene.getParty()[0].variant).toBe(1); }, 20000); - it("Bulbasaur - shiny - variant 1", async() => { + it("Bulbasaur - shiny - variant 2", async() => { await game.importData("src/test/utils/saves/everything.prsv"); + const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => { + const species = game.scene.gameData.dexData[key]; + return species.caughtAttr !== 0n; + }).length; + expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", Mode.TITLE, () => { const currentPhase = game.scene.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); - await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); @@ -445,7 +420,9 @@ describe("UI - Starter select", () => { handler.processInput(Button.V); handler.processInput(Button.V); handler.processInput(Button.ACTION); + game.phaseInterceptor.unlock(); }); + await game.phaseInterceptor.run(SelectStarterPhase); let options: OptionSelectItem[]; let optionSelectUiHandler: OptionSelectUiHandler; await new Promise((resolve) => { @@ -462,21 +439,20 @@ describe("UI - Starter select", () => { expect(options.some(option => option.label === "Cancel")).toBe(true); optionSelectUiHandler.processInput(Button.ACTION); - game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.SUBMIT); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { - const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; - saveSlotSelectUiHandler.processInput(Button.ACTION); - }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); + await new Promise((resolve) => { + game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.SUBMIT); + }); + game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { + const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; + handler.processInput(Button.ACTION); + }); + game.onNextPrompt("SelectStarterPhase", Mode.SAVE_SLOT, () => { + const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; + saveSlotSelectUiHandler.processInput(Button.ACTION); + resolve(); + }); }); await game.phaseInterceptor.whenAboutToRun(EncounterPhase); @@ -485,15 +461,19 @@ describe("UI - Starter select", () => { expect(game.scene.getParty()[0].variant).toBe(2); }, 20000); - it("Check if first pokemon in party is caterpie from gen 1 and 1rd row, 3rd column ", async() => { + it("Check if first pokemon in party is caterpie from gen 1 and 1rd row, 3rd column", async() => { await game.importData("src/test/utils/saves/everything.prsv"); + const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => { + const species = game.scene.gameData.dexData[key]; + return species.caughtAttr !== 0n; + }).length; + expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", Mode.TITLE, () => { const currentPhase = game.scene.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); - await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); @@ -501,7 +481,9 @@ describe("UI - Starter select", () => { handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT); handler.processInput(Button.ACTION); + game.phaseInterceptor.unlock(); }); + await game.phaseInterceptor.run(SelectStarterPhase); let options: OptionSelectItem[]; let optionSelectUiHandler: OptionSelectUiHandler; await new Promise((resolve) => { @@ -526,6 +508,7 @@ describe("UI - Starter select", () => { resolve(); }); }); + expect(starterSelectUiHandler.starterGens[0]).toBe(0); expect(starterSelectUiHandler.starterCursors[0]).toBe(3); expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18); @@ -539,23 +522,23 @@ describe("UI - Starter select", () => { const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; saveSlotSelectUiHandler.processInput(Button.ACTION); }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); - }); await game.phaseInterceptor.whenAboutToRun(EncounterPhase); expect(game.scene.getParty()[0].species.speciesId).toBe(Species.CATERPIE); }, 20000); - it("Check if first pokemon in party is nidoran_m from gen 1 and 2nd row, 4th column (cursor (9+4)-1) ", async() => { + it("Check if first pokemon in party is nidoran_m from gen 1 and 2nd row, 4th column (cursor (9+4)-1)", async() => { await game.importData("src/test/utils/saves/everything.prsv"); + const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => { + const species = game.scene.gameData.dexData[key]; + return species.caughtAttr !== 0n; + }).length; + expect(caughtCount).toBe(Object.keys(allSpecies).length); await game.runToTitle(); game.onNextPrompt("TitlePhase", Mode.TITLE, () => { const currentPhase = game.scene.getCurrentPhase() as TitlePhase; currentPhase.gameMode = GameModes.CLASSIC; currentPhase.end(); }); - await game.phaseInterceptor.mustRun(SelectStarterPhase).catch((error) => expect(error).toBe(SelectStarterPhase)); game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); @@ -564,7 +547,9 @@ describe("UI - Starter select", () => { handler.processInput(Button.RIGHT); handler.processInput(Button.DOWN); handler.processInput(Button.ACTION); + game.phaseInterceptor.unlock(); }); + await game.phaseInterceptor.run(SelectStarterPhase); let options: OptionSelectItem[]; let optionSelectUiHandler: OptionSelectUiHandler; await new Promise((resolve) => { @@ -589,6 +574,7 @@ describe("UI - Starter select", () => { resolve(); }); }); + expect(starterSelectUiHandler.starterGens[0]).toBe(0); expect(starterSelectUiHandler.starterCursors[0]).toBe(12); expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18); @@ -602,10 +588,6 @@ describe("UI - Starter select", () => { const saveSlotSelectUiHandler = game.scene.ui.getHandler() as SaveSlotSelectUiHandler; saveSlotSelectUiHandler.processInput(Button.ACTION); }); - game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { - const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; - handler.processInput(Button.ACTION); - }); await game.phaseInterceptor.whenAboutToRun(EncounterPhase); expect(game.scene.getParty()[0].species.speciesId).toBe(Species.NIDORAN_M); }, 20000); diff --git a/src/test/utils/errorInterceptor.ts b/src/test/utils/errorInterceptor.ts new file mode 100644 index 00000000000..7f06c47fd39 --- /dev/null +++ b/src/test/utils/errorInterceptor.ts @@ -0,0 +1,50 @@ +export default class ErrorInterceptor { + private static instance: ErrorInterceptor; + public running; + + constructor() { + this.running = []; + } + + public static getInstance(): ErrorInterceptor { + if (!ErrorInterceptor.instance) { + ErrorInterceptor.instance = new ErrorInterceptor(); + } + return ErrorInterceptor.instance; + } + + clear() { + this.running = []; + } + + add(obj) { + this.running.push(obj); + } + + remove(obj) { + const index = this.running.indexOf(obj); + if (index !== -1) { + this.running.splice(index, 1); + } + } +} + + +process.on("uncaughtException", (error) => { + console.log(error); + const toStop = ErrorInterceptor.getInstance().running; + for (const elm of toStop) { + elm.rejectAll(error); + } + global.testFailed = true; +}); + +// Global error handler for unhandled promise rejections +process.on("unhandledRejection", (reason, promise) => { + console.log(reason); + const toStop = ErrorInterceptor.getInstance().running; + for (const elm of toStop) { + elm.rejectAll(reason); + } + global.testFailed = true; +}); diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts index cc759fd1563..dc1991d5659 100644 --- a/src/test/utils/gameManager.ts +++ b/src/test/utils/gameManager.ts @@ -2,22 +2,18 @@ import GameWrapper from "#app/test/utils/gameWrapper"; import {Mode} from "#app/ui/ui"; import {generateStarter, waitUntil} from "#app/test/utils/gameManagerUtils"; import { - CheckSwitchPhase, CommandPhase, EncounterPhase, LoginPhase, PostSummonPhase, SelectGenderPhase, SelectStarterPhase, - SummonPhase, TitlePhase, - ToggleDoublePositionPhase, } from "#app/phases"; import BattleScene from "#app/battle-scene.js"; import PhaseInterceptor from "#app/test/utils/phaseInterceptor"; import TextInterceptor from "#app/test/utils/TextInterceptor"; -import {expect} from "vitest"; -import {GameModes} from "#app/game-mode"; +import {GameModes, getGameMode} from "#app/game-mode"; import fs from "fs"; import { AES, enc } from "crypto-js"; import {updateUserInfo} from "#app/account"; @@ -26,6 +22,7 @@ import {PlayerGender} from "#app/data/enums/player-gender"; import {GameDataType} from "#app/data/enums/game-data-type"; import InputsHandler from "#app/test/utils/inputsHandler"; import {ExpNotification} from "#app/enums/exp-notification"; +import ErrorInterceptor from "#app/test/utils/errorInterceptor"; /** * Class to manage the game state and transitions between phases. @@ -43,6 +40,8 @@ export default class GameManager { * @param bypassLogin - Whether to bypass the login phase. */ constructor(phaserGame: Phaser.Game, bypassLogin: boolean = true) { + localStorage.clear(); + ErrorInterceptor.getInstance().clear(); BattleScene.prototype.randBattleSeedInt = (arg) => arg-1; this.gameWrapper = new GameWrapper(phaserGame, bypassLogin); this.scene = new BattleScene(); @@ -94,14 +93,14 @@ export default class GameManager { * @returns A promise that resolves when the title phase is reached. */ runToTitle(): Promise { - return new Promise(async(resolve) => { - await this.phaseInterceptor.run(LoginPhase); + return new Promise(async(resolve, reject) => { + await this.phaseInterceptor.run(LoginPhase).catch((e) => reject(e)); this.onNextPrompt("SelectGenderPhase", Mode.OPTION_SELECT, () => { this.scene.gameData.gender = PlayerGender.MALE; this.endPhase(); }, () => this.isCurrentPhase(TitlePhase)); - await this.phaseInterceptor.run(SelectGenderPhase, () => this.isCurrentPhase(TitlePhase)); - await this.phaseInterceptor.run(TitlePhase); + await this.phaseInterceptor.run(SelectGenderPhase, () => this.isCurrentPhase(TitlePhase)).catch((e) => reject(e)); + await this.phaseInterceptor.run(TitlePhase).catch((e) => reject(e)); this.scene.gameSpeed = 5; this.scene.moveAnimations = false; this.scene.showLevelUpStats = false; @@ -118,15 +117,16 @@ export default class GameManager { * @returns A promise that resolves when the summon phase is reached. */ runToSummon(species?: Species[]): Promise { - return new Promise(async(resolve) => { - await this.runToTitle(); + return new Promise(async(resolve, reject) => { + await this.runToTitle().catch((e) => reject(e)); this.onNextPrompt("TitlePhase", Mode.TITLE, () => { + this.scene.gameMode = getGameMode(GameModes.CLASSIC); const starters = generateStarter(this.scene, species); - const selectStarterPhase = new SelectStarterPhase(this.scene, GameModes.CLASSIC); + const selectStarterPhase = new SelectStarterPhase(this.scene); this.scene.pushPhase(new EncounterPhase(this.scene, false)); selectStarterPhase.initBattle(starters); }); - await this.phaseInterceptor.run(EncounterPhase); + await this.phaseInterceptor.run(EncounterPhase).catch((e) => reject(e)); resolve(); }); } @@ -137,25 +137,18 @@ export default class GameManager { * @returns A promise that resolves when the battle is started. */ startBattle(species?: Species[]): Promise { - return new Promise(async(resolve) => { - await this.runToSummon(species); - await this.phaseInterceptor.runFrom(PostSummonPhase).to(ToggleDoublePositionPhase); - await this.phaseInterceptor.run(SummonPhase, () => this.isCurrentPhase(CheckSwitchPhase) || this.isCurrentPhase(PostSummonPhase)); + return new Promise(async(resolve, reject) => { + await this.runToSummon(species).catch((e) => reject(e)); this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { this.setMode(Mode.MESSAGE); this.endPhase(); - }, () => this.isCurrentPhase(PostSummonPhase)); + }, () => this.isCurrentPhase(CommandPhase)); this.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { this.setMode(Mode.MESSAGE); this.endPhase(); - }, () => this.isCurrentPhase(PostSummonPhase)); - await this.phaseInterceptor.run(CheckSwitchPhase, () => this.isCurrentPhase(PostSummonPhase)); - await this.phaseInterceptor.run(CheckSwitchPhase, () => this.isCurrentPhase(PostSummonPhase)); - await this.phaseInterceptor.runFrom(PostSummonPhase).to(CommandPhase); - await waitUntil(() => this.scene.ui?.getMode() === Mode.COMMAND); + }, () => this.isCurrentPhase(CommandPhase)); + await this.phaseInterceptor.runFrom(PostSummonPhase).to(CommandPhase).catch((e) => reject(e)); console.log("==================[New Turn]=================="); - expect(this.scene.ui?.getMode()).toBe(Mode.COMMAND); - expect(this.scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); return resolve(); }); } diff --git a/src/test/utils/gameManagerUtils.ts b/src/test/utils/gameManagerUtils.ts index 4a72006faa6..1b7bbdabaf0 100644 --- a/src/test/utils/gameManagerUtils.ts +++ b/src/test/utils/gameManagerUtils.ts @@ -3,7 +3,7 @@ import {getDailyRunStarters} from "#app/data/daily-run"; import {Gender} from "#app/data/gender"; import {Species} from "#app/data/enums/species"; import {Starter} from "#app/ui/starter-select-ui-handler"; -import {GameModes, gameModes} from "#app/game-mode"; +import {GameModes, getGameMode} from "#app/game-mode"; import {getPokemonSpecies, getPokemonSpeciesForm} from "#app/data/pokemon-species"; import {PlayerPokemon} from "#app/field/pokemon"; @@ -49,7 +49,7 @@ function getTestRunStarters(scene, seed, species) { return getDailyRunStarters(scene, seed); } const starters: Starter[] = []; - const startingLevel = gameModes[GameModes.CLASSIC].getStartingLevel(); + const startingLevel = getGameMode(GameModes.CLASSIC).getStartingLevel(); for (const specie of species) { const starterSpeciesForm = getPokemonSpeciesForm(specie, 0); diff --git a/src/test/utils/gameWrapper.ts b/src/test/utils/gameWrapper.ts index c9aed9865db..7d0e4110351 100644 --- a/src/test/utils/gameWrapper.ts +++ b/src/test/utils/gameWrapper.ts @@ -86,7 +86,6 @@ export default class GameWrapper { frames: {}, }); Pokemon.prototype.enableMask = () => null; - localStorage.clear(); } setScene(scene: BattleScene) { diff --git a/src/test/utils/mocks/mocksContainer/mockText.ts b/src/test/utils/mocks/mocksContainer/mockText.ts index f219a6d1bad..bfff146465e 100644 --- a/src/test/utils/mocks/mocksContainer/mockText.ts +++ b/src/test/utils/mocks/mocksContainer/mockText.ts @@ -7,9 +7,11 @@ export default class MockText { private scene; private textureManager; public list = []; + public style; constructor(textureManager, x, y, content, styleOptions) { this.scene = textureManager.scene; this.textureManager = textureManager; + this.style = {}; // Phaser.GameObjects.TextStyle.prototype.setStyle = () => null; // Phaser.GameObjects.Text.prototype.updateText = () => null; // Phaser.Textures.TextureManager.prototype.addCanvas = () => {}; diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index 2915f2e614b..49e67e8448c 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -6,10 +6,12 @@ import { LoginPhase, MessagePhase, MoveEffectPhase, MoveEndPhase, MovePhase, NewBattlePhase, NextEncounterPhase, PostSummonPhase, SelectGenderPhase, SelectModifierPhase, - SelectStarterPhase, ShinySparklePhase, ShowAbilityPhase, StatChangePhase, SummonPhase, - TitlePhase, ToggleDoublePositionPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase, VictoryPhase + SelectStarterPhase, SelectTargetPhase, ShinySparklePhase, ShowAbilityPhase, StatChangePhase, SummonPhase, + TitlePhase, ToggleDoublePositionPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase, UnavailablePhase, VictoryPhase } from "#app/phases"; -import {Mode} from "#app/ui/ui"; +import UI, {Mode} from "#app/ui/ui"; +import {Phase} from "#app/phase"; +import ErrorInterceptor from "#app/test/utils/errorInterceptor"; export default class PhaseInterceptor { public scene; @@ -21,6 +23,9 @@ export default class PhaseInterceptor { private intervalRun; private prompts; private phaseFrom; + private inProgress; + private originalSetMode; + private originalSuperEnd; /** * List of phases with their corresponding start methods. @@ -56,6 +61,12 @@ export default class PhaseInterceptor { [MoveEndPhase, this.startPhase], [StatChangePhase, this.startPhase], [ShinySparklePhase, this.startPhase], + [SelectTargetPhase, this.startPhase], + [UnavailablePhase, this.startPhase], + ]; + + private endBySetMode = [ + TitlePhase, SelectGenderPhase, CommandPhase, SelectModifierPhase ]; /** @@ -71,6 +82,15 @@ export default class PhaseInterceptor { this.startPromptHander(); } + rejectAll(error) { + if (this.inProgress) { + clearInterval(this.promptInterval); + clearInterval(this.interval); + clearInterval(this.intervalRun); + this.inProgress.onError(error); + } + } + /** * Method to set the starting phase. * @param phaseFrom - The phase to start from. @@ -86,20 +106,31 @@ export default class PhaseInterceptor { * @param phaseTo - The phase to transition to. * @returns A promise that resolves when the transition is complete. */ - async to(phaseTo): Promise { - return new Promise(async (resolve) => { - await this.run(this.phaseFrom); + async to(phaseTo, runTarget: boolean = true): Promise { + return new Promise(async (resolve, reject) => { + ErrorInterceptor.getInstance().add(this); + await this.run(this.phaseFrom).catch((e) => reject(e)); this.phaseFrom = null; const targetName = typeof phaseTo === "string" ? phaseTo : phaseTo.name; - this.intervalRun = setInterval(async () => { + this.intervalRun = setInterval(async() => { const currentPhase = this.onHold?.length && this.onHold[0]; - if (currentPhase && currentPhase.name !== targetName) { - await this.run(currentPhase.name); - } else if (currentPhase.name === targetName) { - await this.run(currentPhase.name); + if (currentPhase && currentPhase.name === targetName) { clearInterval(this.intervalRun); + if (!runTarget) { + return resolve(); + } + await this.run(currentPhase).catch((e) => { + clearInterval(this.intervalRun); + return reject(e); + }); return resolve(); } + if (currentPhase && currentPhase.name !== targetName) { + await this.run(currentPhase).catch((e) => { + clearInterval(this.intervalRun); + return reject(e); + }); + } }); }); } @@ -111,92 +142,53 @@ export default class PhaseInterceptor { * @returns A promise that resolves when the phase is run. */ run(phaseTarget, skipFn?): Promise { - this.scene.moveAnimations = null; // Mandatory to avoid crash - return new Promise(async (resolve) => { - this.waitUntil(phaseTarget, skipFn).then(() => { - const currentPhase = this.onHold.shift(); - currentPhase.call(); - resolve(); - }).catch(() => { - resolve(); - }); - }); - } - - /** - * Method to ensure a phase is run, to throw error on test if not. - * @param phaseTarget - The phase to run. - * @returns A promise that resolves when the phase is run. - */ - mustRun(phaseTarget): Promise { const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; this.scene.moveAnimations = null; // Mandatory to avoid crash return new Promise(async (resolve, reject) => { + ErrorInterceptor.getInstance().add(this); const interval = setInterval(async () => { - const currentPhase = this.onHold?.length && this.onHold[0]; - if (currentPhase && currentPhase.name !== targetName) { - reject(currentPhase); - } else if (currentPhase && currentPhase.name === targetName) { + const currentPhase = this.onHold.shift(); + if (currentPhase) { + if (currentPhase.name !== targetName) { + clearInterval(interval); + const skip = skipFn && skipFn(currentPhase.name); + if (skip) { + this.onHold.unshift(currentPhase); + ErrorInterceptor.getInstance().remove(this); + return resolve(); + } + clearInterval(interval); + return reject(`Wrong phase: this is ${currentPhase.name} and not ${targetName}`); + } clearInterval(interval); - await this.run(phaseTarget); - resolve(); + this.inProgress = { + name: currentPhase.name, + callback: () => { + ErrorInterceptor.getInstance().remove(this); + resolve(); + }, + onError: (error) => reject(error), + }; + currentPhase.call(); } }); }); } - /** - * Method to execute actions when about to run a phase. Does not run the phase, stop right before. - * @param phaseTarget - The phase to run. - * @param skipFn - Optional skip function. - * @returns A promise that resolves when the phase is about to run. - */ whenAboutToRun(phaseTarget, skipFn?): Promise { - return new Promise(async (resolve) => { - this.waitUntil(phaseTarget, skipFn).then(() => { - resolve(); - }).catch(() => { - resolve(); - }); - }); - } - - /** - * Method to remove a phase from the list. - * @param phaseTarget - The phase to remove. - * @param skipFn - Optional skip function. - * @returns A promise that resolves when the phase is removed. - */ - remove(phaseTarget, skipFn?): Promise { - return new Promise(async (resolve) => { - this.waitUntil(phaseTarget, skipFn).then(() => { - this.onHold.shift(); - this.scene.getCurrentPhase().end(); - resolve(); - }).catch(() => { - resolve(); - }); - }); - } - - /** - * Method to wait until a specific phase is reached. - * @param phaseTarget - The phase to wait for. - * @param skipFn - Optional skip function. - * @returns A promise that resolves when the phase is reached. - */ - waitUntil(phaseTarget, skipFn?): Promise { const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; - return new Promise((resolve, reject) => { - this.interval = setInterval(() => { - const currentPhase = this.onHold?.length && this.onHold[0] && this.onHold[0].name; - // if the currentPhase here is not filled, it means it's a phase we haven't added to the list - if (currentPhase === targetName) { - clearInterval(this.interval); - return resolve(); - } else if (skipFn && skipFn()) { - clearInterval(this.interval); - return reject("Skipped phase"); + this.scene.moveAnimations = null; // Mandatory to avoid crash + return new Promise(async (resolve, reject) => { + ErrorInterceptor.getInstance().add(this); + const interval = setInterval(async () => { + const currentPhase = this.onHold.shift(); + if (currentPhase) { + if (currentPhase.name !== targetName) { + this.onHold.unshift(currentPhase); + } else { + clearInterval(interval); + resolve(); + } } }); }); @@ -206,10 +198,17 @@ export default class PhaseInterceptor { * Method to initialize phases and their corresponding methods. */ initPhases() { - for (const [phase, method] of this.PHASES) { + this.originalSetMode = UI.prototype.setMode; + this.originalSuperEnd = Phase.prototype.end; + UI.prototype.setMode = (mode, ...args) => this.setMode.call(this, mode, ...args); + Phase.prototype.end = () => this.superEndPhase.call(this); + for (const [phase, methodStart] of this.PHASES) { const originalStart = phase.prototype.start; - this.phases[phase.name] = originalStart; - phase.prototype.start = () => method.call(this, phase); + this.phases[phase.name] = { + start: originalStart, + endBySetMode: this.endBySetMode.some((elm) => elm.name === phase.name), + }; + phase.prototype.start = () => methodStart.call(this, phase); } } @@ -223,11 +222,44 @@ export default class PhaseInterceptor { this.onHold.push({ name: phase.name, call: () => { - this.phases[phase.name].apply(instance); + this.phases[phase.name].start.apply(instance); } }); } + unlock() { + this.inProgress?.callback(); + this.inProgress = undefined; + } + + /** + * Method to end a phase and log it. + * @param phase - The phase to start. + */ + superEndPhase() { + const instance = this.scene.getCurrentPhase(); + console.log(`%c INTERCEPTED Super End Phase ${instance.constructor.name}`, "color:red;"); + this.originalSuperEnd.apply(instance); + this.inProgress?.callback(); + this.inProgress = undefined; + } + + /** + * m2m to set mode. + * @param phase - The phase to start. + */ + setMode(mode: Mode, ...args: any[]): Promise { + const currentPhase = this.scene.getCurrentPhase(); + const instance = this.scene.ui; + console.log("setMode", mode, args); + const ret = this.originalSetMode.apply(instance, [mode, ...args]); + if (this.phases[currentPhase.constructor.name].endBySetMode) { + this.inProgress?.callback(); + this.inProgress = undefined; + } + return ret; + } + /** * Method to start the prompt handler. */ @@ -271,9 +303,12 @@ export default class PhaseInterceptor { */ restoreOg() { for (const [phase] of this.PHASES) { - phase.prototype.start = this.phases[phase.name]; + phase.prototype.start = this.phases[phase.name].start; } + UI.prototype.setMode = this.originalSetMode; + Phase.prototype.end = this.originalSuperEnd; clearInterval(this.promptInterval); clearInterval(this.interval); + clearInterval(this.intervalRun); } } diff --git a/src/test/vitest.setup.ts b/src/test/vitest.setup.ts index 8729e7796f0..8dac707255a 100644 --- a/src/test/vitest.setup.ts +++ b/src/test/vitest.setup.ts @@ -9,7 +9,9 @@ import {initSpecies} from "#app/data/pokemon-species"; import {initMoves} from "#app/data/move"; import {initAbilities} from "#app/data/ability"; import {initAchievements} from "#app/system/achv.js"; +import { initVouchers } from "#app/system/voucher.js"; +initVouchers(); initAchievements(); initStatsKeys(); initPokemonPrevolutions(); @@ -19,3 +21,5 @@ initPokemonForms(); initSpecies(); initMoves(); initAbilities(); + +global.testFailed = false; diff --git a/src/touch-controls.ts b/src/touch-controls.ts index 3b734e01467..401ae7c6b93 100644 --- a/src/touch-controls.ts +++ b/src/touch-controls.ts @@ -70,12 +70,14 @@ function simulateKeyboardEvent(eventType: string, key: string, events: EventEmit events.emit("input_down", { controller_type: "keyboard", button: button, + isTouch: true }); break; case "keyup": events.emit("input_up", { controller_type: "keyboard", button: button, + isTouch: true }); break; } diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index d068cbebde7..3523cdc9f1e 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -30,8 +30,23 @@ export class UiInputs { this.listenInputs(); } + detectInputMethod(evt): void { + if (evt.controller_type === "keyboard") { + //if the touch property is present and defined, then this is a simulated keyboard event from the touch screen + if (evt.hasOwnProperty("isTouch") && evt.isTouch) { + this.scene.inputMethod = "touch"; + } else { + this.scene.inputMethod = "keyboard"; + } + } else if (evt.controller_type === "gamepad") { + this.scene.inputMethod = "gamepad"; + } + } + listenInputs(): void { this.events.on("input_down", (event) => { + this.detectInputMethod(event); + const actions = this.getActionsKeyDown(); if (!actions.hasOwnProperty(event.button)) { return; diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts index 9a9e3ef46a9..956ea65fd83 100644 --- a/src/ui/battle-flyout.ts +++ b/src/ui/battle-flyout.ts @@ -55,6 +55,9 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { /** The array of {@linkcode MoveInfo} used to track moves for the {@linkcode Pokemon} linked to the flyout */ private moveInfo: MoveInfo[] = new Array(); + /** Current state of the flyout's visibility */ + public flyoutVisible: boolean = false; + // Stores callbacks in a variable so they can be unsubscribed from when destroyed private readonly onMoveUsedEvent = (event: Event) => this.onMoveUsed(event); private readonly onBerryUsedEvent = (event: Event) => this.onBerryUsed(event); @@ -170,6 +173,8 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { /** Animates the flyout to either show or hide it by applying a fade and translation */ toggleFlyout(visible: boolean): void { + this.flyoutVisible = visible; + this.scene.tweens.add({ targets: this.flyoutParent, x: visible ? this.anchorX : this.anchorX - this.translationX, diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 9fb81f89698..c246af73d07 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -9,6 +9,7 @@ import { Type, getTypeRgb } from "../data/type"; import { getVariantTint } from "#app/data/variant"; import { BattleStat } from "#app/data/battle-stat"; import BattleFlyout from "./battle-flyout"; +import { WindowVariant, addWindow } from "./ui-theme"; const battleStatOrder = [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.ACC, BattleStat.EVA, BattleStat.SPD ]; @@ -52,6 +53,13 @@ export default class BattleInfo extends Phaser.GameObjects.Container { private type3Icon: Phaser.GameObjects.Sprite; private expBar: Phaser.GameObjects.Image; + // #region Type effectiveness hint objects + private effectivenessContainer: Phaser.GameObjects.Container; + private effectivenessWindow: Phaser.GameObjects.NineSlice; + private effectivenessText: Phaser.GameObjects.Text; + private currentEffectiveness?: string; + // #endregion + public expMaskRect: Phaser.GameObjects.Graphics; private statsContainer: Phaser.GameObjects.Container; @@ -59,7 +67,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { private statValuesContainer: Phaser.GameObjects.Container; private statNumbers: Phaser.GameObjects.Sprite[]; - public flyoutMenu: BattleFlyout; + public flyoutMenu?: BattleFlyout; constructor(scene: Phaser.Scene, x: number, y: number, player: boolean) { super(scene, x, y); @@ -250,6 +258,19 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.type3Icon.setName("icon_type_3"); this.type3Icon.setOrigin(0, 0); this.add(this.type3Icon); + + if (!this.player) { + this.effectivenessContainer = this.scene.add.container(0, 0); + this.effectivenessContainer.setPositionRelative(this.type1Icon, 22, 4); + this.effectivenessContainer.setVisible(false); + this.add(this.effectivenessContainer); + + this.effectivenessText = addTextObject(this.scene, 5, 4.5, "", TextStyle.BATTLE_INFO); + this.effectivenessWindow = addWindow((this.scene as BattleScene), 0, 0, 0, 20, false, false, null, null, WindowVariant.XTHIN); + + this.effectivenessContainer.add(this.effectivenessWindow); + this.effectivenessContainer.add(this.effectivenessText); + } } initInfo(pokemon: Pokemon) { @@ -711,6 +732,39 @@ export default class BattleInfo extends Phaser.GameObjects.Container { }); } + /** + * Request the flyoutMenu to toggle if available and hides or shows the effectiveness window where necessary + */ + toggleFlyout(visible: boolean): void { + this.flyoutMenu?.toggleFlyout(visible); + + if (visible) { + this.effectivenessContainer?.setVisible(false); + } else { + this.updateEffectiveness(this.currentEffectiveness); + } + } + + /** + * Show or hide the type effectiveness multiplier window + * Passing undefined will hide the window + */ + updateEffectiveness(effectiveness?: string) { + if (this.player) { + return; + } + this.currentEffectiveness = effectiveness; + + if (!(this.scene as BattleScene).typeHints || effectiveness === undefined || this.flyoutMenu.flyoutVisible) { + this.effectivenessContainer.setVisible(false); + return; + } + + this.effectivenessText.setText(effectiveness); + this.effectivenessWindow.width = 10 + this.effectivenessText.displayWidth; + this.effectivenessContainer.setVisible(true); + } + getBaseY(): number { return this.baseY; } diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts new file mode 100644 index 00000000000..092d954eae7 --- /dev/null +++ b/src/ui/challenges-select-ui-handler.ts @@ -0,0 +1,330 @@ +import BattleScene from "../battle-scene"; +import { TextStyle, addTextObject } from "./text"; +import { Mode } from "./ui"; +import UiHandler from "./ui-handler"; +import { addWindow } from "./ui-theme"; +import {Button} from "../enums/buttons"; +import i18next from "#app/plugins/i18n.js"; +import { SelectStarterPhase, TitlePhase } from "#app/phases.js"; +import { Challenge } from "#app/data/challenge.js"; + +/** + * Handles all the UI for choosing optional challenges. + */ +export default class GameChallengesUiHandler extends UiHandler { + private challengesContainer: Phaser.GameObjects.Container; + private valuesContainer: Phaser.GameObjects.Container; + + private scrollCursor: integer; + + private optionsBg: Phaser.GameObjects.NineSlice; + + // private difficultyText: Phaser.GameObjects.Text; + + private descriptionText: Phaser.GameObjects.Text; + + private challengeLabels: Phaser.GameObjects.Text[]; + private challengeValueLabels: Phaser.GameObjects.Text[]; + + private cursorObj: Phaser.GameObjects.NineSlice; + + private startCursor: Phaser.GameObjects.NineSlice; + + constructor(scene: BattleScene, mode?: Mode) { + super(scene, mode); + } + + setup() { + const ui = this.getUi(); + + this.challengesContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); + + this.challengesContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + + // TODO: Change this back to /9 when adding in difficulty + const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6), 24); + headerBg.setOrigin(0, 0); + + const headerText = addTextObject(this.scene, 0, 0, i18next.t("challenges:title"), TextStyle.SETTINGS_LABEL); + headerText.setOrigin(0, 0); + headerText.setPositionRelative(headerBg, 8, 4); + + // const difficultyBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 18) - 2, 24); + // difficultyBg.setOrigin(0, 0); + // difficultyBg.setPositionRelative(headerBg, headerBg.width, 0); + + // this.difficultyText = addTextObject(this.scene, 0, 0, "0", TextStyle.SETTINGS_LABEL); + // this.difficultyText.setOrigin(0, 0); + // this.difficultyText.setPositionRelative(difficultyBg, 8, 4); + + // const difficultyName = addTextObject(this.scene, 0, 0, i18next.t("challenges:points"), TextStyle.SETTINGS_LABEL); + // difficultyName.setOrigin(0, 0); + // difficultyName.setPositionRelative(difficultyBg, difficultyBg.width - difficultyName.displayWidth - 8, 4); + + this.optionsBg = addWindow(this.scene, 0, headerBg.height, (this.scene.game.canvas.width / 9), (this.scene.game.canvas.height / 6) - headerBg.height - 2); + this.optionsBg.setOrigin(0, 0); + + const descriptionBg = addWindow(this.scene, 0, headerBg.height, (this.scene.game.canvas.width / 18) - 2, (this.scene.game.canvas.height / 6) - headerBg.height - 26); + descriptionBg.setOrigin(0, 0); + descriptionBg.setPositionRelative(this.optionsBg, this.optionsBg.width, 0); + + this.descriptionText = addTextObject(this.scene, 0, 0, "", TextStyle.SETTINGS_LABEL); + this.descriptionText.setOrigin(0, 0); + this.descriptionText.setWordWrapWidth(500, true); + this.descriptionText.setPositionRelative(descriptionBg, 6, 4); + + const startBg = addWindow(this.scene, 0, 0, descriptionBg.width, 24); + startBg.setOrigin(0, 0); + startBg.setPositionRelative(descriptionBg, 0, descriptionBg.height); + + const startText = addTextObject(this.scene, 0, 0, i18next.t("challenges:start"), TextStyle.SETTINGS_LABEL); + startText.setOrigin(0, 0); + startText.setPositionRelative(startBg, 8, 4); + + this.startCursor = this.scene.add.nineslice(0, 0, "summary_moves_cursor", null, (this.scene.game.canvas.width / 18) - 10, 16, 1, 1, 1, 1); + this.startCursor.setOrigin(0, 0); + this.startCursor.setPositionRelative(startBg, 4, 4); + this.startCursor.setVisible(false); + + this.valuesContainer = this.scene.add.container(0, 0); + + this.challengeLabels = []; + this.challengeValueLabels = []; + + for (let i = 0; i < 9; i++) { + this.challengeLabels[i] = addTextObject(this.scene, 8, 28 + i * 16, "", TextStyle.SETTINGS_LABEL); + this.challengeLabels[i].setOrigin(0, 0); + + this.valuesContainer.add(this.challengeLabels[i]); + + this.challengeValueLabels[i] = addTextObject(this.scene, 0, 28 + i * 16, "", TextStyle.SETTINGS_LABEL); + this.challengeValueLabels[i].setPositionRelative(this.challengeLabels[i], 100, 0); + + this.valuesContainer.add(this.challengeValueLabels[i]); + } + + this.challengesContainer.add(headerBg); + this.challengesContainer.add(headerText); + // this.challengesContainer.add(difficultyBg); + // this.challengesContainer.add(this.difficultyText); + // this.challengesContainer.add(difficultyName); + this.challengesContainer.add(this.optionsBg); + this.challengesContainer.add(descriptionBg); + this.challengesContainer.add(this.descriptionText); + this.challengesContainer.add(startBg); + this.challengesContainer.add(startText); + this.challengesContainer.add(this.startCursor); + this.challengesContainer.add(this.valuesContainer); + + ui.add(this.challengesContainer); + + this.setCursor(0); + this.setScrollCursor(0); + + this.challengesContainer.setVisible(false); + } + + + updateText(): void { + if (this.scene.gameMode.challenges.length > 0) { + this.descriptionText.text = this.getActiveChallenge().getDescription(); + this.descriptionText.updateText(); + } + + // const totalDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getDifficulty(), 0); + // const totalMinDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getMinDifficulty(), 0); + // this.difficultyText.text = `${totalDifficulty}` + (totalMinDifficulty ? `/${totalMinDifficulty}` : ""); + // this.difficultyText.updateText(); + + for (let i = 0; i < this.challengeLabels.length; i++) { + if (i + this.scrollCursor < this.scene.gameMode.challenges.length) { + this.challengeLabels[i].setVisible(true); + this.challengeValueLabels[i].setVisible(true); + this.challengeLabels[i].text = this.scene.gameMode.challenges[i + this.scrollCursor].getName(); + this.challengeValueLabels[i].text = this.scene.gameMode.challenges[i + this.scrollCursor].getValue(); + this.challengeLabels[i].updateText(); + this.challengeValueLabels[i].updateText(); + } else { + this.challengeLabels[i].setVisible(false); + this.challengeValueLabels[i].setVisible(false); + } + } + } + + show(args: any[]): boolean { + super.show(args); + + this.startCursor.setVisible(false); + this.challengesContainer.setVisible(true); + this.setCursor(0); + + this.updateText(); + + this.getUi().moveTo(this.challengesContainer, this.getUi().length - 1); + + this.getUi().hideTooltip(); + + return true; + } + + /** + * Processes input from a specified button. + * This method handles navigation through a UI menu, including movement through menu items + * and handling special actions like cancellation. Each button press may adjust the cursor + * position or the menu scroll, and plays a sound effect if the action was successful. + * + * @param button - The button pressed by the user. + * @returns `true` if the action associated with the button was successfully processed, `false` otherwise. + */ + processInput(button: Button): boolean { + const ui = this.getUi(); + // Defines the maximum number of rows that can be displayed on the screen. + const rowsToDisplay = 9; + + let success = false; + + if (button === Button.CANCEL) { + if (this.startCursor.visible) { + this.startCursor.setVisible(false); + this.cursorObj?.setVisible(true); + } else { + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + this.scene.getCurrentPhase().end(); + } + success = true; + } else if (button === Button.SUBMIT || button === Button.ACTION) { + if (this.startCursor.visible) { + const totalDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getDifficulty(), 0); + const totalMinDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getMinDifficulty(), 0); + if (totalDifficulty >= totalMinDifficulty) { + this.scene.unshiftPhase(new SelectStarterPhase(this.scene)); + this.scene.getCurrentPhase().end(); + success = true; + } else { + success = false; + } + } else { + this.startCursor.setVisible(true); + this.cursorObj?.setVisible(false); + success = true; + } + } else { + switch (button) { + case Button.UP: + if (this.cursor === 0) { + if (this.scrollCursor === 0) { + // When at the top of the menu and pressing UP, move to the bottommost item. + if (this.scene.gameMode.challenges.length > rowsToDisplay) { // If there are more than 9 challenges, scroll to the bottom + // First, set the cursor to the last visible element, preparing for the scroll to the end. + const successA = this.setCursor(rowsToDisplay - 1); + // Then, adjust the scroll to display the bottommost elements of the menu. + const successB = this.setScrollCursor(this.scene.gameMode.challenges.length - rowsToDisplay); + success = successA && successB; // success is just there to play the little validation sound effect + } else { // If there are 9 or less challenges, just move to the bottom one + success = this.setCursor(this.scene.gameMode.challenges.length - 1); + } + } else { + success = this.setScrollCursor(this.scrollCursor - 1); + } + } else { + success = this.setCursor(this.cursor - 1); + } + if (success) { + this.updateText(); + } + break; + case Button.DOWN: + if (this.cursor === rowsToDisplay - 1) { + if (this.scrollCursor < this.scene.gameMode.challenges.length - rowsToDisplay) { + // When at the bottom and pressing DOWN, scroll if possible. + success = this.setScrollCursor(this.scrollCursor + 1); + } else { + // When at the bottom of a scrolling menu and pressing DOWN, move to the topmost item. + // First, set the cursor to the first visible element, preparing for the scroll to the top. + const successA = this.setCursor(0); + // Then, adjust the scroll to display the topmost elements of the menu. + const successB = this.setScrollCursor(0); + success = successA && successB; // success is just there to play the little validation sound effect + } + } else if (this.scene.gameMode.challenges.length < rowsToDisplay && this.cursor === this.scene.gameMode.challenges.length - 1) { + // When at the bottom of a non-scrolling menu and pressing DOWN, move to the topmost item. + success = this.setCursor(0); + } else { + success = this.setCursor(this.cursor + 1); + } + if (success) { + this.updateText(); + } + break; + case Button.LEFT: + // Moves the option cursor left, if possible. + success = this.getActiveChallenge().decreaseValue(); + if (success) { + this.updateText(); + } + break; + case Button.RIGHT: + // Moves the option cursor right, if possible. + success = this.getActiveChallenge().increaseValue(); + if (success) { + this.updateText(); + } + break; + } + } + + // Plays a select sound effect if an action was successfully processed. + if (success) { + ui.playSelect(); + } + + return success; + } + + setCursor(cursor: integer): boolean { + let ret = super.setCursor(cursor); + + if (!this.cursorObj) { + this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", null, (this.scene.game.canvas.width / 9) - 10, 16, 1, 1, 1, 1); + this.cursorObj.setOrigin(0, 0); + this.valuesContainer.add(this.cursorObj); + } + + ret ||= !this.cursorObj.visible; + this.cursorObj.setVisible(true); + + this.cursorObj.setPositionRelative(this.optionsBg, 4, 4 + (this.cursor + this.scrollCursor) * 16); + + return ret; + } + + setScrollCursor(scrollCursor: integer): boolean { + if (scrollCursor === this.scrollCursor) { + return false; + } + + this.scrollCursor = scrollCursor; + + this.setCursor(this.cursor); + + return true; + } + + getActiveChallenge(): Challenge { + return this.scene.gameMode.challenges[this.cursor + this.scrollCursor]; + } + + clear() { + super.clear(); + this.challengesContainer.setVisible(false); + this.eraseCursor(); + } + + eraseCursor() { + if (this.cursorObj) { + this.cursorObj.destroy(); + } + this.cursorObj = null; + } +} diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index acbf66b7075..f738fba5573 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -1,6 +1,6 @@ import BattleScene from "../battle-scene"; import { addTextObject, TextStyle } from "./text"; -import { Type } from "../data/type"; +import { getTypeDamageMultiplierColor, Type } from "../data/type"; import { Command } from "./command-ui-handler"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; @@ -9,6 +9,7 @@ import { CommandPhase } from "../phases"; import { MoveCategory } from "#app/data/move.js"; import i18next from "../plugins/i18n"; import {Button} from "../enums/buttons"; +import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; export default class FightUiHandler extends UiHandler { private movesContainer: Phaser.GameObjects.Container; @@ -162,7 +163,8 @@ export default class FightUiHandler extends UiHandler { ui.add(this.cursorObj); } - const moveset = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getMoveset(); + const pokemon = (this.scene.getCurrentPhase() as CommandPhase).getPokemon(); + const moveset = pokemon.getMoveset(); const hasMove = cursor < moveset.length; @@ -179,6 +181,10 @@ export default class FightUiHandler extends UiHandler { this.ppText.setText(`${Utils.padInt(pp, 2, " ")}/${Utils.padInt(maxPP, 2, " ")}`); this.powerText.setText(`${power >= 0 ? power : "---"}`); this.accuracyText.setText(`${accuracy >= 0 ? accuracy : "---"}`); + + pokemon.getOpponents().forEach((opponent) => { + opponent.updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove)); + }); } this.typeIcon.setVisible(hasMove); @@ -195,17 +201,60 @@ export default class FightUiHandler extends UiHandler { return changed; } + /** + * Gets multiplier text for a pokemon's move against a specific opponent + * Returns undefined if it's a status move + */ + private getEffectivenessText(pokemon: Pokemon, opponent: Pokemon, pokemonMove: PokemonMove): string | undefined { + const effectiveness = opponent.getMoveEffectiveness(pokemon, pokemonMove); + if (effectiveness === undefined) { + return undefined; + } + + return `${effectiveness}x`; + } + displayMoves() { - const moveset = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getMoveset(); - for (let m = 0; m < 4; m++) { - const moveText = addTextObject(this.scene, m % 2 === 0 ? 0 : 100, m < 2 ? 0 : 16, "-", TextStyle.WINDOW); - if (m < moveset.length) { - moveText.setText(moveset[m].getName()); + const pokemon = (this.scene.getCurrentPhase() as CommandPhase).getPokemon(); + const moveset = pokemon.getMoveset(); + + for (let moveIndex = 0; moveIndex < 4; moveIndex++) { + const moveText = addTextObject(this.scene, moveIndex % 2 === 0 ? 0 : 100, moveIndex < 2 ? 0 : 16, "-", TextStyle.WINDOW); + + if (moveIndex < moveset.length) { + const pokemonMove = moveset[moveIndex]; + moveText.setText(pokemonMove.getName()); + moveText.setColor(this.getMoveColor(pokemon, pokemonMove) ?? moveText.style.color); } + this.movesContainer.add(moveText); } } + /** + * Returns a specific move's color based on its type effectiveness against opponents + * If there are multiple opponents, the highest effectiveness' color is returned + * @returns A color or undefined if the default color should be used + */ + private getMoveColor(pokemon: Pokemon, pokemonMove: PokemonMove): string | undefined { + if (!this.scene.typeHints) { + return undefined; + } + + const opponents = pokemon.getOpponents(); + if (opponents.length <= 0) { + return undefined; + } + + const moveColors = opponents.map((opponent) => { + return opponent.getMoveEffectiveness(pokemon, pokemonMove); + }).sort((a, b) => b - a).map((effectiveness) => { + return getTypeDamageMultiplierColor(effectiveness, "offense"); + }); + + return moveColors[0]; + } + clear() { super.clear(); this.clearMoves(); @@ -222,6 +271,11 @@ export default class FightUiHandler extends UiHandler { clearMoves() { this.movesContainer.removeAll(true); + + const opponents = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getOpponents(); + opponents.forEach((opponent) => { + opponent.updateEffectiveness(undefined); + }); } eraseCursor() { diff --git a/src/ui/game-stats-ui-handler.ts b/src/ui/game-stats-ui-handler.ts index eda36919d3b..57a02257810 100644 --- a/src/ui/game-stats-ui-handler.ts +++ b/src/ui/game-stats-ui-handler.ts @@ -89,7 +89,7 @@ const displayStats: DisplayStats = { }, highestMoney: { label_key: "highestMoney", - sourceFunc: gameData => Utils.formatFancyLargeNumber(gameData.gameStats.highestMoney, 3), + sourceFunc: gameData => Utils.formatFancyLargeNumber(gameData.gameStats.highestMoney), }, highestDamage: { label_key: "highestDamage", diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 61941c28b2c..9a17f6e344d 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -9,6 +9,7 @@ import { handleTutorial, Tutorial } from "../tutorial"; import {Button} from "../enums/buttons"; import MoveInfoOverlay from "./move-info-overlay"; import { allMoves } from "../data/move"; +import * as Utils from "./../utils"; export const SHOP_OPTIONS_ROW_LIMIT = 6; @@ -17,6 +18,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { private rerollButtonContainer: Phaser.GameObjects.Container; private lockRarityButtonContainer: Phaser.GameObjects.Container; private transferButtonContainer: Phaser.GameObjects.Container; + private checkButtonContainer: Phaser.GameObjects.Container; private rerollCostText: Phaser.GameObjects.Text; private lockRarityButtonText: Phaser.GameObjects.Text; private moveInfoOverlay : MoveInfoOverlay; @@ -44,7 +46,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.modifierContainer = this.scene.add.container(0, 0); ui.add(this.modifierContainer); - this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width / 6) - 1, -64); + this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width / 6) - 71, -64); this.transferButtonContainer.setName("container-transfer-btn"); this.transferButtonContainer.setVisible(false); ui.add(this.transferButtonContainer); @@ -54,6 +56,16 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { transferButtonText.setOrigin(1, 0); this.transferButtonContainer.add(transferButtonText); + this.checkButtonContainer = this.scene.add.container((this.scene.game.canvas.width / 6) - 1, -64); + this.checkButtonContainer.setName("container-use-btn"); + this.checkButtonContainer.setVisible(false); + ui.add(this.checkButtonContainer); + + const checkButtonText = addTextObject(this.scene, -4, -2, "Check Team", TextStyle.PARTY); + checkButtonText.setName("text-use-btn"); + checkButtonText.setOrigin(1, 0); + this.checkButtonContainer.add(checkButtonText); + this.rerollButtonContainer = this.scene.add.container(16, -64); this.rerollButtonContainer.setName("container-reroll-brn"); this.rerollButtonContainer.setVisible(false); @@ -120,6 +132,9 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.transferButtonContainer.setVisible(false); this.transferButtonContainer.setAlpha(0); + this.checkButtonContainer.setVisible(false); + this.checkButtonContainer.setAlpha(0); + this.rerollButtonContainer.setVisible(false); this.rerollButtonContainer.setAlpha(0); @@ -203,12 +218,14 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } this.rerollButtonContainer.setAlpha(0); + this.checkButtonContainer.setAlpha(0); this.lockRarityButtonContainer.setAlpha(0); this.rerollButtonContainer.setVisible(true); + this.checkButtonContainer.setVisible(true); this.lockRarityButtonContainer.setVisible(canLockRarities); this.scene.tweens.add({ - targets: [ this.rerollButtonContainer, this.lockRarityButtonContainer ], + targets: [ this.rerollButtonContainer, this.lockRarityButtonContainer, this.checkButtonContainer ], alpha: 1, duration: 250 }); @@ -266,7 +283,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } else { switch (button) { case Button.UP: - if (!this.rowCursor && this.cursor === 2) { + if (this.rowCursor === 0 && this.cursor === 3) { success = this.setCursor(0); } else if (this.rowCursor < this.shopOptionsRows.length + 1) { success = this.setRowCursor(this.rowCursor + 1); @@ -275,13 +292,29 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { case Button.DOWN: if (this.rowCursor) { success = this.setRowCursor(this.rowCursor - 1); - } else if (this.lockRarityButtonContainer.visible && !this.cursor) { - success = this.setCursor(2); + } else if (this.lockRarityButtonContainer.visible && this.cursor === 0) { + success = this.setCursor(3); } break; case Button.LEFT: if (!this.rowCursor) { - success = this.cursor === 1 && this.rerollButtonContainer.visible && this.setCursor(0); + switch (this.cursor) { + case 0: + success = false; + break; + case 1: + success = this.rerollButtonContainer.visible && this.setCursor(0); + break; + case 2: + if (this.transferButtonContainer.visible) { + success = this.setCursor(1); + } else if (this.rerollButtonContainer.visible) { + success = this.setCursor(0); + } else { + success = false; + } + break; + } } else if (this.cursor) { success = this.setCursor(this.cursor - 1); } else if (this.rowCursor === 1 && this.rerollButtonContainer.visible) { @@ -290,7 +323,21 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { break; case Button.RIGHT: if (!this.rowCursor) { - success = this.cursor !== 1 && this.transferButtonContainer.visible && this.setCursor(1); + switch (this.cursor) { + case 0: + if (this.transferButtonContainer.visible) { + success = this.setCursor(1); + } else { + success = this.setCursor(2); + } + break; + case 1: + success = this.setCursor(2); + break; + case 2: + success = false; + break; + } } else if (this.cursor < this.getRowItems(this.rowCursor) - 1) { success = this.setCursor(this.cursor + 1); } else if (this.rowCursor === 1 && this.transferButtonContainer.visible) { @@ -336,12 +383,15 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { // prepare the move overlay to be shown with the toggle this.moveInfoOverlay.show(allMoves[type.moveId]); } - } else if (!cursor) { + } else if (cursor === 0) { this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? -72 : -60); ui.showText("Spend money to reroll your item options."); } else if (cursor === 1) { - this.cursorObj.setPosition((this.scene.game.canvas.width / 6) - 50, -60); + this.cursorObj.setPosition((this.scene.game.canvas.width / 6) - 120, -60); ui.showText("Transfer a held item from one Pokémon to another."); + } else if (cursor === 2) { + this.cursorObj.setPosition((this.scene.game.canvas.width / 6) - 60, -60); + ui.showText("Check your team or use a form changing item."); } else { this.cursorObj.setPosition(6, -60); ui.showText("Lock item rarities on reroll (affects reroll cost)."); @@ -353,14 +403,15 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { setRowCursor(rowCursor: integer): boolean { const lastRowCursor = this.rowCursor; - if (rowCursor !== lastRowCursor && (rowCursor || this.rerollButtonContainer.visible || this.transferButtonContainer.visible)) { + if (rowCursor !== lastRowCursor) { this.rowCursor = rowCursor; let newCursor = Math.round(this.cursor / Math.max(this.getRowItems(lastRowCursor) - 1, 1) * (this.getRowItems(rowCursor) - 1)); - if (!rowCursor) { - if (!newCursor && !this.rerollButtonContainer.visible) { + if (rowCursor === 0) { + if (newCursor === 0 && !this.rerollButtonContainer.visible) { newCursor = 1; - } else if (newCursor && !this.transferButtonContainer.visible) { - newCursor = 0; + } + if (newCursor === 1 && !this.transferButtonContainer.visible) { + newCursor = 2; } } this.cursor = -1; @@ -374,7 +425,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { private getRowItems(rowCursor: integer): integer { switch (rowCursor) { case 0: - return 2; + return 3; case 1: return this.options.length; default: @@ -398,7 +449,9 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { updateRerollCostText(): void { const canReroll = this.scene.money >= this.rerollCost; - this.rerollCostText.setText(`₽${this.rerollCost.toLocaleString("en-US")}`); + const formattedMoney = Utils.formatMoney(this.scene.moneyFormat, this.rerollCost); + + this.rerollCostText.setText(`₽${formattedMoney}`); this.rerollCostText.setColor(this.getTextColor(canReroll ? TextStyle.MONEY : TextStyle.PARTY_RED)); this.rerollCostText.setShadowColor(this.getTextColor(canReroll ? TextStyle.MONEY : TextStyle.PARTY_RED, true)); } @@ -434,7 +487,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { onComplete: () => options.forEach(o => o.destroy()) }); - [ this.rerollButtonContainer, this.transferButtonContainer, this.lockRarityButtonContainer ].forEach(container => { + [ this.rerollButtonContainer, this.checkButtonContainer, this.transferButtonContainer, this.lockRarityButtonContainer ].forEach(container => { if (container.visible) { this.scene.tweens.add({ targets: container, @@ -656,7 +709,9 @@ class ModifierOption extends Phaser.GameObjects.Container { const scene = this.scene as BattleScene; const textStyle = this.modifierTypeOption.cost <= scene.money ? TextStyle.MONEY : TextStyle.PARTY_RED; - this.itemCostText.setText(`₽${this.modifierTypeOption.cost.toLocaleString("en-US")}`); + const formattedMoney = Utils.formatMoney(scene.moneyFormat, this.modifierTypeOption.cost); + + this.itemCostText.setText(`₽${formattedMoney}`); this.itemCostText.setColor(getTextColor(textStyle, false, scene.uiTheme)); this.itemCostText.setShadowColor(getTextColor(textStyle, true, scene.uiTheme)); } diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 425a9dd788d..9192ffc47bc 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1,4 +1,4 @@ -import { CommandPhase } from "../phases"; +import { CommandPhase, SelectModifierPhase } from "../phases"; import BattleScene from "../battle-scene"; import { PlayerPokemon, PokemonMove } from "../field/pokemon"; import { addTextObject, TextStyle } from "./text"; @@ -17,6 +17,7 @@ import { addWindow } from "./ui-theme"; import { SpeciesFormChangeItemTrigger } from "../data/pokemon-forms"; import { getVariantTint } from "#app/data/variant"; import {Button} from "../enums/buttons"; +import { applyChallenges, ChallengeType } from "#app/data/challenge.js"; import MoveInfoOverlay from "./move-info-overlay"; import i18next from "i18next"; @@ -33,7 +34,8 @@ export enum PartyUiMode { REMEMBER_MOVE_MODIFIER, MODIFIER_TRANSFER, SPLICE, - RELEASE + RELEASE, + CHECK } export enum PartyOption { @@ -120,6 +122,20 @@ export default class PartyUiHandler extends MessageUiHandler { return null; }; + /** + * For consistency reasons, this looks like the above filters. However this is used only internally and is always enforced for switching. + * @param pokemon The pokemon to check. + * @returns + */ + private FilterChallengeLegal = (pokemon: PlayerPokemon) => { + const challengeAllowed = new Utils.BooleanHolder(true); + applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, pokemon, challengeAllowed); + if (!challengeAllowed.value) { + return `${pokemon.name} can't be used in\nthis challenge!`; + } + return null; + }; + private static FilterAllMoves = (_pokemonMove: PokemonMove) => null; public static FilterItemMaxStacks = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => { @@ -280,6 +296,9 @@ export default class PartyUiHandler extends MessageUiHandler { let filterResult: string; if (option !== PartyOption.TRANSFER && option !== PartyOption.SPLICE) { filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon); + if (filterResult === null && (option === PartyOption.SEND_OUT || option === PartyOption.PASS_BATON)) { + filterResult = this.FilterChallengeLegal(pokemon); + } if (filterResult === null && this.partyUiMode === PartyUiMode.MOVE_MODIFIER) { filterResult = this.moveSelectFilter(pokemon.moveset[this.optionsCursor]); } @@ -293,7 +312,7 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.partyUiMode !== PartyUiMode.SPLICE) { this.clearOptions(); } - if (this.selectCallback) { + if (this.selectCallback && this.partyUiMode !== PartyUiMode.CHECK) { if (option === PartyOption.TRANSFER) { if (this.transferCursor !== this.cursor) { (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, this.transferOptionCursor, this.transferQuantities[this.transferOptionCursor], this.cursor); @@ -315,11 +334,8 @@ export default class PartyUiHandler extends MessageUiHandler { selectCallback(this.cursor, option); } } else { - if (option >= PartyOption.FORM_CHANGE_ITEM && this.scene.getCurrentPhase() instanceof CommandPhase) { - switch (this.partyUiMode) { - case PartyUiMode.SWITCH: - case PartyUiMode.FAINT_SWITCH: - case PartyUiMode.POST_BATTLE_SWITCH: + if (option >= PartyOption.FORM_CHANGE_ITEM && this.scene.getCurrentPhase() instanceof SelectModifierPhase) { + if (this.partyUiMode === PartyUiMode.CHECK) { let formChangeItemModifiers = this.scene.findModifiers(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id) as PokemonFormChangeItemModifier[]; if (formChangeItemModifiers.find(m => m.active)) { formChangeItemModifiers = formChangeItemModifiers.filter(m => m.active); @@ -327,7 +343,6 @@ export default class PartyUiHandler extends MessageUiHandler { const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; modifier.active = !modifier.active; this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger, false, true); - break; } } else if (this.cursor) { (this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.POKEMON, this.cursor, option === PartyOption.PASS_BATON); @@ -690,15 +705,6 @@ export default class PartyUiHandler extends MessageUiHandler { this.options.push(PartyOption.PASS_BATON); } } - if (this.scene.getCurrentPhase() instanceof CommandPhase) { - formChangeItemModifiers = this.scene.findModifiers(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id) as PokemonFormChangeItemModifier[]; - if (formChangeItemModifiers.find(m => m.active)) { - formChangeItemModifiers = formChangeItemModifiers.filter(m => m.active); - } - for (let i = 0; i < formChangeItemModifiers.length; i++) { - this.options.push(PartyOption.FORM_CHANGE_ITEM + i); - } - } break; case PartyUiMode.REVIVAL_BLESSING: this.options.push(PartyOption.REVIVE); @@ -724,6 +730,17 @@ export default class PartyUiHandler extends MessageUiHandler { case PartyUiMode.RELEASE: this.options.push(PartyOption.RELEASE); break; + case PartyUiMode.CHECK: + if (this.scene.getCurrentPhase() instanceof SelectModifierPhase) { + formChangeItemModifiers = this.scene.findModifiers(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id) as PokemonFormChangeItemModifier[]; + if (formChangeItemModifiers.find(m => m.active)) { + formChangeItemModifiers = formChangeItemModifiers.filter(m => m.active); + } + for (let i = 0; i < formChangeItemModifiers.length; i++) { + this.options.push(PartyOption.FORM_CHANGE_ITEM + i); + } + } + break; } this.options.push(PartyOption.SUMMARY); diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index d5dda05a519..e5417f83596 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -1,15 +1,15 @@ +import i18next from "i18next"; import BattleScene from "../battle-scene"; -import { gameModes } from "../game-mode"; +import { Button } from "../enums/buttons"; +import { GameMode } from "../game-mode"; +import { PokemonHeldItemModifier } from "../modifier/modifier"; import { SessionSaveData } from "../system/game-data"; +import PokemonData from "../system/pokemon-data"; +import * as Utils from "../utils"; +import MessageUiHandler from "./message-ui-handler"; import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; -import * as Utils from "../utils"; -import PokemonData from "../system/pokemon-data"; -import { PokemonHeldItemModifier } from "../modifier/modifier"; -import MessageUiHandler from "./message-ui-handler"; -import i18next from "i18next"; -import {Button} from "../enums/buttons"; const sessionSlotCount = 5; @@ -118,7 +118,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { originalCallback(cursor); }; if (this.sessionSlots[cursor].hasData) { - ui.showText("Overwrite the data in the selected slot?", null, () => { + ui.showText(i18next.t("saveSlotSelectUiHandler:overwriteData"), null, () => { ui.setOverlayMode(Mode.CONFIRM, () => saveAndCallback(), () => { ui.revertMode(); ui.showText(null, 0); @@ -258,7 +258,7 @@ class SessionSlot extends Phaser.GameObjects.Container { const slotWindow = addWindow(this.scene, 0, 0, 304, 52); this.add(slotWindow); - this.loadingLabel = addTextObject(this.scene, 152, 26, "Loading…", TextStyle.WINDOW); + this.loadingLabel = addTextObject(this.scene, 152, 26, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW); this.loadingLabel.setOrigin(0.5, 0.5); this.add(this.loadingLabel); } @@ -266,7 +266,7 @@ class SessionSlot extends Phaser.GameObjects.Container { async setupWithData(data: SessionSaveData) { this.remove(this.loadingLabel, true); - const gameModeLabel = addTextObject(this.scene, 8, 5, `${gameModes[data.gameMode]?.getName() || "Unknown"} - Wave ${data.waveIndex}`, TextStyle.WINDOW); + const gameModeLabel = addTextObject(this.scene, 8, 5, `${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unkown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}`, TextStyle.WINDOW); this.add(gameModeLabel); const timestampLabel = addTextObject(this.scene, 8, 19, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW); @@ -283,7 +283,7 @@ class SessionSlot extends Phaser.GameObjects.Container { const pokemon = p.toPokemon(this.scene); const icon = this.scene.addPokemonIcon(pokemon, 0, 0, 0, 0); - const text = addTextObject(this.scene, 32, 20, `Lv${Utils.formatLargeNumber(pokemon.level, 1000)}`, TextStyle.PARTY, { fontSize: "54px", color: "#f8f8f8" }); + const text = addTextObject(this.scene, 32, 20, `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(pokemon.level, 1000)}`, TextStyle.PARTY, { fontSize: "54px", color: "#f8f8f8" }); text.setShadow(0, 0, null); text.setStroke("#424242", 14); text.setOrigin(1, 0); @@ -324,7 +324,7 @@ class SessionSlot extends Phaser.GameObjects.Container { this.scene.gameData.getSession(this.slotId).then(async sessionData => { if (!sessionData) { this.hasData = false; - this.loadingLabel.setText(i18next.t("menu:empty")); + this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty")); resolve(false); return; } diff --git a/src/ui/settings/settings-display-ui-handler.ts b/src/ui/settings/settings-display-ui-handler.ts index 9e61c2ba0b2..0aed0689388 100644 --- a/src/ui/settings/settings-display-ui-handler.ts +++ b/src/ui/settings/settings-display-ui-handler.ts @@ -2,7 +2,7 @@ import BattleScene from "../../battle-scene"; import { Mode } from "../ui"; "#app/inputs-controller.js"; import AbstractSettingsUiHandler from "./abstract-settings-ui-handler"; -import { Setting, SettingType } from "#app/system/settings/settings"; +import { Setting, SettingKeys, SettingType } from "#app/system/settings/settings"; export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler { /** @@ -15,6 +15,48 @@ export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler super(scene, mode); this.title = "Display"; this.settings = Setting.filter(s => s.type === SettingType.DISPLAY); + + /** + * Update to current language from default value. + * - default value is 'English' + */ + const languageIndex = this.settings.findIndex(s => s.key === SettingKeys.Language); + if (languageIndex >= 0) { + const currentLocale = localStorage.getItem("prLang"); + switch (currentLocale) { + case "en": + this.settings[languageIndex].options[0] = "English"; + break; + case "es": + this.settings[languageIndex].options[0] = "Español"; + break; + case "it": + this.settings[languageIndex].options[0] = "Italiano"; + break; + case "fr": + this.settings[languageIndex].options[0] = "Français"; + break; + case "de": + this.settings[languageIndex].options[0] = "Deutsch"; + break; + case "pt-BR": + this.settings[languageIndex].options[0] = "Português (BR)"; + break; + case "zh-CN": + this.settings[languageIndex].options[0] = "简体中文"; + break; + case "zh-TW": + this.settings[languageIndex].options[0] = "繁體中文"; + break; + case "ko": + this.settings[languageIndex].options[0] = "한국어"; + break; + default: + this.settings[languageIndex].options[0] = "English"; + break; + } + } + this.localStorageKey = "settings"; } } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 16406fad675..71b61b99e9c 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -18,8 +18,8 @@ import { LevelMoves, pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "../ import PokemonSpecies, { allSpecies, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; import { Type } from "../data/type"; import { Button } from "../enums/buttons"; -import { GameModes, gameModes } from "../game-mode"; -import { TitlePhase } from "../phases"; +import { GameModes } from "../game-mode"; +import { SelectChallengePhase, TitlePhase } from "../phases"; import { AbilityAttr, DexAttr, DexAttrProps, DexEntry, StarterFormMoveData, StarterMoveset } from "../system/game-data"; import { Passive as PassiveAttr } from "#app/data/enums/passive"; import { Tutorial, handleTutorial } from "../tutorial"; @@ -31,6 +31,9 @@ import { StatsContainer } from "./stats-container"; import { TextStyle, addBBCodeTextObject, addTextObject } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; +import {SettingKeyboard} from "#app/system/settings/settings-keyboard"; +import {Device} from "#app/enums/devices"; +import * as Challenge from "../data/challenge"; import MoveInfoOverlay from "./move-info-overlay"; export type StarterSelectCallback = (starters: Starter[]) => void; @@ -55,7 +58,7 @@ interface LanguageSetting { const languageSettings: { [key: string]: LanguageSetting } = { "en":{ starterInfoTextSize: "56px", - instructionTextSize: "42px", + instructionTextSize: "38px", }, "de":{ starterInfoTextSize: "56px", @@ -188,6 +191,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private pokemonCaughtCountText: Phaser.GameObjects.Text; private pokemonHatchedCountText: Phaser.GameObjects.Text; private genOptionsText: Phaser.GameObjects.Text; + private instructionsContainer: Phaser.GameObjects.Container; private instructionsText: Phaser.GameObjects.Text; private starterSelectMessageBox: Phaser.GameObjects.NineSlice; private starterSelectMessageBoxContainer: Phaser.GameObjects.Container; @@ -244,8 +248,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private iconAnimHandler: PokemonIconAnimHandler; + //variables to keep track of the dynamically rendered list of instruction prompts for starter select + private instructionRowX = 0; + private instructionRowY = 0; + private instructionRowTextOffset = 12; + private starterSelectCallback: StarterSelectCallback; - private gameMode: GameModes; protected blockInput: boolean = false; @@ -646,10 +654,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterSelectContainer.add(this.pokemonEggMovesContainer); // The font size should be set per language - const instructionTextSize = textSettings.instructionTextSize; - - this.instructionsText = addTextObject(this.scene, 4, 156, "", TextStyle.PARTY, { fontSize: instructionTextSize }); - this.starterSelectContainer.add(this.instructionsText); + this.instructionsContainer = this.scene.add.container(4, 156); + this.instructionsContainer.setVisible(true); + this.starterSelectContainer.add(this.instructionsContainer); this.starterSelectMessageBoxContainer = this.scene.add.container(0, this.scene.game.canvas.height / 6); this.starterSelectMessageBoxContainer.setVisible(false); @@ -724,14 +731,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { show(args: any[]): boolean { this.moveInfoOverlay.clear(); // clear this when removing a menu; the cancel button doesn't seem to trigger this automatically on controllers - if (args.length >= 2 && args[0] instanceof Function && typeof args[1] === "number") { + if (args.length >= 1 && args[0] instanceof Function) { super.show(args); this.starterSelectCallback = args[0] as StarterSelectCallback; this.starterSelectContainer.setVisible(true); - this.gameMode = args[1]; - this.setGenMode(false); this.setCursor(0); this.setGenMode(true); @@ -957,7 +962,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } else { this.blockInput = true; this.scene.clearPhaseQueue(); - this.scene.pushPhase(new TitlePhase(this.scene)); + if (this.scene.gameMode.isChallenge) { + this.scene.pushPhase(new SelectChallengePhase(this.scene)); + } else { + this.scene.pushPhase(new TitlePhase(this.scene)); + } this.scene.getCurrentPhase().end(); success = true; } @@ -1029,7 +1038,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } const species = this.genSpecies[this.getGenCursorWithScroll()][this.cursor]; - if (!isDupe && this.tryUpdateValue(this.scene.gameData.getSpeciesStarterValue(species.speciesId))) { + + const isValidForChallenge = new Utils.BooleanHolder(true); + Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge); + + if (!isDupe && isValidForChallenge.value && this.tryUpdateValue(this.scene.gameData.getSpeciesStarterValue(species.speciesId))) { const cursorObj = this.starterCursorObjs[this.starterCursors.length]; cursorObj.setVisible(true); cursorObj.setPosition(this.cursorObj.x, this.cursorObj.y); @@ -1479,55 +1492,100 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, undefined, false); } + createButtonFromIconText(iconSetting, gamepadType, translatedText, instructionTextSize): void { + let iconPath; + // touch controls cannot be rebound as is, and are just emulating a keyboard event. + // Additionally, since keyboard controls can be rebound (and will be displayed when they are), we need to have special handling for the touch controls + if (gamepadType === "touch") { + gamepadType = "keyboard"; + switch (iconSetting) { + case SettingKeyboard.Button_Cycle_Shiny: + iconPath = "R.png"; + break; + case SettingKeyboard.Button_Cycle_Form: + iconPath = "F.png"; + break; + case SettingKeyboard.Button_Cycle_Gender: + iconPath = "G.png"; + break; + case SettingKeyboard.Button_Cycle_Ability: + iconPath = "E.png"; + break; + case SettingKeyboard.Button_Cycle_Nature: + iconPath = "N.png"; + break; + case SettingKeyboard.Button_Cycle_Variant: + iconPath = "V.png"; + break; + default: + break; + } + } else { + iconPath = this.scene.inputController?.getIconForLatestInputRecorded(iconSetting); + } + const iconElement = this.scene.add.sprite(this.instructionRowX, this.instructionRowY, gamepadType, iconPath); + iconElement.setScale(0.675); + iconElement.setOrigin(0.0, 0.0); + const controlLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, translatedText, TextStyle.PARTY, { fontSize: instructionTextSize }); + this.instructionsContainer.add([iconElement, controlLabel]); + this.instructionRowY += 8; + if (this.instructionRowY >= 24) { + this.instructionRowY = 0; + this.instructionRowX += 50; + } + } + updateInstructions(): void { - const instructionLines = [ ]; - const cycleInstructionLines = []; + const currentLanguage = i18next.resolvedLanguage; + const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)); + const textSettings = languageSettings[langSettingKey]; + const instructionTextSize = textSettings.instructionTextSize; + this.instructionRowX = 0; + this.instructionRowY = 0; + this.instructionsContainer.removeAll(); + let gamepadType; + if (this.scene.inputMethod === "gamepad") { + gamepadType = this.scene.inputController.getConfig(this.scene.inputController.selectedDevice[Device.GAMEPAD]).padType; + } else { + gamepadType = this.scene.inputMethod; + } + if (this.speciesStarterDexEntry?.caughtAttr) { if (this.canCycleShiny) { - cycleInstructionLines.push(i18next.t("starterSelectUiHandler:cycleShiny")); + this.createButtonFromIconText(SettingKeyboard.Button_Cycle_Shiny, gamepadType, i18next.t("starterSelectUiHandler:cycleShiny"), instructionTextSize); } if (this.canCycleForm) { - cycleInstructionLines.push(i18next.t("starterSelectUiHandler:cycleForm")); + this.createButtonFromIconText(SettingKeyboard.Button_Cycle_Form, gamepadType, i18next.t("starterSelectUiHandler:cycleForm"), instructionTextSize); } if (this.canCycleGender) { - cycleInstructionLines.push(i18next.t("starterSelectUiHandler:cycleGender")); + this.createButtonFromIconText(SettingKeyboard.Button_Cycle_Gender, gamepadType, i18next.t("starterSelectUiHandler:cycleGender"), instructionTextSize); } if (this.canCycleAbility) { - cycleInstructionLines.push(i18next.t("starterSelectUiHandler:cycleAbility")); + this.createButtonFromIconText(SettingKeyboard.Button_Cycle_Ability, gamepadType, i18next.t("starterSelectUiHandler:cycleAbility"), instructionTextSize); } if (this.canCycleNature) { - cycleInstructionLines.push(i18next.t("starterSelectUiHandler:cycleNature")); + this.createButtonFromIconText(SettingKeyboard.Button_Cycle_Nature, gamepadType, i18next.t("starterSelectUiHandler:cycleNature"), instructionTextSize); } if (this.canCycleVariant) { - cycleInstructionLines.push(i18next.t("starterSelectUiHandler:cycleVariant")); + this.createButtonFromIconText(SettingKeyboard.Button_Cycle_Variant, gamepadType, i18next.t("starterSelectUiHandler:cycleVariant"), instructionTextSize); } } - - if (cycleInstructionLines.length > 2) { - cycleInstructionLines[0] += " | " + cycleInstructionLines.splice(1, 1); - if (cycleInstructionLines.length > 2) { - cycleInstructionLines[1] += " | " + cycleInstructionLines.splice(2, 1); - } - if (cycleInstructionLines.length > 2) { - cycleInstructionLines[2] += " | " + cycleInstructionLines.splice(3, 1); - } - } - - for (const cil of cycleInstructionLines) { - instructionLines.push(cil); - } - - this.instructionsText.setText(instructionLines.join("\n")); } getValueLimit(): integer { - switch (this.gameMode) { + const valueLimit = new Utils.IntegerHolder(0); + switch (this.scene.gameMode.modeId) { case GameModes.ENDLESS: case GameModes.SPLICED_ENDLESS: - return 15; + valueLimit.value = 15; + break; default: - return 10; + valueLimit.value = 10; } + + Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_POINTS, valueLimit); + + return valueLimit.value; } setCursor(cursor: integer): boolean { @@ -2160,20 +2218,25 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const speciesSprite = this.starterSelectGenIconContainers[g].getAt(s) as Phaser.GameObjects.Sprite; /** - * If remainValue greater than or equal pokemon species, the user can select. + * If remainValue greater than or equal pokemon species and the pokemon is legal for this challenge, the user can select. * so that the alpha value of pokemon sprite set 1. * * If speciesStarterDexEntry?.caughtAttr is true, this species registered in stater. * we change to can AddParty value to true since the user has enough cost to choose this pokemon and this pokemon registered too. */ - if (remainValue >= speciesStarterValue) { + const isValidForChallenge = new Utils.BooleanHolder(true); + Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.genSpecies[g][s], isValidForChallenge); + + const canBeChosen = remainValue >= speciesStarterValue && isValidForChallenge.value; + + if (canBeChosen) { speciesSprite.setAlpha(1); if (speciesStarterDexEntry?.caughtAttr) { this.canAddParty = true; } } else { /** - * If remainValue less than pokemon, the use can't select. + * If it can't be chosen, the user can't select. * so that the alpha value of pokemon sprite set 0.375. */ speciesSprite.setAlpha(0.375); @@ -2202,8 +2265,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ui.showText(i18next.t("starterSelectUiHandler:confirmStartTeam"), null, () => { ui.setModeWithoutClear(Mode.CONFIRM, () => { - const startRun = (gameMode: GameModes) => { - this.scene.gameMode = gameModes[gameMode]; + const startRun = () => { this.scene.money = this.scene.gameMode.getStartingMoney(); ui.setMode(Mode.STARTER_SELECT); const thisObj = this; @@ -2222,7 +2284,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { }; })); }; - startRun(this.gameMode); + startRun(); }, cancel, null, null, 19); }); diff --git a/src/ui/ui-handler.ts b/src/ui/ui-handler.ts index 74015ed7245..e74853276ea 100644 --- a/src/ui/ui-handler.ts +++ b/src/ui/ui-handler.ts @@ -3,12 +3,19 @@ import { TextStyle, getTextColor } from "./text"; import { Mode } from "./ui"; import {Button} from "../enums/buttons"; +/** + * A basic abstract class to act as a holder and processor for UI elements. + */ export default abstract class UiHandler { protected scene: BattleScene; protected mode: integer; protected cursor: integer = 0; public active: boolean = false; + /** + * @param {BattleScene} scene The same scene as everything else. + * @param {Mode} mode The mode of the UI element. These should be unique. + */ constructor(scene: BattleScene, mode: Mode) { this.scene = scene; this.mode = mode; diff --git a/src/ui/ui.ts b/src/ui/ui.ts index aaf764f501f..366be949374 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -14,6 +14,7 @@ import EvolutionSceneHandler from "./evolution-scene-handler"; import TargetSelectUiHandler from "./target-select-ui-handler"; import SettingsUiHandler from "./settings/settings-ui-handler"; import SettingsGamepadUiHandler from "./settings/settings-gamepad-ui-handler"; +import GameChallengesUiHandler from "./challenges-select-ui-handler"; import { TextStyle, addTextObject } from "./text"; import AchvBar from "./achv-bar"; import MenuUiHandler from "./menu-ui-handler"; @@ -80,7 +81,8 @@ export enum Mode { LOADING, SESSION_RELOAD, UNAVAILABLE, - OUTDATED + OUTDATED, + CHALLENGE_SELECT } const transitionModes = [ @@ -91,7 +93,8 @@ const transitionModes = [ Mode.EVOLUTION_SCENE, Mode.EGG_HATCH_SCENE, Mode.EGG_LIST, - Mode.EGG_GACHA + Mode.EGG_GACHA, + Mode.CHALLENGE_SELECT ]; const noTransitionModes = [ @@ -173,7 +176,8 @@ export default class UI extends Phaser.GameObjects.Container { new LoadingModalUiHandler(scene), new SessionReloadModalUiHandler(scene), new UnavailableModalUiHandler(scene), - new OutdatedModalUiHandler(scene) + new OutdatedModalUiHandler(scene), + new GameChallengesUiHandler(scene) ]; } diff --git a/src/utils.ts b/src/utils.ts index 7b25c4990e1..6f2c9cd0c65 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,5 @@ import i18next from "i18next"; +import { MoneyFormat } from "./enums/money-format"; export const MissingTextureKey = "__MISSING"; @@ -237,7 +238,7 @@ export function formatLargeNumber(count: integer, threshold: integer): string { // Abbreviations from 10^0 to 10^33 const AbbreviationsLargeNumber: string[] = ["", "K", "M", "B", "t", "q", "Q", "s", "S", "o", "n", "d"]; -export function formatFancyLargeNumber(number: number, rounded: number = 2): string { +export function formatFancyLargeNumber(number: number, rounded: number = 3): string { let exponent: number; if (number < 1000) { @@ -251,7 +252,14 @@ export function formatFancyLargeNumber(number: number, rounded: number = 2): str number /= Math.pow(1000, exponent); } - return `${(exponent === 0) ? number : number.toFixed(rounded)}${AbbreviationsLargeNumber[exponent]}`; + return `${(exponent === 0) || number % 1 === 0 ? number : number.toFixed(rounded)}${AbbreviationsLargeNumber[exponent]}`; +} + +export function formatMoney(format: MoneyFormat, amount: number) { + if (format === MoneyFormat.ABBREVIATED) { + return formatFancyLargeNumber(amount); + } + return amount.toLocaleString(); } export function formatStat(stat: integer, forHp: boolean = false): string { @@ -283,12 +291,12 @@ export const localServerUrl = import.meta.env.VITE_SERVER_URL ?? `http://${windo export const serverUrl = isLocal ? localServerUrl : ""; export const apiUrl = isLocal ? serverUrl : "https://api.pokerogue.net"; // used to disable api calls when isLocal is true and a server is not found -export let isLocalServerConnected = false; +export let isLocalServerConnected = true; export function setCookie(cName: string, cValue: string): void { const expiration = new Date(); expiration.setTime(new Date().getTime() + 3600000 * 24 * 30 * 3/*7*/); - document.cookie = `${cName}=${cValue};SameSite=Strict;path=/;expires=${expiration.toUTCString()}`; + document.cookie = `${cName}=${cValue};Secure;SameSite=Strict;Path=/;Expires=${expiration.toUTCString()}`; } export function getCookie(cName: string): string {