mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-12-15 06:15:20 +01:00
[Misc] Allow setting shiny starters via custom seed by
[Misc] Allow setting shiny starters via custom seed
This commit is contained in:
commit
6ed9c88cf4
@ -58,11 +58,19 @@ export function getDailyRunStarters(seed: string): StarterTuple {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Refactor this unmaintainable mess
|
// TODO: Refactor this unmaintainable mess
|
||||||
function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLevel: number): Starter {
|
function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLevel: number, variant?: Variant): Starter {
|
||||||
const starterSpecies =
|
const starterSpecies =
|
||||||
starterSpeciesForm instanceof PokemonSpecies ? starterSpeciesForm : getPokemonSpecies(starterSpeciesForm.speciesId);
|
starterSpeciesForm instanceof PokemonSpecies ? starterSpeciesForm : getPokemonSpecies(starterSpeciesForm.speciesId);
|
||||||
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,
|
||||||
|
undefined,
|
||||||
|
variant != null,
|
||||||
|
variant,
|
||||||
|
);
|
||||||
const starter: Starter = {
|
const starter: Starter = {
|
||||||
speciesId: starterSpecies.speciesId,
|
speciesId: starterSpecies.speciesId,
|
||||||
shiny: pokemon.shiny,
|
shiny: pokemon.shiny,
|
||||||
@ -172,7 +180,11 @@ export function isDailyEventSeed(seed: string): boolean {
|
|||||||
* Must be updated whenever the `MoveId` enum gets a new digit!
|
* Must be updated whenever the `MoveId` enum gets a new digit!
|
||||||
*/
|
*/
|
||||||
const MOVE_ID_STRING_LENGTH = 4;
|
const MOVE_ID_STRING_LENGTH = 4;
|
||||||
|
/**
|
||||||
|
* The regex literal used to parse daily run custom movesets.
|
||||||
|
* @privateRemarks
|
||||||
|
* Intentionally does not use the `g` flag to avoid altering `lastIndex` after each match.
|
||||||
|
*/
|
||||||
const MOVE_ID_SEED_REGEX = /(?<=\/moves)((?:\d{4}){0,4})(?:,((?:\d{4}){0,4}))?(?:,((?:\d{4}){0,4}))?/;
|
const MOVE_ID_SEED_REGEX = /(?<=\/moves)((?:\d{4}){0,4})(?:,((?:\d{4}){0,4}))?(?:,((?:\d{4}){0,4}))?/;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -215,14 +227,106 @@ function setDailyRunEventStarterMovesets(seed: string, starters: StarterTuple):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The regex literal string used to extract the content of the "starters" block of Daily Run custom seeds. */
|
||||||
|
const STARTER_SEED_PREFIX_REGEX = /\/starters(.*?)(?:\/|$)/;
|
||||||
|
/**
|
||||||
|
* The regex literal used to parse daily run custom starter information for a single starter. \
|
||||||
|
* Contains a 4-digit species ID, as well as an optional 2-digit form index and 1-digit variant.
|
||||||
|
*
|
||||||
|
* If either of form index or variant are omitted, the starter will default to its species' base form/
|
||||||
|
* not be shiny, respectively.
|
||||||
|
*/
|
||||||
|
const STARTER_SEED_MATCH_REGEX = /(?:s(?<species>\d{4}))(?:f(?<form>\d{2}))?(?:v(?<variant>\d))?/g;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a custom daily run seed into a set of pre-defined starters.
|
||||||
|
* @see {@linkcode STARTER_SEED_MATCH_REGEX}
|
||||||
|
* @param seed - The daily run seed
|
||||||
|
* @returns An array of {@linkcode Starter}s, or `null` if it did not match.
|
||||||
|
*/
|
||||||
|
// TODO: Rework this setup into JSON or similar - this is quite hard to maintain
|
||||||
|
function getDailyEventSeedStarters(seed: string): StarterTuple | null {
|
||||||
|
if (!isDailyEventSeed(seed)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const seedAfterPrefix = seed.split(STARTER_SEED_PREFIX_REGEX)[1] as string | undefined;
|
||||||
|
if (!seedAfterPrefix) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const speciesConfigurations = [...seedAfterPrefix.matchAll(STARTER_SEED_MATCH_REGEX)];
|
||||||
|
|
||||||
|
if (speciesConfigurations.length !== 3) {
|
||||||
|
// TODO: Remove legacy fallback code after next hotfix version - this is needed for Oct 31's daily to function
|
||||||
|
const legacyStarters = getDailyEventSeedStartersLegacy(seed);
|
||||||
|
if (legacyStarters == null) {
|
||||||
|
return legacyStarters;
|
||||||
|
}
|
||||||
|
console.error("Invalid starters used for custom daily run seed!", seed);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const speciesIds = getEnumValues(SpeciesId);
|
||||||
|
const starters: Starter[] = [];
|
||||||
|
|
||||||
|
for (const match of speciesConfigurations) {
|
||||||
|
const { groups } = match;
|
||||||
|
if (!groups) {
|
||||||
|
console.error("Invalid seed used for custom daily run starter:", match);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { species: speciesStr, form: formStr, variant: variantStr } = groups;
|
||||||
|
|
||||||
|
const speciesId = Number.parseInt(speciesStr) as SpeciesId;
|
||||||
|
|
||||||
|
// NB: We check the parsed integer here to exclude SpeciesID.NONE as well as invalid values;
|
||||||
|
// other fields only check the string to permit 0 as valid inputs
|
||||||
|
if (!speciesId || !speciesIds.includes(speciesId)) {
|
||||||
|
console.error("Invalid species ID used for custom daily run starter:", speciesStr);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const starterSpecies = getPokemonSpecies(speciesId);
|
||||||
|
// Omitted form index = use base form
|
||||||
|
const starterForm = formStr ? starterSpecies.forms[Number.parseInt(formStr)] : starterSpecies;
|
||||||
|
|
||||||
|
if (!starterForm) {
|
||||||
|
console.log(starterSpecies.name);
|
||||||
|
console.error("Invalid form index used for custom daily run starter:", formStr);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get and validate variant
|
||||||
|
let variant = (variantStr ? Number.parseInt(variantStr) : undefined) as Variant | undefined;
|
||||||
|
if (!isBetween(variant ?? 0, 0, 2)) {
|
||||||
|
console.error("Variant used for custom daily run seed starter out of bounds:", variantStr);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to default variant if none exists
|
||||||
|
if (!starterSpecies.hasVariants() && !!variant) {
|
||||||
|
console.warn("Variant for custom daily run seed starter does not exist, using base variant...", variant);
|
||||||
|
variant = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startingLevel = globalScene.gameMode.getStartingLevel();
|
||||||
|
const starter = getDailyRunStarter(starterForm, startingLevel, variant);
|
||||||
|
starters.push(starter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return starters as StarterTuple;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expects the seed to contain `/starters\d{18}/`
|
* Expects the seed to contain `/starters\d{18}/`
|
||||||
* where the digits alternate between 4 digits for the species ID and 2 digits for the form index
|
* where the digits alternate between 4 digits for the species ID and 2 digits for the form index
|
||||||
* (left padded with `0`s as necessary).
|
* (left padded with `0`s as necessary).
|
||||||
* @returns An array of {@linkcode Starter}s, or `null` if no valid match.
|
* @returns An array of {@linkcode Starter}s, or `null` if no valid match.
|
||||||
*/
|
*/
|
||||||
// TODO: Rework this setup into JSON or similar - this is quite hard to maintain
|
// TODO: Can be removed after october 31st 2025
|
||||||
export function getDailyEventSeedStarters(seed: string): StarterTuple | null {
|
function getDailyEventSeedStartersLegacy(seed: string): StarterTuple | null {
|
||||||
if (!isDailyEventSeed(seed)) {
|
if (!isDailyEventSeed(seed)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,11 +118,9 @@ export function getFusedSpeciesName(speciesAName: string, speciesBName: string):
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): PokemonSpeciesForm {
|
export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): PokemonSpeciesForm {
|
||||||
const retSpecies: PokemonSpecies =
|
const retSpecies: PokemonSpecies = getPokemonSpecies(species);
|
||||||
species >= 2000
|
|
||||||
? allSpecies.find(s => s.speciesId === species)! // TODO: is the bang correct?
|
if (formIndex < retSpecies.forms.length) {
|
||||||
: allSpecies[species - 1];
|
|
||||||
if (formIndex < retSpecies.forms?.length) {
|
|
||||||
return retSpecies.forms[formIndex];
|
return retSpecies.forms[formIndex];
|
||||||
}
|
}
|
||||||
return retSpecies;
|
return retSpecies;
|
||||||
|
|||||||
@ -21,6 +21,8 @@ describe("Daily Mode", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
|
|
||||||
|
game.override.disableShinies = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -41,52 +43,85 @@ describe("Daily Mode", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Custom Seeds", () => {
|
describe("Custom Seeds", () => {
|
||||||
it("should support custom moves", async () => {
|
describe("Moves", () => {
|
||||||
vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("/moves0001000200030004,03320006,01300919");
|
it("should support custom moves", async () => {
|
||||||
await game.dailyMode.startBattle();
|
vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("/moves0001000200030004,03320006,01300919");
|
||||||
|
await game.dailyMode.startBattle();
|
||||||
|
|
||||||
const [moves1, moves2, moves3] = game.scene.getPlayerParty().map(p => p.moveset.map(pm => pm.moveId));
|
const [moves1, moves2, moves3] = game.scene.getPlayerParty().map(p => p.moveset.map(pm => pm.moveId));
|
||||||
expect(moves1, stringifyEnumArray(MoveId, moves1)).toEqual([
|
expect(moves1, stringifyEnumArray(MoveId, moves1)).toEqual([
|
||||||
MoveId.POUND,
|
MoveId.POUND,
|
||||||
MoveId.KARATE_CHOP,
|
MoveId.KARATE_CHOP,
|
||||||
MoveId.DOUBLE_SLAP,
|
MoveId.DOUBLE_SLAP,
|
||||||
MoveId.COMET_PUNCH,
|
MoveId.COMET_PUNCH,
|
||||||
]);
|
]);
|
||||||
expect(moves2, stringifyEnumArray(MoveId, moves2)).toEqual([
|
expect(moves2, stringifyEnumArray(MoveId, moves2)).toEqual([
|
||||||
MoveId.AERIAL_ACE,
|
MoveId.AERIAL_ACE,
|
||||||
MoveId.PAY_DAY,
|
MoveId.PAY_DAY,
|
||||||
expect.anything(), // make sure it doesn't replace normal moveset gen
|
expect.anything(), // make sure it doesn't replace normal moveset gen
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
]);
|
]);
|
||||||
expect(moves3, stringifyEnumArray(MoveId, moves3)).toEqual([
|
expect(moves3, stringifyEnumArray(MoveId, moves3)).toEqual([
|
||||||
MoveId.SKULL_BASH,
|
MoveId.SKULL_BASH,
|
||||||
MoveId.MALIGNANT_CHAIN,
|
MoveId.MALIGNANT_CHAIN,
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
]);
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow omitting movesets for some starters", async () => {
|
||||||
|
vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("/moves0001000200030004");
|
||||||
|
await game.dailyMode.startBattle();
|
||||||
|
|
||||||
|
const [moves1, moves2, moves3] = game.scene.getPlayerParty().map(p => p.moveset.map(pm => pm.moveId));
|
||||||
|
expect(moves1, stringifyEnumArray(MoveId, moves1)).toEqual([
|
||||||
|
MoveId.POUND,
|
||||||
|
MoveId.KARATE_CHOP,
|
||||||
|
MoveId.DOUBLE_SLAP,
|
||||||
|
MoveId.COMET_PUNCH,
|
||||||
|
]);
|
||||||
|
expect(moves2, "was not a random moveset").toHaveLength(4);
|
||||||
|
expect(moves3, "was not a random moveset").toHaveLength(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should skip invalid move IDs", async () => {
|
||||||
|
vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("/moves9999,,0919");
|
||||||
|
await game.dailyMode.startBattle();
|
||||||
|
|
||||||
|
const moves = game.field.getPlayerPokemon().moveset.map(pm => pm.moveId);
|
||||||
|
expect(moves, "invalid move was in moveset").not.toContain(MoveId[9999]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow omitting movesets for some starters", async () => {
|
describe("Starters", () => {
|
||||||
vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("/moves0001000200030004");
|
it("should support custom species IDs", async () => {
|
||||||
await game.dailyMode.startBattle();
|
vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("foo/starterss0001s0113s1024");
|
||||||
|
await game.dailyMode.startBattle();
|
||||||
|
|
||||||
const [moves1, moves2, moves3] = game.scene.getPlayerParty().map(p => p.moveset.map(pm => pm.moveId));
|
const party = game.scene.getPlayerParty().map(p => p.species.speciesId);
|
||||||
expect(moves1, stringifyEnumArray(MoveId, moves1)).toEqual([
|
expect(party, stringifyEnumArray(SpeciesId, party)).toEqual([
|
||||||
MoveId.POUND,
|
SpeciesId.BULBASAUR,
|
||||||
MoveId.KARATE_CHOP,
|
SpeciesId.CHANSEY,
|
||||||
MoveId.DOUBLE_SLAP,
|
SpeciesId.TERAPAGOS,
|
||||||
MoveId.COMET_PUNCH,
|
]);
|
||||||
]);
|
});
|
||||||
expect(moves2, "was not a random moveset").toHaveLength(4);
|
|
||||||
expect(moves3, "was not a random moveset").toHaveLength(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should skip invalid move IDs", async () => {
|
it("should support custom forms and variants", async () => {
|
||||||
vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("/moves9999,,0919");
|
vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("/starterss0006f01v2s0113v0s1024f02");
|
||||||
await game.dailyMode.startBattle();
|
await game.dailyMode.startBattle();
|
||||||
|
|
||||||
const moves = game.field.getPlayerPokemon().moveset.map(pm => pm.moveId);
|
const party = game.scene.getPlayerParty().map(p => ({
|
||||||
expect(moves, "invalid move was in moveset").not.toContain(MoveId[9999]);
|
speciesId: p.species.speciesId,
|
||||||
|
variant: p.getVariant(),
|
||||||
|
form: p.formIndex,
|
||||||
|
shiny: p.isShiny(),
|
||||||
|
}));
|
||||||
|
expect(party).toEqual<typeof party>([
|
||||||
|
{ speciesId: SpeciesId.CHARIZARD, variant: 2, form: 1, shiny: true },
|
||||||
|
{ speciesId: SpeciesId.CHANSEY, variant: 0, form: 0, shiny: true },
|
||||||
|
{ speciesId: SpeciesId.TERAPAGOS, variant: expect.anything(), form: 2, shiny: false },
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user