From 1f4efa0e27709f2789aebbd92738da81513969af Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 24 Aug 2025 19:45:00 -0700 Subject: [PATCH 1/9] Update version to 1.10.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a63a1ef2e5b..db07b9be8db 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.10.3", + "version": "1.10.4", "type": "module", "scripts": { "start": "vite", From 56752d6f4a951da4524c41c0459d772b7a65807d Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Mon, 25 Aug 2025 14:08:05 -0500 Subject: [PATCH 2/9] Ensure users that install pokerogue as a PWA do not remain on the old version --- public/service-worker.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/public/service-worker.js b/public/service-worker.js index b45d2484709..2d5b304db6b 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,3 +1,9 @@ +/// self.addEventListener('install', function () { console.log('Service worker installing...'); }); + +self.addEventListener('activate', (event) => { + // @ts-expect-error: See https://github.com/microsoft/TypeScript/issues/14877 + event.waitUntil(self.clients.claim()); +}) \ No newline at end of file From 1b6a52e520395020acf63df88ca403d9b9801f58 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Mon, 25 Aug 2025 14:55:31 -0700 Subject: [PATCH 3/9] [Balance] Moved Future Sight after Weather, before berries (#6412) --- src/phases/turn-start-phase.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 8fc7a763c8f..59211a9eb03 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -179,12 +179,11 @@ export class TurnStartPhase extends FieldPhase { // https://www.smogon.com/forums/threads/sword-shield-battle-mechanics-research.3655528/page-64#post-9244179 phaseManager.pushNew("WeatherEffectPhase"); + phaseManager.pushNew("PositionalTagPhase"); phaseManager.pushNew("BerryPhase"); - /** Add a new phase to check who should be taking status damage */ phaseManager.pushNew("CheckStatusEffectPhase", moveOrder); - phaseManager.pushNew("PositionalTagPhase"); phaseManager.pushNew("TurnEndPhase"); /* From 622ee5ce80de422fe593020531718dd4eb895974 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Mon, 25 Aug 2025 18:25:53 -0500 Subject: [PATCH 4/9] [Bug] Fix typecheck bug (#6415) Add `public/service-worker.js` to `ts`'s exclude --- tsconfig.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index dcbf7456df8..8becb4c00ec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -59,5 +59,12 @@ "outDir": "./build", "noEmit": true }, - "exclude": ["node_modules", "dist", "vite.config.ts", "vitest.config.ts", "vitest.workspace.ts"] + "exclude": [ + "node_modules", + "dist", + "vite.config.ts", + "vitest.config.ts", + "vitest.workspace.ts", + "public/service-worker.js" + ] } From f0c24cd16e02be946a8abb141394c7f5e1eea19a Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Mon, 25 Aug 2025 18:45:41 -0500 Subject: [PATCH 5/9] Remove unnecessary tsdoc comments from `service-worker.js` (#6417) Remove typescript comments form `service-worker.js` --- public/service-worker.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/service-worker.js b/public/service-worker.js index 2d5b304db6b..ff380adca73 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,9 +1,7 @@ -/// self.addEventListener('install', function () { console.log('Service worker installing...'); }); self.addEventListener('activate', (event) => { - // @ts-expect-error: See https://github.com/microsoft/TypeScript/issues/14877 event.waitUntil(self.clients.claim()); }) \ No newline at end of file From c8a66b2e59461a57226cdb9440031278ffaef944 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Tue, 26 Aug 2025 01:48:55 -0500 Subject: [PATCH 6/9] [Bug] Prevent an empty starterpreferences object from being saved (#6410) * Prevent an empty starterpreferences object from being saved * Fix ssui nullish coalescing * Update src/utils/data.ts Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com> --------- Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com> --- src/ui/starter-select-ui-handler.ts | 7 ++++--- src/utils/data.ts | 23 ++++++++++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index a29a65aca80..7396608bfc4 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1828,9 +1828,9 @@ export class StarterSelectUiHandler extends MessageUiHandler { // The persistent starter data to apply e.g. candy upgrades const persistentStarterData = globalScene.gameData.starterData[this.lastSpecies.speciesId]; // The sanitized starter preferences - let starterAttributes = this.starterPreferences[this.lastSpecies.speciesId]; + let starterAttributes = this.starterPreferences[this.lastSpecies.speciesId] ?? {}; // The original starter preferences - const originalStarterAttributes = this.originalStarterPreferences[this.lastSpecies.speciesId]; + const originalStarterAttributes = this.originalStarterPreferences[this.lastSpecies.speciesId] ?? {}; // this gets the correct pokemon cursor depending on whether you're in the starter screen or the party icons if (!this.starterIconsCursorObj.visible) { @@ -3408,8 +3408,9 @@ export class StarterSelectUiHandler extends MessageUiHandler { if (species) { const defaultDexAttr = this.getCurrentDexProps(species.speciesId); const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + // Bang is correct due to the `?` before variant const variant = this.starterPreferences[species.speciesId]?.variant - ? (this.starterPreferences[species.speciesId].variant as Variant) + ? (this.starterPreferences[species.speciesId]!.variant as Variant) : defaultProps.variant; const tint = getVariantTint(variant); this.pokemonShinyIcon.setFrame(getVariantIcon(variant)).setTint(tint); diff --git a/src/utils/data.ts b/src/utils/data.ts index 6580ecf2ee9..337aac1110b 100644 --- a/src/utils/data.ts +++ b/src/utils/data.ts @@ -64,7 +64,7 @@ const StarterPrefers_DEFAULT: string = "{}"; let StarterPrefers_private_latest: string = StarterPrefers_DEFAULT; export interface StarterPreferences { - [key: number]: StarterAttributes; + [key: number]: StarterAttributes | undefined; } // called on starter selection show once @@ -74,10 +74,27 @@ export function loadStarterPreferences(): StarterPreferences { localStorage.getItem(`starterPrefs_${loggedInUser?.username}`) || StarterPrefers_DEFAULT), ); } -// called on starter selection clear, always + +/** + * Check if an object has no properties of its own (its shape is `{}`) + * @param obj - Object to check + * @returns - Whether the object is bare + */ +export function isBareObject(obj: object): boolean { + for (const _ in obj) { + return false; + } + return true; +} export function saveStarterPreferences(prefs: StarterPreferences): void { - const pStr: string = JSON.stringify(prefs); + // Fastest way to check if an object has any properties (does no allocation) + if (isBareObject(prefs)) { + console.warn("Refusing to save empty starter preferences"); + return; + } + // no reason to store `{}` (for starters not customized) + const pStr: string = JSON.stringify(prefs, (_, value) => (isBareObject(value) ? undefined : value)); if (pStr !== StarterPrefers_private_latest) { // something changed, store the update localStorage.setItem(`starterPrefs_${loggedInUser?.username}`, pStr); From 4aac5472a97f09388949a12def706e03ac0cd123 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Tue, 26 Aug 2025 02:51:35 -0400 Subject: [PATCH 7/9] [Bug] Fix oversight where mons aren't guaranteed damaging moves if none are STAB (#6406) --- src/field/pokemon.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index cc7e27caae4..dab96e4090a 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3243,6 +3243,18 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { rand -= stabMovePool[index++][1]; } this.moveset.push(new PokemonMove(stabMovePool[index][0])); + } else { + // If there are no damaging STAB moves, just force a random damaging move + const attackMovePool = baseWeights.filter(m => allMoves[m[0]].category !== MoveCategory.STATUS); + if (attackMovePool.length) { + const totalWeight = attackMovePool.reduce((v, m) => v + m[1], 0); + let rand = randSeedInt(totalWeight); + let index = 0; + while (rand > attackMovePool[index][1]) { + rand -= attackMovePool[index++][1]; + } + this.moveset.push(new PokemonMove(attackMovePool[index][0], 0, 0)); + } } while (baseWeights.length > this.moveset.length && this.moveset.length < 4) { From 88e42ba4c439a2dcaf9baca206a62e461683127d Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Tue, 26 Aug 2025 01:54:26 -0500 Subject: [PATCH 8/9] [Balance][ME]Tweak weird dream do a party heal in option 1, random tera, and no longer revive pokemon in hardcore (#6409) * Tweak weird dream option 1 Transfer HP ratio and status onto transformed pokemon * Make weird dream randomize tera type --- .../encounters/weird-dream-encounter.ts | 24 +++++++++++++++++-- src/enums/pokemon-type.ts | 3 +++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 790bdf0dbef..240a0df9e95 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene"; import { allSpecies, modifierTypes } from "#data/data-lists"; import { getLevelTotalExp } from "#data/exp"; import type { PokemonSpecies } from "#data/pokemon-species"; +import { AbilityId } from "#enums/ability-id"; import { Challenges } from "#enums/challenges"; import { ModifierTier } from "#enums/modifier-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -10,8 +11,9 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Nature } from "#enums/nature"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { PlayerGender } from "#enums/player-gender"; -import { PokemonType } from "#enums/pokemon-type"; +import { MAX_POKEMON_TYPE, PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; +import { StatusEffect } from "#enums/status-effect"; import { TrainerType } from "#enums/trainer-type"; import type { PlayerPokemon, Pokemon } from "#field/pokemon"; import type { PokemonHeldItemModifier } from "#modifiers/modifier"; @@ -219,6 +221,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit await showEncounterText(`${namespace}:option.1.dreamComplete`); await doNewTeamPostProcess(transformations); + globalScene.phaseManager.unshiftNew("PartyHealPhase", true); setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, @@ -230,7 +233,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit ], fillRemaining: false, }); - leaveEncounterWithoutBattle(true); + leaveEncounterWithoutBattle(false); }) .build(), ) @@ -431,6 +434,8 @@ function getTeamTransformations(): PokemonTransformation[] { newAbilityIndex, undefined, ); + + transformation.newPokemon.teraType = randSeedInt(MAX_POKEMON_TYPE); } return pokemonTransformations; @@ -440,6 +445,8 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) { let atLeastOneNewStarter = false; for (const transformation of transformations) { const previousPokemon = transformation.previousPokemon; + const oldHpRatio = previousPokemon.getHpRatio(true); + const oldStatus = previousPokemon.status; const newPokemon = transformation.newPokemon; const speciesRootForm = newPokemon.species.getRootSpeciesId(); @@ -462,6 +469,19 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) { } newPokemon.calculateStats(); + if (oldHpRatio > 0) { + newPokemon.hp = Math.ceil(oldHpRatio * newPokemon.getMaxHp()); + // Assume that the `status` instance can always safely be transferred to the new pokemon + // This is the case (as of version 1.10.4) + // Safeguard against COMATOSE here + if (!newPokemon.hasAbility(AbilityId.COMATOSE, false, true)) { + newPokemon.status = oldStatus; + } + } else { + newPokemon.hp = 0; + newPokemon.doSetStatus(StatusEffect.FAINT); + } + await newPokemon.updateInfo(); } diff --git a/src/enums/pokemon-type.ts b/src/enums/pokemon-type.ts index eca02bae275..210e3c3dcbe 100644 --- a/src/enums/pokemon-type.ts +++ b/src/enums/pokemon-type.ts @@ -20,3 +20,6 @@ export enum PokemonType { FAIRY, STELLAR } + +/** The largest legal value for a {@linkcode PokemonType} (includes Stellar) */ +export const MAX_POKEMON_TYPE = PokemonType.STELLAR; \ No newline at end of file From 63c1c34746d22a3678397ab30d04e4a5f087cbc3 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 26 Aug 2025 08:57:11 +0200 Subject: [PATCH 9/9] [Hotfix] Fix tyrogue evo (#6414) * Fixed tyrogue evo condition * Added test for tyrogue evolution * Update src/data/balance/pokemon-evolutions.ts Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Update src/data/balance/pokemon-evolutions.ts Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Update src/data/balance/pokemon-evolutions.ts Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Added missing typeof in suggestion --------- Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --- src/data/balance/pokemon-evolutions.ts | 5 +++-- test/evolution.test.ts | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index bf90ebb7edc..bf588784f24 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -77,7 +77,8 @@ export enum EvolutionItem { LEADERS_CREST } -type TyrogueMove = MoveId.LOW_SWEEP | MoveId.MACH_PUNCH | MoveId.RAPID_SPIN; +const tyrogueMoves = [MoveId.LOW_SWEEP, MoveId.MACH_PUNCH, MoveId.RAPID_SPIN] as const; +type TyrogueMove = typeof tyrogueMoves[number]; /** * Pokemon Evolution tuple type consisting of: @@ -192,7 +193,7 @@ export class SpeciesEvolutionCondition { case EvoCondKey.WEATHER: return cond.weather.includes(globalScene.arena.getWeatherType()); case EvoCondKey.TYROGUE: - return pokemon.getMoveset(true).find(m => m.moveId as TyrogueMove)?.moveId === cond.move; + return pokemon.getMoveset(true).find(m => (tyrogueMoves as readonly MoveId[]) .includes(m.moveId))?.moveId === cond.move; case EvoCondKey.NATURE: return cond.nature.includes(pokemon.getNature()); case EvoCondKey.RANDOM_FORM: { diff --git a/test/evolution.test.ts b/test/evolution.test.ts index 3fb763e9190..7079404bdec 100644 --- a/test/evolution.test.ts +++ b/test/evolution.test.ts @@ -175,4 +175,27 @@ describe("Evolution", () => { expect(fourForm.evoFormKey).toBe("four"); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is "four" } }); + + it("tyrogue should evolve if move is not in first slot", async () => { + game.override + .moveset([MoveId.TACKLE, MoveId.RAPID_SPIN, MoveId.LOW_KICK]) + .enemySpecies(SpeciesId.GOLEM) + .enemyMoveset(MoveId.SPLASH) + .startingWave(41) + .startingLevel(19) + .enemyLevel(30); + + await game.classicMode.startBattle([SpeciesId.TYROGUE]); + + const tyrogue = game.field.getPlayerPokemon(); + + const golem = game.field.getEnemyPokemon(); + golem.hp = 1; + expect(golem.hp).toBe(1); + + game.move.select(MoveId.TACKLE); + await game.phaseInterceptor.to("EndEvolutionPhase"); + + expect(tyrogue.species.speciesId).toBe(SpeciesId.HITMONTOP); + }); });