mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-05 16:02:20 +02:00
Merge branch 'beta' into My-first-Variant-implementation-
This commit is contained in:
commit
5651df76c2
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@ -65,7 +65,7 @@ Do the reviewers need to do something special in order to test your changes?
|
||||
- [ ] The PR is self-contained and cannot be split into smaller PRs?
|
||||
- [ ] Have I provided a clear explanation of the changes?
|
||||
- [ ] Have I tested the changes manually?
|
||||
- [ ] Are all unit tests still passing? (`npm run test`)
|
||||
- [ ] Are all unit tests still passing? (`npm run test:silent`)
|
||||
- [ ] Have I created new automated tests (`npm run create-test`) or updated existing tests related to the PR's changes?
|
||||
- [ ] Have I provided screenshots/videos of the changes (if applicable)?
|
||||
- [ ] Have I made sure that any UI change works for both UI themes (default and legacy)?
|
||||
|
20
biome.jsonc
20
biome.jsonc
@ -38,6 +38,9 @@
|
||||
"src/data/balance/tms.ts"
|
||||
]
|
||||
},
|
||||
|
||||
// While it'd be nice to enable consistent sorting, enabling this causes issues due to circular import resolution order
|
||||
// TODO: Remove if we ever get down to 0 circular imports
|
||||
"organizeImports": { "enabled": false },
|
||||
"linter": {
|
||||
"ignore": [
|
||||
@ -55,13 +58,13 @@
|
||||
},
|
||||
"style": {
|
||||
"noVar": "error",
|
||||
"useEnumInitializers": "off",
|
||||
"useEnumInitializers": "off", // large enums like Moves/Species would make this cumbersome
|
||||
"useBlockStatements": "error",
|
||||
"useConst": "error",
|
||||
"useImportType": "error",
|
||||
"noNonNullAssertion": "off", // TODO: Turn this on ASAP and fix all non-null assertions
|
||||
"noNonNullAssertion": "off", // TODO: Turn this on ASAP and fix all non-null assertions in non-test files
|
||||
"noParameterAssign": "off",
|
||||
"useExponentiationOperator": "off",
|
||||
"useExponentiationOperator": "off", // Too typo-prone and easy to mixup with standard multiplication (* vs **)
|
||||
"useDefaultParameterLast": "off", // TODO: Fix spots in the codebase where this flag would be triggered, and then enable
|
||||
"useSingleVarDeclarator": "off",
|
||||
"useNodejsImportProtocol": "off",
|
||||
@ -70,17 +73,20 @@
|
||||
},
|
||||
"suspicious": {
|
||||
"noDoubleEquals": "error",
|
||||
// While this would be a nice rule to enable, the current structure of the codebase makes this infeasible
|
||||
// due to being used for move/ability `args` params and save data-related code.
|
||||
// This can likely be enabled for all non-utils files once these are eventually reworked, but until then we leave it off.
|
||||
"noExplicitAny": "off",
|
||||
"noAssignInExpressions": "off",
|
||||
"noPrototypeBuiltins": "off",
|
||||
"noFallthroughSwitchClause": "off",
|
||||
"noImplicitAnyLet": "info", // TODO: Refactor and make this an error
|
||||
"noRedeclare": "off", // TODO: Refactor and make this an error
|
||||
"noFallthroughSwitchClause": "error", // Prevents accidental automatic fallthroughs in switch cases (use disable comment if needed)
|
||||
"noImplicitAnyLet": "warn", // TODO: Refactor and make this an error
|
||||
"noRedeclare": "info", // TODO: Refactor and make this an error
|
||||
"noGlobalIsNan": "off",
|
||||
"noAsyncPromiseExecutor": "warn" // TODO: Refactor and make this an error
|
||||
},
|
||||
"complexity": {
|
||||
"noExcessiveCognitiveComplexity": "warn",
|
||||
"noExcessiveCognitiveComplexity": "warn", // TODO: Refactor and make this an error
|
||||
"useLiteralKeys": "off",
|
||||
"noForEach": "off", // Foreach vs for of is not that simple.
|
||||
"noUselessSwitchCase": "off", // Explicit > Implicit
|
||||
|
@ -1,40 +1,34 @@
|
||||
# ESLint
|
||||
# Biome
|
||||
|
||||
## Key Features
|
||||
|
||||
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**:
|
||||
- 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
|
||||
npx eslint --fix . or npm run eslint
|
||||
npx @biomejs/biome --write
|
||||
```
|
||||
|
||||
- Running this command will lint all files in the repository.
|
||||
|
||||
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**:
|
||||
- **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`).
|
||||
## Summary of Biome Rules
|
||||
|
||||
2. **TypeScript-Specific Rules**:
|
||||
- **Semicolons**:
|
||||
- Enforce semicolons for TypeScript-specific syntax (`@typescript-eslint/semi`).
|
||||
- Disallow unnecessary semicolons (`@typescript-eslint/no-extra-semi`).
|
||||
We use the [recommended ruleset](https://biomejs.dev/linter/rules/) for Biome, with some customizations to better suit our project's needs.
|
||||
|
||||
## 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.
|
||||
- **Code Quality**: Helps catch potential errors and improve overall code quality.
|
||||
- **Readability**: Makes the codebase easier to read and maintain.
|
||||
Some things to consider:
|
||||
|
||||
- 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.
|
||||
|
@ -1,9 +1,10 @@
|
||||
import tseslint from "@typescript-eslint/eslint-plugin";
|
||||
/** @ts-check */
|
||||
import tseslint from "typescript-eslint";
|
||||
import stylisticTs from "@stylistic/eslint-plugin-ts";
|
||||
import parser from "@typescript-eslint/parser";
|
||||
import importX from "eslint-plugin-import-x";
|
||||
|
||||
export default [
|
||||
export default tseslint.config(
|
||||
{
|
||||
name: "eslint-config",
|
||||
files: ["src/**/*.{ts,tsx,js,jsx}", "test/**/*.{ts,tsx,js,jsx}"],
|
||||
@ -14,12 +15,11 @@ export default [
|
||||
plugins: {
|
||||
"import-x": importX,
|
||||
"@stylistic/ts": stylisticTs,
|
||||
"@typescript-eslint": tseslint,
|
||||
"@typescript-eslint": tseslint.plugin,
|
||||
},
|
||||
rules: {
|
||||
"prefer-const": "error", // Enforces the use of `const` for variables that are never reassigned
|
||||
"no-undef": "off", // Disables the rule that disallows the use of undeclared variables (TypeScript handles this)
|
||||
"no-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax
|
||||
"no-extra-semi": "error", // Disallows unnecessary semicolons for TypeScript-specific syntax
|
||||
"import-x/extensions": ["error", "never", { json: "always" }], // Enforces no extension for imports unless json
|
||||
},
|
||||
},
|
||||
@ -33,11 +33,11 @@ export default [
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": tseslint,
|
||||
"@typescript-eslint": tseslint.plugin,
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-floating-promises": "error", // Require Promise-like statements to be handled appropriately. - https://typescript-eslint.io/rules/no-floating-promises/
|
||||
"@typescript-eslint/no-misused-promises": "error", // Disallow Promises in places not designed to handle them. - https://typescript-eslint.io/rules/no-misused-promises/
|
||||
},
|
||||
},
|
||||
];
|
||||
);
|
||||
|
18
package-lock.json
generated
18
package-lock.json
generated
@ -18,8 +18,8 @@
|
||||
"i18next-korean-postposition-processor": "^1.0.0",
|
||||
"json-stable-stringify": "^1.2.0",
|
||||
"jszip": "^3.10.1",
|
||||
"phaser": "^3.70.0",
|
||||
"phaser3-rex-plugins": "^1.80.14"
|
||||
"phaser": "^3.88.2",
|
||||
"phaser3-rex-plugins": "^1.80.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
@ -48,7 +48,7 @@
|
||||
"vitest-canvas-mock": "^0.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
"node": ">=22.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
@ -6227,18 +6227,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/phaser": {
|
||||
"version": "3.80.1",
|
||||
"resolved": "https://registry.npmjs.org/phaser/-/phaser-3.80.1.tgz",
|
||||
"integrity": "sha512-VQGAWoDOkEpAWYkI+PUADv5Ql+SM0xpLuAMBJHz9tBcOLqjJ2wd8bUhxJgOqclQlLTg97NmMd9MhS75w16x1Cw==",
|
||||
"version": "3.88.2",
|
||||
"resolved": "https://registry.npmjs.org/phaser/-/phaser-3.88.2.tgz",
|
||||
"integrity": "sha512-UBgd2sAFuRJbF2xKaQ5jpMWB8oETncChLnymLGHcrnT53vaqiGrQWbUKUDBawKLm24sghjKo4Bf+/xfv8espZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/phaser3-rex-plugins": {
|
||||
"version": "1.80.14",
|
||||
"resolved": "https://registry.npmjs.org/phaser3-rex-plugins/-/phaser3-rex-plugins-1.80.14.tgz",
|
||||
"integrity": "sha512-eHi3VgryO9umNu6D1yQU5IS6tH4TyC2Y6RgJ495nNp37X2fdYnmYpBfgFg+YaumvtaoOvCkUVyi/YqWNPf2X2A==",
|
||||
"version": "1.80.15",
|
||||
"resolved": "https://registry.npmjs.org/phaser3-rex-plugins/-/phaser3-rex-plugins-1.80.15.tgz",
|
||||
"integrity": "sha512-Ur973N1W5st6XEYBcJko8eTcEbdDHMM+m7VqvT3j/EJeJwYyJ3bVb33JJDsFgefk3A2iAz2itP/UY7CzxJOJVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dagre": "^0.8.5",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "pokemon-rogue-battle",
|
||||
"private": true,
|
||||
"version": "1.8.4",
|
||||
"version": "1.8.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
@ -9,7 +9,7 @@
|
||||
"build": "vite build",
|
||||
"build:beta": "vite build --mode beta",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest run",
|
||||
"test": "vitest run --no-isolate",
|
||||
"test:cov": "vitest run --coverage --no-isolate",
|
||||
"test:watch": "vitest watch --coverage --no-isolate",
|
||||
"test:silent": "vitest run --silent --no-isolate",
|
||||
@ -63,8 +63,8 @@
|
||||
"i18next-korean-postposition-processor": "^1.0.0",
|
||||
"json-stable-stringify": "^1.2.0",
|
||||
"jszip": "^3.10.1",
|
||||
"phaser": "^3.70.0",
|
||||
"phaser3-rex-plugins": "^1.80.14"
|
||||
"phaser": "^3.88.2",
|
||||
"phaser3-rex-plugins": "^1.80.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
|
@ -516,8 +516,36 @@
|
||||
"trimmed": true,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "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": {
|
||||
"app": "https://www.aseprite.org/",
|
||||
"version": "1.3.7-dev",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.6 KiB |
16524
public/images/items.json
16524
public/images/items.json
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 58 KiB |
Binary file not shown.
Before Width: | Height: | Size: 915 B After Width: | Height: | Size: 884 B |
@ -1 +1 @@
|
||||
Subproject commit e98f0eb9c2022bc78b53f0444424c636498e725a
|
||||
Subproject commit 833dc40ec7409031fcea147ccbc45ec9c0ba0213
|
@ -151,7 +151,6 @@ import { NextEncounterPhase } from "#app/phases/next-encounter-phase";
|
||||
import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase";
|
||||
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-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 { SummonPhase } from "#app/phases/summon-phase";
|
||||
import { SwitchPhase } from "#app/phases/switch-phase";
|
||||
@ -1298,6 +1297,16 @@ export default class BattleScene extends SceneBase {
|
||||
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?
|
||||
newBattle(
|
||||
waveIndex?: number,
|
||||
@ -1385,9 +1394,9 @@ export default class BattleScene extends SceneBase {
|
||||
if (double === undefined && newWaveIndex > 1) {
|
||||
if (newBattleType === BattleType.WILD && !this.gameMode.isWaveFinal(newWaveIndex)) {
|
||||
newDouble = !randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField));
|
||||
} else if (newBattleType === BattleType.TRAINER) {
|
||||
newDouble = newTrainer?.variant === TrainerVariant.DOUBLE;
|
||||
}
|
||||
} else if (double === undefined && newBattleType === BattleType.TRAINER) {
|
||||
newDouble = newTrainer?.variant === TrainerVariant.DOUBLE;
|
||||
} else if (!battleConfig) {
|
||||
newDouble = !!double;
|
||||
}
|
||||
@ -1461,12 +1470,7 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
|
||||
if (!waveIndex && lastBattle) {
|
||||
const isWaveIndexMultipleOfTen = !(lastBattle.waveIndex % 10);
|
||||
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 isNewBiome = this.isNewBiome(lastBattle);
|
||||
const resetArenaState =
|
||||
isNewBiome ||
|
||||
[BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.currentBattle.battleType) ||
|
||||
@ -1515,7 +1519,6 @@ export default class BattleScene extends SceneBase {
|
||||
if (!this.gameMode.hasRandomBiomes && !isNewBiome) {
|
||||
this.pushPhase(new NextEncounterPhase());
|
||||
} else {
|
||||
this.pushPhase(new SelectBiomePhase());
|
||||
this.pushPhase(new NewBiomeEncounterPhase());
|
||||
|
||||
const newMaxExpLevel = this.getMaxExpLevel();
|
||||
|
@ -31,29 +31,7 @@ import type { CustomModifierSettings } from "#app/modifier/modifier-type";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
|
||||
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,
|
||||
}
|
||||
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
||||
|
||||
export enum BattlerIndex {
|
||||
ATTACKER = -1,
|
||||
|
@ -31,6 +31,7 @@ const cfg_keyboard_qwerty = {
|
||||
KEY_X: Phaser.Input.Keyboard.KeyCodes.X,
|
||||
KEY_Y: Phaser.Input.Keyboard.KeyCodes.Y,
|
||||
KEY_Z: Phaser.Input.Keyboard.KeyCodes.Z,
|
||||
|
||||
KEY_0: Phaser.Input.Keyboard.KeyCodes.ZERO,
|
||||
KEY_1: Phaser.Input.Keyboard.KeyCodes.ONE,
|
||||
KEY_2: Phaser.Input.Keyboard.KeyCodes.TWO,
|
||||
@ -41,11 +42,7 @@ const cfg_keyboard_qwerty = {
|
||||
KEY_7: Phaser.Input.Keyboard.KeyCodes.SEVEN,
|
||||
KEY_8: Phaser.Input.Keyboard.KeyCodes.EIGHT,
|
||||
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_F2: Phaser.Input.Keyboard.KeyCodes.F2,
|
||||
KEY_F3: Phaser.Input.Keyboard.KeyCodes.F3,
|
||||
@ -58,24 +55,41 @@ const cfg_keyboard_qwerty = {
|
||||
KEY_F10: Phaser.Input.Keyboard.KeyCodes.F10,
|
||||
KEY_F11: Phaser.Input.Keyboard.KeyCodes.F11,
|
||||
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_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_MINUS: Phaser.Input.Keyboard.KeyCodes.NUMPAD_SUBTRACT, // Assuming numpad minus
|
||||
KEY_QUOTATION: Phaser.Input.Keyboard.KeyCodes.QUOTES,
|
||||
KEY_SHIFT: Phaser.Input.Keyboard.KeyCodes.SHIFT,
|
||||
|
||||
KEY_SPACE: Phaser.Input.Keyboard.KeyCodes.SPACE,
|
||||
KEY_TAB: Phaser.Input.Keyboard.KeyCodes.TAB,
|
||||
KEY_TILDE: Phaser.Input.Keyboard.KeyCodes.BACKTICK,
|
||||
|
||||
KEY_ARROW_UP: Phaser.Input.Keyboard.KeyCodes.UP,
|
||||
KEY_ARROW_DOWN: Phaser.Input.Keyboard.KeyCodes.DOWN,
|
||||
KEY_ARROW_LEFT: Phaser.Input.Keyboard.KeyCodes.LEFT,
|
||||
KEY_ARROW_RIGHT: Phaser.Input.Keyboard.KeyCodes.RIGHT,
|
||||
|
||||
KEY_LEFT_BRACKET: Phaser.Input.Keyboard.KeyCodes.OPEN_BRACKET,
|
||||
KEY_RIGHT_BRACKET: Phaser.Input.Keyboard.KeyCodes.CLOSED_BRACKET,
|
||||
|
||||
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_ALT: Phaser.Input.Keyboard.KeyCodes.ALT,
|
||||
},
|
||||
@ -160,6 +174,10 @@ const cfg_keyboard_qwerty = {
|
||||
KEY_RIGHT_BRACKET: "RIGHT_BRACKET.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_ALT: "ALT.png",
|
||||
|
@ -9,3 +9,8 @@ export const SESSION_ID_COOKIE_NAME: string = "pokerogue_sessionId";
|
||||
|
||||
/** Max value for an integer attribute in {@linkcode SystemSaveData} */
|
||||
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;
|
||||
|
@ -72,6 +72,7 @@ import type { AbAttrCondition, PokemonDefendCondition, PokemonStatStageChangeCon
|
||||
import type { BattlerIndex } from "#app/battle";
|
||||
import type Move from "#app/data/moves/move";
|
||||
import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag";
|
||||
import { SelectBiomePhase } from "#app/phases/select-biome-phase";
|
||||
|
||||
export class BlockRecoilDamageAttr extends AbAttr {
|
||||
constructor() {
|
||||
@ -653,8 +654,8 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr {
|
||||
*/
|
||||
export class ReverseDrainAbAttr extends PostDefendAbAttr {
|
||||
|
||||
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);
|
||||
override canApplyPostDefend(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _attacker: Pokemon, move: Move, _hitResult: HitResult | null, args: any[]): boolean {
|
||||
return move.hasAttr(HitHealAttr);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -693,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 {
|
||||
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 {
|
||||
@ -734,7 +735,7 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate);
|
||||
const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1];
|
||||
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 {
|
||||
@ -757,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 {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -779,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 {
|
||||
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 {
|
||||
@ -796,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 {
|
||||
this.type = attacker.getMoveType(move);
|
||||
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 {
|
||||
@ -823,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 {
|
||||
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 {
|
||||
@ -847,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 {
|
||||
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
|
||||
&& (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);
|
||||
}
|
||||
|
||||
@ -887,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 {
|
||||
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 {
|
||||
@ -908,10 +909,6 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
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 {
|
||||
if (!simulated) {
|
||||
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages));
|
||||
@ -934,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 {
|
||||
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 {
|
||||
@ -993,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 {
|
||||
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));
|
||||
}
|
||||
|
||||
@ -1011,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 {
|
||||
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 {
|
||||
@ -1037,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 {
|
||||
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) {
|
||||
attacker.setTempAbility(allAbilities[this.ability]);
|
||||
}
|
||||
@ -1066,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 {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -1770,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 {
|
||||
if (
|
||||
super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args)
|
||||
&& !(pokemon !== attacker && move.hitsSubstitute(attacker, pokemon))
|
||||
&& (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)
|
||||
) {
|
||||
@ -1837,8 +1833,7 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
|
||||
if (
|
||||
!simulated &&
|
||||
hitResult < HitResult.NO_EFFECT &&
|
||||
(!this.condition || this.condition(pokemon, attacker, move)) &&
|
||||
!move.hitsSubstitute(attacker, pokemon)
|
||||
(!this.condition || this.condition(pokemon, attacker, move))
|
||||
) {
|
||||
const heldItems = this.getTargetHeldItems(attacker).filter((i) => i.isTransferable);
|
||||
if (heldItems.length) {
|
||||
@ -3177,6 +3172,7 @@ export class PreSetStatusAbAttr extends AbAttr {
|
||||
*/
|
||||
export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr {
|
||||
protected immuneEffects: StatusEffect[];
|
||||
private lastEffect: StatusEffect;
|
||||
|
||||
/**
|
||||
* @param immuneEffects - The status effects to which the Pokémon is immune.
|
||||
@ -3202,6 +3198,7 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr {
|
||||
*/
|
||||
override applyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: BooleanHolder, args: any[]): void {
|
||||
cancelled.value = true;
|
||||
this.lastEffect = effect;
|
||||
}
|
||||
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
@ -3209,7 +3206,7 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr {
|
||||
i18next.t("abilityTriggers:statusEffectImmunityWithName", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
abilityName,
|
||||
statusEffectName: getStatusEffectDescriptor(args[0] as StatusEffect)
|
||||
statusEffectName: getStatusEffectDescriptor(this.lastEffect)
|
||||
}) :
|
||||
i18next.t("abilityTriggers:statusEffectImmunity", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
@ -3411,8 +3408,12 @@ export class BlockCritAbAttr extends AbAttr {
|
||||
super(false);
|
||||
}
|
||||
|
||||
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder, args: any[]): void {
|
||||
(args[0] as BooleanHolder).value = true;
|
||||
/**
|
||||
* Apply the block crit ability by setting the value in the provided boolean holder to false
|
||||
* @param args - [0] is a boolean holder representing whether the attack can crit
|
||||
*/
|
||||
override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder, args: [BooleanHolder, ...any]): void {
|
||||
(args[0]).value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5063,6 +5064,8 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC
|
||||
/**
|
||||
* Takes no damage from the first hit of a damaging move.
|
||||
* This is used in the Disguise and Ice Face abilities.
|
||||
*
|
||||
* Does not apply to a user's substitute
|
||||
* @extends ReceivedMoveDamageMultiplierAbAttr
|
||||
*/
|
||||
export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
|
||||
@ -5487,6 +5490,11 @@ class ForceSwitchOutHelper {
|
||||
|
||||
if (switchOutTarget.hp) {
|
||||
globalScene.pushPhase(new BattleEndPhase(false));
|
||||
|
||||
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
|
||||
globalScene.pushPhase(new SelectBiomePhase());
|
||||
}
|
||||
|
||||
globalScene.pushPhase(new NewBattlePhase());
|
||||
}
|
||||
}
|
||||
@ -7225,7 +7233,7 @@ export function initAbilities() {
|
||||
new Ability(Abilities.CURIOUS_MEDICINE, 8)
|
||||
.attr(PostSummonClearAllyStatStagesAbAttr),
|
||||
new Ability(Abilities.TRANSISTOR, 8)
|
||||
.attr(MoveTypePowerBoostAbAttr, PokemonType.ELECTRIC),
|
||||
.attr(MoveTypePowerBoostAbAttr, PokemonType.ELECTRIC, 1.3),
|
||||
new Ability(Abilities.DRAGONS_MAW, 8)
|
||||
.attr(MoveTypePowerBoostAbAttr, PokemonType.DRAGON),
|
||||
new Ability(Abilities.CHILLING_NEIGH, 8)
|
||||
|
@ -7,7 +7,7 @@ import { MoveTarget } from "#enums/MoveTarget";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
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 type { BattlerIndex } from "#app/battle";
|
||||
import {
|
||||
@ -335,7 +335,7 @@ export class ConditionalProtectTag extends ArenaTag {
|
||||
* @param arena the {@linkcode Arena} containing this tag
|
||||
* @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 attacker the attacking {@linkcode Pokemon}
|
||||
* @param _attacker the attacking {@linkcode Pokemon}
|
||||
* @param defender the defending {@linkcode Pokemon}
|
||||
* @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
|
||||
@ -345,7 +345,7 @@ export class ConditionalProtectTag extends ArenaTag {
|
||||
arena: Arena,
|
||||
simulated: boolean,
|
||||
isProtected: BooleanHolder,
|
||||
attacker: Pokemon,
|
||||
_attacker: Pokemon,
|
||||
defender: Pokemon,
|
||||
moveId: Moves,
|
||||
ignoresProtectBypass: BooleanHolder,
|
||||
@ -354,8 +354,6 @@ export class ConditionalProtectTag extends ArenaTag {
|
||||
if (!isProtected.value) {
|
||||
isProtected.value = true;
|
||||
if (!simulated) {
|
||||
attacker.stopMultiHit(defender);
|
||||
|
||||
new CommonBattleAnim(CommonAnim.PROTECT, defender).play();
|
||||
globalScene.queueMessage(
|
||||
i18next.t("arenaTag:conditionalProtectApply", {
|
||||
@ -899,7 +897,7 @@ export class DelayedAttackTag extends ArenaTag {
|
||||
|
||||
if (!ret) {
|
||||
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?
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ export const speciesEggMoves = {
|
||||
[Species.WEEDLE]: [ Moves.THOUSAND_ARROWS, Moves.NOXIOUS_TORQUE, Moves.ATTACK_ORDER, Moves.VICTORY_DANCE ],
|
||||
[Species.PIDGEY]: [ Moves.BLEAKWIND_STORM, Moves.SANDSEAR_STORM, Moves.CALM_MIND, Moves.BOOMBURST ],
|
||||
[Species.RATTATA]: [ Moves.HYPER_FANG, Moves.PSYCHIC_FANGS, Moves.FIRE_FANG, Moves.EXTREME_SPEED ],
|
||||
[Species.SPEAROW]: [ Moves.FLOATY_FALL, Moves.HYPER_DRILL, Moves.TIDY_UP, Moves.TRIPLE_ARROWS ],
|
||||
[Species.SPEAROW]: [ Moves.FLOATY_FALL, Moves.EXTREME_SPEED, Moves.KNOCK_OFF, Moves.TRIPLE_ARROWS ],
|
||||
[Species.EKANS]: [ Moves.NOXIOUS_TORQUE, Moves.DRAGON_DANCE, Moves.SLACK_OFF, Moves.SHED_TAIL ],
|
||||
[Species.SANDSHREW]: [ Moves.HIGH_HORSEPOWER, Moves.DIRE_CLAW, Moves.SHORE_UP, Moves.MIGHTY_CLEAVE ],
|
||||
[Species.NIDORAN_F]: [ Moves.CALM_MIND, Moves.MOONLIGHT, Moves.MALIGNANT_CHAIN, Moves.SANDSEAR_STORM ],
|
||||
@ -53,7 +53,7 @@ export const speciesEggMoves = {
|
||||
[Species.RHYHORN]: [ Moves.SHORE_UP, Moves.ICE_HAMMER, Moves.ACCELEROCK, Moves.HEAD_SMASH ],
|
||||
[Species.TANGELA]: [ Moves.NATURES_MADNESS, Moves.SNAP_TRAP, Moves.PARTING_SHOT, Moves.SAPPY_SEED ],
|
||||
[Species.KANGASKHAN]: [ Moves.POWER_UP_PUNCH, Moves.TRAILBLAZE, Moves.COVET, Moves.SEISMIC_TOSS ],
|
||||
[Species.HORSEA]: [ Moves.SNIPE_SHOT, Moves.FROST_BREATH, Moves.SLUDGE_BOMB, Moves.CLANGING_SCALES ],
|
||||
[Species.HORSEA]: [ Moves.SNIPE_SHOT, Moves.TAKE_HEART, Moves.SHELL_SIDE_ARM, Moves.DRAGON_ENERGY ],
|
||||
[Species.GOLDEEN]: [ Moves.GLACIAL_LANCE, Moves.SUPERCELL_SLAM, Moves.DRAGON_DANCE, Moves.FISHIOUS_REND ],
|
||||
[Species.STARYU]: [ Moves.CALM_MIND, Moves.BOUNCY_BUBBLE, Moves.MOONBLAST, Moves.MYSTICAL_POWER ],
|
||||
[Species.SCYTHER]: [ Moves.MIGHTY_CLEAVE, Moves.GEAR_GRIND, Moves.STORM_THROW, Moves.BITTER_BLADE ],
|
||||
@ -66,7 +66,7 @@ export const speciesEggMoves = {
|
||||
[Species.PORYGON]: [ Moves.THUNDERCLAP, Moves.AURA_SPHERE, Moves.FLAMETHROWER, Moves.TECHNO_BLAST ],
|
||||
[Species.OMANYTE]: [ Moves.FREEZE_DRY, Moves.GIGA_DRAIN, Moves.POWER_GEM, Moves.STEAM_ERUPTION ],
|
||||
[Species.KABUTO]: [ Moves.CEASELESS_EDGE, Moves.HIGH_HORSEPOWER, Moves.CRABHAMMER, Moves.MIGHTY_CLEAVE ],
|
||||
[Species.AERODACTYL]: [ Moves.FLOATY_FALL, Moves.FLARE_BLITZ, Moves.SWORDS_DANCE, Moves.MIGHTY_CLEAVE ],
|
||||
[Species.AERODACTYL]: [ Moves.FLOATY_FALL, Moves.CLOSE_COMBAT, Moves.STONE_AXE, Moves.SWORDS_DANCE ],
|
||||
[Species.ARTICUNO]: [ Moves.EARTH_POWER, Moves.CALM_MIND, Moves.AURORA_VEIL, Moves.AEROBLAST ],
|
||||
[Species.ZAPDOS]: [ Moves.BLEAKWIND_STORM, Moves.CALM_MIND, Moves.SANDSEAR_STORM, Moves.ELECTRO_SHOT ],
|
||||
[Species.MOLTRES]: [ Moves.EARTH_POWER, Moves.CALM_MIND, Moves.AEROBLAST, Moves.TORCH_SONG ],
|
||||
@ -78,7 +78,7 @@ export const speciesEggMoves = {
|
||||
[Species.CYNDAQUIL]: [ Moves.NASTY_PLOT, Moves.EARTH_POWER, Moves.FIERY_DANCE, Moves.ELECTRO_DRIFT ],
|
||||
[Species.TOTODILE]: [ Moves.THUNDER_PUNCH, Moves.DRAGON_DANCE, Moves.PLAY_ROUGH, Moves.SURGING_STRIKES ],
|
||||
[Species.SENTRET]: [ Moves.TIDY_UP, Moves.FAKE_OUT, Moves.NUZZLE, Moves.EXTREME_SPEED ],
|
||||
[Species.HOOTHOOT]: [ Moves.CALM_MIND, Moves.ESPER_WING, Moves.AEROBLAST, Moves.BOOMBURST ],
|
||||
[Species.HOOTHOOT]: [ Moves.TAKE_HEART, Moves.ESPER_WING, Moves.AEROBLAST, Moves.BOOMBURST ],
|
||||
[Species.LEDYBA]: [ Moves.POLLEN_PUFF, Moves.MAT_BLOCK, Moves.PARTING_SHOT, Moves.SPORE ],
|
||||
[Species.SPINARAK]: [ Moves.PARTING_SHOT, Moves.ATTACK_ORDER, Moves.GASTRO_ACID, Moves.STRENGTH_SAP ],
|
||||
[Species.CHINCHOU]: [ Moves.THUNDERCLAP, Moves.BOUNCY_BUBBLE, Moves.THUNDER_CAGE, Moves.TAIL_GLOW ],
|
||||
@ -166,7 +166,7 @@ export const speciesEggMoves = {
|
||||
[Species.SPOINK]: [ Moves.AURA_SPHERE, Moves.MILK_DRINK, Moves.EXPANDING_FORCE, Moves.TAIL_GLOW ],
|
||||
[Species.SPINDA]: [ Moves.SUPERPOWER, Moves.SLACK_OFF, Moves.FLEUR_CANNON, Moves.V_CREATE ],
|
||||
[Species.TRAPINCH]: [ Moves.FIRE_LASH, Moves.DRAGON_DARTS, Moves.THOUSAND_ARROWS, Moves.DRAGON_ENERGY ],
|
||||
[Species.CACNEA]: [ Moves.EARTH_POWER, Moves.CEASELESS_EDGE, Moves.NIGHT_DAZE, Moves.SAPPY_SEED ],
|
||||
[Species.CACNEA]: [ Moves.EARTH_POWER, Moves.CEASELESS_EDGE, Moves.NIGHT_DAZE, Moves.IVY_CUDGEL ],
|
||||
[Species.SWABLU]: [ Moves.ROOST, Moves.NASTY_PLOT, Moves.FLOATY_FALL, Moves.BOOMBURST ],
|
||||
[Species.ZANGOOSE]: [ Moves.FACADE, Moves.HIGH_HORSEPOWER, Moves.EXTREME_SPEED, Moves.TIDY_UP ],
|
||||
[Species.SEVIPER]: [ Moves.ICE_BEAM, Moves.BITTER_BLADE, Moves.SUCKER_PUNCH, Moves.NO_RETREAT ],
|
||||
@ -222,7 +222,7 @@ export const speciesEggMoves = {
|
||||
[Species.DRIFLOON]: [ Moves.PSYCHO_SHIFT, Moves.MIND_BLOWN, Moves.CALM_MIND, Moves.OBLIVION_WING ],
|
||||
[Species.BUNEARY]: [ Moves.TRIPLE_AXEL, Moves.EXTREME_SPEED, Moves.THUNDEROUS_KICK, Moves.SWORDS_DANCE ],
|
||||
[Species.GLAMEOW]: [ Moves.PARTING_SHOT, Moves.HIGH_HORSEPOWER, Moves.SWORDS_DANCE, Moves.EXTREME_SPEED ],
|
||||
[Species.CHINGLING]: [ Moves.BUZZY_BUZZ, Moves.EERIE_SPELL, Moves.TORCH_SONG, Moves.BOOMBURST ],
|
||||
[Species.CHINGLING]: [ Moves.ALLURING_VOICE, Moves.EERIE_SPELL, Moves.TORCH_SONG, Moves.BOOMBURST ],
|
||||
[Species.STUNKY]: [ Moves.CEASELESS_EDGE, Moves.FIRE_LASH, Moves.RECOVER, Moves.DIRE_CLAW ],
|
||||
[Species.BRONZOR]: [ Moves.RECOVER, Moves.TACHYON_CUTTER, Moves.GLARE, Moves.LUMINA_CRASH ],
|
||||
[Species.BONSLY]: [ Moves.ACCELEROCK, Moves.SWORDS_DANCE, Moves.STRENGTH_SAP, Moves.SAPPY_SEED ],
|
||||
@ -246,7 +246,7 @@ export const speciesEggMoves = {
|
||||
[Species.AZELF]: [ Moves.PSYSTRIKE, Moves.AURA_SPHERE, Moves.ICE_BEAM, Moves.TAIL_GLOW ],
|
||||
[Species.DIALGA]: [ Moves.CORE_ENFORCER, Moves.TAKE_HEART, Moves.RECOVER, Moves.MAKE_IT_RAIN ],
|
||||
[Species.PALKIA]: [ Moves.MALIGNANT_CHAIN, Moves.TAKE_HEART, Moves.RECOVER, Moves.ORIGIN_PULSE ],
|
||||
[Species.HEATRAN]: [ Moves.MATCHA_GOTCHA, Moves.RECOVER, Moves.ERUPTION, Moves.TACHYON_CUTTER ],
|
||||
[Species.HEATRAN]: [ Moves.ENERGY_BALL, Moves.RECOVER, Moves.ERUPTION, Moves.TACHYON_CUTTER ],
|
||||
[Species.REGIGIGAS]: [ Moves.SKILL_SWAP, Moves.RECOVER, Moves.EXTREME_SPEED, Moves.GIGATON_HAMMER ],
|
||||
[Species.GIRATINA]: [ Moves.DRAGON_DANCE, Moves.SPECTRAL_THIEF, Moves.RECOVER, Moves.COLLISION_COURSE ],
|
||||
[Species.CRESSELIA]: [ Moves.COSMIC_POWER, Moves.BODY_PRESS, Moves.SIZZLY_SLIDE, Moves.LUMINA_CRASH ],
|
||||
@ -284,10 +284,10 @@ export const speciesEggMoves = {
|
||||
[Species.BASCULIN]: [ Moves.LAST_RESPECTS, Moves.CLOSE_COMBAT, Moves.SPLISHY_SPLASH, Moves.NO_RETREAT ],
|
||||
[Species.SANDILE]: [ Moves.DIRE_CLAW, Moves.SUCKER_PUNCH, Moves.FIRE_LASH, Moves.HEADLONG_RUSH ],
|
||||
[Species.DARUMAKA]: [ Moves.DRAIN_PUNCH, Moves.ZIPPY_ZAP, Moves.HEADLONG_RUSH, Moves.PYRO_BALL ],
|
||||
[Species.MARACTUS]: [ Moves.EARTH_POWER, Moves.QUIVER_DANCE, Moves.FIERY_DANCE, Moves.SEED_FLARE ],
|
||||
[Species.MARACTUS]: [ Moves.EARTH_POWER, Moves.SIZZLY_SLIDE, Moves.FIERY_DANCE, Moves.QUIVER_DANCE ],
|
||||
[Species.DWEBBLE]: [ Moves.CRABHAMMER, Moves.STONE_AXE, Moves.LEECH_LIFE, Moves.MIGHTY_CLEAVE ],
|
||||
[Species.SCRAGGY]: [ Moves.SUCKER_PUNCH, Moves.BULLET_PUNCH, Moves.NOXIOUS_TORQUE, Moves.VICTORY_DANCE ],
|
||||
[Species.SIGILYPH]: [ Moves.MOONBLAST, Moves.CALM_MIND, Moves.ESPER_WING, Moves.OBLIVION_WING ],
|
||||
[Species.SIGILYPH]: [ Moves.MOONBLAST, Moves.PSYCHO_SHIFT, Moves.ESPER_WING, Moves.OBLIVION_WING ],
|
||||
[Species.YAMASK]: [ Moves.STRENGTH_SAP, Moves.GLARE, Moves.AURA_SPHERE, Moves.ASTRAL_BARRAGE ],
|
||||
[Species.TIRTOUGA]: [ Moves.ICE_SPINNER, Moves.AQUA_STEP, Moves.SHORE_UP, Moves.MIGHTY_CLEAVE ],
|
||||
[Species.ARCHEN]: [ Moves.ROOST, Moves.EARTHQUAKE, Moves.FLOATY_FALL, Moves.MIGHTY_CLEAVE ],
|
||||
@ -319,7 +319,7 @@ export const speciesEggMoves = {
|
||||
[Species.DRUDDIGON]: [ Moves.FIRE_LASH, Moves.MORNING_SUN, Moves.DRAGON_DARTS, Moves.CLANGOROUS_SOUL ],
|
||||
[Species.GOLETT]: [ Moves.SHIFT_GEAR, Moves.DRAIN_PUNCH, Moves.HEADLONG_RUSH, Moves.RAGE_FIST ],
|
||||
[Species.PAWNIARD]: [ Moves.SUCKER_PUNCH, Moves.CEASELESS_EDGE, Moves.BITTER_BLADE, Moves.LAST_RESPECTS ],
|
||||
[Species.BOUFFALANT]: [ Moves.SLACK_OFF, Moves.HIGH_JUMP_KICK, Moves.HEAD_SMASH, Moves.FLARE_BLITZ ],
|
||||
[Species.BOUFFALANT]: [ Moves.HORN_LEECH, Moves.HIGH_JUMP_KICK, Moves.HEAD_SMASH, Moves.FLARE_BLITZ ],
|
||||
[Species.RUFFLET]: [ Moves.FLOATY_FALL, Moves.AURA_SPHERE, Moves.NO_RETREAT, Moves.BOLT_BEAK ],
|
||||
[Species.VULLABY]: [ Moves.FOUL_PLAY, Moves.BODY_PRESS, Moves.ROOST, Moves.RUINATION ],
|
||||
[Species.HEATMOR]: [ Moves.EARTH_POWER, Moves.OVERHEAT, Moves.THUNDERBOLT, Moves.V_CREATE ],
|
||||
@ -441,7 +441,7 @@ export const speciesEggMoves = {
|
||||
[Species.ALOLA_GEODUDE]: [ Moves.THOUSAND_WAVES, Moves.BULK_UP, Moves.STONE_AXE, Moves.EXTREME_SPEED ],
|
||||
[Species.ALOLA_GRIMER]: [ Moves.SUCKER_PUNCH, Moves.BARB_BARRAGE, Moves.RECOVER, Moves.SURGING_STRIKES ],
|
||||
|
||||
[Species.GROOKEY]: [ Moves.HIGH_HORSEPOWER, Moves.CLANGOROUS_SOUL, Moves.GRASSY_GLIDE, Moves.SAPPY_SEED ],
|
||||
[Species.GROOKEY]: [ Moves.ROCK_SLIDE, Moves.PLAY_ROUGH, Moves.GRASSY_GLIDE, Moves.CLANGOROUS_SOUL ],
|
||||
[Species.SCORBUNNY]: [ Moves.EXTREME_SPEED, Moves.HIGH_JUMP_KICK, Moves.TRIPLE_AXEL, Moves.BOLT_STRIKE ],
|
||||
[Species.SOBBLE]: [ Moves.AEROBLAST, Moves.FROST_BREATH, Moves.ENERGY_BALL, Moves.NASTY_PLOT ],
|
||||
[Species.SKWOVET]: [ Moves.SUCKER_PUNCH, Moves.SLACK_OFF, Moves.COIL, Moves.POPULATION_BOMB ],
|
||||
@ -457,7 +457,7 @@ export const speciesEggMoves = {
|
||||
[Species.SILICOBRA]: [ Moves.SHORE_UP, Moves.SHED_TAIL, Moves.MOUNTAIN_GALE, Moves.THOUSAND_ARROWS ],
|
||||
[Species.CRAMORANT]: [ Moves.APPLE_ACID, Moves.SURF, Moves.BOLT_BEAK, Moves.OBLIVION_WING ],
|
||||
[Species.ARROKUDA]: [ Moves.SUPERCELL_SLAM, Moves.TRIPLE_DIVE, Moves.ICE_SPINNER, Moves.SWORDS_DANCE ],
|
||||
[Species.TOXEL]: [ Moves.NASTY_PLOT, Moves.BUG_BUZZ, Moves.SPARKLING_ARIA, Moves.TORCH_SONG ],
|
||||
[Species.TOXEL]: [ Moves.BUZZY_BUZZ, Moves.BUG_BUZZ, Moves.SPARKLING_ARIA, Moves.TORCH_SONG ],
|
||||
[Species.SIZZLIPEDE]: [ Moves.BURNING_BULWARK, Moves.ZING_ZAP, Moves.FIRST_IMPRESSION, Moves.BITTER_BLADE ],
|
||||
[Species.CLOBBOPUS]: [ Moves.STORM_THROW, Moves.JET_PUNCH, Moves.MACH_PUNCH, Moves.SURGING_STRIKES ],
|
||||
[Species.SINISTEA]: [ Moves.SPLISHY_SPLASH, Moves.MATCHA_GOTCHA, Moves.DRAINING_KISS, Moves.MOONGEIST_BEAM ],
|
||||
|
@ -143,7 +143,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
|
||||
[Species.TAUROS]: { 0: Abilities.STAMINA },
|
||||
[Species.MAGIKARP]: { 0: Abilities.MULTISCALE },
|
||||
[Species.GYARADOS]: { 0: Abilities.MULTISCALE, 1: Abilities.MULTISCALE },
|
||||
[Species.LAPRAS]: { 0: Abilities.LIGHTNING_ROD, 1: Abilities.FILTER },
|
||||
[Species.LAPRAS]: { 0: Abilities.FILTER, 1: Abilities.FILTER },
|
||||
[Species.DITTO]: { 0: Abilities.ADAPTABILITY },
|
||||
[Species.EEVEE]: { 0: Abilities.PICKUP, 1: Abilities.PICKUP, 2: Abilities.FLUFFY },
|
||||
[Species.VAPOREON]: { 0: Abilities.REGENERATOR },
|
||||
@ -161,7 +161,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
|
||||
[Species.OMASTAR]: { 0: Abilities.STURDY },
|
||||
[Species.KABUTO]: { 0: Abilities.TOUGH_CLAWS },
|
||||
[Species.KABUTOPS]: { 0: Abilities.TOUGH_CLAWS },
|
||||
[Species.AERODACTYL]: { 0: Abilities.INTIMIDATE, 1: Abilities.INTIMIDATE },
|
||||
[Species.AERODACTYL]: { 0: Abilities.INTIMIDATE, 1: Abilities.ROCKY_PAYLOAD },
|
||||
[Species.ARTICUNO]: { 0: Abilities.SNOW_WARNING },
|
||||
[Species.ZAPDOS]: { 0: Abilities.DRIZZLE },
|
||||
[Species.MOLTRES]: { 0: Abilities.DROUGHT },
|
||||
@ -506,7 +506,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
|
||||
[Species.SNOVER]: { 0: Abilities.SLUSH_RUSH },
|
||||
[Species.ABOMASNOW]: { 0: Abilities.SLUSH_RUSH, 1: Abilities.SEED_SOWER },
|
||||
[Species.ROTOM]: { 0: Abilities.HADRON_ENGINE, 1: Abilities.HADRON_ENGINE, 2: Abilities.HADRON_ENGINE, 3: Abilities.HADRON_ENGINE, 4: Abilities.HADRON_ENGINE, 5: Abilities.HADRON_ENGINE },
|
||||
[Species.UXIE]: { 0: Abilities.UNNERVE },
|
||||
[Species.UXIE]: { 0: Abilities.ILLUSION },
|
||||
[Species.MESPRIT]: { 0: Abilities.MOODY },
|
||||
[Species.AZELF]: { 0: Abilities.NEUROFORCE },
|
||||
[Species.DIALGA]: { 0: Abilities.BERSERK, 1: Abilities.BERSERK },
|
||||
@ -600,8 +600,8 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
|
||||
[Species.ARCHEOPS]: { 0: Abilities.MULTISCALE },
|
||||
[Species.TRUBBISH]: { 0: Abilities.NEUTRALIZING_GAS },
|
||||
[Species.GARBODOR]: { 0: Abilities.NEUTRALIZING_GAS, 1: Abilities.NEUTRALIZING_GAS },
|
||||
[Species.ZORUA]: { 0: Abilities.DARK_AURA },
|
||||
[Species.ZOROARK]: { 0: Abilities.DARK_AURA },
|
||||
[Species.ZORUA]: { 0: Abilities.ADAPTABILITY },
|
||||
[Species.ZOROARK]: { 0: Abilities.ADAPTABILITY },
|
||||
[Species.MINCCINO]: { 0: Abilities.FUR_COAT },
|
||||
[Species.CINCCINO]: { 0: Abilities.FUR_COAT },
|
||||
[Species.GOTHITA]: { 0: Abilities.UNNERVE },
|
||||
@ -729,8 +729,8 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
|
||||
[Species.CLAWITZER]: { 0: Abilities.PROTEAN },
|
||||
[Species.HELIOPTILE]: { 0: Abilities.PROTEAN },
|
||||
[Species.HELIOLISK]: { 0: Abilities.PROTEAN },
|
||||
[Species.TYRUNT]: { 0: Abilities.RECKLESS },
|
||||
[Species.TYRANTRUM]: { 0: Abilities.RECKLESS },
|
||||
[Species.TYRUNT]: { 0: Abilities.SHEER_FORCE },
|
||||
[Species.TYRANTRUM]: { 0: Abilities.SHEER_FORCE },
|
||||
[Species.AMAURA]: { 0: Abilities.ICE_SCALES },
|
||||
[Species.AURORUS]: { 0: Abilities.ICE_SCALES },
|
||||
[Species.HAWLUCHA]: { 0: Abilities.MOXIE },
|
||||
@ -744,8 +744,8 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
|
||||
[Species.KLEFKI]: { 0: Abilities.LEVITATE },
|
||||
[Species.PHANTUMP]: { 0: Abilities.SHADOW_TAG },
|
||||
[Species.TREVENANT]: { 0: Abilities.SHADOW_TAG },
|
||||
[Species.PUMPKABOO]: { 0: Abilities.WELL_BAKED_BODY, 1: Abilities.ADAPTABILITY, 2: Abilities.PRANKSTER, 3: Abilities.SEED_SOWER },
|
||||
[Species.GOURGEIST]: { 0: Abilities.WELL_BAKED_BODY, 1: Abilities.ADAPTABILITY, 2: Abilities.PRANKSTER, 3: Abilities.SEED_SOWER },
|
||||
[Species.PUMPKABOO]: { 0: Abilities.ILLUMINATE, 1: Abilities.ADAPTABILITY, 2: Abilities.WELL_BAKED_BODY, 3: Abilities.SEED_SOWER },
|
||||
[Species.GOURGEIST]: { 0: Abilities.ILLUMINATE, 1: Abilities.ADAPTABILITY, 2: Abilities.WELL_BAKED_BODY, 3: Abilities.SEED_SOWER },
|
||||
[Species.BERGMITE]: { 0: Abilities.ICE_SCALES },
|
||||
[Species.AVALUGG]: { 0: Abilities.ICE_SCALES },
|
||||
[Species.HISUI_AVALUGG]: { 0: Abilities.ICE_SCALES },
|
||||
@ -781,7 +781,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
|
||||
[Species.CRABOMINABLE]: { 0: Abilities.WATER_BUBBLE },
|
||||
[Species.ORICORIO]: { 0: Abilities.ADAPTABILITY, 1: Abilities.ADAPTABILITY, 2: Abilities.ADAPTABILITY, 3: Abilities.ADAPTABILITY },
|
||||
[Species.CUTIEFLY]: { 0: Abilities.PICKUP },
|
||||
[Species.RIBOMBEE]: { 0: Abilities.TINTED_LENS },
|
||||
[Species.RIBOMBEE]: { 0: Abilities.PICKUP },
|
||||
[Species.ROCKRUFF]: { 0: Abilities.PICKUP, 1: Abilities.PICKUP },
|
||||
[Species.LYCANROC]: { 0: Abilities.STURDY, 1: Abilities.INTIMIDATE, 2: Abilities.STAKEOUT },
|
||||
[Species.WISHIWASHI]: { 0: Abilities.REGENERATOR, 1: Abilities.REGENERATOR },
|
||||
@ -932,7 +932,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
|
||||
[Species.COPPERAJAH]: { 0: Abilities.EARTH_EATER, 1: Abilities.EARTH_EATER },
|
||||
[Species.DRACOZOLT]: { 0: Abilities.NO_GUARD },
|
||||
[Species.ARCTOZOLT]: { 0: Abilities.WATER_ABSORB },
|
||||
[Species.DRACOVISH]: { 0: Abilities.SWIFT_SWIM },
|
||||
[Species.DRACOVISH]: { 0: Abilities.THERMAL_EXCHANGE },
|
||||
[Species.ARCTOVISH]: { 0: Abilities.STRONG_JAW },
|
||||
[Species.DURALUDON]: { 0: Abilities.FILTER, 1: Abilities.UNAWARE },
|
||||
[Species.ARCHALUDON]: { 0: Abilities.TRANSISTOR },
|
||||
@ -981,8 +981,8 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
|
||||
[Species.OVERQWIL]: { 0: Abilities.MERCILESS },
|
||||
[Species.HISUI_SNEASEL]: { 0: Abilities.SCRAPPY },
|
||||
[Species.SNEASLER]: { 0: Abilities.SCRAPPY },
|
||||
[Species.HISUI_ZORUA]: { 0: Abilities.ADAPTABILITY },
|
||||
[Species.HISUI_ZOROARK]: { 0: Abilities.ADAPTABILITY },
|
||||
[Species.HISUI_ZORUA]: { 0: Abilities.SHADOW_SHIELD },
|
||||
[Species.HISUI_ZOROARK]: { 0: Abilities.SHADOW_SHIELD },
|
||||
|
||||
[Species.SPRIGATITO]: { 0: Abilities.PICKUP },
|
||||
[Species.FLORAGATO]: { 0: Abilities.MAGICIAN },
|
||||
|
@ -19383,6 +19383,44 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = {
|
||||
[ 100, Moves.SEED_FLARE ],
|
||||
]
|
||||
},
|
||||
[Species.BASCULIN]: {
|
||||
1: [
|
||||
[ 1, Moves.TAIL_WHIP ],
|
||||
[ 1, Moves.WATER_GUN ],
|
||||
[ 4, Moves.TACKLE ],
|
||||
[ 8, Moves.FLAIL ],
|
||||
[ 12, Moves.AQUA_JET ],
|
||||
[ 16, Moves.BITE ],
|
||||
[ 20, Moves.SCARY_FACE ],
|
||||
[ 24, Moves.HEADBUTT ],
|
||||
[ 28, Moves.SOAK ],
|
||||
[ 32, Moves.CRUNCH ],
|
||||
[ 36, Moves.TAKE_DOWN ],
|
||||
[ 40, Moves.FINAL_GAMBIT ],
|
||||
[ 44, Moves.WAVE_CRASH ],
|
||||
[ 48, Moves.THRASH ],
|
||||
[ 52, Moves.DOUBLE_EDGE ],
|
||||
[ 56, Moves.HEAD_SMASH ],
|
||||
],
|
||||
2: [
|
||||
[ 1, Moves.TAIL_WHIP ],
|
||||
[ 1, Moves.WATER_GUN ],
|
||||
[ 4, Moves.TACKLE ],
|
||||
[ 8, Moves.FLAIL ],
|
||||
[ 12, Moves.AQUA_JET ],
|
||||
[ 16, Moves.BITE ],
|
||||
[ 20, Moves.SCARY_FACE ],
|
||||
[ 24, Moves.HEADBUTT ],
|
||||
[ 28, Moves.SOAK ],
|
||||
[ 32, Moves.CRUNCH ],
|
||||
[ 36, Moves.TAKE_DOWN ],
|
||||
[ 40, Moves.UPROAR ],
|
||||
[ 44, Moves.WAVE_CRASH ],
|
||||
[ 48, Moves.THRASH ],
|
||||
[ 52, Moves.DOUBLE_EDGE ],
|
||||
[ 56, Moves.HEAD_SMASH ],
|
||||
]
|
||||
},
|
||||
[Species.KYUREM]: {
|
||||
1: [
|
||||
[ 1, Moves.DRAGON_BREATH ],
|
||||
|
@ -4,12 +4,19 @@ export type SignatureSpecies = {
|
||||
[key in string]: (Species | Species[])[];
|
||||
};
|
||||
|
||||
/*
|
||||
/**
|
||||
* 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.
|
||||
* 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
|
||||
BROCK: [Species.ONIX, Species.GEODUDE, [Species.OMANYTE, Species.KABUTO], Species.AERODACTYL],
|
||||
MISTY: [Species.STARYU, Species.PSYDUCK, Species.WOOPER, Species.LAPRAS],
|
||||
@ -92,71 +99,8 @@ export const signatureSpecies: SignatureSpecies = {
|
||||
RYME: [Species.TOXEL, Species.GREAVARD, Species.SHUPPET, Species.MIMIKYU], // Tera Ghost Toxel
|
||||
TULIP: [Species.FLABEBE, Species.FLITTLE, Species.RALTS, Species.GIRAFARIG], // Tera Psychic Flabebe
|
||||
GRUSHA: [Species.SWABLU, Species.CETODDLE, Species.SNOM, Species.CUBCHOO], // Tera Ice Swablu
|
||||
|
||||
// Elite Four- Kanto
|
||||
LORELEI: [
|
||||
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
|
||||
};
|
||||
}, {
|
||||
get(target, prop: string) {
|
||||
return target[prop as keyof SignatureSpecies] ?? [];
|
||||
}
|
||||
});
|
||||
|
@ -5724,7 +5724,6 @@ export const tmSpecies: TmSpecies = {
|
||||
Species.SCOLIPEDE,
|
||||
Species.WHIMSICOTT,
|
||||
Species.LILLIGANT,
|
||||
Species.BASCULIN,
|
||||
Species.KROOKODILE,
|
||||
Species.DARMANITAN,
|
||||
Species.CRUSTLE,
|
||||
@ -6023,6 +6022,11 @@ export const tmSpecies: TmSpecies = {
|
||||
Species.HISUI_DECIDUEYE,
|
||||
Species.PALDEA_TAUROS,
|
||||
Species.BLOODMOON_URSALUNA,
|
||||
[
|
||||
Species.BASCULIN,
|
||||
"blue-striped",
|
||||
"red-striped",
|
||||
]
|
||||
],
|
||||
[Moves.LOW_KICK]: [
|
||||
Species.SANDSHREW,
|
||||
@ -19335,7 +19339,6 @@ export const tmSpecies: TmSpecies = {
|
||||
Species.CONKELDURR,
|
||||
Species.THROH,
|
||||
Species.SAWK,
|
||||
Species.BASCULIN,
|
||||
Species.DARMANITAN,
|
||||
Species.SCRAFTY,
|
||||
Species.ESCAVALIER,
|
||||
@ -19449,6 +19452,11 @@ export const tmSpecies: TmSpecies = {
|
||||
Species.HISUI_BRAVIARY,
|
||||
Species.HISUI_DECIDUEYE,
|
||||
Species.PALDEA_TAUROS,
|
||||
[
|
||||
Species.BASCULIN,
|
||||
"blue-striped",
|
||||
"red-striped",
|
||||
],
|
||||
],
|
||||
[Moves.SPITE]: [
|
||||
Species.EKANS,
|
||||
@ -51341,7 +51349,6 @@ export const tmSpecies: TmSpecies = {
|
||||
Species.SCOLIPEDE,
|
||||
Species.WHIMSICOTT,
|
||||
Species.LILLIGANT,
|
||||
Species.BASCULIN,
|
||||
Species.KROOKODILE,
|
||||
Species.DARMANITAN,
|
||||
Species.CRUSTLE,
|
||||
@ -51655,6 +51662,11 @@ export const tmSpecies: TmSpecies = {
|
||||
Species.HISUI_DECIDUEYE,
|
||||
Species.PALDEA_TAUROS,
|
||||
Species.BLOODMOON_URSALUNA,
|
||||
[
|
||||
Species.BASCULIN,
|
||||
"blue-striped",
|
||||
"red-striped",
|
||||
],
|
||||
],
|
||||
[Moves.NASTY_PLOT]: [
|
||||
Species.PIKACHU,
|
||||
|
@ -2637,7 +2637,7 @@ export class GulpMissileTag extends BattlerTag {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (moveEffectPhase.move.getMove().hitsSubstitute(attacker, pokemon)) {
|
||||
if (moveEffectPhase.move.hitsSubstitute(attacker, pokemon)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2993,7 +2993,7 @@ export class SubstituteTag extends BattlerTag {
|
||||
if (!attacker) {
|
||||
return;
|
||||
}
|
||||
const move = moveEffectPhase.move.getMove();
|
||||
const move = moveEffectPhase.move;
|
||||
const firstHit = attacker.turnData.hitCount === attacker.turnData.hitsLeft;
|
||||
|
||||
if (firstHit && move.hitsSubstitute(attacker, pokemon)) {
|
||||
@ -3681,7 +3681,7 @@ function getMoveEffectPhaseData(_pokemon: Pokemon): { phase: MoveEffectPhase; at
|
||||
return {
|
||||
phase: phase,
|
||||
attacker: phase.getPokemon(),
|
||||
move: phase.move.getMove(),
|
||||
move: phase.move,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
@ -8,7 +8,8 @@ import { speciesStarterCosts } from "#app/data/balance/starters";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import type { FixedBattleConfig } from "#app/battle";
|
||||
import { ClassicFixedBossWaves, 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 { PokemonType } from "#enums/pokemon-type";
|
||||
|
20
src/data/moves/move-utils.ts
Normal file
20
src/data/moves/move-utils.ts
Normal 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;
|
||||
}
|
@ -60,6 +60,7 @@ import {
|
||||
MoveTypeChangeAbAttr,
|
||||
PostDamageForceSwitchAbAttr,
|
||||
PostItemLostAbAttr,
|
||||
ReflectStatusMoveAbAttr,
|
||||
ReverseDrainAbAttr,
|
||||
UserFieldMoveTypePowerBoostAbAttr,
|
||||
VariableMovePowerAbAttr,
|
||||
@ -122,6 +123,7 @@ import { MoveEffectTrigger } from "#enums/MoveEffectTrigger";
|
||||
import { MultiHitType } from "#enums/MultiHitType";
|
||||
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves";
|
||||
import { TrainerVariant } from "#app/field/trainer";
|
||||
import { SelectBiomePhase } from "#app/phases/select-biome-phase";
|
||||
|
||||
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
|
||||
type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
|
||||
@ -650,7 +652,7 @@ export default class Move implements Localizable {
|
||||
break;
|
||||
case MoveFlags.IGNORE_ABILITIES:
|
||||
if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) {
|
||||
const abilityEffectsIgnored = new BooleanHolder(false);
|
||||
const abilityEffectsIgnored = new BooleanHolder(false);
|
||||
applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this);
|
||||
if (abilityEffectsIgnored.value) {
|
||||
return true;
|
||||
@ -665,6 +667,17 @@ export default class Move implements Localizable {
|
||||
return true;
|
||||
}
|
||||
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);
|
||||
@ -1716,7 +1729,7 @@ export class SacrificialAttr extends MoveEffectAttr {
|
||||
**/
|
||||
export class SacrificialAttrOnHit extends MoveEffectAttr {
|
||||
constructor() {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT });
|
||||
super(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1955,6 +1968,14 @@ export class PartyStatusCureAttr extends MoveEffectAttr {
|
||||
* @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 target - The target Pokémon.
|
||||
@ -2177,7 +2198,7 @@ export class HitHealAttr extends MoveEffectAttr {
|
||||
private healStat: EffectiveStat | null;
|
||||
|
||||
constructor(healRatio?: number | null, healStat?: EffectiveStat) {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT });
|
||||
super(true);
|
||||
|
||||
this.healRatio = healRatio ?? 0.5;
|
||||
this.healStat = healStat ?? null;
|
||||
@ -2426,7 +2447,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
||||
public overrideStatus: boolean = false;
|
||||
|
||||
constructor(effect: StatusEffect, selfTarget?: boolean, turnsRemaining?: number, overrideStatus: boolean = false) {
|
||||
super(selfTarget, { trigger: MoveEffectTrigger.HIT });
|
||||
super(selfTarget);
|
||||
|
||||
this.effect = effect;
|
||||
this.turnsRemaining = turnsRemaining;
|
||||
@ -2434,26 +2455,15 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
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 statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance;
|
||||
if (statusCheck) {
|
||||
const pokemon = this.selfTarget ? user : target;
|
||||
if (pokemon.status && !this.overrideStatus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user !== target && target.isSafeguarded(user)) {
|
||||
if (move.category === MoveCategory.STATUS) {
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) }));
|
||||
}
|
||||
if (user !== target && move.category === MoveCategory.STATUS && !target.canSetStatus(this.effect, false, false, user, true)) {
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
@ -2495,7 +2505,7 @@ export class MultiStatusEffectAttr extends StatusEffectAttr {
|
||||
|
||||
export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
||||
constructor() {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2534,15 +2544,11 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
|
||||
private chance: number;
|
||||
|
||||
constructor(chance: number) {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
this.chance = chance;
|
||||
}
|
||||
|
||||
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);
|
||||
if (rand >= this.chance) {
|
||||
return false;
|
||||
@ -2590,7 +2596,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||
private berriesOnly: boolean;
|
||||
|
||||
constructor(berriesOnly: boolean) {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
this.berriesOnly = berriesOnly;
|
||||
}
|
||||
|
||||
@ -2600,17 +2606,13 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||
* @param target Target {@linkcode Pokemon} that the moves applies to
|
||||
* @param move {@linkcode Move} that is used
|
||||
* @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 {
|
||||
if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // Check for abilities that block item theft
|
||||
|
||||
@ -2664,8 +2666,8 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||
*/
|
||||
export class EatBerryAttr extends MoveEffectAttr {
|
||||
protected chosenBerry: BerryModifier | undefined;
|
||||
constructor() {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT });
|
||||
constructor(selfTarget: boolean) {
|
||||
super(selfTarget);
|
||||
}
|
||||
/**
|
||||
* Causes the target to eat a berry.
|
||||
@ -2680,17 +2682,20 @@ export class EatBerryAttr extends MoveEffectAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
const heldBerries = this.getTargetHeldBerries(target);
|
||||
const pokemon = this.selfTarget ? user : target;
|
||||
|
||||
const heldBerries = this.getTargetHeldBerries(pokemon);
|
||||
if (heldBerries.length <= 0) {
|
||||
return false;
|
||||
}
|
||||
this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)];
|
||||
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) {
|
||||
this.reduceBerryModifier(target);
|
||||
this.reduceBerryModifier(pokemon);
|
||||
}
|
||||
this.eatBerry(target);
|
||||
this.eatBerry(pokemon);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2718,20 +2723,17 @@ export class EatBerryAttr extends MoveEffectAttr {
|
||||
*/
|
||||
export class StealEatBerryAttr extends EatBerryAttr {
|
||||
constructor() {
|
||||
super();
|
||||
super(false);
|
||||
}
|
||||
/**
|
||||
* 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 {Pokemon} target Pokemon that will have its berry stolen
|
||||
* @param {Move} move Move being used
|
||||
* @param {any[]} args Unused
|
||||
* @returns {boolean} true if the function succeeds
|
||||
* @param user - Pokemon that used the move and will eat the stolen berry
|
||||
* @param target - Pokemon that will have its berry stolen
|
||||
* @param move - Move being used
|
||||
* @param args Unused
|
||||
* @returns true if the function succeeds
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // check for abilities that block item theft
|
||||
if (cancelled.value === true) {
|
||||
@ -2782,10 +2784,6 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.selfTarget && move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Special edge case for shield dust blocking Sparkling Aria curing burn
|
||||
const moveTargets = getMoveTargets(user, move.id);
|
||||
if (target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && move.id === Moves.SPARKLING_ARIA && moveTargets.targets.length === 1) {
|
||||
@ -3163,14 +3161,6 @@ export class StatStageChangeAttr extends MoveEffectAttr {
|
||||
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
|
||||
* @param user {@linkcode Pokemon} the user of the move
|
||||
@ -3184,10 +3174,6 @@ export class StatStageChangeAttr extends MoveEffectAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.selfTarget && move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
||||
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
||||
const stages = this.getLevels(user);
|
||||
@ -3471,7 +3457,7 @@ export class CutHpStatStageBoostAttr extends StatStageChangeAttr {
|
||||
*/
|
||||
export class OrderUpStatBoostAttr extends MoveEffectAttr {
|
||||
constructor() {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT });
|
||||
super(true);
|
||||
}
|
||||
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
|
||||
@ -3548,17 +3534,15 @@ export class ResetStatsAttr extends MoveEffectAttr {
|
||||
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) {
|
||||
// Target all pokemon on the field when Freezy Frost or Haze are used
|
||||
const activePokemon = globalScene.getField(true);
|
||||
activePokemon.forEach((p) => this.resetStats(p));
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:statEliminated"));
|
||||
} else { // Affects only the single target when Clear Smog is used
|
||||
if (!move.hitsSubstitute(user, target)) {
|
||||
this.resetStats(target);
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) }));
|
||||
}
|
||||
this.resetStats(target);
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) }));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -4217,7 +4201,8 @@ export class PresentPowerAttr extends VariablePowerAttr {
|
||||
(args[0] as NumberHolder).value = 120;
|
||||
} else if (80 < powerSeed && powerSeed <= 100) {
|
||||
// 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(),
|
||||
toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true));
|
||||
}
|
||||
@ -4811,8 +4796,8 @@ export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const category = (args[0] as NumberHolder);
|
||||
|
||||
const predictedPhysDmg = target.getBaseDamage(user, move, MoveCategory.PHYSICAL, true, true, true, true);
|
||||
const predictedSpecDmg = target.getBaseDamage(user, move, MoveCategory.SPECIAL, 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({source: user, move, moveCategory: MoveCategory.SPECIAL, ignoreAbility: true, ignoreSourceAbility: true, ignoreAllyAbility: true, ignoreSourceAllyAbility: true, simulated: true});
|
||||
|
||||
if (predictedPhysDmg > predictedSpecDmg) {
|
||||
category.value = MoveCategory.PHYSICAL;
|
||||
@ -5371,7 +5356,7 @@ export class BypassRedirectAttr extends MoveAttr {
|
||||
|
||||
export class FrenzyAttr extends MoveEffectAttr {
|
||||
constructor() {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT, lastHitOnly: true });
|
||||
super(true, { lastHitOnly: true });
|
||||
}
|
||||
|
||||
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
|
||||
@ -5443,22 +5428,20 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
||||
protected cancelOnFail: 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 });
|
||||
|
||||
this.tagType = tagType;
|
||||
this.turnCountMin = turnCountMin;
|
||||
this.turnCountMax = turnCountMax !== undefined ? turnCountMax : turnCountMin;
|
||||
this.failOnOverlap = !!failOnOverlap;
|
||||
this.cancelOnFail = cancelOnFail;
|
||||
}
|
||||
|
||||
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;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
@ -5549,19 +5532,6 @@ export class LeechSeedAttr extends AddBattlerTagAttr {
|
||||
constructor() {
|
||||
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 +5707,6 @@ export class FlinchAttr extends AddBattlerTagAttr {
|
||||
constructor() {
|
||||
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 {
|
||||
@ -5759,16 +5722,13 @@ export class ConfuseAttr extends AddBattlerTagAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!move.hitsSubstitute(user, target)) {
|
||||
return super.apply(user, target, move, args);
|
||||
}
|
||||
return false;
|
||||
return super.apply(user, target, move, args);
|
||||
}
|
||||
}
|
||||
|
||||
export class RechargeAttr extends AddBattlerTagAttr {
|
||||
constructor() {
|
||||
super(BattlerTagType.RECHARGING, true, false, 1, 1, true, true);
|
||||
super(BattlerTagType.RECHARGING, true, false, 1, 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6151,7 +6111,7 @@ export class AddPledgeEffectAttr extends AddArenaTagAttr {
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class RevivalBlessingAttr extends MoveEffectAttr {
|
||||
constructor(user?: boolean) {
|
||||
constructor() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
@ -6366,6 +6326,11 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
|
||||
if (!allyPokemon?.isActive(true) && switchOutTarget.hp) {
|
||||
globalScene.pushPhase(new BattleEndPhase(false));
|
||||
|
||||
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
|
||||
globalScene.pushPhase(new SelectBiomePhase());
|
||||
}
|
||||
|
||||
globalScene.pushPhase(new NewBattlePhase());
|
||||
}
|
||||
}
|
||||
@ -6392,10 +6357,6 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
const player = switchOutTarget instanceof PlayerPokemon;
|
||||
|
||||
if (!this.selfSwitch) {
|
||||
if (move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Dondozo with an allied Tatsugiri in its mouth cannot be forced out
|
||||
const commandedTag = switchOutTarget.getTag(BattlerTagType.COMMANDED);
|
||||
if (commandedTag?.getSourcePokemon()?.isActive(true)) {
|
||||
@ -6650,7 +6611,7 @@ export class ChangeTypeAttr extends MoveEffectAttr {
|
||||
private type: PokemonType;
|
||||
|
||||
constructor(type: PokemonType) {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
|
||||
this.type = type;
|
||||
}
|
||||
@ -6673,7 +6634,7 @@ export class AddTypeAttr extends MoveEffectAttr {
|
||||
private type: PokemonType;
|
||||
|
||||
constructor(type: PokemonType) {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
|
||||
this.type = type;
|
||||
}
|
||||
@ -7369,7 +7330,7 @@ export class AbilityChangeAttr extends MoveEffectAttr {
|
||||
public ability: Abilities;
|
||||
|
||||
constructor(ability: Abilities, selfTarget?: boolean) {
|
||||
super(selfTarget, { trigger: MoveEffectTrigger.HIT });
|
||||
super(selfTarget);
|
||||
|
||||
this.ability = ability;
|
||||
}
|
||||
@ -7400,7 +7361,7 @@ export class AbilityCopyAttr extends MoveEffectAttr {
|
||||
public copyToPartner: boolean;
|
||||
|
||||
constructor(copyToPartner: boolean = false) {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
|
||||
this.copyToPartner = copyToPartner;
|
||||
}
|
||||
@ -7441,7 +7402,7 @@ export class AbilityGiveAttr extends MoveEffectAttr {
|
||||
public copyToPartner: boolean;
|
||||
|
||||
constructor() {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
@ -7704,23 +7665,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 {
|
||||
constructor() {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT, firstHitOnly: true });
|
||||
super(true, {firstHitOnly: true });
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move): boolean {
|
||||
@ -7787,7 +7734,7 @@ export class StatusIfBoostedAttr extends MoveEffectAttr {
|
||||
public effect: StatusEffect;
|
||||
|
||||
constructor(effect: StatusEffect) {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT });
|
||||
super(true);
|
||||
this.effect = effect;
|
||||
}
|
||||
|
||||
@ -8671,7 +8618,9 @@ export function initMoves() {
|
||||
.condition((user, target, move) => !target.summonData?.illusion && !user.summonData?.illusion)
|
||||
// transforming from or into fusion pokemon causes various problems (such as crashes)
|
||||
.condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE) && !user.fusionSpecies && !target.fusionSpecies)
|
||||
.ignoresProtect(),
|
||||
.ignoresProtect()
|
||||
// Transforming should copy the target's rage fist hit count
|
||||
.edgeCase(),
|
||||
new AttackMove(Moves.BUBBLE, PokemonType.WATER, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
|
||||
.attr(StatStageChangeAttr, [ Stat.SPD ], -1)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
@ -10556,8 +10505,7 @@ export function initMoves() {
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
})
|
||||
.attr(DiscourageFrequentUseAttr),
|
||||
}),
|
||||
|
||||
new AttackMove(Moves.SNIPE_SHOT, PokemonType.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, 0, 8)
|
||||
.attr(HighCritAttr)
|
||||
@ -10566,7 +10514,7 @@ export function initMoves() {
|
||||
.attr(JawLockAttr)
|
||||
.bitingMove(),
|
||||
new SelfStatusMove(Moves.STUFF_CHEEKS, PokemonType.NORMAL, -1, 10, -1, 0, 8)
|
||||
.attr(EatBerryAttr)
|
||||
.attr(EatBerryAttr, true)
|
||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true)
|
||||
.condition((user) => {
|
||||
const userBerries = globalScene.findModifiers(m => m instanceof BerryModifier, user.isPlayer());
|
||||
@ -10590,7 +10538,7 @@ export function initMoves() {
|
||||
.makesContact(false)
|
||||
.partial(), // smart targetting is unimplemented
|
||||
new StatusMove(Moves.TEATIME, PokemonType.NORMAL, -1, 10, -1, 0, 8)
|
||||
.attr(EatBerryAttr)
|
||||
.attr(EatBerryAttr, false)
|
||||
.target(MoveTarget.ALL),
|
||||
new StatusMove(Moves.OCTOLOCK, PokemonType.FIGHTING, 100, 15, -1, 0, 8)
|
||||
.condition(failIfGhostTypeCondition)
|
||||
|
@ -22,7 +22,7 @@ import { EggTier } from "#enums/egg-type";
|
||||
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
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 */
|
||||
const namespace = "mysteryEncounters/aTrainersTest";
|
||||
|
@ -37,7 +37,7 @@ import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
|
||||
import type { BerryType } from "#enums/berry-type";
|
||||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||
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";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
|
@ -23,7 +23,7 @@ import { speciesStarterCosts } from "#app/data/balance/starters";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
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";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
|
@ -36,7 +36,7 @@ import i18next from "#app/plugins/i18n";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { PERMANENT_STATS, Stat } from "#enums/stat";
|
||||
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 */
|
||||
const namespace = "mysteryEncounters/berriesAbound";
|
||||
|
@ -52,7 +52,7 @@ import i18next from "i18next";
|
||||
import MoveInfoOverlay from "#app/ui/move-info-overlay";
|
||||
import { allMoves } from "#app/data/moves/move";
|
||||
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";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
|
@ -46,7 +46,7 @@ import { Moves } from "#enums/moves";
|
||||
import { EncounterBattleAnim } from "#app/data/battle-anims";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
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 { Challenges } from "#enums/challenges";
|
||||
|
||||
|
@ -24,7 +24,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import type Pokemon 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 { LearnMovePhase } from "#app/phases/learn-move-phase";
|
||||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||
|
@ -19,7 +19,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
|
||||
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
||||
import type { PokemonHeldItemModifier } 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";
|
||||
|
||||
/** i18n namespace for encounter */
|
||||
|
@ -18,7 +18,7 @@ import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/u
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import type { PlayerPokemon } 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 {
|
||||
BerryModifier,
|
||||
|
@ -10,7 +10,7 @@ import { Species } from "#enums/species";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
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 */
|
||||
const namespace = "mysteryEncounters/departmentStoreSale";
|
||||
|
@ -18,7 +18,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { Stat } from "#enums/stat";
|
||||
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 */
|
||||
const namespace = "mysteryEncounters/fieldTrip";
|
||||
|
@ -41,7 +41,7 @@ import {
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
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 { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||
|
@ -33,7 +33,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { randSeedInt } from "#app/utils/common";
|
||||
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 */
|
||||
const namespace = "mysteryEncounters/fightOrFlight";
|
||||
|
@ -30,7 +30,7 @@ import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
||||
import { PostSummonPhase } from "#app/phases/post-summon-phase";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
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";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
|
@ -48,7 +48,7 @@ import { Gender, getGenderSymbol } from "#app/data/gender";
|
||||
import { getNatureName } from "#app/data/nature";
|
||||
import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball";
|
||||
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 type { PokeballType } from "#enums/pokeball";
|
||||
import { doShinySparkleAnim } from "#app/field/anims";
|
||||
|
@ -10,7 +10,7 @@ import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter
|
||||
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
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";
|
||||
|
||||
const OPTION_1_REQUIRED_MOVE = Moves.SURF;
|
||||
|
@ -16,7 +16,7 @@ import { randSeedInt } from "#app/utils/common";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
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 */
|
||||
const namespace = "mysteryEncounters/mysteriousChallengers";
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
koPlayerPokemon,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
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 { GameOverPhase } from "#app/phases/game-over-phase";
|
||||
import { randSeedInt } from "#app/utils/common";
|
||||
|
@ -20,7 +20,7 @@ import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-enco
|
||||
import i18next from "i18next";
|
||||
import type { PlayerPokemon } 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";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
|
@ -31,7 +31,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { ScanIvsPhase } from "#app/phases/scan-ivs-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";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
|
@ -26,7 +26,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import type { Nature } from "#enums/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";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
|
@ -26,7 +26,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
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 { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||
|
||||
|
@ -29,7 +29,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||
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 {
|
||||
getEncounterPokemonLevelForWave,
|
||||
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
|
||||
|
@ -11,7 +11,7 @@ import { randSeedShuffle } from "#app/utils/common";
|
||||
import type MysteryEncounter from "../mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
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 { TrainerType } from "#enums/trainer-type";
|
||||
import i18next from "i18next";
|
||||
|
@ -3,7 +3,7 @@ import {
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
updatePlayerMoney,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
|
||||
import { isNullOrUndefined, NumberHolder, randSeedInt, randSeedItem } from "#app/utils/common";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
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 { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
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 { 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 */
|
||||
const namespace = "mysteryEncounters/thePokemonSalesman";
|
||||
@ -38,6 +39,9 @@ const MAX_POKEMON_PRICE_MULTIPLIER = 4;
|
||||
/** Odds of shiny magikarp will be 1/value */
|
||||
const SHINY_MAGIKARP_WEIGHT = 100;
|
||||
|
||||
/** Odds of event sale will be value/100 */
|
||||
const EVENT_THRESHOLD = 50;
|
||||
|
||||
/**
|
||||
* Pokemon Salesman encounter.
|
||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3799 | GitHub Issue #3799}
|
||||
@ -82,15 +86,46 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
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;
|
||||
/**
|
||||
* 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 (
|
||||
randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 ||
|
||||
isNullOrUndefined(species.abilityHidden) ||
|
||||
species.abilityHidden === Abilities.NONE
|
||||
r === 0 ||
|
||||
((isNullOrUndefined(species.abilityHidden) || 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);
|
||||
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 {
|
||||
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||
import { Stat } from "#enums/stat";
|
||||
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 */
|
||||
const namespace = "mysteryEncounters/theStrongStuff";
|
||||
|
@ -32,7 +32,7 @@ import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
|
||||
import { ReturnPhase } from "#app/phases/return-phase";
|
||||
import i18next from "i18next";
|
||||
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";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
|
@ -28,7 +28,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
|
||||
import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
|
||||
import i18next from "i18next";
|
||||
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 type { Nature } from "#enums/nature";
|
||||
|
||||
|
@ -26,7 +26,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
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/common";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
|
@ -37,7 +37,7 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun
|
||||
import { BerryModifier } from "#app/modifier/modifier";
|
||||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||
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 */
|
||||
const namespace = "mysteryEncounters/uncommonBreed";
|
||||
|
@ -424,6 +424,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
||||
console.log(
|
||||
`Pokemon: ${getPokemonNameWithAffix(enemyPokemon)}`,
|
||||
`| Species ID: ${enemyPokemon.species.speciesId}`,
|
||||
`| Level: ${enemyPokemon.level}`,
|
||||
`| Nature: ${getNatureName(enemyPokemon.nature, true, true, true)}`,
|
||||
);
|
||||
console.log(`Stats (IVs): ${stats}`);
|
||||
@ -1075,8 +1076,8 @@ export function getRandomEncounterSpecies(level: number, isBoss = false, rerollH
|
||||
ret.formIndex = formIndex;
|
||||
}
|
||||
|
||||
//Reroll shiny for event encounters
|
||||
if (isEventEncounter && !ret.shiny) {
|
||||
//Reroll shiny or variant for event encounters
|
||||
if (isEventEncounter) {
|
||||
ret.trySetShinySeed();
|
||||
}
|
||||
//Reroll hidden ability
|
||||
|
@ -488,6 +488,7 @@ export abstract class PokemonSpeciesForm {
|
||||
if (formSpriteKey.startsWith("behemoth")) {
|
||||
formSpriteKey = "crowned";
|
||||
}
|
||||
// biome-ignore lint/suspicious/no-fallthrough: Falls through
|
||||
default:
|
||||
ret += `-${formSpriteKey}`;
|
||||
break;
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { startingWave } from "#app/starting-wave";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import { GameModes } from "#app/game-mode";
|
||||
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
||||
|
||||
export class TrainerPartyTemplate {
|
||||
public size: number;
|
||||
@ -222,19 +224,18 @@ export const trainerPartyTemplates = {
|
||||
*/
|
||||
export function getEvilGruntPartyTemplate(): TrainerPartyTemplate {
|
||||
const waveIndex = globalScene.currentBattle?.waveIndex;
|
||||
if (waveIndex < 40) {
|
||||
return trainerPartyTemplates.TWO_AVG;
|
||||
switch (waveIndex) {
|
||||
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[]) {
|
||||
@ -245,11 +246,36 @@ export function getWavePartyTemplate(...templates: TrainerPartyTemplate[]) {
|
||||
}
|
||||
|
||||
export function getGymLeaderPartyTemplate() {
|
||||
return getWavePartyTemplate(
|
||||
trainerPartyTemplates.GYM_LEADER_1,
|
||||
trainerPartyTemplates.GYM_LEADER_2,
|
||||
trainerPartyTemplates.GYM_LEADER_3,
|
||||
trainerPartyTemplates.GYM_LEADER_4,
|
||||
trainerPartyTemplates.GYM_LEADER_5,
|
||||
);
|
||||
const { currentBattle, gameMode } = globalScene;
|
||||
switch (gameMode.modeId) {
|
||||
case GameModes.DAILY:
|
||||
if (currentBattle?.waveIndex <= 20) {
|
||||
return trainerPartyTemplates.GYM_LEADER_2
|
||||
}
|
||||
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
@ -369,6 +369,7 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
|
||||
if (hasSun) {
|
||||
weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 });
|
||||
}
|
||||
break;
|
||||
case Biome.VOLCANO:
|
||||
weatherPool = [
|
||||
{
|
||||
|
@ -1,7 +1,6 @@
|
||||
export enum MoveEffectTrigger {
|
||||
PRE_APPLY,
|
||||
POST_APPLY,
|
||||
HIT,
|
||||
/** Triggers one time after all target effects have applied */
|
||||
POST_TARGET
|
||||
}
|
||||
|
22
src/enums/fixed-boss-waves.ts
Normal file
22
src/enums/fixed-boss-waves.ts
Normal 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
|
||||
}
|
23
src/enums/hit-check-result.ts
Normal file
23
src/enums/hit-check-result.ts
Normal 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];
|
@ -12,7 +12,6 @@ import BattleInfo, {
|
||||
import type Move from "#app/data/moves/move";
|
||||
import {
|
||||
HighCritAttr,
|
||||
StatChangeBeforeDmgCalcAttr,
|
||||
HitsTagAttr,
|
||||
applyMoveAttrs,
|
||||
FixedDamageAttr,
|
||||
@ -70,10 +69,8 @@ import {
|
||||
EFFECTIVE_STATS,
|
||||
} from "#enums/stat";
|
||||
import {
|
||||
DamageMoneyRewardModifier,
|
||||
EnemyDamageBoosterModifier,
|
||||
EnemyDamageReducerModifier,
|
||||
EnemyEndureChanceModifier,
|
||||
EnemyFusionChanceModifier,
|
||||
HiddenAbilityRateBoosterModifier,
|
||||
BaseStatModifier,
|
||||
@ -119,7 +116,6 @@ import {
|
||||
TypeImmuneTag,
|
||||
getBattlerTag,
|
||||
SemiInvulnerableTag,
|
||||
TypeBoostTag,
|
||||
MoveRestrictionBattlerTag,
|
||||
ExposedTag,
|
||||
DragonCheerTag,
|
||||
@ -188,7 +184,7 @@ import {
|
||||
PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr,
|
||||
applyAllyStatMultiplierAbAttrs,
|
||||
AllyStatMultiplierAbAttr,
|
||||
MoveAbilityBypassAbAttr
|
||||
MoveAbilityBypassAbAttr,
|
||||
} from "#app/data/abilities/ability";
|
||||
import { allAbilities } from "#app/data/data-lists";
|
||||
import type PokemonData from "#app/system/pokemon-data";
|
||||
@ -202,7 +198,7 @@ import {
|
||||
EVOLVE_MOVE,
|
||||
RELEARN_MOVE,
|
||||
} 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 { DexAttr } from "#app/system/game-data";
|
||||
import {
|
||||
@ -248,6 +244,7 @@ import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||
import { getStatusEffectOverlapText } from "#app/data/status-effect";
|
||||
import {
|
||||
BASE_HIDDEN_ABILITY_CHANCE,
|
||||
BASE_SHINY_CHANCE,
|
||||
@ -277,6 +274,36 @@ export enum FieldPosition {
|
||||
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 {
|
||||
public id: number;
|
||||
public name: string;
|
||||
@ -1441,25 +1468,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* Calculate the critical-hit stage of a move used against this pokemon by
|
||||
* the given source
|
||||
*
|
||||
* @param source the {@linkcode Pokemon} who using the move
|
||||
* @param move the {@linkcode Move} being used
|
||||
* @returns the final critical-hit stage value
|
||||
* @param source - The {@linkcode Pokemon} who using the move
|
||||
* @param move - The {@linkcode Move} being used
|
||||
* @returns The final critical-hit stage value
|
||||
*/
|
||||
getCritStage(source: Pokemon, move: Move): number {
|
||||
const critStage = new NumberHolder(0);
|
||||
applyMoveAttrs(HighCritAttr, source, this, move, critStage);
|
||||
globalScene.applyModifiers(
|
||||
CritBoosterModifier,
|
||||
source.isPlayer(),
|
||||
source,
|
||||
critStage,
|
||||
);
|
||||
globalScene.applyModifiers(
|
||||
TempCritBoosterModifier,
|
||||
source.isPlayer(),
|
||||
critStage,
|
||||
);
|
||||
applyAbAttrs(BonusCritAbAttr, source, null, false, critStage)
|
||||
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
|
||||
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
|
||||
applyAbAttrs(BonusCritAbAttr, source, null, false, critStage);
|
||||
const critBoostTag = source.getTag(CritBoostTag);
|
||||
if (critBoostTag) {
|
||||
if (critBoostTag instanceof DragonCheerTag) {
|
||||
@ -1475,6 +1493,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
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
|
||||
* items, move effects, opponent abilities, and whether there was a critical
|
||||
@ -2584,7 +2615,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @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 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
|
||||
*/
|
||||
getMoveEffectiveness(
|
||||
@ -3170,7 +3200,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
/**
|
||||
* 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.
|
||||
* 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`
|
||||
* @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)
|
||||
@ -3181,29 +3211,31 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
thresholdOverride?: number,
|
||||
applyModifiersToOverride?: boolean,
|
||||
): boolean {
|
||||
const shinyThreshold = new NumberHolder(BASE_SHINY_CHANCE);
|
||||
if (thresholdOverride === undefined || applyModifiersToOverride) {
|
||||
if (thresholdOverride !== undefined && applyModifiersToOverride) {
|
||||
shinyThreshold.value = thresholdOverride;
|
||||
}
|
||||
if (timedEventManager.isEventActive()) {
|
||||
shinyThreshold.value *= timedEventManager.getShinyMultiplier();
|
||||
}
|
||||
if (!this.hasTrainer()) {
|
||||
if (!this.shiny) {
|
||||
const shinyThreshold = new NumberHolder(BASE_SHINY_CHANCE);
|
||||
if (thresholdOverride === undefined || applyModifiersToOverride) {
|
||||
if (thresholdOverride !== undefined && applyModifiersToOverride) {
|
||||
shinyThreshold.value = thresholdOverride;
|
||||
}
|
||||
if (timedEventManager.isEventActive()) {
|
||||
shinyThreshold.value *= timedEventManager.getShinyMultiplier();
|
||||
}
|
||||
globalScene.applyModifiers(
|
||||
ShinyRateBoosterModifier,
|
||||
true,
|
||||
shinyThreshold,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
shinyThreshold.value = thresholdOverride;
|
||||
else {
|
||||
shinyThreshold.value = thresholdOverride;
|
||||
}
|
||||
|
||||
this.shiny = randSeedInt(65536) < shinyThreshold.value;
|
||||
}
|
||||
|
||||
this.shiny = randSeedInt(65536) < shinyThreshold.value;
|
||||
|
||||
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.variant + 1 + (this.fusionShiny ? this.fusionVariant + 1 : 0);
|
||||
this.initShinySparkle();
|
||||
@ -4073,27 +4105,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.
|
||||
* Used during damage calculation and for Shell Side Arm's forecasting effect.
|
||||
* @param source the attacking {@linkcode Pokemon}.
|
||||
* @param move the {@linkcode Move} used in the attack.
|
||||
* @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 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 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 simulated if `true`, suppresses changes to game state during calculation (defaults to `true`).
|
||||
* @param source - The attacking {@linkcode Pokemon}.
|
||||
* @param move - The {@linkcode Move} used in the attack.
|
||||
* @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 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 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 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.
|
||||
*/
|
||||
getBaseDamage(
|
||||
source: Pokemon,
|
||||
move: Move,
|
||||
moveCategory: MoveCategory,
|
||||
{
|
||||
source,
|
||||
move,
|
||||
moveCategory,
|
||||
ignoreAbility = false,
|
||||
ignoreSourceAbility = false,
|
||||
ignoreAllyAbility = false,
|
||||
ignoreSourceAllyAbility = false,
|
||||
isCritical = false,
|
||||
simulated = true,
|
||||
simulated = true}: getBaseDamageParams
|
||||
): number {
|
||||
const isPhysical = moveCategory === MoveCategory.PHYSICAL;
|
||||
|
||||
@ -4220,27 +4253,27 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
/**
|
||||
* Calculates the damage of an attack made by another Pokemon against this 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 ignoreSourceAbility If `true`, ignores the attacking 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 isCritical If `true`, calculates damage for a critical hit.
|
||||
* @param simulated If `true`, suppresses changes to game state during the calculation.
|
||||
* @returns a {@linkcode DamageCalculationResult} object with three fields:
|
||||
* - `cancelled`: `true` if the move was cancelled by another effect.
|
||||
* - `result`: {@linkcode HitResult} indicates the attack's type effectiveness.
|
||||
* - `damage`: `number` the attack's final damage output.
|
||||
* @param effectiveness If defined, used in place of calculated effectiveness values
|
||||
* @returns The {@linkcode DamageCalculationResult}
|
||||
*/
|
||||
getAttackDamage(
|
||||
source: Pokemon,
|
||||
move: Move,
|
||||
ignoreAbility = false,
|
||||
ignoreSourceAbility = false,
|
||||
ignoreAllyAbility = false,
|
||||
ignoreSourceAllyAbility = false,
|
||||
isCritical = false,
|
||||
simulated = true,
|
||||
{
|
||||
source,
|
||||
move,
|
||||
ignoreAbility = false,
|
||||
ignoreSourceAbility = false,
|
||||
ignoreAllyAbility = false,
|
||||
ignoreSourceAllyAbility = false,
|
||||
isCritical = false,
|
||||
simulated = true,
|
||||
effectiveness}: getAttackDamageParams,
|
||||
): DamageCalculationResult {
|
||||
const damage = new NumberHolder(0);
|
||||
const defendingSide = this.isPlayer()
|
||||
@ -4270,7 +4303,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
*
|
||||
* Note that the source's abilities are not ignored here
|
||||
*/
|
||||
const typeMultiplier = this.getMoveEffectiveness(
|
||||
const typeMultiplier = effectiveness ?? this.getMoveEffectiveness(
|
||||
source,
|
||||
move,
|
||||
ignoreAbility,
|
||||
@ -4342,7 +4375,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* 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
|
||||
*/
|
||||
const baseDamage = this.getBaseDamage(
|
||||
const baseDamage = this.getBaseDamage({
|
||||
source,
|
||||
move,
|
||||
moveCategory,
|
||||
@ -4352,7 +4385,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
ignoreSourceAllyAbility,
|
||||
isCritical,
|
||||
simulated,
|
||||
);
|
||||
});
|
||||
|
||||
/** 25% damage debuff on moves hitting more than one non-fainted target (regardless of immunities) */
|
||||
const { targets, multiple } = getMoveTargets(source, move.id);
|
||||
@ -4563,211 +4596,36 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the results of a move to this pokemon
|
||||
* @param source The {@linkcode Pokemon} using the move
|
||||
* @param move The {@linkcode Move} being used
|
||||
* @returns The {@linkcode HitResult} of the attack
|
||||
*/
|
||||
apply(source: Pokemon, move: Move): HitResult {
|
||||
const defendingSide = this.isPlayer()
|
||||
? ArenaTagSide.PLAYER
|
||||
: ArenaTagSide.ENEMY;
|
||||
const moveCategory = new NumberHolder(move.category);
|
||||
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;
|
||||
/** Calculate whether the given move critically hits this pokemon
|
||||
* @param source - The {@linkcode Pokemon} using the move
|
||||
* @param move - The {@linkcode Move} being used
|
||||
* @param simulated - If `true`, suppresses changes to game state during calculation (defaults to `true`)
|
||||
* @returns whether the move critically hits the pokemon
|
||||
*/
|
||||
getCriticalHitResult(source: Pokemon, move: Move, simulated: boolean = true): boolean {
|
||||
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide);
|
||||
if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr(FixedDamageAttr)) {
|
||||
return false;
|
||||
}
|
||||
/** Determines whether the attack critically hits */
|
||||
let isCritical: boolean;
|
||||
const critOnly = new BooleanHolder(false);
|
||||
const critAlways = source.getTag(BattlerTagType.ALWAYS_CRIT);
|
||||
applyMoveAttrs(CritOnlyAttr, source, this, move, critOnly);
|
||||
applyAbAttrs(
|
||||
ConditionalCritAbAttr,
|
||||
source,
|
||||
null,
|
||||
false,
|
||||
critOnly,
|
||||
this,
|
||||
move,
|
||||
);
|
||||
if (critOnly.value || critAlways) {
|
||||
isCritical = true;
|
||||
} else {
|
||||
const isCritical = new BooleanHolder(false);
|
||||
|
||||
if (source.getTag(BattlerTagType.ALWAYS_CRIT)) {
|
||||
isCritical.value = true;
|
||||
}
|
||||
applyMoveAttrs(CritOnlyAttr, source, this, move, isCritical);
|
||||
applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move);
|
||||
if (!isCritical.value) {
|
||||
const critChance = [24, 8, 2, 1][
|
||||
Math.max(0, Math.min(this.getCritStage(source, move), 3))
|
||||
];
|
||||
isCritical =
|
||||
critChance === 1 || !globalScene.randBattleSeedInt(critChance);
|
||||
isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance);
|
||||
}
|
||||
|
||||
const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide);
|
||||
const blockCrit = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockCritAbAttr, this, null, false, blockCrit);
|
||||
if (noCritTag || blockCrit.value || Overrides.NEVER_CRIT_OVERRIDE) {
|
||||
isCritical = false;
|
||||
}
|
||||
applyAbAttrs(BlockCritAbAttr, this, null, simulated, isCritical);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
return isCritical.value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4831,7 +4689,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
|
||||
* @param damage integer - passed to damage()
|
||||
* @param result an enum if it's super effective, not very, etc.
|
||||
@ -5134,8 +4993,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
/**
|
||||
* Gets whether the given move is currently disabled for this Pokemon.
|
||||
*
|
||||
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
|
||||
* @returns {boolean} `true` if the move is disabled for this Pokemon, otherwise `false`
|
||||
* @param moveId - The {@linkcode Moves} ID of the move to check
|
||||
* @returns `true` if the move is disabled for this Pokemon, otherwise `false`
|
||||
*
|
||||
* @see {@linkcode MoveRestrictionBattlerTag}
|
||||
*/
|
||||
@ -5146,9 +5005,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
|
||||
*
|
||||
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
|
||||
* @param {Pokemon} user {@linkcode Pokemon} the move user
|
||||
* @param {Pokemon} target {@linkcode Pokemon} the target of the move
|
||||
* @param moveId - The {@linkcode Moves} ID of the move to check
|
||||
* @param user - The move user
|
||||
* @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
|
||||
*
|
||||
@ -5178,10 +5037,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
/**
|
||||
* Gets the {@link MoveRestrictionBattlerTag} that is restricting a move, if it exists.
|
||||
*
|
||||
* @param {Moves} 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 {Pokemon} 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.
|
||||
* @param moveId - {@linkcode Moves} ID of the move to check
|
||||
* @param user - {@linkcode Pokemon} the move user, 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 The first tag on this Pokemon that restricts the move, or `null` if the move is not restricted.
|
||||
*/
|
||||
getRestrictingTag(
|
||||
moveId: Moves,
|
||||
@ -5243,20 +5102,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
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> {
|
||||
return new Promise(resolve => {
|
||||
this.formIndex = Math.max(
|
||||
@ -5516,6 +5361,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.
|
||||
*
|
||||
@ -5534,6 +5391,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
): boolean {
|
||||
if (effect !== StatusEffect.FAINT) {
|
||||
if (overrideStatus ? this.status?.effect === effect : this.status) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
@ -5541,18 +5399,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
!ignoreField &&
|
||||
globalScene.arena.terrain?.terrainType === TerrainType.MISTY
|
||||
) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
sourcePokemon &&
|
||||
sourcePokemon !== this &&
|
||||
this.isSafeguarded(sourcePokemon)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const types = this.getTypes(true, true);
|
||||
|
||||
switch (effect) {
|
||||
@ -5581,17 +5432,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (this.isOfType(PokemonType.POISON) || this.isOfType(PokemonType.STEEL)) {
|
||||
if (poisonImmunity.includes(true)) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case StatusEffect.PARALYSIS:
|
||||
if (this.isOfType(PokemonType.ELECTRIC)) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
@ -5600,6 +5453,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.isGrounded() &&
|
||||
globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC
|
||||
) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
@ -5612,11 +5466,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
globalScene.arena.weather.weatherType,
|
||||
))
|
||||
) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case StatusEffect.BURN:
|
||||
if (this.isOfType(PokemonType.FIRE)) {
|
||||
this.queueImmuneMessage(quiet, effect);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
@ -5651,6 +5507,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
sourcePokemon &&
|
||||
sourcePokemon !== this &&
|
||||
this.isSafeguarded(sourcePokemon)
|
||||
) {
|
||||
if(!quiet){
|
||||
globalScene.queueMessage(
|
||||
i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this)
|
||||
}));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -5660,9 +5529,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
sourcePokemon: Pokemon | null = null,
|
||||
turnsRemaining = 0,
|
||||
sourceText: string | null = null,
|
||||
overrideStatus?: boolean
|
||||
overrideStatus?: boolean,
|
||||
quiet = true,
|
||||
): boolean {
|
||||
if (!this.canSetStatus(effect, asPhase, overrideStatus, sourcePokemon)) {
|
||||
if (!this.canSetStatus(effect, quiet, overrideStatus, sourcePokemon)) {
|
||||
return false;
|
||||
}
|
||||
if (this.isFainted() && effect !== StatusEffect.FAINT) {
|
||||
@ -5674,7 +5544,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* cancel the attack's subsequent hits.
|
||||
*/
|
||||
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) {
|
||||
@ -7153,6 +7027,15 @@ export class EnemyPokemon extends Pokemon {
|
||||
}
|
||||
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 =
|
||||
@ -7309,14 +7192,15 @@ export class EnemyPokemon extends Pokemon {
|
||||
].includes(move.id);
|
||||
return (
|
||||
doesNotFail &&
|
||||
p.getAttackDamage(
|
||||
this,
|
||||
p.getAttackDamage({
|
||||
source: this,
|
||||
move,
|
||||
!p.battleData.abilityRevealed,
|
||||
false,
|
||||
!p.getAlly()?.battleData.abilityRevealed,
|
||||
false,
|
||||
ignoreAbility: !p.battleData.abilityRevealed,
|
||||
ignoreSourceAbility: false,
|
||||
ignoreAllyAbility: !p.getAlly()?.battleData.abilityRevealed,
|
||||
ignoreSourceAllyAbility: false,
|
||||
isCritical,
|
||||
}
|
||||
).damage >= p.hp
|
||||
);
|
||||
})
|
||||
|
@ -13,6 +13,7 @@ import { Species } from "#enums/species";
|
||||
import { Challenges } from "./enums/challenges";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getDailyStartingBiome } from "./data/daily-run";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES } from "./constants";
|
||||
|
||||
export enum GameModes {
|
||||
CLASSIC,
|
||||
@ -36,10 +37,6 @@ interface GameModeConfig {
|
||||
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 {
|
||||
public modeId: GameModes;
|
||||
public isClassic: boolean;
|
||||
|
@ -93,7 +93,7 @@ const startGame = async (manifest?: any) => {
|
||||
dom: {
|
||||
createContainer: true,
|
||||
},
|
||||
pixelArt: true,
|
||||
antialias: false,
|
||||
pipeline: [InvertPostFX] as unknown as Phaser.Types.Core.PipelineConfig,
|
||||
scene: [LoadingScene, BattleScene],
|
||||
version: version,
|
||||
|
@ -14,6 +14,7 @@ import { BattleEndPhase } from "./battle-end-phase";
|
||||
import { NewBattlePhase } from "./new-battle-phase";
|
||||
import { PokemonPhase } from "./pokemon-phase";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { SelectBiomePhase } from "./select-biome-phase";
|
||||
|
||||
export class AttemptRunPhase extends PokemonPhase {
|
||||
/** For testing purposes: this is to force the pokemon to fail and escape */
|
||||
@ -59,6 +60,11 @@ export class AttemptRunPhase extends PokemonPhase {
|
||||
});
|
||||
|
||||
globalScene.pushPhase(new BattleEndPhase(false));
|
||||
|
||||
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
|
||||
globalScene.pushPhase(new SelectBiomePhase());
|
||||
}
|
||||
|
||||
globalScene.pushPhase(new NewBattlePhase());
|
||||
} else {
|
||||
playerPokemon.turnData.failedRunAway = true;
|
||||
|
@ -2,7 +2,12 @@ import { BattlerIndex } from "#app/battle";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||
import { applyAbAttrs, SyncEncounterNatureAbAttr, applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/abilities/ability";
|
||||
import {
|
||||
applyAbAttrs,
|
||||
SyncEncounterNatureAbAttr,
|
||||
applyPreSummonAbAttrs,
|
||||
PreSummonAbAttr,
|
||||
} from "#app/data/abilities/ability";
|
||||
import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims";
|
||||
import { getCharVariantFromDialogue } from "#app/data/dialogue";
|
||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
@ -196,6 +201,7 @@ export class EncounterPhase extends BattlePhase {
|
||||
console.log(
|
||||
`Pokemon: ${getPokemonNameWithAffix(enemyPokemon)}`,
|
||||
`| Species ID: ${enemyPokemon.species.speciesId}`,
|
||||
`| Level: ${enemyPokemon.level}`,
|
||||
`| Nature: ${getNatureName(enemyPokemon.nature, true, true, true)}`,
|
||||
);
|
||||
console.log(`Stats (IVs): ${stats}`);
|
||||
|
@ -35,19 +35,19 @@ import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
|
||||
export class FaintPhase extends PokemonPhase {
|
||||
/**
|
||||
* Whether or not enduring (for this phase's purposes, Reviver Seed) should be prevented
|
||||
* Whether or not instant revive should be prevented
|
||||
*/
|
||||
private preventEndure: boolean;
|
||||
private preventInstantRevive: boolean;
|
||||
|
||||
/**
|
||||
* The source Pokemon that dealt fatal damage
|
||||
*/
|
||||
private source?: Pokemon;
|
||||
|
||||
constructor(battlerIndex: BattlerIndex, preventEndure = false, source?: Pokemon) {
|
||||
constructor(battlerIndex: BattlerIndex, preventInstantRevive = false, source?: Pokemon) {
|
||||
super(battlerIndex);
|
||||
|
||||
this.preventEndure = preventEndure;
|
||||
this.preventInstantRevive = preventInstantRevive;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ export class FaintPhase extends PokemonPhase {
|
||||
|
||||
faintPokemon.resetSummonData();
|
||||
|
||||
if (!this.preventEndure) {
|
||||
if (!this.preventInstantRevive) {
|
||||
const instantReviveModifier = globalScene.applyModifier(
|
||||
PokemonInstantReviveModifier,
|
||||
this.player,
|
||||
|
@ -31,6 +31,7 @@ import ChallengeData from "#app/system/challenge-data";
|
||||
import TrainerData from "#app/system/trainer-data";
|
||||
import ArenaData from "#app/system/arena-data";
|
||||
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
|
||||
import { MessagePhase } from "./message-phase";
|
||||
|
||||
export class GameOverPhase extends BattlePhase {
|
||||
private isVictory: boolean;
|
||||
@ -122,7 +123,7 @@ export class GameOverPhase extends BattlePhase {
|
||||
globalScene.disableMenu = true;
|
||||
globalScene.time.delayedCall(1000, () => {
|
||||
let firstClear = false;
|
||||
if (this.isVictory && newClear) {
|
||||
if (this.isVictory) {
|
||||
if (globalScene.gameMode.isClassic) {
|
||||
firstClear = globalScene.validateAchv(achvs.CLASSIC_VICTORY);
|
||||
globalScene.validateAchv(achvs.UNEVOLVED_CLASSIC_VICTORY);
|
||||
@ -226,7 +227,17 @@ export class GameOverPhase extends BattlePhase {
|
||||
isVictory: this.isVictory,
|
||||
clientSessionId: clientSessionId,
|
||||
})
|
||||
.then(success => doGameOver(!!success));
|
||||
.then(success => doGameOver(!globalScene.gameMode.isDaily || !!success))
|
||||
.catch(_err => {
|
||||
globalScene.clearPhaseQueue();
|
||||
globalScene.clearPhaseQueueSplice();
|
||||
globalScene.unshiftPhase(new MessagePhase(i18next.t("menu:serverCommunicationFailed"), 2500));
|
||||
// force the game to reload after 2 seconds.
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
this.end();
|
||||
});
|
||||
} else if (this.isVictory) {
|
||||
globalScene.gameData.offlineNewClear().then(result => {
|
||||
doGameOver(result);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -404,9 +404,10 @@ export class MovePhase extends BattlePhase {
|
||||
* if the move fails.
|
||||
*/
|
||||
if (success) {
|
||||
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove());
|
||||
const move = this.move.getMove();
|
||||
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, move);
|
||||
globalScene.unshiftPhase(
|
||||
new MoveEffectPhase(this.pokemon.getBattlerIndex(), this.targets, this.move, this.reflected),
|
||||
new MoveEffectPhase(this.pokemon.getBattlerIndex(), this.targets, move, this.reflected, this.move.virtual),
|
||||
);
|
||||
} else {
|
||||
if ([Moves.ROAR, Moves.WHIRLWIND, Moves.TRICK_OR_TREAT, Moves.FORESTS_CURSE].includes(this.move.moveId)) {
|
||||
|
@ -27,6 +27,7 @@ import { IvScannerModifier } from "../modifier/modifier";
|
||||
import { Phase } from "../phase";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import { isNullOrUndefined, randSeedItem } from "#app/utils/common";
|
||||
import { SelectBiomePhase } from "./select-biome-phase";
|
||||
|
||||
/**
|
||||
* Will handle (in order):
|
||||
@ -612,6 +613,10 @@ export class PostMysteryEncounterPhase extends Phase {
|
||||
*/
|
||||
continueEncounter() {
|
||||
const endPhase = () => {
|
||||
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
|
||||
globalScene.pushPhase(new SelectBiomePhase());
|
||||
}
|
||||
|
||||
globalScene.pushPhase(new NewBattlePhase());
|
||||
this.end();
|
||||
};
|
||||
|
@ -14,9 +14,10 @@ export class SelectBiomePhase extends BattlePhase {
|
||||
super.start();
|
||||
|
||||
const currentBiome = globalScene.arena.biomeType;
|
||||
const nextWaveIndex = globalScene.currentBattle.waveIndex + 1;
|
||||
|
||||
const setNextBiome = (nextBiome: Biome) => {
|
||||
if (globalScene.currentBattle.waveIndex % 10 === 1) {
|
||||
if (nextWaveIndex % 10 === 1) {
|
||||
globalScene.applyModifiers(MoneyInterestModifier, true);
|
||||
globalScene.unshiftPhase(new PartyHealPhase(false));
|
||||
}
|
||||
@ -25,13 +26,13 @@ export class SelectBiomePhase extends BattlePhase {
|
||||
};
|
||||
|
||||
if (
|
||||
(globalScene.gameMode.isClassic && globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex + 9)) ||
|
||||
(globalScene.gameMode.isDaily && globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) ||
|
||||
(globalScene.gameMode.hasShortBiomes && !(globalScene.currentBattle.waveIndex % 50))
|
||||
(globalScene.gameMode.isClassic && globalScene.gameMode.isWaveFinal(nextWaveIndex + 9)) ||
|
||||
(globalScene.gameMode.isDaily && globalScene.gameMode.isWaveFinal(nextWaveIndex)) ||
|
||||
(globalScene.gameMode.hasShortBiomes && !(nextWaveIndex % 50))
|
||||
) {
|
||||
setNextBiome(Biome.END);
|
||||
} else if (globalScene.gameMode.hasRandomBiomes) {
|
||||
setNextBiome(this.generateNextBiome());
|
||||
setNextBiome(this.generateNextBiome(nextWaveIndex));
|
||||
} else if (Array.isArray(biomeLinks[currentBiome])) {
|
||||
const biomes: Biome[] = (biomeLinks[currentBiome] as (Biome | [Biome, number])[])
|
||||
.filter(b => !Array.isArray(b) || !randSeedInt(b[1]))
|
||||
@ -59,14 +60,14 @@ export class SelectBiomePhase extends BattlePhase {
|
||||
} else if (biomeLinks.hasOwnProperty(currentBiome)) {
|
||||
setNextBiome(biomeLinks[currentBiome] as Biome);
|
||||
} else {
|
||||
setNextBiome(this.generateNextBiome());
|
||||
setNextBiome(this.generateNextBiome(nextWaveIndex));
|
||||
}
|
||||
}
|
||||
|
||||
generateNextBiome(): Biome {
|
||||
if (!(globalScene.currentBattle.waveIndex % 50)) {
|
||||
generateNextBiome(waveIndex: number): Biome {
|
||||
if (!(waveIndex % 50)) {
|
||||
return Biome.END;
|
||||
}
|
||||
return globalScene.generateRandomBiome(globalScene.currentBattle.waveIndex);
|
||||
return globalScene.generateRandomBiome(waveIndex);
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,10 @@ export class SwitchBiomePhase extends BattlePhase {
|
||||
return this.end();
|
||||
}
|
||||
|
||||
// Before switching biomes, make sure to set the last encounter for other phases that need it too.
|
||||
globalScene.lastEnemyTrainer = globalScene.currentBattle?.trainer ?? null;
|
||||
globalScene.lastMysteryEncounter = globalScene.currentBattle?.mysteryEncounter;
|
||||
|
||||
globalScene.tweens.add({
|
||||
targets: [globalScene.arenaEnemy, globalScene.lastEnemyTrainer],
|
||||
x: "+=300",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { BattlerIndex } from "#app/battle";
|
||||
import { ClassicFixedBossWaves } from "#app/battle";
|
||||
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
import type { CustomModifierSettings } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
@ -15,6 +15,7 @@ import { TrainerVictoryPhase } from "./trainer-victory-phase";
|
||||
import { handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { timedEventManager } from "#app/global-event-manager";
|
||||
import { SelectBiomePhase } from "./select-biome-phase";
|
||||
|
||||
export class VictoryPhase extends PokemonPhase {
|
||||
/** If true, indicates that the phase is intended for EXP purposes only, and not to continue a battle to next phase */
|
||||
@ -111,6 +112,11 @@ export class VictoryPhase extends PokemonPhase {
|
||||
globalScene.pushPhase(new AddEnemyBuffModifierPhase());
|
||||
}
|
||||
}
|
||||
|
||||
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
|
||||
globalScene.pushPhase(new SelectBiomePhase());
|
||||
}
|
||||
|
||||
globalScene.pushPhase(new NewBattlePhase());
|
||||
} else {
|
||||
globalScene.currentBattle.battleType = BattleType.CLEAR;
|
||||
|
@ -20,17 +20,20 @@ export class PokerogueSessionSavedataApi extends ApiBase {
|
||||
* *This is **NOT** the same as {@linkcode clear | clear()}.*
|
||||
* @param params The {@linkcode NewClearSessionSavedataRequest} to send
|
||||
* @returns The raw savedata as `string`.
|
||||
* @throws Error if the request fails
|
||||
*/
|
||||
public async newclear(params: NewClearSessionSavedataRequest) {
|
||||
try {
|
||||
const urlSearchParams = this.toUrlSearchParams(params);
|
||||
const response = await this.doGet(`/savedata/session/newclear?${urlSearchParams}`);
|
||||
const json = await response.json();
|
||||
|
||||
return Boolean(json);
|
||||
if (response.ok) {
|
||||
return Boolean(json);
|
||||
}
|
||||
throw new Error("Could not newclear session!");
|
||||
} catch (err) {
|
||||
console.warn("Could not newclear session!", err);
|
||||
return false;
|
||||
throw new Error("Could not newclear session!");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,14 +15,17 @@ export class PokerogueSystemSavedataApi extends ApiBase {
|
||||
/**
|
||||
* Get a system savedata.
|
||||
* @param params The {@linkcode GetSystemSavedataRequest} to send
|
||||
* @returns The system savedata as `string` or `null` on error
|
||||
* @returns The system savedata as `string` or either the status code or `null` on error
|
||||
*/
|
||||
public async get(params: GetSystemSavedataRequest) {
|
||||
public async get(params: GetSystemSavedataRequest): Promise<string | number | null> {
|
||||
try {
|
||||
const urlSearchParams = this.toUrlSearchParams(params);
|
||||
const response = await this.doGet(`/savedata/system/get?${urlSearchParams}`);
|
||||
const rawSavedata = await response.text();
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn("Could not get system savedata!", response.status, rawSavedata);
|
||||
return response.status;
|
||||
}
|
||||
return rawSavedata;
|
||||
} catch (err) {
|
||||
console.warn("Could not get system savedata!", err);
|
||||
|
@ -462,8 +462,13 @@ export class GameData {
|
||||
|
||||
if (!bypassLogin) {
|
||||
pokerogueApi.savedata.system.get({ clientSessionId }).then(saveDataOrErr => {
|
||||
if (!saveDataOrErr || saveDataOrErr.length === 0 || saveDataOrErr[0] !== "{") {
|
||||
if (saveDataOrErr?.startsWith("sql: no rows in result set")) {
|
||||
if (
|
||||
typeof saveDataOrErr === "number" ||
|
||||
!saveDataOrErr ||
|
||||
saveDataOrErr.length === 0 ||
|
||||
saveDataOrErr[0] !== "{"
|
||||
) {
|
||||
if (saveDataOrErr === 404) {
|
||||
globalScene.queueMessage(
|
||||
"Save data could not be found. If this is a new account, you can safely ignore this message.",
|
||||
null,
|
||||
@ -471,7 +476,7 @@ export class GameData {
|
||||
);
|
||||
return resolve(true);
|
||||
}
|
||||
if (saveDataOrErr?.includes("Too many connections")) {
|
||||
if (typeof saveDataOrErr === "string" && saveDataOrErr?.includes("Too many connections")) {
|
||||
globalScene.queueMessage(
|
||||
"Too many people are trying to connect and the server is overloaded. Please try again later.",
|
||||
null,
|
||||
@ -479,7 +484,6 @@ export class GameData {
|
||||
);
|
||||
return resolve(false);
|
||||
}
|
||||
console.error(saveDataOrErr);
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
@ -1500,7 +1504,7 @@ export class GameData {
|
||||
link.remove();
|
||||
};
|
||||
if (!bypassLogin && dataType < GameDataType.SETTINGS) {
|
||||
let promise: Promise<string | null> = Promise.resolve(null);
|
||||
let promise: Promise<string | null | number> = Promise.resolve(null);
|
||||
|
||||
if (dataType === GameDataType.SYSTEM) {
|
||||
promise = pokerogueApi.savedata.system.get({ clientSessionId });
|
||||
@ -1512,7 +1516,7 @@ export class GameData {
|
||||
}
|
||||
|
||||
promise.then(response => {
|
||||
if (!response?.length || response[0] !== "{") {
|
||||
if (typeof response === "number" || !response?.length || response[0] !== "{") {
|
||||
console.error(response);
|
||||
resolve(false);
|
||||
return;
|
||||
|
@ -804,6 +804,7 @@ export function setSetting(setting: string, value: number): boolean {
|
||||
break;
|
||||
case SettingKeys.Candy_Upgrade_Display:
|
||||
globalScene.candyUpgradeDisplay = value;
|
||||
break;
|
||||
case SettingKeys.Money_Format:
|
||||
switch (Setting[index].options[value].value) {
|
||||
case "Normal":
|
||||
|
@ -176,11 +176,13 @@ export class UiInputs {
|
||||
return;
|
||||
}
|
||||
switch (globalScene.ui?.getMode()) {
|
||||
case UiMode.MESSAGE:
|
||||
case UiMode.MESSAGE: {
|
||||
const messageHandler = globalScene.ui.getHandler<MessageUiHandler>();
|
||||
if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) {
|
||||
return;
|
||||
}
|
||||
// biome-ignore lint/suspicious/noFallthroughSwitchClause: falls through to show menu overlay
|
||||
}
|
||||
case UiMode.TITLE:
|
||||
case UiMode.COMMAND:
|
||||
case UiMode.MODIFIER_SELECT:
|
||||
|
@ -557,11 +557,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
||||
this.ownedIcon,
|
||||
this.championRibbon,
|
||||
this.statusIndicator,
|
||||
this.levelContainer,
|
||||
this.statValuesContainer,
|
||||
].map(e => (e.x += 48 * (boss ? -1 : 1)));
|
||||
this.hpBar.x += 38 * (boss ? -1 : 1);
|
||||
this.hpBar.y += 2 * (this.boss ? -1 : 1);
|
||||
this.levelContainer.x += 2 * (boss ? -1 : 1);
|
||||
this.hpBar.setTexture(`overlay_hp${boss ? "_boss" : ""}`);
|
||||
this.box.setTexture(this.getTextureName());
|
||||
this.statsBox.setTexture(`${this.getTextureName()}_stats`);
|
||||
|
@ -20,7 +20,6 @@ export class FilterText extends Phaser.GameObjects.Container {
|
||||
private window: Phaser.GameObjects.NineSlice;
|
||||
private labels: Phaser.GameObjects.Text[] = [];
|
||||
private selections: Phaser.GameObjects.Text[] = [];
|
||||
private selectionStrings: string[] = [];
|
||||
private rows: FilterTextRow[] = [];
|
||||
public cursorObj: Phaser.GameObjects.Image;
|
||||
public numFilters = 0;
|
||||
@ -112,8 +111,6 @@ export class FilterText extends Phaser.GameObjects.Container {
|
||||
this.selections.push(filterTypesSelection);
|
||||
this.add(filterTypesSelection);
|
||||
|
||||
this.selectionStrings.push("");
|
||||
|
||||
this.calcFilterPositions();
|
||||
this.numFilters++;
|
||||
|
||||
@ -122,7 +119,6 @@ export class FilterText extends Phaser.GameObjects.Container {
|
||||
|
||||
resetSelection(index: number): void {
|
||||
this.selections[index].setText(this.defaultText);
|
||||
this.selectionStrings[index] = "";
|
||||
this.onChange();
|
||||
}
|
||||
|
||||
@ -204,6 +200,17 @@ export class FilterText extends Phaser.GameObjects.Container {
|
||||
return this.selections[row].getWrappedText()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcibly set the selection text for a specific filter row and then call the `onChange` function
|
||||
*
|
||||
* @param row - The filter row to set the text for
|
||||
* @param value - The text to set for the filter row
|
||||
*/
|
||||
setValue(row: FilterTextRow, value: string) {
|
||||
this.selections[row].setText(value);
|
||||
this.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nearest filter to the provided container on the y-axis
|
||||
* @param container the StarterContainer to compare position against
|
||||
|
@ -292,6 +292,13 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
starterSelectBg.setOrigin(0, 0);
|
||||
this.starterSelectContainer.add(starterSelectBg);
|
||||
|
||||
this.pokemonSprite = globalScene.add.sprite(53, 63, "pkmn__sub");
|
||||
this.pokemonSprite.setPipeline(globalScene.spritePipeline, {
|
||||
tone: [0.0, 0.0, 0.0, 0.0],
|
||||
ignoreTimeTint: true,
|
||||
});
|
||||
this.starterSelectContainer.add(this.pokemonSprite);
|
||||
|
||||
this.shinyOverlay = globalScene.add.image(6, 6, "summary_overlay_shiny");
|
||||
this.shinyOverlay.setOrigin(0, 0);
|
||||
this.shinyOverlay.setVisible(false);
|
||||
@ -343,13 +350,6 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
|
||||
this.starterSelectContainer.add(starterBoxContainer);
|
||||
|
||||
this.pokemonSprite = globalScene.add.sprite(53, 63, "pkmn__sub");
|
||||
this.pokemonSprite.setPipeline(globalScene.spritePipeline, {
|
||||
tone: [0.0, 0.0, 0.0, 0.0],
|
||||
ignoreTimeTint: true,
|
||||
});
|
||||
this.starterSelectContainer.add(this.pokemonSprite);
|
||||
|
||||
this.type1Icon = globalScene.add.sprite(8, 98, getLocalizedSpriteKey("types"));
|
||||
this.type1Icon.setScale(0.5);
|
||||
this.type1Icon.setOrigin(0, 0);
|
||||
@ -921,16 +921,22 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
return biomes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the caughtAttr of a given species, sanitized.
|
||||
*
|
||||
* @param otherSpecies The species to check; defaults to current species
|
||||
* @returns caught DexAttr for the species
|
||||
*/
|
||||
isCaught(otherSpecies?: PokemonSpecies): bigint {
|
||||
const species = otherSpecies ? otherSpecies : this.species;
|
||||
|
||||
if (globalScene.dexForDevs) {
|
||||
return 255n;
|
||||
species.getFullUnlocksData();
|
||||
}
|
||||
|
||||
const species = otherSpecies ? otherSpecies : this.species;
|
||||
const dexEntry = globalScene.gameData.dexData[species.speciesId];
|
||||
const starterDexEntry = globalScene.gameData.dexData[this.getStarterSpeciesId(species.speciesId)];
|
||||
|
||||
return (dexEntry?.caughtAttr ?? 0n) & (starterDexEntry?.caughtAttr ?? 0n) & species.getFullUnlocksData();
|
||||
return (dexEntry?.caughtAttr ?? 0n) & species.getFullUnlocksData();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -939,7 +945,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
*
|
||||
* @param otherSpecies The species to check; defaults to current species
|
||||
* @param otherFormIndex The form index of the form to check; defaults to current form
|
||||
* @returns StarterAttributes for the species
|
||||
* @returns `true` if the form is caught
|
||||
*/
|
||||
isFormCaught(otherSpecies?: PokemonSpecies, otherFormIndex?: number | undefined): boolean {
|
||||
if (globalScene.dexForDevs) {
|
||||
@ -954,6 +960,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
}
|
||||
|
||||
const isFormCaught = (caughtAttr & globalScene.gameData.getFormAttr(formIndex ?? 0)) > 0n;
|
||||
|
||||
return isFormCaught;
|
||||
}
|
||||
|
||||
@ -1151,7 +1158,6 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
||||
this.blockInput = false;
|
||||
} else {
|
||||
ui.revertMode().then(() => {
|
||||
console.log("exitCallback", this.exitCallback);
|
||||
if (this.exitCallback instanceof Function) {
|
||||
const exitCallback = this.exitCallback;
|
||||
this.exitCallback = null;
|
||||
|
@ -37,10 +37,9 @@ import { addWindow } from "./ui-theme";
|
||||
import type { OptionSelectConfig } from "./abstact-option-select-ui-handler";
|
||||
import { FilterText, FilterTextRow } from "./filter-text";
|
||||
import { allAbilities } from "#app/data/data-lists";
|
||||
import { starterPassiveAbilities } from "#app/data/balance/passives";
|
||||
import { allMoves } from "#app/data/moves/move";
|
||||
import { speciesTmMoves } from "#app/data/balance/tms";
|
||||
import { pokemonPrevolutions, pokemonStarters } from "#app/data/balance/pokemon-evolutions";
|
||||
import { pokemonStarters } from "#app/data/balance/pokemon-evolutions";
|
||||
import { Biome } from "#enums/biome";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
@ -174,7 +173,6 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
||||
private scrollCursor: number;
|
||||
private oldCursor = -1;
|
||||
|
||||
private allSpecies: PokemonSpecies[] = [];
|
||||
private lastSpecies: PokemonSpecies;
|
||||
private speciesLoaded: Map<Species, boolean> = new Map<Species, boolean>();
|
||||
private pokerusSpecies: PokemonSpecies[] = [];
|
||||
@ -493,12 +491,11 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
||||
|
||||
for (const species of allSpecies) {
|
||||
this.speciesLoaded.set(species.speciesId, false);
|
||||
this.allSpecies.push(species);
|
||||
}
|
||||
|
||||
// Here code to declare 81 containers
|
||||
for (let i = 0; i < 81; i++) {
|
||||
const pokemonContainer = new PokedexMonContainer(this.allSpecies[i]).setVisible(false);
|
||||
const pokemonContainer = new PokedexMonContainer(allSpecies[i]).setVisible(false);
|
||||
const pos = calcStarterPosition(i);
|
||||
pokemonContainer.setPosition(pos.x, pos.y);
|
||||
this.iconAnimHandler.addOrUpdate(pokemonContainer.icon, PokemonIconAnimMode.NONE);
|
||||
@ -1342,7 +1339,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
||||
|
||||
this.filteredPokemonData = [];
|
||||
|
||||
this.allSpecies.forEach(species => {
|
||||
allSpecies.forEach(species => {
|
||||
const starterId = this.getStarterSpeciesId(species.speciesId);
|
||||
|
||||
const currentDexAttr = this.getCurrentDexProps(species.speciesId);
|
||||
@ -1412,12 +1409,11 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
||||
|
||||
// Ability filter
|
||||
const abilities = [species.ability1, species.ability2, species.abilityHidden].map(a => allAbilities[a].name);
|
||||
const passiveId = starterPassiveAbilities.hasOwnProperty(species.speciesId)
|
||||
? species.speciesId
|
||||
: starterPassiveAbilities.hasOwnProperty(starterId)
|
||||
? starterId
|
||||
: pokemonPrevolutions[starterId];
|
||||
const passives = starterPassiveAbilities[passiveId];
|
||||
// get the passive ability for the species
|
||||
const passives = [species.getPassiveAbility()];
|
||||
for (const form of species.forms) {
|
||||
passives.push(form.getPassiveAbility());
|
||||
}
|
||||
|
||||
const selectedAbility1 = this.filterText.getValue(FilterTextRow.ABILITY_1);
|
||||
const fitsFormAbility1 = species.forms.some(form =>
|
||||
|
@ -596,6 +596,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.iconAnimHandler = new PokemonIconAnimHandler();
|
||||
this.iconAnimHandler.setup();
|
||||
|
||||
this.pokemonSprite = globalScene.add.sprite(53, 63, "pkmn__sub");
|
||||
this.pokemonSprite.setPipeline(globalScene.spritePipeline, {
|
||||
tone: [0.0, 0.0, 0.0, 0.0],
|
||||
ignoreTimeTint: true,
|
||||
});
|
||||
this.starterSelectContainer.add(this.pokemonSprite);
|
||||
|
||||
this.pokemonNumberText = addTextObject(17, 1, "0000", TextStyle.SUMMARY);
|
||||
this.pokemonNumberText.setOrigin(0, 0);
|
||||
this.starterSelectContainer.add(this.pokemonNumberText);
|
||||
@ -825,13 +832,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
return icon;
|
||||
});
|
||||
|
||||
this.pokemonSprite = globalScene.add.sprite(53, 63, "pkmn__sub");
|
||||
this.pokemonSprite.setPipeline(globalScene.spritePipeline, {
|
||||
tone: [0.0, 0.0, 0.0, 0.0],
|
||||
ignoreTimeTint: true,
|
||||
});
|
||||
this.starterSelectContainer.add(this.pokemonSprite);
|
||||
|
||||
this.type1Icon = globalScene.add.sprite(8, 98, getLocalizedSpriteKey("types"));
|
||||
this.type1Icon.setScale(0.5);
|
||||
this.type1Icon.setOrigin(0, 0);
|
||||
|
@ -186,7 +186,7 @@ describe("Abilities - Disguise", () => {
|
||||
await game.toNextTurn();
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.doKillOpponents();
|
||||
await game.phaseInterceptor.to("PartyHealPhase");
|
||||
await game.phaseInterceptor.to("QuietFormChangePhase");
|
||||
|
||||
expect(mimikyu1.formIndex).toBe(disguisedForm);
|
||||
});
|
||||
|
@ -50,7 +50,11 @@ describe("Moves - Friend Guard", () => {
|
||||
// Get the last return value from `getAttackDamage`
|
||||
const turn1Damage = spy.mock.results[spy.mock.results.length - 1].value.damage;
|
||||
// Making sure the test is controlled; turn 1 damage is equal to base damage (after rounding)
|
||||
expect(turn1Damage).toBe(Math.floor(player1.getBaseDamage(enemy1, allMoves[Moves.TACKLE], MoveCategory.PHYSICAL)));
|
||||
expect(turn1Damage).toBe(
|
||||
Math.floor(
|
||||
player1.getBaseDamage({ source: enemy1, move: allMoves[Moves.TACKLE], moveCategory: MoveCategory.PHYSICAL }),
|
||||
),
|
||||
);
|
||||
|
||||
vi.spyOn(player2, "getAbility").mockReturnValue(allAbilities[Abilities.FRIEND_GUARD]);
|
||||
|
||||
@ -64,7 +68,10 @@ describe("Moves - Friend Guard", () => {
|
||||
const turn2Damage = spy.mock.results[spy.mock.results.length - 1].value.damage;
|
||||
// With the ally's Friend Guard, damage should have been reduced from base damage by 25%
|
||||
expect(turn2Damage).toBe(
|
||||
Math.floor(player1.getBaseDamage(enemy1, allMoves[Moves.TACKLE], MoveCategory.PHYSICAL) * 0.75),
|
||||
Math.floor(
|
||||
player1.getBaseDamage({ source: enemy1, move: allMoves[Moves.TACKLE], moveCategory: MoveCategory.PHYSICAL }) *
|
||||
0.75,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -4,7 +4,6 @@ import { PokemonType } from "#enums/pokemon-type";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species";
|
||||
import { HitResult } from "#app/field/pokemon";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@ -38,13 +37,13 @@ describe("Abilities - Galvanize", () => {
|
||||
});
|
||||
|
||||
it("should change Normal-type attacks to Electric type and boost their power", async () => {
|
||||
await game.startBattle();
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
vi.spyOn(playerPokemon, "getMoveType");
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
vi.spyOn(enemyPokemon, "apply");
|
||||
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||
|
||||
const move = allMoves[Moves.TACKLE];
|
||||
vi.spyOn(move, "calculateBattlePower");
|
||||
@ -54,21 +53,23 @@ describe("Abilities - Galvanize", () => {
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(playerPokemon.getMoveType).toHaveLastReturnedWith(PokemonType.ELECTRIC);
|
||||
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.EFFECTIVE);
|
||||
expect(spy).toHaveReturnedWith(1);
|
||||
expect(move.calculateBattlePower).toHaveReturnedWith(48);
|
||||
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("should cause Normal-type attacks to activate Volt Absorb", async () => {
|
||||
game.override.enemyAbility(Abilities.VOLT_ABSORB);
|
||||
|
||||
await game.startBattle();
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
vi.spyOn(playerPokemon, "getMoveType");
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
vi.spyOn(enemyPokemon, "apply");
|
||||
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||
|
||||
enemyPokemon.hp = Math.floor(enemyPokemon.getMaxHp() * 0.8);
|
||||
|
||||
@ -77,37 +78,37 @@ describe("Abilities - Galvanize", () => {
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(playerPokemon.getMoveType).toHaveLastReturnedWith(PokemonType.ELECTRIC);
|
||||
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.NO_EFFECT);
|
||||
expect(spy).toHaveReturnedWith(0);
|
||||
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||
});
|
||||
|
||||
it("should not change the type of variable-type moves", async () => {
|
||||
game.override.enemySpecies(Species.MIGHTYENA);
|
||||
|
||||
await game.startBattle([Species.ESPEON]);
|
||||
await game.classicMode.startBattle([Species.ESPEON]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
vi.spyOn(playerPokemon, "getMoveType");
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
vi.spyOn(enemyPokemon, "apply");
|
||||
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||
|
||||
game.move.select(Moves.REVELATION_DANCE);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(playerPokemon.getMoveType).not.toHaveLastReturnedWith(PokemonType.ELECTRIC);
|
||||
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.NO_EFFECT);
|
||||
expect(spy).toHaveReturnedWith(0);
|
||||
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||
});
|
||||
|
||||
it("should affect all hits of a Normal-type multi-hit move", async () => {
|
||||
await game.startBattle();
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
vi.spyOn(playerPokemon, "getMoveType");
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
vi.spyOn(enemyPokemon, "apply");
|
||||
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||
|
||||
game.move.select(Moves.FURY_SWIPES);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
@ -125,6 +126,6 @@ describe("Abilities - Galvanize", () => {
|
||||
expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp);
|
||||
}
|
||||
|
||||
expect(enemyPokemon.apply).not.toHaveReturnedWith(HitResult.NO_EFFECT);
|
||||
expect(spy).not.toHaveReturnedWith(0);
|
||||
});
|
||||
});
|
||||
|
@ -23,18 +23,18 @@ describe("Abilities - Illusion", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override.battleStyle("single");
|
||||
game.override.enemySpecies(Species.ZORUA);
|
||||
game.override.enemyAbility(Abilities.ILLUSION);
|
||||
game.override.enemyMoveset(Moves.TACKLE);
|
||||
game.override.enemyHeldItems([{ name: "WIDE_LENS", count: 3 }]);
|
||||
|
||||
game.override.moveset([Moves.WORRY_SEED, Moves.SOAK, Moves.TACKLE]);
|
||||
game.override.startingHeldItems([{ name: "WIDE_LENS", count: 3 }]);
|
||||
game.override
|
||||
.battleStyle("single")
|
||||
.enemySpecies(Species.ZORUA)
|
||||
.enemyAbility(Abilities.ILLUSION)
|
||||
.enemyMoveset(Moves.TACKLE)
|
||||
.enemyHeldItems([{ name: "WIDE_LENS", count: 3 }])
|
||||
.moveset([Moves.WORRY_SEED, Moves.SOAK, Moves.TACKLE])
|
||||
.startingHeldItems([{ name: "WIDE_LENS", count: 3 }]);
|
||||
});
|
||||
|
||||
it("creates illusion at the start", async () => {
|
||||
await game.classicMode.startBattle([Species.ZOROARK, Species.AXEW]);
|
||||
await game.classicMode.startBattle([Species.ZOROARK, Species.FEEBAS]);
|
||||
const zoroark = game.scene.getPlayerPokemon()!;
|
||||
const zorua = game.scene.getEnemyPokemon()!;
|
||||
|
||||
@ -43,7 +43,7 @@ describe("Abilities - Illusion", () => {
|
||||
});
|
||||
|
||||
it("break after receiving damaging move", async () => {
|
||||
await game.classicMode.startBattle([Species.AXEW]);
|
||||
await game.classicMode.startBattle([Species.FEEBAS]);
|
||||
game.move.select(Moves.TACKLE);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
@ -55,7 +55,7 @@ describe("Abilities - Illusion", () => {
|
||||
});
|
||||
|
||||
it("break after getting ability changed", async () => {
|
||||
await game.classicMode.startBattle([Species.AXEW]);
|
||||
await game.classicMode.startBattle([Species.FEEBAS]);
|
||||
game.move.select(Moves.WORRY_SEED);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
@ -76,7 +76,7 @@ describe("Abilities - Illusion", () => {
|
||||
|
||||
it("causes enemy AI to consider the illusion's type instead of the actual type when considering move effectiveness", async () => {
|
||||
game.override.enemyMoveset([Moves.FLAMETHROWER, Moves.PSYCHIC, Moves.TACKLE]);
|
||||
await game.classicMode.startBattle([Species.ZOROARK, Species.AXEW]);
|
||||
await game.classicMode.startBattle([Species.ZOROARK, Species.FEEBAS]);
|
||||
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
const zoroark = game.scene.getPlayerPokemon()!;
|
||||
|
@ -61,11 +61,11 @@ describe("Abilities - Infiltrator", () => {
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
const preScreenDmg = enemy.getAttackDamage(player, allMoves[move]).damage;
|
||||
const preScreenDmg = enemy.getAttackDamage({ source: player, move: allMoves[move] }).damage;
|
||||
|
||||
game.scene.arena.addTag(tagType, 1, Moves.NONE, enemy.id, ArenaTagSide.ENEMY, true);
|
||||
|
||||
const postScreenDmg = enemy.getAttackDamage(player, allMoves[move]).damage;
|
||||
const postScreenDmg = enemy.getAttackDamage({ source: player, move: allMoves[move] }).damage;
|
||||
|
||||
expect(postScreenDmg).toBe(preScreenDmg);
|
||||
expect(player.battleData.abilitiesApplied[0]).toBe(Abilities.INFILTRATOR);
|
||||
|
@ -4,6 +4,7 @@ import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { HitCheckResult } from "#enums/hit-check-result";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
|
||||
@ -28,6 +29,7 @@ describe("Abilities - No Guard", () => {
|
||||
.moveset(Moves.ZAP_CANNON)
|
||||
.ability(Abilities.NO_GUARD)
|
||||
.enemyLevel(200)
|
||||
.enemySpecies(Species.SNORLAX)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
@ -48,7 +50,7 @@ describe("Abilities - No Guard", () => {
|
||||
|
||||
await game.phaseInterceptor.to(MoveEndPhase);
|
||||
|
||||
expect(moveEffectPhase.hitCheck).toHaveReturnedWith(true);
|
||||
expect(moveEffectPhase.hitCheck).toHaveReturnedWith([HitCheckResult.HIT, 1]);
|
||||
});
|
||||
|
||||
it("should guarantee double battle with any one LURE", async () => {
|
||||
|
@ -52,7 +52,7 @@ describe("Abilities - Shield Dust", () => {
|
||||
|
||||
// Shield Dust negates secondary effect
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
const move = phase.move;
|
||||
expect(move.id).toBe(Moves.AIR_SLASH);
|
||||
|
||||
const chance = new NumberHolder(move.chance);
|
||||
|
@ -25,7 +25,6 @@ describe("Abilities - Super Luck", () => {
|
||||
.moveset([Moves.TACKLE])
|
||||
.ability(Abilities.SUPER_LUCK)
|
||||
.battleStyle("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
|
@ -2,7 +2,6 @@ import { BattlerIndex } from "#app/battle";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species";
|
||||
import { HitResult } from "#app/field/pokemon";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@ -87,13 +86,15 @@ describe("Abilities - Tera Shell", () => {
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
vi.spyOn(playerPokemon, "apply");
|
||||
const spy = vi.spyOn(playerPokemon, "getMoveEffectiveness");
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
expect(playerPokemon.apply).toHaveLastReturnedWith(HitResult.EFFECTIVE);
|
||||
expect(spy).toHaveLastReturnedWith(1);
|
||||
expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp() - 40);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("should change the effectiveness of all strikes of a multi-strike move", async () => {
|
||||
@ -102,7 +103,7 @@ describe("Abilities - Tera Shell", () => {
|
||||
await game.classicMode.startBattle([Species.SNORLAX]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
vi.spyOn(playerPokemon, "apply");
|
||||
const spy = vi.spyOn(playerPokemon, "getMoveEffectiveness");
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
|
||||
@ -110,8 +111,9 @@ describe("Abilities - Tera Shell", () => {
|
||||
await game.move.forceHit();
|
||||
for (let i = 0; i < 2; i++) {
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
expect(playerPokemon.apply).toHaveLastReturnedWith(HitResult.NOT_VERY_EFFECTIVE);
|
||||
expect(spy).toHaveLastReturnedWith(0.5);
|
||||
}
|
||||
expect(playerPokemon.apply).toHaveReturnedTimes(2);
|
||||
expect(spy).toHaveReturnedTimes(2);
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
@ -47,7 +47,9 @@ describe("Battle Mechanics - Damage Calculation", () => {
|
||||
|
||||
// expected base damage = [(2*level/5 + 2) * power * playerATK / enemyDEF / 50] + 2
|
||||
// = 31.8666...
|
||||
expect(enemyPokemon.getAttackDamage(playerPokemon, allMoves[Moves.TACKLE]).damage).toBeCloseTo(31);
|
||||
expect(enemyPokemon.getAttackDamage({ source: playerPokemon, move: allMoves[Moves.TACKLE] }).damage).toBeCloseTo(
|
||||
31,
|
||||
);
|
||||
});
|
||||
|
||||
it("Attacks deal 1 damage at minimum", async () => {
|
||||
@ -91,7 +93,7 @@ describe("Battle Mechanics - Damage Calculation", () => {
|
||||
const magikarp = game.scene.getPlayerPokemon()!;
|
||||
const dragonite = game.scene.getEnemyPokemon()!;
|
||||
|
||||
expect(dragonite.getAttackDamage(magikarp, allMoves[Moves.DRAGON_RAGE]).damage).toBe(40);
|
||||
expect(dragonite.getAttackDamage({ source: magikarp, move: allMoves[Moves.DRAGON_RAGE] }).damage).toBe(40);
|
||||
});
|
||||
|
||||
it("One-hit KO moves ignore damage multipliers", async () => {
|
||||
@ -102,7 +104,7 @@ describe("Battle Mechanics - Damage Calculation", () => {
|
||||
const magikarp = game.scene.getPlayerPokemon()!;
|
||||
const aggron = game.scene.getEnemyPokemon()!;
|
||||
|
||||
expect(aggron.getAttackDamage(magikarp, allMoves[Moves.FISSURE]).damage).toBe(aggron.hp);
|
||||
expect(aggron.getAttackDamage({ source: magikarp, move: allMoves[Moves.FISSURE] }).damage).toBe(aggron.hp);
|
||||
});
|
||||
|
||||
it("When the user fails to use Jump Kick with Wonder Guard ability, the damage should be 1.", async () => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { PokemonTurnData, TurnMove, PokemonMove } from "#app/field/pokemon";
|
||||
import type { PokemonTurnData, TurnMove } from "#app/field/pokemon";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import type BattleScene from "#app/battle-scene";
|
||||
@ -186,12 +186,8 @@ describe("BattlerTag - SubstituteTag", () => {
|
||||
vi.spyOn(mockPokemon.scene as BattleScene, "triggerPokemonBattleAnim").mockReturnValue(true);
|
||||
vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue();
|
||||
|
||||
const pokemonMove = {
|
||||
getMove: vi.fn().mockReturnValue(allMoves[Moves.TACKLE]) as PokemonMove["getMove"],
|
||||
} as PokemonMove;
|
||||
|
||||
const moveEffectPhase = {
|
||||
move: pokemonMove,
|
||||
move: allMoves[Moves.TACKLE],
|
||||
getUserPokemon: vi.fn().mockReturnValue(undefined) as MoveEffectPhase["getUserPokemon"],
|
||||
} as MoveEffectPhase;
|
||||
|
||||
|
@ -209,4 +209,19 @@ describe("Spec - Pokemon", () => {
|
||||
expect(types[1]).toBe(PokemonType.DARK);
|
||||
});
|
||||
});
|
||||
|
||||
it.each([5, 25, 55, 95, 145, 195])(
|
||||
"should set minimum IVs for enemy trainer pokemon based on wave (%i)",
|
||||
async wave => {
|
||||
game.override.startingWave(wave);
|
||||
await game.classicMode.startBattle([Species.FEEBAS]);
|
||||
const { waveIndex } = game.scene.currentBattle;
|
||||
|
||||
for (const pokemon of game.scene.getEnemyParty()) {
|
||||
for (const index in pokemon.ivs) {
|
||||
expect(pokemon.ivs[index]).toBeGreaterThanOrEqual(Math.floor(waveIndex / 10));
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user