From 1ddcca7da57e5485217c123692bef72f0f1f8381 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 16 Aug 2025 19:14:40 +0200 Subject: [PATCH 01/13] Introduced `getSpeciesData` function --- src/data/challenge.ts | 61 ++++++++++++++++++++++++++++- src/enums/challenge-type.ts | 5 +++ src/system/game-data.ts | 8 ++-- src/ui/starter-select-ui-handler.ts | 42 ++++++++++++++------ src/utils/challenge-utils.ts | 18 ++++++++- 5 files changed, 117 insertions(+), 17 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 89435149d2f..0b643d4291e 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -3,9 +3,11 @@ import { getRandomTrainerFunc } from "#app/battle"; import { defaultStarterSpecies } from "#app/constants"; import { speciesStarterCosts } from "#balance/starters"; import type { PokemonSpecies } from "#data/pokemon-species"; +import { AbilityAttr } from "#enums/ability-attr"; import { BattleType } from "#enums/battle-type"; import { Challenges } from "#enums/challenges"; import { TypeColor, TypeShadow } from "#enums/color"; +import { DexAttr } from "#enums/dex-attr"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import { ModifierTier } from "#enums/modifier-tier"; import { MoveId } from "#enums/move-id"; @@ -19,8 +21,9 @@ import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; import { Trainer } from "#field/trainer"; import type { ModifierTypeOption } from "#modifiers/modifier-type"; import { PokemonMove } from "#moves/pokemon-move"; -import type { DexAttrProps, GameData } from "#system/game-data"; +import type { DexAttrProps, GameData, StarterDataEntry } from "#system/game-data"; import { RibbonData, type RibbonFlag } from "#system/ribbons/ribbon-data"; +import type { DexEntry } from "#types/dex-data"; import { type BooleanHolder, isBetween, type NumberHolder, randSeedItem } from "#utils/common"; import { deepCopy } from "#utils/data"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; @@ -237,6 +240,15 @@ export abstract class Challenge { return false; } + /** + * An apply function for STARTER_SELECT_MODIFY challenges. Derived classes should alter this. + * @param _pokemon {@link Pokemon} The starter pokemon to modify. + * @returns {@link boolean} Whether this function did anything. + */ + applyStarterSelectModify(_dexEntry: DexEntry, _starterDataEntry: StarterDataEntry): boolean { + return false; + } + /** * An apply function for STARTER_MODIFY challenges. Derived classes should alter this. * @param _pokemon {@link Pokemon} The starter pokemon to modify. @@ -797,6 +809,53 @@ export class FreshStartChallenge extends Challenge { return true; } + applyStarterSelectModify(dexEntry: DexEntry, starterDataEntry: StarterDataEntry): boolean { + // Remove all egg moves + starterDataEntry.eggMoves = 0; + console.log("I AM APPLYING, ", starterDataEntry.eggMoves); + + // Remove hidden and passive ability + const defaultAbilities = AbilityAttr.ABILITY_1 | AbilityAttr.ABILITY_2; + starterDataEntry.abilityAttr &= defaultAbilities; + starterDataEntry.passiveAttr = 0; + + // Remove cost reduction + starterDataEntry.valueReduction = 0; + + // Remove natures except for the default ones + const neutralNaturesAttr = + (1 << (Nature.HARDY + 1)) | + (1 << (Nature.DOCILE + 1)) | + (1 << (Nature.SERIOUS + 1)) | + (1 << (Nature.BASHFUL + 1)) | + (1 << (Nature.QUIRKY + 1)); + dexEntry.natureAttr &= neutralNaturesAttr; + + // Set all ivs to 15 + dexEntry.ivs = [15, 15, 15, 15, 15, 15]; + + // Removes shiny, variants, and any unlocked forms + const defaultDexEntry = DexAttr.NON_SHINY | DexAttr.MALE | DexAttr.FEMALE | DexAttr.DEFAULT_FORM; + dexEntry.caughtAttr &= defaultDexEntry; + + /** + let validMoves = pokemon.species + .getLevelMoves() + .filter(m => isBetween(m[0], 1, 5)) + .map(lm => lm[1]); + // Filter egg moves out of the moveset + pokemon.moveset = pokemon.moveset.filter(pm => validMoves.includes(pm.moveId)); + if (pokemon.moveset.length < 4) { + // If there's empty slots fill with remaining valid moves + const existingMoveIds = pokemon.moveset.map(pm => pm.moveId); + validMoves = validMoves.filter(m => !existingMoveIds.includes(m)); + pokemon.moveset = pokemon.moveset.concat(validMoves.map(m => new PokemonMove(m))).slice(0, 4); + } + pokemon.teraType = pokemon.species.type1; // Always primary tera type + */ + return true; + } + applyStarterModify(pokemon: Pokemon): boolean { pokemon.abilityIndex = pokemon.abilityIndex % 2; // Always base ability, if you set it to hidden it wraps to first ability pokemon.passive = false; // Passive isn't unlocked diff --git a/src/enums/challenge-type.ts b/src/enums/challenge-type.ts index 053bcf92011..f3a4b7c68f9 100644 --- a/src/enums/challenge-type.ts +++ b/src/enums/challenge-type.ts @@ -18,6 +18,11 @@ export enum ChallengeType { * @see {@link Challenge.applyStarterPointCost} */ STARTER_COST, + /** + * Challenges which modify the starter data in starter select + * @see {@link Challenge.applyStarterSelectModify} + */ + STARTER_SELECT_MODIFY, /** * Challenges which modify your starters in some way * @see {@link Challenge.applyStarterModify} diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 90cbf6e18cc..16f40b1b907 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -64,7 +64,7 @@ import { trainerConfigs } from "#trainers/trainer-config"; import type { DexData, DexEntry } from "#types/dex-data"; import { RUN_HISTORY_LIMIT } from "#ui/run-history-ui-handler"; import { applyChallenges } from "#utils/challenge-utils"; -import { executeIf, fixedInt, isLocal, NumberHolder, randInt, randSeedItem } from "#utils/common"; +import { executeIf, fixedInt, isLocal, isNullOrUndefined, NumberHolder, randInt, randSeedItem } from "#utils/common"; import { decrypt, encrypt } from "#utils/data"; import { getEnumKeys } from "#utils/enums"; import { getPokemonSpecies } from "#utils/pokemon-utils"; @@ -2103,8 +2103,10 @@ export class GameData { }; } - getStarterSpeciesDefaultAbilityIndex(species: PokemonSpecies): number { - const abilityAttr = this.starterData[species.speciesId].abilityAttr; + getStarterSpeciesDefaultAbilityIndex(species: PokemonSpecies, abilityAttr?: number): number { + if (isNullOrUndefined(abilityAttr)) { + abilityAttr = this.starterData[species.speciesId].abilityAttr; + } return abilityAttr & AbilityAttr.ABILITY_1 ? 0 : !species.ability2 || abilityAttr & AbilityAttr.ABILITY_2 ? 1 : 2; } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 82467506720..9635804ba16 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -44,7 +44,7 @@ import { BattleSceneEventType } from "#events/battle-scene"; import type { Variant } from "#sprites/variant"; import { getVariantIcon, getVariantTint } from "#sprites/variant"; import { achvs } from "#system/achv"; -import type { DexAttrProps, StarterAttributes, StarterMoveset } from "#system/game-data"; +import type { DexAttrProps, StarterAttributes, StarterDataEntry, StarterMoveset } from "#system/game-data"; import { RibbonData } from "#system/ribbons/ribbon-data"; import { SettingKeyboard } from "#system/settings-keyboard"; import type { DexEntry } from "#types/dex-data"; @@ -1141,7 +1141,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.allSpecies.forEach((species, s) => { const icon = this.starterContainers[s].icon; - const dexEntry = globalScene.gameData.dexData[species.speciesId]; + const { dexEntry } = this.getSpeciesData(species.speciesId); // Initialize the StarterAttributes for this species this.starterPreferences[species.speciesId] = this.initStarterPrefs(species); @@ -1714,7 +1714,8 @@ export class StarterSelectUiHandler extends MessageUiHandler { globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), this.isPartyValid(), ); - const isCaught = globalScene.gameData.dexData[species.speciesId].caughtAttr; + const { dexEntry } = this.getSpeciesData(species.speciesId); + const isCaught = dexEntry.caughtAttr; return ( !isDupe && isValidForChallenge && currentPartyValue + starterCost <= this.getValueLimit() && isCaught ); @@ -2994,8 +2995,9 @@ export class StarterSelectUiHandler extends MessageUiHandler { container.cost = globalScene.gameData.getSpeciesStarterValue(container.species.speciesId); // First, ensure you have the caught attributes for the species else default to bigint 0 - const caughtAttr = globalScene.gameData.dexData[container.species.speciesId]?.caughtAttr || BigInt(0); - const starterData = globalScene.gameData.starterData[container.species.speciesId]; + const { dexEntry, starterDataEntry } = this.getSpeciesData(container.species.speciesId); + const caughtAttr = dexEntry?.caughtAttr || BigInt(0); + const starterData = starterDataEntry; const isStarterProgressable = speciesEggMoves.hasOwnProperty(container.species.speciesId); // Gen filter @@ -3393,7 +3395,11 @@ export class StarterSelectUiHandler extends MessageUiHandler { } setSpecies(species: PokemonSpecies | null) { - this.speciesStarterDexEntry = species ? globalScene.gameData.dexData[species.speciesId] : null; + this.speciesStarterDexEntry = null; + if (species) { + const { dexEntry } = this.getSpeciesData(species.speciesId); + this.speciesStarterDexEntry = dexEntry; + } this.dexAttrCursor = species ? this.getCurrentDexProps(species.speciesId) : 0n; this.abilityCursor = species ? globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species) : 0; this.natureCursor = species ? globalScene.gameData.getSpeciesDefaultNature(species) : 0; @@ -3663,6 +3669,17 @@ export class StarterSelectUiHandler extends MessageUiHandler { } } + getSpeciesData(speciesId: SpeciesId): { dexEntry: DexEntry; starterDataEntry: StarterDataEntry } { + const dexEntry = globalScene.gameData.dexData[speciesId]; + const starterDataEntry = globalScene.gameData.starterData[speciesId]; + + const copiedDexEntry = { ...dexEntry }; + const copiedStarterDataEntry = { ...starterDataEntry }; + applyChallenges(ChallengeType.STARTER_SELECT_MODIFY, copiedDexEntry, copiedStarterDataEntry); + + return { dexEntry: { ...copiedDexEntry }, starterDataEntry: { ...copiedStarterDataEntry } }; + } + setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}): void { let { shiny, formIndex, female, variant, abilityIndex, natureIndex, teraType } = options; const forSeen: boolean = options.forSeen ?? false; @@ -3740,12 +3757,12 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.speciesStarterMoves = []; if (species) { - const dexEntry = globalScene.gameData.dexData[species.speciesId]; - const abilityAttr = globalScene.gameData.starterData[species.speciesId].abilityAttr; + const { dexEntry, starterDataEntry } = this.getSpeciesData(species.speciesId); + const caughtAttr = dexEntry.caughtAttr || BigInt(0); + const abilityAttr = starterDataEntry.abilityAttr; + console.log("I HAVE APPLIED, ", starterDataEntry.eggMoves); - const caughtAttr = globalScene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0); - - if (!dexEntry.caughtAttr) { + if (!caughtAttr) { const props = globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); const defaultAbilityIndex = globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species); const defaultNature = globalScene.gameData.getSpeciesDefaultNature(species); @@ -4382,7 +4399,8 @@ export class StarterSelectUiHandler extends MessageUiHandler { */ getCurrentDexProps(speciesId: number): bigint { let props = 0n; - const caughtAttr = globalScene.gameData.dexData[speciesId].caughtAttr; + const { dexEntry } = this.getSpeciesData(speciesId); + const caughtAttr = dexEntry.caughtAttr; /* this checks the gender of the pokemon; this works by checking a) that the starter preferences for the species exist, and if so, is it female. If so, it'll add DexAttr.FEMALE to our temp props * It then checks b) if the caughtAttr for the pokemon is female and NOT male - this means that the ONLY gender we've gotten is female, and we need to add DexAttr.FEMALE to our temp props diff --git a/src/utils/challenge-utils.ts b/src/utils/challenge-utils.ts index c4fac3a0323..93768f673d9 100644 --- a/src/utils/challenge-utils.ts +++ b/src/utils/challenge-utils.ts @@ -10,7 +10,8 @@ import type { MoveSourceType } from "#enums/move-source-type"; import type { SpeciesId } from "#enums/species-id"; import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; import type { ModifierTypeOption } from "#modifiers/modifier-type"; -import type { DexAttrProps } from "#system/game-data"; +import type { DexAttrProps, StarterDataEntry } from "#system/game-data"; +import type { DexEntry } from "#types/dex-data"; import { BooleanHolder, type NumberHolder } from "./common"; import { getPokemonSpecies } from "./pokemon-utils"; @@ -47,6 +48,18 @@ export function applyChallenges( species: SpeciesId, cost: NumberHolder, ): boolean; +/** + * Apply all challenges that modify selectable starter data. + * @param challengeType {@link ChallengeType} ChallengeType.STARTER_SELECT_MODIFY + * @param dexEntry {@link DexEntry} The pokedex data associated to the pokemon. + * @param starterDataEntry {@link StarterDataEntry} The starter data associated to the pokemon. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.STARTER_SELECT_MODIFY, + dexEntry: DexEntry, + starterDataEntry: StarterDataEntry, +): boolean; /** * Apply all challenges that modify a starter after selection. * @param challengeType {@link ChallengeType} ChallengeType.STARTER_MODIFY @@ -269,6 +282,9 @@ export function applyChallenges(challengeType: ChallengeType, ...args: any[]): b case ChallengeType.STARTER_COST: ret ||= c.applyStarterCost(args[0], args[1]); break; + case ChallengeType.STARTER_SELECT_MODIFY: + ret ||= c.applyStarterSelectModify(args[0], args[1]); + break; case ChallengeType.STARTER_MODIFY: ret ||= c.applyStarterModify(args[0]); break; From 344ec58a5b1f1546d5f64878bd857bdfb7757d3a Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 16 Aug 2025 21:34:05 +0200 Subject: [PATCH 02/13] Saving and loading starter preferences as intended without conflicts --- src/ui/starter-select-ui-handler.ts | 95 +++++++++++++++++++---------- 1 file changed, 63 insertions(+), 32 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 9635804ba16..941c02ffe17 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -400,6 +400,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { private starterSelectCallback: StarterSelectCallback | null; private starterPreferences: StarterPreferences; + private originalStarterPreferences: StarterPreferences; protected blockInput = false; @@ -1126,10 +1127,6 @@ export class StarterSelectUiHandler extends MessageUiHandler { } show(args: any[]): boolean { - if (!this.starterPreferences) { - // starterPreferences haven't been loaded yet - this.starterPreferences = loadStarterPreferences(); - } this.moveInfoOverlay.clear(); // clear this when removing a menu; the cancel button doesn't seem to trigger this automatically on controllers this.pokerusSpecies = getPokerusStarters(); @@ -1139,12 +1136,20 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.starterSelectContainer.setVisible(true); + this.starterPreferences = loadStarterPreferences(); + this.originalStarterPreferences = loadStarterPreferences(); + this.allSpecies.forEach((species, s) => { const icon = this.starterContainers[s].icon; const { dexEntry } = this.getSpeciesData(species.speciesId); // Initialize the StarterAttributes for this species - this.starterPreferences[species.speciesId] = this.initStarterPrefs(species); + this.starterPreferences[species.speciesId] = this.initStarterPrefs(species, this.starterPreferences); + this.originalStarterPreferences[species.speciesId] = this.initStarterPrefs( + species, + this.originalStarterPreferences, + true, + ); if (dexEntry.caughtAttr) { icon.clearTint(); @@ -1179,10 +1184,14 @@ export class StarterSelectUiHandler extends MessageUiHandler { * @param species The species to get Starter Preferences for * @returns StarterAttributes for the species */ - initStarterPrefs(species: PokemonSpecies): StarterAttributes { - const starterAttributes = this.starterPreferences[species.speciesId]; - const dexEntry = globalScene.gameData.dexData[species.speciesId]; - const starterData = globalScene.gameData.starterData[species.speciesId]; + initStarterPrefs( + species: PokemonSpecies, + preferences: StarterPreferences, + ignoreChallenge = false, + ): StarterAttributes { + const starterAttributes = preferences[species.speciesId]; + const { dexEntry, starterDataEntry } = this.getSpeciesData(species.speciesId, !ignoreChallenge); + const starterData = starterDataEntry; // no preferences or Pokemon wasn't caught, return empty attribute if (!starterAttributes || !dexEntry.caughtAttr) { @@ -1782,9 +1791,11 @@ export class StarterSelectUiHandler extends MessageUiHandler { } } else { let starterContainer: StarterContainer; - const starterData = globalScene.gameData.starterData[this.lastSpecies.speciesId]; + const { starterDataEntry } = this.getSpeciesData(this.lastSpecies.speciesId); + const starterData = starterDataEntry; // prepare persistent starter data to store changes let starterAttributes = this.starterPreferences[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) { @@ -2000,6 +2011,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { starterAttributes = this.starterPreferences[this.lastSpecies.speciesId] = {}; } starterAttributes.nature = n; + originalStarterAttributes.nature = starterAttributes.nature; this.clearText(); ui.setMode(UiMode.STARTER_SELECT); // set nature for starter @@ -2068,6 +2080,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { label: i18next.t("starterSelectUiHandler:addToFavorites"), handler: () => { starterAttributes.favorite = true; + originalStarterAttributes.favorite = true; // if the starter container not exists, it means the species is not in the filtered starters if (starterContainer) { starterContainer.favoriteIcon.setVisible(starterAttributes.favorite); @@ -2081,6 +2094,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { label: i18next.t("starterSelectUiHandler:removeFromFavorites"), handler: () => { starterAttributes.favorite = false; + originalStarterAttributes.favorite = false; // if the starter container not exists, it means the species is not in the filtered starters if (starterContainer) { starterContainer.favoriteIcon.setVisible(starterAttributes.favorite); @@ -2103,6 +2117,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { (sanitizedName: string) => { ui.playSelect(); starterAttributes.nickname = sanitizedName; + originalStarterAttributes.nickname = sanitizedName; const name = decodeURIComponent(escape(atob(starterAttributes.nickname))); if (name.length > 0) { this.pokemonNameText.setText(name); @@ -2329,6 +2344,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant)).setTint(tint).setVisible(true); starterAttributes.shiny = true; + originalStarterAttributes.shiny = true; } else { // If shiny, we update the variant let newVariant = props.variant; @@ -2352,6 +2368,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { } } while (newVariant !== props.variant); starterAttributes.variant = newVariant; // store the selected variant + originalStarterAttributes.variant = newVariant; if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.NON_SHINY && newVariant <= props.variant) { // If we have run out of variants, go back to non shiny this.setSpeciesDetails(this.lastSpecies, { @@ -2361,6 +2378,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.pokemonShinyIcon.setVisible(false); success = true; starterAttributes.shiny = false; + originalStarterAttributes.shiny = false; } else { // If going to a higher variant, or only shiny forms are caught, go to next variant this.setSpeciesDetails(this.lastSpecies, { @@ -2389,7 +2407,9 @@ export class StarterSelectUiHandler extends MessageUiHandler { } } while (newFormIndex !== props.formIndex); starterAttributes.form = newFormIndex; // store the selected form + originalStarterAttributes.form = newFormIndex; starterAttributes.tera = this.lastSpecies.forms[newFormIndex].type1; + originalStarterAttributes.tera = starterAttributes.tera; this.setSpeciesDetails(this.lastSpecies, { formIndex: newFormIndex, teraType: starterAttributes.tera, @@ -2400,6 +2420,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { case Button.CYCLE_GENDER: if (this.canCycleGender) { starterAttributes.female = !props.female; + originalStarterAttributes.female = starterAttributes.female; this.setSpeciesDetails(this.lastSpecies, { female: !props.female, }); @@ -2409,7 +2430,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { case Button.CYCLE_ABILITY: if (this.canCycleAbility) { const abilityCount = this.lastSpecies.getAbilityCount(); - const abilityAttr = globalScene.gameData.starterData[this.lastSpecies.speciesId].abilityAttr; + const abilityAttr = starterDataEntry.abilityAttr; const hasAbility1 = abilityAttr & AbilityAttr.ABILITY_1; let newAbilityIndex = this.abilityCursor; do { @@ -2431,6 +2452,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { } } while (newAbilityIndex !== this.abilityCursor); starterAttributes.ability = newAbilityIndex; // store the selected ability + originalStarterAttributes.ability = newAbilityIndex; const { visible: tooltipVisible } = globalScene.ui.getTooltip(); @@ -2452,6 +2474,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { const newNature = natures[natureIndex < natures.length - 1 ? natureIndex + 1 : 0]; // store cycled nature as default starterAttributes.nature = newNature as unknown as number; + originalStarterAttributes.nature = starterAttributes.nature; this.setSpeciesDetails(this.lastSpecies, { natureIndex: newNature, }); @@ -2463,11 +2486,13 @@ export class StarterSelectUiHandler extends MessageUiHandler { const speciesForm = getPokemonSpeciesForm(this.lastSpecies.speciesId, starterAttributes.form ?? 0); if (speciesForm.type1 === this.teraCursor && !isNullOrUndefined(speciesForm.type2)) { starterAttributes.tera = speciesForm.type2!; + originalStarterAttributes.form = starterAttributes.tera; this.setSpeciesDetails(this.lastSpecies, { teraType: speciesForm.type2!, }); } else { starterAttributes.tera = speciesForm.type1; + originalStarterAttributes.form = starterAttributes.tera; this.setSpeciesDetails(this.lastSpecies, { teraType: speciesForm.type1, }); @@ -2714,16 +2739,16 @@ export class StarterSelectUiHandler extends MessageUiHandler { } const updatedMoveset = starterMoveset.slice() as StarterMoveset; const formIndex = globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.dexAttrCursor).formIndex; - const starterData = globalScene.gameData.starterData[speciesId]; + const { starterDataEntry } = this.getSpeciesData(this.lastSpecies.speciesId); // species has different forms if (pokemonFormLevelMoves.hasOwnProperty(speciesId)) { // Species has forms with different movesets - if (!starterData.moveset || Array.isArray(starterData.moveset)) { - starterData.moveset = {}; + if (!starterDataEntry.moveset || Array.isArray(starterDataEntry.moveset)) { + starterDataEntry.moveset = {}; } - starterData.moveset[formIndex] = updatedMoveset; + starterDataEntry.moveset[formIndex] = updatedMoveset; } else { - starterData.moveset = updatedMoveset; + starterDataEntry.moveset = updatedMoveset; } this.setSpeciesDetails(this.lastSpecies, { forSeen: false }); @@ -3669,14 +3694,18 @@ export class StarterSelectUiHandler extends MessageUiHandler { } } - getSpeciesData(speciesId: SpeciesId): { dexEntry: DexEntry; starterDataEntry: StarterDataEntry } { + getSpeciesData( + speciesId: SpeciesId, + applyChallenge = true, + ): { dexEntry: DexEntry; starterDataEntry: StarterDataEntry } { const dexEntry = globalScene.gameData.dexData[speciesId]; const starterDataEntry = globalScene.gameData.starterData[speciesId]; const copiedDexEntry = { ...dexEntry }; const copiedStarterDataEntry = { ...starterDataEntry }; - applyChallenges(ChallengeType.STARTER_SELECT_MODIFY, copiedDexEntry, copiedStarterDataEntry); - + if (applyChallenge) { + applyChallenges(ChallengeType.STARTER_SELECT_MODIFY, copiedDexEntry, copiedStarterDataEntry); + } return { dexEntry: { ...copiedDexEntry }, starterDataEntry: { ...copiedStarterDataEntry } }; } @@ -3760,7 +3789,6 @@ export class StarterSelectUiHandler extends MessageUiHandler { const { dexEntry, starterDataEntry } = this.getSpeciesData(species.speciesId); const caughtAttr = dexEntry.caughtAttr || BigInt(0); const abilityAttr = starterDataEntry.abilityAttr; - console.log("I HAVE APPLIED, ", starterDataEntry.eggMoves); if (!caughtAttr) { const props = globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); @@ -3903,7 +3931,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { .setColor(this.getTextColor(!isHidden ? TextStyle.SUMMARY_ALT : TextStyle.SUMMARY_GOLD)) .setShadowColor(this.getTextColor(!isHidden ? TextStyle.SUMMARY_ALT : TextStyle.SUMMARY_GOLD, true)); - const passiveAttr = globalScene.gameData.starterData[species.speciesId].passiveAttr; + const passiveAttr = starterDataEntry.passiveAttr; const passiveAbility = allAbilities[this.lastSpecies.getPassiveAbility(formIndex)]; if (this.pokemonAbilityText.visible) { @@ -3984,13 +4012,13 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.speciesStarterMoves.push(...levelMoves.filter(lm => lm[0] > 0 && lm[0] <= 5).map(lm => lm[1])); if (speciesEggMoves.hasOwnProperty(species.speciesId)) { for (let em = 0; em < 4; em++) { - if (globalScene.gameData.starterData[species.speciesId].eggMoves & (1 << em)) { + if (starterDataEntry.eggMoves & (1 << em)) { this.speciesStarterMoves.push(speciesEggMoves[species.speciesId][em]); } } } - const speciesMoveData = globalScene.gameData.starterData[species.speciesId].moveset; + const speciesMoveData = starterDataEntry.moveset; const moveData: StarterMoveset | null = speciesMoveData ? Array.isArray(speciesMoveData) ? speciesMoveData @@ -3998,9 +4026,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { : null; const availableStarterMoves = this.speciesStarterMoves.concat( speciesEggMoves.hasOwnProperty(species.speciesId) - ? speciesEggMoves[species.speciesId].filter( - (_: any, em: number) => globalScene.gameData.starterData[species.speciesId].eggMoves & (1 << em), - ) + ? speciesEggMoves[species.speciesId].filter((_: any, em: number) => starterDataEntry.eggMoves & (1 << em)) : [], ); this.starterMoveset = (moveData || (this.speciesStarterMoves.slice(0, 4) as StarterMoveset)).filter(m => @@ -4062,10 +4088,15 @@ export class StarterSelectUiHandler extends MessageUiHandler { } const hasEggMoves = species && speciesEggMoves.hasOwnProperty(species.speciesId); + let eggMoves = 0; + if (species) { + const { starterDataEntry } = this.getSpeciesData(this.lastSpecies.speciesId); + eggMoves = starterDataEntry.eggMoves; + } for (let em = 0; em < 4; em++) { const eggMove = hasEggMoves ? allMoves[speciesEggMoves[species.speciesId][em]] : null; - const eggMoveUnlocked = eggMove && globalScene.gameData.starterData[species.speciesId].eggMoves & (1 << em); + const eggMoveUnlocked = eggMove && eggMoves & (1 << em); this.pokemonEggMoveBgs[em].setFrame( PokemonType[eggMove ? eggMove.type : PokemonType.UNKNOWN].toString().toLowerCase(), ); @@ -4335,14 +4366,12 @@ export class StarterSelectUiHandler extends MessageUiHandler { originalStarterSelectCallback?.( new Array(this.starterSpecies.length).fill(0).map((_, i) => { const starterSpecies = thisObj.starterSpecies[i]; + const { starterDataEntry } = this.getSpeciesData(starterSpecies.speciesId); return { species: starterSpecies, dexAttr: thisObj.starterAttr[i], abilityIndex: thisObj.starterAbilityIndexes[i], - passive: !( - globalScene.gameData.starterData[starterSpecies.speciesId].passiveAttr ^ - (PassiveAttr.ENABLED | PassiveAttr.UNLOCKED) - ), + passive: !(starterDataEntry.passiveAttr ^ (PassiveAttr.ENABLED | PassiveAttr.UNLOCKED)), nature: thisObj.starterNatures[i] as Nature, teraType: thisObj.starterTeras[i] as PokemonType, moveset: thisObj.starterMovesets[i], @@ -4519,7 +4548,8 @@ export class StarterSelectUiHandler extends MessageUiHandler { clear(): void { super.clear(); - saveStarterPreferences(this.starterPreferences); + saveStarterPreferences(this.originalStarterPreferences); + this.clearStarterPreferences(); this.cursor = -1; this.hideInstructions(); this.activeTooltip = undefined; @@ -4562,5 +4592,6 @@ export class StarterSelectUiHandler extends MessageUiHandler { */ clearStarterPreferences() { this.starterPreferences = {}; + this.originalStarterPreferences = {}; } } From 4aa6699e8fb3ef3aa45ea3acb7ccd286ab3410f7 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 16 Aug 2025 21:55:18 +0200 Subject: [PATCH 03/13] Hiding shiny star and hidden ability icon, showing correct nature --- src/data/challenge.ts | 2 +- src/system/game-data.ts | 6 ++-- src/ui/starter-select-ui-handler.ts | 46 ++++++++++++++--------------- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 0b643d4291e..a9d04a87bcd 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -829,7 +829,7 @@ export class FreshStartChallenge extends Challenge { (1 << (Nature.SERIOUS + 1)) | (1 << (Nature.BASHFUL + 1)) | (1 << (Nature.QUIRKY + 1)); - dexEntry.natureAttr &= neutralNaturesAttr; + dexEntry.natureAttr = neutralNaturesAttr; // Set all ivs to 15 dexEntry.ivs = [15, 15, 15, 15, 15, 15]; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 16f40b1b907..2cabbb1ad62 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -2110,8 +2110,10 @@ export class GameData { return abilityAttr & AbilityAttr.ABILITY_1 ? 0 : !species.ability2 || abilityAttr & AbilityAttr.ABILITY_2 ? 1 : 2; } - getSpeciesDefaultNature(species: PokemonSpecies): Nature { - const dexEntry = this.dexData[species.speciesId]; + getSpeciesDefaultNature(species: PokemonSpecies, dexEntry?: DexEntry): Nature { + if (isNullOrUndefined(dexEntry)) { + dexEntry = this.dexData[species.speciesId]; + } for (let n = 0; n < 25; n++) { if (dexEntry.natureAttr & (1 << (n + 1))) { return n as Nature; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 941c02ffe17..a338b4e717a 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -3254,12 +3254,12 @@ export class StarterSelectUiHandler extends MessageUiHandler { onScreenFirstIndex + maxRows * maxColumns - 1, ); - const gameData = globalScene.gameData; - this.starterSelectScrollBar.setScrollCursor(this.scrollCursor); let pokerusCursorIndex = 0; this.filteredStarterContainers.forEach((container, i) => { + const { dexEntry, starterDataEntry } = this.getSpeciesData(container.species.speciesId); + const pos = calcStarterPosition(i, this.scrollCursor); container.setPosition(pos.x, pos.y); if (i < onScreenFirstIndex || i > onScreenLastIndex) { @@ -3295,10 +3295,8 @@ export class StarterSelectUiHandler extends MessageUiHandler { container.label.setVisible(true); const speciesVariants = - speciesId && gameData.dexData[speciesId].caughtAttr & DexAttr.SHINY - ? [DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3].filter( - v => !!(gameData.dexData[speciesId].caughtAttr & v), - ) + speciesId && dexEntry.caughtAttr & DexAttr.SHINY + ? [DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3].filter(v => !!(dexEntry.caughtAttr & v)) : []; for (let v = 0; v < 3; v++) { const hasVariant = speciesVariants.length > v; @@ -3312,15 +3310,11 @@ export class StarterSelectUiHandler extends MessageUiHandler { } } - container.starterPassiveBgs.setVisible(!!gameData.starterData[speciesId].passiveAttr); - container.hiddenAbilityIcon.setVisible( - !!gameData.dexData[speciesId].caughtAttr && !!(gameData.starterData[speciesId].abilityAttr & 4), - ); + container.starterPassiveBgs.setVisible(!!starterDataEntry.passiveAttr); + container.hiddenAbilityIcon.setVisible(!!dexEntry.caughtAttr && !!(starterDataEntry.abilityAttr & 4)); container.classicWinIcon - .setVisible(gameData.starterData[speciesId].classicWinCount > 0) - .setTexture( - gameData.dexData[speciesId].ribbons.has(RibbonData.NUZLOCKE) ? "champion_ribbon_emerald" : "champion_ribbon", - ); + .setVisible(starterDataEntry.classicWinCount > 0) + .setTexture(dexEntry.ribbons.has(RibbonData.NUZLOCKE) ? "champion_ribbon_emerald" : "champion_ribbon"); container.favoriteIcon.setVisible(this.starterPreferences[speciesId]?.favorite ?? false); // 'Candy Icon' mode @@ -3421,14 +3415,19 @@ export class StarterSelectUiHandler extends MessageUiHandler { setSpecies(species: PokemonSpecies | null) { this.speciesStarterDexEntry = null; + this.dexAttrCursor = 0n; + this.abilityCursor = 0; + this.natureCursor = 0; + this.teraCursor = PokemonType.UNKNOWN; + if (species) { const { dexEntry } = this.getSpeciesData(species.speciesId); this.speciesStarterDexEntry = dexEntry; + this.dexAttrCursor = this.getCurrentDexProps(species.speciesId); + this.abilityCursor = globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species); + this.natureCursor = globalScene.gameData.getSpeciesDefaultNature(species, dexEntry); + this.teraCursor = species.type1; } - this.dexAttrCursor = species ? this.getCurrentDexProps(species.speciesId) : 0n; - this.abilityCursor = species ? globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species) : 0; - this.natureCursor = species ? globalScene.gameData.getSpeciesDefaultNature(species) : 0; - this.teraCursor = species ? species.type1 : PokemonType.UNKNOWN; if (!species && globalScene.ui.getTooltip().visible) { globalScene.ui.hideTooltip(); @@ -3593,11 +3592,12 @@ export class StarterSelectUiHandler extends MessageUiHandler { teraType: this.starterTeras[starterIndex], }); } else { - const defaultDexAttr = this.getCurrentDexProps(species.speciesId); const defaultAbilityIndex = starterAttributes?.ability ?? globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species); // load default nature from stater save data, if set - const defaultNature = starterAttributes?.nature || globalScene.gameData.getSpeciesDefaultNature(species); + const { dexEntry } = this.getSpeciesData(species.speciesId); + const defaultNature = + starterAttributes?.nature || globalScene.gameData.getSpeciesDefaultNature(species, dexEntry); props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); if (starterAttributes?.variant && !Number.isNaN(starterAttributes.variant)) { if (props.shiny) { @@ -3711,12 +3711,13 @@ export class StarterSelectUiHandler extends MessageUiHandler { setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}): void { let { shiny, formIndex, female, variant, abilityIndex, natureIndex, teraType } = options; + const { dexEntry, starterDataEntry } = this.getSpeciesData(species.speciesId); const forSeen: boolean = options.forSeen ?? false; const oldProps = species ? globalScene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor) : null; const oldAbilityIndex = this.abilityCursor > -1 ? this.abilityCursor : globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species); const oldNatureIndex = - this.natureCursor > -1 ? this.natureCursor : globalScene.gameData.getSpeciesDefaultNature(species); + this.natureCursor > -1 ? this.natureCursor : globalScene.gameData.getSpeciesDefaultNature(species, dexEntry); this.dexAttrCursor = 0n; this.abilityCursor = -1; this.natureCursor = -1; @@ -3786,14 +3787,13 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.speciesStarterMoves = []; if (species) { - const { dexEntry, starterDataEntry } = this.getSpeciesData(species.speciesId); const caughtAttr = dexEntry.caughtAttr || BigInt(0); const abilityAttr = starterDataEntry.abilityAttr; if (!caughtAttr) { const props = globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); const defaultAbilityIndex = globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species); - const defaultNature = globalScene.gameData.getSpeciesDefaultNature(species); + const defaultNature = globalScene.gameData.getSpeciesDefaultNature(species, dexEntry); if (shiny === undefined || shiny !== props.shiny) { shiny = props.shiny; From f749ab8f636ab88471a661365774179866e9a5e2 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 16 Aug 2025 22:13:47 +0200 Subject: [PATCH 04/13] Forcing first tera type always --- src/ui/starter-select-ui-handler.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index a338b4e717a..5fa700d8425 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -27,6 +27,7 @@ import { AbilityAttr } from "#enums/ability-attr"; import { AbilityId } from "#enums/ability-id"; import { Button } from "#enums/buttons"; import { ChallengeType } from "#enums/challenge-type"; +import { Challenges } from "#enums/challenges"; import { Device } from "#enums/devices"; import { DexAttr } from "#enums/dex-attr"; import { DropDownColumn } from "#enums/drop-down-column"; @@ -403,6 +404,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { private originalStarterPreferences: StarterPreferences; protected blockInput = false; + private allowTera: boolean; constructor() { super(UiMode.STARTER_SELECT); @@ -1130,6 +1132,8 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.moveInfoOverlay.clear(); // clear this when removing a menu; the cancel button doesn't seem to trigger this automatically on controllers this.pokerusSpecies = getPokerusStarters(); + this.allowTera = globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id); + if (args.length >= 1 && args[0] instanceof Function) { super.show(args); this.starterSelectCallback = args[0] as StarterSelectCallback; @@ -1271,6 +1275,12 @@ export class StarterSelectUiHandler extends MessageUiHandler { } } + if (starterAttributes.tera !== undefined) { + if (globalScene.gameMode.hasChallenge(Challenges.FRESH_START)) { + starterAttributes.tera = species.type1; + } + } + return starterAttributes; } @@ -3903,8 +3913,9 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.canCycleNature = globalScene.gameData.getNaturesForAttr(dexEntry.natureAttr).length > 1; this.canCycleTera = !this.statsMode && - globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && - !isNullOrUndefined(getPokemonSpeciesForm(species.speciesId, formIndex ?? 0).type2); + this.allowTera && + !isNullOrUndefined(getPokemonSpeciesForm(species.speciesId, formIndex ?? 0).type2) && + !globalScene.gameMode.hasChallenge(Challenges.FRESH_START); } if (dexEntry.caughtAttr && species.malePercent !== null) { @@ -4053,9 +4064,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.setTypeIcons(speciesForm.type1, speciesForm.type2); this.teraIcon.setFrame(PokemonType[this.teraCursor].toLowerCase()); - this.teraIcon.setVisible( - !this.statsMode && globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id), - ); + this.teraIcon.setVisible(!this.statsMode && this.allowTera); } else { this.pokemonAbilityText.setText(""); this.pokemonPassiveText.setText(""); @@ -4497,7 +4506,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.pokemonSprite.setVisible(!!this.speciesStarterDexEntry?.caughtAttr); //@ts-expect-error this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. !?!? - this.teraIcon.setVisible(globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id)); + this.teraIcon.setVisible(this.allowTera); const props = globalScene.gameData.getSpeciesDexAttrProps( this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId), @@ -4505,8 +4514,9 @@ export class StarterSelectUiHandler extends MessageUiHandler { const formIndex = props.formIndex; this.canCycleTera = !this.statsMode && - globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && - !isNullOrUndefined(getPokemonSpeciesForm(this.lastSpecies.speciesId, formIndex ?? 0).type2); + this.allowTera && + !isNullOrUndefined(getPokemonSpeciesForm(this.lastSpecies.speciesId, formIndex ?? 0).type2) && + !globalScene.gameMode.hasChallenge(Challenges.FRESH_START); this.updateInstructions(); } } From 1e786a72b8b26d4674111e019d39907d97a5f18a Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 16 Aug 2025 22:50:32 +0200 Subject: [PATCH 05/13] Fixed bug which prevented tera from displaying the saved preference; tera resets in fresh start --- src/ui/starter-select-ui-handler.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 5fa700d8425..2213d3a6d8c 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1142,6 +1142,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.starterPreferences = loadStarterPreferences(); this.originalStarterPreferences = loadStarterPreferences(); + console.log("Loaded", this.originalStarterPreferences); this.allSpecies.forEach((species, s) => { const icon = this.starterContainers[s].icon; @@ -1163,6 +1164,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.setUpgradeAnimation(icon, species); }); + console.log("Initiated", this.originalStarterPreferences); this.resetFilters(); this.updateStarters(); @@ -2495,14 +2497,14 @@ export class StarterSelectUiHandler extends MessageUiHandler { if (this.canCycleTera) { const speciesForm = getPokemonSpeciesForm(this.lastSpecies.speciesId, starterAttributes.form ?? 0); if (speciesForm.type1 === this.teraCursor && !isNullOrUndefined(speciesForm.type2)) { - starterAttributes.tera = speciesForm.type2!; - originalStarterAttributes.form = starterAttributes.tera; + starterAttributes.tera = speciesForm.type2; + originalStarterAttributes.tera = starterAttributes.tera; this.setSpeciesDetails(this.lastSpecies, { - teraType: speciesForm.type2!, + teraType: speciesForm.type2, }); } else { starterAttributes.tera = speciesForm.type1; - originalStarterAttributes.form = starterAttributes.tera; + originalStarterAttributes.tera = starterAttributes.tera; this.setSpeciesDetails(this.lastSpecies, { teraType: speciesForm.type1, }); @@ -3624,6 +3626,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { variant: props.variant, abilityIndex: defaultAbilityIndex, natureIndex: defaultNature, + teraType: starterAttributes?.tera, }); } From 1a0765ea187b0f95fe7c26e8418c44a91690d8a7 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 16 Aug 2025 22:51:22 +0200 Subject: [PATCH 06/13] Ensuring that label text updates correctly --- src/ui/starter-select-ui-handler.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 2213d3a6d8c..3a6d1e297cd 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -4217,9 +4217,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { textStyle = TextStyle.SUMMARY_GOLD; break; } - if (baseStarterValue - starterValue > 0) { - starter.label.setColor(this.getTextColor(textStyle)).setShadowColor(this.getTextColor(textStyle, true)); - } + starter.label.setColor(this.getTextColor(textStyle)).setShadowColor(this.getTextColor(textStyle, true)); } tryUpdateValue(add?: number, addingToParty?: boolean): boolean { From e45f24c1246d4b1792ae0c45f5d7fc7080530cc1 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 16 Aug 2025 23:00:11 +0200 Subject: [PATCH 07/13] Removed large commented code, plus lock on hardy nature. --- src/data/challenge.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index a9d04a87bcd..6a3097f9074 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -838,28 +838,12 @@ export class FreshStartChallenge extends Challenge { const defaultDexEntry = DexAttr.NON_SHINY | DexAttr.MALE | DexAttr.FEMALE | DexAttr.DEFAULT_FORM; dexEntry.caughtAttr &= defaultDexEntry; - /** - let validMoves = pokemon.species - .getLevelMoves() - .filter(m => isBetween(m[0], 1, 5)) - .map(lm => lm[1]); - // Filter egg moves out of the moveset - pokemon.moveset = pokemon.moveset.filter(pm => validMoves.includes(pm.moveId)); - if (pokemon.moveset.length < 4) { - // If there's empty slots fill with remaining valid moves - const existingMoveIds = pokemon.moveset.map(pm => pm.moveId); - validMoves = validMoves.filter(m => !existingMoveIds.includes(m)); - pokemon.moveset = pokemon.moveset.concat(validMoves.map(m => new PokemonMove(m))).slice(0, 4); - } - pokemon.teraType = pokemon.species.type1; // Always primary tera type - */ return true; } applyStarterModify(pokemon: Pokemon): boolean { pokemon.abilityIndex = pokemon.abilityIndex % 2; // Always base ability, if you set it to hidden it wraps to first ability pokemon.passive = false; // Passive isn't unlocked - pokemon.nature = Nature.HARDY; // Neutral nature let validMoves = pokemon.species .getLevelMoves() .filter(m => isBetween(m[0], 1, 5)) From 4155c43a7c5831f1e168f72ce6ecdd7b133099d0 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 16 Aug 2025 23:31:09 +0200 Subject: [PATCH 08/13] Excluded specific forms from fresh start, fixed bug with default nature --- src/data/challenge.ts | 30 ++++++++++++++++++++++++----- src/ui/starter-select-ui-handler.ts | 12 ++++++++---- src/utils/challenge-utils.ts | 4 +++- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 6a3097f9074..3ac09ae0be1 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -245,7 +245,7 @@ export abstract class Challenge { * @param _pokemon {@link Pokemon} The starter pokemon to modify. * @returns {@link boolean} Whether this function did anything. */ - applyStarterSelectModify(_dexEntry: DexEntry, _starterDataEntry: StarterDataEntry): boolean { + applyStarterSelectModify(_speciesId: SpeciesId, _dexEntry: DexEntry, _starterDataEntry: StarterDataEntry): boolean { return false; } @@ -809,7 +809,7 @@ export class FreshStartChallenge extends Challenge { return true; } - applyStarterSelectModify(dexEntry: DexEntry, starterDataEntry: StarterDataEntry): boolean { + applyStarterSelectModify(speciesId: SpeciesId, dexEntry: DexEntry, starterDataEntry: StarterDataEntry): boolean { // Remove all egg moves starterDataEntry.eggMoves = 0; console.log("I AM APPLYING, ", starterDataEntry.eggMoves); @@ -834,9 +834,29 @@ export class FreshStartChallenge extends Challenge { // Set all ivs to 15 dexEntry.ivs = [15, 15, 15, 15, 15, 15]; - // Removes shiny, variants, and any unlocked forms - const defaultDexEntry = DexAttr.NON_SHINY | DexAttr.MALE | DexAttr.FEMALE | DexAttr.DEFAULT_FORM; - dexEntry.caughtAttr &= defaultDexEntry; + // Removes shiny and variants + dexEntry.caughtAttr &= ~DexAttr.SHINY; + dexEntry.caughtAttr &= ~(DexAttr.VARIANT_2 | DexAttr.VARIANT_3); + + // Remove unlocked forms for specific species + if (speciesId === SpeciesId.ZYGARDE) { + const formMask = (DexAttr.DEFAULT_FORM << 2n) - 1n; // Sets 10%-PC to 10%-AB and 50%-PC to 50%-AB + dexEntry.caughtAttr &= formMask; + } + if ( + [ + SpeciesId.PIKACHU, + SpeciesId.EEVEE, + SpeciesId.PICHU, + SpeciesId.ROTOM, + SpeciesId.MELOETTA, + SpeciesId.FROAKIE, + SpeciesId.ROCKRUFF, + ].includes(speciesId) + ) { + const formMask = (DexAttr.DEFAULT_FORM << 1n) - 1n; // These mons are set to form 0 because they're meant to be unlocks or mid-run form changes + dexEntry.caughtAttr &= formMask; + } return true; } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 3a6d1e297cd..82e9b8f4ce4 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -3717,20 +3717,23 @@ export class StarterSelectUiHandler extends MessageUiHandler { const copiedDexEntry = { ...dexEntry }; const copiedStarterDataEntry = { ...starterDataEntry }; if (applyChallenge) { - applyChallenges(ChallengeType.STARTER_SELECT_MODIFY, copiedDexEntry, copiedStarterDataEntry); + applyChallenges(ChallengeType.STARTER_SELECT_MODIFY, speciesId, copiedDexEntry, copiedStarterDataEntry); } return { dexEntry: { ...copiedDexEntry }, starterDataEntry: { ...copiedStarterDataEntry } }; } setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}): void { let { shiny, formIndex, female, variant, abilityIndex, natureIndex, teraType } = options; - const { dexEntry, starterDataEntry } = this.getSpeciesData(species.speciesId); const forSeen: boolean = options.forSeen ?? false; const oldProps = species ? globalScene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor) : null; const oldAbilityIndex = this.abilityCursor > -1 ? this.abilityCursor : globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species); - const oldNatureIndex = - this.natureCursor > -1 ? this.natureCursor : globalScene.gameData.getSpeciesDefaultNature(species, dexEntry); + let oldNatureIndex = -1; + if (species) { + const { dexEntry } = this.getSpeciesData(species.speciesId); + oldNatureIndex = + this.natureCursor > -1 ? this.natureCursor : globalScene.gameData.getSpeciesDefaultNature(species, dexEntry); + } this.dexAttrCursor = 0n; this.abilityCursor = -1; this.natureCursor = -1; @@ -3800,6 +3803,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.speciesStarterMoves = []; if (species) { + const { dexEntry, starterDataEntry } = this.getSpeciesData(species.speciesId); const caughtAttr = dexEntry.caughtAttr || BigInt(0); const abilityAttr = starterDataEntry.abilityAttr; diff --git a/src/utils/challenge-utils.ts b/src/utils/challenge-utils.ts index 93768f673d9..b0c162a74ed 100644 --- a/src/utils/challenge-utils.ts +++ b/src/utils/challenge-utils.ts @@ -51,12 +51,14 @@ export function applyChallenges( /** * Apply all challenges that modify selectable starter data. * @param challengeType {@link ChallengeType} ChallengeType.STARTER_SELECT_MODIFY + * @param speciesId {@link SpeciesId} The speciesId of the pokemon * @param dexEntry {@link DexEntry} The pokedex data associated to the pokemon. * @param starterDataEntry {@link StarterDataEntry} The starter data associated to the pokemon. * @returns True if any challenge was successfully applied. */ export function applyChallenges( challengeType: ChallengeType.STARTER_SELECT_MODIFY, + speciesId: SpeciesId, dexEntry: DexEntry, starterDataEntry: StarterDataEntry, ): boolean; @@ -283,7 +285,7 @@ export function applyChallenges(challengeType: ChallengeType, ...args: any[]): b ret ||= c.applyStarterCost(args[0], args[1]); break; case ChallengeType.STARTER_SELECT_MODIFY: - ret ||= c.applyStarterSelectModify(args[0], args[1]); + ret ||= c.applyStarterSelectModify(args[0], args[1], args[2]); break; case ChallengeType.STARTER_MODIFY: ret ||= c.applyStarterModify(args[0]); From c14c12e312e537a73b918fa647d055d360169866 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sat, 16 Aug 2025 23:50:19 +0200 Subject: [PATCH 09/13] Removed Rockruff from restricted forms --- src/data/challenge.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 3ac09ae0be1..1957b948732 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -851,7 +851,6 @@ export class FreshStartChallenge extends Challenge { SpeciesId.ROTOM, SpeciesId.MELOETTA, SpeciesId.FROAKIE, - SpeciesId.ROCKRUFF, ].includes(speciesId) ) { const formMask = (DexAttr.DEFAULT_FORM << 1n) - 1n; // These mons are set to form 0 because they're meant to be unlocks or mid-run form changes @@ -890,7 +889,6 @@ export class FreshStartChallenge extends Challenge { SpeciesId.ROTOM, SpeciesId.MELOETTA, SpeciesId.FROAKIE, - SpeciesId.ROCKRUFF, ].includes(pokemon.species.speciesId) ) { pokemon.formIndex = 0; // These mons are set to form 0 because they're meant to be unlocks or mid-run form changes From 5d23026b09bf7875aeb74bc60de98dcd9abd0b75 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Sun, 17 Aug 2025 11:08:50 +0200 Subject: [PATCH 10/13] Moves update correctly when switched around --- src/ui/starter-select-ui-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 82e9b8f4ce4..bc3c6b6c863 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2751,7 +2751,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { } const updatedMoveset = starterMoveset.slice() as StarterMoveset; const formIndex = globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.dexAttrCursor).formIndex; - const { starterDataEntry } = this.getSpeciesData(this.lastSpecies.speciesId); + const starterDataEntry = globalScene.gameData.starterData[speciesId]; // species has different forms if (pokemonFormLevelMoves.hasOwnProperty(speciesId)) { // Species has forms with different movesets From 9fa4925224d59edf54ec45cb8cb4966dd2c3ce32 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 18 Aug 2025 08:53:09 +0200 Subject: [PATCH 11/13] Making tera type persist in preferences --- src/ui/starter-select-ui-handler.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index bc3c6b6c863..f30e1296a89 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1142,7 +1142,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.starterPreferences = loadStarterPreferences(); this.originalStarterPreferences = loadStarterPreferences(); - console.log("Loaded", this.originalStarterPreferences); + console.log("Loaded", this.originalStarterPreferences[1]); this.allSpecies.forEach((species, s) => { const icon = this.starterContainers[s].icon; @@ -1164,7 +1164,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.setUpgradeAnimation(icon, species); }); - console.log("Initiated", this.originalStarterPreferences); + console.log("Initiated", this.originalStarterPreferences[1]); this.resetFilters(); this.updateStarters(); @@ -1278,7 +1278,12 @@ export class StarterSelectUiHandler extends MessageUiHandler { } if (starterAttributes.tera !== undefined) { - if (globalScene.gameMode.hasChallenge(Challenges.FRESH_START)) { + // If somehow we have an illegal tera type, it is reset here + if (!(starterAttributes.tera === species.type1 || starterAttributes.tera === species?.type2)) { + starterAttributes.tera = species.type1; + } + // In fresh start challenge, the tera type is always reset to the first one + if (globalScene.gameMode.hasChallenge(Challenges.FRESH_START) && !ignoreChallenge) { starterAttributes.tera = species.type1; } } From fd43266d0c45518319dac6a66a1de47061290f4e Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 18 Aug 2025 09:00:21 +0200 Subject: [PATCH 12/13] Saving starter preferences after every change --- src/ui/starter-select-ui-handler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index f30e1296a89..c1323c729d2 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -4133,6 +4133,8 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.tryUpdateValue(); this.updateInstructions(); + + saveStarterPreferences(this.originalStarterPreferences); } setTypeIcons(type1: PokemonType | null, type2: PokemonType | null): void { @@ -4568,7 +4570,6 @@ export class StarterSelectUiHandler extends MessageUiHandler { clear(): void { super.clear(); - saveStarterPreferences(this.originalStarterPreferences); this.clearStarterPreferences(); this.cursor = -1; this.hideInstructions(); From 967ad05b8e087c780aa942c93a07e9dde224852b Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 18 Aug 2025 21:43:11 +0200 Subject: [PATCH 13/13] Tera type not resetting randomly --- src/ui/starter-select-ui-handler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index c1323c729d2..3526782a52b 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -3739,6 +3739,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { oldNatureIndex = this.natureCursor > -1 ? this.natureCursor : globalScene.gameData.getSpeciesDefaultNature(species, dexEntry); } + const oldTeraType = this.teraCursor > -1 ? this.teraCursor : species ? species.type1 : PokemonType.UNKNOWN; this.dexAttrCursor = 0n; this.abilityCursor = -1; this.natureCursor = -1; @@ -3785,7 +3786,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { ); // TODO: is this bang correct? this.abilityCursor = abilityIndex !== undefined ? abilityIndex : (abilityIndex = oldAbilityIndex); this.natureCursor = natureIndex !== undefined ? natureIndex : (natureIndex = oldNatureIndex); - this.teraCursor = !isNullOrUndefined(teraType) ? teraType : (teraType = species.type1); + this.teraCursor = !isNullOrUndefined(teraType) ? teraType : (teraType = oldTeraType); const [isInParty, partyIndex]: [boolean, number] = this.isInParty(species); // we use this to firstly check if the pokemon is in the party, and if so, to get the party index in order to update the icon image if (isInParty) { this.updatePartyIcon(species, partyIndex);