[Refactor] Refactor Starter and its handling (#6477)

* Reworked `Starter` interface with more explicit information

* Use Starter in ssui

* Fixed some bugs

* Passing starter.ivs to playerPokemon

* Using speciesIds

* Fixed getTestRunStarters

* Reverted some parameter changes

* Initialize starters in ssui

* Don't clear starters before starting run

* Fix to game manager

* Apply suggestions from code review

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Set ivs to 0 in part timer test

* Setting the right ivs

* Moved ssui to handlers folder

* Ran biome all

* Fixed broken imports

---------

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
Wlowscha 2025-09-21 20:07:08 +02:00 committed by GitHub
parent cf5e7fd981
commit 2fe99cc3bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 137 additions and 129 deletions

View File

@ -4,8 +4,10 @@ import type { BattleType } from "#enums/battle-type";
import type { GameModes } from "#enums/game-modes"; import type { GameModes } from "#enums/game-modes";
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type { Nature } from "#enums/nature";
import type { PlayerGender } from "#enums/player-gender"; import type { PlayerGender } from "#enums/player-gender";
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
import type { SpeciesId } from "#enums/species-id";
import type { MysteryEncounterSaveData } from "#mystery-encounters/mystery-encounter-save-data"; import type { MysteryEncounterSaveData } from "#mystery-encounters/mystery-encounter-save-data";
import type { Variant } from "#sprites/variant"; import type { Variant } from "#sprites/variant";
import type { ArenaData } from "#system/arena-data"; import type { ArenaData } from "#system/arena-data";
@ -108,6 +110,22 @@ export interface DexAttrProps {
formIndex: number; formIndex: number;
} }
export interface Starter {
speciesId: SpeciesId;
shiny: boolean;
variant: Variant;
formIndex: number;
female?: boolean;
abilityIndex: number;
passive: boolean;
nature: Nature;
moveset?: StarterMoveset;
pokerus: boolean;
nickname?: string;
teraType?: PokemonType;
ivs: number[];
}
export type RunHistoryData = Record<number, RunEntry>; export type RunHistoryData = Record<number, RunEntry>;
export interface RunEntry { export interface RunEntry {

View File

@ -6,7 +6,7 @@ import { PokemonSpecies } from "#data/pokemon-species";
import { BiomeId } from "#enums/biome-id"; import { BiomeId } from "#enums/biome-id";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import type { Starter } from "#ui/starter-select-ui-handler"; import type { Starter } from "#types/save-data";
import { randSeedGauss, randSeedInt, randSeedItem } from "#utils/common"; import { randSeedGauss, randSeedInt, randSeedItem } from "#utils/common";
import { getEnumValues } from "#utils/enums"; import { getEnumValues } from "#utils/enums";
import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils";
@ -66,8 +66,11 @@ function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLeve
const formIndex = starterSpeciesForm instanceof PokemonSpecies ? undefined : starterSpeciesForm.formIndex; const formIndex = starterSpeciesForm instanceof PokemonSpecies ? undefined : starterSpeciesForm.formIndex;
const pokemon = globalScene.addPlayerPokemon(starterSpecies, startingLevel, undefined, formIndex); const pokemon = globalScene.addPlayerPokemon(starterSpecies, startingLevel, undefined, formIndex);
const starter: Starter = { const starter: Starter = {
species: starterSpecies, speciesId: starterSpecies.speciesId,
dexAttr: pokemon.getDexAttr(), shiny: pokemon.shiny,
variant: pokemon.variant,
formIndex: pokemon.formIndex,
ivs: pokemon.ivs,
abilityIndex: pokemon.abilityIndex, abilityIndex: pokemon.abilityIndex,
passive: false, passive: false,
nature: pokemon.getNature(), nature: pokemon.getNature(),

View File

@ -4,11 +4,10 @@ import { Phase } from "#app/phase";
import { SpeciesFormChangeMoveLearnedTrigger } from "#data/form-change-triggers"; import { SpeciesFormChangeMoveLearnedTrigger } from "#data/form-change-triggers";
import { Gender } from "#data/gender"; import { Gender } from "#data/gender";
import { ChallengeType } from "#enums/challenge-type"; import { ChallengeType } from "#enums/challenge-type";
import type { SpeciesId } from "#enums/species-id";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import { overrideHeldItems, overrideModifiers } from "#modifiers/modifier"; import { overrideHeldItems, overrideModifiers } from "#modifiers/modifier";
import { SaveSlotUiMode } from "#ui/save-slot-select-ui-handler"; import type { Starter } from "#types/save-data";
import type { Starter } from "#ui/starter-select-ui-handler"; import { SaveSlotUiMode } from "#ui/handlers/save-slot-select-ui-handler";
import { applyChallenges } from "#utils/challenge-utils"; import { applyChallenges } from "#utils/challenge-utils";
import { getPokemonSpecies } from "#utils/pokemon-utils"; import { getPokemonSpecies } from "#utils/pokemon-utils";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
@ -44,33 +43,32 @@ export class SelectStarterPhase extends Phase {
const loadPokemonAssets: Promise<void>[] = []; const loadPokemonAssets: Promise<void>[] = [];
starters.forEach((starter: Starter, i: number) => { starters.forEach((starter: Starter, i: number) => {
if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { if (!i && Overrides.STARTER_SPECIES_OVERRIDE) {
starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as SpeciesId); starter.speciesId = Overrides.STARTER_SPECIES_OVERRIDE;
} }
const starterProps = globalScene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); const species = getPokemonSpecies(starter.speciesId);
let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); let starterFormIndex = starter.formIndex;
if ( if (
starter.species.speciesId in Overrides.STARTER_FORM_OVERRIDES starter.speciesId in Overrides.STARTER_FORM_OVERRIDES
&& Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId] != null && Overrides.STARTER_FORM_OVERRIDES[starter.speciesId] != null
&& starter.species.forms[Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!] && species.forms[Overrides.STARTER_FORM_OVERRIDES[starter.speciesId]!]
) { ) {
starterFormIndex = Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!; starterFormIndex = Overrides.STARTER_FORM_OVERRIDES[starter.speciesId]!;
} }
let starterGender = let starterGender =
starter.species.malePercent !== null ? (!starterProps.female ? Gender.MALE : Gender.FEMALE) : Gender.GENDERLESS; species.malePercent !== null ? (!starter.female ? Gender.MALE : Gender.FEMALE) : Gender.GENDERLESS;
if (Overrides.GENDER_OVERRIDE !== null) { if (Overrides.GENDER_OVERRIDE !== null) {
starterGender = Overrides.GENDER_OVERRIDE; starterGender = Overrides.GENDER_OVERRIDE;
} }
const starterIvs = globalScene.gameData.dexData[starter.species.speciesId].ivs.slice(0);
const starterPokemon = globalScene.addPlayerPokemon( const starterPokemon = globalScene.addPlayerPokemon(
starter.species, species,
globalScene.gameMode.getStartingLevel(), globalScene.gameMode.getStartingLevel(),
starter.abilityIndex, starter.abilityIndex,
starterFormIndex, starterFormIndex,
starterGender, starterGender,
starterProps.shiny, starter.shiny,
starterProps.variant, starter.variant,
starterIvs, starter.ivs,
starter.nature, starter.nature,
); );
starter.moveset && starterPokemon.tryPopulateMoveset(starter.moveset); starter.moveset && starterPokemon.tryPopulateMoveset(starter.moveset);
@ -78,7 +76,7 @@ export class SelectStarterPhase extends Phase {
starterPokemon.passive = true; starterPokemon.passive = true;
} }
starterPokemon.luck = globalScene.gameData.getDexAttrLuck( starterPokemon.luck = globalScene.gameData.getDexAttrLuck(
globalScene.gameData.dexData[starter.species.speciesId].caughtAttr, globalScene.gameData.dexData[species.speciesId].caughtAttr,
); );
if (starter.pokerus) { if (starter.pokerus) {
starterPokemon.pokerus = true; starterPokemon.pokerus = true;

View File

@ -19,6 +19,7 @@ import type { SessionSaveData } from "#types/save-data";
import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import type { OptionSelectConfig, OptionSelectItem } from "#ui/abstract-option-select-ui-handler";
import { SaveSlotUiMode } from "#ui/save-slot-select-ui-handler"; import { SaveSlotUiMode } from "#ui/save-slot-select-ui-handler";
import { isLocal, isLocalServerConnected } from "#utils/common"; import { isLocal, isLocalServerConnected } from "#utils/common";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import i18next from "i18next"; import i18next from "i18next";
export class TitlePhase extends Phase { export class TitlePhase extends Phase {
@ -218,23 +219,19 @@ export class TitlePhase extends Phase {
const party = globalScene.getPlayerParty(); const party = globalScene.getPlayerParty();
const loadPokemonAssets: Promise<void>[] = []; const loadPokemonAssets: Promise<void>[] = [];
for (const starter of starters) { for (const starter of starters) {
const starterProps = globalScene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); const species = getPokemonSpecies(starter.speciesId);
const starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); const starterFormIndex = starter.formIndex;
const starterGender = const starterGender =
starter.species.malePercent !== null species.malePercent !== null ? (starter.female ? Gender.FEMALE : Gender.MALE) : Gender.GENDERLESS;
? !starterProps.female
? Gender.MALE
: Gender.FEMALE
: Gender.GENDERLESS;
const starterPokemon = globalScene.addPlayerPokemon( const starterPokemon = globalScene.addPlayerPokemon(
starter.species, species,
startingLevel, startingLevel,
starter.abilityIndex, starter.abilityIndex,
starterFormIndex, starterFormIndex,
starterGender, starterGender,
starterProps.shiny, starter.shiny,
starterProps.variant, starter.variant,
undefined, starter.ivs,
starter.nature, starter.nature,
); );
starterPokemon.setVisible(false); starterPokemon.setVisible(false);

View File

@ -49,7 +49,7 @@ import { achvs } from "#system/achv";
import { RibbonData } from "#system/ribbons/ribbon-data"; import { RibbonData } from "#system/ribbons/ribbon-data";
import { SettingKeyboard } from "#system/settings-keyboard"; import { SettingKeyboard } from "#system/settings-keyboard";
import type { DexEntry } from "#types/dex-data"; import type { DexEntry } from "#types/dex-data";
import type { DexAttrProps, StarterAttributes, StarterDataEntry, StarterMoveset } from "#types/save-data"; import type { Starter, StarterAttributes, StarterDataEntry, StarterMoveset } from "#types/save-data";
import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler";
import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#ui/dropdown"; import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#ui/dropdown";
import { FilterBar } from "#ui/filter-bar"; import { FilterBar } from "#ui/filter-bar";
@ -82,18 +82,6 @@ import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
export type StarterSelectCallback = (starters: Starter[]) => void; export type StarterSelectCallback = (starters: Starter[]) => void;
export interface Starter {
species: PokemonSpecies;
dexAttr: bigint;
abilityIndex: number;
passive: boolean;
nature: Nature;
moveset?: StarterMoveset;
pokerus: boolean;
nickname?: string;
teraType?: PokemonType;
}
interface LanguageSetting { interface LanguageSetting {
starterInfoTextSize: string; starterInfoTextSize: string;
instructionTextSize: string; instructionTextSize: string;
@ -364,15 +352,13 @@ export class StarterSelectUiHandler extends MessageUiHandler {
private allSpecies: PokemonSpecies[] = []; private allSpecies: PokemonSpecies[] = [];
private lastSpecies: PokemonSpecies; private lastSpecies: PokemonSpecies;
private speciesLoaded: Map<SpeciesId, boolean> = new Map<SpeciesId, boolean>(); private speciesLoaded: Map<SpeciesId, boolean> = new Map<SpeciesId, boolean>();
private starters: Starter[] = [];
public starterSpecies: PokemonSpecies[] = []; public starterSpecies: PokemonSpecies[] = [];
private pokerusSpecies: PokemonSpecies[] = []; private pokerusSpecies: PokemonSpecies[] = [];
private starterAttr: bigint[] = [];
private starterAbilityIndexes: number[] = [];
private starterNatures: Nature[] = [];
private starterTeras: PokemonType[] = [];
private starterMovesets: StarterMoveset[] = [];
private speciesStarterDexEntry: DexEntry | null; private speciesStarterDexEntry: DexEntry | null;
private speciesStarterMoves: MoveId[]; private speciesStarterMoves: MoveId[];
private canCycleShiny: boolean; private canCycleShiny: boolean;
private canCycleForm: boolean; private canCycleForm: boolean;
private canCycleGender: boolean; private canCycleGender: boolean;
@ -2758,12 +2744,26 @@ export class StarterSelectUiHandler extends MessageUiHandler {
props.variant, props.variant,
); );
const { dexEntry, starterDataEntry } = this.getSpeciesData(species.speciesId);
const starter = {
speciesId: species.speciesId,
shiny: props.shiny,
variant: props.variant,
formIndex: props.formIndex,
female: props.female,
abilityIndex,
passive: !(starterDataEntry.passiveAttr ^ (PassiveAttr.ENABLED | PassiveAttr.UNLOCKED)),
nature,
moveset,
pokerus: this.pokerusSpecies.includes(species),
nickname: this.starterPreferences[species.speciesId]?.nickname,
teraType,
ivs: dexEntry.ivs,
};
this.starters.push(starter);
this.starterSpecies.push(species); this.starterSpecies.push(species);
this.starterAttr.push(dexAttr);
this.starterAbilityIndexes.push(abilityIndex);
this.starterNatures.push(nature);
this.starterTeras.push(teraType);
this.starterMovesets.push(moveset);
if (this.speciesLoaded.get(species.speciesId) || randomSelection) { if (this.speciesLoaded.get(species.speciesId) || randomSelection) {
getPokemonSpeciesForm(species.speciesId, props.formIndex).cry(); getPokemonSpeciesForm(species.speciesId, props.formIndex).cry();
} }
@ -2833,7 +2833,7 @@ export class StarterSelectUiHandler extends MessageUiHandler {
for (const [index, species] of this.starterSpecies.entries()) { for (const [index, species] of this.starterSpecies.entries()) {
if (species.speciesId === id) { if (species.speciesId === id) {
this.starterMovesets[index] = this.starterMoveset; this.starters[index].moveset = this.starterMoveset;
} }
} }
} }
@ -3640,20 +3640,20 @@ export class StarterSelectUiHandler extends MessageUiHandler {
const starterIndex = this.starterSpecies.indexOf(species); const starterIndex = this.starterSpecies.indexOf(species);
let props: DexAttrProps; const props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
if (starterIndex > -1) { if (starterIndex > -1) {
props = globalScene.gameData.getSpeciesDexAttrProps(species, this.starterAttr[starterIndex]); const starter = this.starters[starterIndex];
this.setSpeciesDetails( this.setSpeciesDetails(
species, species,
{ {
shiny: props.shiny, shiny: starter.shiny,
formIndex: props.formIndex, formIndex: starter.formIndex,
female: props.female, female: starter.female,
variant: props.variant, variant: starter.variant,
abilityIndex: this.starterAbilityIndexes[starterIndex], abilityIndex: starter.abilityIndex,
natureIndex: this.starterNatures[starterIndex], natureIndex: starter.nature,
teraType: this.starterTeras[starterIndex], teraType: starter.teraType,
}, },
false, false,
); );
@ -3664,7 +3664,6 @@ export class StarterSelectUiHandler extends MessageUiHandler {
const { dexEntry } = this.getSpeciesData(species.speciesId); const { dexEntry } = this.getSpeciesData(species.speciesId);
const defaultNature = const defaultNature =
starterAttributes?.nature || globalScene.gameData.getSpeciesDefaultNature(species, dexEntry); starterAttributes?.nature || globalScene.gameData.getSpeciesDefaultNature(species, dexEntry);
props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
if (starterAttributes?.variant && !Number.isNaN(starterAttributes.variant) && props.shiny) { if (starterAttributes?.variant && !Number.isNaN(starterAttributes.variant) && props.shiny) {
props.variant = starterAttributes.variant as Variant; props.variant = starterAttributes.variant as Variant;
} }
@ -3910,10 +3909,15 @@ export class StarterSelectUiHandler extends MessageUiHandler {
const starterIndex = this.starterSpecies.indexOf(species); const starterIndex = this.starterSpecies.indexOf(species);
if (starterIndex > -1) { if (starterIndex > -1) {
this.starterAttr[starterIndex] = this.dexAttrCursor; const starter = this.starters[starterIndex];
this.starterAbilityIndexes[starterIndex] = this.abilityCursor; const props = globalScene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor);
this.starterNatures[starterIndex] = this.natureCursor; starter.shiny = props.shiny;
this.starterTeras[starterIndex] = this.teraCursor; starter.variant = props.variant;
starter.female = props.female;
starter.formIndex = props.formIndex;
starter.abilityIndex = this.abilityCursor;
starter.nature = this.natureCursor;
starter.teraType = this.teraCursor;
} }
const assetLoadCancelled = new BooleanHolder(false); const assetLoadCancelled = new BooleanHolder(false);
@ -4215,11 +4219,7 @@ export class StarterSelectUiHandler extends MessageUiHandler {
popStarter(index: number): void { popStarter(index: number): void {
this.starterSpecies.splice(index, 1); this.starterSpecies.splice(index, 1);
this.starterAttr.splice(index, 1); this.starters.splice(index, 1);
this.starterAbilityIndexes.splice(index, 1);
this.starterNatures.splice(index, 1);
this.starterTeras.splice(index, 1);
this.starterMovesets.splice(index, 1);
for (let s = 0; s < this.starterSpecies.length; s++) { for (let s = 0; s < this.starterSpecies.length; s++) {
const species = this.starterSpecies[s]; const species = this.starterSpecies[s];
@ -4443,27 +4443,11 @@ export class StarterSelectUiHandler extends MessageUiHandler {
() => { () => {
const startRun = () => { const startRun = () => {
globalScene.money = globalScene.gameMode.getStartingMoney(); globalScene.money = globalScene.gameMode.getStartingMoney();
const starters = this.starters.slice(0);
ui.setMode(UiMode.STARTER_SELECT); ui.setMode(UiMode.STARTER_SELECT);
const thisObj = this;
const originalStarterSelectCallback = this.starterSelectCallback; const originalStarterSelectCallback = this.starterSelectCallback;
this.starterSelectCallback = null; this.starterSelectCallback = null;
originalStarterSelectCallback?.( originalStarterSelectCallback?.(starters);
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: !(starterDataEntry.passiveAttr ^ (PassiveAttr.ENABLED | PassiveAttr.UNLOCKED)),
nature: thisObj.starterNatures[i] as Nature,
teraType: thisObj.starterTeras[i] as PokemonType,
moveset: thisObj.starterMovesets[i],
pokerus: thisObj.pokerusSpecies.includes(starterSpecies),
nickname: thisObj.starterPreferences[starterSpecies.speciesId]?.nickname,
};
}),
);
}; };
startRun(); startRun();
}, },
@ -4492,10 +4476,17 @@ export class StarterSelectUiHandler extends MessageUiHandler {
*/ */
isPartyValid(): boolean { isPartyValid(): boolean {
let canStart = false; let canStart = false;
for (const species of this.starterSpecies) { for (let s = 0; s < this.starterSpecies.length; s++) {
const species = this.starterSpecies[s];
const starter = this.starters[s];
const isValidForChallenge = checkStarterValidForChallenge( const isValidForChallenge = checkStarterValidForChallenge(
species, species,
globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), {
formIndex: starter.formIndex,
shiny: starter.shiny,
variant: starter.variant,
female: starter.female ?? false,
},
false, false,
); );
canStart ||= isValidForChallenge; canStart ||= isValidForChallenge;

View File

@ -168,6 +168,7 @@ describe("Part-Timer - Mystery Encounter", () => {
// Override party levels to 50 so stats can be fully reflective // Override party levels to 50 so stats can be fully reflective
scene.getPlayerParty().forEach(p => { scene.getPlayerParty().forEach(p => {
p.level = 50; p.level = 50;
p.ivs = [0, 0, 0, 0, 0, 0];
p.calculateStats(); p.calculateStats();
}); });
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 3 }); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 3 });

View File

@ -8,8 +8,7 @@ import { GameModes } from "#enums/game-modes";
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
import type { SpeciesId } from "#enums/species-id"; import type { SpeciesId } from "#enums/species-id";
import { PlayerPokemon } from "#field/pokemon"; import { PlayerPokemon } from "#field/pokemon";
import type { StarterMoveset } from "#types/save-data"; import type { Starter, StarterMoveset } from "#types/save-data";
import type { Starter } from "#ui/starter-select-ui-handler";
import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils";
/** Function to convert Blob to string */ /** Function to convert Blob to string */
@ -33,24 +32,24 @@ export function holdOn(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
} }
export function generateStarter(scene: BattleScene, species?: SpeciesId[]): Starter[] { export function generateStarters(scene: BattleScene, speciesIds?: SpeciesId[]): Starter[] {
const seed = "test"; const seed = "test";
const starters = getTestRunStarters(seed, species); const starters = getTestRunStarters(seed, speciesIds);
const startingLevel = scene.gameMode.getStartingLevel(); const startingLevel = scene.gameMode.getStartingLevel();
for (const starter of starters) { for (const starter of starters) {
const starterProps = scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); const species = getPokemonSpecies(starter.speciesId);
const starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); const starterFormIndex = starter.formIndex;
const starterGender = const starterGender =
starter.species.malePercent !== null ? (!starterProps.female ? Gender.MALE : Gender.FEMALE) : Gender.GENDERLESS; species.malePercent !== null ? (starter.female ? Gender.FEMALE : Gender.MALE) : Gender.GENDERLESS;
const starterPokemon = scene.addPlayerPokemon( const starterPokemon = scene.addPlayerPokemon(
starter.species, species,
startingLevel, startingLevel,
starter.abilityIndex, starter.abilityIndex,
starterFormIndex, starterFormIndex,
starterGender, starterGender,
starterProps.shiny, starter.shiny,
starterProps.variant, starter.variant,
undefined, starter.ivs,
starter.nature, starter.nature,
); );
const moveset: MoveId[] = []; const moveset: MoveId[] = [];
@ -62,20 +61,23 @@ export function generateStarter(scene: BattleScene, species?: SpeciesId[]): Star
return starters; return starters;
} }
function getTestRunStarters(seed: string, species?: SpeciesId[]): Starter[] { function getTestRunStarters(seed: string, speciesIds?: SpeciesId[]): Starter[] {
if (!species) { if (!speciesIds || speciesIds.length === 0) {
return getDailyRunStarters(seed); return getDailyRunStarters(seed);
} }
const starters: Starter[] = []; const starters: Starter[] = [];
const startingLevel = getGameMode(GameModes.CLASSIC).getStartingLevel(); const startingLevel = getGameMode(GameModes.CLASSIC).getStartingLevel();
for (const specie of species) { for (const speciesId of speciesIds) {
const starterSpeciesForm = getPokemonSpeciesForm(specie, 0); const starterSpeciesForm = getPokemonSpeciesForm(speciesId, 0);
const starterSpecies = getPokemonSpecies(starterSpeciesForm.speciesId); const starterSpecies = getPokemonSpecies(starterSpeciesForm.speciesId);
const pokemon = new PlayerPokemon(starterSpecies, startingLevel, undefined, 0); const pokemon = new PlayerPokemon(starterSpecies, startingLevel, undefined, 0);
const starter: Starter = { const starter: Starter = {
species: starterSpecies, speciesId,
dexAttr: pokemon.getDexAttr(), shiny: pokemon.shiny,
variant: pokemon.variant,
formIndex: pokemon.formIndex,
ivs: pokemon.ivs,
abilityIndex: pokemon.abilityIndex, abilityIndex: pokemon.abilityIndex,
passive: false, passive: false,
nature: pokemon.getNature(), nature: pokemon.getNature(),
@ -89,22 +91,20 @@ function getTestRunStarters(seed: string, species?: SpeciesId[]): Starter[] {
/** /**
* Useful for populating party, wave index, etc. without having to spin up and run through an entire EncounterPhase * Useful for populating party, wave index, etc. without having to spin up and run through an entire EncounterPhase
*/ */
export function initSceneWithoutEncounterPhase(scene: BattleScene, species?: SpeciesId[]): void { export function initSceneWithoutEncounterPhase(scene: BattleScene, speciesIds?: SpeciesId[]): void {
const starters = generateStarter(scene, species); const starters = generateStarters(scene, speciesIds);
starters.forEach(starter => { starters.forEach(starter => {
const starterProps = scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); const starterFormIndex = starter.formIndex;
const starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0));
const starterGender = Gender.MALE; const starterGender = Gender.MALE;
const starterIvs = scene.gameData.dexData[starter.species.speciesId].ivs.slice(0);
const starterPokemon = scene.addPlayerPokemon( const starterPokemon = scene.addPlayerPokemon(
starter.species, getPokemonSpecies(starter.speciesId),
scene.gameMode.getStartingLevel(), scene.gameMode.getStartingLevel(),
starter.abilityIndex, starter.abilityIndex,
starterFormIndex, starterFormIndex,
starterGender, starterGender,
starterProps.shiny, starter.shiny,
starterProps.variant, starter.variant,
starterIvs, starter.ivs,
starter.nature, starter.nature,
); );
starter.moveset && starterPokemon.tryPopulateMoveset(starter.moveset); starter.moveset && starterPokemon.tryPopulateMoveset(starter.moveset);

View File

@ -29,7 +29,7 @@ import { TurnEndPhase } from "#phases/turn-end-phase";
import { TurnInitPhase } from "#phases/turn-init-phase"; import { TurnInitPhase } from "#phases/turn-init-phase";
import { TurnStartPhase } from "#phases/turn-start-phase"; import { TurnStartPhase } from "#phases/turn-start-phase";
import { ErrorInterceptor } from "#test/test-utils/error-interceptor"; import { ErrorInterceptor } from "#test/test-utils/error-interceptor";
import { generateStarter } from "#test/test-utils/game-manager-utils"; import { generateStarters } from "#test/test-utils/game-manager-utils";
import { GameWrapper } from "#test/test-utils/game-wrapper"; import { GameWrapper } from "#test/test-utils/game-wrapper";
import { ChallengeModeHelper } from "#test/test-utils/helpers/challenge-mode-helper"; import { ChallengeModeHelper } from "#test/test-utils/helpers/challenge-mode-helper";
import { ClassicModeHelper } from "#test/test-utils/helpers/classic-mode-helper"; import { ClassicModeHelper } from "#test/test-utils/helpers/classic-mode-helper";
@ -215,7 +215,7 @@ export class GameManager {
this.onNextPrompt("TitlePhase", UiMode.TITLE, () => { this.onNextPrompt("TitlePhase", UiMode.TITLE, () => {
this.scene.gameMode = getGameMode(mode); this.scene.gameMode = getGameMode(mode);
const starters = generateStarter(this.scene, species); const starters = generateStarters(this.scene, species);
const selectStarterPhase = new SelectStarterPhase(); const selectStarterPhase = new SelectStarterPhase();
this.scene.phaseManager.pushPhase(new EncounterPhase(false)); this.scene.phaseManager.pushPhase(new EncounterPhase(false));
selectStarterPhase.initBattle(starters); selectStarterPhase.initBattle(starters);
@ -251,7 +251,7 @@ export class GameManager {
UiMode.TITLE, UiMode.TITLE,
() => { () => {
this.scene.gameMode = getGameMode(GameModes.CLASSIC); this.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(this.scene, species); const starters = generateStarters(this.scene, species);
const selectStarterPhase = new SelectStarterPhase(); const selectStarterPhase = new SelectStarterPhase();
this.scene.phaseManager.pushPhase(new EncounterPhase(false)); this.scene.phaseManager.pushPhase(new EncounterPhase(false));
selectStarterPhase.initBattle(starters); selectStarterPhase.initBattle(starters);

View File

@ -9,7 +9,7 @@ import { CommandPhase } from "#phases/command-phase";
import { EncounterPhase } from "#phases/encounter-phase"; import { EncounterPhase } from "#phases/encounter-phase";
import { SelectStarterPhase } from "#phases/select-starter-phase"; import { SelectStarterPhase } from "#phases/select-starter-phase";
import { TurnInitPhase } from "#phases/turn-init-phase"; import { TurnInitPhase } from "#phases/turn-init-phase";
import { generateStarter } from "#test/test-utils/game-manager-utils"; import { generateStarters } from "#test/test-utils/game-manager-utils";
import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper"; import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper";
/** /**
@ -34,7 +34,7 @@ export class ChallengeModeHelper extends GameManagerHelper {
* @param gameMode - Optional game mode to set. * @param gameMode - Optional game mode to set.
* @returns A promise that resolves when the summon phase is reached. * @returns A promise that resolves when the summon phase is reached.
*/ */
async runToSummon(species?: SpeciesId[]) { async runToSummon(speciesIds?: SpeciesId[]) {
await this.game.runToTitle(); await this.game.runToTitle();
if (this.game.override.disableShinies) { if (this.game.override.disableShinies) {
@ -43,7 +43,7 @@ export class ChallengeModeHelper extends GameManagerHelper {
this.game.onNextPrompt("TitlePhase", UiMode.TITLE, () => { this.game.onNextPrompt("TitlePhase", UiMode.TITLE, () => {
this.game.scene.gameMode.challenges = this.challenges; this.game.scene.gameMode.challenges = this.challenges;
const starters = generateStarter(this.game.scene, species); const starters = generateStarters(this.game.scene, speciesIds);
const selectStarterPhase = new SelectStarterPhase(); const selectStarterPhase = new SelectStarterPhase();
this.game.scene.phaseManager.pushPhase(new EncounterPhase(false)); this.game.scene.phaseManager.pushPhase(new EncounterPhase(false));
selectStarterPhase.initBattle(starters); selectStarterPhase.initBattle(starters);

View File

@ -10,7 +10,7 @@ import { CommandPhase } from "#phases/command-phase";
import { EncounterPhase } from "#phases/encounter-phase"; import { EncounterPhase } from "#phases/encounter-phase";
import { SelectStarterPhase } from "#phases/select-starter-phase"; import { SelectStarterPhase } from "#phases/select-starter-phase";
import { TurnInitPhase } from "#phases/turn-init-phase"; import { TurnInitPhase } from "#phases/turn-init-phase";
import { generateStarter } from "#test/test-utils/game-manager-utils"; import { generateStarters } from "#test/test-utils/game-manager-utils";
import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper"; import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper";
/** /**
@ -35,7 +35,7 @@ export class ClassicModeHelper extends GameManagerHelper {
// biome-ignore lint/style/useUnifiedTypeSignatures: Marks the overload for deprecation // biome-ignore lint/style/useUnifiedTypeSignatures: Marks the overload for deprecation
async runToSummon(): Promise<void>; async runToSummon(): Promise<void>;
async runToSummon(species: SpeciesId[] | undefined): Promise<void>; async runToSummon(species: SpeciesId[] | undefined): Promise<void>;
async runToSummon(species?: SpeciesId[]): Promise<void> { async runToSummon(speciesIds?: SpeciesId[]): Promise<void> {
await this.game.runToTitle(); await this.game.runToTitle();
if (this.game.override.disableShinies) { if (this.game.override.disableShinies) {
@ -50,7 +50,7 @@ export class ClassicModeHelper extends GameManagerHelper {
this.game.onNextPrompt("TitlePhase", UiMode.TITLE, () => { this.game.onNextPrompt("TitlePhase", UiMode.TITLE, () => {
this.game.scene.gameMode = getGameMode(GameModes.CLASSIC); this.game.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(this.game.scene, species); const starters = generateStarters(this.game.scene, speciesIds);
const selectStarterPhase = new SelectStarterPhase(); const selectStarterPhase = new SelectStarterPhase();
this.game.scene.phaseManager.pushPhase(new EncounterPhase(false)); this.game.scene.phaseManager.pushPhase(new EncounterPhase(false));
selectStarterPhase.initBattle(starters); selectStarterPhase.initBattle(starters);