Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug][Challenge][UI/UX] Exclude invalid starters when combining challenges #5509

Open
wants to merge 4 commits into
base: beta
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 79 additions & 60 deletions src/data/challenge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ import { TrainerType } from "#enums/trainer-type";
import { Nature } from "#enums/nature";
import type { Moves } from "#enums/moves";
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 { 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;
Expand Down Expand Up @@ -285,15 +286,9 @@ export abstract class Challenge {
* @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 _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.
*/
applyStarterChoice(
_pokemon: PokemonSpecies,
_valid: Utils.BooleanHolder,
_dexAttr: DexAttrProps,
_soft = false,
): boolean {
applyStarterChoice(_pokemon: PokemonSpecies, _valid: Utils.BooleanHolder, _dexAttr: DexAttrProps): boolean {
return false;
}

Expand Down Expand Up @@ -445,27 +440,8 @@ export class SingleGenerationChallenge extends Challenge {
super(Challenges.SINGLE_GENERATION, 9);
}

applyStarterChoice(
pokemon: PokemonSpecies,
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)) {
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder): boolean {
if (pokemon.generation !== this.value) {
valid.value = false;
return true;
}
Expand Down Expand Up @@ -745,35 +721,9 @@ export class SingleTypeChallenge extends Challenge {
super(Challenges.SINGLE_TYPE, 18);
}

override applyStarterChoice(
pokemon: PokemonSpecies,
valid: Utils.BooleanHolder,
dexAttr: DexAttrProps,
soft = false,
): boolean {
override applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps): boolean {
const speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex);
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)) {
valid.value = false;
return true;
Expand Down Expand Up @@ -1030,7 +980,6 @@ export class LowerStarterPointsChallenge extends Challenge {
* @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 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.
*/
export function applyChallenges(
Expand All @@ -1039,7 +988,6 @@ export function applyChallenges(
pokemon: PokemonSpecies,
valid: Utils.BooleanHolder,
dexAttr: DexAttrProps,
soft: boolean,
): boolean;
/**
* Apply all challenges that modify available total starter points.
Expand Down Expand Up @@ -1222,7 +1170,7 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
if (c.value !== 0) {
switch (challengeType) {
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;
case ChallengeType.STARTER_POINTS:
ret ||= c.applyStarterPoints(args[0]);
Expand Down Expand Up @@ -1305,3 +1253,74 @@ export function initChallenges() {
new FlipStatChallenge(),
);
}

/**
* Apply all challenges to the given starter (and form) to check its validity.
* Differs from {@linkcode checkSpeciesValidForChallenge} which only checks form changes.
* @param species - The {@linkcode PokemonSpecies} to check the validity of.
* @param dexAttr - The {@linkcode DexAttrProps | dex attributes} of the species, including its form index.
* @param soft - 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 {@linkcode checkStarterValidForChallenge} which also checks evolutions.
* @param species - The {@linkcode PokemonSpecies} to check the validity of.
* @param dexAttr - The {@linkcode DexAttrProps | dex attributes} of the species, including its form index.
* @param soft - 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;
}
19 changes: 0 additions & 19 deletions src/ui/pokedex-page-ui-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { allSpecies, getPokemonSpecies, getPokemonSpeciesForm, normalForm } from
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
import { starterPassiveAbilities } from "#app/data/balance/passives";
import { PokemonType } from "#enums/pokemon-type";
import { GameModes } from "#app/game-mode";
import type { DexEntry, StarterAttributes } from "#app/system/game-data";
import { AbilityAttr, DexAttr } from "#app/system/game-data";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
Expand All @@ -33,7 +32,6 @@ import { Egg } from "#app/data/egg";
import Overrides from "#app/overrides";
import { SettingKeyboard } from "#app/system/settings/settings-keyboard";
import { Passive as PassiveAttr } from "#enums/passive";
import * as Challenge from "#app/data/challenge";
import MoveInfoOverlay from "#app/ui/move-info-overlay";
import PokedexInfoOverlay from "#app/ui/pokedex-info-overlay";
import { getEggTierForSpecies } from "#app/data/egg";
Expand All @@ -51,7 +49,6 @@ import {
BooleanHolder,
getLocalizedSpriteKey,
isNullOrUndefined,
NumberHolder,
padInt,
rgbHexToRgba,
toReadableString,
Expand Down Expand Up @@ -2128,22 +2125,6 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
}
}

getValueLimit(): number {
const valueLimit = new NumberHolder(0);
switch (globalScene.gameMode.modeId) {
case GameModes.ENDLESS:
case GameModes.SPLICED_ENDLESS:
valueLimit.value = 15;
break;
default:
valueLimit.value = 10;
}

Challenge.applyChallenges(globalScene.gameMode, Challenge.ChallengeType.STARTER_POINTS, valueLimit);

return valueLimit.value;
}

setCursor(cursor: number): boolean {
const ret = super.setCursor(cursor);

Expand Down
Loading