From 39a1963941332b77056b928711a2f5e414b3646f Mon Sep 17 00:00:00 2001 From: Lugiad Date: Sat, 14 Sep 2024 18:33:41 +0200 Subject: [PATCH 1/3] [Localization] French Translations tiny settings batch (#4250) * Update settings.json * Update move.json --- src/locales/fr/move.json | 4 ++-- src/locales/fr/settings.json | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/locales/fr/move.json b/src/locales/fr/move.json index a48e17b3fd9..957895b5db9 100644 --- a/src/locales/fr/move.json +++ b/src/locales/fr/move.json @@ -364,8 +364,8 @@ "effect": "Le lanceur creuse au premier tour et frappe au second." }, "toxic": { - "name": "Fil Toxique", - "effect": "Tisse un fil imprégné de venin. Empoisonne la cible et baisse sa Vitesse." + "name": "Toxik", + "effect": "Le lanceur empoisonne gravement la cible. Les dégâts dus au poison augmentent à chaque tour." }, "confusion": { "name": "Choc Mental", diff --git a/src/locales/fr/settings.json b/src/locales/fr/settings.json index c752b336b6e..e310f5d5733 100644 --- a/src/locales/fr/settings.json +++ b/src/locales/fr/settings.json @@ -11,6 +11,10 @@ "expGainsSpeed": "Vit. barre d’Exp", "expPartyDisplay": "Afficher Exp équipe", "skipSeenDialogues": "Passer dialogues connus", + "eggSkip": "Animation d’éclosion", + "never": "Jamais", + "always": "Toujours", + "ask": "Demander", "battleStyle": "Style de combat", "enableRetries": "Activer les réessais", "hideIvs": "Masquer Scanner d’IV", From bdc7c95c1cfc0e9a3177cfadde2c1170e92f0268 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Sat, 14 Sep 2024 09:35:36 -0700 Subject: [PATCH 2/3] [Misc][AI] Fix KO filter not accounting for move conditions (#4245) * Only filter KO moves that won't fail * Add Last Resort enemy command test --- src/field/pokemon.ts | 5 ++++- src/test/enemy_command.test.ts | 41 +++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 43a7090bff5..faae3b06ba0 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4410,7 +4410,10 @@ export class EnemyPokemon extends Pokemon { const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT); return move.category !== MoveCategory.STATUS - && moveTargets.some(p => p.getAttackDamage(this, move, !p.battleData.abilityRevealed, false, isCritical).damage >= p.hp); + && moveTargets.some(p => { + const doesNotFail = move.applyConditions(this, p, move) || [Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP].includes(move.id); + return doesNotFail && p.getAttackDamage(this, move, !p.battleData.abilityRevealed, false, isCritical).damage >= p.hp; + }); }, this); if (koMoves.length > 0) { diff --git a/src/test/enemy_command.test.ts b/src/test/enemy_command.test.ts index 39b77845ea9..9a2caa56dfc 100644 --- a/src/test/enemy_command.test.ts +++ b/src/test/enemy_command.test.ts @@ -6,7 +6,7 @@ import { AiType, EnemyPokemon } from "#app/field/pokemon"; import { randSeedInt } from "#app/utils"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const TIMEOUT = 20 * 1000; const NUM_TRIALS = 300; @@ -36,22 +36,26 @@ describe("Enemy Commands - Move Selection", () => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, }); - game = new GameManager(phaserGame); - game.override.ability(Abilities.BALL_FETCH); }); afterEach(() => { game.phaseInterceptor.restoreOg(); }); + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override + .ability(Abilities.BALL_FETCH) + .enemyAbility(Abilities.BALL_FETCH); + }); + it( "should never use Status moves if an attack can KO", async () => { game.override .enemySpecies(Species.ETERNATUS) .enemyMoveset([Moves.ETERNABEAM, Moves.SLUDGE_BOMB, Moves.DRAGON_DANCE, Moves.COSMIC_POWER]) - .enemyAbility(Abilities.BALL_FETCH) - .ability(Abilities.BALL_FETCH) .startingLevel(1) .enemyLevel(100); @@ -72,4 +76,31 @@ describe("Enemy Commands - Move Selection", () => { }); }, TIMEOUT ); + + it( + "should not select Last Resort if it would fail, even if the move KOs otherwise", + async () => { + game.override + .enemySpecies(Species.KANGASKHAN) + .enemyMoveset([Moves.LAST_RESORT, Moves.GIGA_IMPACT, Moves.SPLASH, Moves.SWORDS_DANCE]) + .startingLevel(1) + .enemyLevel(100); + + await game.classicMode.startBattle([Species.MAGIKARP]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + enemyPokemon.aiType = AiType.SMART_RANDOM; + + const moveChoices: MoveChoiceSet = {}; + const enemyMoveset = enemyPokemon.getMoveset(); + enemyMoveset.forEach(mv => moveChoices[mv!.moveId] = 0); + getEnemyMoveChoices(enemyPokemon, moveChoices); + + enemyMoveset.forEach(mv => { + if (mv?.getMove().category === MoveCategory.STATUS || mv?.moveId === Moves.LAST_RESORT) { + expect(moveChoices[mv.moveId]).toBe(0); + } + }); + }, TIMEOUT + ); }); From 763c1922fbad172e6c3d2b178aae9be5679b3267 Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Sat, 14 Sep 2024 09:35:46 -0700 Subject: [PATCH 3/3] [Bug] Fix exp gains speed not applying properly (#4243) * update battle-info.ts * add: ExpGainsSpeed enum * address PR comments * add test coverage (exp gains speed) --------- Co-authored-by: Mr.WaterT Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/battle-scene.ts | 3 +- src/enums/exp-gains-speed.ts | 22 ++++++++++ src/phases/show-party-exp-bar-phase.ts | 3 +- src/test/ui/battle_info.test.ts | 55 ++++++++++++++++++++++++ src/test/utils/gameManager.ts | 3 +- src/test/utils/helpers/settingsHelper.ts | 10 +++++ src/test/utils/phaseInterceptor.ts | 4 +- src/ui/battle-info.ts | 9 +++- 8 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 src/enums/exp-gains-speed.ts create mode 100644 src/test/ui/battle_info.test.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 9a7221d3fb3..516662617f1 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -105,6 +105,7 @@ import HeldModifierConfig from "#app/interfaces/held-modifier-config"; import { ExpPhase } from "#app/phases/exp-phase"; import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; +import { ExpGainsSpeed } from "./enums/exp-gains-speed"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -180,7 +181,7 @@ export default class BattleScene extends SceneBase { public experimentalSprites: boolean = false; public musicPreference: integer = 0; public moveAnimations: boolean = true; - public expGainsSpeed: integer = 0; + public expGainsSpeed: ExpGainsSpeed = ExpGainsSpeed.DEFAULT; public skipSeenDialogues: boolean = false; /** * Determines if the egg hatching animation should be skipped diff --git a/src/enums/exp-gains-speed.ts b/src/enums/exp-gains-speed.ts new file mode 100644 index 00000000000..964c4f99c70 --- /dev/null +++ b/src/enums/exp-gains-speed.ts @@ -0,0 +1,22 @@ +/** + * Defines the speed of gaining experience. + * + * @remarks + * The `expGainSpeed` can have several modes: + * - `0` - Default: The normal speed. + * - `1` - Fast: Fast speed. + * - `2` - Faster: Faster speed. + * - `3` - Skip: Skip gaining exp animation. + * + * @default 0 - Uses the default normal speed. + */ +export enum ExpGainsSpeed { + /** The normal speed. */ + DEFAULT, + /** Fast speed. */ + FAST, + /** Faster speed. */ + FASTER, + /** Skip gaining exp animation. */ + SKIP +} diff --git a/src/phases/show-party-exp-bar-phase.ts b/src/phases/show-party-exp-bar-phase.ts index 9e019b202a5..f1783e7715f 100644 --- a/src/phases/show-party-exp-bar-phase.ts +++ b/src/phases/show-party-exp-bar-phase.ts @@ -1,4 +1,5 @@ import BattleScene from "#app/battle-scene"; +import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; import { ExpNotification } from "#app/enums/exp-notification"; import { ExpBoosterModifier } from "#app/modifier/modifier"; import * as Utils from "#app/utils"; @@ -44,7 +45,7 @@ export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { } else { this.end(); } - } else if (this.scene.expGainsSpeed < 3) { + } else if (this.scene.expGainsSpeed < ExpGainsSpeed.SKIP) { this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, false, newLevel).then(() => { setTimeout(() => this.end(), 500 / Math.pow(2, this.scene.expGainsSpeed)); }); diff --git a/src/test/ui/battle_info.test.ts b/src/test/ui/battle_info.test.ts new file mode 100644 index 00000000000..4d511b75e6f --- /dev/null +++ b/src/test/ui/battle_info.test.ts @@ -0,0 +1,55 @@ +import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; +import { Species } from "#app/enums/species"; +import { ExpPhase } from "#app/phases/exp-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("../data/exp", ({}) => { + return { + getLevelRelExp: vi.fn(() => 1), //consistent levelRelExp + }; +}); + +describe("UI - Battle Info", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([Moves.GUILLOTINE, Moves.SPLASH]) + .battleType("single") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .enemySpecies(Species.CATERPIE); + }); + + it.each([ExpGainsSpeed.FAST, ExpGainsSpeed.FASTER, ExpGainsSpeed.SKIP])( + "should increase exp gains animation by 2^%i", + async (expGainsSpeed) => { + game.settings.expGainsSpeed(expGainsSpeed); + vi.spyOn(Math, "pow"); + + await game.classicMode.startBattle([Species.CHARIZARD]); + + game.move.select(Moves.SPLASH); + await game.doKillOpponents(); + await game.phaseInterceptor.to(ExpPhase, true); + + expect(Math.pow).not.toHaveBeenCalledWith(2, expGainsSpeed); + } + ); +}); diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts index a10ed70b97e..452956ab466 100644 --- a/src/test/utils/gameManager.ts +++ b/src/test/utils/gameManager.ts @@ -54,6 +54,7 @@ import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { expect } from "vitest"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { isNullOrUndefined } from "#app/utils"; +import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; /** * Class to manage the game state and transitions between phases. @@ -148,7 +149,7 @@ export default class GameManager { this.scene.gameSpeed = 5; this.scene.moveAnimations = false; this.scene.showLevelUpStats = false; - this.scene.expGainsSpeed = 3; + this.scene.expGainsSpeed = ExpGainsSpeed.SKIP; this.scene.expParty = ExpNotification.SKIP; this.scene.hpBarSpeed = 3; this.scene.enableTutorials = false; diff --git a/src/test/utils/helpers/settingsHelper.ts b/src/test/utils/helpers/settingsHelper.ts index 8fca2a34d47..c611a705107 100644 --- a/src/test/utils/helpers/settingsHelper.ts +++ b/src/test/utils/helpers/settingsHelper.ts @@ -1,6 +1,7 @@ import { PlayerGender } from "#app/enums/player-gender"; import { BattleStyle } from "#app/enums/battle-style"; import { GameManagerHelper } from "./gameManagerHelper"; +import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; /** * Helper to handle settings for tests @@ -38,6 +39,15 @@ export class SettingsHelper extends GameManagerHelper { this.log(`Gender set to: ${PlayerGender[gender]} (=${gender})` ); } + /** + * Change the exp gains speed + * @param speed the {@linkcode ExpGainsSpeed} to set + */ + expGainsSpeed(speed: ExpGainsSpeed) { + this.game.scene.expGainsSpeed = speed; + this.log(`Exp Gains Speed set to: ${ExpGainsSpeed[speed]} (=${speed})` ); + } + private log(...params: any[]) { console.log("Settings:", ...params); } diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index cdf0ad41057..46bb757c867 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -60,6 +60,7 @@ export interface PromptHandler { expireFn?: () => void; awaitingActionInput?: boolean; } +import { ExpPhase } from "#app/phases/exp-phase"; export default class PhaseInterceptor { public scene; @@ -127,7 +128,8 @@ export default class PhaseInterceptor { [MysteryEncounterRewardsPhase, this.startPhase], [PostMysteryEncounterPhase, this.startPhase], [ModifierRewardPhase, this.startPhase], - [PartyExpPhase, this.startPhase] + [PartyExpPhase, this.startPhase], + [ExpPhase, this.startPhase], ]; private endBySetMode = [ diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index c7b82dc826e..8e7e5bc4060 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -11,8 +11,11 @@ import { Stat } from "#enums/stat"; import BattleFlyout from "./battle-flyout"; import { WindowVariant, addWindow } from "./ui-theme"; import i18next from "i18next"; +import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; export default class BattleInfo extends Phaser.GameObjects.Container { + public static readonly EXP_GAINS_DURATION_BASE = 1650; + private baseY: number; private player: boolean; @@ -702,7 +705,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container { instant = true; } const durationMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")(1 - (Math.max(this.lastLevel - 100, 0) / 150)); - const duration = this.visible && !instant ? (((levelExp - this.lastLevelExp) / relLevelExp) * 1650) * durationMultiplier * levelDurationMultiplier : 0; + let duration = this.visible && !instant ? (((levelExp - this.lastLevelExp) / relLevelExp) * BattleInfo.EXP_GAINS_DURATION_BASE) * durationMultiplier * levelDurationMultiplier : 0; + const speed = (this.scene as BattleScene).expGainsSpeed; + if (speed && speed >= ExpGainsSpeed.DEFAULT) { + duration = speed >= ExpGainsSpeed.SKIP ? ExpGainsSpeed.DEFAULT : duration / Math.pow(2, speed); + } if (ratio === 1) { this.lastLevelExp = 0; this.lastLevel++;