From a288de700d083ab1c3ac17df9a2f8776c14a1234 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sat, 26 Apr 2025 00:14:08 -0500 Subject: [PATCH 1/9] [Docs][Dev] Update linting docs (#5709) --- docs/linting.md | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/docs/linting.md b/docs/linting.md index 39b30b7a1c0..ff512740a80 100644 --- a/docs/linting.md +++ b/docs/linting.md @@ -1,40 +1,34 @@ -# ESLint +# Biome + ## Key Features 1. **Automation**: - - A pre-commit hook has been added to automatically run ESLint on the added or modified files, ensuring code quality before commits. + - A pre-commit hook has been added to automatically run Biome on the added or modified files, ensuring code quality before commits. 2. **Manual Usage**: - - If you prefer not to use the pre-commit hook, you can manually run ESLint to automatically fix issues using the command: + - If you prefer not to use the pre-commit hook, you can manually run biome to automatically fix issues using the command: + ```sh - npx eslint --fix . or npm run eslint + npx @biomejs/biome --write ``` + - Running this command will lint all files in the repository. 3. **GitHub Action**: - - A GitHub Action has been added to automatically run ESLint on every push and pull request, ensuring code quality in the CI/CD pipeline. + - A GitHub Action has been added to automatically run Biome on every push and pull request, ensuring code quality in the CI/CD pipeline. -## Summary of ESLint Rules +If you are getting linting errors from biome and want to see which files they are coming from, you can find that out by running biome in a way that is configured to only show the errors for that specific rule: ``npx @biomejs/biome lint --only=category/ruleName`` -1. **General Rules**: - - **Equality**: Use `===` and `!==` instead of `==` and `!=` (`eqeqeq`). - - **Indentation**: Enforce 2-space indentation (`indent`). - - **Quotes**: Use doublequotes for strings (`quotes`). - - **Variable Declarations**: - - Disallow `var`; use `let` or `const` (`no-var`). - - Prefer `const` for variables that are never reassigned (`prefer-const`). - - **Unused Variables**: Allow unused function parameters but enforce error for other unused variables (`@typescript-eslint/no-unused-vars`). - - **End of Line**: Ensure at least one newline at the end of files (`eol-last`). - - **Curly Braces**: Enforce the use of curly braces for all control statements (`curly`). - - **Brace Style**: Use one true brace style (`1tbs`) for TypeScript-specific syntax (`@typescript-eslint/brace-style`). +## Summary of Biome Rules -2. **TypeScript-Specific Rules**: - - **Semicolons**: - - Enforce semicolons for TypeScript-specific syntax (`@typescript-eslint/semi`). - - Disallow unnecessary semicolons (`@typescript-eslint/no-extra-semi`). +We use the [recommended ruleset](https://biomejs.dev/linter/rules/) for Biome, with some customizations to better suit our project's needs. -## Benefits +For a complete list of rules and their configurations, refer to the `biome.jsonc` file in the project root. -- **Consistency**: Ensures consistent coding style across the project. -- **Code Quality**: Helps catch potential errors and improve overall code quality. -- **Readability**: Makes the codebase easier to read and maintain. \ No newline at end of file +Some things to consider: + +- We have disabled rules that prioritize style over performance, such as `useTemplate` +- Some rules are currently marked as warnings (`warn`) to allow for gradual refactoring without blocking development. Do not write new code that triggers these warnings. +- The linter is configured to ignore specific files and folders, such as large or complex files that are pending refactors, to improve performance and focus on actionable areas. + +Formatting is also handled by Biome. You should not have to worry about manually formatting your code. From 0a48726e70469ecf091aaa0be29a705e42601f17 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 26 Apr 2025 07:24:11 +0200 Subject: [PATCH 2/9] =?UTF-8?q?[UI/UX]=20Move=20Pok=C3=A9mon=20sprite=20be?= =?UTF-8?q?low=20name=20and=20other=20text=20in=20dex=20pages=20(#5698)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moved sprite below name --- src/ui/pokedex-page-ui-handler.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index d0b85544494..4888e14b24f 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -292,6 +292,13 @@ export default class PokedexPageUiHandler extends MessageUiHandler { starterSelectBg.setOrigin(0, 0); this.starterSelectContainer.add(starterSelectBg); + this.pokemonSprite = globalScene.add.sprite(53, 63, "pkmn__sub"); + this.pokemonSprite.setPipeline(globalScene.spritePipeline, { + tone: [0.0, 0.0, 0.0, 0.0], + ignoreTimeTint: true, + }); + this.starterSelectContainer.add(this.pokemonSprite); + this.shinyOverlay = globalScene.add.image(6, 6, "summary_overlay_shiny"); this.shinyOverlay.setOrigin(0, 0); this.shinyOverlay.setVisible(false); @@ -343,13 +350,6 @@ export default class PokedexPageUiHandler extends MessageUiHandler { this.starterSelectContainer.add(starterBoxContainer); - this.pokemonSprite = globalScene.add.sprite(53, 63, "pkmn__sub"); - this.pokemonSprite.setPipeline(globalScene.spritePipeline, { - tone: [0.0, 0.0, 0.0, 0.0], - ignoreTimeTint: true, - }); - this.starterSelectContainer.add(this.pokemonSprite); - this.type1Icon = globalScene.add.sprite(8, 98, getLocalizedSpriteKey("types")); this.type1Icon.setScale(0.5); this.type1Icon.setOrigin(0, 0); From a036f865f05822576652d624dd6f0ea7cc12b4a9 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sat, 26 Apr 2025 01:17:42 -0500 Subject: [PATCH 3/9] [Bug] Fix improper critical hit key in move effect phase (#5713) --- src/phases/move-effect-phase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 01085834ba5..4b4e62db71b 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -859,7 +859,7 @@ export class MoveEffectPhase extends PokemonPhase { }); if (isCritical) { - globalScene.queueMessage(i18next.t("battle:criticalHit")); + globalScene.queueMessage(i18next.t("battle:hitResultCriticalHit")); } if (damage <= 0) { From 423bab1057945e10f1871a3fa9d6907ecbb8e0b7 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 26 Apr 2025 19:17:54 +0200 Subject: [PATCH 4/9] =?UTF-8?q?[UI/UX]=20Move=20Pok=C3=A9mon=20sprite=20be?= =?UTF-8?q?low=20name=20and=20other=20text=20in=20starter=20select=20(#571?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moving the sprite down --- src/ui/starter-select-ui-handler.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 1902c691715..7c345f1735e 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -596,6 +596,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.iconAnimHandler = new PokemonIconAnimHandler(); this.iconAnimHandler.setup(); + this.pokemonSprite = globalScene.add.sprite(53, 63, "pkmn__sub"); + this.pokemonSprite.setPipeline(globalScene.spritePipeline, { + tone: [0.0, 0.0, 0.0, 0.0], + ignoreTimeTint: true, + }); + this.starterSelectContainer.add(this.pokemonSprite); + this.pokemonNumberText = addTextObject(17, 1, "0000", TextStyle.SUMMARY); this.pokemonNumberText.setOrigin(0, 0); this.starterSelectContainer.add(this.pokemonNumberText); @@ -825,13 +832,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return icon; }); - this.pokemonSprite = globalScene.add.sprite(53, 63, "pkmn__sub"); - this.pokemonSprite.setPipeline(globalScene.spritePipeline, { - tone: [0.0, 0.0, 0.0, 0.0], - ignoreTimeTint: true, - }); - this.starterSelectContainer.add(this.pokemonSprite); - this.type1Icon = globalScene.add.sprite(8, 98, getLocalizedSpriteKey("types")); this.type1Icon.setScale(0.5); this.type1Icon.setOrigin(0, 0); From ab7d010a17f46bab0a0c9d72ce4d29fdaf8752ef Mon Sep 17 00:00:00 2001 From: Dean <69436131+emdeann@users.noreply.github.com> Date: Sat, 26 Apr 2025 13:32:49 -0700 Subject: [PATCH 5/9] [Bug] Fix message for `StatusEffectImmunityAbAttr` (#5701) Use class var for getTriggerMessage --- src/data/abilities/ability.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index b018a87a08d..9a6094f4649 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -3172,6 +3172,7 @@ export class PreSetStatusAbAttr extends AbAttr { */ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { protected immuneEffects: StatusEffect[]; + private lastEffect: StatusEffect; /** * @param immuneEffects - The status effects to which the Pokémon is immune. @@ -3197,6 +3198,7 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { */ override applyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: BooleanHolder, args: any[]): void { cancelled.value = true; + this.lastEffect = effect; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -3204,7 +3206,7 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { i18next.t("abilityTriggers:statusEffectImmunityWithName", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, - statusEffectName: getStatusEffectDescriptor(args[0] as StatusEffect) + statusEffectName: getStatusEffectDescriptor(this.lastEffect) }) : i18next.t("abilityTriggers:statusEffectImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), From 6460d46a5d01bf48df82af5c4f5e8fa29d60b57d Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 26 Apr 2025 15:01:46 -0700 Subject: [PATCH 6/9] [Bug] API / Save data hotfix (#5716) * Loading data now checks statusCode not error string * Bump version to 1.8.5 --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> --- package.json | 2 +- src/plugins/api/pokerogue-system-savedata-api.ts | 9 ++++++--- src/system/game-data.ts | 16 ++++++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 4758e6c5182..341bca80c2e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.8.4", + "version": "1.8.5", "type": "module", "scripts": { "start": "vite", diff --git a/src/plugins/api/pokerogue-system-savedata-api.ts b/src/plugins/api/pokerogue-system-savedata-api.ts index 659584776c4..d6fbb39ae0a 100644 --- a/src/plugins/api/pokerogue-system-savedata-api.ts +++ b/src/plugins/api/pokerogue-system-savedata-api.ts @@ -15,14 +15,17 @@ export class PokerogueSystemSavedataApi extends ApiBase { /** * Get a system savedata. * @param params The {@linkcode GetSystemSavedataRequest} to send - * @returns The system savedata as `string` or `null` on error + * @returns The system savedata as `string` or either the status code or `null` on error */ - public async get(params: GetSystemSavedataRequest) { + public async get(params: GetSystemSavedataRequest): Promise { try { const urlSearchParams = this.toUrlSearchParams(params); const response = await this.doGet(`/savedata/system/get?${urlSearchParams}`); const rawSavedata = await response.text(); - + if (!response.ok) { + console.warn("Could not get system savedata!", response.status, rawSavedata); + return response.status; + } return rawSavedata; } catch (err) { console.warn("Could not get system savedata!", err); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 8b7987556ee..8573c774054 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -462,8 +462,13 @@ export class GameData { if (!bypassLogin) { pokerogueApi.savedata.system.get({ clientSessionId }).then(saveDataOrErr => { - if (!saveDataOrErr || saveDataOrErr.length === 0 || saveDataOrErr[0] !== "{") { - if (saveDataOrErr?.startsWith("sql: no rows in result set")) { + if ( + typeof saveDataOrErr === "number" || + !saveDataOrErr || + saveDataOrErr.length === 0 || + saveDataOrErr[0] !== "{" + ) { + if (saveDataOrErr === 404) { globalScene.queueMessage( "Save data could not be found. If this is a new account, you can safely ignore this message.", null, @@ -471,7 +476,7 @@ export class GameData { ); return resolve(true); } - if (saveDataOrErr?.includes("Too many connections")) { + if (typeof saveDataOrErr === "string" && saveDataOrErr?.includes("Too many connections")) { globalScene.queueMessage( "Too many people are trying to connect and the server is overloaded. Please try again later.", null, @@ -479,7 +484,6 @@ export class GameData { ); return resolve(false); } - console.error(saveDataOrErr); return resolve(false); } @@ -1500,7 +1504,7 @@ export class GameData { link.remove(); }; if (!bypassLogin && dataType < GameDataType.SETTINGS) { - let promise: Promise = Promise.resolve(null); + let promise: Promise = Promise.resolve(null); if (dataType === GameDataType.SYSTEM) { promise = pokerogueApi.savedata.system.get({ clientSessionId }); @@ -1512,7 +1516,7 @@ export class GameData { } promise.then(response => { - if (!response?.length || response[0] !== "{") { + if (typeof response === "number" || !response?.length || response[0] !== "{") { console.error(response); resolve(false); return; From c6e7dd660e1482c7901396b584b121bb666a2966 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sat, 26 Apr 2025 18:04:40 -0500 Subject: [PATCH 7/9] [Bug] Hotfix 1.8.5 (#5715) * Loading data now checks statusCode not error string * Bump version to 1.8.5 --- package.json | 2 +- src/plugins/api/pokerogue-system-savedata-api.ts | 9 ++++++--- src/system/game-data.ts | 16 ++++++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 6b1c73db158..9a74fdcbb53 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.8.4", + "version": "1.8.5", "type": "module", "scripts": { "start": "vite", diff --git a/src/plugins/api/pokerogue-system-savedata-api.ts b/src/plugins/api/pokerogue-system-savedata-api.ts index 659584776c4..d6fbb39ae0a 100644 --- a/src/plugins/api/pokerogue-system-savedata-api.ts +++ b/src/plugins/api/pokerogue-system-savedata-api.ts @@ -15,14 +15,17 @@ export class PokerogueSystemSavedataApi extends ApiBase { /** * Get a system savedata. * @param params The {@linkcode GetSystemSavedataRequest} to send - * @returns The system savedata as `string` or `null` on error + * @returns The system savedata as `string` or either the status code or `null` on error */ - public async get(params: GetSystemSavedataRequest) { + public async get(params: GetSystemSavedataRequest): Promise { try { const urlSearchParams = this.toUrlSearchParams(params); const response = await this.doGet(`/savedata/system/get?${urlSearchParams}`); const rawSavedata = await response.text(); - + if (!response.ok) { + console.warn("Could not get system savedata!", response.status, rawSavedata); + return response.status; + } return rawSavedata; } catch (err) { console.warn("Could not get system savedata!", err); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 391ceec503d..998f56efbde 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -461,8 +461,13 @@ export class GameData { if (!bypassLogin) { pokerogueApi.savedata.system.get({ clientSessionId }).then(saveDataOrErr => { - if (!saveDataOrErr || saveDataOrErr.length === 0 || saveDataOrErr[0] !== "{") { - if (saveDataOrErr?.startsWith("sql: no rows in result set")) { + if ( + typeof saveDataOrErr === "number" || + !saveDataOrErr || + saveDataOrErr.length === 0 || + saveDataOrErr[0] !== "{" + ) { + if (saveDataOrErr === 404) { globalScene.queueMessage( "Save data could not be found. If this is a new account, you can safely ignore this message.", null, @@ -470,7 +475,7 @@ export class GameData { ); return resolve(true); } - if (saveDataOrErr?.includes("Too many connections")) { + if (typeof saveDataOrErr === "string" && saveDataOrErr?.includes("Too many connections")) { globalScene.queueMessage( "Too many people are trying to connect and the server is overloaded. Please try again later.", null, @@ -478,7 +483,6 @@ export class GameData { ); return resolve(false); } - console.error(saveDataOrErr); return resolve(false); } @@ -1499,7 +1503,7 @@ export class GameData { link.remove(); }; if (!bypassLogin && dataType < GameDataType.SETTINGS) { - let promise: Promise = Promise.resolve(null); + let promise: Promise = Promise.resolve(null); if (dataType === GameDataType.SYSTEM) { promise = pokerogueApi.savedata.system.get({ clientSessionId }); @@ -1511,7 +1515,7 @@ export class GameData { } promise.then(response => { - if (!response?.length || response[0] !== "{") { + if (typeof response === "number" || !response?.length || response[0] !== "{") { console.error(response); resolve(false); return; From 89a9d55d3c4cd540f8a80aca8e72c541f62ad23d Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 26 Apr 2025 23:22:56 -0700 Subject: [PATCH 8/9] [Dev] Add enemy pokemon level to encounter logging (#5718) --- .../mystery-encounters/utils/encounter-phase-utils.ts | 1 + src/phases/encounter-phase.ts | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 65051b937f8..67904fc856c 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -424,6 +424,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): console.log( `Pokemon: ${getPokemonNameWithAffix(enemyPokemon)}`, `| Species ID: ${enemyPokemon.species.speciesId}`, + `| Level: ${enemyPokemon.level}`, `| Nature: ${getNatureName(enemyPokemon.nature, true, true, true)}`, ); console.log(`Stats (IVs): ${stats}`); diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 6fd11c416a2..20ed69119f9 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -2,7 +2,12 @@ import { BattlerIndex } from "#app/battle"; import { BattleType } from "#enums/battle-type"; import { globalScene } from "#app/global-scene"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; -import { applyAbAttrs, SyncEncounterNatureAbAttr, applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/abilities/ability"; +import { + applyAbAttrs, + SyncEncounterNatureAbAttr, + applyPreSummonAbAttrs, + PreSummonAbAttr, +} from "#app/data/abilities/ability"; import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims"; import { getCharVariantFromDialogue } from "#app/data/dialogue"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -196,6 +201,7 @@ export class EncounterPhase extends BattlePhase { console.log( `Pokemon: ${getPokemonNameWithAffix(enemyPokemon)}`, `| Species ID: ${enemyPokemon.species.speciesId}`, + `| Level: ${enemyPokemon.level}`, `| Nature: ${getNatureName(enemyPokemon.nature, true, true, true)}`, ); console.log(`Stats (IVs): ${stats}`); From a7479c8eb68d64080f5e4dd3e4bed1bc9fd6fc92 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 27 Apr 2025 12:27:34 -0700 Subject: [PATCH 9/9] [Balance] Trainer pokemon now have minimum IVs of `wave/10` (#5719) --- src/field/pokemon.ts | 19 ++++++++++++------- test/field/pokemon.test.ts | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 2de8cc150c9..f6810ad38e1 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -12,7 +12,6 @@ import BattleInfo, { import type Move from "#app/data/moves/move"; import { HighCritAttr, - StatChangeBeforeDmgCalcAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, @@ -70,10 +69,8 @@ import { EFFECTIVE_STATS, } from "#enums/stat"; import { - DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, - EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, @@ -119,7 +116,6 @@ import { TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, - TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, @@ -188,7 +184,7 @@ import { PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, applyAllyStatMultiplierAbAttrs, AllyStatMultiplierAbAttr, - MoveAbilityBypassAbAttr + MoveAbilityBypassAbAttr, } from "#app/data/abilities/ability"; import { allAbilities } from "#app/data/data-lists"; import type PokemonData from "#app/system/pokemon-data"; @@ -202,7 +198,7 @@ import { EVOLVE_MOVE, RELEARN_MOVE, } from "#app/data/balance/pokemon-level-moves"; -import { DamageAchv, achvs } from "#app/system/achv"; +import { achvs } from "#app/system/achv"; import type { StarterDataEntry, StarterMoveset } from "#app/system/game-data"; import { DexAttr } from "#app/system/game-data"; import { @@ -248,7 +244,7 @@ import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { SwitchType } from "#enums/switch-type"; import { SpeciesFormKey } from "#enums/species-form-key"; -import {getStatusEffectOverlapText } from "#app/data/status-effect"; +import { getStatusEffectOverlapText } from "#app/data/status-effect"; import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, @@ -7030,6 +7026,15 @@ export class EnemyPokemon extends Pokemon { } speciesId = prevolution; } + + if (this.hasTrainer() && globalScene.currentBattle) { + const { waveIndex } = globalScene.currentBattle; + const ivs: number[] = []; + while (ivs.length < 6) { + ivs.push(this.randSeedIntRange(Math.floor(waveIndex / 10), 31)); + } + this.ivs = ivs; + } } this.aiType = diff --git a/test/field/pokemon.test.ts b/test/field/pokemon.test.ts index 85128a31f7f..f763ab2c401 100644 --- a/test/field/pokemon.test.ts +++ b/test/field/pokemon.test.ts @@ -209,4 +209,19 @@ describe("Spec - Pokemon", () => { expect(types[1]).toBe(PokemonType.DARK); }); }); + + it.each([5, 25, 55, 95, 145, 195])( + "should set minimum IVs for enemy trainer pokemon based on wave (%i)", + async wave => { + game.override.startingWave(wave); + await game.classicMode.startBattle([Species.FEEBAS]); + const { waveIndex } = game.scene.currentBattle; + + for (const pokemon of game.scene.getEnemyParty()) { + for (const index in pokemon.ivs) { + expect(pokemon.ivs[index]).toBeGreaterThanOrEqual(Math.floor(waveIndex / 10)); + } + } + }, + ); });