Merge branch 'beta' into markdown

This commit is contained in:
Bertie690 2025-05-01 18:42:10 -04:00 committed by GitHub
commit 96d1b773f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 8392 additions and 8301 deletions

View File

@ -65,7 +65,7 @@ Do the reviewers need to do something special in order to test your changes?
- [ ] The PR is self-contained and cannot be split into smaller PRs? - [ ] The PR is self-contained and cannot be split into smaller PRs?
- [ ] Have I provided a clear explanation of the changes? - [ ] Have I provided a clear explanation of the changes?
- [ ] Have I tested the changes manually? - [ ] Have I tested the changes manually?
- [ ] Are all unit tests still passing? (`npm run test`) - [ ] Are all unit tests still passing? (`npm run test:silent`)
- [ ] Have I created new automated tests (`npm run create-test`) or updated existing tests related to the PR's changes? - [ ] Have I created new automated tests (`npm run create-test`) or updated existing tests related to the PR's changes?
- [ ] Have I provided screenshots/videos of the changes (if applicable)? - [ ] Have I provided screenshots/videos of the changes (if applicable)?
- [ ] Have I made sure that any UI change works for both UI themes (default and legacy)? - [ ] Have I made sure that any UI change works for both UI themes (default and legacy)?

View File

@ -38,6 +38,9 @@
"src/data/balance/tms.ts" "src/data/balance/tms.ts"
] ]
}, },
// While it'd be nice to enable consistent sorting, enabling this causes issues due to circular import resolution order
// TODO: Remove if we ever get down to 0 circular imports
"organizeImports": { "enabled": false }, "organizeImports": { "enabled": false },
"linter": { "linter": {
"ignore": [ "ignore": [
@ -55,13 +58,13 @@
}, },
"style": { "style": {
"noVar": "error", "noVar": "error",
"useEnumInitializers": "off", "useEnumInitializers": "off", // large enums like Moves/Species would make this cumbersome
"useBlockStatements": "error", "useBlockStatements": "error",
"useConst": "error", "useConst": "error",
"useImportType": "error", "useImportType": "error",
"noNonNullAssertion": "off", // TODO: Turn this on ASAP and fix all non-null assertions "noNonNullAssertion": "off", // TODO: Turn this on ASAP and fix all non-null assertions in non-test files
"noParameterAssign": "off", "noParameterAssign": "off",
"useExponentiationOperator": "off", "useExponentiationOperator": "off", // Too typo-prone and easy to mixup with standard multiplication (* vs **)
"useDefaultParameterLast": "off", // TODO: Fix spots in the codebase where this flag would be triggered, and then enable "useDefaultParameterLast": "off", // TODO: Fix spots in the codebase where this flag would be triggered, and then enable
"useSingleVarDeclarator": "off", "useSingleVarDeclarator": "off",
"useNodejsImportProtocol": "off", "useNodejsImportProtocol": "off",
@ -70,17 +73,20 @@
}, },
"suspicious": { "suspicious": {
"noDoubleEquals": "error", "noDoubleEquals": "error",
// While this would be a nice rule to enable, the current structure of the codebase makes this infeasible
// due to being used for move/ability `args` params and save data-related code.
// This can likely be enabled for all non-utils files once these are eventually reworked, but until then we leave it off.
"noExplicitAny": "off", "noExplicitAny": "off",
"noAssignInExpressions": "off", "noAssignInExpressions": "off",
"noPrototypeBuiltins": "off", "noPrototypeBuiltins": "off",
"noFallthroughSwitchClause": "off", "noFallthroughSwitchClause": "error", // Prevents accidental automatic fallthroughs in switch cases (use disable comment if needed)
"noImplicitAnyLet": "info", // TODO: Refactor and make this an error "noImplicitAnyLet": "warn", // TODO: Refactor and make this an error
"noRedeclare": "off", // TODO: Refactor and make this an error "noRedeclare": "info", // TODO: Refactor and make this an error
"noGlobalIsNan": "off", "noGlobalIsNan": "off",
"noAsyncPromiseExecutor": "warn" // TODO: Refactor and make this an error "noAsyncPromiseExecutor": "warn" // TODO: Refactor and make this an error
}, },
"complexity": { "complexity": {
"noExcessiveCognitiveComplexity": "warn", "noExcessiveCognitiveComplexity": "warn", // TODO: Refactor and make this an error
"useLiteralKeys": "off", "useLiteralKeys": "off",
"noForEach": "off", // Foreach vs for of is not that simple. "noForEach": "off", // Foreach vs for of is not that simple.
"noUselessSwitchCase": "off", // Explicit > Implicit "noUselessSwitchCase": "off", // Explicit > Implicit

View File

@ -1,9 +1,10 @@
import tseslint from "@typescript-eslint/eslint-plugin"; /** @ts-check */
import tseslint from "typescript-eslint";
import stylisticTs from "@stylistic/eslint-plugin-ts"; import stylisticTs from "@stylistic/eslint-plugin-ts";
import parser from "@typescript-eslint/parser"; import parser from "@typescript-eslint/parser";
import importX from "eslint-plugin-import-x"; import importX from "eslint-plugin-import-x";
export default [ export default tseslint.config(
{ {
name: "eslint-config", name: "eslint-config",
files: ["src/**/*.{ts,tsx,js,jsx}", "test/**/*.{ts,tsx,js,jsx}"], files: ["src/**/*.{ts,tsx,js,jsx}", "test/**/*.{ts,tsx,js,jsx}"],
@ -14,12 +15,11 @@ export default [
plugins: { plugins: {
"import-x": importX, "import-x": importX,
"@stylistic/ts": stylisticTs, "@stylistic/ts": stylisticTs,
"@typescript-eslint": tseslint, "@typescript-eslint": tseslint.plugin,
}, },
rules: { rules: {
"prefer-const": "error", // Enforces the use of `const` for variables that are never reassigned
"no-undef": "off", // Disables the rule that disallows the use of undeclared variables (TypeScript handles this) "no-undef": "off", // Disables the rule that disallows the use of undeclared variables (TypeScript handles this)
"no-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax "no-extra-semi": "error", // Disallows unnecessary semicolons for TypeScript-specific syntax
"import-x/extensions": ["error", "never", { json: "always" }], // Enforces no extension for imports unless json "import-x/extensions": ["error", "never", { json: "always" }], // Enforces no extension for imports unless json
}, },
}, },
@ -33,11 +33,11 @@ export default [
}, },
}, },
plugins: { plugins: {
"@typescript-eslint": tseslint, "@typescript-eslint": tseslint.plugin,
}, },
rules: { rules: {
"@typescript-eslint/no-floating-promises": "error", // Require Promise-like statements to be handled appropriately. - https://typescript-eslint.io/rules/no-floating-promises/ "@typescript-eslint/no-floating-promises": "error", // Require Promise-like statements to be handled appropriately. - https://typescript-eslint.io/rules/no-floating-promises/
"@typescript-eslint/no-misused-promises": "error", // Disallow Promises in places not designed to handle them. - https://typescript-eslint.io/rules/no-misused-promises/ "@typescript-eslint/no-misused-promises": "error", // Disallow Promises in places not designed to handle them. - https://typescript-eslint.io/rules/no-misused-promises/
}, },
}, },
]; );

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 915 B

