This commit is contained in:
Wlowscha 2025-08-18 18:38:16 -07:00 committed by GitHub
commit 81abc30fda
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 245 additions and 86 deletions

View File

@ -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(_speciesId: SpeciesId, _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,10 +809,60 @@ export class FreshStartChallenge extends Challenge {
return true;
}
applyStarterSelectModify(speciesId: SpeciesId, 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 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,
].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;
}
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))
@ -827,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

View File

@ -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}

View File

@ -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,13 +2103,17 @@ 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;
}
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;

View File

@ -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";
@ -44,7 +45,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";
@ -400,8 +401,10 @@ export class StarterSelectUiHandler extends MessageUiHandler {
private starterSelectCallback: StarterSelectCallback | null;
private starterPreferences: StarterPreferences;
private originalStarterPreferences: StarterPreferences;
protected blockInput = false;
private allowTera: boolean;
constructor() {
super(UiMode.STARTER_SELECT);
@ -1126,25 +1129,32 @@ 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();
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;
this.starterSelectContainer.setVisible(true);
this.starterPreferences = loadStarterPreferences();
this.originalStarterPreferences = loadStarterPreferences();
console.log("Loaded", this.originalStarterPreferences[1]);
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);
this.starterPreferences[species.speciesId] = this.initStarterPrefs(species, this.starterPreferences);
this.originalStarterPreferences[species.speciesId] = this.initStarterPrefs(
species,
this.originalStarterPreferences,
true,
);
if (dexEntry.caughtAttr) {
icon.clearTint();
@ -1154,6 +1164,7 @@ export class StarterSelectUiHandler extends MessageUiHandler {
this.setUpgradeAnimation(icon, species);
});
console.log("Initiated", this.originalStarterPreferences[1]);
this.resetFilters();
this.updateStarters();
@ -1179,10 +1190,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) {
@ -1262,6 +1277,17 @@ export class StarterSelectUiHandler extends MessageUiHandler {
}
}
if (starterAttributes.tera !== undefined) {
// 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;
}
}
return starterAttributes;
}
@ -1714,7 +1740,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
);
@ -1781,9 +1808,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) {
@ -1999,6 +2028,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
@ -2067,6 +2097,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);
@ -2080,6 +2111,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);
@ -2102,6 +2134,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);
@ -2328,6 +2361,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;
@ -2351,6 +2385,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, {
@ -2360,6 +2395,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, {
@ -2388,7 +2424,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,
@ -2399,6 +2437,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,
});
@ -2408,7 +2447,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 {
@ -2430,6 +2469,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();
@ -2451,6 +2491,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,
});
@ -2461,12 +2502,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!;
starterAttributes.tera = speciesForm.type2;
originalStarterAttributes.tera = starterAttributes.tera;
this.setSpeciesDetails(this.lastSpecies, {
teraType: speciesForm.type2!,
teraType: speciesForm.type2,
});
} else {
starterAttributes.tera = speciesForm.type1;
originalStarterAttributes.tera = starterAttributes.tera;
this.setSpeciesDetails(this.lastSpecies, {
teraType: speciesForm.type1,
});
@ -2713,16 +2756,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 = globalScene.gameData.starterData[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 });
@ -2994,8 +3037,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
@ -3227,12 +3271,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) {
@ -3268,10 +3312,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;
@ -3285,15 +3327,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
@ -3393,11 +3431,20 @@ export class StarterSelectUiHandler extends MessageUiHandler {
}
setSpecies(species: PokemonSpecies | null) {
this.speciesStarterDexEntry = species ? globalScene.gameData.dexData[species.speciesId] : null;
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;
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;
}
if (!species && globalScene.ui.getTooltip().visible) {
globalScene.ui.hideTooltip();
@ -3562,11 +3609,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) {
@ -3583,6 +3631,7 @@ export class StarterSelectUiHandler extends MessageUiHandler {
variant: props.variant,
abilityIndex: defaultAbilityIndex,
natureIndex: defaultNature,
teraType: starterAttributes?.tera,
});
}
@ -3663,14 +3712,34 @@ export class StarterSelectUiHandler extends MessageUiHandler {
}
}
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 };
if (applyChallenge) {
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 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);
let oldNatureIndex = -1;
if (species) {
const { dexEntry } = this.getSpeciesData(species.speciesId);
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;
@ -3717,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);
@ -3740,15 +3809,14 @@ 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;
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);
const defaultNature = globalScene.gameData.getSpeciesDefaultNature(species, dexEntry);
if (shiny === undefined || shiny !== props.shiny) {
shiny = props.shiny;
@ -3858,8 +3926,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) {
@ -3886,7 +3955,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) {
@ -3967,13 +4036,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
@ -3981,9 +4050,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 =>
@ -4010,9 +4077,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("");
@ -4045,10 +4110,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(),
);
@ -4064,6 +4134,8 @@ export class StarterSelectUiHandler extends MessageUiHandler {
this.tryUpdateValue();
this.updateInstructions();
saveStarterPreferences(this.originalStarterPreferences);
}
setTypeIcons(type1: PokemonType | null, type2: PokemonType | null): void {
@ -4157,10 +4229,8 @@ 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));
}
}
tryUpdateValue(add?: number, addingToParty?: boolean): boolean {
const value = this.starterSpecies
@ -4318,14 +4388,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],
@ -4382,7 +4450,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
@ -4450,7 +4519,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),
@ -4458,8 +4527,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();
}
}
@ -4501,7 +4571,7 @@ export class StarterSelectUiHandler extends MessageUiHandler {
clear(): void {
super.clear();
saveStarterPreferences(this.starterPreferences);
this.clearStarterPreferences();
this.cursor = -1;
this.hideInstructions();
this.activeTooltip = undefined;
@ -4544,5 +4614,6 @@ export class StarterSelectUiHandler extends MessageUiHandler {
*/
clearStarterPreferences() {
this.starterPreferences = {};
this.originalStarterPreferences = {};
}
}

View File

@ -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,20 @@ 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 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;
/**
* Apply all challenges that modify a starter after selection.
* @param challengeType {@link ChallengeType} ChallengeType.STARTER_MODIFY
@ -269,6 +284,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], args[2]);
break;
case ChallengeType.STARTER_MODIFY:
ret ||= c.applyStarterModify(args[0]);
break;