Filtering correctly when combining gen and monotype challenges

This commit is contained in:
Wlowscha 2025-03-12 00:59:20 +01:00
parent b298138157
commit a29747fcc7
No known key found for this signature in database
GPG Key ID: 3C8F1AD330565D04
2 changed files with 99 additions and 122 deletions

View File

@ -18,9 +18,10 @@ import { TrainerType } from "#enums/trainer-type";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type { Moves } from "#enums/moves"; import type { Moves } from "#enums/moves";
import { TypeColor, TypeShadow } from "#enums/color"; import { TypeColor, TypeShadow } from "#enums/color";
import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { pokemonFormChanges } from "#app/data/pokemon-forms";
import { ModifierTier } from "#app/modifier/modifier-tier"; 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 */ /** A constant for the default max cost of the starting party before a run */
const DEFAULT_PARTY_MAX_COST = 10; const DEFAULT_PARTY_MAX_COST = 10;
@ -285,15 +286,9 @@ export abstract class Challenge {
* @param _pokemon {@link PokemonSpecies} The pokemon to check the validity of. * @param _pokemon {@link PokemonSpecies} The pokemon to check the validity of.
* @param _valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. * @param _valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
* @param _dexAttr {@link DexAttrProps} The dex attributes of the pokemon. * @param _dexAttr {@link DexAttrProps} The dex attributes of the pokemon.
* @param _soft {@link boolean} If true, allow it if it could become a valid pokemon.
* @returns {@link boolean} Whether this function did anything. * @returns {@link boolean} Whether this function did anything.
*/ */
applyStarterChoice( applyStarterChoice(_pokemon: PokemonSpecies, _valid: Utils.BooleanHolder, _dexAttr: DexAttrProps): boolean {
_pokemon: PokemonSpecies,
_valid: Utils.BooleanHolder,
_dexAttr: DexAttrProps,
_soft = false,
): boolean {
return false; return false;
} }
@ -445,27 +440,8 @@ export class SingleGenerationChallenge extends Challenge {
super(Challenges.SINGLE_GENERATION, 9); super(Challenges.SINGLE_GENERATION, 9);
} }
applyStarterChoice( applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder): boolean {
pokemon: PokemonSpecies, if (pokemon.generation !== this.value) {
valid: Utils.BooleanHolder,
_dexAttr: DexAttrProps,
soft = false,
): boolean {
const generations = [pokemon.generation];
if (soft) {
const speciesToCheck = [pokemon.speciesId];
while (speciesToCheck.length) {
const checking = speciesToCheck.pop();
if (checking && pokemonEvolutions.hasOwnProperty(checking)) {
pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId);
generations.push(getPokemonSpecies(e.speciesId).generation);
});
}
}
}
if (!generations.includes(this.value)) {
valid.value = false; valid.value = false;
return true; return true;
} }
@ -745,35 +721,9 @@ export class SingleTypeChallenge extends Challenge {
super(Challenges.SINGLE_TYPE, 18); super(Challenges.SINGLE_TYPE, 18);
} }
override applyStarterChoice( override applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps): boolean {
pokemon: PokemonSpecies,
valid: Utils.BooleanHolder,
dexAttr: DexAttrProps,
soft = false,
): boolean {
const speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex); const speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex);
const types = [speciesForm.type1, speciesForm.type2]; const types = [speciesForm.type1, speciesForm.type2];
if (soft && !SingleTypeChallenge.SPECIES_OVERRIDES.includes(pokemon.speciesId)) {
const speciesToCheck = [pokemon.speciesId];
while (speciesToCheck.length) {
const checking = speciesToCheck.pop();
if (checking && pokemonEvolutions.hasOwnProperty(checking)) {
pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId);
types.push(getPokemonSpecies(e.speciesId).type1, getPokemonSpecies(e.speciesId).type2);
});
}
if (checking && pokemonFormChanges.hasOwnProperty(checking)) {
pokemonFormChanges[checking].forEach(f1 => {
getPokemonSpecies(checking).forms.forEach(f2 => {
if (f1.formKey === f2.formKey) {
types.push(f2.type1, f2.type2);
}
});
});
}
}
}
if (!types.includes(this.value - 1)) { if (!types.includes(this.value - 1)) {
valid.value = false; valid.value = false;
return true; return true;
@ -1030,7 +980,6 @@ export class LowerStarterPointsChallenge extends Challenge {
* @param pokemon {@link PokemonSpecies} The pokemon to check the validity of. * @param pokemon {@link PokemonSpecies} The pokemon to check the validity of.
* @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. * @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
* @param dexAttr {@link DexAttrProps} The dex attributes of the pokemon. * @param dexAttr {@link DexAttrProps} The dex attributes of the pokemon.
* @param soft {@link boolean} If true, allow it if it could become a valid pokemon.
* @returns True if any challenge was successfully applied. * @returns True if any challenge was successfully applied.
*/ */
export function applyChallenges( export function applyChallenges(
@ -1039,7 +988,6 @@ export function applyChallenges(
pokemon: PokemonSpecies, pokemon: PokemonSpecies,
valid: Utils.BooleanHolder, valid: Utils.BooleanHolder,
dexAttr: DexAttrProps, dexAttr: DexAttrProps,
soft: boolean,
): boolean; ): boolean;
/** /**
* Apply all challenges that modify available total starter points. * Apply all challenges that modify available total starter points.
@ -1222,7 +1170,7 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
if (c.value !== 0) { if (c.value !== 0) {
switch (challengeType) { switch (challengeType) {
case ChallengeType.STARTER_CHOICE: case ChallengeType.STARTER_CHOICE:
ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]); ret ||= c.applyStarterChoice(args[0], args[1], args[2]);
break; break;
case ChallengeType.STARTER_POINTS: case ChallengeType.STARTER_POINTS:
ret ||= c.applyStarterPoints(args[0]); ret ||= c.applyStarterPoints(args[0]);
@ -1305,3 +1253,74 @@ export function initChallenges() {
new FlipStatChallenge(), 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 speciesToCheck = [species.speciesId];
while (speciesToCheck.length) {
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 || !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;
}
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;
}

View File

@ -1,6 +1,6 @@
import type { CandyUpgradeNotificationChangedEvent } from "#app/events/battle-scene"; import type { CandyUpgradeNotificationChangedEvent } from "#app/events/battle-scene";
import { BattleSceneEventType } from "#app/events/battle-scene"; import { BattleSceneEventType } from "#app/events/battle-scene";
import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import type { Variant } from "#app/data/variant"; import type { Variant } from "#app/data/variant";
import { getVariantTint, getVariantIcon } from "#app/data/variant"; import { getVariantTint, getVariantIcon } from "#app/data/variant";
import { argbFromRgba } from "@material/material-color-utilities"; import { argbFromRgba } from "@material/material-color-utilities";
@ -19,7 +19,7 @@ import { pokemonFormChanges } from "#app/data/pokemon-forms";
import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves";
import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species"; import { allSpecies, getPokemonSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species";
import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { GameModes } from "#app/game-mode"; import { GameModes } from "#app/game-mode";
@ -80,6 +80,7 @@ import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
import * as Utils from "../utils"; import * as Utils from "../utils";
import type { GameObjects } from "phaser"; import type { GameObjects } from "phaser";
import { checkStarterValidForChallenge } from "#app/data/challenge";
export type StarterSelectCallback = (starters: Starter[]) => void; export type StarterSelectCallback = (starters: Starter[]) => void;
@ -1760,21 +1761,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
const species = starter.species; const species = starter.species;
const [isDupe] = this.isInParty(species); const [isDupe] = this.isInParty(species);
const starterCost = globalScene.gameData.getSpeciesStarterValue(species.speciesId); const starterCost = globalScene.gameData.getSpeciesStarterValue(species.speciesId);
const isValidForChallenge = new BooleanHolder(true); const isValidForChallenge = checkStarterValidForChallenge(
Challenge.applyChallenges(
globalScene.gameMode,
Challenge.ChallengeType.STARTER_CHOICE,
species, species,
isValidForChallenge,
globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)),
this.isPartyValid(), this.isPartyValid(),
); );
const isCaught = globalScene.gameData.dexData[species.speciesId].caughtAttr; const isCaught = globalScene.gameData.dexData[species.speciesId].caughtAttr;
return ( return (
!isDupe && !isDupe && isValidForChallenge && currentPartyValue + starterCost <= this.getValueLimit() && isCaught
isValidForChallenge.value &&
currentPartyValue + starterCost <= this.getValueLimit() &&
isCaught
); );
}); });
if (validStarters.length === 0) { if (validStarters.length === 0) {
@ -1861,16 +1855,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
const ui = this.getUi(); const ui = this.getUi();
let options: any[] = []; // TODO: add proper type 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 isPartyValid = this.isPartyValid();
const isValidForChallenge = new BooleanHolder(true); const isValidForChallenge = checkStarterValidForChallenge(
Challenge.applyChallenges(
globalScene.gameMode,
Challenge.ChallengeType.STARTER_CHOICE,
this.lastSpecies, this.lastSpecies,
isValidForChallenge,
globalScene.gameData.getSpeciesDexAttrProps( globalScene.gameData.getSpeciesDexAttrProps(
this.lastSpecies, this.lastSpecies,
this.getCurrentDexProps(this.lastSpecies.speciesId), this.getCurrentDexProps(this.lastSpecies.speciesId),
@ -1888,11 +1877,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
const newCost = globalScene.gameData.getSpeciesStarterValue(this.lastSpecies.speciesId); const newCost = globalScene.gameData.getSpeciesStarterValue(this.lastSpecies.speciesId);
if ( if (
!isDupe && !isDupe &&
isValidForChallenge.value && isValidForChallenge &&
currentPartyValue + newCost <= this.getValueLimit() && currentPartyValue + newCost <= this.getValueLimit() &&
this.starterSpecies.length < PLAYER_PARTY_MAX_SIZE 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 = [ options = [
{ {
label: i18next.t("starterSelectUiHandler:addToParty"), label: i18next.t("starterSelectUiHandler:addToParty"),
@ -1902,7 +1890,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
globalScene.gameData.getSpeciesStarterValue(this.lastSpecies.speciesId), globalScene.gameData.getSpeciesStarterValue(this.lastSpecies.speciesId),
true, true,
); );
if (!isDupe && isValidForChallenge.value && isOverValueLimit) { if (!isDupe && isValidForChallenge && isOverValueLimit) {
const cursorObj = this.starterCursorObjs[this.starterSpecies.length]; const cursorObj = this.starterCursorObjs[this.starterSpecies.length];
cursorObj.setVisible(true); cursorObj.setVisible(true);
cursorObj.setPosition(this.cursorObj.x, this.cursorObj.y); cursorObj.setPosition(this.cursorObj.x, this.cursorObj.y);
@ -2994,31 +2982,23 @@ 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 * 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 tempFormProps = BigInt(Math.pow(2, i)) * DexAttr.DEFAULT_FORM;
const isValidForChallenge = new BooleanHolder(true); const isValidForChallenge = checkStarterValidForChallenge(
Challenge.applyChallenges(
globalScene.gameMode,
Challenge.ChallengeType.STARTER_CHOICE,
container.species, container.species,
isValidForChallenge,
globalScene.gameData.getSpeciesDexAttrProps(species, tempFormProps), globalScene.gameData.getSpeciesDexAttrProps(species, tempFormProps),
true, true,
); );
allFormsValid = allFormsValid || isValidForChallenge.value; allFormsValid = allFormsValid || isValidForChallenge;
} }
} else { } else {
const isValidForChallenge = new BooleanHolder(true); const isValidForChallenge = checkStarterValidForChallenge(
Challenge.applyChallenges(
globalScene.gameMode,
Challenge.ChallengeType.STARTER_CHOICE,
container.species, container.species,
isValidForChallenge,
globalScene.gameData.getSpeciesDexAttrProps( globalScene.gameData.getSpeciesDexAttrProps(
species, species,
globalScene.gameData.getSpeciesDefaultDexAttr(container.species, false, true), globalScene.gameData.getSpeciesDefaultDexAttr(container.species, false, true),
), ),
true, true,
); );
allFormsValid = isValidForChallenge.value; allFormsValid = isValidForChallenge;
} }
if (allFormsValid) { if (allFormsValid) {
this.validStarterContainers.push(container); this.validStarterContainers.push(container);
@ -3851,15 +3831,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonSprite.setVisible(!this.statsMode); this.pokemonSprite.setVisible(!this.statsMode);
} }
const isValidForChallenge = new BooleanHolder(true);
Challenge.applyChallenges(
globalScene.gameMode,
Challenge.ChallengeType.STARTER_CHOICE,
species,
isValidForChallenge,
globalScene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor),
!!this.starterSpecies.length,
);
const currentFilteredContainer = this.filteredStarterContainers.find( const currentFilteredContainer = this.filteredStarterContainers.find(
p => p.species.speciesId === species.speciesId, p => p.species.speciesId === species.speciesId,
); );
@ -4233,20 +4204,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
globalScene.time.delayedCall(fixedInt(500), () => this.tryUpdateValue()); globalScene.time.delayedCall(fixedInt(500), () => this.tryUpdateValue());
return false; return false;
} }
let isPartyValid: boolean = this.isPartyValid(); // this checks to see if the party is valid let isPartyValid: boolean = this.isPartyValid();
if (addingToParty) { if (addingToParty) {
// this does a check to see if the pokemon being added is valid; if so, it will update the isPartyValid boolean
const isNewPokemonValid = new BooleanHolder(true);
const species = this.filteredStarterContainers[this.cursor].species; const species = this.filteredStarterContainers[this.cursor].species;
Challenge.applyChallenges( const isNewPokemonValid = checkStarterValidForChallenge(
globalScene.gameMode,
Challenge.ChallengeType.STARTER_CHOICE,
species, species,
isNewPokemonValid,
globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)),
false, false,
); );
isPartyValid = isPartyValid || isNewPokemonValid.value; isPartyValid = isPartyValid || isNewPokemonValid;
} }
/** /**
@ -4270,12 +4236,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
* If speciesStarterDexEntry?.caughtAttr is true, this species registered in stater. * 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. * 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 = new BooleanHolder(true); const isValidForChallenge = checkStarterValidForChallenge(
Challenge.applyChallenges(
globalScene.gameMode,
Challenge.ChallengeType.STARTER_CHOICE,
this.allSpecies[s], this.allSpecies[s],
isValidForChallenge,
globalScene.gameData.getSpeciesDexAttrProps( globalScene.gameData.getSpeciesDexAttrProps(
this.allSpecies[s], this.allSpecies[s],
this.getCurrentDexProps(this.allSpecies[s].speciesId), this.getCurrentDexProps(this.allSpecies[s].speciesId),
@ -4283,7 +4245,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
isPartyValid, isPartyValid,
); );
const canBeChosen = remainValue >= speciesStarterValue && isValidForChallenge.value; const canBeChosen = remainValue >= speciesStarterValue && isValidForChallenge;
const isPokemonInParty = this.isInParty(this.allSpecies[s])[0]; // this will get the valud of isDupe from isInParty. This will let us see if the pokemon in question is in our party already so we don't grey out the sprites if they're invalid const isPokemonInParty = this.isInParty(this.allSpecies[s])[0]; // this will get the valud of isDupe from isInParty. This will let us see if the pokemon in question is in our party already so we don't grey out the sprites if they're invalid
@ -4417,17 +4379,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
isPartyValid(): boolean { isPartyValid(): boolean {
let canStart = false; let canStart = false;
for (let s = 0; s < this.starterSpecies.length; s++) { for (let s = 0; s < this.starterSpecies.length; s++) {
const isValidForChallenge = new BooleanHolder(true);
const species = this.starterSpecies[s]; const species = this.starterSpecies[s];
Challenge.applyChallenges( const isValidForChallenge = checkStarterValidForChallenge(
globalScene.gameMode,
Challenge.ChallengeType.STARTER_CHOICE,
species, species,
isValidForChallenge,
globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)),
false, false,
); );
canStart = canStart || isValidForChallenge.value; canStart = canStart || isValidForChallenge;
} }
return canStart; return canStart;
} }