After

Width:  |  Height:  |  Size: 884 B

@ -1 +1 @@
Subproject commit 18c1963ef309612a5a7fef76f9879709a7202189 Subproject commit 833dc40ec7409031fcea147ccbc45ec9c0ba0213

View File

@ -3408,8 +3408,12 @@ export class BlockCritAbAttr extends AbAttr {
super(false); super(false);
} }
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void { /**
(args[0] as BooleanHolder).value = true; * Apply the block crit ability by setting the value in the provided boolean holder to false
* @param args - [0] is a boolean holder representing whether the attack can crit
*/
override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder, args: [BooleanHolder, ...any]): void {
(args[0]).value = false;
} }
} }

View File

@ -651,7 +651,7 @@ export default class Move implements Localizable {
break; break;
case MoveFlags.IGNORE_ABILITIES: case MoveFlags.IGNORE_ABILITIES:
if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) {
const abilityEffectsIgnored = new BooleanHolder(false); const abilityEffectsIgnored = new BooleanHolder(false);
applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this);
if (abilityEffectsIgnored.value) { if (abilityEffectsIgnored.value) {
return true; return true;
@ -3156,7 +3156,7 @@ export class StatStageChangeAttr extends MoveEffectAttr {
private get showMessage () { private get showMessage () {
return this.options?.showMessage ?? true; return this.options?.showMessage ?? true;
} }
/** /**
* Attempts to change stats of the user or target (depending on value of selfTarget) if conditions are met * Attempts to change stats of the user or target (depending on value of selfTarget) if conditions are met
* @param user {@linkcode Pokemon} the user of the move * @param user {@linkcode Pokemon} the user of the move
@ -6322,11 +6322,11 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
if (!allyPokemon?.isActive(true) && switchOutTarget.hp) { if (!allyPokemon?.isActive(true) && switchOutTarget.hp) {
globalScene.pushPhase(new BattleEndPhase(false)); globalScene.pushPhase(new BattleEndPhase(false));
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
globalScene.pushPhase(new SelectBiomePhase()); globalScene.pushPhase(new SelectBiomePhase());
} }
globalScene.pushPhase(new NewBattlePhase()); globalScene.pushPhase(new NewBattlePhase());
} }
} }
@ -8614,7 +8614,9 @@ export function initMoves() {
.condition((user, target, move) => !target.summonData?.illusion && !user.summonData?.illusion) .condition((user, target, move) => !target.summonData?.illusion && !user.summonData?.illusion)
// transforming from or into fusion pokemon causes various problems (such as crashes) // transforming from or into fusion pokemon causes various problems (such as crashes)
.condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE) && !user.fusionSpecies && !target.fusionSpecies) .condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE) && !user.fusionSpecies && !target.fusionSpecies)
.ignoresProtect(), .ignoresProtect()
// Transforming should copy the target's rage fist hit count
.edgeCase(),
new AttackMove(Moves.BUBBLE, PokemonType.WATER, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1) new AttackMove(Moves.BUBBLE, PokemonType.WATER, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
.attr(StatStageChangeAttr, [ Stat.SPD ], -1) .attr(StatStageChangeAttr, [ Stat.SPD ], -1)
.target(MoveTarget.ALL_NEAR_ENEMIES), .target(MoveTarget.ALL_NEAR_ENEMIES),

View File

@ -488,6 +488,7 @@ export abstract class PokemonSpeciesForm {
if (formSpriteKey.startsWith("behemoth")) { if (formSpriteKey.startsWith("behemoth")) {
formSpriteKey = "crowned"; formSpriteKey = "crowned";
} }
// biome-ignore lint/suspicious/no-fallthrough: Falls through
default: default:
ret += `-${formSpriteKey}`; ret += `-${formSpriteKey}`;
break; break;

View File

@ -369,6 +369,7 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
if (hasSun) { if (hasSun) {
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 });
} }
break;
case Biome.VOLCANO: case Biome.VOLCANO:
weatherPool = [ weatherPool = [
{ {

View File

@ -31,6 +31,7 @@ import ChallengeData from "#app/system/challenge-data";
import TrainerData from "#app/system/trainer-data"; import TrainerData from "#app/system/trainer-data";
import ArenaData from "#app/system/arena-data"; import ArenaData from "#app/system/arena-data";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import { MessagePhase } from "./message-phase";
export class GameOverPhase extends BattlePhase { export class GameOverPhase extends BattlePhase {
private isVictory: boolean; private isVictory: boolean;
@ -122,7 +123,7 @@ export class GameOverPhase extends BattlePhase {
globalScene.disableMenu = true; globalScene.disableMenu = true;
globalScene.time.delayedCall(1000, () => { globalScene.time.delayedCall(1000, () => {
let firstClear = false; let firstClear = false;
if (this.isVictory && newClear) { if (this.isVictory) {
if (globalScene.gameMode.isClassic) { if (globalScene.gameMode.isClassic) {
firstClear = globalScene.validateAchv(achvs.CLASSIC_VICTORY); firstClear = globalScene.validateAchv(achvs.CLASSIC_VICTORY);
globalScene.validateAchv(achvs.UNEVOLVED_CLASSIC_VICTORY); globalScene.validateAchv(achvs.UNEVOLVED_CLASSIC_VICTORY);
@ -226,7 +227,17 @@ export class GameOverPhase extends BattlePhase {
isVictory: this.isVictory, isVictory: this.isVictory,
clientSessionId: clientSessionId, clientSessionId: clientSessionId,
}) })
.then(success => doGameOver(!!success)); .then(success => doGameOver(!globalScene.gameMode.isDaily || !!success))
.catch(_err => {
globalScene.clearPhaseQueue();
globalScene.clearPhaseQueueSplice();
globalScene.unshiftPhase(new MessagePhase(i18next.t("menu:serverCommunicationFailed"), 2500));
// force the game to reload after 2 seconds.
setTimeout(() => {
window.location.reload();
}, 2000);
this.end();
});
} else if (this.isVictory) { } else if (this.isVictory) {
globalScene.gameData.offlineNewClear().then(result => { globalScene.gameData.offlineNewClear().then(result => {
doGameOver(result); doGameOver(result);

View File

@ -20,17 +20,20 @@ export class PokerogueSessionSavedataApi extends ApiBase {
* *This is **NOT** the same as {@linkcode clear | clear()}.* * *This is **NOT** the same as {@linkcode clear | clear()}.*
* @param params The {@linkcode NewClearSessionSavedataRequest} to send * @param params The {@linkcode NewClearSessionSavedataRequest} to send
* @returns The raw savedata as `string`. * @returns The raw savedata as `string`.
* @throws Error if the request fails
*/ */
public async newclear(params: NewClearSessionSavedataRequest) { public async newclear(params: NewClearSessionSavedataRequest) {
try { try {
const urlSearchParams = this.toUrlSearchParams(params); const urlSearchParams = this.toUrlSearchParams(params);
const response = await this.doGet(`/savedata/session/newclear?${urlSearchParams}`); const response = await this.doGet(`/savedata/session/newclear?${urlSearchParams}`);
const json = await response.json(); const json = await response.json();
if (response.ok) {
return Boolean(json); return Boolean(json);
}
throw new Error("Could not newclear session!");
} catch (err) { } catch (err) {
console.warn("Could not newclear session!", err); console.warn("Could not newclear session!", err);
return false; throw new Error("Could not newclear session!");
} }
} }

View File

@ -804,6 +804,7 @@ export function setSetting(setting: string, value: number): boolean {
break; break;
case SettingKeys.Candy_Upgrade_Display: case SettingKeys.Candy_Upgrade_Display:
globalScene.candyUpgradeDisplay = value; globalScene.candyUpgradeDisplay = value;
break;
case SettingKeys.Money_Format: case SettingKeys.Money_Format:
switch (Setting[index].options[value].value) { switch (Setting[index].options[value].value) {
case "Normal": case "Normal":

View File

@ -176,11 +176,13 @@ export class UiInputs {
return; return;
} }
switch (globalScene.ui?.getMode()) { switch (globalScene.ui?.getMode()) {
case UiMode.MESSAGE: case UiMode.MESSAGE: {
const messageHandler = globalScene.ui.getHandler<MessageUiHandler>(); const messageHandler = globalScene.ui.getHandler<MessageUiHandler>();
if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) { if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) {
return; return;
} }
// biome-ignore lint/suspicious/noFallthroughSwitchClause: falls through to show menu overlay
}
case UiMode.TITLE: case UiMode.TITLE:
case UiMode.COMMAND: case UiMode.COMMAND:
case UiMode.MODIFIER_SELECT: case UiMode.MODIFIER_SELECT:

View File

@ -921,16 +921,22 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
return biomes; return biomes;
} }
/**
* Return the caughtAttr of a given species, sanitized.
*
* @param otherSpecies The species to check; defaults to current species
* @returns caught DexAttr for the species
*/
isCaught(otherSpecies?: PokemonSpecies): bigint { isCaught(otherSpecies?: PokemonSpecies): bigint {
const species = otherSpecies ? otherSpecies : this.species;
if (globalScene.dexForDevs) { if (globalScene.dexForDevs) {
return 255n; species.getFullUnlocksData();
} }
const species = otherSpecies ? otherSpecies : this.species;
const dexEntry = globalScene.gameData.dexData[species.speciesId]; const dexEntry = globalScene.gameData.dexData[species.speciesId];
const starterDexEntry = globalScene.gameData.dexData[this.getStarterSpeciesId(species.speciesId)];
return (dexEntry?.caughtAttr ?? 0n) & (starterDexEntry?.caughtAttr ?? 0n) & species.getFullUnlocksData(); return (dexEntry?.caughtAttr ?? 0n) & species.getFullUnlocksData();
} }
/** /**
@ -939,7 +945,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
* *
* @param otherSpecies The species to check; defaults to current species * @param otherSpecies The species to check; defaults to current species
* @param otherFormIndex The form index of the form to check; defaults to current form * @param otherFormIndex The form index of the form to check; defaults to current form
* @returns StarterAttributes for the species * @returns `true` if the form is caught
*/ */
isFormCaught(otherSpecies?: PokemonSpecies, otherFormIndex?: number | undefined): boolean { isFormCaught(otherSpecies?: PokemonSpecies, otherFormIndex?: number | undefined): boolean {
if (globalScene.dexForDevs) { if (globalScene.dexForDevs) {
@ -954,6 +960,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
} }
const isFormCaught = (caughtAttr & globalScene.gameData.getFormAttr(formIndex ?? 0)) > 0n; const isFormCaught = (caughtAttr & globalScene.gameData.getFormAttr(formIndex ?? 0)) > 0n;
return isFormCaught; return isFormCaught;
} }
@ -1151,7 +1158,6 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
this.blockInput = false; this.blockInput = false;
} else { } else {
ui.revertMode().then(() => { ui.revertMode().then(() => {
console.log("exitCallback", this.exitCallback);
if (this.exitCallback instanceof Function) { if (this.exitCallback instanceof Function) {
const exitCallback = this.exitCallback; const exitCallback = this.exitCallback;
this.exitCallback = null; this.exitCallback = null;

View File

@ -57,9 +57,7 @@ describe("Pokerogue Session Savedata API", () => {
it("should return false and report a warning on ERROR", async () => { it("should return false and report a warning on ERROR", async () => {
server.use(http.get(`${apiBase}/savedata/session/newclear`, () => HttpResponse.error())); server.use(http.get(`${apiBase}/savedata/session/newclear`, () => HttpResponse.error()));
const success = await sessionSavedataApi.newclear(params); await expect(sessionSavedataApi.newclear(params)).rejects.toThrow("Could not newclear session!");
expect(success).toBe(false);
expect(console.warn).toHaveBeenCalledWith("Could not newclear session!", expect.any(Error)); expect(console.warn).toHaveBeenCalledWith("Could not newclear session!", expect.any(Error));
}); });
}); });

File diff suppressed because one or more lines are too long

View File

@ -12,6 +12,8 @@ import { DropDownColumn } from "#app/ui/filter-bar";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import PokedexPageUiHandler from "#app/ui/pokedex-page-ui-handler";
import type { StarterAttributes } from "#app/system/game-data";
/* /*
Information for the `data_pokedex_tests.psrv`: Information for the `data_pokedex_tests.psrv`:
@ -80,6 +82,26 @@ describe("UI - Pokedex", () => {
return handler as PokedexUiHandler; return handler as PokedexUiHandler;
} }
/**
* Run the game to open the pokedex UI.
* @returns The handler for the pokedex UI.
*/
async function runToPokedexPage(
species: PokemonSpecies,
starterAttributes: StarterAttributes = {},
): Promise<PokedexPageUiHandler> {
// Open the pokedex UI.
await game.runToTitle();
await game.phaseInterceptor.setOverlayMode(UiMode.POKEDEX_PAGE, species, starterAttributes);
// Get the handler for the current UI.
const handler = game.scene.ui.getHandler();
expect(handler).toBeInstanceOf(PokedexPageUiHandler);
return handler as PokedexPageUiHandler;
}
/** /**
* Compute a set of pokemon that have a specific ability in allAbilities * Compute a set of pokemon that have a specific ability in allAbilities
* @param ability - The ability to filter for * @param ability - The ability to filter for
@ -489,4 +511,37 @@ describe("UI - Pokedex", () => {
expect(selectedPokemon).toEqual(pokedexHandler.lastSpecies.speciesId); expect(selectedPokemon).toEqual(pokedexHandler.lastSpecies.speciesId);
}, },
); );
/****************************
* Tests for Pokédex Pages *
****************************/
it("should show caught battle form as caught", async () => {
await game.importData("./test/testUtils/saves/data_pokedex_tests_v2.prsv");
const pageHandler = await runToPokedexPage(getPokemonSpecies(Species.VENUSAUR), { form: 1 });
// @ts-expect-error - `species` is private
expect(pageHandler.species.speciesId).toEqual(Species.VENUSAUR);
// @ts-expect-error - `formIndex` is private
expect(pageHandler.formIndex).toEqual(1);
expect(pageHandler.isFormCaught()).toEqual(true);
expect(pageHandler.isSeen()).toEqual(true);
});
//TODO: check tint of the sprite
it("should show uncaught battle form as seen", async () => {
await game.importData("./test/testUtils/saves/data_pokedex_tests_v2.prsv");
const pageHandler = await runToPokedexPage(getPokemonSpecies(Species.VENUSAUR), { form: 2 });
// @ts-expect-error - `species` is private
expect(pageHandler.species.speciesId).toEqual(Species.VENUSAUR);
// @ts-expect-error - `formIndex` is private
expect(pageHandler.formIndex).toEqual(2);
expect(pageHandler.isFormCaught()).toEqual(false);
expect(pageHandler.isSeen()).toEqual(true);
});
}); });

View File

@ -7,7 +7,7 @@
"esModuleInterop": true, "esModuleInterop": true,
"strictNullChecks": true, "strictNullChecks": true,
"sourceMap": false, "sourceMap": false,
"strict": false, "strict": false, // TODO: Enable this eventually
"rootDir": ".", "rootDir": ".",
"baseUrl": "./src", "baseUrl": "./src",
"paths": { "paths": {