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);
+ });
});