Merge branch 'beta' into shields-down-tests

This commit is contained in:
Bertie690 2025-08-17 09:32:57 -04:00 committed by GitHub
commit e28d0f7fd9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 530 additions and 472 deletions

View File

@ -15,8 +15,6 @@
"test:silent": "vitest run --silent='passed-only' --no-isolate",
"test:create": "node scripts/create-test/create-test.js",
"typecheck": "tsc --noEmit",
"eslint": "eslint --fix .",
"eslint-ci": "eslint .",
"biome": "biome check --write --changed --no-errors-on-unmatched --diagnostic-level=error",
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched",
"docs": "typedoc",
@ -29,6 +27,7 @@
"devDependencies": {
"@biomejs/biome": "2.0.0",
"@ls-lint/ls-lint": "2.3.1",
"@types/crypto-js": "^4.2.0",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.16.5",
"@vitest/coverage-istanbul": "^3.2.4",

View File

@ -48,6 +48,9 @@ importers:
'@ls-lint/ls-lint':
specifier: 2.3.1
version: 2.3.1
'@types/crypto-js':
specifier: ^4.2.0
version: 4.2.2
'@types/jsdom':
specifier: ^21.1.7
version: 21.1.7
@ -718,6 +721,9 @@ packages:
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
'@types/crypto-js@4.2.2':
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
@ -2525,6 +2531,8 @@ snapshots:
'@types/cookie@0.6.0': {}
'@types/crypto-js@4.2.2': {}
'@types/deep-eql@4.0.2': {}
'@types/estree@1.0.8': {}

@ -1 +1 @@
Subproject commit ab2716d5440c25f73986664aa3f3131821c3c392
Subproject commit 1ea8f865e30d1940caa0fceeabf37ae2e4689471

View File

@ -17,45 +17,42 @@ export function initLoggedInUser(): void {
};
}
export function updateUserInfo(): Promise<[boolean, number]> {
return new Promise<[boolean, number]>(resolve => {
if (bypassLogin) {
loggedInUser = {
username: "Guest",
lastSessionSlot: -1,
discordId: "",
googleId: "",
hasAdminRole: false,
};
let lastSessionSlot = -1;
for (let s = 0; s < 5; s++) {
if (localStorage.getItem(`sessionData${s ? s : ""}_${loggedInUser.username}`)) {
lastSessionSlot = s;
break;
}
export async function updateUserInfo(): Promise<[boolean, number]> {
if (bypassLogin) {
loggedInUser = {
username: "Guest",
lastSessionSlot: -1,
discordId: "",
googleId: "",
hasAdminRole: false,
};
let lastSessionSlot = -1;
for (let s = 0; s < 5; s++) {
if (localStorage.getItem(`sessionData${s ? s : ""}_${loggedInUser.username}`)) {
lastSessionSlot = s;
break;
}
loggedInUser.lastSessionSlot = lastSessionSlot;
// Migrate old data from before the username was appended
["data", "sessionData", "sessionData1", "sessionData2", "sessionData3", "sessionData4"].map(d => {
const lsItem = localStorage.getItem(d);
if (lsItem && !!loggedInUser?.username) {
const lsUserItem = localStorage.getItem(`${d}_${loggedInUser.username}`);
if (lsUserItem) {
localStorage.setItem(`${d}_${loggedInUser.username}_bak`, lsUserItem);
}
localStorage.setItem(`${d}_${loggedInUser.username}`, lsItem);
localStorage.removeItem(d);
}
});
return resolve([true, 200]);
}
pokerogueApi.account.getInfo().then(([accountInfo, status]) => {
if (!accountInfo) {
resolve([false, status]);
return;
loggedInUser.lastSessionSlot = lastSessionSlot;
// Migrate old data from before the username was appended
["data", "sessionData", "sessionData1", "sessionData2", "sessionData3", "sessionData4"].forEach(d => {
const lsItem = localStorage.getItem(d);
if (lsItem && !!loggedInUser?.username) {
const lsUserItem = localStorage.getItem(`${d}_${loggedInUser.username}`);
if (lsUserItem) {
localStorage.setItem(`${d}_${loggedInUser.username}_bak`, lsUserItem);
}
localStorage.setItem(`${d}_${loggedInUser.username}`, lsItem);
localStorage.removeItem(d);
}
loggedInUser = accountInfo;
resolve([true, 200]);
});
});
return [true, 200];
}
const [accountInfo, status] = await pokerogueApi.account.getInfo();
if (!accountInfo) {
return [false, status];
}
loggedInUser = accountInfo;
return [true, 200];
}

View File

@ -27,13 +27,7 @@ import { UiInputs } from "#app/ui-inputs";
import { biomeDepths, getBiomeName } from "#balance/biomes";
import { pokemonPrevolutions } from "#balance/pokemon-evolutions";
import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#balance/starters";
import {
initCommonAnims,
initMoveAnim,
loadCommonAnimAssets,
loadMoveAnimAssets,
populateAnims,
} from "#data/battle-anims";
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets } from "#data/battle-anims";
import { allAbilities, allMoves, allSpecies, modifierTypes } from "#data/data-lists";
import { battleSpecDialogue } from "#data/dialogue";
import type { SpeciesFormChangeTrigger } from "#data/form-change-triggers";
@ -388,7 +382,6 @@ export class BattleScene extends SceneBase {
const defaultMoves = [MoveId.TACKLE, MoveId.TAIL_WHIP, MoveId.FOCUS_ENERGY, MoveId.STRUGGLE];
await Promise.all([
populateAnims(),
this.initVariantData(),
initCommonAnims().then(() => loadCommonAnimAssets(true)),
Promise.all(defaultMoves.map(m => initMoveAnim(m))).then(() => loadMoveAnimAssets(defaultMoves, true)),

View File

@ -1505,9 +1505,9 @@ export function initSpecies() {
new PokemonForm("Hero of Many Battles", "hero-of-many-battles", PokemonType.FIGHTING, null, 2.9, 210, AbilityId.DAUNTLESS_SHIELD, AbilityId.NONE, AbilityId.NONE, 660, 92, 120, 115, 80, 115, 138, 10, 0, 335, false, "", true),
new PokemonForm("Crowned", "crowned", PokemonType.FIGHTING, PokemonType.STEEL, 2.9, 785, AbilityId.DAUNTLESS_SHIELD, AbilityId.NONE, AbilityId.NONE, 700, 92, 120, 140, 80, 140, 128, 10, 0, 360)
),
new PokemonSpecies(SpeciesId.ETERNATUS, 8, false, true, false, "Gigantic Pokémon", PokemonType.POISON, PokemonType.DRAGON, 20, 950, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 690, 140, 85, 95, 145, 95, 130, 255, 0, 345, GrowthRate.SLOW, null, false, true,
new PokemonForm("Normal", "", PokemonType.POISON, PokemonType.DRAGON, 20, 950, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 690, 140, 85, 95, 145, 95, 130, 255, 0, 345, false, null, true),
new PokemonForm("E-Max", "eternamax", PokemonType.POISON, PokemonType.DRAGON, 100, 999.9, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 1125, 255, 115, 250, 125, 250, 130, 255, 0, 345)
new PokemonSpecies(SpeciesId.ETERNATUS, 8, false, true, false, "Gigantic Pokémon", PokemonType.POISON, PokemonType.DRAGON, 20, 950, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 690, 140, 85, 95, 145, 95, 130, 45, 0, 345, GrowthRate.SLOW, null, false, true,
new PokemonForm("Normal", "", PokemonType.POISON, PokemonType.DRAGON, 20, 950, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 690, 140, 85, 95, 145, 95, 130, 45, 0, 345, false, null, true),
new PokemonForm("E-Max", "eternamax", PokemonType.POISON, PokemonType.DRAGON, 100, 999.9, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 1125, 255, 115, 250, 125, 250, 130, 45, 0, 345)
),
new PokemonSpecies(SpeciesId.KUBFU, 8, true, false, false, "Wushu Pokémon", PokemonType.FIGHTING, null, 0.6, 12, AbilityId.INNER_FOCUS, AbilityId.NONE, AbilityId.NONE, 385, 60, 90, 60, 53, 50, 72, 3, 50, 77, GrowthRate.SLOW, 87.5, false),
new PokemonSpecies(SpeciesId.URSHIFU, 8, true, false, false, "Wushu Pokémon", PokemonType.FIGHTING, PokemonType.DARK, 1.9, 105, AbilityId.UNSEEN_FIST, AbilityId.NONE, AbilityId.NONE, 550, 100, 130, 100, 63, 60, 97, 3, 50, 275, GrowthRate.SLOW, 87.5, false, true,

View File

@ -45736,6 +45736,285 @@ export const tmSpecies: TmSpecies = {
SpeciesId.HISUI_ARCANINE,
SpeciesId.HISUI_AVALUGG,
],
[MoveId.SHOCK_WAVE]: [
SpeciesId.RATTATA,
SpeciesId.RATICATE,
SpeciesId.PIKACHU,
SpeciesId.RAICHU,
SpeciesId.NIDORAN_F,
SpeciesId.NIDORINA,
SpeciesId.NIDOQUEEN,
SpeciesId.NIDORAN_M,
SpeciesId.NIDORINO,
SpeciesId.NIDOKING,
SpeciesId.CLEFAIRY,
SpeciesId.CLEFABLE,
SpeciesId.JIGGLYPUFF,
SpeciesId.WIGGLYTUFF,
SpeciesId.MEOWTH,
SpeciesId.PERSIAN,
SpeciesId.ABRA,
SpeciesId.KADABRA,
SpeciesId.ALAKAZAM,
SpeciesId.MAGNEMITE,
SpeciesId.MAGNETON,
SpeciesId.GRIMER,
SpeciesId.MUK,
SpeciesId.VOLTORB,
SpeciesId.ELECTRODE,
SpeciesId.LICKITUNG,
SpeciesId.KOFFING,
SpeciesId.WEEZING,
SpeciesId.RHYHORN,
SpeciesId.RHYDON,
SpeciesId.CHANSEY,
SpeciesId.TANGELA,
SpeciesId.KANGASKHAN,
SpeciesId.MR_MIME,
SpeciesId.ELECTABUZZ,
SpeciesId.TAUROS,
SpeciesId.LAPRAS,
SpeciesId.JOLTEON,
SpeciesId.PORYGON,
SpeciesId.SNORLAX,
SpeciesId.ZAPDOS,
SpeciesId.DRATINI,
SpeciesId.DRAGONAIR,
SpeciesId.DRAGONITE,
SpeciesId.MEWTWO,
SpeciesId.MEW,
SpeciesId.SENTRET,
SpeciesId.FURRET,
SpeciesId.CHINCHOU,
SpeciesId.LANTURN,
SpeciesId.PICHU,
SpeciesId.CLEFFA,
SpeciesId.IGGLYBUFF,
SpeciesId.TOGEPI,
SpeciesId.TOGETIC,
SpeciesId.MAREEP,
SpeciesId.FLAAFFY,
SpeciesId.AMPHAROS,
SpeciesId.AIPOM,
SpeciesId.MISDREAVUS,
SpeciesId.GIRAFARIG,
SpeciesId.DUNSPARCE,
SpeciesId.SNUBBULL,
SpeciesId.GRANBULL,
SpeciesId.QWILFISH,
SpeciesId.PORYGON2,
SpeciesId.STANTLER,
SpeciesId.ELEKID,
SpeciesId.MILTANK,
SpeciesId.BLISSEY,
SpeciesId.RAIKOU,
SpeciesId.TYRANITAR,
SpeciesId.LUGIA,
SpeciesId.HO_OH,
SpeciesId.CELEBI,
SpeciesId.ZIGZAGOON,
SpeciesId.LINOONE,
SpeciesId.WINGULL,
SpeciesId.PELIPPER,
SpeciesId.RALTS,
SpeciesId.KIRLIA,
SpeciesId.GARDEVOIR,
SpeciesId.SLAKOTH,
SpeciesId.VIGOROTH,
SpeciesId.SLAKING,
SpeciesId.WHISMUR,
SpeciesId.LOUDRED,
SpeciesId.EXPLOUD,
SpeciesId.NOSEPASS,
SpeciesId.SKITTY,
SpeciesId.DELCATTY,
SpeciesId.SABLEYE,
SpeciesId.ARON,
SpeciesId.LAIRON,
SpeciesId.AGGRON,
SpeciesId.ELECTRIKE,
SpeciesId.MANECTRIC,
SpeciesId.PLUSLE,
SpeciesId.MINUN,
SpeciesId.VOLBEAT,
SpeciesId.ILLUMISE,
SpeciesId.GULPIN,
SpeciesId.SWALOT,
SpeciesId.SPOINK,
SpeciesId.GRUMPIG,
SpeciesId.SPINDA,
SpeciesId.ZANGOOSE,
SpeciesId.CASTFORM,
SpeciesId.KECLEON,
SpeciesId.SHUPPET,
SpeciesId.BANETTE,
SpeciesId.CHIMECHO,
SpeciesId.ABSOL,
SpeciesId.REGIROCK,
SpeciesId.REGICE,
SpeciesId.REGISTEEL,
SpeciesId.LATIAS,
SpeciesId.LATIOS,
SpeciesId.KYOGRE,
SpeciesId.GROUDON,
SpeciesId.RAYQUAZA,
SpeciesId.JIRACHI,
SpeciesId.DEOXYS,
SpeciesId.BIDOOF,
SpeciesId.BIBAREL,
SpeciesId.SHINX,
SpeciesId.LUXIO,
SpeciesId.LUXRAY,
SpeciesId.CRANIDOS,
SpeciesId.RAMPARDOS,
SpeciesId.SHIELDON,
SpeciesId.BASTIODON,
SpeciesId.PACHIRISU,
SpeciesId.AMBIPOM,
SpeciesId.DRIFLOON,
SpeciesId.DRIFBLIM,
SpeciesId.BUNEARY,
SpeciesId.LOPUNNY,
SpeciesId.MISMAGIUS,
SpeciesId.GLAMEOW,
SpeciesId.PURUGLY,
SpeciesId.CHINGLING,
SpeciesId.MIME_JR,
SpeciesId.HAPPINY,
SpeciesId.SPIRITOMB,
SpeciesId.MUNCHLAX,
SpeciesId.MAGNEZONE,
SpeciesId.LICKILICKY,
SpeciesId.RHYPERIOR,
SpeciesId.TANGROWTH,
SpeciesId.ELECTIVIRE,
SpeciesId.TOGEKISS,
SpeciesId.PORYGON_Z,
SpeciesId.GALLADE,
SpeciesId.PROBOPASS,
SpeciesId.FROSLASS,
SpeciesId.ROTOM,
SpeciesId.UXIE,
SpeciesId.MESPRIT,
SpeciesId.AZELF,
SpeciesId.DIALGA,
SpeciesId.PALKIA,
SpeciesId.REGIGIGAS,
SpeciesId.GIRATINA,
SpeciesId.DARKRAI,
SpeciesId.ARCEUS,
SpeciesId.VICTINI,
SpeciesId.PATRAT,
SpeciesId.WATCHOG,
SpeciesId.LILLIPUP,
SpeciesId.HERDIER,
SpeciesId.STOUTLAND,
SpeciesId.MUNNA,
SpeciesId.MUSHARNA,
SpeciesId.BLITZLE,
SpeciesId.ZEBSTRIKA,
SpeciesId.WOOBAT,
SpeciesId.SWOOBAT,
SpeciesId.SIGILYPH,
SpeciesId.YAMASK,
SpeciesId.COFAGRIGUS,
SpeciesId.MINCCINO,
SpeciesId.CINCCINO,
SpeciesId.GOTHITA,
SpeciesId.GOTHORITA,
SpeciesId.GOTHITELLE,
SpeciesId.SOLOSIS,
SpeciesId.DUOSION,
SpeciesId.REUNICLUS,
SpeciesId.EMOLGA,
SpeciesId.FRILLISH,
SpeciesId.JELLICENT,
SpeciesId.JOLTIK,
SpeciesId.GALVANTULA,
SpeciesId.KLINK,
SpeciesId.KLANG,
SpeciesId.KLINKLANG,
SpeciesId.EELEKTRIK,
SpeciesId.EELEKTROSS,
SpeciesId.ELGYEM,
SpeciesId.BEHEEYEM,
SpeciesId.LITWICK,
SpeciesId.LAMPENT,
SpeciesId.CHANDELURE,
SpeciesId.AXEW,
SpeciesId.FRAXURE,
SpeciesId.HAXORUS,
SpeciesId.STUNFISK,
SpeciesId.DRUDDIGON,
SpeciesId.GOLETT,
SpeciesId.GOLURK,
SpeciesId.DEINO,
SpeciesId.ZWEILOUS,
SpeciesId.HYDREIGON,
SpeciesId.THUNDURUS,
SpeciesId.ZEKROM,
SpeciesId.MELOETTA,
SpeciesId.GENESECT,
SpeciesId.BRAIXEN,
SpeciesId.DELPHOX,
SpeciesId.ESPURR,
SpeciesId.MEOWSTIC,
SpeciesId.HONEDGE,
SpeciesId.DOUBLADE,
SpeciesId.AEGISLASH,
SpeciesId.SKRELP,
SpeciesId.DRAGALGE,
SpeciesId.HELIOPTILE,
SpeciesId.HELIOLISK,
SpeciesId.DEDENNE,
SpeciesId.GOOMY,
SpeciesId.SLIGGOO,
SpeciesId.GOODRA,
SpeciesId.ZYGARDE,
SpeciesId.HOOPA,
SpeciesId.YUNGOOS,
SpeciesId.GUMSHOOS,
SpeciesId.GRUBBIN,
SpeciesId.CHARJABUG,
SpeciesId.VIKAVOLT,
SpeciesId.PASSIMIAN,
SpeciesId.TURTONATOR,
SpeciesId.TOGEDEMARU,
SpeciesId.DRAMPA,
SpeciesId.KOMMO_O,
SpeciesId.TAPU_KOKO,
SpeciesId.SOLGALEO,
SpeciesId.LUNALA,
SpeciesId.PHEROMOSA,
SpeciesId.XURKITREE,
SpeciesId.CELESTEELA,
SpeciesId.GUZZLORD,
SpeciesId.NECROZMA,
SpeciesId.MAGEARNA,
SpeciesId.NAGANADEL,
SpeciesId.ZERAORA,
SpeciesId.TOXTRICITY,
SpeciesId.MR_RIME,
SpeciesId.REGIELEKI,
SpeciesId.WYRDEER,
SpeciesId.FARIGIRAF,
SpeciesId.DUDUNSPARCE,
SpeciesId.MIRAIDON,
SpeciesId.RAGING_BOLT,
SpeciesId.ALOLA_RATTATA,
SpeciesId.ALOLA_RATICATE,
SpeciesId.ALOLA_RAICHU,
SpeciesId.ALOLA_MEOWTH,
SpeciesId.ALOLA_PERSIAN,
SpeciesId.ALOLA_GRAVELER,
SpeciesId.ALOLA_GOLEM,
SpeciesId.ALOLA_GRIMER,
SpeciesId.ALOLA_MUK,
SpeciesId.GALAR_WEEZING,
SpeciesId.GALAR_MR_MIME,
SpeciesId.HISUI_SLIGGOO,
SpeciesId.HISUI_GOODRA,
],
[MoveId.WATER_PULSE]: [
SpeciesId.SQUIRTLE,
SpeciesId.WARTORTLE,
@ -68747,6 +69026,7 @@ export const tmPoolTiers: TmPoolTiers = {
[MoveId.LEAF_BLADE]: ModifierTier.ULTRA,
[MoveId.DRAGON_DANCE]: ModifierTier.GREAT,
[MoveId.ROCK_BLAST]: ModifierTier.GREAT,
[MoveId.SHOCK_WAVE]: ModifierTier.GREAT,
[MoveId.WATER_PULSE]: ModifierTier.GREAT,
[MoveId.ROOST]: ModifierTier.GREAT,
[MoveId.GRAVITY]: ModifierTier.COMMON,

View File

@ -404,22 +404,18 @@ export const chargeAnims = new Map<ChargeAnim, AnimConfig | [AnimConfig, AnimCon
export const commonAnims = new Map<CommonAnim, AnimConfig>();
export const encounterAnims = new Map<EncounterAnim, AnimConfig>();
export function initCommonAnims(): Promise<void> {
return new Promise(resolve => {
const commonAnimNames = getEnumKeys(CommonAnim);
const commonAnimIds = getEnumValues(CommonAnim);
const commonAnimFetches: Promise<Map<CommonAnim, AnimConfig>>[] = [];
for (let ca = 0; ca < commonAnimIds.length; ca++) {
const commonAnimId = commonAnimIds[ca];
commonAnimFetches.push(
globalScene
.cachedFetch(`./battle-anims/common-${toKebabCase(commonAnimNames[ca])}.json`)
.then(response => response.json())
.then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas))),
);
}
Promise.allSettled(commonAnimFetches).then(() => resolve());
});
export async function initCommonAnims(): Promise<void> {
const commonAnimFetches: Promise<Map<CommonAnim, AnimConfig>>[] = [];
for (const commonAnimName of getEnumKeys(CommonAnim)) {
const commonAnimId = CommonAnim[commonAnimName];
commonAnimFetches.push(
globalScene
.cachedFetch(`./battle-anims/common-${toKebabCase(commonAnimName)}.json`)
.then(response => response.json())
.then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas))),
);
}
await Promise.allSettled(commonAnimFetches);
}
export function initMoveAnim(move: MoveId): Promise<void> {
@ -1396,279 +1392,3 @@ export class EncounterBattleAnim extends BattleAnim {
return this.oppAnim;
}
}
export async function populateAnims() {
const commonAnimNames = getEnumKeys(CommonAnim).map(k => k.toLowerCase());
const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/_/g, ""));
const commonAnimIds = getEnumValues(CommonAnim);
const chargeAnimNames = getEnumKeys(ChargeAnim).map(k => k.toLowerCase());
const chargeAnimMatchNames = chargeAnimNames.map(k => k.replace(/_/g, " "));
const chargeAnimIds = getEnumValues(ChargeAnim);
const commonNamePattern = /name: (?:Common:)?(Opp )?(.*)/;
const moveNameToId = {};
// Exclude MoveId.NONE;
for (const move of getEnumValues(MoveId).slice(1)) {
// KARATE_CHOP => KARATECHOP
const moveName = MoveId[move].toUpperCase().replace(/_/g, "");
moveNameToId[moveName] = move;
}
const seNames: string[] = []; //(await fs.readdir('./public/audio/se/battle_anims/')).map(se => se.toString());
const animsData: any[] = []; //battleAnimRawData.split('!ruby/array:PBAnimation').slice(1); // TODO: add a proper type
for (let a = 0; a < animsData.length; a++) {
const fields = animsData[a].split("@").slice(1);
const nameField = fields.find(f => f.startsWith("name: "));
let isOppMove: boolean | undefined;
let commonAnimId: CommonAnim | undefined;
let chargeAnimId: ChargeAnim | undefined;
if (!nameField.startsWith("name: Move:") && !(isOppMove = nameField.startsWith("name: OppMove:"))) {
const nameMatch = commonNamePattern.exec(nameField)!; // TODO: is this bang correct?
const name = nameMatch[2].toLowerCase();
if (commonAnimMatchNames.indexOf(name) > -1) {
commonAnimId = commonAnimIds[commonAnimMatchNames.indexOf(name)];
} else if (chargeAnimMatchNames.indexOf(name) > -1) {
isOppMove = nameField.startsWith("name: Opp ");
chargeAnimId = chargeAnimIds[chargeAnimMatchNames.indexOf(name)];
}
}
const nameIndex = nameField.indexOf(":", 5) + 1;
const animName = nameField.slice(nameIndex, nameField.indexOf("\n", nameIndex));
if (!moveNameToId.hasOwnProperty(animName) && !commonAnimId && !chargeAnimId) {
continue;
}
const anim = commonAnimId || chargeAnimId ? new AnimConfig() : new AnimConfig();
if (anim instanceof AnimConfig) {
(anim as AnimConfig).id = moveNameToId[animName];
}
if (commonAnimId) {
commonAnims.set(commonAnimId, anim);
} else if (chargeAnimId) {
chargeAnims.set(chargeAnimId, !isOppMove ? anim : [chargeAnims.get(chargeAnimId) as AnimConfig, anim]);
} else {
moveAnims.set(
moveNameToId[animName],
!isOppMove ? (anim as AnimConfig) : [moveAnims.get(moveNameToId[animName]) as AnimConfig, anim as AnimConfig],
);
}
for (let f = 0; f < fields.length; f++) {
const field = fields[f];
const fieldName = field.slice(0, field.indexOf(":"));
const fieldData = field.slice(fieldName.length + 1, field.lastIndexOf("\n")).trim();
switch (fieldName) {
case "array": {
const framesData = fieldData.split(" - - - ").slice(1);
for (let fd = 0; fd < framesData.length; fd++) {
anim.frames.push([]);
const frameData = framesData[fd];
const focusFramesData = frameData.split(" - - ");
for (let tf = 0; tf < focusFramesData.length; tf++) {
const values = focusFramesData[tf].replace(/ {6}- /g, "").split("\n");
const targetFrame = new AnimFrame(
Number.parseFloat(values[0]),
Number.parseFloat(values[1]),
Number.parseFloat(values[2]),
Number.parseFloat(values[11]),
Number.parseFloat(values[3]),
Number.parseInt(values[4]) === 1,
Number.parseInt(values[6]) === 1,
Number.parseInt(values[5]),
Number.parseInt(values[7]),
Number.parseInt(values[8]),
Number.parseInt(values[12]),
Number.parseInt(values[13]),
Number.parseInt(values[14]),
Number.parseInt(values[15]),
Number.parseInt(values[16]),
Number.parseInt(values[17]),
Number.parseInt(values[18]),
Number.parseInt(values[19]),
Number.parseInt(values[21]),
Number.parseInt(values[22]),
Number.parseInt(values[23]),
Number.parseInt(values[24]),
Number.parseInt(values[20]) === 1,
Number.parseInt(values[25]),
Number.parseInt(values[26]) as AnimFocus,
);
anim.frames[fd].push(targetFrame);
}
}
break;
}
case "graphic": {
const graphic = fieldData !== "''" ? fieldData : "";
anim.graphic = graphic.indexOf(".") > -1 ? graphic.slice(0, fieldData.indexOf(".")) : graphic;
break;
}
case "timing": {
const timingEntries = fieldData.split("- !ruby/object:PBAnimTiming ").slice(1);
for (let t = 0; t < timingEntries.length; t++) {
const timingData = timingEntries[t]
.replace(/\n/g, " ")
.replace(/[ ]{2,}/g, " ")
.replace(/[a-z]+: ! '', /gi, "")
.replace(/name: (.*?),/, 'name: "$1",')
.replace(
/flashColor: !ruby\/object:Color { alpha: ([\d.]+), blue: ([\d.]+), green: ([\d.]+), red: ([\d.]+)}/,
"flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1",
);
const frameIndex = Number.parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct?
let resourceName = /name: "(.*?)"/.exec(timingData)![1].replace("''", ""); // TODO: is the bang correct?
const timingType = Number.parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct?
let timedEvent: AnimTimedEvent | undefined;
switch (timingType) {
case 0:
if (resourceName && resourceName.indexOf(".") === -1) {
let ext: string | undefined;
["wav", "mp3", "m4a"].every(e => {
if (seNames.indexOf(`${resourceName}.${e}`) > -1) {
ext = e;
return false;
}
return true;
});
if (!ext) {
ext = ".wav";
}
resourceName += `.${ext}`;
}
timedEvent = new AnimTimedSoundEvent(frameIndex, resourceName);
break;
case 1:
timedEvent = new AnimTimedAddBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf(".")));
break;
case 2:
timedEvent = new AnimTimedUpdateBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf(".")));
break;
}
if (!timedEvent) {
continue;
}
const propPattern = /([a-z]+): (.*?)(?:,|\})/gi;
let propMatch: RegExpExecArray;
while ((propMatch = propPattern.exec(timingData)!)) {
// TODO: is this bang correct?
const prop = propMatch[1];
let value: any = propMatch[2];
switch (prop) {
case "bgX":
case "bgY":
value = Number.parseFloat(value);
break;
case "volume":
case "pitch":
case "opacity":
case "colorRed":
case "colorGreen":
case "colorBlue":
case "colorAlpha":
case "duration":
case "flashScope":
case "flashRed":
case "flashGreen":
case "flashBlue":
case "flashAlpha":
case "flashDuration":
value = Number.parseInt(value);
break;
}
if (timedEvent.hasOwnProperty(prop)) {
timedEvent[prop] = value;
}
}
if (!anim.frameTimedEvents.has(frameIndex)) {
anim.frameTimedEvents.set(frameIndex, []);
}
anim.frameTimedEvents.get(frameIndex)!.push(timedEvent); // TODO: is this bang correct?
}
break;
}
case "position":
anim.position = Number.parseInt(fieldData);
break;
case "hue":
anim.hue = Number.parseInt(fieldData);
break;
}
}
}
// biome-ignore lint/correctness/noUnusedVariables: used in commented code
const animReplacer = (k, v) => {
if (k === "id" && !v) {
return undefined;
}
if (v instanceof Map) {
return Object.fromEntries(v);
}
if (v instanceof AnimTimedEvent) {
v["eventType"] = v.getEventType();
}
return v;
};
const animConfigProps = ["id", "graphic", "frames", "frameTimedEvents", "position", "hue"];
const animFrameProps = [
"x",
"y",
"zoomX",
"zoomY",
"angle",
"mirror",
"visible",
"blendType",
"target",
"graphicFrame",
"opacity",
"color",
"tone",
"flash",
"locked",
"priority",
"focus",
];
const propSets = [animConfigProps, animFrameProps];
// biome-ignore lint/correctness/noUnusedVariables: used in commented code
const animComparator = (a: Element, b: Element) => {
let props: string[];
for (let p = 0; p < propSets.length; p++) {
props = propSets[p];
// @ts-expect-error TODO
const ai = props.indexOf(a.key);
if (ai === -1) {
continue;
}
// @ts-expect-error TODO
const bi = props.indexOf(b.key);
return ai < bi ? -1 : ai > bi ? 1 : 0;
}
return 0;
};
/*for (let ma of moveAnims.keys()) {
const data = moveAnims.get(ma);
(async () => {
await fs.writeFile(`../public/battle-anims/${Moves[ma].toLowerCase().replace(/\_/g, '-')}.json`, stringify(data, { replacer: animReplacer, cmp: animComparator, space: ' ' }));
})();
}
for (let ca of chargeAnims.keys()) {
const data = chargeAnims.get(ca);
(async () => {
await fs.writeFile(`../public/battle-anims/${chargeAnimNames[chargeAnimIds.indexOf(ca)].replace(/\_/g, '-')}.json`, stringify(data, { replacer: animReplacer, cmp: animComparator, space: ' ' }));
})();
}
for (let cma of commonAnims.keys()) {
const data = commonAnims.get(cma);
(async () => {
await fs.writeFile(`../public/battle-anims/common-${commonAnimNames[commonAnimIds.indexOf(cma)].replace(/\_/g, '-')}.json`, stringify(data, { replacer: animReplacer, cmp: animComparator, space: ' ' }));
})();
}*/
}

View File

@ -8512,7 +8512,7 @@ export function initMoves() {
.punchingMove(),
new AttackMove(MoveId.SCRATCH, PokemonType.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, 0, 1),
new AttackMove(MoveId.VISE_GRIP, PokemonType.NORMAL, MoveCategory.PHYSICAL, 55, 100, 30, -1, 0, 1),
new AttackMove(MoveId.GUILLOTINE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1)
new AttackMove(MoveId.GUILLOTINE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 250, 30, 5, -1, 0, 1)
.attr(OneHitKOAttr)
.attr(OneHitKOAccuracyAttr),
new ChargingAttackMove(MoveId.RAZOR_WIND, PokemonType.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 1)
@ -8565,7 +8565,7 @@ export function initMoves() {
new AttackMove(MoveId.HORN_ATTACK, PokemonType.NORMAL, MoveCategory.PHYSICAL, 65, 100, 25, -1, 0, 1),
new AttackMove(MoveId.FURY_ATTACK, PokemonType.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, 0, 1)
.attr(MultiHitAttr),
new AttackMove(MoveId.HORN_DRILL, PokemonType.NORMAL, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1)
new AttackMove(MoveId.HORN_DRILL, PokemonType.NORMAL, MoveCategory.PHYSICAL, 250, 30, 5, -1, 0, 1)
.attr(OneHitKOAttr)
.attr(OneHitKOAccuracyAttr),
new AttackMove(MoveId.TACKLE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, 0, 1),
@ -8746,7 +8746,7 @@ export function initMoves() {
.attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1)
.makesContact(false)
.target(MoveTarget.ALL_NEAR_OTHERS),
new AttackMove(MoveId.FISSURE, PokemonType.GROUND, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1)
new AttackMove(MoveId.FISSURE, PokemonType.GROUND, MoveCategory.PHYSICAL, 250, 30, 5, -1, 0, 1)
.attr(OneHitKOAttr)
.attr(OneHitKOAccuracyAttr)
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND)
@ -9528,7 +9528,7 @@ export function initMoves() {
new AttackMove(MoveId.SAND_TOMB, PokemonType.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 3)
.attr(TrapAttr, BattlerTagType.SAND_TOMB)
.makesContact(false),
new AttackMove(MoveId.SHEER_COLD, PokemonType.ICE, MoveCategory.SPECIAL, 200, 30, 5, -1, 0, 3)
new AttackMove(MoveId.SHEER_COLD, PokemonType.ICE, MoveCategory.SPECIAL, 250, 30, 5, -1, 0, 3)
.attr(IceNoEffectTypeAttr)
.attr(OneHitKOAttr)
.attr(SheerColdAccuracyAttr),

View File

@ -99,6 +99,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
MysteryEncounterType.DARK_DEAL,
)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withDisallowedChallenges(Challenges.HARDCORE)
.withIntroSpriteConfigs([
{
spriteKey: "dark_deal_scientist",

View File

@ -673,6 +673,8 @@ export async function catchPokemon(
globalScene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
return new Promise(resolve => {
const addStatus = new BooleanHolder(true);
applyChallenges(ChallengeType.POKEMON_ADD_TO_PARTY, pokemon, addStatus);
const doPokemonCatchMenu = () => {
const end = () => {
// Ensure the pokemon is in the enemy party in all situations
@ -708,9 +710,7 @@ export async function catchPokemon(
});
};
Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => {
const addStatus = new BooleanHolder(true);
applyChallenges(ChallengeType.POKEMON_ADD_TO_PARTY, pokemon, addStatus);
if (!addStatus.value) {
if (!(isObtain || addStatus.value)) {
removePokemon();
end();
return;
@ -807,10 +807,16 @@ export async function catchPokemon(
};
if (showCatchObtainMessage) {
let catchMessage: string;
if (isObtain) {
catchMessage = "battle:pokemonObtained";
} else if (addStatus.value) {
catchMessage = "battle:pokemonCaught";
} else {
catchMessage = "battle:pokemonCaughtButChallenge";
}
globalScene.ui.showText(
i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", {
pokemonName: pokemon.getNameToRender(),
}),
i18next.t(catchMessage, { pokemonName: pokemon.getNameToRender() }),
null,
doPokemonCatchMenu,
0,

View File

@ -253,8 +253,11 @@ export class AttemptCapturePhase extends PokemonPhase {
globalScene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
const addStatus = new BooleanHolder(true);
applyChallenges(ChallengeType.POKEMON_ADD_TO_PARTY, pokemon, addStatus);
globalScene.ui.showText(
i18next.t("battle:pokemonCaught", {
i18next.t(addStatus.value ? "battle:pokemonCaught" : "battle:pokemonCaughtButChallenge", {
pokemonName: getPokemonNameWithAffix(pokemon),
}),
null,
@ -290,8 +293,6 @@ export class AttemptCapturePhase extends PokemonPhase {
});
};
Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => {
const addStatus = new BooleanHolder(true);
applyChallenges(ChallengeType.POKEMON_ADD_TO_PARTY, pokemon, addStatus);
if (!addStatus.value) {
removePokemon();
end();

View File

@ -16,8 +16,10 @@ export class SelectBiomePhase extends BattlePhase {
globalScene.resetSeed();
const gameMode = globalScene.gameMode;
const currentBiome = globalScene.arena.biomeType;
const nextWaveIndex = globalScene.currentBattle.waveIndex + 1;
const currentWaveIndex = globalScene.currentBattle.waveIndex;
const nextWaveIndex = currentWaveIndex + 1;
const setNextBiome = (nextBiome: BiomeId) => {
if (nextWaveIndex % 10 === 1) {
@ -26,6 +28,15 @@ export class SelectBiomePhase extends BattlePhase {
applyChallenges(ChallengeType.PARTY_HEAL, healStatus);
if (healStatus.value) {
globalScene.phaseManager.unshiftNew("PartyHealPhase", false);
} else {
globalScene.phaseManager.unshiftNew(
"SelectModifierPhase",
undefined,
undefined,
gameMode.isFixedBattle(currentWaveIndex)
? gameMode.getFixedBattle(currentWaveIndex).customModifierRewardSettings
: undefined,
);
}
}
globalScene.phaseManager.unshiftNew("SwitchBiomePhase", nextBiome);
@ -33,12 +44,12 @@ export class SelectBiomePhase extends BattlePhase {
};
if (
(globalScene.gameMode.isClassic && globalScene.gameMode.isWaveFinal(nextWaveIndex + 9)) ||
(globalScene.gameMode.isDaily && globalScene.gameMode.isWaveFinal(nextWaveIndex)) ||
(globalScene.gameMode.hasShortBiomes && !(nextWaveIndex % 50))
(gameMode.isClassic && gameMode.isWaveFinal(nextWaveIndex + 9)) ||
(gameMode.isDaily && gameMode.isWaveFinal(nextWaveIndex)) ||
(gameMode.hasShortBiomes && !(nextWaveIndex % 50))
) {
setNextBiome(BiomeId.END);
} else if (globalScene.gameMode.hasRandomBiomes) {
} else if (gameMode.hasRandomBiomes) {
setNextBiome(this.generateNextBiome(nextWaveIndex));
} else if (Array.isArray(biomeLinks[currentBiome])) {
const biomes: BiomeId[] = (biomeLinks[currentBiome] as (BiomeId | [BiomeId, number])[])
@ -73,9 +84,6 @@ export class SelectBiomePhase extends BattlePhase {
}
generateNextBiome(waveIndex: number): BiomeId {
if (!(waveIndex % 50)) {
return BiomeId.END;
}
return globalScene.generateRandomBiome(waveIndex);
return waveIndex % 50 === 0 ? BiomeId.END : globalScene.generateRandomBiome(waveIndex);
}
}

View File

@ -3,13 +3,9 @@ import { globalScene } from "#app/global-scene";
import { modifierTypes } from "#data/data-lists";
import { BattleType } from "#enums/battle-type";
import type { BattlerIndex } from "#enums/battler-index";
import { ChallengeType } from "#enums/challenge-type";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
import type { CustomModifierSettings } from "#modifiers/modifier-type";
import { handleMysteryEncounterVictory } from "#mystery-encounters/encounter-phase-utils";
import { PokemonPhase } from "#phases/pokemon-phase";
import { applyChallenges } from "#utils/challenge-utils";
import { BooleanHolder } from "#utils/common";
export class VictoryPhase extends PokemonPhase {
public readonly phaseName = "VictoryPhase";
@ -49,15 +45,19 @@ export class VictoryPhase extends PokemonPhase {
if (globalScene.currentBattle.battleType === BattleType.TRAINER) {
globalScene.phaseManager.pushNew("TrainerVictoryPhase");
}
if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) {
const gameMode = globalScene.gameMode;
const currentWaveIndex = globalScene.currentBattle.waveIndex;
if (gameMode.isEndless || !gameMode.isWaveFinal(currentWaveIndex)) {
globalScene.phaseManager.pushNew("EggLapsePhase");
if (globalScene.gameMode.isClassic) {
switch (globalScene.currentBattle.waveIndex) {
if (gameMode.isClassic) {
switch (currentWaveIndex) {
case ClassicFixedBossWaves.RIVAL_1:
case ClassicFixedBossWaves.RIVAL_2:
// Get event modifiers for this wave
timedEventManager
.getFixedBattleEventRewards(globalScene.currentBattle.waveIndex)
.getFixedBattleEventRewards(currentWaveIndex)
.map(r => globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes[r]));
break;
case ClassicFixedBossWaves.EVIL_BOSS_2:
@ -66,59 +66,53 @@ export class VictoryPhase extends PokemonPhase {
break;
}
}
const healStatus = new BooleanHolder(globalScene.currentBattle.waveIndex % 10 === 0);
applyChallenges(ChallengeType.PARTY_HEAL, healStatus);
if (!healStatus.value) {
if (currentWaveIndex % 10) {
globalScene.phaseManager.pushNew(
"SelectModifierPhase",
undefined,
undefined,
this.getFixedBattleCustomModifiers(),
gameMode.isFixedBattle(currentWaveIndex)
? gameMode.getFixedBattle(currentWaveIndex).customModifierRewardSettings
: undefined,
);
} else if (globalScene.gameMode.isDaily) {
} else if (gameMode.isDaily) {
globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.EXP_CHARM);
if (
globalScene.currentBattle.waveIndex > 10 &&
!globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)
) {
if (currentWaveIndex > 10 && !gameMode.isWaveFinal(currentWaveIndex)) {
globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.GOLDEN_POKEBALL);
}
} else {
const superExpWave = !globalScene.gameMode.isEndless ? (globalScene.offsetGym ? 0 : 20) : 10;
if (globalScene.gameMode.isEndless && globalScene.currentBattle.waveIndex === 10) {
const superExpWave = !gameMode.isEndless ? (globalScene.offsetGym ? 0 : 20) : 10;
if (gameMode.isEndless && currentWaveIndex === 10) {
globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.EXP_SHARE);
}
if (
globalScene.currentBattle.waveIndex <= 750 &&
(globalScene.currentBattle.waveIndex <= 500 || globalScene.currentBattle.waveIndex % 30 === superExpWave)
) {
if (currentWaveIndex <= 750 && (currentWaveIndex <= 500 || currentWaveIndex % 30 === superExpWave)) {
globalScene.phaseManager.pushNew(
"ModifierRewardPhase",
globalScene.currentBattle.waveIndex % 30 !== superExpWave || globalScene.currentBattle.waveIndex > 250
currentWaveIndex % 30 !== superExpWave || currentWaveIndex > 250
? modifierTypes.EXP_CHARM
: modifierTypes.SUPER_EXP_CHARM,
);
}
if (globalScene.currentBattle.waveIndex <= 150 && !(globalScene.currentBattle.waveIndex % 50)) {
if (currentWaveIndex <= 150 && !(currentWaveIndex % 50)) {
globalScene.phaseManager.pushNew("ModifierRewardPhase", modifierTypes.GOLDEN_POKEBALL);
}
if (globalScene.gameMode.isEndless && !(globalScene.currentBattle.waveIndex % 50)) {
if (gameMode.isEndless && !(currentWaveIndex % 50)) {
globalScene.phaseManager.pushNew(
"ModifierRewardPhase",
!(globalScene.currentBattle.waveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS,
!(currentWaveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS,
);
globalScene.phaseManager.pushNew("AddEnemyBuffModifierPhase");
}
}
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
if (gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
globalScene.phaseManager.pushNew("SelectBiomePhase");
}
globalScene.phaseManager.pushNew("NewBattlePhase");
} else {
globalScene.currentBattle.battleType = BattleType.CLEAR;
globalScene.score += globalScene.gameMode.getClearScoreBonus();
globalScene.score += gameMode.getClearScoreBonus();
globalScene.updateScoreText();
globalScene.phaseManager.pushNew("GameOverPhase", true);
}
@ -126,18 +120,4 @@ export class VictoryPhase extends PokemonPhase {
this.end();
}
/**
* If this wave is a fixed battle with special custom modifier rewards,
* will pass those settings to the upcoming {@linkcode SelectModifierPhase}`.
*/
getFixedBattleCustomModifiers(): CustomModifierSettings | undefined {
const gameMode = globalScene.gameMode;
const waveIndex = globalScene.currentBattle.waveIndex;
if (gameMode.isFixedBattle(waveIndex)) {
return gameMode.getFixedBattle(waveIndex).customModifierRewardSettings;
}
return undefined;
}
}

View File

@ -56,15 +56,15 @@ export class PokerogueSessionSavedataApi extends ApiBase {
/**
* Update a session savedata.
* @param params The {@linkcode UpdateSessionSavedataRequest} to send
* @param rawSavedata The raw savedata (as `string`)
* @param params - The request to send
* @param rawSavedata - The raw, unencrypted savedata
* @returns An error message if something went wrong
*/
public async update(params: UpdateSessionSavedataRequest, rawSavedata: string) {
public async update(params: UpdateSessionSavedataRequest, rawSavedata: string): Promise<string> {
try {
const urlSearchParams = this.toUrlSearchParams(params);
const response = await this.doPost(`/savedata/session/update?${urlSearchParams}`, rawSavedata);
const response = await this.doPost(`/savedata/session/update?${urlSearchParams}`, rawSavedata);
return await response.text();
} catch (err) {
console.warn("Could not update session savedata!", err);

View File

@ -448,6 +448,8 @@ export function getAchievementDescription(localizationKey: string): string {
return i18next.t("achv:FLIP_STATS.description", { context: genderStr });
case "FLIP_INVERSE":
return i18next.t("achv:FLIP_INVERSE.description", { context: genderStr });
case "NUZLOCKE":
return i18next.t("achv:NUZLOCKE.description", { context: genderStr });
case "BREEDERS_IN_SPACE":
return i18next.t("achv:BREEDERS_IN_SPACE.description", {
context: genderStr,

View File

@ -128,7 +128,8 @@ export interface SessionSaveData {
battleType: BattleType;
trainer: TrainerData;
gameVersion: string;
runNameText: string;
/** The player-chosen name of the run */
name: string;
timestamp: number;
challenges: ChallengeData[];
mysteryEncounterType: MysteryEncounterType | -1; // Only defined when current wave is ME,
@ -986,51 +987,45 @@ export class GameData {
}
async renameSession(slotId: number, newName: string): Promise<boolean> {
return new Promise(async resolve => {
if (slotId < 0) {
return resolve(false);
}
const sessionData: SessionSaveData | null = await this.getSession(slotId);
if (slotId < 0) {
return false;
}
if (newName === "") {
return true;
}
const sessionData: SessionSaveData | null = await this.getSession(slotId);
if (!sessionData) {
return resolve(false);
}
if (!sessionData) {
return false;
}
if (newName === "") {
return resolve(true);
}
sessionData.name = newName;
// update timestamp by 1 to ensure the session is saved
sessionData.timestamp += 1;
const updatedDataStr = JSON.stringify(sessionData);
const encrypted = encrypt(updatedDataStr, bypassLogin);
const secretId = this.secretId;
const trainerId = this.trainerId;
sessionData.runNameText = newName;
const updatedDataStr = JSON.stringify(sessionData);
const encrypted = encrypt(updatedDataStr, bypassLogin);
const secretId = this.secretId;
const trainerId = this.trainerId;
if (bypassLogin) {
localStorage.setItem(
`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`,
encrypt(updatedDataStr, bypassLogin),
);
return true;
}
if (bypassLogin) {
localStorage.setItem(
`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`,
encrypt(updatedDataStr, bypassLogin),
);
resolve(true);
return;
}
pokerogueApi.savedata.session
.update({ slot: slotId, trainerId, secretId, clientSessionId }, encrypted)
.then(error => {
if (error) {
console.error("Failed to update session name:", error);
resolve(false);
} else {
localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypted);
updateUserInfo().then(success => {
if (success !== null && !success) {
return resolve(false);
}
});
resolve(true);
}
});
});
const response = await pokerogueApi.savedata.session.update(
{ slot: slotId, trainerId, secretId, clientSessionId },
updatedDataStr,
);
if (response) {
return false;
}
localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypted);
const success = await updateUserInfo();
return !(success !== null && !success);
}
loadSession(slotId: number, sessionData?: SessionSaveData): Promise<boolean> {

View File

@ -2142,7 +2142,12 @@ class PartyCancelButton extends Phaser.GameObjects.Container {
this.partyCancelPb = partyCancelPb;
const partyCancelText = addTextObject(-10, -7, i18next.t("partyUiHandler:cancel"), TextStyle.PARTY_CANCEL_BUTTON);
const partyCancelText = addTextObject(
-10,
-7,
i18next.t("partyUiHandler:cancelButton"),
TextStyle.PARTY_CANCEL_BUTTON,
);
this.add(partyCancelText);
}

View File

@ -208,6 +208,26 @@ export class PokedexMonContainer extends Phaser.GameObjects.Container {
);
this.checkIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant);
this.add(this.icon);
[
this.hiddenAbilityIcon,
this.favoriteIcon,
this.classicWinIcon,
this.candyUpgradeIcon,
this.candyUpgradeOverlayIcon,
this.eggMove1Icon,
this.tmMove1Icon,
this.eggMove2Icon,
this.tmMove2Icon,
this.passive1Icon,
this.passive2Icon,
this.passive1OverlayIcon,
this.passive2OverlayIcon,
].forEach(icon => {
if (icon) {
this.bringToTop(icon);
}
});
}
checkIconId(female, formIndex, shiny, variant) {

View File

@ -410,6 +410,11 @@ export class PokedexUiHandler extends MessageUiHandler {
new DropDownLabel(i18next.t("filterBar:hasHiddenAbility"), undefined, DropDownState.ON),
new DropDownLabel(i18next.t("filterBar:noHiddenAbility"), undefined, DropDownState.EXCLUDE),
];
const seenSpeciesLabels = [
new DropDownLabel(i18next.t("filterBar:seenSpecies"), undefined, DropDownState.OFF),
new DropDownLabel(i18next.t("filterBar:isSeen"), undefined, DropDownState.ON),
new DropDownLabel(i18next.t("filterBar:isUnseen"), undefined, DropDownState.EXCLUDE),
];
const eggLabels = [
new DropDownLabel(i18next.t("filterBar:egg"), undefined, DropDownState.OFF),
new DropDownLabel(i18next.t("filterBar:eggPurchasable"), undefined, DropDownState.ON),
@ -423,6 +428,7 @@ export class PokedexUiHandler extends MessageUiHandler {
new DropDownOption("FAVORITE", favoriteLabels),
new DropDownOption("WIN", winLabels),
new DropDownOption("HIDDEN_ABILITY", hiddenAbilityLabels),
new DropDownOption("SEEN_SPECIES", seenSpeciesLabels),
new DropDownOption("EGG", eggLabels),
new DropDownOption("POKERUS", pokerusLabels),
];
@ -792,13 +798,15 @@ export class PokedexUiHandler extends MessageUiHandler {
this.starterSelectMessageBoxContainer.setVisible(!!text?.length);
}
isSeen(species: PokemonSpecies, dexEntry: DexEntry): boolean {
isSeen(species: PokemonSpecies, dexEntry: DexEntry, seenFilter?: boolean): boolean {
if (dexEntry?.seenAttr) {
return true;
}
const starterDexEntry = globalScene.gameData.dexData[this.getStarterSpeciesId(species.speciesId)];
return !!starterDexEntry?.caughtAttr;
if (!seenFilter) {
const starterDexEntry = globalScene.gameData.dexData[this.getStarterSpeciesId(species.speciesId)];
return !!starterDexEntry?.caughtAttr;
}
return false;
}
/**
@ -1617,6 +1625,21 @@ export class PokedexUiHandler extends MessageUiHandler {
}
});
// Seen Filter
const dexEntry = globalScene.gameData.dexData[species.speciesId];
const isItSeen = this.isSeen(species, dexEntry, true);
const fitsSeen = this.filterBar.getVals(DropDownColumn.MISC).some(misc => {
if (misc.val === "SEEN_SPECIES" && misc.state === DropDownState.ON) {
return isItSeen;
}
if (misc.val === "SEEN_SPECIES" && misc.state === DropDownState.EXCLUDE) {
return !isItSeen;
}
if (misc.val === "SEEN_SPECIES" && misc.state === DropDownState.OFF) {
return true;
}
});
// Egg Purchasable Filter
const isEggPurchasable = this.isSameSpeciesEggAvailable(species.speciesId);
const fitsEgg = this.filterBar.getVals(DropDownColumn.MISC).some(misc => {
@ -1658,6 +1681,7 @@ export class PokedexUiHandler extends MessageUiHandler {
fitsFavorite &&
fitsWin &&
fitsHA &&
fitsSeen &&
fitsEgg &&
fitsPokerus
) {

View File

@ -208,9 +208,10 @@ export class RunInfoUiHandler extends UiHandler {
headerText.setOrigin(0, 0);
headerText.setPositionRelative(headerBg, 8, 4);
this.runContainer.add(headerText);
const runName = addTextObject(0, 0, this.runInfo.runNameText, TextStyle.WINDOW);
const runName = addTextObject(0, 0, this.runInfo.name, TextStyle.WINDOW);
runName.setOrigin(0, 0);
runName.setPositionRelative(headerBg, 60, 4);
const runNameX = headerText.width / 6 + headerText.x + 4;
runName.setPositionRelative(headerBg, runNameX, 4);
this.runContainer.add(runName);
}

View File

@ -377,7 +377,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
"select_cursor_highlight_thick",
undefined,
294,
this.sessionSlots[prevSlotIndex ?? 0]?.saveData?.runNameText ? 50 : 60,
this.sessionSlots[prevSlotIndex ?? 0]?.saveData?.name ? 50 : 60,
6,
6,
6,
@ -553,10 +553,10 @@ class SessionSlot extends Phaser.GameObjects.Container {
}
async setupWithData(data: SessionSaveData) {
const hasName = data?.runNameText;
const hasName = data?.name;
this.remove(this.loadingLabel, true);
if (hasName) {
const nameLabel = addTextObject(8, 5, data.runNameText, TextStyle.WINDOW);
const nameLabel = addTextObject(8, 5, data.name, TextStyle.WINDOW);
this.add(nameLabel);
} else {
const fallbackName = this.decideFallback(data);

View File

@ -45,17 +45,17 @@ export function deepMergeSpriteData(dest: object, source: object) {
}
export function encrypt(data: string, bypassLogin: boolean): string {
return (bypassLogin
? (data: string) => btoa(encodeURIComponent(data))
: (data: string) => AES.encrypt(data, saveKey))(data) as unknown as string; // TODO: is this correct?
if (bypassLogin) {
return btoa(encodeURIComponent(data));
}
return AES.encrypt(data, saveKey).toString();
}
export function decrypt(data: string, bypassLogin: boolean): string {
return (
bypassLogin
? (data: string) => decodeURIComponent(atob(data))
: (data: string) => AES.decrypt(data, saveKey).toString(enc.Utf8)
)(data);
if (bypassLogin) {
return decodeURIComponent(atob(data));
}
return AES.decrypt(data, saveKey).toString(enc.Utf8);
}
// the latest data saved/loaded for the Starter Preferences. Required to reduce read/writes. Initialize as "{}", since this is the default value and no data needs to be stored if present.

View File

@ -1,8 +1,10 @@
import { AbilityId } from "#enums/ability-id";
import { Challenges } from "#enums/challenges";
import { MoveId } from "#enums/move-id";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PokeballType } from "#enums/pokeball";
import { SpeciesId } from "#enums/species-id";
import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils";
import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@ -52,4 +54,18 @@ describe("Challenges - Limited Catch", () => {
expect(game.scene.getPlayerParty()).toHaveLength(1);
});
it("should allow gift Pokémon from Mystery Encounters to be added to party", async () => {
game.override
.mysteryEncounterChance(100)
.mysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN)
.startingWave(12);
game.scene.money = 20000;
await game.challengeMode.runToSummon([SpeciesId.NUZLEAF]);
await runMysteryEncounterToEnd(game, 1);
expect(game.scene.getPlayerParty()).toHaveLength(2);
});
});

View File

@ -3,6 +3,7 @@ import { Challenges } from "#enums/challenges";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { UiMode } from "#enums/ui-mode";
import { ExpBoosterModifier } from "#modifiers/modifier";
import { GameManager } from "#test/test-utils/game-manager";
import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler";
import Phaser from "phaser";
@ -75,6 +76,7 @@ describe("Challenges - Limited Support", () => {
await game.doKillOpponents();
await game.toNextWave();
expect(game.scene.getModifiers(ExpBoosterModifier)).toHaveLength(1);
expect(playerPokemon).not.toHaveFullHp();
game.move.use(MoveId.SPLASH);