Merge branch 'beta' into held-item-refactor

This commit is contained in:
Wlowscha 2025-05-01 09:59:25 +02:00 committed by GitHub
commit e370698dcf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
613 changed files with 5241 additions and 3830 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,40 +1,34 @@
# ESLint # Biome
## Key Features ## Key Features
1. **Automation**: 1. **Automation**:
- A pre-commit hook has been added to automatically run ESLint on the added or modified files, ensuring code quality before commits. - A pre-commit hook has been added to automatically run Biome on the added or modified files, ensuring code quality before commits.
2. **Manual Usage**: 2. **Manual Usage**:
- If you prefer not to use the pre-commit hook, you can manually run ESLint to automatically fix issues using the command: - If you prefer not to use the pre-commit hook, you can manually run biome to automatically fix issues using the command:
```sh ```sh
npx eslint --fix . or npm run eslint npx @biomejs/biome --write
``` ```
- Running this command will lint all files in the repository. - Running this command will lint all files in the repository.
3. **GitHub Action**: 3. **GitHub Action**:
- A GitHub Action has been added to automatically run ESLint on every push and pull request, ensuring code quality in the CI/CD pipeline. - A GitHub Action has been added to automatically run Biome on every push and pull request, ensuring code quality in the CI/CD pipeline.
## Summary of ESLint Rules If you are getting linting errors from biome and want to see which files they are coming from, you can find that out by running biome in a way that is configured to only show the errors for that specific rule: ``npx @biomejs/biome lint --only=category/ruleName``
1. **General Rules**: ## Summary of Biome Rules
- **Equality**: Use `===` and `!==` instead of `==` and `!=` (`eqeqeq`).
- **Indentation**: Enforce 2-space indentation (`indent`).
- **Quotes**: Use doublequotes for strings (`quotes`).
- **Variable Declarations**:
- Disallow `var`; use `let` or `const` (`no-var`).
- Prefer `const` for variables that are never reassigned (`prefer-const`).
- **Unused Variables**: Allow unused function parameters but enforce error for other unused variables (`@typescript-eslint/no-unused-vars`).
- **End of Line**: Ensure at least one newline at the end of files (`eol-last`).
- **Curly Braces**: Enforce the use of curly braces for all control statements (`curly`).
- **Brace Style**: Use one true brace style (`1tbs`) for TypeScript-specific syntax (`@typescript-eslint/brace-style`).
2. **TypeScript-Specific Rules**: We use the [recommended ruleset](https://biomejs.dev/linter/rules/) for Biome, with some customizations to better suit our project's needs.
- **Semicolons**:
- Enforce semicolons for TypeScript-specific syntax (`@typescript-eslint/semi`).
- Disallow unnecessary semicolons (`@typescript-eslint/no-extra-semi`).
## Benefits For a complete list of rules and their configurations, refer to the `biome.jsonc` file in the project root.
- **Consistency**: Ensures consistent coding style across the project. Some things to consider:
- **Code Quality**: Helps catch potential errors and improve overall code quality.
- **Readability**: Makes the codebase easier to read and maintain. - We have disabled rules that prioritize style over performance, such as `useTemplate`
- Some rules are currently marked as warnings (`warn`) to allow for gradual refactoring without blocking development. Do not write new code that triggers these warnings.
- The linter is configured to ignore specific files and folders, such as large or complex files that are pending refactors, to improve performance and focus on actionable areas.
Formatting is also handled by Biome. You should not have to worry about manually formatting your code.

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/
}, },
}, },
]; );

18
package-lock.json generated
View File

