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] [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);