From 9a9759d31cd1cf376d52b9d7bd8ac02cb84c8897 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Tue, 29 Apr 2025 17:27:40 -0400 Subject: [PATCH 1/5] [Balance] Fix Basculin white stripe moveset (#5729) * Fix Basculin moveset and TMs --- src/data/balance/pokemon-level-moves.ts | 38 +++++++++++++++++++++++++ src/data/balance/tms.ts | 18 ++++++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/data/balance/pokemon-level-moves.ts b/src/data/balance/pokemon-level-moves.ts index dcbc2fb0c0d..0b0ba1b5f71 100644 --- a/src/data/balance/pokemon-level-moves.ts +++ b/src/data/balance/pokemon-level-moves.ts @@ -19383,6 +19383,44 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { [ 100, Moves.SEED_FLARE ], ] }, + [Species.BASCULIN]: { + 1: [ + [ 1, Moves.TAIL_WHIP ], + [ 1, Moves.WATER_GUN ], + [ 4, Moves.TACKLE ], + [ 8, Moves.FLAIL ], + [ 12, Moves.AQUA_JET ], + [ 16, Moves.BITE ], + [ 20, Moves.SCARY_FACE ], + [ 24, Moves.HEADBUTT ], + [ 28, Moves.SOAK ], + [ 32, Moves.CRUNCH ], + [ 36, Moves.TAKE_DOWN ], + [ 40, Moves.FINAL_GAMBIT ], + [ 44, Moves.WAVE_CRASH ], + [ 48, Moves.THRASH ], + [ 52, Moves.DOUBLE_EDGE ], + [ 56, Moves.HEAD_SMASH ], + ], + 2: [ + [ 1, Moves.TAIL_WHIP ], + [ 1, Moves.WATER_GUN ], + [ 4, Moves.TACKLE ], + [ 8, Moves.FLAIL ], + [ 12, Moves.AQUA_JET ], + [ 16, Moves.BITE ], + [ 20, Moves.SCARY_FACE ], + [ 24, Moves.HEADBUTT ], + [ 28, Moves.SOAK ], + [ 32, Moves.CRUNCH ], + [ 36, Moves.TAKE_DOWN ], + [ 40, Moves.UPROAR ], + [ 44, Moves.WAVE_CRASH ], + [ 48, Moves.THRASH ], + [ 52, Moves.DOUBLE_EDGE ], + [ 56, Moves.HEAD_SMASH ], + ] + }, [Species.KYUREM]: { 1: [ [ 1, Moves.DRAGON_BREATH ], diff --git a/src/data/balance/tms.ts b/src/data/balance/tms.ts index 62199fd6968..69aef9b135d 100644 --- a/src/data/balance/tms.ts +++ b/src/data/balance/tms.ts @@ -5724,7 +5724,6 @@ export const tmSpecies: TmSpecies = { Species.SCOLIPEDE, Species.WHIMSICOTT, Species.LILLIGANT, - Species.BASCULIN, Species.KROOKODILE, Species.DARMANITAN, Species.CRUSTLE, @@ -6023,6 +6022,11 @@ export const tmSpecies: TmSpecies = { Species.HISUI_DECIDUEYE, Species.PALDEA_TAUROS, Species.BLOODMOON_URSALUNA, + [ + Species.BASCULIN, + "blue-striped", + "red-striped", + ] ], [Moves.LOW_KICK]: [ Species.SANDSHREW, @@ -19335,7 +19339,6 @@ export const tmSpecies: TmSpecies = { Species.CONKELDURR, Species.THROH, Species.SAWK, - Species.BASCULIN, Species.DARMANITAN, Species.SCRAFTY, Species.ESCAVALIER, @@ -19449,6 +19452,11 @@ export const tmSpecies: TmSpecies = { Species.HISUI_BRAVIARY, Species.HISUI_DECIDUEYE, Species.PALDEA_TAUROS, + [ + Species.BASCULIN, + "blue-striped", + "red-striped", + ], ], [Moves.SPITE]: [ Species.EKANS, @@ -51341,7 +51349,6 @@ export const tmSpecies: TmSpecies = { Species.SCOLIPEDE, Species.WHIMSICOTT, Species.LILLIGANT, - Species.BASCULIN, Species.KROOKODILE, Species.DARMANITAN, Species.CRUSTLE, @@ -51655,6 +51662,11 @@ export const tmSpecies: TmSpecies = { Species.HISUI_DECIDUEYE, Species.PALDEA_TAUROS, Species.BLOODMOON_URSALUNA, + [ + Species.BASCULIN, + "blue-striped", + "red-striped", + ], ], [Moves.NASTY_PLOT]: [ Species.PIKACHU, From 43d73b01b1023509e48c12142367234965fe0ba3 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Tue, 29 Apr 2025 19:21:28 -0400 Subject: [PATCH 2/5] [Code] Added and enforced `no-fallthrough` + added eslint type checking (#5705) * Added and enforced `no-fallthrough` * Fixed errors * Fix package.json * Moved vule to biom * Fixed stuff * Added workspace files to .gitignore for anyone who wants to do this stuff * reverted accidental gitignore changes * Update biome.jsonc Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update biome.jsonc Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update pokemon-species.ts * Update biome.jsonc to apply reviews * Fixed package.json * Fix typo --------- Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- biome.jsonc | 20 +++++++++++++------- eslint.config.js | 14 +++++++------- public/locales | 2 +- src/data/moves/move.ts | 8 ++++---- src/data/pokemon-species.ts | 1 + src/data/weather.ts | 1 + src/system/settings/settings.ts | 1 + src/ui-inputs.ts | 4 +++- tsconfig.json | 2 +- 9 files changed, 32 insertions(+), 21 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index 9d0e6a9b5ff..a433470cd90 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -38,6 +38,9 @@ "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 }, "linter": { "ignore": [ @@ -55,13 +58,13 @@ }, "style": { "noVar": "error", - "useEnumInitializers": "off", + "useEnumInitializers": "off", // large enums like Moves/Species would make this cumbersome "useBlockStatements": "error", "useConst": "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", - "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 "useSingleVarDeclarator": "off", "useNodejsImportProtocol": "off", @@ -70,17 +73,20 @@ }, "suspicious": { "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", "noAssignInExpressions": "off", "noPrototypeBuiltins": "off", - "noFallthroughSwitchClause": "off", - "noImplicitAnyLet": "info", // TODO: Refactor and make this an error - "noRedeclare": "off", // TODO: Refactor and make this an error + "noFallthroughSwitchClause": "error", // Prevents accidental automatic fallthroughs in switch cases (use disable comment if needed) + "noImplicitAnyLet": "warn", // TODO: Refactor and make this an error + "noRedeclare": "info", // TODO: Refactor and make this an error "noGlobalIsNan": "off", "noAsyncPromiseExecutor": "warn" // TODO: Refactor and make this an error }, "complexity": { - "noExcessiveCognitiveComplexity": "warn", + "noExcessiveCognitiveComplexity": "warn", // TODO: Refactor and make this an error "useLiteralKeys": "off", "noForEach": "off", // Foreach vs for of is not that simple. "noUselessSwitchCase": "off", // Explicit > Implicit diff --git a/eslint.config.js b/eslint.config.js index a97e3902411..aebcab7feae 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -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 parser from "@typescript-eslint/parser"; import importX from "eslint-plugin-import-x"; -export default [ +export default tseslint.config( { name: "eslint-config", files: ["src/**/*.{ts,tsx,js,jsx}", "test/**/*.{ts,tsx,js,jsx}"], @@ -14,12 +15,11 @@ export default [ plugins: { "import-x": importX, "@stylistic/ts": stylisticTs, - "@typescript-eslint": tseslint, + "@typescript-eslint": tseslint.plugin, }, 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-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 }, }, @@ -33,11 +33,11 @@ export default [ }, }, plugins: { - "@typescript-eslint": tseslint, + "@typescript-eslint": tseslint.plugin, }, 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-misused-promises": "error", // Disallow Promises in places not designed to handle them. - https://typescript-eslint.io/rules/no-misused-promises/ }, }, -]; +); diff --git a/public/locales b/public/locales index 18c1963ef30..e98f0eb9c20 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 18c1963ef309612a5a7fef76f9879709a7202189 +Subproject commit e98f0eb9c2022bc78b53f0444424c636498e725a diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 6f854a3bbd8..9b8703d6e85 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -652,7 +652,7 @@ export default class Move implements Localizable { break; case MoveFlags.IGNORE_ABILITIES: if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { - const abilityEffectsIgnored = new BooleanHolder(false); + const abilityEffectsIgnored = new BooleanHolder(false); applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); if (abilityEffectsIgnored.value) { return true; @@ -3160,7 +3160,7 @@ export class StatStageChangeAttr extends MoveEffectAttr { private get showMessage () { return this.options?.showMessage ?? true; } - + /** * 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 @@ -6326,11 +6326,11 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { if (!allyPokemon?.isActive(true) && switchOutTarget.hp) { globalScene.pushPhase(new BattleEndPhase(false)); - + if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { globalScene.pushPhase(new SelectBiomePhase()); } - + globalScene.pushPhase(new NewBattlePhase()); } } diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 2fff2b562c0..5a9a6ee9b3d 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -488,6 +488,7 @@ export abstract class PokemonSpeciesForm { if (formSpriteKey.startsWith("behemoth")) { formSpriteKey = "crowned"; } + // biome-ignore lint/suspicious/no-fallthrough: Falls through default: ret += `-${formSpriteKey}`; break; diff --git a/src/data/weather.ts b/src/data/weather.ts index 81559304661..be9107798df 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -369,6 +369,7 @@ export function getRandomWeatherType(arena: Arena): WeatherType { if (hasSun) { weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); } + break; case Biome.VOLCANO: weatherPool = [ { diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 31faf2b6283..8bbba267bd6 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -804,6 +804,7 @@ export function setSetting(setting: string, value: number): boolean { break; case SettingKeys.Candy_Upgrade_Display: globalScene.candyUpgradeDisplay = value; + break; case SettingKeys.Money_Format: switch (Setting[index].options[value].value) { case "Normal": diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index bf4f51e5af7..0c13cdb9512 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -176,11 +176,13 @@ export class UiInputs { return; } switch (globalScene.ui?.getMode()) { - case UiMode.MESSAGE: + case UiMode.MESSAGE: { const messageHandler = globalScene.ui.getHandler(); if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) { return; } + // biome-ignore lint/suspicious/noFallthroughSwitchClause: falls through to show menu overlay + } case UiMode.TITLE: case UiMode.COMMAND: case UiMode.MODIFIER_SELECT: diff --git a/tsconfig.json b/tsconfig.json index 30e208745b9..6af3e9ce650 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "esModuleInterop": true, "strictNullChecks": true, "sourceMap": false, - "strict": false, + "strict": false, // TODO: Enable this eventually "rootDir": ".", "baseUrl": "./src", "paths": { From 1e8fc076a7b63a9edad8e419888e3f2788f1f8da Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Tue, 29 Apr 2025 20:30:28 -0500 Subject: [PATCH 3/5] [Misc][Move] Add edge case to transform (#5732) --- src/data/moves/move.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 9b8703d6e85..f02c98fad1f 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -8618,7 +8618,9 @@ export function initMoves() { .condition((user, target, move) => !target.summonData?.illusion && !user.summonData?.illusion) // transforming from or into fusion pokemon causes various problems (such as crashes) .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) .attr(StatStageChangeAttr, [ Stat.SPD ], -1) .target(MoveTarget.ALL_NEAR_ENEMIES), From 84a2ce979f64dc5759b1269d686da95712d8d172 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Tue, 29 Apr 2025 21:48:06 -0500 Subject: [PATCH 4/5] [GitHub] Update pull request template to say `test:silent` (#5733) --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 032e1fee69c..a25a2f807f3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -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? - [ ] Have I provided a clear explanation of the changes? - [ ] 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 provided screenshots/videos of the changes (if applicable)? - [ ] Have I made sure that any UI change works for both UI themes (default and legacy)? From ef9a867e67edc6c312e1aa89ab04711120b5c07b Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 30 Apr 2025 01:55:42 -0500 Subject: [PATCH 5/5] [Bug] Fix clear ignoring errors from server (#5722) * Fix clear ignoring errors from server * Update tests to expect a throw --- src/phases/game-over-phase.ts | 15 +++++++++++++-- src/plugins/api/pokerogue-session-savedata-api.ts | 9 ++++++--- .../api/pokerogue-session-savedata-api.test.ts | 4 +--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 304d876a99e..3a3305fd45e 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -31,6 +31,7 @@ import ChallengeData from "#app/system/challenge-data"; import TrainerData from "#app/system/trainer-data"; import ArenaData from "#app/system/arena-data"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import { MessagePhase } from "./message-phase"; export class GameOverPhase extends BattlePhase { private isVictory: boolean; @@ -122,7 +123,7 @@ export class GameOverPhase extends BattlePhase { globalScene.disableMenu = true; globalScene.time.delayedCall(1000, () => { let firstClear = false; - if (this.isVictory && newClear) { + if (this.isVictory) { if (globalScene.gameMode.isClassic) { firstClear = globalScene.validateAchv(achvs.CLASSIC_VICTORY); globalScene.validateAchv(achvs.UNEVOLVED_CLASSIC_VICTORY); @@ -226,7 +227,17 @@ export class GameOverPhase extends BattlePhase { isVictory: this.isVictory, 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) { globalScene.gameData.offlineNewClear().then(result => { doGameOver(result); diff --git a/src/plugins/api/pokerogue-session-savedata-api.ts b/src/plugins/api/pokerogue-session-savedata-api.ts index e703d55a242..aac8b9b93ad 100644 --- a/src/plugins/api/pokerogue-session-savedata-api.ts +++ b/src/plugins/api/pokerogue-session-savedata-api.ts @@ -20,17 +20,20 @@ export class PokerogueSessionSavedataApi extends ApiBase { * *This is **NOT** the same as {@linkcode clear | clear()}.* * @param params The {@linkcode NewClearSessionSavedataRequest} to send * @returns The raw savedata as `string`. + * @throws Error if the request fails */ public async newclear(params: NewClearSessionSavedataRequest) { try { const urlSearchParams = this.toUrlSearchParams(params); const response = await this.doGet(`/savedata/session/newclear?${urlSearchParams}`); const json = await response.json(); - - return Boolean(json); + if (response.ok) { + return Boolean(json); + } + throw new Error("Could not newclear session!"); } catch (err) { console.warn("Could not newclear session!", err); - return false; + throw new Error("Could not newclear session!"); } } diff --git a/test/plugins/api/pokerogue-session-savedata-api.test.ts b/test/plugins/api/pokerogue-session-savedata-api.test.ts index d4c235ac51a..4d4774f2283 100644 --- a/test/plugins/api/pokerogue-session-savedata-api.test.ts +++ b/test/plugins/api/pokerogue-session-savedata-api.test.ts @@ -57,9 +57,7 @@ describe("Pokerogue Session Savedata API", () => { it("should return false and report a warning on ERROR", async () => { server.use(http.get(`${apiBase}/savedata/session/newclear`, () => HttpResponse.error())); - const success = await sessionSavedataApi.newclear(params); - - expect(success).toBe(false); + await expect(sessionSavedataApi.newclear(params)).rejects.toThrow("Could not newclear session!"); expect(console.warn).toHaveBeenCalledWith("Could not newclear session!", expect.any(Error)); }); });