From e05082167e1b1c25c1fbd1f99be5bdf5835f64ef Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 11 Mar 2025 22:43:23 +0100 Subject: [PATCH 1/5] Removed unnecessary else statement --- public/locales | 2 +- src/data/challenge.ts | 19 +--- src/ui/starter-select-ui-handler.ts | 137 ++++++++++++++---------- test/abilities/lightningrod.test.ts | 17 ++- test/abilities/neutralizing_gas.test.ts | 7 +- test/abilities/storm_drain.test.ts | 17 ++- 6 files changed, 105 insertions(+), 94 deletions(-) diff --git a/public/locales b/public/locales index 6b3f37cb351..b4534f03ba8 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 6b3f37cb351552721232f4dabefa17bddb5b9004 +Subproject commit b4534f03ba8eb8709486ee967257b6f3725702dd diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 9a60a41b2d2..00d96fd94fe 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -285,11 +285,7 @@ export abstract class Challenge { * @param _dexAttr {@link DexAttrProps} The dex attributes of the pokemon. * @returns {@link boolean} Whether this function did anything. */ - applyStarterChoice( - _pokemon: PokemonSpecies, - _valid: Utils.BooleanHolder, - _dexAttr: DexAttrProps, - ): boolean { + applyStarterChoice(_pokemon: PokemonSpecies, _valid: Utils.BooleanHolder, _dexAttr: DexAttrProps): boolean { return false; } @@ -441,10 +437,7 @@ export class SingleGenerationChallenge extends Challenge { super(Challenges.SINGLE_GENERATION, 9); } - applyStarterChoice( - pokemon: PokemonSpecies, - valid: Utils.BooleanHolder, - ): boolean { + applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder): boolean { if (pokemon.generation !== this.value) { valid.value = false; return true; @@ -725,13 +718,9 @@ export class SingleTypeChallenge extends Challenge { super(Challenges.SINGLE_TYPE, 18); } - override applyStarterChoice( - pokemon: PokemonSpecies, - valid: Utils.BooleanHolder, - dexAttr: DexAttrProps, - ): boolean { + override applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps): boolean { const speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex); - const types = [ speciesForm.type1, speciesForm.type2 ]; + const types = [speciesForm.type1, speciesForm.type2]; if (!types.includes(this.value - 1)) { valid.value = false; return true; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 6c19553f34b..1f834774650 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1569,53 +1569,52 @@ export default class StarterSelectUiHandler extends MessageUiHandler { Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, - props + props, ); return isValidForChallenge.value; - } else { - // We check the validity of every evolution and battle form separately, - // and require that at least one is valid - const allValidities: boolean[] = []; - const speciesToCheck = [ species.speciesId ]; - while (speciesToCheck.length) { - const checking = speciesToCheck.pop(); - const checkingSpecies = getPokemonSpecies(checking ?? Species.BULBASAUR); - const isEvoValidForChallenge = new BooleanHolder(true); - Challenge.applyChallenges( - globalScene.gameMode, - Challenge.ChallengeType.STARTER_CHOICE, - checkingSpecies, - isEvoValidForChallenge, - props // This might be wrong, in principle we need to pass the right formIndex - ); - allValidities.push(isEvoValidForChallenge.value); - if (checking && pokemonEvolutions.hasOwnProperty(checking)) { - pokemonEvolutions[checking].forEach(e => { - speciesToCheck.push(e.speciesId); - }); - } - if (checking && pokemonFormChanges.hasOwnProperty(checking)) { - pokemonFormChanges[checking].forEach(f1 => { - checkingSpecies.forms.forEach((f2, formIndex) => { - if (f1.formKey === f2.formKey) { - const formProps = { ...props }; - formProps.formIndex = formIndex; - const isFormValidForChallenge = new BooleanHolder(true); - Challenge.applyChallenges( - globalScene.gameMode, - Challenge.ChallengeType.STARTER_CHOICE, - checkingSpecies, - isFormValidForChallenge, - formProps - ); - allValidities.push(isFormValidForChallenge.value); - } - }); - }); - } - } - return allValidities.filter(v => v).length > 0; } + // We check the validity of every evolution and battle form separately, + // and require that at least one is valid + const allValidities: boolean[] = []; + const speciesToCheck = [species.speciesId]; + while (speciesToCheck.length) { + const checking = speciesToCheck.pop(); + const checkingSpecies = getPokemonSpecies(checking ?? Species.BULBASAUR); + const isEvoValidForChallenge = new BooleanHolder(true); + Challenge.applyChallenges( + globalScene.gameMode, + Challenge.ChallengeType.STARTER_CHOICE, + checkingSpecies, + isEvoValidForChallenge, + props, // This might be wrong, in principle we need to pass the right formIndex + ); + allValidities.push(isEvoValidForChallenge.value); + if (checking && pokemonEvolutions.hasOwnProperty(checking)) { + pokemonEvolutions[checking].forEach(e => { + speciesToCheck.push(e.speciesId); + }); + } + if (checking && pokemonFormChanges.hasOwnProperty(checking)) { + pokemonFormChanges[checking].forEach(f1 => { + checkingSpecies.forms.forEach((f2, formIndex) => { + if (f1.formKey === f2.formKey) { + const formProps = { ...props }; + formProps.formIndex = formIndex; + const isFormValidForChallenge = new BooleanHolder(true); + Challenge.applyChallenges( + globalScene.gameMode, + Challenge.ChallengeType.STARTER_CHOICE, + checkingSpecies, + isFormValidForChallenge, + formProps, + ); + allValidities.push(isFormValidForChallenge.value); + } + }); + }); + } + } + return allValidities.filter(v => v).length > 0; } processInput(button: Button): boolean { @@ -1831,10 +1830,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ); const isCaught = globalScene.gameData.dexData[species.speciesId].caughtAttr; return ( - !isDupe && - isValidForChallenge && - currentPartyValue + starterCost <= this.getValueLimit() && - isCaught + !isDupe && isValidForChallenge && currentPartyValue + starterCost <= this.getValueLimit() && isCaught ); }); if (validStarters.length === 0) { @@ -1926,7 +1922,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const isPartyValid = this.isPartyValid(); const isValidForChallenge = this.checkValidForChallenge( this.lastSpecies, - globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)), + globalScene.gameData.getSpeciesDexAttrProps( + this.lastSpecies, + this.getCurrentDexProps(this.lastSpecies.speciesId), + ), isPartyValid, ); @@ -3046,11 +3045,22 @@ export default class StarterSelectUiHandler extends MessageUiHandler { * Since some pokemon rely on forms to be valid (i.e. blaze tauros for fire challenges), we make a fake form and dex props to use in the challenge */ const tempFormProps = BigInt(Math.pow(2, i)) * DexAttr.DEFAULT_FORM; - const isValidForChallenge = this.checkValidForChallenge(container.species, globalScene.gameData.getSpeciesDexAttrProps(species, tempFormProps), true); + const isValidForChallenge = this.checkValidForChallenge( + container.species, + globalScene.gameData.getSpeciesDexAttrProps(species, tempFormProps), + true, + ); allFormsValid = allFormsValid || isValidForChallenge; } } else { - const isValidForChallenge = this.checkValidForChallenge(container.species, globalScene.gameData.getSpeciesDexAttrProps(species, globalScene.gameData.getSpeciesDefaultDexAttr(container.species, false, true)), true); + const isValidForChallenge = this.checkValidForChallenge( + container.species, + globalScene.gameData.getSpeciesDexAttrProps( + species, + globalScene.gameData.getSpeciesDefaultDexAttr(container.species, false, true), + ), + true, + ); allFormsValid = isValidForChallenge; } if (allFormsValid) { @@ -3884,7 +3894,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonSprite.setVisible(!this.statsMode); } - const currentFilteredContainer = this.filteredStarterContainers.find(p => p.species.speciesId === species.speciesId); + const currentFilteredContainer = this.filteredStarterContainers.find( + p => p.species.speciesId === species.speciesId, + ); if (currentFilteredContainer) { const starterSprite = currentFilteredContainer.icon as Phaser.GameObjects.Sprite; starterSprite.setTexture( @@ -4259,7 +4271,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (addingToParty) { // this does a check to see if the pokemon being added is valid; if so, it will update the isPartyValid boolean const species = this.filteredStarterContainers[this.cursor].species; - const isNewPokemonValid = this.checkValidForChallenge(species, globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), false); + const isNewPokemonValid = this.checkValidForChallenge( + species, + globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), + false, + ); isPartyValid = isPartyValid || isNewPokemonValid; } @@ -4284,7 +4300,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler { * If speciesStarterDexEntry?.caughtAttr is true, this species registered in stater. * we change to can AddParty value to true since the user has enough cost to choose this pokemon and this pokemon registered too. */ - const isValidForChallenge = this.checkValidForChallenge(this.allSpecies[s], globalScene.gameData.getSpeciesDexAttrProps(this.allSpecies[s], this.getCurrentDexProps(this.allSpecies[s].speciesId)), isPartyValid); + const isValidForChallenge = this.checkValidForChallenge( + this.allSpecies[s], + globalScene.gameData.getSpeciesDexAttrProps( + this.allSpecies[s], + this.getCurrentDexProps(this.allSpecies[s].speciesId), + ), + isPartyValid, + ); const canBeChosen = remainValue >= speciesStarterValue && isValidForChallenge; @@ -4421,7 +4444,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { let canStart = false; for (let s = 0; s < this.starterSpecies.length; s++) { const species = this.starterSpecies[s]; - const isValidForChallenge = this.checkValidForChallenge(species, globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), false); + const isValidForChallenge = this.checkValidForChallenge( + species, + globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), + false, + ); canStart = canStart || isValidForChallenge; } return canStart; diff --git a/test/abilities/lightningrod.test.ts b/test/abilities/lightningrod.test.ts index 1ca6c6b1e89..986899353ff 100644 --- a/test/abilities/lightningrod.test.ts +++ b/test/abilities/lightningrod.test.ts @@ -24,7 +24,7 @@ describe("Abilities - Lightningrod", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .moveset([ Moves.SPLASH, Moves.SHOCK_WAVE ]) + .moveset([Moves.SPLASH, Moves.SHOCK_WAVE]) .ability(Abilities.BALL_FETCH) .battleType("double") .disableCrits() @@ -34,7 +34,7 @@ describe("Abilities - Lightningrod", () => { }); it("should redirect electric type moves", async () => { - await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + await game.classicMode.startBattle([Species.FEEBAS, Species.MAGIKARP]); const enemy1 = game.scene.getEnemyField()[0]; const enemy2 = game.scene.getEnemyField()[1]; @@ -49,8 +49,8 @@ describe("Abilities - Lightningrod", () => { }); it("should not redirect non-electric type moves", async () => { - game.override.moveset([ Moves.SPLASH, Moves.AERIAL_ACE ]); - await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + game.override.moveset([Moves.SPLASH, Moves.AERIAL_ACE]); + await game.classicMode.startBattle([Species.FEEBAS, Species.MAGIKARP]); const enemy1 = game.scene.getEnemyField()[0]; const enemy2 = game.scene.getEnemyField()[1]; @@ -65,7 +65,7 @@ describe("Abilities - Lightningrod", () => { }); it("should boost the user's spatk without damaging", async () => { - await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + await game.classicMode.startBattle([Species.FEEBAS, Species.MAGIKARP]); const enemy2 = game.scene.getEnemyField()[1]; @@ -81,7 +81,7 @@ describe("Abilities - Lightningrod", () => { it("should not redirect moves changed from electric type via ability", async () => { game.override.ability(Abilities.NORMALIZE); - await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + await game.classicMode.startBattle([Species.FEEBAS, Species.MAGIKARP]); const enemy1 = game.scene.getEnemyField()[0]; const enemy2 = game.scene.getEnemyField()[1]; @@ -96,9 +96,8 @@ describe("Abilities - Lightningrod", () => { }); it("should redirect moves changed to electric type via ability", async () => { - game.override.ability(Abilities.GALVANIZE) - .moveset(Moves.TACKLE); - await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + game.override.ability(Abilities.GALVANIZE).moveset(Moves.TACKLE); + await game.classicMode.startBattle([Species.FEEBAS, Species.MAGIKARP]); const enemy1 = game.scene.getEnemyField()[0]; const enemy2 = game.scene.getEnemyField()[1]; diff --git a/test/abilities/neutralizing_gas.test.ts b/test/abilities/neutralizing_gas.test.ts index 08ab884d806..dbb4b0562be 100644 --- a/test/abilities/neutralizing_gas.test.ts +++ b/test/abilities/neutralizing_gas.test.ts @@ -158,11 +158,8 @@ describe("Abilities - Neutralizing Gas", () => { }); it("should not activate abilities of pokemon no longer on the field", async () => { - game.override - .battleType("single") - .ability(Abilities.NEUTRALIZING_GAS) - .enemyAbility(Abilities.DELTA_STREAM); - await game.classicMode.startBattle([ Species.MAGIKARP ]); + game.override.battleType("single").ability(Abilities.NEUTRALIZING_GAS).enemyAbility(Abilities.DELTA_STREAM); + await game.classicMode.startBattle([Species.MAGIKARP]); const enemy = game.scene.getEnemyPokemon()!; const weatherChangeAttr = enemy.getAbilityAttrs(PostSummonWeatherChangeAbAttr, false)[0]; diff --git a/test/abilities/storm_drain.test.ts b/test/abilities/storm_drain.test.ts index e2a7b3e212e..58ff477fa43 100644 --- a/test/abilities/storm_drain.test.ts +++ b/test/abilities/storm_drain.test.ts @@ -24,7 +24,7 @@ describe("Abilities - Storm Drain", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .moveset([ Moves.SPLASH, Moves.WATER_GUN ]) + .moveset([Moves.SPLASH, Moves.WATER_GUN]) .ability(Abilities.BALL_FETCH) .battleType("double") .disableCrits() @@ -34,7 +34,7 @@ describe("Abilities - Storm Drain", () => { }); it("should redirect water type moves", async () => { - await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + await game.classicMode.startBattle([Species.FEEBAS, Species.MAGIKARP]); const enemy1 = game.scene.getEnemyField()[0]; const enemy2 = game.scene.getEnemyField()[1]; @@ -49,8 +49,8 @@ describe("Abilities - Storm Drain", () => { }); it("should not redirect non-water type moves", async () => { - game.override.moveset([ Moves.SPLASH, Moves.AERIAL_ACE ]); - await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + game.override.moveset([Moves.SPLASH, Moves.AERIAL_ACE]); + await game.classicMode.startBattle([Species.FEEBAS, Species.MAGIKARP]); const enemy1 = game.scene.getEnemyField()[0]; const enemy2 = game.scene.getEnemyField()[1]; @@ -65,7 +65,7 @@ describe("Abilities - Storm Drain", () => { }); it("should boost the user's spatk without damaging", async () => { - await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + await game.classicMode.startBattle([Species.FEEBAS, Species.MAGIKARP]); const enemy2 = game.scene.getEnemyField()[1]; @@ -81,7 +81,7 @@ describe("Abilities - Storm Drain", () => { it("should not redirect moves changed from water type via ability", async () => { game.override.ability(Abilities.NORMALIZE); - await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + await game.classicMode.startBattle([Species.FEEBAS, Species.MAGIKARP]); const enemy1 = game.scene.getEnemyField()[0]; const enemy2 = game.scene.getEnemyField()[1]; @@ -96,9 +96,8 @@ describe("Abilities - Storm Drain", () => { }); it("should redirect moves changed to water type via ability", async () => { - game.override.ability(Abilities.LIQUID_VOICE) - .moveset(Moves.PSYCHIC_NOISE); - await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + game.override.ability(Abilities.LIQUID_VOICE).moveset(Moves.PSYCHIC_NOISE); + await game.classicMode.startBattle([Species.FEEBAS, Species.MAGIKARP]); const enemy1 = game.scene.getEnemyField()[0]; const enemy2 = game.scene.getEnemyField()[1]; From 6eda64b2706ea885e741b2dd631f80a6bae4b4a3 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 11 Mar 2025 22:54:48 +0100 Subject: [PATCH 2/5] Cleaning up comments --- src/ui/starter-select-ui-handler.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 1f834774650..c8cb129d02f 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1917,7 +1917,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const ui = this.getUi(); let options: any[] = []; // TODO: add proper type - const [isDupe, removeIndex]: [boolean, number] = this.isInParty(this.lastSpecies); // checks to see if the pokemon is a duplicate; if it is, returns the index that will be removed + const [isDupe, removeIndex]: [boolean, number] = this.isInParty(this.lastSpecies); const isPartyValid = this.isPartyValid(); const isValidForChallenge = this.checkValidForChallenge( @@ -1943,7 +1943,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler { currentPartyValue + newCost <= this.getValueLimit() && this.starterSpecies.length < PLAYER_PARTY_MAX_SIZE ) { - // this checks to make sure the pokemon doesn't exist in your party, it's valid for the challenge and that it won't go over the cost limit; if it meets all these criteria it will add it to your party options = [ { label: i18next.t("starterSelectUiHandler:addToParty"), @@ -4267,9 +4266,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { globalScene.time.delayedCall(fixedInt(500), () => this.tryUpdateValue()); return false; } - let isPartyValid: boolean = this.isPartyValid(); // this checks to see if the party is valid + let isPartyValid: boolean = this.isPartyValid(); if (addingToParty) { - // this does a check to see if the pokemon being added is valid; if so, it will update the isPartyValid boolean const species = this.filteredStarterContainers[this.cursor].species; const isNewPokemonValid = this.checkValidForChallenge( species, From a9ed02d45d4f835c574a7229149961ce709ddec7 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 11 Mar 2025 23:32:35 +0100 Subject: [PATCH 3/5] Moving challenge filter logic to challenge.ts --- src/data/challenge.ts | 77 ++++++++++++++++++++++++++++ src/ui/starter-select-ui-handler.ts | 78 +++-------------------------- 2 files changed, 85 insertions(+), 70 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 00d96fd94fe..561c6feb914 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -19,6 +19,9 @@ import { Nature } from "#enums/nature"; import type { Moves } from "#enums/moves"; import { TypeColor, TypeShadow } from "#enums/color"; import { ModifierTier } from "#app/modifier/modifier-tier"; +import { globalScene } from "#app/global-scene"; +import { pokemonFormChanges } from "./pokemon-forms"; +import { pokemonEvolutions } from "./balance/pokemon-evolutions"; /** A constant for the default max cost of the starting party before a run */ const DEFAULT_PARTY_MAX_COST = 10; @@ -1250,3 +1253,77 @@ export function initChallenges() { new FlipStatChallenge(), ); } + +/** + * Apply all challenges to the given starter (and form) to check its validity. + * Differs from {@link checkSpeciesValidForChallenge} which only checks form changes. + * @param species {@link PokemonSpecies} The species to check the validity of. + * @param dexAttr {@link DexAttrProps} The dex attributes of the species, including its form index. + * @param soft {@link boolean} If true, allow it if it could become valid through evolution or form change. + * @returns True if the species is considered valid. + */ +export function checkStarterValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) { + if (!soft) { + const isValidForChallenge = new Utils.BooleanHolder(true); + applyChallenges(globalScene.gameMode, ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); + return isValidForChallenge.value; + } + // We check the validity of every evolution and form change, and require that at least one is valid + const isValid = false; + const speciesToCheck = [species.speciesId]; + while (speciesToCheck.length && !isValid) { + const checking = speciesToCheck.pop(); + // Linter complains if we don't handle this + if (!checking) { + return false; + } + const checkingSpecies = getPokemonSpecies(checking); + if (checkSpeciesValidForChallenge(checkingSpecies, props, true)) { + return true; + } + if (checking && pokemonEvolutions.hasOwnProperty(checking)) { + pokemonEvolutions[checking].forEach(e => { + speciesToCheck.push(e.speciesId); + }); + } + } + return false; +} + +/** + * Apply all challenges to the given species (and form) to check its validity. + * Differs from {@link checkStarterValidForChallenge} which also checks evolutions. + * @param species {@link PokemonSpecies} The species to check the validity of. + * @param dexAttr {@link DexAttrProps} The dex attributes of the species, including its form index. + * @param soft {@link boolean} If true, allow it if it could become valid through a form change. + * @returns True if the species is considered valid. + */ +function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) { + if (!soft) { + const isValidForChallenge = new Utils.BooleanHolder(true); + applyChallenges(globalScene.gameMode, ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); + return isValidForChallenge.value; + } + if (pokemonFormChanges.hasOwnProperty(species.speciesId)) { + pokemonFormChanges[species.speciesId].forEach(f1 => { + species.forms.forEach((f2, formIndex) => { + if (f1.formKey === f2.formKey) { + const formProps = { ...props }; + formProps.formIndex = formIndex; + const isFormValidForChallenge = new Utils.BooleanHolder(true); + applyChallenges( + globalScene.gameMode, + ChallengeType.STARTER_CHOICE, + species, + isFormValidForChallenge, + formProps, + ); + if (isFormValidForChallenge.value) { + return true; + } + } + }); + }); + } + return false; +} diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index c8cb129d02f..e2c3297edf5 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -80,6 +80,7 @@ import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { achvs } from "#app/system/achv"; import * as Utils from "../utils"; import type { GameObjects } from "phaser"; +import { checkStarterValidForChallenge } from "#app/data/challenge"; export type StarterSelectCallback = (starters: Starter[]) => void; @@ -1554,69 +1555,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler { }); } - /** - * Apply all challenges to the given species (and form) to check its validity. - * @param species {@link PokemonSpecies} The species to check the validity of. - * @param dexAttr {@link DexAttrProps} The dex attributes of the species, including its form index. - * @param soft {@link boolean} If true, allow it if it could become valid through evolution or form change. - * @returns True if the species is considered valid. - */ - checkValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) { - if (!soft) { - const isValidForChallenge = new BooleanHolder(true); - Challenge.applyChallenges( - globalScene.gameMode, - Challenge.ChallengeType.STARTER_CHOICE, - species, - isValidForChallenge, - props, - ); - return isValidForChallenge.value; - } - // We check the validity of every evolution and battle form separately, - // and require that at least one is valid - const allValidities: boolean[] = []; - const speciesToCheck = [species.speciesId]; - while (speciesToCheck.length) { - const checking = speciesToCheck.pop(); - const checkingSpecies = getPokemonSpecies(checking ?? Species.BULBASAUR); - const isEvoValidForChallenge = new BooleanHolder(true); - Challenge.applyChallenges( - globalScene.gameMode, - Challenge.ChallengeType.STARTER_CHOICE, - checkingSpecies, - isEvoValidForChallenge, - props, // This might be wrong, in principle we need to pass the right formIndex - ); - allValidities.push(isEvoValidForChallenge.value); - if (checking && pokemonEvolutions.hasOwnProperty(checking)) { - pokemonEvolutions[checking].forEach(e => { - speciesToCheck.push(e.speciesId); - }); - } - if (checking && pokemonFormChanges.hasOwnProperty(checking)) { - pokemonFormChanges[checking].forEach(f1 => { - checkingSpecies.forms.forEach((f2, formIndex) => { - if (f1.formKey === f2.formKey) { - const formProps = { ...props }; - formProps.formIndex = formIndex; - const isFormValidForChallenge = new BooleanHolder(true); - Challenge.applyChallenges( - globalScene.gameMode, - Challenge.ChallengeType.STARTER_CHOICE, - checkingSpecies, - isFormValidForChallenge, - formProps, - ); - allValidities.push(isFormValidForChallenge.value); - } - }); - }); - } - } - return allValidities.filter(v => v).length > 0; - } - processInput(button: Button): boolean { if (this.blockInput) { return false; @@ -1823,7 +1761,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const species = starter.species; const [isDupe] = this.isInParty(species); const starterCost = globalScene.gameData.getSpeciesStarterValue(species.speciesId); - const isValidForChallenge = this.checkValidForChallenge( + const isValidForChallenge = checkStarterValidForChallenge( species, globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), this.isPartyValid(), @@ -1920,7 +1858,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const [isDupe, removeIndex]: [boolean, number] = this.isInParty(this.lastSpecies); const isPartyValid = this.isPartyValid(); - const isValidForChallenge = this.checkValidForChallenge( + const isValidForChallenge = checkStarterValidForChallenge( this.lastSpecies, globalScene.gameData.getSpeciesDexAttrProps( this.lastSpecies, @@ -3044,7 +2982,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { * Since some pokemon rely on forms to be valid (i.e. blaze tauros for fire challenges), we make a fake form and dex props to use in the challenge */ const tempFormProps = BigInt(Math.pow(2, i)) * DexAttr.DEFAULT_FORM; - const isValidForChallenge = this.checkValidForChallenge( + const isValidForChallenge = checkStarterValidForChallenge( container.species, globalScene.gameData.getSpeciesDexAttrProps(species, tempFormProps), true, @@ -3052,7 +2990,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { allFormsValid = allFormsValid || isValidForChallenge; } } else { - const isValidForChallenge = this.checkValidForChallenge( + const isValidForChallenge = checkStarterValidForChallenge( container.species, globalScene.gameData.getSpeciesDexAttrProps( species, @@ -4269,7 +4207,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { let isPartyValid: boolean = this.isPartyValid(); if (addingToParty) { const species = this.filteredStarterContainers[this.cursor].species; - const isNewPokemonValid = this.checkValidForChallenge( + const isNewPokemonValid = checkStarterValidForChallenge( species, globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), false, @@ -4298,7 +4236,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { * If speciesStarterDexEntry?.caughtAttr is true, this species registered in stater. * we change to can AddParty value to true since the user has enough cost to choose this pokemon and this pokemon registered too. */ - const isValidForChallenge = this.checkValidForChallenge( + const isValidForChallenge = checkStarterValidForChallenge( this.allSpecies[s], globalScene.gameData.getSpeciesDexAttrProps( this.allSpecies[s], @@ -4442,7 +4380,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { let canStart = false; for (let s = 0; s < this.starterSpecies.length; s++) { const species = this.starterSpecies[s]; - const isValidForChallenge = this.checkValidForChallenge( + const isValidForChallenge = checkStarterValidForChallenge( species, globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), false, From aceb922073f41b86634cdd1b2bfc7d61aacfe590 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Tue, 11 Mar 2025 23:43:58 +0100 Subject: [PATCH 4/5] Fixed logic to allow mons with no forms --- src/data/challenge.ts | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 561c6feb914..a187ee65496 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1299,31 +1299,29 @@ export function checkStarterValidForChallenge(species: PokemonSpecies, props: De * @returns True if the species is considered valid. */ function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) { - if (!soft) { + if (!soft || !pokemonFormChanges.hasOwnProperty(species.speciesId)) { const isValidForChallenge = new Utils.BooleanHolder(true); applyChallenges(globalScene.gameMode, ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); return isValidForChallenge.value; } - if (pokemonFormChanges.hasOwnProperty(species.speciesId)) { - pokemonFormChanges[species.speciesId].forEach(f1 => { - species.forms.forEach((f2, formIndex) => { - if (f1.formKey === f2.formKey) { - const formProps = { ...props }; - formProps.formIndex = formIndex; - const isFormValidForChallenge = new Utils.BooleanHolder(true); - applyChallenges( - globalScene.gameMode, - ChallengeType.STARTER_CHOICE, - species, - isFormValidForChallenge, - formProps, - ); - if (isFormValidForChallenge.value) { - return true; - } + pokemonFormChanges[species.speciesId].forEach(f1 => { + species.forms.forEach((f2, formIndex) => { + if (f1.formKey === f2.formKey) { + const formProps = { ...props }; + formProps.formIndex = formIndex; + const isFormValidForChallenge = new Utils.BooleanHolder(true); + applyChallenges( + globalScene.gameMode, + ChallengeType.STARTER_CHOICE, + species, + isFormValidForChallenge, + formProps, + ); + if (isFormValidForChallenge.value) { + return true; } - }); + } }); - } + }); return false; } From b09e978a02b92c1a9683808f11b675ffbfc27c6c Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 12 Mar 2025 00:01:16 +0100 Subject: [PATCH 5/5] Make sure that pokemon with forms are not filtered out --- src/data/challenge.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index a187ee65496..d1c3fe97595 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1269,9 +1269,8 @@ export function checkStarterValidForChallenge(species: PokemonSpecies, props: De return isValidForChallenge.value; } // We check the validity of every evolution and form change, and require that at least one is valid - const isValid = false; const speciesToCheck = [species.speciesId]; - while (speciesToCheck.length && !isValid) { + while (speciesToCheck.length) { const checking = speciesToCheck.pop(); // Linter complains if we don't handle this if (!checking) { @@ -1299,7 +1298,7 @@ export function checkStarterValidForChallenge(species: PokemonSpecies, props: De * @returns True if the species is considered valid. */ function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) { - if (!soft || !pokemonFormChanges.hasOwnProperty(species.speciesId)) { + if (!soft || !pokemonFormChanges.hasOwnProperty(species.speciesId) || props.formIndex === 0) { const isValidForChallenge = new Utils.BooleanHolder(true); applyChallenges(globalScene.gameMode, ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); return isValidForChallenge.value;