@ -18,8 +18,8 @@
"i18next-korean-postposition-processor": "^1.0.0", "i18next-korean-postposition-processor": "^1.0.0",
"json-stable-stringify": "^1.2.0", "json-stable-stringify": "^1.2.0",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"phaser": "^3.70.0", "phaser": "^3.88.2",
"phaser3-rex-plugins": "^1.80.14" "phaser3-rex-plugins": "^1.80.15"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4", "@biomejs/biome": "1.9.4",
@ -48,7 +48,7 @@
"vitest-canvas-mock": "^0.3.3" "vitest-canvas-mock": "^0.3.3"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=22.0.0"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
@ -6227,18 +6227,18 @@
} }
}, },
"node_modules/phaser": { "node_modules/phaser": {
"version": "3.80.1", "version": "3.88.2",
"resolved": "https://registry.npmjs.org/phaser/-/phaser-3.80.1.tgz", "resolved": "https://registry.npmjs.org/phaser/-/phaser-3.88.2.tgz",
"integrity": "sha512-VQGAWoDOkEpAWYkI+PUADv5Ql+SM0xpLuAMBJHz9tBcOLqjJ2wd8bUhxJgOqclQlLTg97NmMd9MhS75w16x1Cw==", "integrity": "sha512-UBgd2sAFuRJbF2xKaQ5jpMWB8oETncChLnymLGHcrnT53vaqiGrQWbUKUDBawKLm24sghjKo4Bf+/xfv8espZQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"eventemitter3": "^5.0.1" "eventemitter3": "^5.0.1"
} }
}, },
"node_modules/phaser3-rex-plugins": { "node_modules/phaser3-rex-plugins": {
"version": "1.80.14", "version": "1.80.15",
"resolved": "https://registry.npmjs.org/phaser3-rex-plugins/-/phaser3-rex-plugins-1.80.14.tgz", "resolved": "https://registry.npmjs.org/phaser3-rex-plugins/-/phaser3-rex-plugins-1.80.15.tgz",
"integrity": "sha512-eHi3VgryO9umNu6D1yQU5IS6tH4TyC2Y6RgJ495nNp37X2fdYnmYpBfgFg+YaumvtaoOvCkUVyi/YqWNPf2X2A==", "integrity": "sha512-Ur973N1W5st6XEYBcJko8eTcEbdDHMM+m7VqvT3j/EJeJwYyJ3bVb33JJDsFgefk3A2iAz2itP/UY7CzxJOJVA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"dagre": "^0.8.5", "dagre": "^0.8.5",

View File

@ -1,7 +1,7 @@
{ {
"name": "pokemon-rogue-battle", "name": "pokemon-rogue-battle",
"private": true, "private": true,
"version": "1.8.4", "version": "1.8.5",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "vite", "start": "vite",
@ -9,7 +9,7 @@
"build": "vite build", "build": "vite build",
"build:beta": "vite build --mode beta", "build:beta": "vite build --mode beta",
"preview": "vite preview", "preview": "vite preview",
"test": "vitest run", "test": "vitest run --no-isolate",
"test:cov": "vitest run --coverage --no-isolate", "test:cov": "vitest run --coverage --no-isolate",
"test:watch": "vitest watch --coverage --no-isolate", "test:watch": "vitest watch --coverage --no-isolate",
"test:silent": "vitest run --silent --no-isolate", "test:silent": "vitest run --silent --no-isolate",
@ -63,8 +63,8 @@
"i18next-korean-postposition-processor": "^1.0.0", "i18next-korean-postposition-processor": "^1.0.0",
"json-stable-stringify": "^1.2.0", "json-stable-stringify": "^1.2.0",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"phaser": "^3.70.0", "phaser": "^3.88.2",
"phaser3-rex-plugins": "^1.80.14" "phaser3-rex-plugins": "^1.80.15"
}, },
"engines": { "engines": {
"node": ">=22.0.0" "node": ">=22.0.0"

View File

@ -516,8 +516,36 @@
"trimmed": true, "trimmed": true,
"spriteSourceSize": { "x": 0, "y": 0, "w": 28, "h": 11 }, "spriteSourceSize": { "x": 0, "y": 0, "w": 28, "h": 11 },
"sourceSize": { "w": 28, "h": 11 } "sourceSize": { "w": 28, "h": 11 }
},
"BACK_SLASH.png": {
"frame": { "x": 147, "y": 66, "w": 12, "h": 11 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": { "w": 12, "h": 11 }
},
"FORWARD_SLASH.png": {
"frame": { "x": 144, "y": 55, "w": 12, "h": 11 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": { "w": 12, "h": 11 }
},
"COMMA.png": {
"frame": { "x": 144, "y": 44, "w": 12, "h": 11 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": { "w": 12, "h": 11 }
},
"PERIOD.png": {
"frame": { "x": 143, "y": 22, "w": 11, "h": 11 },
"rotated": false,
"trimmed": true,
"spriteSourceSize": { "x": 0, "y": 0, "w": 11, "h": 11 },
"sourceSize": { "w": 11, "h": 11 }
} }
}, },
"meta": { "meta": {
"app": "https://www.aseprite.org/", "app": "https://www.aseprite.org/",
"version": "1.3.7-dev", "version": "1.3.7-dev",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -1 +1 @@
Subproject commit e98f0eb9c2022bc78b53f0444424c636498e725a Subproject commit 833dc40ec7409031fcea147ccbc45ec9c0ba0213

View File

@ -1,7 +1,7 @@
import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import type { UserInfo } from "#app/@types/UserInfo"; import type { UserInfo } from "#app/@types/UserInfo";
import { bypassLogin } from "#app/battle-scene"; import { bypassLogin } from "./global-vars/bypass-login";
import { randomString } from "#app/utils"; import { randomString } from "#app/utils/common";
export let loggedInUser: UserInfo | null = null; export let loggedInUser: UserInfo | null = null;
// This is a random string that is used to identify the client session - unique per session (tab or window) so that the game will only save on the one that the server is expecting // This is a random string that is used to identify the client session - unique per session (tab or window) so that the game will only save on the one that the server is expecting

View File

@ -18,7 +18,7 @@ import {
isNullOrUndefined, isNullOrUndefined,
BooleanHolder, BooleanHolder,
type Constructor, type Constructor,
} from "#app/utils"; } from "#app/utils/common";
import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier"; import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier";
import { import {
ConsumableModifier, ConsumableModifier,
@ -77,7 +77,8 @@ import {
} from "#app/data/abilities/ability"; } from "#app/data/abilities/ability";
import { allAbilities } from "./data/data-lists"; import { allAbilities } from "./data/data-lists";
import type { FixedBattleConfig } from "#app/battle"; import type { FixedBattleConfig } from "#app/battle";
import Battle, { BattleType } from "#app/battle"; import Battle from "#app/battle";
import { BattleType } from "#enums/battle-type";
import type { GameMode } from "#app/game-mode"; import type { GameMode } from "#app/game-mode";
import { GameModes, getGameMode } from "#app/game-mode"; import { GameModes, getGameMode } from "#app/game-mode";
import FieldSpritePipeline from "#app/pipelines/field-sprite"; import FieldSpritePipeline from "#app/pipelines/field-sprite";
@ -150,7 +151,6 @@ import { NextEncounterPhase } from "#app/phases/next-encounter-phase";
import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase"; import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase";
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
import { ReturnPhase } from "#app/phases/return-phase"; import { ReturnPhase } from "#app/phases/return-phase";
import { SelectBiomePhase } from "#app/phases/select-biome-phase";
import { ShowTrainerPhase } from "#app/phases/show-trainer-phase"; import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
import { SummonPhase } from "#app/phases/summon-phase"; import { SummonPhase } from "#app/phases/summon-phase";
import { SwitchPhase } from "#app/phases/switch-phase"; import { SwitchPhase } from "#app/phases/switch-phase";
@ -184,8 +184,8 @@ import { HideAbilityPhase } from "#app/phases/hide-ability-phase";
import { expSpriteKeys } from "./sprites/sprite-keys"; import { expSpriteKeys } from "./sprites/sprite-keys";
import { hasExpSprite } from "./sprites/sprite-utils"; import { hasExpSprite } from "./sprites/sprite-utils";
import { timedEventManager } from "./global-event-manager"; import { timedEventManager } from "./global-event-manager";
import { starterColors } from "./global-vars/starter-colors";
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; import { startingWave } from "./starting-wave";
const DEBUG_RNG = false; const DEBUG_RNG = false;
@ -193,13 +193,6 @@ const OPP_IVS_OVERRIDE_VALIDATED: number[] = (
Array.isArray(Overrides.OPP_IVS_OVERRIDE) ? Overrides.OPP_IVS_OVERRIDE : new Array(6).fill(Overrides.OPP_IVS_OVERRIDE) Array.isArray(Overrides.OPP_IVS_OVERRIDE) ? Overrides.OPP_IVS_OVERRIDE : new Array(6).fill(Overrides.OPP_IVS_OVERRIDE)
).map(iv => (Number.isNaN(iv) || iv === null || iv > 31 ? -1 : iv)); ).map(iv => (Number.isNaN(iv) || iv === null || iv > 31 ? -1 : iv));
export const startingWave = Overrides.STARTING_WAVE_OVERRIDE || 1;
export let starterColors: StarterColors;
interface StarterColors {
[key: string]: [string, string];
}
export interface PokeballCounts { export interface PokeballCounts {
[pb: string]: number; [pb: string]: number;
} }
@ -809,11 +802,11 @@ export default class BattleScene extends SceneBase {
} }
async initStarterColors(): Promise<void> { async initStarterColors(): Promise<void> {
if (starterColors) { if (Object.keys(starterColors).length > 0) {
// already initialized
return; return;
} }
const sc = await this.cachedFetch("./starter-colors.json").then(res => res.json()); const sc = await this.cachedFetch("./starter-colors.json").then(res => res.json());
starterColors = {};
for (const key of Object.keys(sc)) { for (const key of Object.keys(sc)) {
starterColors[key] = sc[key]; starterColors[key] = sc[key];
} }
@ -1304,6 +1297,16 @@ export default class BattleScene extends SceneBase {
return Math.max(doubleChance.value, 1); return Math.max(doubleChance.value, 1);
} }
isNewBiome(currentBattle = this.currentBattle) {
const isWaveIndexMultipleOfTen = !(currentBattle.waveIndex % 10);
const isEndlessOrDaily = this.gameMode.hasShortBiomes || this.gameMode.isDaily;
const isEndlessFifthWave = this.gameMode.hasShortBiomes && currentBattle.waveIndex % 5 === 0;
const isWaveIndexMultipleOfFiftyMinusOne = currentBattle.waveIndex % 50 === 49;
const isNewBiome =
isWaveIndexMultipleOfTen || isEndlessFifthWave || (isEndlessOrDaily && isWaveIndexMultipleOfFiftyMinusOne);
return isNewBiome;
}
// TODO: ...this never actually returns `null`, right? // TODO: ...this never actually returns `null`, right?
newBattle( newBattle(
waveIndex?: number, waveIndex?: number,
@ -1338,22 +1341,27 @@ export default class BattleScene extends SceneBase {
} else { } else {
if ( if (
!this.gameMode.hasTrainers || !this.gameMode.hasTrainers ||
Overrides.BATTLE_TYPE_OVERRIDE === BattleType.WILD ||
(Overrides.DISABLE_STANDARD_TRAINERS_OVERRIDE && isNullOrUndefined(trainerData)) (Overrides.DISABLE_STANDARD_TRAINERS_OVERRIDE && isNullOrUndefined(trainerData))
) { ) {
newBattleType = BattleType.WILD; newBattleType = BattleType.WILD;
} else if (battleType === undefined) {
newBattleType = this.gameMode.isWaveTrainer(newWaveIndex, this.arena) ? BattleType.TRAINER : BattleType.WILD;
} else { } else {
newBattleType = battleType; newBattleType =
Overrides.BATTLE_TYPE_OVERRIDE ??
battleType ??
(this.gameMode.isWaveTrainer(newWaveIndex, this.arena) ? BattleType.TRAINER : BattleType.WILD);
} }
if (newBattleType === BattleType.TRAINER) { if (newBattleType === BattleType.TRAINER) {
const trainerType = this.arena.randomTrainerType(newWaveIndex); const trainerType =
Overrides.RANDOM_TRAINER_OVERRIDE?.trainerType ?? this.arena.randomTrainerType(newWaveIndex);
let doubleTrainer = false; let doubleTrainer = false;
if (trainerConfigs[trainerType].doubleOnly) { if (trainerConfigs[trainerType].doubleOnly) {
doubleTrainer = true; doubleTrainer = true;
} else if (trainerConfigs[trainerType].hasDouble) { } else if (trainerConfigs[trainerType].hasDouble) {
doubleTrainer = !randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField)); doubleTrainer =
Overrides.RANDOM_TRAINER_OVERRIDE?.alwaysDouble ||
!randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField));
// Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance // Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance
if ( if (
trainerConfigs[trainerType].trainerTypeDouble && trainerConfigs[trainerType].trainerTypeDouble &&
@ -1373,7 +1381,10 @@ export default class BattleScene extends SceneBase {
// Check for mystery encounter // Check for mystery encounter
// Can only occur in place of a standard (non-boss) wild battle, waves 10-180 // Can only occur in place of a standard (non-boss) wild battle, waves 10-180
if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex) || newBattleType === BattleType.MYSTERY_ENCOUNTER) { if (
!Overrides.BATTLE_TYPE_OVERRIDE &&
(this.isWaveMysteryEncounter(newBattleType, newWaveIndex) || newBattleType === BattleType.MYSTERY_ENCOUNTER)
) {
newBattleType = BattleType.MYSTERY_ENCOUNTER; newBattleType = BattleType.MYSTERY_ENCOUNTER;
// Reset to base spawn weight // Reset to base spawn weight
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT; this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
@ -1395,10 +1406,10 @@ export default class BattleScene extends SceneBase {
newDouble = false; newDouble = false;
} }
if (!isNullOrUndefined(Overrides.BATTLE_TYPE_OVERRIDE)) { if (!isNullOrUndefined(Overrides.BATTLE_STYLE_OVERRIDE)) {
let doubleOverrideForWave: "single" | "double" | null = null; let doubleOverrideForWave: "single" | "double" | null = null;
switch (Overrides.BATTLE_TYPE_OVERRIDE) { switch (Overrides.BATTLE_STYLE_OVERRIDE) {
case "double": case "double":
doubleOverrideForWave = "double"; doubleOverrideForWave = "double";
break; break;
@ -1418,7 +1429,7 @@ export default class BattleScene extends SceneBase {
} }
/** /**
* Override battles into single only if not fighting with trainers. * Override battles into single only if not fighting with trainers.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/1948 | GitHub Issue #1948} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/1948 GitHub Issue #1948}
*/ */
if (newBattleType !== BattleType.TRAINER && doubleOverrideForWave === "single") { if (newBattleType !== BattleType.TRAINER && doubleOverrideForWave === "single") {
newDouble = false; newDouble = false;
@ -1459,12 +1470,7 @@ export default class BattleScene extends SceneBase {
} }
if (!waveIndex && lastBattle) { if (!waveIndex && lastBattle) {
const isWaveIndexMultipleOfTen = !(lastBattle.waveIndex % 10); const isNewBiome = this.isNewBiome(lastBattle);
const isEndlessOrDaily = this.gameMode.hasShortBiomes || this.gameMode.isDaily;
const isEndlessFifthWave = this.gameMode.hasShortBiomes && lastBattle.waveIndex % 5 === 0;
const isWaveIndexMultipleOfFiftyMinusOne = lastBattle.waveIndex % 50 === 49;
const isNewBiome =
isWaveIndexMultipleOfTen || isEndlessFifthWave || (isEndlessOrDaily && isWaveIndexMultipleOfFiftyMinusOne);
const resetArenaState = const resetArenaState =
isNewBiome || isNewBiome ||
[BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.currentBattle.battleType) || [BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.currentBattle.battleType) ||
@ -1513,7 +1519,6 @@ export default class BattleScene extends SceneBase {
if (!this.gameMode.hasRandomBiomes && !isNewBiome) { if (!this.gameMode.hasRandomBiomes && !isNewBiome) {
this.pushPhase(new NextEncounterPhase()); this.pushPhase(new NextEncounterPhase());
} else { } else {
this.pushPhase(new SelectBiomePhase());
this.pushPhase(new NewBiomeEncounterPhase()); this.pushPhase(new NewBiomeEncounterPhase());
const newMaxExpLevel = this.getMaxExpLevel(); const newMaxExpLevel = this.getMaxExpLevel();

View File

@ -8,7 +8,7 @@ import {
shiftCharCodes, shiftCharCodes,
randSeedItem, randSeedItem,
randInt, randInt,
} from "#app/utils"; } from "#app/utils/common";
import Trainer, { TrainerVariant } from "./field/trainer"; import Trainer, { TrainerVariant } from "./field/trainer";
import type { GameMode } from "./game-mode"; import type { GameMode } from "./game-mode";
import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier"; import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
@ -30,36 +30,8 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import type { CustomModifierSettings } from "#app/modifier/modifier-type";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { BattleType } from "#enums/battle-type";
export enum ClassicFixedBossWaves { import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
TOWN_YOUNGSTER = 5,
RIVAL_1 = 8,
RIVAL_2 = 25,
EVIL_GRUNT_1 = 35,
RIVAL_3 = 55,
EVIL_GRUNT_2 = 62,
EVIL_GRUNT_3 = 64,
EVIL_ADMIN_1 = 66,
RIVAL_4 = 95,
EVIL_GRUNT_4 = 112,
EVIL_ADMIN_2 = 114,
EVIL_BOSS_1 = 115,
RIVAL_5 = 145,
EVIL_BOSS_2 = 165,
ELITE_FOUR_1 = 182,
ELITE_FOUR_2 = 184,
ELITE_FOUR_3 = 186,
ELITE_FOUR_4 = 188,
CHAMPION = 190,
RIVAL_6 = 195,
}
export enum BattleType {
WILD,
TRAINER,
CLEAR,
MYSTERY_ENCOUNTER,
}
export enum BattlerIndex { export enum BattlerIndex {
ATTACKER = -1, ATTACKER = -1,

View File

@ -31,6 +31,7 @@ const cfg_keyboard_qwerty = {
KEY_X: Phaser.Input.Keyboard.KeyCodes.X, KEY_X: Phaser.Input.Keyboard.KeyCodes.X,
KEY_Y: Phaser.Input.Keyboard.KeyCodes.Y, KEY_Y: Phaser.Input.Keyboard.KeyCodes.Y,
KEY_Z: Phaser.Input.Keyboard.KeyCodes.Z, KEY_Z: Phaser.Input.Keyboard.KeyCodes.Z,
KEY_0: Phaser.Input.Keyboard.KeyCodes.ZERO, KEY_0: Phaser.Input.Keyboard.KeyCodes.ZERO,
KEY_1: Phaser.Input.Keyboard.KeyCodes.ONE, KEY_1: Phaser.Input.Keyboard.KeyCodes.ONE,
KEY_2: Phaser.Input.Keyboard.KeyCodes.TWO, KEY_2: Phaser.Input.Keyboard.KeyCodes.TWO,
@ -41,11 +42,7 @@ const cfg_keyboard_qwerty = {
KEY_7: Phaser.Input.Keyboard.KeyCodes.SEVEN, KEY_7: Phaser.Input.Keyboard.KeyCodes.SEVEN,
KEY_8: Phaser.Input.Keyboard.KeyCodes.EIGHT, KEY_8: Phaser.Input.Keyboard.KeyCodes.EIGHT,
KEY_9: Phaser.Input.Keyboard.KeyCodes.NINE, KEY_9: Phaser.Input.Keyboard.KeyCodes.NINE,
KEY_CTRL: Phaser.Input.Keyboard.KeyCodes.CTRL,
KEY_DEL: Phaser.Input.Keyboard.KeyCodes.DELETE,
KEY_END: Phaser.Input.Keyboard.KeyCodes.END,
KEY_ENTER: Phaser.Input.Keyboard.KeyCodes.ENTER,
KEY_ESC: Phaser.Input.Keyboard.KeyCodes.ESC,
KEY_F1: Phaser.Input.Keyboard.KeyCodes.F1, KEY_F1: Phaser.Input.Keyboard.KeyCodes.F1,
KEY_F2: Phaser.Input.Keyboard.KeyCodes.F2, KEY_F2: Phaser.Input.Keyboard.KeyCodes.F2,
KEY_F3: Phaser.Input.Keyboard.KeyCodes.F3, KEY_F3: Phaser.Input.Keyboard.KeyCodes.F3,
@ -58,24 +55,41 @@ const cfg_keyboard_qwerty = {
KEY_F10: Phaser.Input.Keyboard.KeyCodes.F10, KEY_F10: Phaser.Input.Keyboard.KeyCodes.F10,
KEY_F11: Phaser.Input.Keyboard.KeyCodes.F11, KEY_F11: Phaser.Input.Keyboard.KeyCodes.F11,
KEY_F12: Phaser.Input.Keyboard.KeyCodes.F12, KEY_F12: Phaser.Input.Keyboard.KeyCodes.F12,
KEY_HOME: Phaser.Input.Keyboard.KeyCodes.HOME,
KEY_INSERT: Phaser.Input.Keyboard.KeyCodes.INSERT,
KEY_PAGE_DOWN: Phaser.Input.Keyboard.KeyCodes.PAGE_DOWN, KEY_PAGE_DOWN: Phaser.Input.Keyboard.KeyCodes.PAGE_DOWN,
KEY_PAGE_UP: Phaser.Input.Keyboard.KeyCodes.PAGE_UP, KEY_PAGE_UP: Phaser.Input.Keyboard.KeyCodes.PAGE_UP,
KEY_CTRL: Phaser.Input.Keyboard.KeyCodes.CTRL,
KEY_DEL: Phaser.Input.Keyboard.KeyCodes.DELETE,
KEY_END: Phaser.Input.Keyboard.KeyCodes.END,
KEY_ENTER: Phaser.Input.Keyboard.KeyCodes.ENTER,
KEY_ESC: Phaser.Input.Keyboard.KeyCodes.ESC,
KEY_HOME: Phaser.Input.Keyboard.KeyCodes.HOME,
KEY_INSERT: Phaser.Input.Keyboard.KeyCodes.INSERT,
KEY_PLUS: Phaser.Input.Keyboard.KeyCodes.NUMPAD_ADD, // Assuming numpad plus KEY_PLUS: Phaser.Input.Keyboard.KeyCodes.NUMPAD_ADD, // Assuming numpad plus
KEY_MINUS: Phaser.Input.Keyboard.KeyCodes.NUMPAD_SUBTRACT, // Assuming numpad minus KEY_MINUS: Phaser.Input.Keyboard.KeyCodes.NUMPAD_SUBTRACT, // Assuming numpad minus
KEY_QUOTATION: Phaser.Input.Keyboard.KeyCodes.QUOTES, KEY_QUOTATION: Phaser.Input.Keyboard.KeyCodes.QUOTES,
KEY_SHIFT: Phaser.Input.Keyboard.KeyCodes.SHIFT, KEY_SHIFT: Phaser.Input.Keyboard.KeyCodes.SHIFT,
KEY_SPACE: Phaser.Input.Keyboard.KeyCodes.SPACE, KEY_SPACE: Phaser.Input.Keyboard.KeyCodes.SPACE,
KEY_TAB: Phaser.Input.Keyboard.KeyCodes.TAB, KEY_TAB: Phaser.Input.Keyboard.KeyCodes.TAB,
KEY_TILDE: Phaser.Input.Keyboard.KeyCodes.BACKTICK, KEY_TILDE: Phaser.Input.Keyboard.KeyCodes.BACKTICK,
KEY_ARROW_UP: Phaser.Input.Keyboard.KeyCodes.UP, KEY_ARROW_UP: Phaser.Input.Keyboard.KeyCodes.UP,
KEY_ARROW_DOWN: Phaser.Input.Keyboard.KeyCodes.DOWN, KEY_ARROW_DOWN: Phaser.Input.Keyboard.KeyCodes.DOWN,
KEY_ARROW_LEFT: Phaser.Input.Keyboard.KeyCodes.LEFT, KEY_ARROW_LEFT: Phaser.Input.Keyboard.KeyCodes.LEFT,
KEY_ARROW_RIGHT: Phaser.Input.Keyboard.KeyCodes.RIGHT, KEY_ARROW_RIGHT: Phaser.Input.Keyboard.KeyCodes.RIGHT,
KEY_LEFT_BRACKET: Phaser.Input.Keyboard.KeyCodes.OPEN_BRACKET, KEY_LEFT_BRACKET: Phaser.Input.Keyboard.KeyCodes.OPEN_BRACKET,
KEY_RIGHT_BRACKET: Phaser.Input.Keyboard.KeyCodes.CLOSED_BRACKET, KEY_RIGHT_BRACKET: Phaser.Input.Keyboard.KeyCodes.CLOSED_BRACKET,
KEY_SEMICOLON: Phaser.Input.Keyboard.KeyCodes.SEMICOLON, KEY_SEMICOLON: Phaser.Input.Keyboard.KeyCodes.SEMICOLON,
KEY_COMMA: Phaser.Input.Keyboard.KeyCodes.COMMA,
KEY_PERIOD: Phaser.Input.Keyboard.KeyCodes.PERIOD,
KEY_BACK_SLASH: Phaser.Input.Keyboard.KeyCodes.BACK_SLASH,
KEY_FORWARD_SLASH: Phaser.Input.Keyboard.KeyCodes.FORWARD_SLASH,
KEY_BACKSPACE: Phaser.Input.Keyboard.KeyCodes.BACKSPACE, KEY_BACKSPACE: Phaser.Input.Keyboard.KeyCodes.BACKSPACE,
KEY_ALT: Phaser.Input.Keyboard.KeyCodes.ALT, KEY_ALT: Phaser.Input.Keyboard.KeyCodes.ALT,
}, },
@ -160,6 +174,10 @@ const cfg_keyboard_qwerty = {
KEY_RIGHT_BRACKET: "RIGHT_BRACKET.png", KEY_RIGHT_BRACKET: "RIGHT_BRACKET.png",
KEY_SEMICOLON: "SEMICOLON.png", KEY_SEMICOLON: "SEMICOLON.png",
KEY_COMMA: "COMMA.png",
KEY_PERIOD: "PERIOD.png",
KEY_BACK_SLASH: "BACK_SLASH.png",
KEY_FORWARD_SLASH: "FORWARD_SLASH.png",
KEY_BACKSPACE: "BACK.png", KEY_BACKSPACE: "BACK.png",
KEY_ALT: "ALT.png", KEY_ALT: "ALT.png",

View File

@ -9,3 +9,8 @@ export const SESSION_ID_COOKIE_NAME: string = "pokerogue_sessionId";
/** Max value for an integer attribute in {@linkcode SystemSaveData} */ /** Max value for an integer attribute in {@linkcode SystemSaveData} */
export const MAX_INT_ATTR_VALUE = 0x80000000; export const MAX_INT_ATTR_VALUE = 0x80000000;
/** The min and max waves for mystery encounters to spawn in classic mode */
export const CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180] as const;
/** The min and max waves for mystery encounters to spawn in challenge mode */
export const CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180] as const;

View File

@ -1,6 +1,6 @@
import type { AbAttrCondition } from "#app/@types/ability-types"; import type { AbAttrCondition } from "#app/@types/ability-types";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type * as Utils from "#app/utils"; import type { BooleanHolder } from "#app/utils/common";
export abstract class AbAttr { export abstract class AbAttr {
public showAbility: boolean; public showAbility: boolean;
@ -22,7 +22,7 @@ export abstract class AbAttr {
_pokemon: Pokemon, _pokemon: Pokemon,
_passive: boolean, _passive: boolean,
_simulated: boolean, _simulated: boolean,
_cancelled: Utils.BooleanHolder | null, _cancelled: BooleanHolder | null,
_args: any[], _args: any[],
): void {} ): void {}

View File

@ -3,7 +3,7 @@ import type { AbAttrCondition } from "#app/@types/ability-types";
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
import i18next from "i18next"; import i18next from "i18next";
import type { Localizable } from "#app/interfaces/locales"; import type { Localizable } from "#app/interfaces/locales";
import type { Constructor } from "#app/utils"; import type { Constructor } from "#app/utils/common";
export class Ability implements Localizable { export class Ability implements Localizable {
public id: Abilities; public id: Abilities;

View File

@ -1,5 +1,5 @@
import { HitResult, MoveResult, PlayerPokemon } from "#app/field/pokemon"; import { HitResult, MoveResult, PlayerPokemon } from "#app/field/pokemon";
import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor } from "#app/utils"; import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor } from "#app/utils/common";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { BattlerTagLapseType, GroundedTag } from "#app/data/battler-tags"; import { BattlerTagLapseType, GroundedTag } from "#app/data/battler-tags";
import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect"; import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect";
@ -30,7 +30,7 @@ import i18next from "i18next";
import { Command } from "#app/ui/command-ui-handler"; import { Command } from "#app/ui/command-ui-handler";
import { BerryModifierType } from "#app/modifier/modifier-type"; import { BerryModifierType } from "#app/modifier/modifier-type";
import { getPokeballName } from "#app/data/pokeball"; import { getPokeballName } from "#app/data/pokeball";
import { BattleType } from "#app/battle"; import { BattleType } from "#enums/battle-type";
import { MovePhase } from "#app/phases/move-phase"; import { MovePhase } from "#app/phases/move-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
@ -44,6 +44,7 @@ import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; import { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
import { Ability } from "#app/data/abilities/ability-class"; import { Ability } from "#app/data/abilities/ability-class";
import { TrainerVariant } from "#app/field/trainer";
// Enum imports // Enum imports
import { Stat, type BattleStat , BATTLE_STATS, EFFECTIVE_STATS, getStatKey, type EffectiveStat } from "#enums/stat"; import { Stat, type BattleStat , BATTLE_STATS, EFFECTIVE_STATS, getStatKey, type EffectiveStat } from "#enums/stat";
@ -61,6 +62,7 @@ import { MoveFlags } from "#enums/MoveFlags";
import { MoveTarget } from "#enums/MoveTarget"; import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
// Type imports // Type imports
import type { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import type { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
@ -70,6 +72,7 @@ import type { AbAttrCondition, PokemonDefendCondition, PokemonStatStageChangeCon
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#app/battle";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag";
import { SelectBiomePhase } from "#app/phases/select-biome-phase";
export class BlockRecoilDamageAttr extends AbAttr { export class BlockRecoilDamageAttr extends AbAttr {
constructor() { constructor() {
@ -651,8 +654,8 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr {
*/ */
export class ReverseDrainAbAttr extends PostDefendAbAttr { export class ReverseDrainAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { override canApplyPostDefend(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _attacker: Pokemon, move: Move, _hitResult: HitResult | null, args: any[]): boolean {
return move.hasAttr(HitHealAttr) && !move.hitsSubstitute(attacker, pokemon); return move.hasAttr(HitHealAttr);
} }
/** /**
@ -691,7 +694,7 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr {
} }
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon); return this.condition(pokemon, attacker, move);
} }
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
@ -732,7 +735,7 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate); const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate);
const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1]; const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1];
const damageReceived = lastAttackReceived?.damage || 0; const damageReceived = lastAttackReceived?.damage || 0;
return this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat) && !move.hitsSubstitute(attacker, pokemon); return this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat);
} }
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
@ -755,7 +758,7 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
const tag = globalScene.arena.getTag(this.tagType) as ArenaTrapTag; const tag = globalScene.arena.getTag(this.tagType) as ArenaTrapTag;
return (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) return (this.condition(pokemon, attacker, move))
&& (!globalScene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers); && (!globalScene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers);
} }
@ -777,7 +780,7 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
} }
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon); return this.condition(pokemon, attacker, move);
} }
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
@ -794,7 +797,7 @@ export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
this.type = attacker.getMoveType(move); this.type = attacker.getMoveType(move);
const pokemonTypes = pokemon.getTypes(true); const pokemonTypes = pokemon.getTypes(true);
return hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon) && (simulated || pokemonTypes.length !== 1 || pokemonTypes[0] !== this.type); return hitResult < HitResult.NO_EFFECT && (simulated || pokemonTypes.length !== 1 || pokemonTypes[0] !== this.type);
} }
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): void { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): void {
@ -821,7 +824,7 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
} }
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
return hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon) && globalScene.arena.canSetTerrain(this.terrainType); return hitResult < HitResult.NO_EFFECT && globalScene.arena.canSetTerrain(this.terrainType);
} }
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): void { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): void {
@ -845,7 +848,7 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && !attacker.status return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && !attacker.status
&& (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)
&& attacker.canSetStatus(effect, true, false, pokemon); && attacker.canSetStatus(effect, true, false, pokemon);
} }
@ -885,7 +888,7 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && pokemon.randSeedInt(100) < this.chance return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && pokemon.randSeedInt(100) < this.chance
&& !move.hitsSubstitute(attacker, pokemon) && attacker.canAddTag(this.tagType); && attacker.canAddTag(this.tagType);
} }
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
@ -906,10 +909,6 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
this.stages = stages; this.stages = stages;
} }
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return !move.hitsSubstitute(attacker, pokemon);
}
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
if (!simulated) { if (!simulated) {
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages));
@ -932,7 +931,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return !simulated && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) return !simulated && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})
&& !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon); && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr);
} }
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
@ -991,7 +990,7 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr {
} }
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return (!(this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon)) return (!(this.condition && !this.condition(pokemon, attacker, move))
&& !globalScene.arena.weather?.isImmutable() && globalScene.arena.canSetWeather(this.weatherType)); && !globalScene.arena.weather?.isImmutable() && globalScene.arena.canSetWeather(this.weatherType));
} }
@ -1009,7 +1008,7 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})
&& attacker.getAbility().isSwappable && !move.hitsSubstitute(attacker, pokemon); && attacker.getAbility().isSwappable;
} }
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, args: any[]): void { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, args: any[]): void {
@ -1035,10 +1034,10 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && attacker.getAbility().isSuppressable return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && attacker.getAbility().isSuppressable
&& !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon); && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr);
} }
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { override applyPostDefend(_pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
if (!simulated) { if (!simulated) {
attacker.setTempAbility(allAbilities[this.ability]); attacker.setTempAbility(allAbilities[this.ability]);
} }
@ -1064,7 +1063,7 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
} }
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon) return attacker.getTag(BattlerTagType.DISABLED) === null
&& move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance); && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance);
} }
@ -1768,7 +1767,6 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
override canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { override canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
if ( if (
super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args)
&& !(pokemon !== attacker && move.hitsSubstitute(attacker, pokemon))
&& (simulated || !attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker && (simulated || !attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker
&& (!this.contactRequired || move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) && (!this.contactRequired || move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})) && pokemon.randSeedInt(100) < this.chance && !pokemon.status)
) { ) {
@ -1835,8 +1833,7 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
if ( if (
!simulated && !simulated &&
hitResult < HitResult.NO_EFFECT && hitResult < HitResult.NO_EFFECT &&
(!this.condition || this.condition(pokemon, attacker, move)) && (!this.condition || this.condition(pokemon, attacker, move))
!move.hitsSubstitute(attacker, pokemon)
) { ) {
const heldItems = this.getTargetHeldItems(attacker).filter((i) => i.isTransferable); const heldItems = this.getTargetHeldItems(attacker).filter((i) => i.isTransferable);
if (heldItems.length) { if (heldItems.length) {
@ -2217,18 +2214,6 @@ export class PostSummonMessageAbAttr extends PostSummonAbAttr {
} }
} }
/**
* Removes illusions when a Pokemon is summoned.
*/
export class PostSummonRemoveIllusionAbAttr extends PostSummonAbAttr {
applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
for (const pokemon of globalScene.getField(true)) {
pokemon.breakIllusion();
}
return true;
}
}
export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr { export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr {
//Attr doesn't force pokemon name on the message //Attr doesn't force pokemon name on the message
private message: string; private message: string;
@ -3187,6 +3172,7 @@ export class PreSetStatusAbAttr extends AbAttr {
*/ */
export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr {
protected immuneEffects: StatusEffect[]; protected immuneEffects: StatusEffect[];
private lastEffect: StatusEffect;
/** /**
* @param immuneEffects - The status effects to which the Pokémon is immune. * @param immuneEffects - The status effects to which the Pokémon is immune.
@ -3212,6 +3198,7 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr {
*/ */
override applyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: BooleanHolder, args: any[]): void { override applyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: BooleanHolder, args: any[]): void {
cancelled.value = true; cancelled.value = true;
this.lastEffect = effect;
} }
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
@ -3219,7 +3206,7 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr {
i18next.t("abilityTriggers:statusEffectImmunityWithName", { i18next.t("abilityTriggers:statusEffectImmunityWithName", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName, abilityName,
statusEffectName: getStatusEffectDescriptor(args[0] as StatusEffect) statusEffectName: getStatusEffectDescriptor(this.lastEffect)
}) : }) :
i18next.t("abilityTriggers:statusEffectImmunity", { i18next.t("abilityTriggers:statusEffectImmunity", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
@ -5073,6 +5060,8 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC
/** /**
* Takes no damage from the first hit of a damaging move. * Takes no damage from the first hit of a damaging move.
* This is used in the Disguise and Ice Face abilities. * This is used in the Disguise and Ice Face abilities.
*
* Does not apply to a user's substitute
* @extends ReceivedMoveDamageMultiplierAbAttr * @extends ReceivedMoveDamageMultiplierAbAttr
*/ */
export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
@ -5175,7 +5164,14 @@ export class IllusionPreSummonAbAttr extends PreSummonAbAttr {
} }
} }
export class IllusionBreakAbAttr extends PostDefendAbAttr { export class IllusionBreakAbAttr extends AbAttr {
override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder | null, _args: any[]): void {
pokemon.breakIllusion();
pokemon.summonData.illusionBroken = true;
}
}
export class PostDefendIllusionBreakAbAttr extends PostDefendAbAttr {
/** /**
* Destroy the illusion upon taking damage * Destroy the illusion upon taking damage
* *
@ -5490,6 +5486,11 @@ class ForceSwitchOutHelper {
if (switchOutTarget.hp) { if (switchOutTarget.hp) {
globalScene.pushPhase(new BattleEndPhase(false)); globalScene.pushPhase(new BattleEndPhase(false));
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
globalScene.pushPhase(new SelectBiomePhase());
}
globalScene.pushPhase(new NewBattlePhase()); globalScene.pushPhase(new NewBattlePhase());
} }
} }
@ -5526,8 +5527,8 @@ class ForceSwitchOutHelper {
const party = player ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); const party = player ? globalScene.getPlayerParty() : globalScene.getEnemyParty();
return (!player && globalScene.currentBattle.battleType === BattleType.WILD) return (!player && globalScene.currentBattle.battleType === BattleType.WILD)
|| party.filter(p => p.isAllowedInBattle() || party.filter(p => p.isAllowedInBattle() && !p.isOnField()
&& (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > globalScene.currentBattle.getBattlerCount(); && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > 0;
} }
/** /**
@ -6267,7 +6268,7 @@ export function applyOnGainAbAttrs(
} }
/** /**
* Clears primal weather/neutralizing gas during the turn if {@linkcode pokemon}'s ability corresponds to one * Applies ability attributes which activate when the ability is lost or suppressed (i.e. primal weather)
*/ */
export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void { export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
applySingleAbAttrs<PreLeaveFieldAbAttr>( applySingleAbAttrs<PreLeaveFieldAbAttr>(
@ -6279,6 +6280,17 @@ export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated
args, args,
true, true,
simulated); simulated);
applySingleAbAttrs<IllusionBreakAbAttr>(
pokemon,
passive,
IllusionBreakAbAttr,
(attr, passive) => attr.apply(pokemon, passive, simulated, null, args),
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
args,
true,
simulated
)
} }
/** /**
@ -6778,11 +6790,12 @@ export function initAbilities() {
return isNullOrUndefined(movePhase); return isNullOrUndefined(movePhase);
}, 1.3), }, 1.3),
new Ability(Abilities.ILLUSION, 5) new Ability(Abilities.ILLUSION, 5)
//The pokemon generate an illusion if it's available // The Pokemon generate an illusion if it's available
.attr(IllusionPreSummonAbAttr, false) .attr(IllusionPreSummonAbAttr, false)
//The pokemon loses his illusion when he is damaged by a move .attr(IllusionBreakAbAttr)
.attr(IllusionBreakAbAttr, true) // The Pokemon loses its illusion when damaged by a move
//Illusion is available again after a battle .attr(PostDefendIllusionBreakAbAttr, true)
// Illusion is available again after a battle
.conditionalAttr((pokemon) => pokemon.isAllowedInBattle(), IllusionPostBattleAbAttr, false) .conditionalAttr((pokemon) => pokemon.isAllowedInBattle(), IllusionPostBattleAbAttr, false)
.uncopiable() .uncopiable()
.bypassFaint(), .bypassFaint(),
@ -7196,8 +7209,6 @@ export function initAbilities() {
.attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr) .attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr)
.uncopiable() .uncopiable()
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonNeutralizingGas", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
.attr(PostSummonRemoveIllusionAbAttr)
.bypassFaint(), .bypassFaint(),
new Ability(Abilities.PASTEL_VEIL, 8) new Ability(Abilities.PASTEL_VEIL, 8)
.attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
@ -7218,7 +7229,7 @@ export function initAbilities() {
new Ability(Abilities.CURIOUS_MEDICINE, 8) new Ability(Abilities.CURIOUS_MEDICINE, 8)
.attr(PostSummonClearAllyStatStagesAbAttr), .attr(PostSummonClearAllyStatStagesAbAttr),
new Ability(Abilities.TRANSISTOR, 8) new Ability(Abilities.TRANSISTOR, 8)
.attr(MoveTypePowerBoostAbAttr, PokemonType.ELECTRIC), .attr(MoveTypePowerBoostAbAttr, PokemonType.ELECTRIC, 1.3),
new Ability(Abilities.DRAGONS_MAW, 8) new Ability(Abilities.DRAGONS_MAW, 8)
.attr(MoveTypePowerBoostAbAttr, PokemonType.DRAGON), .attr(MoveTypePowerBoostAbAttr, PokemonType.DRAGON),
new Ability(Abilities.CHILLING_NEIGH, 8) new Ability(Abilities.CHILLING_NEIGH, 8)

View File

@ -1,13 +1,13 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { Arena } from "#app/field/arena"; import type { Arena } from "#app/field/arena";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { BooleanHolder, NumberHolder, toDmgValue } from "#app/utils"; import { BooleanHolder, NumberHolder, toDmgValue } from "#app/utils/common";
import { allMoves } from "#app/data/moves/move"; import { allMoves } from "#app/data/moves/move";
import { MoveTarget } from "#enums/MoveTarget"; import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { HitResult, PokemonMove } from "#app/field/pokemon"; import { HitResult } from "#app/field/pokemon";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#app/battle";
import { import {
@ -335,7 +335,7 @@ export class ConditionalProtectTag extends ArenaTag {
* @param arena the {@linkcode Arena} containing this tag * @param arena the {@linkcode Arena} containing this tag
* @param simulated `true` if the tag is applied quietly; `false` otherwise. * @param simulated `true` if the tag is applied quietly; `false` otherwise.
* @param isProtected a {@linkcode BooleanHolder} used to flag if the move is protected against * @param isProtected a {@linkcode BooleanHolder} used to flag if the move is protected against
* @param attacker the attacking {@linkcode Pokemon} * @param _attacker the attacking {@linkcode Pokemon}
* @param defender the defending {@linkcode Pokemon} * @param defender the defending {@linkcode Pokemon}
* @param moveId the {@linkcode Moves | identifier} for the move being used * @param moveId the {@linkcode Moves | identifier} for the move being used
* @param ignoresProtectBypass a {@linkcode BooleanHolder} used to flag if a protection effect supercedes effects that ignore protection * @param ignoresProtectBypass a {@linkcode BooleanHolder} used to flag if a protection effect supercedes effects that ignore protection
@ -345,7 +345,7 @@ export class ConditionalProtectTag extends ArenaTag {
arena: Arena, arena: Arena,
simulated: boolean, simulated: boolean,
isProtected: BooleanHolder, isProtected: BooleanHolder,
attacker: Pokemon, _attacker: Pokemon,
defender: Pokemon, defender: Pokemon,
moveId: Moves, moveId: Moves,
ignoresProtectBypass: BooleanHolder, ignoresProtectBypass: BooleanHolder,
@ -354,8 +354,6 @@ export class ConditionalProtectTag extends ArenaTag {
if (!isProtected.value) { if (!isProtected.value) {
isProtected.value = true; isProtected.value = true;
if (!simulated) { if (!simulated) {
attacker.stopMultiHit(defender);
new CommonBattleAnim(CommonAnim.PROTECT, defender).play(); new CommonBattleAnim(CommonAnim.PROTECT, defender).play();
globalScene.queueMessage( globalScene.queueMessage(
i18next.t("arenaTag:conditionalProtectApply", { i18next.t("arenaTag:conditionalProtectApply", {
@ -899,7 +897,7 @@ export class DelayedAttackTag extends ArenaTag {
if (!ret) { if (!ret) {
globalScene.unshiftPhase( globalScene.unshiftPhase(
new MoveEffectPhase(this.sourceId!, [this.targetIndex], new PokemonMove(this.sourceMove!, 0, 0, true)), new MoveEffectPhase(this.sourceId!, [this.targetIndex], allMoves[this.sourceMove!], false, true),
); // TODO: are those bangs correct? ); // TODO: are those bangs correct?
} }

View File

@ -1,5 +1,5 @@
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { randSeedInt, getEnumValues } from "#app/utils"; import { randSeedInt, getEnumValues } from "#app/utils/common";
import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import i18next from "i18next"; import i18next from "i18next";

View File

@ -1,5 +1,5 @@
import { allMoves } from "#app/data/moves/move"; import { allMoves } from "#app/data/moves/move";
import { getEnumKeys, getEnumValues } from "#app/utils"; import { getEnumKeys, getEnumValues } from "#app/utils/common";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";

View File

@ -3,7 +3,7 @@ import { Gender } from "#app/data/gender";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils/common";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
@ -14,6 +14,7 @@ import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifi
import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesFormKey } from "#enums/species-form-key";
import { speciesStarterCosts } from "./starters"; import { speciesStarterCosts } from "./starters";
import i18next from "i18next"; import i18next from "i18next";
import { initI18n } from "#app/plugins/i18n";
export enum SpeciesWildEvolutionDelay { export enum SpeciesWildEvolutionDelay {
@ -95,6 +96,9 @@ export class SpeciesFormEvolution {
public description = ""; public description = "";
constructor(speciesId: Species, preFormKey: string | null, evoFormKey: string | null, level: number, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) { constructor(speciesId: Species, preFormKey: string | null, evoFormKey: string | null, level: number, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) {
if (!i18next.isInitialized) {
initI18n();
}
this.speciesId = speciesId; this.speciesId = speciesId;
this.preFormKey = preFormKey; this.preFormKey = preFormKey;
this.evoFormKey = evoFormKey; this.evoFormKey = evoFormKey;

View File

@ -19383,6 +19383,44 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
[ 100, Moves.SEED_FLARE ], [ 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]: { [Species.KYUREM]: {
1: [ 1: [
[ 1, Moves.DRAGON_BREATH ], [ 1, Moves.DRAGON_BREATH ],

View File

@ -4,159 +4,103 @@ export type SignatureSpecies = {
[key in string]: (Species | Species[])[]; [key in string]: (Species | Species[])[];
}; };
/* /**
* The signature species for each Gym Leader, Elite Four member, and Champion. * The signature species for each Gym Leader, Elite Four member, and Champion.
* The key is the trainer type, and the value is an array of Species or Species arrays. * The key is the trainer type, and the value is an array of Species or Species arrays.
* This is in a separate const so it can be accessed from other places and not just the trainerConfigs * This is in a separate const so it can be accessed from other places and not just the trainerConfigs
*
* @remarks
* The `Proxy` object allows us to define a handler that will intercept
* the property access and return an empty array if the property does not exist in the object.
*
* This means that accessing `signatureSpecies` will not throw an error if the property does not exist,
* but instead default to an empty array.
*/ */
export const signatureSpecies: SignatureSpecies = { export const signatureSpecies: SignatureSpecies = new Proxy({
// Gym Leaders- Kanto // Gym Leaders- Kanto
BROCK: [Species.GEODUDE, Species.ONIX], BROCK: [Species.ONIX, Species.GEODUDE, [Species.OMANYTE, Species.KABUTO], Species.AERODACTYL],
MISTY: [Species.STARYU, Species.PSYDUCK], MISTY: [Species.STARYU, Species.PSYDUCK, Species.WOOPER, Species.LAPRAS],
LT_SURGE: [Species.VOLTORB, Species.PIKACHU, Species.ELECTABUZZ], LT_SURGE: [Species.PICHU, Species.VOLTORB, Species.ELEKID, Species.JOLTEON],
ERIKA: [Species.ODDISH, Species.BELLSPROUT, Species.TANGELA, Species.HOPPIP], ERIKA: [Species.ODDISH, Species.BELLSPROUT, Species.TANGELA, Species.HOPPIP],
JANINE: [Species.VENONAT, Species.SPINARAK, Species.ZUBAT], JANINE: [Species.VENONAT, Species.SPINARAK, Species.ZUBAT, Species.KOFFING],
SABRINA: [Species.ABRA, Species.MR_MIME, Species.ESPEON], SABRINA: [Species.ABRA, Species.MR_MIME, Species.SMOOCHUM, Species.ESPEON],
BLAINE: [Species.GROWLITHE, Species.PONYTA, Species.MAGMAR], BLAINE: [Species.GROWLITHE, Species.PONYTA, Species.MAGBY, Species.VULPIX],
GIOVANNI: [Species.SANDILE, Species.MURKROW, Species.NIDORAN_M, Species.NIDORAN_F], GIOVANNI: [Species.RHYHORN, Species.MEOWTH, [Species.NIDORAN_F, Species.NIDORAN_M], Species.DIGLETT], // Tera Ground Meowth
// Gym Leaders- Johto // Gym Leaders- Johto
FALKNER: [Species.PIDGEY, Species.HOOTHOOT, Species.DODUO], FALKNER: [Species.PIDGEY, Species.HOOTHOOT, Species.NATU, Species.MURKROW],
BUGSY: [Species.SCYTHER, Species.HERACROSS, Species.SHUCKLE, Species.PINSIR], BUGSY: [Species.SCYTHER, Species.SHUCKLE, Species.YANMA, [Species.PINSIR, Species.HERACROSS]],
WHITNEY: [Species.JIGGLYPUFF, Species.MILTANK, Species.AIPOM, Species.GIRAFARIG], WHITNEY: [Species.MILTANK, Species.AIPOM, Species.IGGLYBUFF, [Species.GIRAFARIG, Species.STANTLER]],
MORTY: [Species.GASTLY, Species.MISDREAVUS, Species.SABLEYE], MORTY: [Species.GASTLY, Species.MISDREAVUS, Species.DUSKULL, Species.SABLEYE],
CHUCK: [Species.POLIWRATH, Species.MANKEY], CHUCK: [Species.POLIWRATH, Species.MANKEY, Species.TYROGUE, Species.MACHOP],
JASMINE: [Species.MAGNEMITE, Species.STEELIX], JASMINE: [Species.STEELIX, Species.MAGNEMITE, Species.PINECO, Species.SKARMORY],
PRYCE: [Species.SEEL, Species.SWINUB], PRYCE: [Species.SWINUB, Species.SEEL, Species.SHELLDER, Species.SNEASEL],
CLAIR: [Species.DRATINI, Species.HORSEA, Species.GYARADOS], CLAIR: [Species.HORSEA, Species.DRATINI, Species.MAGIKARP, Species.DRUDDIGON], // Tera Dragon Magikarp
// Gym Leaders- Hoenn // Gym Leaders- Hoenn
ROXANNE: [Species.GEODUDE, Species.NOSEPASS], ROXANNE: [Species.NOSEPASS, Species.GEODUDE, [Species.LILEEP, Species.ANORITH], Species.ARON],
BRAWLY: [Species.MACHOP, Species.MAKUHITA], BRAWLY: [Species.MAKUHITA, Species.MACHOP, Species.MEDITITE, Species.SHROOMISH],
WATTSON: [Species.MAGNEMITE, Species.VOLTORB, Species.ELECTRIKE], WATTSON: [Species.ELECTRIKE, Species.VOLTORB, Species.MAGNEMITE, [Species.PLUSLE, Species.MINUN]],
FLANNERY: [Species.SLUGMA, Species.TORKOAL, Species.NUMEL], FLANNERY: [Species.TORKOAL, Species.SLUGMA, Species.NUMEL, Species.HOUNDOUR],
NORMAN: [Species.SLAKOTH, Species.SPINDA, Species.ZIGZAGOON, Species.KECLEON], NORMAN: [Species.SLAKOTH, Species.KECLEON, Species.WHISMUR, Species.ZANGOOSE],
WINONA: [Species.SWABLU, Species.WINGULL, Species.TROPIUS, Species.SKARMORY], WINONA: [Species.SWABLU, Species.WINGULL, Species.TROPIUS, Species.SKARMORY],
TATE: [Species.SOLROCK, Species.NATU, Species.CHIMECHO, Species.GALLADE], TATE: [Species.SOLROCK, Species.NATU, Species.CHINGLING, Species.GALLADE],
LIZA: [Species.LUNATONE, Species.SPOINK, Species.BALTOY, Species.GARDEVOIR], LIZA: [Species.LUNATONE, Species.BALTOY, Species.SPOINK, Species.GARDEVOIR],
JUAN: [Species.HORSEA, Species.BARBOACH, Species.SPHEAL, Species.RELICANTH], JUAN: [Species.HORSEA, Species.SPHEAL, Species.BARBOACH, Species.CORPHISH],
// Gym Leaders- Sinnoh // Gym Leaders- Sinnoh
ROARK: [Species.CRANIDOS, Species.LARVITAR, Species.GEODUDE], ROARK: [Species.CRANIDOS, Species.GEODUDE, Species.NOSEPASS, Species.LARVITAR],
GARDENIA: [Species.ROSELIA, Species.TANGELA, Species.TURTWIG], GARDENIA: [Species.BUDEW, Species.CHERUBI, Species.TURTWIG, Species.LEAFEON],
MAYLENE: [Species.LUCARIO, Species.MEDITITE, Species.CHIMCHAR], MAYLENE: [Species.RIOLU, Species.MEDITITE, Species.CHIMCHAR, Species.CROAGUNK],
CRASHER_WAKE: [Species.BUIZEL, Species.WOOPER, Species.PIPLUP, Species.MAGIKARP], CRASHER_WAKE: [Species.BUIZEL, Species.WOOPER, Species.PIPLUP, Species.MAGIKARP],
FANTINA: [Species.MISDREAVUS, Species.DRIFLOON, Species.SPIRITOMB], FANTINA: [Species.MISDREAVUS, Species.DRIFLOON, Species.DUSKULL, Species.SPIRITOMB],
BYRON: [Species.SHIELDON, Species.BRONZOR, Species.AGGRON], BYRON: [Species.SHIELDON, Species.BRONZOR, Species.ARON, Species.SKARMORY],
CANDICE: [Species.SNEASEL, Species.SNOVER, Species.SNORUNT], CANDICE: [Species.FROSLASS, Species.SNOVER, Species.SNEASEL, Species.GLACEON],
VOLKNER: [Species.SHINX, Species.CHINCHOU, Species.ROTOM], VOLKNER: [Species.ELEKID, Species.SHINX, Species.CHINCHOU, Species.ROTOM],
// Gym Leaders- Unova // Gym Leaders- Unova
CILAN: [Species.PANSAGE, Species.FOONGUS, Species.PETILIL], CILAN: [Species.PANSAGE, Species.SNIVY, Species.MARACTUS, Species.FERROSEED],
CHILI: [Species.PANSEAR, Species.DARUMAKA, Species.NUMEL], CHILI: [Species.PANSEAR, Species.TEPIG, Species.HEATMOR, Species.DARUMAKA],
CRESS: [Species.PANPOUR, Species.TYMPOLE, Species.SLOWPOKE], CRESS: [Species.PANPOUR, Species.OSHAWOTT, Species.BASCULIN, Species.TYMPOLE],
CHEREN: [Species.LILLIPUP, Species.MINCCINO, Species.PIDOVE], CHEREN: [Species.LILLIPUP, Species.MINCCINO, Species.PIDOVE, Species.BOUFFALANT],
LENORA: [Species.PATRAT, Species.DEERLING, Species.AUDINO], LENORA: [Species.PATRAT, Species.DEERLING, Species.AUDINO, Species.BRAVIARY],
ROXIE: [Species.VENIPEDE, Species.TRUBBISH, Species.SKORUPI], ROXIE: [Species.VENIPEDE, Species.KOFFING, Species.TRUBBISH, Species.TOXEL],
BURGH: [Species.SEWADDLE, Species.SHELMET, Species.KARRABLAST], BURGH: [Species.SEWADDLE, Species.DWEBBLE, [Species.KARRABLAST, Species.SHELMET], Species.DURANT],
ELESA: [Species.EMOLGA, Species.BLITZLE, Species.JOLTIK], ELESA: [Species.BLITZLE, Species.EMOLGA, Species.JOLTIK, Species.TYNAMO],
CLAY: [Species.DRILBUR, Species.SANDILE, Species.GOLETT], CLAY: [Species.DRILBUR, Species.SANDILE, Species.TYMPOLE, Species.GOLETT],
SKYLA: [Species.DUCKLETT, Species.WOOBAT, Species.RUFFLET], SKYLA: [Species.DUCKLETT, Species.WOOBAT, [Species.RUFFLET, Species.VULLABY], Species.ARCHEN],
BRYCEN: [Species.CRYOGONAL, Species.VANILLITE, Species.CUBCHOO], BRYCEN: [Species.CRYOGONAL, Species.VANILLITE, Species.CUBCHOO, Species.GALAR_DARUMAKA],
DRAYDEN: [Species.DRUDDIGON, Species.AXEW, Species.DEINO], DRAYDEN: [Species.AXEW, Species.DRUDDIGON, Species.TRAPINCH, Species.DEINO],
MARLON: [Species.WAILMER, Species.FRILLISH, Species.TIRTOUGA], MARLON: [Species.FRILLISH, Species.TIRTOUGA, Species.WAILMER, Species.MANTYKE],
// Gym Leaders- Kalos // Gym Leaders- Kalos
VIOLA: [Species.SURSKIT, Species.SCATTERBUG], VIOLA: [Species.SCATTERBUG, Species.SURSKIT, Species.CUTIEFLY, Species.BLIPBUG],
GRANT: [Species.AMAURA, Species.TYRUNT], GRANT: [Species.TYRUNT, Species.AMAURA, Species.BINACLE, Species.DWEBBLE],
KORRINA: [Species.HAWLUCHA, Species.LUCARIO, Species.MIENFOO], KORRINA: [Species.RIOLU, Species.MIENFOO, Species.HAWLUCHA, Species.PANCHAM],
RAMOS: [Species.SKIDDO, Species.HOPPIP, Species.BELLSPROUT], RAMOS: [Species.SKIDDO, Species.HOPPIP, Species.BELLSPROUT, [Species.PHANTUMP, Species.PUMPKABOO]],
CLEMONT: [Species.HELIOPTILE, Species.MAGNEMITE, Species.EMOLGA], CLEMONT: [Species.HELIOPTILE, Species.MAGNEMITE, Species.DEDENNE, Species.ROTOM],
VALERIE: [Species.SYLVEON, Species.MAWILE, Species.MR_MIME], VALERIE: [Species.SYLVEON, Species.MAWILE, Species.MR_MIME, [Species.SPRITZEE, Species.SWIRLIX]],
OLYMPIA: [Species.ESPURR, Species.SIGILYPH, Species.SLOWKING], OLYMPIA: [Species.ESPURR, Species.SIGILYPH, Species.INKAY, Species.SLOWKING],
WULFRIC: [Species.BERGMITE, Species.SNOVER, Species.CRYOGONAL], WULFRIC: [Species.BERGMITE, Species.SNOVER, Species.CRYOGONAL, Species.SWINUB],
// Gym Leaders- Galar // Gym Leaders- Galar
MILO: [Species.GOSSIFLEUR, Species.APPLIN, Species.BOUNSWEET], MILO: [Species.GOSSIFLEUR, Species.SEEDOT, Species.APPLIN, Species.LOTAD],
NESSA: [Species.CHEWTLE, Species.ARROKUDA, Species.WIMPOD], NESSA: [Species.CHEWTLE, Species.WIMPOD, Species.ARROKUDA, Species.MAREANIE],
KABU: [Species.SIZZLIPEDE, Species.VULPIX, Species.TORKOAL], KABU: [Species.SIZZLIPEDE, Species.VULPIX, Species.GROWLITHE, Species.TORKOAL],
BEA: [Species.GALAR_FARFETCHD, Species.MACHOP, Species.CLOBBOPUS], BEA: [Species.MACHOP, Species.GALAR_FARFETCHD, Species.CLOBBOPUS, Species.FALINKS],
ALLISTER: [Species.GALAR_YAMASK, Species.GALAR_CORSOLA, Species.GASTLY], ALLISTER: [Species.GASTLY, Species.GALAR_YAMASK, Species.GALAR_CORSOLA, Species.SINISTEA],
OPAL: [Species.MILCERY, Species.TOGETIC, Species.GALAR_WEEZING], OPAL: [Species.MILCERY, Species.GALAR_WEEZING, Species.TOGEPI, Species.MAWILE],
BEDE: [Species.HATENNA, Species.GALAR_PONYTA, Species.GARDEVOIR], BEDE: [Species.HATENNA, Species.GALAR_PONYTA, Species.GARDEVOIR, Species.SYLVEON],
GORDIE: [Species.ROLYCOLY, Species.STONJOURNER, Species.BINACLE], GORDIE: [Species.ROLYCOLY, [Species.SHUCKLE, Species.BINACLE], Species.STONJOURNER, Species.LARVITAR],
MELONY: [Species.SNOM, Species.GALAR_DARUMAKA, Species.GALAR_MR_MIME], MELONY: [Species.LAPRAS, Species.SNOM, Species.EISCUE, [Species.GALAR_MR_MIME, Species.GALAR_DARUMAKA]],
PIERS: [Species.GALAR_ZIGZAGOON, Species.SCRAGGY, Species.INKAY], PIERS: [Species.GALAR_ZIGZAGOON, Species.SCRAGGY, Species.TOXEL, Species.INKAY], // Tera Dark Toxel
MARNIE: [Species.IMPIDIMP, Species.PURRLOIN, Species.MORPEKO], MARNIE: [Species.IMPIDIMP, Species.MORPEKO, Species.PURRLOIN, Species.CROAGUNK], // Tera Dark Croagunk
RAIHAN: [Species.DURALUDON, Species.TURTONATOR, Species.GOOMY], RAIHAN: [Species.DURALUDON, Species.TRAPINCH, Species.GOOMY, Species.TURTONATOR],
// Gym Leaders- Paldea; First slot is Tera // Gym Leaders- Paldea; First slot is Tera
KATY: [Species.TEDDIURSA, Species.NYMBLE, Species.TAROUNTULA], // Tera Bug Teddiursa KATY: [Species.TEDDIURSA, Species.NYMBLE, Species.TAROUNTULA, Species.RELLOR], // Tera Bug Teddiursa
BRASSIUS: [Species.SUDOWOODO, Species.BRAMBLIN, Species.SMOLIV], // Tera Grass Sudowoodo BRASSIUS: [Species.BONSLY, Species.SMOLIV, Species.BRAMBLIN, Species.SUNKERN], // Tera Grass Bonsly
IONO: [Species.MISDREAVUS, Species.TADBULB, Species.WATTREL], // Tera Ghost Misdreavus IONO: [Species.MISDREAVUS, Species.TADBULB, Species.WATTREL, Species.MAGNEMITE], // Tera Ghost Misdreavus
KOFU: [Species.CRABRAWLER, Species.VELUZA, Species.WIGLETT, Species.WINGULL], // Tera Water Crabrawler KOFU: [Species.CRABRAWLER, Species.VELUZA, Species.WIGLETT, Species.WINGULL], // Tera Water Crabrawler
LARRY: [Species.STARLY, Species.DUNSPARCE, Species.LECHONK, Species.KOMALA], // Tera Normal Starly LARRY: [Species.STARLY, Species.DUNSPARCE, Species.LECHONK, Species.KOMALA], // Tera Normal Starly
RYME: [Species.TOXEL, Species.GREAVARD, Species.SHUPPET, Species.MIMIKYU], // Tera Ghost Toxel RYME: [Species.TOXEL, Species.GREAVARD, Species.SHUPPET, Species.MIMIKYU], // Tera Ghost Toxel
TULIP: [Species.FLABEBE, Species.FLITTLE, Species.RALTS, Species.GIRAFARIG], // Tera Psychic Flabebe TULIP: [Species.FLABEBE, Species.FLITTLE, Species.RALTS, Species.GIRAFARIG], // Tera Psychic Flabebe
GRUSHA: [Species.SWABLU, Species.CETODDLE, Species.CUBCHOO, Species.ALOLA_VULPIX], // Tera Ice Swablu GRUSHA: [Species.SWABLU, Species.CETODDLE, Species.SNOM, Species.CUBCHOO], // Tera Ice Swablu
}, {
// Elite Four- Kanto get(target, prop: string) {
LORELEI: [ return target[prop as keyof SignatureSpecies] ?? [];
Species.JYNX, }
[Species.SLOWBRO, Species.GALAR_SLOWBRO], });
Species.LAPRAS,
[Species.CLOYSTER, Species.ALOLA_SANDSLASH],
],
BRUNO: [Species.MACHAMP, Species.HITMONCHAN, Species.HITMONLEE, [Species.GOLEM, Species.ALOLA_GOLEM]],
AGATHA: [Species.GENGAR, [Species.ARBOK, Species.WEEZING], Species.CROBAT, Species.ALOLA_MAROWAK],
LANCE: [Species.DRAGONITE, Species.GYARADOS, Species.AERODACTYL, Species.ALOLA_EXEGGUTOR],
// Elite Four- Johto (Bruno included)
WILL: [Species.XATU, Species.JYNX, [Species.SLOWBRO, Species.SLOWKING], Species.EXEGGUTOR],
KOGA: [[Species.MUK, Species.WEEZING], [Species.VENOMOTH, Species.ARIADOS], Species.CROBAT, Species.TENTACRUEL],
KAREN: [Species.UMBREON, Species.HONCHKROW, Species.HOUNDOOM, Species.WEAVILE],
// Elite Four- Hoenn
SIDNEY: [
[Species.SHIFTRY, Species.CACTURNE],
[Species.SHARPEDO, Species.CRAWDAUNT],
Species.ABSOL,
Species.MIGHTYENA,
],
PHOEBE: [Species.SABLEYE, Species.DUSKNOIR, Species.BANETTE, [Species.DRIFBLIM, Species.MISMAGIUS]],
GLACIA: [Species.GLALIE, Species.WALREIN, Species.FROSLASS, Species.ABOMASNOW],
DRAKE: [Species.ALTARIA, Species.SALAMENCE, Species.FLYGON, Species.KINGDRA],
// Elite Four- Sinnoh
AARON: [[Species.SCIZOR, Species.KLEAVOR], Species.HERACROSS, [Species.VESPIQUEN, Species.YANMEGA], Species.DRAPION],
BERTHA: [Species.WHISCASH, Species.HIPPOWDON, Species.GLISCOR, Species.RHYPERIOR],
FLINT: [
[Species.RAPIDASH, Species.FLAREON],
Species.MAGMORTAR,
[Species.STEELIX, Species.LOPUNNY],
Species.INFERNAPE,
], // Tera Fire Steelix or Lopunny
LUCIAN: [Species.MR_MIME, Species.GALLADE, Species.BRONZONG, [Species.ALAKAZAM, Species.ESPEON]],
// Elite Four- Unova
SHAUNTAL: [Species.COFAGRIGUS, Species.CHANDELURE, Species.GOLURK, Species.JELLICENT],
MARSHAL: [Species.CONKELDURR, Species.MIENSHAO, Species.THROH, Species.SAWK],
GRIMSLEY: [Species.LIEPARD, Species.KINGAMBIT, Species.SCRAFTY, Species.KROOKODILE],
CAITLIN: [Species.MUSHARNA, Species.GOTHITELLE, Species.SIGILYPH, Species.REUNICLUS],
// Elite Four- Kalos
MALVA: [Species.PYROAR, Species.TORKOAL, Species.CHANDELURE, Species.TALONFLAME],
SIEBOLD: [Species.CLAWITZER, Species.GYARADOS, Species.BARBARACLE, Species.STARMIE],
WIKSTROM: [Species.KLEFKI, Species.PROBOPASS, Species.SCIZOR, Species.AEGISLASH],
DRASNA: [Species.DRAGALGE, Species.DRUDDIGON, Species.ALTARIA, Species.NOIVERN],
// Elite Four- Alola
HALA: [Species.HARIYAMA, Species.BEWEAR, Species.CRABOMINABLE, [Species.POLIWRATH, Species.ANNIHILAPE]],
MOLAYNE: [Species.KLEFKI, Species.MAGNEZONE, Species.METAGROSS, Species.ALOLA_DUGTRIO],
OLIVIA: [Species.RELICANTH, Species.CARBINK, Species.ALOLA_GOLEM, Species.LYCANROC],
ACEROLA: [[Species.BANETTE, Species.DRIFBLIM], Species.MIMIKYU, Species.DHELMISE, Species.PALOSSAND],
KAHILI: [[Species.BRAVIARY, Species.MANDIBUZZ], Species.HAWLUCHA, Species.ORICORIO, Species.TOUCANNON],
// Elite Four- Galar
MARNIE_ELITE: [Species.MORPEKO, Species.LIEPARD, [Species.TOXICROAK, Species.SCRAFTY], Species.GRIMMSNARL],
NESSA_ELITE: [Species.GOLISOPOD, [Species.QUAGSIRE, Species.PELIPPER], Species.TOXAPEX, Species.DREDNAW],
BEA_ELITE: [Species.HAWLUCHA, [Species.GRAPPLOCT, Species.SIRFETCHD], Species.FALINKS, Species.MACHAMP],
ALLISTER_ELITE: [Species.DUSKNOIR, [Species.POLTEAGEIST, Species.RUNERIGUS], Species.CURSOLA, Species.GENGAR],
RAIHAN_ELITE: [Species.GOODRA, [Species.TORKOAL, Species.TURTONATOR], Species.FLYGON, Species.ARCHALUDON],
// Elite Four- Paldea
RIKA: [Species.CLODSIRE, [Species.DUGTRIO, Species.DONPHAN], Species.CAMERUPT, Species.WHISCASH], // Tera Ground Clodsire
POPPY: [Species.TINKATON, Species.BRONZONG, Species.CORVIKNIGHT, Species.COPPERAJAH], // Tera Steel Tinkaton
LARRY_ELITE: [Species.FLAMIGO, Species.STARAPTOR, [Species.ALTARIA, Species.TROPIUS], Species.ORICORIO], // Tera Flying Flamigo; random Oricorio
HASSEL: [Species.BAXCALIBUR, [Species.FLAPPLE, Species.APPLETUN], Species.DRAGALGE, Species.NOIVERN], // Tera Dragon Baxcalibur
// Elite Four- BBL
CRISPIN: [Species.BLAZIKEN, Species.MAGMORTAR, [Species.CAMERUPT, Species.TALONFLAME], Species.ROTOM], // Tera Fire Blaziken; Heat Rotom
AMARYS: [Species.METAGROSS, Species.SCIZOR, Species.EMPOLEON, Species.SKARMORY], // Tera Steel Metagross
LACEY: [Species.EXCADRILL, Species.PRIMARINA, [Species.WHIMSICOTT, Species.ALCREMIE], Species.GRANBULL], // Tera Fairy Excadrill
DRAYTON: [Species.ARCHALUDON, Species.DRAGONITE, Species.HAXORUS, Species.SCEPTILE], // Tera Dragon Archaludon
};

View File

@ -5724,7 +5724,6 @@ export const tmSpecies: TmSpecies = {
Species.SCOLIPEDE, Species.SCOLIPEDE,
Species.WHIMSICOTT, Species.WHIMSICOTT,
Species.LILLIGANT, Species.LILLIGANT,
Species.BASCULIN,
Species.KROOKODILE, Species.KROOKODILE,
Species.DARMANITAN, Species.DARMANITAN,
Species.CRUSTLE, Species.CRUSTLE,
@ -6023,6 +6022,11 @@ export const tmSpecies: TmSpecies = {
Species.HISUI_DECIDUEYE, Species.HISUI_DECIDUEYE,
Species.PALDEA_TAUROS, Species.PALDEA_TAUROS,
Species.BLOODMOON_URSALUNA, Species.BLOODMOON_URSALUNA,
[
Species.BASCULIN,
"blue-striped",
"red-striped",
]
], ],
[Moves.LOW_KICK]: [ [Moves.LOW_KICK]: [
Species.SANDSHREW, Species.SANDSHREW,
@ -19335,7 +19339,6 @@ export const tmSpecies: TmSpecies = {
Species.CONKELDURR, Species.CONKELDURR,
Species.THROH, Species.THROH,
Species.SAWK, Species.SAWK,
Species.BASCULIN,
Species.DARMANITAN, Species.DARMANITAN,
Species.SCRAFTY, Species.SCRAFTY,
Species.ESCAVALIER, Species.ESCAVALIER,
@ -19449,6 +19452,11 @@ export const tmSpecies: TmSpecies = {
Species.HISUI_BRAVIARY, Species.HISUI_BRAVIARY,
Species.HISUI_DECIDUEYE, Species.HISUI_DECIDUEYE,
Species.PALDEA_TAUROS, Species.PALDEA_TAUROS,
[
Species.BASCULIN,
"blue-striped",
"red-striped",
],
], ],
[Moves.SPITE]: [ [Moves.SPITE]: [
Species.EKANS, Species.EKANS,
@ -51341,7 +51349,6 @@ export const tmSpecies: TmSpecies = {
Species.SCOLIPEDE, Species.SCOLIPEDE,
Species.WHIMSICOTT, Species.WHIMSICOTT,
Species.LILLIGANT, Species.LILLIGANT,
Species.BASCULIN,
Species.KROOKODILE, Species.KROOKODILE,
Species.DARMANITAN, Species.DARMANITAN,
Species.CRUSTLE, Species.CRUSTLE,
@ -51655,6 +51662,11 @@ export const tmSpecies: TmSpecies = {
Species.HISUI_DECIDUEYE, Species.HISUI_DECIDUEYE,
Species.PALDEA_TAUROS, Species.PALDEA_TAUROS,
Species.BLOODMOON_URSALUNA, Species.BLOODMOON_URSALUNA,
[
Species.BASCULIN,
"blue-striped",
"red-striped",
],
], ],
[Moves.NASTY_PLOT]: [ [Moves.NASTY_PLOT]: [
Species.PIKACHU, Species.PIKACHU,

View File

@ -2,11 +2,11 @@ import { globalScene } from "#app/global-scene";
import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove, allMoves } from "./moves/move"; import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove, allMoves } from "./moves/move";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils"; import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common";
import type { BattlerIndex } from "../battle"; import type { BattlerIndex } from "../battle";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { SubstituteTag } from "./battler-tags"; import { SubstituteTag } from "./battler-tags";
import { isNullOrUndefined } from "../utils"; import { isNullOrUndefined } from "../utils/common";
import Phaser from "phaser"; import Phaser from "phaser";
import { EncounterAnim } from "#enums/encounter-anims"; import { EncounterAnim } from "#enums/encounter-anims";

View File

@ -33,7 +33,7 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils"; import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils/common";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
@ -42,7 +42,7 @@ import { Species } from "#enums/species";
import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat } from "#enums/stat"; import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { isNullOrUndefined } from "#app/utils"; import { isNullOrUndefined } from "#app/utils/common";
export enum BattlerTagLapseType { export enum BattlerTagLapseType {
FAINT, FAINT,
@ -2637,7 +2637,7 @@ export class GulpMissileTag extends BattlerTag {
return false; return false;
} }
if (moveEffectPhase.move.getMove().hitsSubstitute(attacker, pokemon)) { if (moveEffectPhase.move.hitsSubstitute(attacker, pokemon)) {
return true; return true;
} }
@ -2993,7 +2993,7 @@ export class SubstituteTag extends BattlerTag {
if (!attacker) { if (!attacker) {
return; return;
} }
const move = moveEffectPhase.move.getMove(); const move = moveEffectPhase.move;
const firstHit = attacker.turnData.hitCount === attacker.turnData.hitsLeft; const firstHit = attacker.turnData.hitCount === attacker.turnData.hitsLeft;
if (firstHit && move.hitsSubstitute(attacker, pokemon)) { if (firstHit && move.hitsSubstitute(attacker, pokemon)) {
@ -3681,7 +3681,7 @@ function getMoveEffectPhaseData(_pokemon: Pokemon): { phase: MoveEffectPhase; at
return { return {
phase: phase, phase: phase,
attacker: phase.getPokemon(), attacker: phase.getPokemon(),
move: phase.move.getMove(), move: phase.move,
}; };
} }
return null; return null;

View File

@ -2,7 +2,7 @@ import { getPokemonNameWithAffix } from "../messages";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { HitResult } from "../field/pokemon"; import { HitResult } from "../field/pokemon";
import { getStatusEffectHealText } from "./status-effect"; import { getStatusEffectHealText } from "./status-effect";
import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils"; import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common";
import { import {
DoubleBerryEffectAbAttr, DoubleBerryEffectAbAttr,
PostItemLostAbAttr, PostItemLostAbAttr,

View File

@ -1,4 +1,4 @@
import { BooleanHolder, type NumberHolder, randSeedItem, deepCopy } from "#app/utils"; import { BooleanHolder, type NumberHolder, randSeedItem, deepCopy } from "#app/utils/common";
import i18next from "i18next"; import i18next from "i18next";
import type { DexAttrProps, GameData } from "#app/system/game-data"; import type { DexAttrProps, GameData } from "#app/system/game-data";
import { defaultStarterSpecies } from "#app/system/game-data"; import { defaultStarterSpecies } from "#app/system/game-data";
@ -8,7 +8,9 @@ import { speciesStarterCosts } from "#app/data/balance/starters";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon";
import type { FixedBattleConfig } from "#app/battle"; import type { FixedBattleConfig } from "#app/battle";
import { ClassicFixedBossWaves, BattleType, getRandomTrainerFunc } from "#app/battle"; import { getRandomTrainerFunc } from "#app/battle";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
import { BattleType } from "#enums/battle-type";
import Trainer, { TrainerVariant } from "#app/field/trainer"; import Trainer, { TrainerVariant } from "#app/field/trainer";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";

View File

@ -1,6 +1,6 @@
import type { Abilities } from "#enums/abilities"; import type { Abilities } from "#enums/abilities";
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
import { isNullOrUndefined } from "#app/utils"; import { isNullOrUndefined } from "#app/utils/common";
import type { Nature } from "#enums/nature"; import type { Nature } from "#enums/nature";
/** /**

View File

@ -3,7 +3,7 @@ import type { Species } from "#enums/species";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { PlayerPokemon } from "#app/field/pokemon"; import { PlayerPokemon } from "#app/field/pokemon";
import type { Starter } from "#app/ui/starter-select-ui-handler"; import type { Starter } from "#app/ui/starter-select-ui-handler";
import { randSeedGauss, randSeedInt, randSeedItem, getEnumValues } from "#app/utils"; import { randSeedGauss, randSeedInt, randSeedItem, getEnumValues } from "#app/utils/common";
import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; import type { PokemonSpeciesForm } from "#app/data/pokemon-species";
import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";

View File

@ -4,7 +4,7 @@ import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { VariantTier } from "#enums/variant-tier"; import { VariantTier } from "#enums/variant-tier";
import { randInt, randomString, randSeedInt, getIvsFromId } from "#app/utils"; import { randInt, randomString, randSeedInt, getIvsFromId } from "#app/utils/common";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";

View File

@ -0,0 +1,20 @@
import { MoveTarget } from "#enums/MoveTarget";
import type Move from "./move";
/**
* Return whether the move targets the field
*
* Examples include
* - Hazard moves like spikes
* - Weather moves like rain dance
* - User side moves like reflect and safeguard
*/
export function isFieldTargeted(move: Move): boolean {
switch (move.moveTarget) {
case MoveTarget.BOTH_SIDES:
case MoveTarget.USER_SIDE:
case MoveTarget.ENEMY_SIDE:
return true;
}
return false;
}

View File

@ -29,7 +29,7 @@ import {
} from "../status-effect"; } from "../status-effect";
import { getTypeDamageMultiplier } from "../type"; import { getTypeDamageMultiplier } from "../type";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { BooleanHolder, NumberHolder, isNullOrUndefined, toDmgValue, randSeedItem, randSeedInt, getEnumValues, toReadableString, type Constructor } from "#app/utils"; import { BooleanHolder, NumberHolder, isNullOrUndefined, toDmgValue, randSeedItem, randSeedInt, getEnumValues, toReadableString, type Constructor } from "#app/utils/common";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import type { ArenaTrapTag } from "../arena-tag"; import type { ArenaTrapTag } from "../arena-tag";
import { ArenaTagSide, WeakenMoveTypeTag } from "../arena-tag"; import { ArenaTagSide, WeakenMoveTypeTag } from "../arena-tag";
@ -60,6 +60,7 @@ import {
MoveTypeChangeAbAttr, MoveTypeChangeAbAttr,
PostDamageForceSwitchAbAttr, PostDamageForceSwitchAbAttr,
PostItemLostAbAttr, PostItemLostAbAttr,
ReflectStatusMoveAbAttr,
ReverseDrainAbAttr, ReverseDrainAbAttr,
UserFieldMoveTypePowerBoostAbAttr, UserFieldMoveTypePowerBoostAbAttr,
VariableMovePowerAbAttr, VariableMovePowerAbAttr,
@ -75,7 +76,7 @@ import {
PreserveBerryModifier, PreserveBerryModifier,
} from "../../modifier/modifier"; } from "../../modifier/modifier";
import type { BattlerIndex } from "../../battle"; import type { BattlerIndex } from "../../battle";
import { BattleType } from "../../battle"; import { BattleType } from "#enums/battle-type";
import { TerrainType } from "../terrain"; import { TerrainType } from "../terrain";
import { ModifierPoolType } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#app/modifier/modifier-type";
import { Command } from "../../ui/command-ui-handler"; import { Command } from "../../ui/command-ui-handler";
@ -122,6 +123,8 @@ import { MoveEffectTrigger } from "#enums/MoveEffectTrigger";
import { MultiHitType } from "#enums/MultiHitType"; import { MultiHitType } from "#enums/MultiHitType";
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves";
import { applyAttackTypeBoosterHeldItem } from "#app/modifier/held-items"; import { applyAttackTypeBoosterHeldItem } from "#app/modifier/held-items";
import { TrainerVariant } from "#app/field/trainer";
import { SelectBiomePhase } from "#app/phases/select-biome-phase";
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
@ -650,7 +653,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;
@ -665,6 +668,17 @@ export default class Move implements Localizable {
return true; return true;
} }
break; break;
case MoveFlags.REFLECTABLE:
// If the target is not semi-invulnerable and either has magic coat active or an unignored magic bounce ability
if (
target?.getTag(SemiInvulnerableTag) ||
!(target?.getTag(BattlerTagType.MAGIC_COAT) ||
(!this.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) &&
target?.hasAbilityWithAttr(ReflectStatusMoveAbAttr)))
) {
return false;
}
break;
} }
return !!(this.flags & flag); return !!(this.flags & flag);
@ -1716,7 +1730,7 @@ export class SacrificialAttr extends MoveEffectAttr {
**/ **/
export class SacrificialAttrOnHit extends MoveEffectAttr { export class SacrificialAttrOnHit extends MoveEffectAttr {
constructor() { constructor() {
super(true, { trigger: MoveEffectTrigger.HIT }); super(true);
} }
/** /**
@ -1955,6 +1969,14 @@ export class PartyStatusCureAttr extends MoveEffectAttr {
* @extends MoveEffectAttr * @extends MoveEffectAttr
*/ */
export class FlameBurstAttr extends MoveEffectAttr { export class FlameBurstAttr extends MoveEffectAttr {
constructor() {
/**
* This is self-targeted to bypass immunity to target-facing secondary
* effects when the target has an active Substitute doll.
* TODO: Find a more intuitive way to implement Substitute bypassing.
*/
super(true);
}
/** /**
* @param user - n/a * @param user - n/a
* @param target - The target Pokémon. * @param target - The target Pokémon.
@ -2177,7 +2199,7 @@ export class HitHealAttr extends MoveEffectAttr {
private healStat: EffectiveStat | null; private healStat: EffectiveStat | null;
constructor(healRatio?: number | null, healStat?: EffectiveStat) { constructor(healRatio?: number | null, healStat?: EffectiveStat) {
super(true, { trigger: MoveEffectTrigger.HIT }); super(true);
this.healRatio = healRatio ?? 0.5; this.healRatio = healRatio ?? 0.5;
this.healStat = healStat ?? null; this.healStat = healStat ?? null;
@ -2426,7 +2448,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
public overrideStatus: boolean = false; public overrideStatus: boolean = false;
constructor(effect: StatusEffect, selfTarget?: boolean, turnsRemaining?: number, overrideStatus: boolean = false) { constructor(effect: StatusEffect, selfTarget?: boolean, turnsRemaining?: number, overrideStatus: boolean = false) {
super(selfTarget, { trigger: MoveEffectTrigger.HIT }); super(selfTarget);
this.effect = effect; this.effect = effect;
this.turnsRemaining = turnsRemaining; this.turnsRemaining = turnsRemaining;
@ -2434,26 +2456,15 @@ export class StatusEffectAttr extends MoveEffectAttr {
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!this.selfTarget && move.hitsSubstitute(user, target)) {
return false;
}
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance; const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance;
if (statusCheck) { if (statusCheck) {
const pokemon = this.selfTarget ? user : target; const pokemon = this.selfTarget ? user : target;
if (pokemon.status && !this.overrideStatus) { if (user !== target && move.category === MoveCategory.STATUS && !target.canSetStatus(this.effect, false, false, user, true)) {
return false;
}
if (user !== target && target.isSafeguarded(user)) {
if (move.category === MoveCategory.STATUS) {
globalScene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) }));
}
return false; return false;
} }
if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0)) if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0))
&& pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus)) { && pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus, false)) {
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect); applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
return true; return true;
} }
@ -2495,7 +2506,7 @@ export class MultiStatusEffectAttr extends StatusEffectAttr {
export class PsychoShiftEffectAttr extends MoveEffectAttr { export class PsychoShiftEffectAttr extends MoveEffectAttr {
constructor() { constructor() {
super(false, { trigger: MoveEffectTrigger.HIT }); super(false);
} }
/** /**
@ -2534,15 +2545,11 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
private chance: number; private chance: number;
constructor(chance: number) { constructor(chance: number) {
super(false, { trigger: MoveEffectTrigger.HIT }); super(false);
this.chance = chance; this.chance = chance;
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (move.hitsSubstitute(user, target)) {
return false;
}
const rand = Phaser.Math.RND.realInRange(0, 1); const rand = Phaser.Math.RND.realInRange(0, 1);
if (rand >= this.chance) { if (rand >= this.chance) {
return false; return false;
@ -2590,7 +2597,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
private berriesOnly: boolean; private berriesOnly: boolean;
constructor(berriesOnly: boolean) { constructor(berriesOnly: boolean) {
super(false, { trigger: MoveEffectTrigger.HIT }); super(false);
this.berriesOnly = berriesOnly; this.berriesOnly = berriesOnly;
} }
@ -2600,17 +2607,13 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
* @param target Target {@linkcode Pokemon} that the moves applies to * @param target Target {@linkcode Pokemon} that the moves applies to
* @param move {@linkcode Move} that is used * @param move {@linkcode Move} that is used
* @param args N/A * @param args N/A
* @returns {boolean} True if an item was removed * @returns True if an item was removed
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia) if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia)
return false; return false;
} }
if (move.hitsSubstitute(user, target)) {
return false;
}
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // Check for abilities that block item theft applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // Check for abilities that block item theft
@ -2664,8 +2667,8 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
*/ */
export class EatBerryAttr extends MoveEffectAttr { export class EatBerryAttr extends MoveEffectAttr {
protected chosenBerry: BerryModifier | undefined; protected chosenBerry: BerryModifier | undefined;
constructor() { constructor(selfTarget: boolean) {
super(true, { trigger: MoveEffectTrigger.HIT }); super(selfTarget);
} }
/** /**
* Causes the target to eat a berry. * Causes the target to eat a berry.
@ -2680,17 +2683,20 @@ export class EatBerryAttr extends MoveEffectAttr {
return false; return false;
} }
const heldBerries = this.getTargetHeldBerries(target); const pokemon = this.selfTarget ? user : target;
const heldBerries = this.getTargetHeldBerries(pokemon);
if (heldBerries.length <= 0) { if (heldBerries.length <= 0) {
return false; return false;
} }
this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)]; this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)];
const preserve = new BooleanHolder(false); const preserve = new BooleanHolder(false);
globalScene.applyModifiers(PreserveBerryModifier, target.isPlayer(), target, preserve); // check for berry pouch preservation // check for berry pouch preservation
globalScene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve);
if (!preserve.value) { if (!preserve.value) {
this.reduceBerryModifier(target); this.reduceBerryModifier(pokemon);
} }
this.eatBerry(target); this.eatBerry(pokemon);
return true; return true;
} }
@ -2718,20 +2724,17 @@ export class EatBerryAttr extends MoveEffectAttr {
*/ */
export class StealEatBerryAttr extends EatBerryAttr { export class StealEatBerryAttr extends EatBerryAttr {
constructor() { constructor() {
super(); super(false);
} }
/** /**
* User steals a random berry from the target and then eats it. * User steals a random berry from the target and then eats it.
* @param {Pokemon} user Pokemon that used the move and will eat the stolen berry * @param user - Pokemon that used the move and will eat the stolen berry
* @param {Pokemon} target Pokemon that will have its berry stolen * @param target - Pokemon that will have its berry stolen
* @param {Move} move Move being used * @param move - Move being used
* @param {any[]} args Unused * @param args Unused
* @returns {boolean} true if the function succeeds * @returns true if the function succeeds
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (move.hitsSubstitute(user, target)) {
return false;
}
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // check for abilities that block item theft applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // check for abilities that block item theft
if (cancelled.value === true) { if (cancelled.value === true) {
@ -2782,10 +2785,6 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
return false; return false;
} }
if (!this.selfTarget && move.hitsSubstitute(user, target)) {
return false;
}
// Special edge case for shield dust blocking Sparkling Aria curing burn // Special edge case for shield dust blocking Sparkling Aria curing burn
const moveTargets = getMoveTargets(user, move.id); const moveTargets = getMoveTargets(user, move.id);
if (target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && move.id === Moves.SPARKLING_ARIA && moveTargets.targets.length === 1) { if (target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && move.id === Moves.SPARKLING_ARIA && moveTargets.targets.length === 1) {
@ -3163,14 +3162,6 @@ export class StatStageChangeAttr extends MoveEffectAttr {
return this.options?.showMessage ?? true; return this.options?.showMessage ?? true;
} }
/**
* Indicates when the stat change should trigger
* @default MoveEffectTrigger.HIT
*/
public override get trigger () {
return this.options?.trigger ?? MoveEffectTrigger.HIT;
}
/** /**
* 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
@ -3184,10 +3175,6 @@ export class StatStageChangeAttr extends MoveEffectAttr {
return false; return false;
} }
if (!this.selfTarget && move.hitsSubstitute(user, target)) {
return false;
}
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) { if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
const stages = this.getLevels(user); const stages = this.getLevels(user);
@ -3471,7 +3458,7 @@ export class CutHpStatStageBoostAttr extends StatStageChangeAttr {
*/ */
export class OrderUpStatBoostAttr extends MoveEffectAttr { export class OrderUpStatBoostAttr extends MoveEffectAttr {
constructor() { constructor() {
super(true, { trigger: MoveEffectTrigger.HIT }); super(true);
} }
override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
@ -3548,17 +3535,15 @@ export class ResetStatsAttr extends MoveEffectAttr {
this.targetAllPokemon = targetAllPokemon; this.targetAllPokemon = targetAllPokemon;
} }
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { override apply(_user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
if (this.targetAllPokemon) { if (this.targetAllPokemon) {
// Target all pokemon on the field when Freezy Frost or Haze are used // Target all pokemon on the field when Freezy Frost or Haze are used
const activePokemon = globalScene.getField(true); const activePokemon = globalScene.getField(true);
activePokemon.forEach((p) => this.resetStats(p)); activePokemon.forEach((p) => this.resetStats(p));
globalScene.queueMessage(i18next.t("moveTriggers:statEliminated")); globalScene.queueMessage(i18next.t("moveTriggers:statEliminated"));
} else { // Affects only the single target when Clear Smog is used } else { // Affects only the single target when Clear Smog is used
if (!move.hitsSubstitute(user, target)) { this.resetStats(target);
this.resetStats(target); globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) }));
globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) }));
}
} }
return true; return true;
} }
@ -4217,7 +4202,8 @@ export class PresentPowerAttr extends VariablePowerAttr {
(args[0] as NumberHolder).value = 120; (args[0] as NumberHolder).value = 120;
} else if (80 < powerSeed && powerSeed <= 100) { } else if (80 < powerSeed && powerSeed <= 100) {
// If this move is multi-hit, disable all other hits // If this move is multi-hit, disable all other hits
user.stopMultiHit(); user.turnData.hitCount = 1;
user.turnData.hitsLeft = 1;
globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(),
toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true)); toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true));
} }
@ -4811,8 +4797,8 @@ export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const category = (args[0] as NumberHolder); const category = (args[0] as NumberHolder);
const predictedPhysDmg = target.getBaseDamage(user, move, MoveCategory.PHYSICAL, true, true, true, true); const predictedPhysDmg = target.getBaseDamage({source: user, move, moveCategory: MoveCategory.PHYSICAL, ignoreAbility: true, ignoreSourceAbility: true, ignoreAllyAbility: true, ignoreSourceAllyAbility: true, simulated: true});
const predictedSpecDmg = target.getBaseDamage(user, move, MoveCategory.SPECIAL, true, true, true, true); const predictedSpecDmg = target.getBaseDamage({source: user, move, moveCategory: MoveCategory.SPECIAL, ignoreAbility: true, ignoreSourceAbility: true, ignoreAllyAbility: true, ignoreSourceAllyAbility: true, simulated: true});
if (predictedPhysDmg > predictedSpecDmg) { if (predictedPhysDmg > predictedSpecDmg) {
category.value = MoveCategory.PHYSICAL; category.value = MoveCategory.PHYSICAL;
@ -5371,7 +5357,7 @@ export class BypassRedirectAttr extends MoveAttr {
export class FrenzyAttr extends MoveEffectAttr { export class FrenzyAttr extends MoveEffectAttr {
constructor() { constructor() {
super(true, { trigger: MoveEffectTrigger.HIT, lastHitOnly: true }); super(true, { lastHitOnly: true });
} }
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) { canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
@ -5443,22 +5429,20 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
protected cancelOnFail: boolean; protected cancelOnFail: boolean;
private failOnOverlap: boolean; private failOnOverlap: boolean;
constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: number = 0, turnCountMax?: number, lastHitOnly: boolean = false, cancelOnFail: boolean = false) { constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: number = 0, turnCountMax?: number, lastHitOnly: boolean = false) {
super(selfTarget, { lastHitOnly: lastHitOnly }); super(selfTarget, { lastHitOnly: lastHitOnly });
this.tagType = tagType; this.tagType = tagType;
this.turnCountMin = turnCountMin; this.turnCountMin = turnCountMin;
this.turnCountMax = turnCountMax !== undefined ? turnCountMax : turnCountMin; this.turnCountMax = turnCountMax !== undefined ? turnCountMax : turnCountMin;
this.failOnOverlap = !!failOnOverlap; this.failOnOverlap = !!failOnOverlap;
this.cancelOnFail = cancelOnFail;
} }
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!super.canApply(user, target, move, args) || (this.cancelOnFail === true && user.getLastXMoves(1)[0]?.result === MoveResult.FAIL)) { if (!super.canApply(user, target, move, args)) {
return false; return false;
} else {
return true;
} }
return true;
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
@ -5549,19 +5533,6 @@ export class LeechSeedAttr extends AddBattlerTagAttr {
constructor() { constructor() {
super(BattlerTagType.SEEDED); super(BattlerTagType.SEEDED);
} }
/**
* Adds a Seeding effect to the target if the target does not have an active Substitute.
* @param user the {@linkcode Pokemon} using the move
* @param target the {@linkcode Pokemon} targeted by the move
* @param move the {@linkcode Move} invoking this effect
* @param args n/a
* @returns `true` if the effect successfully applies; `false` otherwise
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
return !move.hitsSubstitute(user, target)
&& super.apply(user, target, move, args);
}
} }
/** /**
@ -5737,13 +5708,6 @@ export class FlinchAttr extends AddBattlerTagAttr {
constructor() { constructor() {
super(BattlerTagType.FLINCHED, false); super(BattlerTagType.FLINCHED, false);
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!move.hitsSubstitute(user, target)) {
return super.apply(user, target, move, args);
}
return false;
}
} }
export class ConfuseAttr extends AddBattlerTagAttr { export class ConfuseAttr extends AddBattlerTagAttr {
@ -5759,16 +5723,13 @@ export class ConfuseAttr extends AddBattlerTagAttr {
return false; return false;
} }
if (!move.hitsSubstitute(user, target)) { return super.apply(user, target, move, args);
return super.apply(user, target, move, args);
}
return false;
} }
} }
export class RechargeAttr extends AddBattlerTagAttr { export class RechargeAttr extends AddBattlerTagAttr {
constructor() { constructor() {
super(BattlerTagType.RECHARGING, true, false, 1, 1, true, true); super(BattlerTagType.RECHARGING, true, false, 1, 1, true);
} }
} }
@ -6151,7 +6112,7 @@ export class AddPledgeEffectAttr extends AddArenaTagAttr {
* @see {@linkcode apply} * @see {@linkcode apply}
*/ */
export class RevivalBlessingAttr extends MoveEffectAttr { export class RevivalBlessingAttr extends MoveEffectAttr {
constructor(user?: boolean) { constructor() {
super(true); super(true);
} }
@ -6296,9 +6257,10 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
return false; return false;
} else if (globalScene.currentBattle.battleType !== BattleType.WILD) { // Switch out logic for enemy trainers } else if (globalScene.currentBattle.battleType !== BattleType.WILD) { // Switch out logic for enemy trainers
// Find indices of off-field Pokemon that are eligible to be switched into // Find indices of off-field Pokemon that are eligible to be switched into
const isPartnerTrainer = globalScene.currentBattle.trainer?.isPartner();
const eligibleNewIndices: number[] = []; const eligibleNewIndices: number[] = [];
globalScene.getEnemyParty().forEach((pokemon, index) => { globalScene.getEnemyParty().forEach((pokemon, index) => {
if (pokemon.isAllowedInBattle() && !pokemon.isOnField()) { if (pokemon.isAllowedInBattle() && !pokemon.isOnField() && (!isPartnerTrainer || pokemon.trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)) {
eligibleNewIndices.push(index); eligibleNewIndices.push(index);
} }
}); });
@ -6348,15 +6310,6 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
} }
} }
if (globalScene.currentBattle.waveIndex % 10 === 0) {
return false;
}
// Don't allow wild mons to flee with U-turn et al.
if (this.selfSwitch && !user.isPlayer() && move.category !== MoveCategory.STATUS) {
return false;
}
const allyPokemon = switchOutTarget.getAlly(); const allyPokemon = switchOutTarget.getAlly();
if (switchOutTarget.hp > 0) { if (switchOutTarget.hp > 0) {
@ -6369,13 +6322,17 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
} }
} }
if (!allyPokemon?.isActive(true)) { // clear out enemy held item modifiers of the switch out target
globalScene.clearEnemyHeldItemModifiers(); globalScene.clearEnemyHeldItemModifiers(switchOutTarget);
if (switchOutTarget.hp) { if (!allyPokemon?.isActive(true) && switchOutTarget.hp) {
globalScene.pushPhase(new BattleEndPhase(false)); globalScene.pushPhase(new BattleEndPhase(false));
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
globalScene.pushPhase(new SelectBiomePhase());
}
globalScene.pushPhase(new NewBattlePhase()); globalScene.pushPhase(new NewBattlePhase());
}
} }
} }
@ -6394,16 +6351,13 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
} }
} }
getSwitchOutCondition(): MoveConditionFunc { getSwitchOutCondition(): MoveConditionFunc {
return (user, target, move) => { return (user, target, move) => {
const switchOutTarget = (this.selfSwitch ? user : target); const switchOutTarget = (this.selfSwitch ? user : target);
const player = switchOutTarget instanceof PlayerPokemon; const player = switchOutTarget instanceof PlayerPokemon;
if (!this.selfSwitch) { if (!this.selfSwitch) {
if (move.hitsSubstitute(user, target)) {
return false;
}
// Dondozo with an allied Tatsugiri in its mouth cannot be forced out // Dondozo with an allied Tatsugiri in its mouth cannot be forced out
const commandedTag = switchOutTarget.getTag(BattlerTagType.COMMANDED); const commandedTag = switchOutTarget.getTag(BattlerTagType.COMMANDED);
if (commandedTag?.getSourcePokemon()?.isActive(true)) { if (commandedTag?.getSourcePokemon()?.isActive(true)) {
@ -6417,23 +6371,23 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
const blockedByAbility = new BooleanHolder(false); const blockedByAbility = new BooleanHolder(false);
applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility); applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility);
return !blockedByAbility.value; if (blockedByAbility.value) {
return false;
}
} }
if (!player && globalScene.currentBattle.battleType === BattleType.WILD) { if (!player && globalScene.currentBattle.battleType === BattleType.WILD) {
if (this.isBatonPass()) { // wild pokemon cannot switch out with baton pass.
return false; return !this.isBatonPass()
} && globalScene.currentBattle.waveIndex % 10 !== 0
// Don't allow wild opponents to flee on the boss stage since it can ruin a run early on // Don't allow wild mons to flee with U-turn et al.
if (globalScene.currentBattle.waveIndex % 10 === 0) { && !(this.selfSwitch && MoveCategory.STATUS !== move.category);
return false;
}
} }
const party = player ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); const party = player ? globalScene.getPlayerParty() : globalScene.getEnemyParty();
return (!player && !globalScene.currentBattle.battleType) return party.filter(p => p.isAllowedInBattle() && !p.isOnField()
|| party.filter(p => p.isAllowedInBattle() && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > 0;
&& (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > globalScene.currentBattle.getBattlerCount();
}; };
} }
@ -6658,7 +6612,7 @@ export class ChangeTypeAttr extends MoveEffectAttr {
private type: PokemonType; private type: PokemonType;
constructor(type: PokemonType) { constructor(type: PokemonType) {
super(false, { trigger: MoveEffectTrigger.HIT }); super(false);
this.type = type; this.type = type;
} }
@ -6681,7 +6635,7 @@ export class AddTypeAttr extends MoveEffectAttr {
private type: PokemonType; private type: PokemonType;
constructor(type: PokemonType) { constructor(type: PokemonType) {
super(false, { trigger: MoveEffectTrigger.HIT }); super(false);
this.type = type; this.type = type;
} }
@ -7377,7 +7331,7 @@ export class AbilityChangeAttr extends MoveEffectAttr {
public ability: Abilities; public ability: Abilities;
constructor(ability: Abilities, selfTarget?: boolean) { constructor(ability: Abilities, selfTarget?: boolean) {
super(selfTarget, { trigger: MoveEffectTrigger.HIT }); super(selfTarget);
this.ability = ability; this.ability = ability;
} }
@ -7408,7 +7362,7 @@ export class AbilityCopyAttr extends MoveEffectAttr {
public copyToPartner: boolean; public copyToPartner: boolean;
constructor(copyToPartner: boolean = false) { constructor(copyToPartner: boolean = false) {
super(false, { trigger: MoveEffectTrigger.HIT }); super(false);
this.copyToPartner = copyToPartner; this.copyToPartner = copyToPartner;
} }
@ -7449,7 +7403,7 @@ export class AbilityGiveAttr extends MoveEffectAttr {
public copyToPartner: boolean; public copyToPartner: boolean;
constructor() { constructor() {
super(false, { trigger: MoveEffectTrigger.HIT }); super(false);
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
@ -7712,23 +7666,9 @@ export class AverageStatsAttr extends MoveEffectAttr {
} }
} }
export class DiscourageFrequentUseAttr extends MoveAttr {
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
const lastMoves = user.getLastXMoves(4);
console.log(lastMoves);
for (let m = 0; m < lastMoves.length; m++) {
if (lastMoves[m].move === move.id) {
return (4 - (m + 1)) * -10;
}
}
return 0;
}
}
export class MoneyAttr extends MoveEffectAttr { export class MoneyAttr extends MoveEffectAttr {
constructor() { constructor() {
super(true, { trigger: MoveEffectTrigger.HIT, firstHitOnly: true }); super(true, {firstHitOnly: true });
} }
apply(user: Pokemon, target: Pokemon, move: Move): boolean { apply(user: Pokemon, target: Pokemon, move: Move): boolean {
@ -7795,7 +7735,7 @@ export class StatusIfBoostedAttr extends MoveEffectAttr {
public effect: StatusEffect; public effect: StatusEffect;
constructor(effect: StatusEffect) { constructor(effect: StatusEffect) {
super(true, { trigger: MoveEffectTrigger.HIT }); super(true);
this.effect = effect; this.effect = effect;
} }
@ -8191,7 +8131,7 @@ export type MoveTargetSet = {
export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveTarget): MoveTargetSet { export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveTarget): MoveTargetSet {
const variableTarget = new NumberHolder(0); const variableTarget = new NumberHolder(0);
user.getOpponents().forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget)); user.getOpponents(false).forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget));
let moveTarget: MoveTarget | undefined; let moveTarget: MoveTarget | undefined;
if (allMoves[move].hasAttr(VariableTargetAttr)) { if (allMoves[move].hasAttr(VariableTargetAttr)) {
@ -8203,7 +8143,7 @@ export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveT
} else if (move === undefined) { } else if (move === undefined) {
moveTarget = MoveTarget.NEAR_ENEMY; moveTarget = MoveTarget.NEAR_ENEMY;
} }
const opponents = user.getOpponents(); const opponents = user.getOpponents(false);
let set: Pokemon[] = []; let set: Pokemon[] = [];
let multiple = false; let multiple = false;
@ -8679,7 +8619,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),
@ -10564,8 +10506,7 @@ export function initMoves() {
} else { } else {
return 1; return 1;
} }
}) }),
.attr(DiscourageFrequentUseAttr),
new AttackMove(Moves.SNIPE_SHOT, PokemonType.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, 0, 8) new AttackMove(Moves.SNIPE_SHOT, PokemonType.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, 0, 8)
.attr(HighCritAttr) .attr(HighCritAttr)
@ -10574,7 +10515,7 @@ export function initMoves() {
.attr(JawLockAttr) .attr(JawLockAttr)
.bitingMove(), .bitingMove(),
new SelfStatusMove(Moves.STUFF_CHEEKS, PokemonType.NORMAL, -1, 10, -1, 0, 8) new SelfStatusMove(Moves.STUFF_CHEEKS, PokemonType.NORMAL, -1, 10, -1, 0, 8)
.attr(EatBerryAttr) .attr(EatBerryAttr, true)
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true) .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true)
.condition((user) => { .condition((user) => {
const userBerries = globalScene.findModifiers(m => m instanceof BerryModifier, user.isPlayer()); const userBerries = globalScene.findModifiers(m => m instanceof BerryModifier, user.isPlayer());
@ -10598,7 +10539,7 @@ export function initMoves() {
.makesContact(false) .makesContact(false)
.partial(), // smart targetting is unimplemented .partial(), // smart targetting is unimplemented
new StatusMove(Moves.TEATIME, PokemonType.NORMAL, -1, 10, -1, 0, 8) new StatusMove(Moves.TEATIME, PokemonType.NORMAL, -1, 10, -1, 0, 8)
.attr(EatBerryAttr) .attr(EatBerryAttr, false)
.target(MoveTarget.ALL), .target(MoveTarget.ALL),
new StatusMove(Moves.OCTOLOCK, PokemonType.FIGHTING, 100, 15, -1, 0, 8) new StatusMove(Moves.OCTOLOCK, PokemonType.FIGHTING, 100, 15, -1, 0, 8)
.condition(failIfGhostTypeCondition) .condition(failIfGhostTypeCondition)
@ -11239,6 +11180,8 @@ export function initMoves() {
new AttackMove(Moves.TEMPER_FLARE, PokemonType.FIRE, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 9) new AttackMove(Moves.TEMPER_FLARE, PokemonType.FIRE, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 9)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.getLastXMoves(2)[1]?.result === MoveResult.MISS || user.getLastXMoves(2)[1]?.result === MoveResult.FAIL ? 2 : 1), .attr(MovePowerMultiplierAttr, (user, target, move) => user.getLastXMoves(2)[1]?.result === MoveResult.MISS || user.getLastXMoves(2)[1]?.result === MoveResult.FAIL ? 2 : 1),
new AttackMove(Moves.SUPERCELL_SLAM, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 100, 95, 15, -1, 0, 9) new AttackMove(Moves.SUPERCELL_SLAM, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 100, 95, 15, -1, 0, 9)
.attr(AlwaysHitMinimizeAttr)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED)
.attr(MissEffectAttr, crashDamageFunc) .attr(MissEffectAttr, crashDamageFunc)
.attr(NoEffectAttr, crashDamageFunc) .attr(NoEffectAttr, crashDamageFunc)
.recklessMove(), .recklessMove(),

View File

@ -14,7 +14,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils/common";
import i18next from "i18next"; import i18next from "i18next";
import type { IEggOptions } from "#app/data/egg"; import type { IEggOptions } from "#app/data/egg";
import { EggSourceType } from "#enums/egg-source-types"; import { EggSourceType } from "#enums/egg-source-types";
@ -22,7 +22,7 @@ import { EggTier } from "#enums/egg-type";
import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/aTrainersTest"; const namespace = "mysteryEncounters/aTrainersTest";

View File

@ -24,7 +24,7 @@ import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modif
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { randInt } from "#app/utils"; import { randInt } from "#app/utils/common";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { import {
applyModifierTypeToPlayerPokemon, applyModifierTypeToPlayerPokemon,
@ -37,7 +37,7 @@ import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
import type { BerryType } from "#enums/berry-type"; import type { BerryType } from "#enums/berry-type";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import i18next from "i18next"; import i18next from "i18next";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */

View File

@ -23,7 +23,7 @@ import { speciesStarterCosts } from "#app/data/balance/starters";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import i18next from "i18next"; import i18next from "i18next";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */

View File

@ -13,7 +13,7 @@ import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type"; import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
import { ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils/common";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -36,7 +36,7 @@ import i18next from "#app/plugins/i18n";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { PERMANENT_STATS, Stat } from "#enums/stat"; import { PERMANENT_STATS, Stat } from "#enums/stat";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/berriesAbound"; const namespace = "mysteryEncounters/berriesAbound";

View File

@ -16,7 +16,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils"; import { isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -52,7 +52,7 @@ import i18next from "i18next";
import MoveInfoOverlay from "#app/ui/move-info-overlay"; import MoveInfoOverlay from "#app/ui/move-info-overlay";
import { allMoves } from "#app/data/moves/move"; import { allMoves } from "#app/data/moves/move";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */

View File

@ -31,9 +31,9 @@ import {
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { randSeedInt, randSeedShuffle } from "#app/utils"; import { randSeedInt, randSeedShuffle } from "#app/utils/common";
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { Mode } from "#app/ui/ui"; import { UiMode } from "#enums/ui-mode";
import i18next from "i18next"; import i18next from "i18next";
import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
@ -46,7 +46,7 @@ import { Moves } from "#enums/moves";
import { EncounterBattleAnim } from "#app/data/battle-anims"; import { EncounterBattleAnim } from "#app/data/battle-anims";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { EncounterAnim } from "#enums/encounter-anims"; import { EncounterAnim } from "#enums/encounter-anims";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
@ -437,7 +437,7 @@ async function handleSwapAbility() {
await showEncounterDialogue(`${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`); await showEncounterDialogue(`${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`);
await showEncounterText(`${namespace}:option.1.apply_ability_message`); await showEncounterText(`${namespace}:option.1.apply_ability_message`);
globalScene.ui.setMode(Mode.MESSAGE).then(() => { globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
displayYesNoOptions(resolve); displayYesNoOptions(resolve);
}); });
}); });
@ -467,7 +467,7 @@ function displayYesNoOptions(resolve) {
maxOptions: 7, maxOptions: 7,
yOffset: 0, yOffset: 0,
}; };
globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true); globalScene.ui.setModeWithoutClear(UiMode.OPTION_SELECT, config, null, true);
} }
function onYesAbilitySwap(resolve) { function onYesAbilitySwap(resolve) {
@ -477,11 +477,11 @@ function onYesAbilitySwap(resolve) {
applyAbilityOverrideToPokemon(pokemon, encounter.misc.ability); applyAbilityOverrideToPokemon(pokemon, encounter.misc.ability);
encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender()); encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
globalScene.ui.setMode(Mode.MESSAGE).then(() => resolve(true)); globalScene.ui.setMode(UiMode.MESSAGE).then(() => resolve(true));
}; };
const onPokemonNotSelected = () => { const onPokemonNotSelected = () => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => { globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
displayYesNoOptions(resolve); displayYesNoOptions(resolve);
}); });
}; };

View File

@ -24,7 +24,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";

View File

@ -1,5 +1,5 @@
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -19,7 +19,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { PokemonFormChangeItemModifier } from "#app/modifier/modifier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
/** i18n namespace for encounter */ /** i18n namespace for encounter */

View File

@ -18,7 +18,7 @@ import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/u
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
import { import {
BerryModifier, BerryModifier,
@ -32,7 +32,7 @@ import { modifierTypes } from "#app/modifier/modifier-type";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { randSeedItem } from "#app/utils"; import { randSeedItem } from "#app/utils/common";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";

View File

@ -4,13 +4,13 @@ import {
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; import type { ModifierTypeFunc } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** i18n namespace for encounter */ /** i18n namespace for encounter */
const namespace = "mysteryEncounters/departmentStoreSale"; const namespace = "mysteryEncounters/departmentStoreSale";

View File

@ -18,7 +18,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import i18next from "i18next"; import i18next from "i18next";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** i18n namespace for the encounter */ /** i18n namespace for the encounter */
const namespace = "mysteryEncounters/fieldTrip"; const namespace = "mysteryEncounters/fieldTrip";

View File

@ -30,7 +30,7 @@ import { PokemonMove } from "#app/field/pokemon";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { EncounterBattleAnim } from "#app/data/battle-anims"; import { EncounterBattleAnim } from "#app/data/battle-anims";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { import {
@ -41,7 +41,7 @@ import {
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { EncounterAnim } from "#enums/encounter-anims"; import { EncounterAnim } from "#enums/encounter-anims";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";

View File

@ -31,9 +31,9 @@ import {
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils/common";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/fightOrFlight"; const namespace = "mysteryEncounters/fightOrFlight";

View File

@ -30,7 +30,7 @@ import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
import { PostSummonPhase } from "#app/phases/post-summon-phase"; import { PostSummonPhase } from "#app/phases/post-summon-phase";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */

View File

@ -23,7 +23,14 @@ import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { NumberHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle, randSeedItem } from "#app/utils"; import {
NumberHolder,
isNullOrUndefined,
randInt,
randSeedInt,
randSeedShuffle,
randSeedItem,
} from "#app/utils/common";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
@ -41,7 +48,7 @@ import { Gender, getGenderSymbol } from "#app/data/gender";
import { getNatureName } from "#app/data/nature"; import { getNatureName } from "#app/data/nature";
import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import type { PokeballType } from "#enums/pokeball"; import type { PokeballType } from "#enums/pokeball";
import { doShinySparkleAnim } from "#app/field/anims"; import { doShinySparkleAnim } from "#app/field/anims";

View File

@ -10,7 +10,7 @@ import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon";
const OPTION_1_REQUIRED_MOVE = Moves.SURF; const OPTION_1_REQUIRED_MOVE = Moves.SURF;

View File

@ -12,11 +12,11 @@ import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils/common";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/mysteriousChallengers"; const namespace = "mysteryEncounters/mysteriousChallengers";

View File

@ -15,10 +15,10 @@ import {
koPlayerPokemon, koPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { GameOverPhase } from "#app/phases/game-over-phase"; import { GameOverPhase } from "#app/phases/game-over-phase";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils/common";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";

View File

@ -20,7 +20,7 @@ import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-enco
import i18next from "i18next"; import i18next from "i18next";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */

View File

@ -15,7 +15,7 @@ import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifi
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { NumberHolder, randSeedInt } from "#app/utils"; import { NumberHolder, randSeedInt } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
@ -31,7 +31,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase"; import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
import { SummonPhase } from "#app/phases/summon-phase"; import { SummonPhase } from "#app/phases/summon-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups"; import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */

View File

@ -8,7 +8,7 @@ import {
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -26,7 +26,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import type { Nature } from "#enums/nature"; import type { Nature } from "#enums/nature";
import { getNatureName } from "#app/data/nature"; import { getNatureName } from "#app/data/nature";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import i18next from "i18next"; import i18next from "i18next";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */

View File

@ -26,7 +26,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";

View File

@ -7,7 +7,7 @@ import {
transitionMysteryEncounterIntroVisuals, transitionMysteryEncounterIntroVisuals,
updatePlayerMoney, updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
@ -29,7 +29,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { import {
getEncounterPokemonLevelForWave, getEncounterPokemonLevelForWave,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,

View File

@ -7,11 +7,11 @@ import {
import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { randSeedShuffle } from "#app/utils"; import { randSeedShuffle } from "#app/utils/common";
import type MysteryEncounter from "../mystery-encounter"; import type MysteryEncounter from "../mystery-encounter";
import { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import i18next from "i18next"; import i18next from "i18next";

View File

@ -3,7 +3,7 @@ import {
transitionMysteryEncounterIntroVisuals, transitionMysteryEncounterIntroVisuals,
updatePlayerMoney, updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, NumberHolder, randSeedInt, randSeedItem } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
@ -26,9 +26,10 @@ import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encoun
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups"; import { NON_LEGEND_PARADOX_POKEMON, NON_LEGEND_ULTRA_BEASTS } from "#app/data/balance/special-species-groups";
import { timedEventManager } from "#app/global-event-manager";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/thePokemonSalesman"; const namespace = "mysteryEncounters/thePokemonSalesman";
@ -38,6 +39,9 @@ const MAX_POKEMON_PRICE_MULTIPLIER = 4;
/** Odds of shiny magikarp will be 1/value */ /** Odds of shiny magikarp will be 1/value */
const SHINY_MAGIKARP_WEIGHT = 100; const SHINY_MAGIKARP_WEIGHT = 100;
/** Odds of event sale will be value/100 */
const EVENT_THRESHOLD = 50;
/** /**
* Pokemon Salesman encounter. * Pokemon Salesman encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3799 | GitHub Issue #3799} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3799 | GitHub Issue #3799}
@ -82,15 +86,46 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui
tries++; tries++;
} }
const r = randSeedInt(SHINY_MAGIKARP_WEIGHT);
const validEventEncounters = timedEventManager
.getEventEncounters()
.filter(
s =>
!getPokemonSpecies(s.species).legendary &&
!getPokemonSpecies(s.species).subLegendary &&
!getPokemonSpecies(s.species).mythical &&
!NON_LEGEND_PARADOX_POKEMON.includes(s.species) &&
!NON_LEGEND_ULTRA_BEASTS.includes(s.species),
);
let pokemon: PlayerPokemon; let pokemon: PlayerPokemon;
/**
* Mon is determined as follows:
* If you roll the 1% for Shiny Magikarp, you get Magikarp with a random variant
* If an event with more than 1 valid event encounter species is active, you have 20% chance to get one of those
* If the rolled species has no HA, and there are valid event encounters, you will get one of those
* If the rolled species has no HA and there are no valid event encounters, you will get Shiny Magikarp
* Mons rolled from the event encounter pool get 2 extra shiny rolls
*/
if ( if (
randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || r === 0 ||
isNullOrUndefined(species.abilityHidden) || ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) &&
species.abilityHidden === Abilities.NONE (validEventEncounters.length === 0))
) { ) {
// If no HA mon found or you roll 1%, give shiny Magikarp with random variant // If you roll 1%, give shiny Magikarp with random variant
species = getPokemonSpecies(Species.MAGIKARP); species = getPokemonSpecies(Species.MAGIKARP);
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex, undefined, true); pokemon = new PlayerPokemon(species, 5, 2, undefined, undefined, true);
} else if (
(validEventEncounters.length > 0 && (r <= EVENT_THRESHOLD ||
(isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE)))
) {
// If you roll 20%, give event encounter with 2 extra shiny rolls and its HA, if it has one
const enc = randSeedItem(validEventEncounters);
species = getPokemonSpecies(enc.species);
pokemon = new PlayerPokemon(species, 5, species.abilityHidden === Abilities.NONE ? undefined : 2, enc.formIndex);
pokemon.trySetShinySeed();
pokemon.trySetShinySeed();
} else { } else {
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex); pokemon = new PlayerPokemon(species, 5, 2, species.formIndex);
} }

View File

@ -28,7 +28,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/theStrongStuff"; const namespace = "mysteryEncounters/theStrongStuff";

View File

@ -32,7 +32,7 @@ import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
import { ReturnPhase } from "#app/phases/return-phase"; import { ReturnPhase } from "#app/phases/return-phase";
import i18next from "i18next"; import i18next from "i18next";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */

View File

@ -15,7 +15,7 @@ import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { AbilityAttr } from "#app/system/game-data"; import { AbilityAttr } from "#app/system/game-data";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { isNullOrUndefined, randSeedShuffle } from "#app/utils"; import { isNullOrUndefined, randSeedShuffle } from "#app/utils/common";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -28,7 +28,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
import i18next from "i18next"; import i18next from "i18next";
import { getStatKey } from "#enums/stat"; import { getStatKey } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import type { Nature } from "#enums/nature"; import type { Nature } from "#enums/nature";

View File

@ -26,8 +26,8 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils/common";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/trashToTreasure"; const namespace = "mysteryEncounters/trashToTreasure";

View File

@ -27,7 +27,7 @@ import {
getSpriteKeysFromPokemon, getSpriteKeysFromPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import type { Moves } from "#enums/moves"; import type { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { SelfStatusMove } from "#app/data/moves/move"; import { SelfStatusMove } from "#app/data/moves/move";
@ -37,7 +37,7 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun
import { BerryModifier } from "#app/modifier/modifier"; import { BerryModifier } from "#app/modifier/modifier";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/uncommonBreed"; const namespace = "mysteryEncounters/uncommonBreed";

View File

@ -17,7 +17,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon";
import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils"; import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier";

View File

@ -12,7 +12,7 @@ import {
} from "#app/data/mystery-encounters/mystery-encounter-requirements"; } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import type { CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement"; import type { CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
import { CanLearnMoveRequirement } from "./requirements/can-learn-move-requirement"; import { CanLearnMoveRequirement } from "./requirements/can-learn-move-requirement";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
// biome-ignore lint/suspicious/noConfusingVoidType: void unions in callbacks are OK // biome-ignore lint/suspicious/noConfusingVoidType: void unions in callbacks are OK

View File

@ -9,7 +9,7 @@ import { WeatherType } from "#enums/weather-type";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import { isNullOrUndefined } from "#app/utils"; import { isNullOrUndefined } from "#app/utils/common";
import type { Abilities } from "#enums/abilities"; import type { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";

View File

@ -1,6 +1,6 @@
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/data/mystery-encounters/mystery-encounters"; import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/data/mystery-encounters/mystery-encounters";
import { isNullOrUndefined } from "#app/utils"; import { isNullOrUndefined } from "#app/utils/common";
import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
export class SeenEncounterData { export class SeenEncounterData {

View File

@ -1,11 +1,11 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils"; import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils/common";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro"; import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils/common";
import type { StatusEffect } from "#enums/status-effect"; import type { StatusEffect } from "#enums/status-effect";
import type { OptionTextDisplay } from "./mystery-encounter-dialogue"; import type { OptionTextDisplay } from "./mystery-encounter-dialogue";
import type MysteryEncounterDialogue from "./mystery-encounter-dialogue"; import type MysteryEncounterDialogue from "./mystery-encounter-dialogue";

View File

@ -1,7 +1,7 @@
import type { Moves } from "#app/enums/moves"; import type { Moves } from "#app/enums/moves";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon";
import { isNullOrUndefined } from "#app/utils"; import { isNullOrUndefined } from "#app/utils/common";
import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";

View File

@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
import type { TextStyle } from "#app/ui/text"; import type { TextStyle } from "#app/ui/text";
import { getTextWithColors } from "#app/ui/text"; import { getTextWithColors } from "#app/ui/text";
import { UiTheme } from "#enums/ui-theme"; import { UiTheme } from "#enums/ui-theme";
import { isNullOrUndefined } from "#app/utils"; import { isNullOrUndefined } from "#app/utils/common";
import i18next from "i18next"; import i18next from "i18next";
/** /**

View File

@ -1,5 +1,6 @@
import type Battle from "#app/battle"; import type Battle from "#app/battle";
import { BattlerIndex, BattleType } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { BattleType } from "#enums/battle-type";
import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes"; import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes";
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
import { import {
@ -29,8 +30,8 @@ import type PokemonData from "#app/system/pokemon-data";
import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
import { PartyUiMode } from "#app/ui/party-ui-handler"; import { PartyUiMode } from "#app/ui/party-ui-handler";
import { Mode } from "#app/ui/ui"; import { UiMode } from "#enums/ui-mode";
import { isNullOrUndefined, randSeedInt, randomString, randSeedItem } from "#app/utils"; import { isNullOrUndefined, randSeedInt, randomString, randSeedItem } from "#app/utils/common";
import type { BattlerTagType } from "#enums/battler-tag-type"; import type { BattlerTagType } from "#enums/battler-tag-type";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import type { TrainerType } from "#enums/trainer-type"; import type { TrainerType } from "#enums/trainer-type";
@ -423,6 +424,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
console.log( console.log(
`Pokemon: ${getPokemonNameWithAffix(enemyPokemon)}`, `Pokemon: ${getPokemonNameWithAffix(enemyPokemon)}`,
`| Species ID: ${enemyPokemon.species.speciesId}`, `| Species ID: ${enemyPokemon.species.speciesId}`,
`| Level: ${enemyPokemon.level}`,
`| Nature: ${getNatureName(enemyPokemon.nature, true, true, true)}`, `| Nature: ${getNatureName(enemyPokemon.nature, true, true, true)}`,
); );
console.log(`Stats (IVs): ${stats}`); console.log(`Stats (IVs): ${stats}`);
@ -562,7 +564,7 @@ export function selectPokemonForOption(
// Open party screen to choose pokemon // Open party screen to choose pokemon
globalScene.ui.setMode( globalScene.ui.setMode(
Mode.PARTY, UiMode.PARTY,
PartyUiMode.SELECT, PartyUiMode.SELECT,
-1, -1,
(slotIndex: number, _option: PartyOption) => { (slotIndex: number, _option: PartyOption) => {
@ -580,7 +582,7 @@ export function selectPokemonForOption(
} }
// There is a second option to choose after selecting the Pokemon // There is a second option to choose after selecting the Pokemon
globalScene.ui.setMode(Mode.MESSAGE).then(() => { globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
const displayOptions = () => { const displayOptions = () => {
// Always appends a cancel option to bottom of options // Always appends a cancel option to bottom of options
const fullOptions = secondaryOptions const fullOptions = secondaryOptions
@ -622,7 +624,7 @@ export function selectPokemonForOption(
if (fullOptions[0].onHover) { if (fullOptions[0].onHover) {
fullOptions[0].onHover(); fullOptions[0].onHover();
} }
globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true); globalScene.ui.setModeWithoutClear(UiMode.OPTION_SELECT, config, null, true);
}; };
const textPromptKey = const textPromptKey =
@ -672,20 +674,20 @@ export function selectOptionThenPokemon(
const modeToSetOnExit = globalScene.ui.getMode(); const modeToSetOnExit = globalScene.ui.getMode();
const displayOptions = (config: OptionSelectConfig) => { const displayOptions = (config: OptionSelectConfig) => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => { globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
if (!optionSelectPromptKey) { if (!optionSelectPromptKey) {
// Do hover over the starting selection option // Do hover over the starting selection option
if (fullOptions[0].onHover) { if (fullOptions[0].onHover) {
fullOptions[0].onHover(); fullOptions[0].onHover();
} }
globalScene.ui.setMode(Mode.OPTION_SELECT, config); globalScene.ui.setMode(UiMode.OPTION_SELECT, config);
} else { } else {
showEncounterText(optionSelectPromptKey).then(() => { showEncounterText(optionSelectPromptKey).then(() => {
// Do hover over the starting selection option // Do hover over the starting selection option
if (fullOptions[0].onHover) { if (fullOptions[0].onHover) {
fullOptions[0].onHover(); fullOptions[0].onHover();
} }
globalScene.ui.setMode(Mode.OPTION_SELECT, config); globalScene.ui.setMode(UiMode.OPTION_SELECT, config);
}); });
} }
}); });
@ -694,7 +696,7 @@ export function selectOptionThenPokemon(
const selectPokemonAfterOption = (selectedOptionIndex: number) => { const selectPokemonAfterOption = (selectedOptionIndex: number) => {
// Open party screen to choose a Pokemon // Open party screen to choose a Pokemon
globalScene.ui.setMode( globalScene.ui.setMode(
Mode.PARTY, UiMode.PARTY,
PartyUiMode.SELECT, PartyUiMode.SELECT,
-1, -1,
(slotIndex: number, _option: PartyOption) => { (slotIndex: number, _option: PartyOption) => {
@ -1074,8 +1076,8 @@ export function getRandomEncounterSpecies(level: number, isBoss = false, rerollH
ret.formIndex = formIndex; ret.formIndex = formIndex;
} }
//Reroll shiny for event encounters //Reroll shiny or variant for event encounters
if (isEventEncounter && !ret.shiny) { if (isEventEncounter) {
ret.trySetShinySeed(); ret.trySetShinySeed();
} }
//Reroll hidden ability //Reroll hidden ability

View File

@ -1,6 +1,6 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import i18next from "i18next"; import i18next from "i18next";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { PokemonHeldItemModifier } from "#app/modifier/modifier";
import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
@ -14,7 +14,7 @@ import { PlayerGender } from "#enums/player-gender";
import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims";
import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect"; import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect";
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
import { Mode } from "#app/ui/ui"; import { UiMode } from "#enums/ui-mode";
import type { PartyOption } from "#app/ui/party-ui-handler"; import type { PartyOption } from "#app/ui/party-ui-handler";
import { PartyUiMode } from "#app/ui/party-ui-handler"; import { PartyUiMode } from "#app/ui/party-ui-handler";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
@ -714,7 +714,7 @@ export async function catchPokemon(
() => { () => {
globalScene.pokemonInfoContainer.makeRoomForConfirmUi(1, true); globalScene.pokemonInfoContainer.makeRoomForConfirmUi(1, true);
globalScene.ui.setMode( globalScene.ui.setMode(
Mode.CONFIRM, UiMode.CONFIRM,
() => { () => {
const newPokemon = globalScene.addPlayerPokemon( const newPokemon = globalScene.addPlayerPokemon(
pokemon.species, pokemon.species,
@ -729,12 +729,12 @@ export async function catchPokemon(
pokemon, pokemon,
); );
globalScene.ui.setMode( globalScene.ui.setMode(
Mode.SUMMARY, UiMode.SUMMARY,
newPokemon, newPokemon,
0, 0,
SummaryUiMode.DEFAULT, SummaryUiMode.DEFAULT,
() => { () => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => { globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
promptRelease(); promptRelease();
}); });
}, },
@ -749,13 +749,13 @@ export async function catchPokemon(
female: pokemon.gender === Gender.FEMALE, female: pokemon.gender === Gender.FEMALE,
}; };
globalScene.ui.setOverlayMode( globalScene.ui.setOverlayMode(
Mode.POKEDEX_PAGE, UiMode.POKEDEX_PAGE,
pokemon.species, pokemon.species,
pokemon.formIndex, pokemon.formIndex,
attributes, attributes,
null, null,
() => { () => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => { globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
promptRelease(); promptRelease();
}); });
}, },
@ -763,11 +763,11 @@ export async function catchPokemon(
}, },
() => { () => {
globalScene.ui.setMode( globalScene.ui.setMode(
Mode.PARTY, UiMode.PARTY,
PartyUiMode.RELEASE, PartyUiMode.RELEASE,
0, 0,
(slotIndex: number, _option: PartyOption) => { (slotIndex: number, _option: PartyOption) => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => { globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
if (slotIndex < 6) { if (slotIndex < 6) {
addToParty(slotIndex); addToParty(slotIndex);
} else { } else {
@ -778,7 +778,7 @@ export async function catchPokemon(
); );
}, },
() => { () => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => { globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
removePokemon(); removePokemon();
end(); end();
}); });

View File

@ -1,5 +1,5 @@
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { getFrameMs } from "#app/utils"; import { getFrameMs } from "#app/utils/common";
import { cos, sin } from "#app/field/anims"; import { cos, sin } from "#app/field/anims";
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";

View File

@ -1,4 +1,4 @@
import { toReadableString } from "#app/utils"; import { toReadableString } from "#app/utils/common";
import { TextStyle, getBBCodeFrag } from "../ui/text"; import { TextStyle, getBBCodeFrag } from "../ui/text";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { UiTheme } from "#enums/ui-theme"; import { UiTheme } from "#enums/ui-theme";

View File

@ -1,6 +1,6 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier"; import { CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier";
import { NumberHolder } from "#app/utils"; import { NumberHolder } from "#app/utils/common";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import i18next from "i18next"; import i18next from "i18next";

View File

@ -3,7 +3,7 @@ import type Pokemon from "../field/pokemon";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { allMoves } from "./moves/move"; import { allMoves } from "./moves/move";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
import type { Constructor, nil } from "#app/utils"; import type { Constructor, nil } from "#app/utils/common";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";

View File

@ -8,7 +8,7 @@ import type { AnySound } from "#app/battle-scene";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { GameMode } from "#app/game-mode"; import type { GameMode } from "#app/game-mode";
import { DexAttr, type StarterMoveset } from "#app/system/game-data"; import { DexAttr, type StarterMoveset } from "#app/system/game-data";
import { isNullOrUndefined, capitalizeString, randSeedInt, randSeedGauss, randSeedItem } from "#app/utils"; import { isNullOrUndefined, capitalizeString, randSeedInt, randSeedGauss, randSeedItem } from "#app/utils/common";
import { uncatchableSpecies } from "#app/data/balance/biomes"; import { uncatchableSpecies } from "#app/data/balance/biomes";
import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { speciesEggMoves } from "#app/data/balance/egg-moves";
import { GrowthRate } from "#app/data/exp"; import { GrowthRate } from "#app/data/exp";
@ -27,7 +27,7 @@ import {
} from "#app/data/balance/pokemon-level-moves"; } from "#app/data/balance/pokemon-level-moves";
import type { Stat } from "#enums/stat"; import type { Stat } from "#enums/stat";
import type { Variant, VariantSet } from "#app/sprites/variant"; import type { Variant, VariantSet } from "#app/sprites/variant";
import { populateVariantColorCache, variantData } from "#app/sprites/variant"; import { populateVariantColorCache, variantColorCache, variantData } from "#app/sprites/variant";
import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesFormKey } from "#enums/species-form-key";
import { starterPassiveAbilities } from "#app/data/balance/passives"; import { starterPassiveAbilities } from "#app/data/balance/passives";
@ -404,7 +404,7 @@ export abstract class PokemonSpeciesForm {
} }
/** Compute the sprite ID of the pokemon form. */ /** Compute the sprite ID of the pokemon form. */
getSpriteId(female: boolean, formIndex?: number, shiny?: boolean, variant = 0, back?: boolean): string { getSpriteId(female: boolean, formIndex?: number, shiny?: boolean, variant = 0, back = false): string {
const baseSpriteKey = this.getBaseSpriteKey(female, formIndex); const baseSpriteKey = this.getBaseSpriteKey(female, formIndex);
let config = variantData; let config = variantData;
@ -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;
@ -594,6 +595,44 @@ export abstract class PokemonSpeciesForm {
return true; return true;
} }
/**
* Load the variant colors for the species into the variant color cache
*
* @param spriteKey - The sprite key to use
* @param female - Whether to load female instead of male
* @param back - Whether the back sprite is being loaded
*
*/
async loadVariantColors(
spriteKey: string,
female: boolean,
variant: Variant,
back = false,
formIndex?: number,
): Promise<void> {
let baseSpriteKey = this.getBaseSpriteKey(female, formIndex);
if (back) {
baseSpriteKey = "back__" + baseSpriteKey;
}
if (variantColorCache.hasOwnProperty(baseSpriteKey)) {
// Variant colors have already been loaded
return;
}
const variantInfo = variantData[this.getVariantDataIndex(formIndex)];
// Do nothing if there is no variant information or the variant does not have color replacements
if (!variantInfo || variantInfo[variant] !== 1) {
return;
}
await populateVariantColorCache(
"pkmn__" + baseSpriteKey,
globalScene.experimentalSprites && hasExpSprite(spriteKey),
baseSpriteKey.replace("__", "/"),
);
}
async loadAssets( async loadAssets(
female: boolean, female: boolean,
formIndex?: number, formIndex?: number,
@ -606,15 +645,9 @@ export abstract class PokemonSpeciesForm {
const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant, back); const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant, back);
globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant, back)); globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant, back));
globalScene.load.audio(this.getCryKey(formIndex), `audio/${this.getCryKey(formIndex)}.m4a`); globalScene.load.audio(this.getCryKey(formIndex), `audio/${this.getCryKey(formIndex)}.m4a`);
if (!isNullOrUndefined(variant)) {
const baseSpriteKey = this.getBaseSpriteKey(female, formIndex); await this.loadVariantColors(spriteKey, female, variant, back, formIndex);
}
// Force the variant color cache to be loaded for the form
await populateVariantColorCache(
"pkmn__" + baseSpriteKey,
globalScene.experimentalSprites && hasExpSprite(spriteKey),
baseSpriteKey,
);
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => { globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => {
const originalWarn = console.warn; const originalWarn = console.warn;

View File

@ -1,4 +1,4 @@
import { randIntRange } from "#app/utils"; import { randIntRange } from "#app/utils/common";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import type { ParseKeys } from "i18next"; import type { ParseKeys } from "i18next";
import i18next from "i18next"; import i18next from "i18next";

View File

@ -59,7 +59,7 @@ export class Terrain {
// Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain
return ( return (
move.getPriority(user) > 0 && move.getPriority(user) > 0 &&
user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded()) user.getOpponents(true).some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded())
); );
} }
} }

View File

@ -1,5 +1,5 @@
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { toReadableString } from "#app/utils"; import { toReadableString } from "#app/utils/common";
class TrainerNameConfig { class TrainerNameConfig {
public urls: string[]; public urls: string[];

View File

@ -1,6 +1,8 @@
import { startingWave } from "#app/battle-scene"; import { startingWave } from "#app/starting-wave";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { GameModes } from "#app/game-mode";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
export class TrainerPartyTemplate { export class TrainerPartyTemplate {
public size: number; public size: number;
@ -222,19 +224,18 @@ export const trainerPartyTemplates = {
*/ */
export function getEvilGruntPartyTemplate(): TrainerPartyTemplate { export function getEvilGruntPartyTemplate(): TrainerPartyTemplate {
const waveIndex = globalScene.currentBattle?.waveIndex; const waveIndex = globalScene.currentBattle?.waveIndex;
if (waveIndex < 40) { switch (waveIndex) {
return trainerPartyTemplates.TWO_AVG; case ClassicFixedBossWaves.EVIL_GRUNT_1:
return trainerPartyTemplates.TWO_AVG;
case ClassicFixedBossWaves.EVIL_GRUNT_2:
return trainerPartyTemplates.THREE_AVG;
case ClassicFixedBossWaves.EVIL_GRUNT_3:
return trainerPartyTemplates.TWO_AVG_ONE_STRONG;
case ClassicFixedBossWaves.EVIL_ADMIN_1:
return trainerPartyTemplates.GYM_LEADER_4; // 3avg 1 strong 1 stronger
default:
return trainerPartyTemplates.GYM_LEADER_5; // 3 avg 2 strong 1 stronger
} }
if (waveIndex < 63) {
return trainerPartyTemplates.THREE_AVG;
}
if (waveIndex < 65) {
return trainerPartyTemplates.TWO_AVG_ONE_STRONG;
}
if (waveIndex < 112) {
return trainerPartyTemplates.GYM_LEADER_4; // 3avg 1 strong 1 stronger
}
return trainerPartyTemplates.GYM_LEADER_5; // 3 avg 2 strong 1 stronger
} }
export function getWavePartyTemplate(...templates: TrainerPartyTemplate[]) { export function getWavePartyTemplate(...templates: TrainerPartyTemplate[]) {
@ -245,11 +246,36 @@ export function getWavePartyTemplate(...templates: TrainerPartyTemplate[]) {
} }
export function getGymLeaderPartyTemplate() { export function getGymLeaderPartyTemplate() {
return getWavePartyTemplate( const { currentBattle, gameMode } = globalScene;
trainerPartyTemplates.GYM_LEADER_1, switch (gameMode.modeId) {
trainerPartyTemplates.GYM_LEADER_2, case GameModes.DAILY:
trainerPartyTemplates.GYM_LEADER_3, if (currentBattle?.waveIndex <= 20) {
trainerPartyTemplates.GYM_LEADER_4, return trainerPartyTemplates.GYM_LEADER_2
trainerPartyTemplates.GYM_LEADER_5, }
); return trainerPartyTemplates.GYM_LEADER_3;
case GameModes.CHALLENGE: // In the future, there may be a ChallengeType to call here. For now, use classic's.
case GameModes.CLASSIC:
if (currentBattle?.waveIndex <= 20) {
return trainerPartyTemplates.GYM_LEADER_1; // 1 avg 1 strong
}
else if (currentBattle?.waveIndex <= 30) {
return trainerPartyTemplates.GYM_LEADER_2; // 1 avg 1 strong 1 stronger
}
else if (currentBattle?.waveIndex <= 60) { // 50 and 60
return trainerPartyTemplates.GYM_LEADER_3; // 2 avg 1 strong 1 stronger
}
else if (currentBattle?.waveIndex <= 90) { // 80 and 90
return trainerPartyTemplates.GYM_LEADER_4; // 3 avg 1 strong 1 stronger
}
// 110+
return trainerPartyTemplates.GYM_LEADER_5; // 3 avg 2 strong 1 stronger
default:
return getWavePartyTemplate(
trainerPartyTemplates.GYM_LEADER_1,
trainerPartyTemplates.GYM_LEADER_2,
trainerPartyTemplates.GYM_LEADER_3,
trainerPartyTemplates.GYM_LEADER_4,
trainerPartyTemplates.GYM_LEADER_5,
);
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ import type Pokemon from "../field/pokemon";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import type Move from "./moves/move"; import type Move from "./moves/move";
import { AttackMove } from "./moves/move"; import { AttackMove } from "./moves/move";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils/common";
import { SuppressWeatherEffectAbAttr } from "./abilities/ability"; import { SuppressWeatherEffectAbAttr } from "./abilities/ability";
import { TerrainType, getTerrainName } from "./terrain"; import { TerrainType, getTerrainName } from "./terrain";
import i18next from "i18next"; import i18next from "i18next";
@ -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

@ -1,7 +1,6 @@
export enum MoveEffectTrigger { export enum MoveEffectTrigger {
PRE_APPLY, PRE_APPLY,
POST_APPLY, POST_APPLY,
HIT,
/** Triggers one time after all target effects have applied */ /** Triggers one time after all target effects have applied */
POST_TARGET POST_TARGET
} }

6
src/enums/battle-type.ts Normal file
View File

@ -0,0 +1,6 @@
export enum BattleType {
WILD,
TRAINER,
CLEAR,
MYSTERY_ENCOUNTER
}

View File

@ -0,0 +1,22 @@
export enum ClassicFixedBossWaves {
TOWN_YOUNGSTER = 5,
RIVAL_1 = 8,
RIVAL_2 = 25,
EVIL_GRUNT_1 = 35,
RIVAL_3 = 55,
EVIL_GRUNT_2 = 62,
EVIL_GRUNT_3 = 64,
EVIL_ADMIN_1 = 66,
RIVAL_4 = 95,
EVIL_GRUNT_4 = 112,
EVIL_ADMIN_2 = 114,
EVIL_BOSS_1 = 115,
RIVAL_5 = 145,
EVIL_BOSS_2 = 165,
ELITE_FOUR_1 = 182,
ELITE_FOUR_2 = 184,
ELITE_FOUR_3 = 186,
ELITE_FOUR_4 = 188,
CHAMPION = 190,
RIVAL_6 = 195
}

View File

@ -0,0 +1,23 @@
/** The result of a hit check calculation */
export const HitCheckResult = {
/** Hit checks haven't been evaluated yet in this pass */
PENDING: 0,
/** The move hits the target successfully */
HIT: 1,
/** The move has no effect on the target */
NO_EFFECT: 2,
/** The move has no effect on the target, but doesn't proc the default "no effect" message */
NO_EFFECT_NO_MESSAGE: 3,
/** The target protected itself against the move */
PROTECTED: 4,
/** The move missed the target */
MISS: 5,
/** The move is reflected by magic coat or magic bounce */
REFLECTED: 6,
/** The target is no longer on the field */
TARGET_NOT_ON_FIELD: 7,
/** The move failed unexpectedly */
ERROR: 8,
} as const;
export type HitCheckResult = typeof HitCheckResult[keyof typeof HitCheckResult];

47
src/enums/ui-mode.ts Normal file
View File

@ -0,0 +1,47 @@
export enum UiMode {
MESSAGE,
TITLE,
COMMAND,
FIGHT,
BALL,
TARGET_SELECT,
MODIFIER_SELECT,
SAVE_SLOT,
PARTY,
SUMMARY,
STARTER_SELECT,
EVOLUTION_SCENE,
EGG_HATCH_SCENE,
EGG_HATCH_SUMMARY,
CONFIRM,
OPTION_SELECT,
MENU,
MENU_OPTION_SELECT,
SETTINGS,
SETTINGS_DISPLAY,
SETTINGS_AUDIO,
SETTINGS_GAMEPAD,
GAMEPAD_BINDING,
SETTINGS_KEYBOARD,
KEYBOARD_BINDING,
ACHIEVEMENTS,
GAME_STATS,
EGG_LIST,
EGG_GACHA,
POKEDEX,
POKEDEX_SCAN,
POKEDEX_PAGE,
LOGIN_FORM,
REGISTRATION_FORM,
LOADING,
SESSION_RELOAD,
UNAVAILABLE,
CHALLENGE_SELECT,
RENAME_POKEMON,
RUN_HISTORY,
RUN_INFO,
TEST_DIALOGUE,
AUTO_COMPLETE,
ADMIN,
MYSTERY_ENCOUNTER
}

View File

@ -1,7 +1,7 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import type { Variant } from "#app/sprites/variant"; import type { Variant } from "#app/sprites/variant";
import { getFrameMs, randGauss } from "#app/utils"; import { getFrameMs, randGauss } from "#app/utils/common";
export function addPokeballOpenParticles(x: number, y: number, pokeballType: PokeballType): void { export function addPokeballOpenParticles(x: number, y: number, pokeballType: PokeballType): void {
switch (pokeballType) { switch (pokeballType) {

View File

@ -1,7 +1,7 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { BiomeTierTrainerPools, PokemonPools } from "#app/data/balance/biomes"; import type { BiomeTierTrainerPools, PokemonPools } from "#app/data/balance/biomes";
import { biomePokemonPools, BiomePoolTier, biomeTrainerPools } from "#app/data/balance/biomes"; import { biomePokemonPools, BiomePoolTier, biomeTrainerPools } from "#app/data/balance/biomes";
import { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from "#app/utils"; import { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { import {

View File

@ -2,7 +2,7 @@ import { TextStyle, addTextObject } from "../ui/text";
import type { DamageResult } from "./pokemon"; import type { DamageResult } from "./pokemon";
import type Pokemon from "./pokemon"; import type Pokemon from "./pokemon";
import { HitResult } from "./pokemon"; import { HitResult } from "./pokemon";
import { formatStat, fixedInt } from "#app/utils"; import { formatStat, fixedInt } from "#app/utils/common";
import type { BattlerIndex } from "../battle"; import type { BattlerIndex } from "../battle";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";

View File

@ -2,7 +2,7 @@ import type { GameObjects } from "phaser";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import type { Species } from "#enums/species"; import type { Species } from "#enums/species";
import { isNullOrUndefined } from "#app/utils"; import { isNullOrUndefined } from "#app/utils/common";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import type { Variant } from "#app/sprites/variant"; import type { Variant } from "#app/sprites/variant";
import { doShinySparkleAnim } from "#app/field/anims"; import { doShinySparkleAnim } from "#app/field/anims";

View File

@ -1,6 +1,6 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import Pokemon from "./pokemon"; import Pokemon from "./pokemon";
import { fixedInt, randInt } from "#app/utils"; import { fixedInt, randInt } from "#app/utils/common";
export default class PokemonSpriteSparkleHandler { export default class PokemonSpriteSparkleHandler {
private sprites: Set<Phaser.GameObjects.Sprite>; private sprites: Set<Phaser.GameObjects.Sprite>;

View File

@ -12,7 +12,6 @@ import BattleInfo, {
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import { import {
HighCritAttr, HighCritAttr,
StatChangeBeforeDmgCalcAttr,
HitsTagAttr, HitsTagAttr,
applyMoveAttrs, applyMoveAttrs,
FixedDamageAttr, FixedDamageAttr,
@ -55,7 +54,7 @@ import {
getStarterValueFriendshipCap, getStarterValueFriendshipCap,
speciesStarterCosts, speciesStarterCosts,
} from "#app/data/balance/starters"; } from "#app/data/balance/starters";
import { NumberHolder, randSeedInt, getIvsFromId, BooleanHolder, randSeedItem, isNullOrUndefined, getEnumValues, toDmgValue, fixedInt, rgbaToInt, rgbHexToRgba, rgbToHsv, deltaRgb, isBetween, type nil, type Constructor } from "#app/utils"; import { NumberHolder, randSeedInt, getIvsFromId, BooleanHolder, randSeedItem, isNullOrUndefined, getEnumValues, toDmgValue, fixedInt, rgbaToInt, rgbHexToRgba, rgbToHsv, deltaRgb, isBetween, type nil, type Constructor } from "#app/utils/common";
import type { TypeDamageMultiplier } from "#app/data/type"; import type { TypeDamageMultiplier } from "#app/data/type";
import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type"; import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
@ -70,10 +69,8 @@ import {
EFFECTIVE_STATS, EFFECTIVE_STATS,
} from "#enums/stat"; } from "#enums/stat";
import { import {
DamageMoneyRewardModifier,
EnemyDamageBoosterModifier, EnemyDamageBoosterModifier,
EnemyDamageReducerModifier, EnemyDamageReducerModifier,
EnemyEndureChanceModifier,
EnemyFusionChanceModifier, EnemyFusionChanceModifier,
HiddenAbilityRateBoosterModifier, HiddenAbilityRateBoosterModifier,
BaseStatModifier, BaseStatModifier,
@ -119,7 +116,6 @@ import {
TypeImmuneTag, TypeImmuneTag,
getBattlerTag, getBattlerTag,
SemiInvulnerableTag, SemiInvulnerableTag,
TypeBoostTag,
MoveRestrictionBattlerTag, MoveRestrictionBattlerTag,
ExposedTag, ExposedTag,
DragonCheerTag, DragonCheerTag,
@ -188,12 +184,12 @@ import {
PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr,
applyAllyStatMultiplierAbAttrs, applyAllyStatMultiplierAbAttrs,
AllyStatMultiplierAbAttr, AllyStatMultiplierAbAttr,
MoveAbilityBypassAbAttr MoveAbilityBypassAbAttr,
} from "#app/data/abilities/ability"; } from "#app/data/abilities/ability";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import type PokemonData from "#app/system/pokemon-data"; import type PokemonData from "#app/system/pokemon-data";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { Mode } from "#app/ui/ui"; import { UiMode } from "#enums/ui-mode";
import type { PartyOption } from "#app/ui/party-ui-handler"; import type { PartyOption } from "#app/ui/party-ui-handler";
import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
@ -202,7 +198,7 @@ import {
EVOLVE_MOVE, EVOLVE_MOVE,
RELEARN_MOVE, RELEARN_MOVE,
} from "#app/data/balance/pokemon-level-moves"; } from "#app/data/balance/pokemon-level-moves";
import { DamageAchv, achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
import type { StarterDataEntry, StarterMoveset } from "#app/system/game-data"; import type { StarterDataEntry, StarterMoveset } from "#app/system/game-data";
import { DexAttr } from "#app/system/game-data"; import { DexAttr } from "#app/system/game-data";
import { import {
@ -248,6 +244,7 @@ import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { SwitchType } from "#enums/switch-type"; import { SwitchType } from "#enums/switch-type";
import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesFormKey } from "#enums/species-form-key";
import { getStatusEffectOverlapText } from "#app/data/status-effect";
import { import {
BASE_HIDDEN_ABILITY_CHANCE, BASE_HIDDEN_ABILITY_CHANCE,
BASE_SHINY_CHANCE, BASE_SHINY_CHANCE,
@ -278,6 +275,36 @@ export enum FieldPosition {
RIGHT, RIGHT,
} }
/** Base typeclass for damage parameter methods, used for DRY */
type damageParams = {
/** The attacking {@linkcode Pokemon} */
source: Pokemon;
/** The move used in the attack */
move: Move;
/** The move's {@linkcode MoveCategory} after variable-category effects are applied */
moveCategory: MoveCategory;
/** If `true`, ignores this Pokemon's defensive ability effects */
ignoreAbility?: boolean;
/** If `true`, ignores the attacking Pokemon's ability effects */
ignoreSourceAbility?: boolean;
/** If `true`, ignores the ally Pokemon's ability effects */
ignoreAllyAbility?: boolean;
/** If `true`, ignores the ability effects of the attacking pokemon's ally */
ignoreSourceAllyAbility?: boolean;
/** If `true`, calculates damage for a critical hit */
isCritical?: boolean;
/** If `true`, suppresses changes to game state during the calculation */
simulated?: boolean;
/** If defined, used in place of calculated effectiveness values */
effectiveness?: number;
}
/** Type for the parameters of {@linkcode Pokemon#getBaseDamage | getBaseDamage} */
type getBaseDamageParams = Omit<damageParams, "effectiveness">
/** Type for the parameters of {@linkcode Pokemon#getAttackDamage | getAttackDamage} */
type getAttackDamageParams = Omit<damageParams, "moveCategory">;
export default abstract class Pokemon extends Phaser.GameObjects.Container { export default abstract class Pokemon extends Phaser.GameObjects.Container {
public id: number; public id: number;
public name: string; public name: string;
@ -1112,7 +1139,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
getSpeciesForm(ignoreOverride?: boolean, useIllusion: boolean = false): PokemonSpeciesForm { getSpeciesForm(ignoreOverride?: boolean, useIllusion: boolean = false): PokemonSpeciesForm {
const species: PokemonSpecies = useIllusion && !!this.summonData?.illusion ? getPokemonSpecies(this.summonData?.illusion.species) : this.species; const species: PokemonSpecies = useIllusion && !!this.summonData?.illusion ? getPokemonSpecies(this.summonData?.illusion.species) : this.species;
const formIndex: integer = useIllusion && !!this.summonData?.illusion ? this.summonData?.illusion.formIndex : this.formIndex; const formIndex: integer = useIllusion && !!this.summonData?.illusion ? this.summonData?.illusion.formIndex : this.formIndex;
if (!ignoreOverride && this.summonData?.speciesForm) { if (!ignoreOverride && this.summonData?.speciesForm) {
@ -1447,25 +1473,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* Calculate the critical-hit stage of a move used against this pokemon by * Calculate the critical-hit stage of a move used against this pokemon by
* the given source * the given source
* *
* @param source the {@linkcode Pokemon} who using the move * @param source - The {@linkcode Pokemon} who using the move
* @param move the {@linkcode Move} being used * @param move - The {@linkcode Move} being used
* @returns the final critical-hit stage value * @returns The final critical-hit stage value
*/ */
getCritStage(source: Pokemon, move: Move): number { getCritStage(source: Pokemon, move: Move): number {
const critStage = new NumberHolder(0); const critStage = new NumberHolder(0);
applyMoveAttrs(HighCritAttr, source, this, move, critStage); applyMoveAttrs(HighCritAttr, source, this, move, critStage);
globalScene.applyModifiers( globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
CritBoosterModifier, globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
source.isPlayer(), applyAbAttrs(BonusCritAbAttr, source, null, false, critStage);
source,
critStage,
);
globalScene.applyModifiers(
TempCritBoosterModifier,
source.isPlayer(),
critStage,
);
applyAbAttrs(BonusCritAbAttr, source, null, false, critStage)
const critBoostTag = source.getTag(CritBoostTag); const critBoostTag = source.getTag(CritBoostTag);
if (critBoostTag) { if (critBoostTag) {
if (critBoostTag instanceof DragonCheerTag) { if (critBoostTag instanceof DragonCheerTag) {
@ -1481,6 +1498,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return critStage.value; return critStage.value;
} }
/**
* Calculates the category of a move when used by this pokemon after
* category-changing move effects are applied.
* @param target - The {@linkcode Pokemon} using the move
* @param move - The {@linkcode Move} being used
* @returns The given move's final category
*/
getMoveCategory(target: Pokemon, move: Move): MoveCategory {
const moveCategory = new NumberHolder(move.category);
applyMoveAttrs(VariableMoveCategoryAttr, this, target, move, moveCategory);
return moveCategory.value;
}
/** /**
* Calculates and retrieves the final value of a stat considering any held * Calculates and retrieves the final value of a stat considering any held
* items, move effects, opponent abilities, and whether there was a critical * items, move effects, opponent abilities, and whether there was a critical
@ -2590,7 +2620,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param simulated Whether to apply abilities via simulated calls (defaults to `true`) * @param simulated Whether to apply abilities via simulated calls (defaults to `true`)
* @param cancelled {@linkcode BooleanHolder} Stores whether the move was cancelled by a non-type-based immunity. * @param cancelled {@linkcode BooleanHolder} Stores whether the move was cancelled by a non-type-based immunity.
* @param useIllusion - Whether we want the attack move effectiveness on the illusion or not * @param useIllusion - Whether we want the attack move effectiveness on the illusion or not
* Currently only used by {@linkcode Pokemon.apply} to determine whether a "No effect" message should be shown.
* @returns The type damage multiplier, indicating the effectiveness of the move * @returns The type damage multiplier, indicating the effectiveness of the move
*/ */
getMoveEffectiveness( getMoveEffectiveness(
@ -3176,7 +3205,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** /**
* Function that tries to set a Pokemon shiny based on seed. * Function that tries to set a Pokemon shiny based on seed.
* For manual use only, usually to roll a Pokemon's shiny chance a second time. * For manual use only, usually to roll a Pokemon's shiny chance a second time.
* If it rolls shiny, also sets a random variant and give the Pokemon the associated luck. * If it rolls shiny, or if it's already shiny, also sets a random variant and give the Pokemon the associated luck.
* *
* The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536` * The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536`
* @param thresholdOverride number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm) * @param thresholdOverride number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
@ -3187,29 +3216,31 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
thresholdOverride?: number, thresholdOverride?: number,
applyModifiersToOverride?: boolean, applyModifiersToOverride?: boolean,
): boolean { ): boolean {
const shinyThreshold = new NumberHolder(BASE_SHINY_CHANCE); if (!this.shiny) {
if (thresholdOverride === undefined || applyModifiersToOverride) { const shinyThreshold = new NumberHolder(BASE_SHINY_CHANCE);
if (thresholdOverride !== undefined && applyModifiersToOverride) { if (thresholdOverride === undefined || applyModifiersToOverride) {
shinyThreshold.value = thresholdOverride; if (thresholdOverride !== undefined && applyModifiersToOverride) {
} shinyThreshold.value = thresholdOverride;
if (timedEventManager.isEventActive()) { }
shinyThreshold.value *= timedEventManager.getShinyMultiplier(); if (timedEventManager.isEventActive()) {
} shinyThreshold.value *= timedEventManager.getShinyMultiplier();
if (!this.hasTrainer()) { }
globalScene.applyModifiers( globalScene.applyModifiers(
ShinyRateBoosterModifier, ShinyRateBoosterModifier,
true, true,
shinyThreshold, shinyThreshold,
); );
} }
} else { else {
shinyThreshold.value = thresholdOverride; shinyThreshold.value = thresholdOverride;
}
this.shiny = randSeedInt(65536) < shinyThreshold.value;
} }
this.shiny = randSeedInt(65536) < shinyThreshold.value;
if (this.shiny) { if (this.shiny) {
this.variant = this.generateShinyVariant(); this.variant = this.variant ?? 0;
this.variant = Math.max(this.generateShinyVariant(), this.variant) as Variant; // Don't set a variant lower than the current one
this.luck = this.luck =
this.variant + 1 + (this.fusionShiny ? this.fusionVariant + 1 : 0); this.variant + 1 + (this.fusionShiny ? this.fusionVariant + 1 : 0);
this.initShinySparkle(); this.initShinySparkle();
@ -3858,12 +3889,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return null; return null;
} }
getOpponents(): Pokemon[] { /**
* Returns the pokemon that oppose this one and are active
*
* @param onField - whether to also check if the pokemon is currently on the field (defaults to true)
*/
getOpponents(onField = true): Pokemon[] {
return ( return (
(this.isPlayer() (this.isPlayer()
? globalScene.getEnemyField() ? globalScene.getEnemyField()
: globalScene.getPlayerField()) as Pokemon[] : globalScene.getPlayerField()) as Pokemon[]
).filter(p => p.isActive()); ).filter(p => p.isActive(onField));
} }
getOpponentDescriptor(): string { getOpponentDescriptor(): string {
@ -4074,27 +4110,28 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** /**
* Calculates the base damage of the given move against this Pokemon when attacked by the given source. * Calculates the base damage of the given move against this Pokemon when attacked by the given source.
* Used during damage calculation and for Shell Side Arm's forecasting effect. * Used during damage calculation and for Shell Side Arm's forecasting effect.
* @param source the attacking {@linkcode Pokemon}. * @param source - The attacking {@linkcode Pokemon}.
* @param move the {@linkcode Move} used in the attack. * @param move - The {@linkcode Move} used in the attack.
* @param moveCategory the move's {@linkcode MoveCategory} after variable-category effects are applied. * @param moveCategory - The move's {@linkcode MoveCategory} after variable-category effects are applied.
* @param ignoreAbility if `true`, ignores this Pokemon's defensive ability effects (defaults to `false`). * @param ignoreAbility - If `true`, ignores this Pokemon's defensive ability effects (defaults to `false`).
* @param ignoreSourceAbility if `true`, ignore's the attacking Pokemon's ability effects (defaults to `false`). * @param ignoreSourceAbility - If `true`, ignore's the attacking Pokemon's ability effects (defaults to `false`).
* @param ignoreAllyAbility if `true`, ignores the ally Pokemon's ability effects (defaults to `false`). * @param ignoreAllyAbility - If `true`, ignores the ally Pokemon's ability effects (defaults to `false`).
* @param ignoreSourceAllyAbility if `true`, ignores the attacking Pokemon's ally's ability effects (defaults to `false`). * @param ignoreSourceAllyAbility - If `true`, ignores the attacking Pokemon's ally's ability effects (defaults to `false`).
* @param isCritical if `true`, calculates effective stats as if the hit were critical (defaults to `false`). * @param isCritical - if `true`, calculates effective stats as if the hit were critical (defaults to `false`).
* @param simulated if `true`, suppresses changes to game state during calculation (defaults to `true`). * @param simulated - if `true`, suppresses changes to game state during calculation (defaults to `true`).
* @returns The move's base damage against this Pokemon when used by the source Pokemon. * @returns The move's base damage against this Pokemon when used by the source Pokemon.
*/ */
getBaseDamage( getBaseDamage(
source: Pokemon, {
move: Move, source,
moveCategory: MoveCategory, move,
moveCategory,
ignoreAbility = false, ignoreAbility = false,
ignoreSourceAbility = false, ignoreSourceAbility = false,
ignoreAllyAbility = false, ignoreAllyAbility = false,
ignoreSourceAllyAbility = false, ignoreSourceAllyAbility = false,
isCritical = false, isCritical = false,
simulated = true, simulated = true}: getBaseDamageParams
): number { ): number {
const isPhysical = moveCategory === MoveCategory.PHYSICAL; const isPhysical = moveCategory === MoveCategory.PHYSICAL;
@ -4221,27 +4258,27 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** /**
* Calculates the damage of an attack made by another Pokemon against this Pokemon * Calculates the damage of an attack made by another Pokemon against this Pokemon
* @param source {@linkcode Pokemon} the attacking Pokemon * @param source {@linkcode Pokemon} the attacking Pokemon
* @param move {@linkcode Pokemon} the move used in the attack * @param move The {@linkcode Move} used in the attack
* @param ignoreAbility If `true`, ignores this Pokemon's defensive ability effects * @param ignoreAbility If `true`, ignores this Pokemon's defensive ability effects
* @param ignoreSourceAbility If `true`, ignores the attacking Pokemon's ability effects * @param ignoreSourceAbility If `true`, ignores the attacking Pokemon's ability effects
* @param ignoreAllyAbility If `true`, ignores the ally Pokemon's ability effects * @param ignoreAllyAbility If `true`, ignores the ally Pokemon's ability effects
* @param ignoreSourceAllyAbility If `true`, ignores the ability effects of the attacking pokemon's ally * @param ignoreSourceAllyAbility If `true`, ignores the ability effects of the attacking pokemon's ally
* @param isCritical If `true`, calculates damage for a critical hit. * @param isCritical If `true`, calculates damage for a critical hit.
* @param simulated If `true`, suppresses changes to game state during the calculation. * @param simulated If `true`, suppresses changes to game state during the calculation.
* @returns a {@linkcode DamageCalculationResult} object with three fields: * @param effectiveness If defined, used in place of calculated effectiveness values
* - `cancelled`: `true` if the move was cancelled by another effect. * @returns The {@linkcode DamageCalculationResult}
* - `result`: {@linkcode HitResult} indicates the attack's type effectiveness.
* - `damage`: `number` the attack's final damage output.
*/ */
getAttackDamage( getAttackDamage(
source: Pokemon, {
move: Move, source,
ignoreAbility = false, move,
ignoreSourceAbility = false, ignoreAbility = false,
ignoreAllyAbility = false, ignoreSourceAbility = false,
ignoreSourceAllyAbility = false, ignoreAllyAbility = false,
isCritical = false, ignoreSourceAllyAbility = false,
simulated = true, isCritical = false,
simulated = true,
effectiveness}: getAttackDamageParams,
): DamageCalculationResult { ): DamageCalculationResult {
const damage = new NumberHolder(0); const damage = new NumberHolder(0);
const defendingSide = this.isPlayer() const defendingSide = this.isPlayer()
@ -4271,7 +4308,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* *
* Note that the source's abilities are not ignored here * Note that the source's abilities are not ignored here
*/ */
const typeMultiplier = this.getMoveEffectiveness( const typeMultiplier = effectiveness ?? this.getMoveEffectiveness(
source, source,
move, move,
ignoreAbility, ignoreAbility,
@ -4343,7 +4380,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* The attack's base damage, as determined by the source's level, move power * The attack's base damage, as determined by the source's level, move power
* and Attack stat as well as this Pokemon's Defense stat * and Attack stat as well as this Pokemon's Defense stat
*/ */
const baseDamage = this.getBaseDamage( const baseDamage = this.getBaseDamage({
source, source,
move, move,
moveCategory, moveCategory,
@ -4353,7 +4390,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
ignoreSourceAllyAbility, ignoreSourceAllyAbility,
isCritical, isCritical,
simulated, simulated,
); });
/** 25% damage debuff on moves hitting more than one non-fainted target (regardless of immunities) */ /** 25% damage debuff on moves hitting more than one non-fainted target (regardless of immunities) */
const { targets, multiple } = getMoveTargets(source, move.id); const { targets, multiple } = getMoveTargets(source, move.id);
@ -4564,211 +4601,36 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}; };
} }
/** /** Calculate whether the given move critically hits this pokemon
* Applies the results of a move to this pokemon * @param source - The {@linkcode Pokemon} using the move
* @param source The {@linkcode Pokemon} using the move * @param move - The {@linkcode Move} being used
* @param move The {@linkcode Move} being used * @param simulated - If `true`, suppresses changes to game state during calculation (defaults to `true`)
* @returns The {@linkcode HitResult} of the attack * @returns whether the move critically hits the pokemon
*/ */
apply(source: Pokemon, move: Move): HitResult { getCriticalHitResult(source: Pokemon, move: Move, simulated: boolean = true): boolean {
const defendingSide = this.isPlayer() const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
? ArenaTagSide.PLAYER const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide);
: ArenaTagSide.ENEMY; if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr(FixedDamageAttr)) {
const moveCategory = new NumberHolder(move.category); return false;
applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, moveCategory);
if (moveCategory.value === MoveCategory.STATUS) {
const cancelled = new BooleanHolder(false);
const typeMultiplier = this.getMoveEffectiveness(
source,
move,
false,
false,
cancelled,
);
if (!cancelled.value && typeMultiplier === 0) {
globalScene.queueMessage(
i18next.t("battle:hitResultNoEffect", {
pokemonName: getPokemonNameWithAffix(this),
}),
);
}
return typeMultiplier === 0 ? HitResult.NO_EFFECT : HitResult.STATUS;
} }
/** Determines whether the attack critically hits */ const isCritical = new BooleanHolder(false);
let isCritical: boolean;
const critOnly = new BooleanHolder(false); if (source.getTag(BattlerTagType.ALWAYS_CRIT)) {
const critAlways = source.getTag(BattlerTagType.ALWAYS_CRIT); isCritical.value = true;
applyMoveAttrs(CritOnlyAttr, source, this, move, critOnly); }
applyAbAttrs( applyMoveAttrs(CritOnlyAttr, source, this, move, isCritical);
ConditionalCritAbAttr, applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move);
source, if (!isCritical.value) {
null,
false,
critOnly,
this,
move,
);
if (critOnly.value || critAlways) {
isCritical = true;
} else {
const critChance = [24, 8, 2, 1][ const critChance = [24, 8, 2, 1][
Math.max(0, Math.min(this.getCritStage(source, move), 3)) Math.max(0, Math.min(this.getCritStage(source, move), 3))
]; ];
isCritical = isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance);
critChance === 1 || !globalScene.randBattleSeedInt(critChance);
} }
const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide); applyAbAttrs(BlockCritAbAttr, this, null, simulated, isCritical);
const blockCrit = new BooleanHolder(false);
applyAbAttrs(BlockCritAbAttr, this, null, false, blockCrit);
if (noCritTag || blockCrit.value || Overrides.NEVER_CRIT_OVERRIDE) {
isCritical = false;
}
/** return isCritical.value;
* Applies stat changes from {@linkcode move} and gives it to {@linkcode source}
* before damage calculation
*/
applyMoveAttrs(StatChangeBeforeDmgCalcAttr, source, this, move);
const {
cancelled,
result,
damage: dmg,
} = this.getAttackDamage(source, move, false, false, false, false, isCritical, false);
const typeBoost = source.findTag(
t =>
t instanceof TypeBoostTag && t.boostedType === source.getMoveType(move),
) as TypeBoostTag;
if (typeBoost?.oneUse) {
source.removeTag(typeBoost.tagType);
}
if (
cancelled ||
result === HitResult.IMMUNE ||
result === HitResult.NO_EFFECT
) {
source.stopMultiHit(this);
if (!cancelled) {
if (result === HitResult.IMMUNE) {
globalScene.queueMessage(
i18next.t("battle:hitResultImmune", {
pokemonName: getPokemonNameWithAffix(this),
}),
);
} else {
globalScene.queueMessage(
i18next.t("battle:hitResultNoEffect", {
pokemonName: getPokemonNameWithAffix(this),
}),
);
}
}
return result;
}
// In case of fatal damage, this tag would have gotten cleared before we could lapse it.
const destinyTag = this.getTag(BattlerTagType.DESTINY_BOND);
const grudgeTag = this.getTag(BattlerTagType.GRUDGE);
if (dmg) {
this.lapseTags(BattlerTagLapseType.HIT);
const substitute = this.getTag(SubstituteTag);
const isBlockedBySubstitute =
!!substitute && move.hitsSubstitute(source, this);
if (isBlockedBySubstitute) {
substitute.hp -= dmg;
}
if (!this.isPlayer() && dmg >= this.hp) {
globalScene.applyModifiers(EnemyEndureChanceModifier, false, this);
}
/**
* We explicitly require to ignore the faint phase here, as we want to show the messages
* about the critical hit and the super effective/not very effective messages before the faint phase.
*/
const damage = this.damageAndUpdate(isBlockedBySubstitute ? 0 : dmg,
{
result: result as DamageResult,
isCritical,
ignoreFaintPhase: true,
source
});
if (damage > 0) {
if (source.isPlayer()) {
globalScene.validateAchvs(DamageAchv, new NumberHolder(damage));
if (damage > globalScene.gameData.gameStats.highestDamage) {
globalScene.gameData.gameStats.highestDamage = damage;
}
}
source.turnData.totalDamageDealt += damage;
source.turnData.singleHitDamageDealt = damage;
this.turnData.damageTaken += damage;
this.battleData.hitCount++;
const attackResult = {
move: move.id,
result: result as DamageResult,
damage: damage,
critical: isCritical,
sourceId: source.id,
sourceBattlerIndex: source.getBattlerIndex(),
};
this.turnData.attacksReceived.unshift(attackResult);
if (source.isPlayer() && !this.isPlayer()) {
globalScene.applyModifiers(
DamageMoneyRewardModifier,
true,
source,
new NumberHolder(damage),
);
}
}
}
if (isCritical) {
globalScene.queueMessage(i18next.t("battle:hitResultCriticalHit"));
}
// want to include is.Fainted() in case multi hit move ends early, still want to render message
if (source.turnData.hitsLeft === 1 || this.isFainted()) {
switch (result) {
case HitResult.SUPER_EFFECTIVE:
globalScene.queueMessage(i18next.t("battle:hitResultSuperEffective"));
break;
case HitResult.NOT_VERY_EFFECTIVE:
globalScene.queueMessage(
i18next.t("battle:hitResultNotVeryEffective"),
);
break;
case HitResult.ONE_HIT_KO:
globalScene.queueMessage(i18next.t("battle:hitResultOneHitKO"));
break;
}
}
if (this.isFainted()) {
// set splice index here, so future scene queues happen before FaintedPhase
globalScene.setPhaseQueueSplice();
globalScene.unshiftPhase(
new FaintPhase(
this.getBattlerIndex(),
false,
source,
),
);
this.destroySubstitute();
this.lapseTag(BattlerTagType.COMMANDED);
}
return result;
} }
/** /**
@ -4832,7 +4694,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
/** /**
* Called by apply(), given the damage, adds a new DamagePhase and actually updates HP values, etc. * Given the damage, adds a new DamagePhase and update HP values, etc.
*
* Checks for 'Indirect' HitResults to account for Endure/Reviver Seed applying correctly * Checks for 'Indirect' HitResults to account for Endure/Reviver Seed applying correctly
* @param damage integer - passed to damage() * @param damage integer - passed to damage()
* @param result an enum if it's super effective, not very, etc. * @param result an enum if it's super effective, not very, etc.
@ -5135,8 +4998,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** /**
* Gets whether the given move is currently disabled for this Pokemon. * Gets whether the given move is currently disabled for this Pokemon.
* *
* @param {Moves} moveId {@linkcode Moves} ID of the move to check * @param moveId - The {@linkcode Moves} ID of the move to check
* @returns {boolean} `true` if the move is disabled for this Pokemon, otherwise `false` * @returns `true` if the move is disabled for this Pokemon, otherwise `false`
* *
* @see {@linkcode MoveRestrictionBattlerTag} * @see {@linkcode MoveRestrictionBattlerTag}
*/ */
@ -5147,9 +5010,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** /**
* Gets whether the given move is currently disabled for the user based on the player's target selection * Gets whether the given move is currently disabled for the user based on the player's target selection
* *
* @param {Moves} moveId {@linkcode Moves} ID of the move to check * @param moveId - The {@linkcode Moves} ID of the move to check
* @param {Pokemon} user {@linkcode Pokemon} the move user * @param user - The move user
* @param {Pokemon} target {@linkcode Pokemon} the target of the move * @param target - The target of the move
* *
* @returns {boolean} `true` if the move is disabled for this Pokemon due to the player's target selection * @returns {boolean} `true` if the move is disabled for this Pokemon due to the player's target selection
* *
@ -5179,10 +5042,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** /**
* Gets the {@link MoveRestrictionBattlerTag} that is restricting a move, if it exists. * Gets the {@link MoveRestrictionBattlerTag} that is restricting a move, if it exists.
* *
* @param {Moves} moveId {@linkcode Moves} ID of the move to check * @param moveId - {@linkcode Moves} ID of the move to check
* @param {Pokemon} user {@linkcode Pokemon} the move user, optional and used when the target is a factor in the move's restricted status * @param user - {@linkcode Pokemon} the move user, optional and used when the target is a factor in the move's restricted status
* @param {Pokemon} target {@linkcode Pokemon} the target of the move, optional and used when the target is a factor in the move's restricted status * @param target - {@linkcode Pokemon} the target of the move, optional and used when the target is a factor in the move's restricted status
* @returns {MoveRestrictionBattlerTag | null} the first tag on this Pokemon that restricts the move, or `null` if the move is not restricted. * @returns The first tag on this Pokemon that restricts the move, or `null` if the move is not restricted.
*/ */
getRestrictingTag( getRestrictingTag(
moveId: Moves, moveId: Moves,
@ -5244,20 +5107,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.summonData.moveQueue; return this.summonData.moveQueue;
} }
/**
* If this Pokemon is using a multi-hit move, cancels all subsequent strikes
* @param {Pokemon} target If specified, this only cancels subsequent strikes against the given target
*/
stopMultiHit(target?: Pokemon): void {
const effectPhase = globalScene.getCurrentPhase();
if (
effectPhase instanceof MoveEffectPhase &&
effectPhase.getUserPokemon() === this
) {
effectPhase.stopMultiHit(target);
}
}
changeForm(formChange: SpeciesFormChange): Promise<void> { changeForm(formChange: SpeciesFormChange): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
this.formIndex = Math.max( this.formIndex = Math.max(
@ -5287,13 +5136,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
sceneOverride?: BattleScene, sceneOverride?: BattleScene,
): AnySound { ): AnySound {
const scene = sceneOverride ?? globalScene; // TODO: is `sceneOverride` needed? const scene = sceneOverride ?? globalScene; // TODO: is `sceneOverride` needed?
const cry = this.getSpeciesForm().cry(soundConfig); const cry = this.getSpeciesForm(undefined, true).cry(soundConfig);
let duration = cry.totalDuration * 1000; let duration = cry.totalDuration * 1000;
if ( if (
this.fusionSpecies && this.fusionSpecies &&
this.getSpeciesForm() !== this.getFusionSpeciesForm() this.getSpeciesForm(undefined, true) !== this.getFusionSpeciesForm(undefined, true)
) { ) {
let fusionCry = this.getFusionSpeciesForm().cry(soundConfig, true); let fusionCry = this.getFusionSpeciesForm(undefined, true).cry(soundConfig, true);
duration = Math.min(duration, fusionCry.totalDuration * 1000); duration = Math.min(duration, fusionCry.totalDuration * 1000);
fusionCry.destroy(); fusionCry.destroy();
scene.time.delayedCall(fixedInt(Math.ceil(duration * 0.4)), () => { scene.time.delayedCall(fixedInt(Math.ceil(duration * 0.4)), () => {
@ -5303,7 +5152,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
cry, cry,
fixedInt(Math.ceil(duration * 0.2)), fixedInt(Math.ceil(duration * 0.2)),
); );
fusionCry = this.getFusionSpeciesForm().cry( fusionCry = this.getFusionSpeciesForm(undefined, true).cry(
Object.assign( Object.assign(
{ seek: Math.max(fusionCry.totalDuration * 0.4, 0) }, { seek: Math.max(fusionCry.totalDuration * 0.4, 0) },
soundConfig, soundConfig,
@ -5517,6 +5366,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
); );
} }
queueImmuneMessage(quiet: boolean, effect?: StatusEffect): void {
if (!effect || quiet) {
return;
}
const message = effect && this.status?.effect === effect
? getStatusEffectOverlapText(effect ?? StatusEffect.NONE, getPokemonNameWithAffix(this))
: i18next.t("abilityTriggers:moveImmunity", {
pokemonNameWithAffix: getPokemonNameWithAffix(this),
});
globalScene.queueMessage(message);
}
/** /**
* Checks if a status effect can be applied to the Pokemon. * Checks if a status effect can be applied to the Pokemon.
* *
@ -5535,6 +5396,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
): boolean { ): boolean {
if (effect !== StatusEffect.FAINT) { if (effect !== StatusEffect.FAINT) {
if (overrideStatus ? this.status?.effect === effect : this.status) { if (overrideStatus ? this.status?.effect === effect : this.status) {
this.queueImmuneMessage(quiet, effect);
return false; return false;
} }
if ( if (
@ -5542,18 +5404,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
!ignoreField && !ignoreField &&
globalScene.arena.terrain?.terrainType === TerrainType.MISTY globalScene.arena.terrain?.terrainType === TerrainType.MISTY
) { ) {
this.queueImmuneMessage(quiet, effect);
return false; return false;
} }
} }
if (
sourcePokemon &&
sourcePokemon !== this &&
this.isSafeguarded(sourcePokemon)
) {
return false;
}
const types = this.getTypes(true, true); const types = this.getTypes(true, true);
switch (effect) { switch (effect) {
@ -5582,17 +5437,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
return true; return true;
}); });
if (this.isOfType(PokemonType.POISON) || this.isOfType(PokemonType.STEEL)) { if (this.isOfType(PokemonType.POISON) || this.isOfType(PokemonType.STEEL)) {
if (poisonImmunity.includes(true)) { if (poisonImmunity.includes(true)) {
this.queueImmuneMessage(quiet, effect);
return false; return false;
} }
} }
break; break;
case StatusEffect.PARALYSIS: case StatusEffect.PARALYSIS:
if (this.isOfType(PokemonType.ELECTRIC)) { if (this.isOfType(PokemonType.ELECTRIC)) {
this.queueImmuneMessage(quiet, effect);
return false; return false;
} }
break; break;
@ -5601,6 +5458,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.isGrounded() && this.isGrounded() &&
globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC
) { ) {
this.queueImmuneMessage(quiet, effect);
return false; return false;
} }
break; break;
@ -5613,11 +5471,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
globalScene.arena.weather.weatherType, globalScene.arena.weather.weatherType,
)) ))
) { ) {
this.queueImmuneMessage(quiet, effect);
return false; return false;
} }
break; break;
case StatusEffect.BURN: case StatusEffect.BURN:
if (this.isOfType(PokemonType.FIRE)) { if (this.isOfType(PokemonType.FIRE)) {
this.queueImmuneMessage(quiet, effect);
return false; return false;
} }
break; break;
@ -5652,6 +5512,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false; return false;
} }
if (
sourcePokemon &&
sourcePokemon !== this &&
this.isSafeguarded(sourcePokemon)
) {
if(!quiet){
globalScene.queueMessage(
i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this)
}));
}
return false;
}
return true; return true;
} }
@ -5661,9 +5534,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
sourcePokemon: Pokemon | null = null, sourcePokemon: Pokemon | null = null,
turnsRemaining = 0, turnsRemaining = 0,
sourceText: string | null = null, sourceText: string | null = null,
overrideStatus?: boolean overrideStatus?: boolean,
quiet = true,
): boolean { ): boolean {
if (!this.canSetStatus(effect, asPhase, overrideStatus, sourcePokemon)) { if (!this.canSetStatus(effect, quiet, overrideStatus, sourcePokemon)) {
return false; return false;
} }
if (this.isFainted() && effect !== StatusEffect.FAINT) { if (this.isFainted() && effect !== StatusEffect.FAINT) {
@ -5675,7 +5549,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* cancel the attack's subsequent hits. * cancel the attack's subsequent hits.
*/ */
if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) { if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) {
this.stopMultiHit(); const currentPhase = globalScene.getCurrentPhase();
if (currentPhase instanceof MoveEffectPhase && currentPhase.getUserPokemon() === this) {
this.turnData.hitCount = 1;
this.turnData.hitsLeft = 1;
}
} }
if (asPhase) { if (asPhase) {
@ -6587,7 +6465,7 @@ export class PlayerPokemon extends Pokemon {
this.leaveField(switchType === SwitchType.SWITCH); this.leaveField(switchType === SwitchType.SWITCH);
globalScene.ui.setMode( globalScene.ui.setMode(
Mode.PARTY, UiMode.PARTY,
PartyUiMode.FAINT_SWITCH, PartyUiMode.FAINT_SWITCH,
this.getFieldIndex(), this.getFieldIndex(),
(slotIndex: number, option: PartyOption) => { (slotIndex: number, option: PartyOption) => {
@ -6605,7 +6483,7 @@ export class PlayerPokemon extends Pokemon {
MoveEndPhase, MoveEndPhase,
); );
} }
globalScene.ui.setMode(Mode.MESSAGE).then(resolve); globalScene.ui.setMode(UiMode.MESSAGE).then(resolve);
}, },
PartyUiHandler.FilterNonFainted, PartyUiHandler.FilterNonFainted,
); );
@ -7154,6 +7032,15 @@ export class EnemyPokemon extends Pokemon {
} }
speciesId = prevolution; speciesId = prevolution;
} }
if (this.hasTrainer() && globalScene.currentBattle) {
const { waveIndex } = globalScene.currentBattle;
const ivs: number[] = [];
while (ivs.length < 6) {
ivs.push(this.randSeedIntRange(Math.floor(waveIndex / 10), 31));
}
this.ivs = ivs;
}
} }
this.aiType = this.aiType =
@ -7163,8 +7050,8 @@ export class EnemyPokemon extends Pokemon {
initBattleInfo(): void { initBattleInfo(): void {
if (!this.battleInfo) { if (!this.battleInfo) {
this.battleInfo = new EnemyBattleInfo(); this.battleInfo = new EnemyBattleInfo();
this.battleInfo.updateBossSegments(this);
this.battleInfo.initInfo(this); this.battleInfo.initInfo(this);
this.battleInfo.updateBossSegments(this);
} else { } else {
this.battleInfo.updateBossSegments(this); this.battleInfo.updateBossSegments(this);
} }
@ -7310,14 +7197,15 @@ export class EnemyPokemon extends Pokemon {
].includes(move.id); ].includes(move.id);
return ( return (
doesNotFail && doesNotFail &&
p.getAttackDamage( p.getAttackDamage({
this, source: this,
move, move,
!p.battleData.abilityRevealed, ignoreAbility: !p.battleData.abilityRevealed,
false, ignoreSourceAbility: false,
!p.getAlly()?.battleData.abilityRevealed, ignoreAllyAbility: !p.getAlly()?.battleData.abilityRevealed,
false, ignoreSourceAllyAbility: false,
isCritical, isCritical,
}
).damage >= p.hp ).damage >= p.hp
); );
}) })

View File

@ -11,7 +11,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
import { TrainerPoolTier } from "#enums/trainer-pool-tier"; import { TrainerPoolTier } from "#enums/trainer-pool-tier";
import { TeraAIMode } from "#enums/tera-ai-mode"; import { TeraAIMode } from "#enums/tera-ai-mode";
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import { randSeedWeightedItem, randSeedItem, randSeedInt } from "#app/utils"; import { randSeedWeightedItem, randSeedItem, randSeedInt } from "#app/utils/common";
import type { PersistentModifier } from "#app/modifier/modifier"; import type { PersistentModifier } from "#app/modifier/modifier";
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import { getIsInitialized, initI18n } from "#app/plugins/i18n";
@ -223,6 +223,13 @@ export default class Trainer extends Phaser.GameObjects.Container {
return this.config.doubleOnly || this.variant === TrainerVariant.DOUBLE; return this.config.doubleOnly || this.variant === TrainerVariant.DOUBLE;
} }
/**
* Return whether the trainer is a duo, like Tate & Liza
*/
isPartner(): boolean {
return this.variant === TrainerVariant.DOUBLE;
}
getMixedBattleBgm(): string { getMixedBattleBgm(): string {
return this.config.mixedBattleBgm; return this.config.mixedBattleBgm;
} }

View File

@ -7,12 +7,13 @@ import type PokemonSpecies from "./data/pokemon-species";
import { allSpecies } from "./data/pokemon-species"; import { allSpecies } from "./data/pokemon-species";
import type { Arena } from "./field/arena"; import type { Arena } from "./field/arena";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { randSeedInt, randSeedItem } from "#app/utils"; import { randSeedInt, randSeedItem } from "#app/utils/common";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { Challenges } from "./enums/challenges"; import { Challenges } from "./enums/challenges";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { getDailyStartingBiome } from "./data/daily-run"; import { getDailyStartingBiome } from "./data/daily-run";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES } from "./constants";
export enum GameModes { export enum GameModes {
CLASSIC, CLASSIC,
@ -36,10 +37,6 @@ interface GameModeConfig {
hasMysteryEncounters?: boolean; hasMysteryEncounters?: boolean;
} }
// Describes min and max waves for MEs in specific game modes
export const CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180];
export const CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180];
export class GameMode implements GameModeConfig { export class GameMode implements GameModeConfig {
public modeId: GameModes; public modeId: GameModes;
public isClassic: boolean; public isClassic: boolean;

View File

@ -0,0 +1 @@
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";

View File

@ -0,0 +1,4 @@
export const starterColors: StarterColors = {};
interface StarterColors {
[key: string]: [string, string];
}

View File

@ -1,11 +1,11 @@
import Phaser from "phaser"; import Phaser from "phaser";
import { deepCopy, getEnumValues } from "#app/utils"; import { deepCopy, getEnumValues } from "#app/utils/common";
import pad_generic from "./configs/inputs/pad_generic"; import pad_generic from "./configs/inputs/pad_generic";
import pad_unlicensedSNES from "./configs/inputs/pad_unlicensedSNES"; import pad_unlicensedSNES from "./configs/inputs/pad_unlicensedSNES";
import pad_xbox360 from "./configs/inputs/pad_xbox360"; import pad_xbox360 from "./configs/inputs/pad_xbox360";
import pad_dualshock from "./configs/inputs/pad_dualshock"; import pad_dualshock from "./configs/inputs/pad_dualshock";
import pad_procon from "./configs/inputs/pad_procon"; import pad_procon from "./configs/inputs/pad_procon";
import { Mode } from "./ui/ui"; import { UiMode } from "#enums/ui-mode";
import type SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler"; import type SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler";
import type SettingsKeyboardUiHandler from "./ui/settings/settings-keyboard-ui-handler"; import type SettingsKeyboardUiHandler from "./ui/settings/settings-keyboard-ui-handler";
import cfg_keyboard_qwerty from "./configs/inputs/cfg_keyboard_qwerty"; import cfg_keyboard_qwerty from "./configs/inputs/cfg_keyboard_qwerty";
@ -235,7 +235,7 @@ export class InputsController {
if (gamepadName) { if (gamepadName) {
this.selectedDevice[Device.GAMEPAD] = gamepadName.toLowerCase(); this.selectedDevice[Device.GAMEPAD] = gamepadName.toLowerCase();
} }
const handler = globalScene.ui?.handlers[Mode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler; const handler = globalScene.ui?.handlers[UiMode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler;
handler?.updateChosenGamepadDisplay(); handler?.updateChosenGamepadDisplay();
} }
@ -248,7 +248,7 @@ export class InputsController {
if (layoutKeyboard) { if (layoutKeyboard) {
this.selectedDevice[Device.KEYBOARD] = layoutKeyboard.toLowerCase(); this.selectedDevice[Device.KEYBOARD] = layoutKeyboard.toLowerCase();
} }
const handler = globalScene.ui?.handlers[Mode.SETTINGS_KEYBOARD] as SettingsKeyboardUiHandler; const handler = globalScene.ui?.handlers[UiMode.SETTINGS_KEYBOARD] as SettingsKeyboardUiHandler;
handler?.updateChosenKeyboardDisplay(); handler?.updateChosenKeyboardDisplay();
} }
@ -296,7 +296,7 @@ export class InputsController {
globalScene.gameData?.saveMappingConfigs(gamepadID, this.configs[gamepadID]); globalScene.gameData?.saveMappingConfigs(gamepadID, this.configs[gamepadID]);
} }
this.lastSource = "gamepad"; this.lastSource = "gamepad";
const handler = globalScene.ui?.handlers[Mode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler; const handler = globalScene.ui?.handlers[UiMode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler;
handler?.updateChosenGamepadDisplay(); handler?.updateChosenGamepadDisplay();
} }
@ -406,7 +406,7 @@ export class InputsController {
this.lastSource = "gamepad"; this.lastSource = "gamepad";
if ( if (
!this.selectedDevice[Device.GAMEPAD] || !this.selectedDevice[Device.GAMEPAD] ||
(globalScene.ui.getMode() !== Mode.GAMEPAD_BINDING && (globalScene.ui.getMode() !== UiMode.GAMEPAD_BINDING &&
this.selectedDevice[Device.GAMEPAD] !== pad.id.toLowerCase()) this.selectedDevice[Device.GAMEPAD] !== pad.id.toLowerCase())
) { ) {
this.setChosenGamepad(pad.id); this.setChosenGamepad(pad.id);

View File

@ -4,7 +4,7 @@ import CacheBustedLoaderPlugin from "#app/plugins/cache-busted-loader-plugin";
import { SceneBase } from "#app/scene-base"; import { SceneBase } from "#app/scene-base";
import { WindowVariant, getWindowVariantSuffix } from "#app/ui/ui-theme"; import { WindowVariant, getWindowVariantSuffix } from "#app/ui/ui-theme";
import { isMobile } from "#app/touch-controls"; import { isMobile } from "#app/touch-controls";
import { localPing, getEnumValues, hasAllLocalizedSprites, getEnumKeys } from "#app/utils"; import { localPing, getEnumValues, hasAllLocalizedSprites, getEnumKeys } from "#app/utils/common";
import { initPokemonPrevolutions, initPokemonStarters } from "#app/data/balance/pokemon-evolutions"; import { initPokemonPrevolutions, initPokemonStarters } from "#app/data/balance/pokemon-evolutions";
import { initBiomes } from "#app/data/balance/biomes"; import { initBiomes } from "#app/data/balance/biomes";
import { initEggMoves } from "#app/data/balance/egg-moves"; import { initEggMoves } from "#app/data/balance/egg-moves";

Some files were not shown because too many files have changed in this diff Show More