mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-21 17:12:44 +02:00
Merge branch 'beta' into hebrew-pr
This commit is contained in:
commit
55d6b9a168
@ -4,7 +4,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
name: "only-type-imports",
|
name: "only-type-imports",
|
||||||
severity: "error",
|
severity: "error",
|
||||||
comment: "Files in enums and @types may only use type imports.",
|
comment: "Files in 'enums/' and '@types/' must only use type imports.",
|
||||||
from: {
|
from: {
|
||||||
path: ["(^|/)src/@types", "(^|/)src/enums"],
|
path: ["(^|/)src/@types", "(^|/)src/enums"],
|
||||||
},
|
},
|
||||||
@ -14,7 +14,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no-circular-at-runtime",
|
name: "no-circular-at-runtime",
|
||||||
severity: "warn",
|
severity: "error",
|
||||||
comment:
|
comment:
|
||||||
"This dependency is part of a circular relationship. You might want to revise " +
|
"This dependency is part of a circular relationship. You might want to revise " +
|
||||||
"your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ",
|
"your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ",
|
||||||
@ -34,7 +34,7 @@ module.exports = {
|
|||||||
"add an exception for it in your dependency-cruiser configuration. By default " +
|
"add an exception for it in your dependency-cruiser configuration. By default " +
|
||||||
"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
|
"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
|
||||||
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
|
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
|
||||||
severity: "warn",
|
severity: "error",
|
||||||
from: {
|
from: {
|
||||||
orphan: true,
|
orphan: true,
|
||||||
pathNot: [
|
pathNot: [
|
||||||
@ -42,8 +42,7 @@ module.exports = {
|
|||||||
"[.]d[.]ts$", // TypeScript declaration files
|
"[.]d[.]ts$", // TypeScript declaration files
|
||||||
"(^|/)tsconfig[.]json$", // TypeScript config
|
"(^|/)tsconfig[.]json$", // TypeScript config
|
||||||
"(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs
|
"(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs
|
||||||
// anything in src/@types
|
"(^|/)test/.+[.]setup[.]ts", // Vitest setup files
|
||||||
"(^|/)src/@types/",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
to: {},
|
to: {},
|
||||||
@ -53,7 +52,7 @@ module.exports = {
|
|||||||
comment:
|
comment:
|
||||||
"A module depends on a node core module that has been deprecated. Find an alternative - these are " +
|
"A module depends on a node core module that has been deprecated. Find an alternative - these are " +
|
||||||
"bound to exist - node doesn't deprecate lightly.",
|
"bound to exist - node doesn't deprecate lightly.",
|
||||||
severity: "warn",
|
severity: "error",
|
||||||
from: {},
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
dependencyTypes: ["core"],
|
dependencyTypes: ["core"],
|
||||||
@ -86,7 +85,7 @@ module.exports = {
|
|||||||
comment:
|
comment:
|
||||||
"This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " +
|
"This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " +
|
||||||
"version of that module, or find an alternative. Deprecated modules are a security risk.",
|
"version of that module, or find an alternative. Deprecated modules are a security risk.",
|
||||||
severity: "warn",
|
severity: "error",
|
||||||
from: {},
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
dependencyTypes: ["deprecated"],
|
dependencyTypes: ["deprecated"],
|
||||||
@ -122,7 +121,7 @@ module.exports = {
|
|||||||
"Likely this module depends on an external ('npm') package that occurs more than once " +
|
"Likely this module depends on an external ('npm') package that occurs more than once " +
|
||||||
"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " +
|
"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " +
|
||||||
"maintenance problems later on.",
|
"maintenance problems later on.",
|
||||||
severity: "warn",
|
severity: "error",
|
||||||
from: {},
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
moreThanOneDependencyType: true,
|
moreThanOneDependencyType: true,
|
||||||
@ -133,7 +132,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
/* rules you might want to tweak for your specific situation: */
|
// rules you might want to tweak for your specific situation:
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "not-to-spec",
|
name: "not-to-spec",
|
||||||
@ -188,7 +187,7 @@ module.exports = {
|
|||||||
"in your package.json. This makes sense if your package is e.g. a plugin, but in " +
|
"in your package.json. This makes sense if your package is e.g. a plugin, but in " +
|
||||||
"other cases - maybe not so much. If the use of a peer dependency is intentional " +
|
"other cases - maybe not so much. If the use of a peer dependency is intentional " +
|
||||||
"add an exception to your dependency-cruiser configuration.",
|
"add an exception to your dependency-cruiser configuration.",
|
||||||
severity: "warn",
|
severity: "error",
|
||||||
from: {},
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
dependencyTypes: ["npm-peer"],
|
dependencyTypes: ["npm-peer"],
|
||||||
@ -196,6 +195,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
|
exclude: ["src/plugins/vite/*", "src/vite.env.d.ts"],
|
||||||
/* Which modules not to follow further when encountered */
|
/* Which modules not to follow further when encountered */
|
||||||
doNotFollow: {
|
doNotFollow: {
|
||||||
/* path: an array of regular expressions in strings to match against */
|
/* path: an array of regular expressions in strings to match against */
|
||||||
@ -235,7 +235,7 @@ module.exports = {
|
|||||||
true: also detect dependencies that only exist before typescript-to-javascript compilation
|
true: also detect dependencies that only exist before typescript-to-javascript compilation
|
||||||
"specify": for each dependency identify whether it only exists before compilation or also after
|
"specify": for each dependency identify whether it only exists before compilation or also after
|
||||||
*/
|
*/
|
||||||
// tsPreCompilationDeps: false,
|
tsPreCompilationDeps: true,
|
||||||
|
|
||||||
/* list of extensions to scan that aren't javascript or compile-to-javascript.
|
/* list of extensions to scan that aren't javascript or compile-to-javascript.
|
||||||
Empty by default. Only put extensions in here that you want to take into
|
Empty by default. Only put extensions in here that you want to take into
|
||||||
|
35
.github/pull_request_template.md
vendored
35
.github/pull_request_template.md
vendored
@ -2,25 +2,28 @@
|
|||||||
<!-- Feel free to look at other PRs for examples -->
|
<!-- Feel free to look at other PRs for examples -->
|
||||||
<!--
|
<!--
|
||||||
Make sure the title includes categorization (choose the one that best fits):
|
Make sure the title includes categorization (choose the one that best fits):
|
||||||
- [Bug]: If the PR is primarily a bug fix
|
- [Bug]: If the PR is primarily a bug fix
|
||||||
- [Move]: If a move has new or changed functionality
|
- [Move]: If a move has new or changed functionality
|
||||||
- [Ability]: If an ability has new or changed functionality
|
- [Ability]: If an ability has new or changed functionality
|
||||||
- [Item]: For new or modified items
|
- [Item]: For new or modified items
|
||||||
- [Mystery]: For new or modified Mystery Encounters
|
- [Mystery]: For new or modified Mystery Encounters
|
||||||
- [Test]: If the PR is primarily adding or modifying tests
|
- [Test]: If the PR is primarily adding or modifying tests
|
||||||
- [UI/UX]: If the PR is changing UI/UX elements
|
- [UI/UX]: If the PR is changing UI/UX elements
|
||||||
- [Audio]: If the PR is adding or changing music/sfx
|
- [Audio]: If the PR is adding or changing music/sfx
|
||||||
- [Sprite]: If the PR is adding or changing sprites
|
- [Sprite]: If the PR is adding or changing sprites
|
||||||
- [Balance]: If the PR is related to game balance
|
- [Balance]: If the PR is related to game balance
|
||||||
- [Challenge]: If the PR is adding or modifying challenges
|
- [Challenge]: If the PR is adding or modifying challenges
|
||||||
- [Refactor]: If the PR is primarily rewriting existing code
|
- [Refactor]: If the PR is primarily rewriting existing code
|
||||||
- [Docs]: If the PR is just adding or modifying documentation (such as tsdocs/code comments)
|
- [Dev]: If the PR is primarily changing something pertaining to development (lefthook hooks, linter rules, etc.)
|
||||||
- [GitHub]: For changes to GitHub workflows/templates/etc
|
- [i18n]: If the PR is primarily adding/changing locale keys or key usage (may come with an associated locales PR)
|
||||||
- [Misc]: If no other category fits the PR
|
- [Docs]: If the PR is adding or modifying documentation (such as tsdocs/code comments)
|
||||||
|
- [GitHub]: For changes to GitHub workflows/templates/etc
|
||||||
|
- [Misc]: If no other category fits the PR
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Make sure that this PR is not overlapping with someone else's work
|
Make sure that this PR is not overlapping with someone else's work
|
||||||
Please try to keep the PR self-contained (and small)
|
Please try to keep the PR self-contained (and small!)
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## What are the changes the user will see?
|
## What are the changes the user will see?
|
||||||
@ -66,7 +69,7 @@ Do the reviewers need to do something special in order to test your changes?
|
|||||||
- [ ] Have I provided a clear explanation of the changes?
|
- [ ] Have I provided a clear explanation of the changes?
|
||||||
- [ ] Have I tested the changes manually?
|
- [ ] Have I tested the changes manually?
|
||||||
- [ ] Are all unit tests still passing? (`npm run test:silent`)
|
- [ ] Are all unit tests still passing? (`npm run test:silent`)
|
||||||
- [ ] Have I created new automated tests (`npm run create-test`) or updated existing tests related to the PR's changes?
|
- [ ] Have I created new automated tests (`npm run test:create`) or updated existing tests related to the PR's changes?
|
||||||
- [ ] Have I provided screenshots/videos of the changes (if applicable)?
|
- [ ] Have I provided screenshots/videos of the changes (if applicable)?
|
||||||
- [ ] Have I made sure that any UI change works for both UI themes (default and legacy)?
|
- [ ] Have I made sure that any UI change works for both UI themes (default and legacy)?
|
||||||
|
|
||||||
|
42
.github/workflows/linting.yml
vendored
Normal file
42
.github/workflows/linting.yml
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
name: Linting
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- beta
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- beta
|
||||||
|
merge_group:
|
||||||
|
types: [checks_requested]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-linters:
|
||||||
|
name: Run linters
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out Git repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: 'recursive'
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install Node.js dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run ESLint
|
||||||
|
run: npm run eslint-ci
|
||||||
|
|
||||||
|
- name: Lint with Biome
|
||||||
|
run: npm run biome-ci
|
||||||
|
|
||||||
|
- name: Check dependencies with depcruise
|
||||||
|
run: npm run depcruise
|
41
.github/workflows/quality.yml
vendored
41
.github/workflows/quality.yml
vendored
@ -1,41 +0,0 @@
|
|||||||
name: Biome Code Quality
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Trigger the workflow on push or pull request,
|
|
||||||
# but only for the main branch
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main # Trigger on push events to the main branch
|
|
||||||
- beta # Trigger on push events to the beta branch
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main # Trigger on pull request events targeting the main branch
|
|
||||||
- beta # Trigger on pull request events targeting the beta branch
|
|
||||||
merge_group:
|
|
||||||
types: [checks_requested]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
run-linters: # Define a job named "run-linters"
|
|
||||||
name: Run linters # Human-readable name for the job
|
|
||||||
runs-on: ubuntu-latest # Specify the latest Ubuntu runner for the job
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out Git repository # Step to check out the repository
|
|
||||||
uses: actions/checkout@v4 # Use the checkout action version 4
|
|
||||||
with:
|
|
||||||
submodules: 'recursive'
|
|
||||||
|
|
||||||
- name: Set up Node.js # Step to set up Node.js environment
|
|
||||||
uses: actions/setup-node@v4 # Use the setup-node action version 4
|
|
||||||
with:
|
|
||||||
node-version-file: '.nvmrc'
|
|
||||||
cache: 'npm'
|
|
||||||
|
|
||||||
- name: Install Node.js dependencies # Step to install Node.js dependencies
|
|
||||||
run: npm ci # Use 'npm ci' to install dependencies
|
|
||||||
|
|
||||||
- name: eslint # Step to run linters
|
|
||||||
run: npm run eslint-ci
|
|
||||||
|
|
||||||
- name: Lint with Biome # Step to run linters
|
|
||||||
run: npm run biome-ci
|
|
29
biome.jsonc
29
biome.jsonc
@ -28,7 +28,6 @@
|
|||||||
".vscode/*",
|
".vscode/*",
|
||||||
"*.css", // TODO?
|
"*.css", // TODO?
|
||||||
"*.html", // TODO?
|
"*.html", // TODO?
|
||||||
"src/overrides.ts",
|
|
||||||
// TODO: these files are too big and complex, ignore them until their respective refactors
|
// TODO: these files are too big and complex, ignore them until their respective refactors
|
||||||
"src/data/moves/move.ts",
|
"src/data/moves/move.ts",
|
||||||
|
|
||||||
@ -47,8 +46,8 @@
|
|||||||
"correctness": {
|
"correctness": {
|
||||||
"noUndeclaredVariables": "off",
|
"noUndeclaredVariables": "off",
|
||||||
"noUnusedVariables": "error",
|
"noUnusedVariables": "error",
|
||||||
"noSwitchDeclarations": "warn", // TODO: refactor and make this an error
|
"noSwitchDeclarations": "error",
|
||||||
"noVoidTypeReturn": "warn", // TODO: Refactor and make this an error
|
"noVoidTypeReturn": "error",
|
||||||
"noUnusedImports": "error"
|
"noUnusedImports": "error"
|
||||||
},
|
},
|
||||||
"style": {
|
"style": {
|
||||||
@ -85,7 +84,7 @@
|
|||||||
"useLiteralKeys": "off",
|
"useLiteralKeys": "off",
|
||||||
"noForEach": "off", // Foreach vs for of is not that simple.
|
"noForEach": "off", // Foreach vs for of is not that simple.
|
||||||
"noUselessSwitchCase": "off", // Explicit > Implicit
|
"noUselessSwitchCase": "off", // Explicit > Implicit
|
||||||
"noUselessConstructor": "warn", // TODO: Refactor and make this an error
|
"noUselessConstructor": "error",
|
||||||
"noBannedTypes": "warn" // TODO: Refactor and make this an error
|
"noBannedTypes": "warn" // TODO: Refactor and make this an error
|
||||||
},
|
},
|
||||||
"nursery": {
|
"nursery": {
|
||||||
@ -120,6 +119,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes)
|
||||||
|
{
|
||||||
|
"include": ["src/overrides.ts", "src/enums/*"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"correctness": {
|
||||||
|
"noUnusedImports": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["src/overrides.ts"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"style": {
|
||||||
|
"useImportType": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
"eslint": "eslint --fix .",
|
"eslint": "eslint --fix .",
|
||||||
"eslint-ci": "eslint .",
|
"eslint-ci": "eslint .",
|
||||||
"biome": "biome check --write --changed --no-errors-on-unmatched",
|
"biome": "biome check --write --changed --no-errors-on-unmatched",
|
||||||
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --changed --no-errors-on-unmatched",
|
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched",
|
||||||
"docs": "typedoc",
|
"docs": "typedoc",
|
||||||
"depcruise": "depcruise src",
|
"depcruise": "depcruise src test",
|
||||||
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
|
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
|
||||||
"postinstall": "npx lefthook install && npx lefthook run post-merge",
|
"postinstall": "npx lefthook install && npx lefthook run post-merge",
|
||||||
"update-version:patch": "npm version patch --force --no-git-tag-version",
|
"update-version:patch": "npm version patch --force --no-git-tag-version",
|
||||||
|
@ -1,11 +1,27 @@
|
|||||||
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
|
import type { AbAttr } from "#app/data/abilities/ability";
|
||||||
import type Move from "#app/data/moves/move";
|
import type Move from "#app/data/moves/move";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import type { BattleStat } from "#enums/stat";
|
import type { BattleStat } from "#enums/stat";
|
||||||
|
import type { AbAttrConstructorMap } from "#app/data/abilities/ability";
|
||||||
|
|
||||||
export type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => void;
|
// Intentionally re-export all types from the ability attributes module
|
||||||
export type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean;
|
export type * from "#app/data/abilities/ability";
|
||||||
|
|
||||||
|
export type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean, ...args: any[]) => void;
|
||||||
|
export type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean, ...args: any[]) => boolean;
|
||||||
export type AbAttrCondition = (pokemon: Pokemon) => boolean;
|
export type AbAttrCondition = (pokemon: Pokemon) => boolean;
|
||||||
export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
|
export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
|
||||||
export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
|
export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
|
||||||
export type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean;
|
export type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union type of all ability attribute class names as strings
|
||||||
|
*/
|
||||||
|
export type AbAttrString = keyof AbAttrConstructorMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of ability attribute class names to an instance of the class.
|
||||||
|
*/
|
||||||
|
export type AbAttrMap = {
|
||||||
|
[K in keyof AbAttrConstructorMap]: InstanceType<AbAttrConstructorMap[K]>;
|
||||||
|
};
|
||||||
|
32
src/@types/modifier-types.ts
Normal file
32
src/@types/modifier-types.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Re-exports of all the types defined in the modifier module.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
import type { ModifierConstructorMap } from "#app/modifier/modifier";
|
||||||
|
import type { ModifierType, WeightedModifierType } from "#app/modifier/modifier-type";
|
||||||
|
export type ModifierTypeFunc = () => ModifierType;
|
||||||
|
export type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number;
|
||||||
|
|
||||||
|
export type { ModifierConstructorMap } from "#app/modifier/modifier";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of modifier names to their respective instance types
|
||||||
|
*/
|
||||||
|
export type ModifierInstanceMap = {
|
||||||
|
[K in keyof ModifierConstructorMap]: InstanceType<ModifierConstructorMap[K]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union type of all modifier constructors.
|
||||||
|
*/
|
||||||
|
export type ModifierClass = ModifierConstructorMap[keyof ModifierConstructorMap];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union type of all modifier names as strings.
|
||||||
|
*/
|
||||||
|
export type ModifierString = keyof ModifierConstructorMap;
|
||||||
|
|
||||||
|
export type ModifierPool = {
|
||||||
|
[tier: string]: WeightedModifierType[];
|
||||||
|
};
|
@ -58,23 +58,15 @@ import {
|
|||||||
getEnemyModifierTypesForWave,
|
getEnemyModifierTypesForWave,
|
||||||
getLuckString,
|
getLuckString,
|
||||||
getLuckTextTint,
|
getLuckTextTint,
|
||||||
getModifierPoolForType,
|
|
||||||
getModifierType,
|
|
||||||
getPartyLuckValue,
|
getPartyLuckValue,
|
||||||
ModifierPoolType,
|
|
||||||
modifierTypes,
|
|
||||||
PokemonHeldItemModifierType,
|
PokemonHeldItemModifierType,
|
||||||
} from "#app/modifier/modifier-type";
|
} from "#app/modifier/modifier-type";
|
||||||
|
import { getModifierType } from "./utils/modifier-utils";
|
||||||
|
import { modifierTypes } from "./data/data-lists";
|
||||||
|
import { getModifierPoolForType } from "./utils/modifier-utils";
|
||||||
|
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||||
import AbilityBar from "#app/ui/ability-bar";
|
import AbilityBar from "#app/ui/ability-bar";
|
||||||
import {
|
import { applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs } from "./data/abilities/apply-ab-attrs";
|
||||||
applyAbAttrs,
|
|
||||||
applyPostBattleInitAbAttrs,
|
|
||||||
applyPostItemLostAbAttrs,
|
|
||||||
BlockItemTheftAbAttr,
|
|
||||||
DoubleBattleChanceAbAttr,
|
|
||||||
PostBattleInitAbAttr,
|
|
||||||
PostItemLostAbAttr,
|
|
||||||
} from "#app/data/abilities/ability";
|
|
||||||
import { allAbilities } from "./data/data-lists";
|
import { allAbilities } from "./data/data-lists";
|
||||||
import type { FixedBattleConfig } from "#app/battle";
|
import type { FixedBattleConfig } from "#app/battle";
|
||||||
import Battle from "#app/battle";
|
import Battle from "#app/battle";
|
||||||
@ -145,14 +137,13 @@ import { LoadingScene } from "#app/loading-scene";
|
|||||||
import type { MovePhase } from "#app/phases/move-phase";
|
import type { MovePhase } from "#app/phases/move-phase";
|
||||||
import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
|
import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
|
||||||
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
|
import { allMysteryEncounters, mysteryEncountersByBiome } from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
import {
|
import {
|
||||||
allMysteryEncounters,
|
|
||||||
ANTI_VARIANCE_WEIGHT_MODIFIER,
|
ANTI_VARIANCE_WEIGHT_MODIFIER,
|
||||||
AVERAGE_ENCOUNTERS_PER_RUN_TARGET,
|
AVERAGE_ENCOUNTERS_PER_RUN_TARGET,
|
||||||
BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT,
|
BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT,
|
||||||
MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT,
|
MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT,
|
||||||
mysteryEncountersByBiome,
|
} from "./constants";
|
||||||
} from "#app/data/mystery-encounters/mystery-encounters";
|
|
||||||
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
|
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
@ -1264,7 +1255,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8);
|
const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8);
|
||||||
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
|
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
|
||||||
for (const p of playerField) {
|
for (const p of playerField) {
|
||||||
applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance);
|
applyAbAttrs("DoubleBattleChanceAbAttr", p, null, false, doubleChance);
|
||||||
}
|
}
|
||||||
return Math.max(doubleChance.value, 1);
|
return Math.max(doubleChance.value, 1);
|
||||||
}
|
}
|
||||||
@ -1469,7 +1460,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
for (const pokemon of this.getPlayerParty()) {
|
for (const pokemon of this.getPlayerParty()) {
|
||||||
pokemon.resetBattleAndWaveData();
|
pokemon.resetBattleAndWaveData();
|
||||||
pokemon.resetTera();
|
pokemon.resetTera();
|
||||||
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
|
applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
|
||||||
if (
|
if (
|
||||||
pokemon.hasSpecies(SpeciesId.TERAPAGOS) ||
|
pokemon.hasSpecies(SpeciesId.TERAPAGOS) ||
|
||||||
(this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190)
|
(this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190)
|
||||||
@ -1644,6 +1635,9 @@ export default class BattleScene extends SceneBase {
|
|||||||
case SpeciesId.TATSUGIRI:
|
case SpeciesId.TATSUGIRI:
|
||||||
case SpeciesId.PALDEA_TAUROS:
|
case SpeciesId.PALDEA_TAUROS:
|
||||||
return randSeedInt(species.forms.length);
|
return randSeedInt(species.forms.length);
|
||||||
|
case SpeciesId.MAUSHOLD:
|
||||||
|
case SpeciesId.DUDUNSPARCE:
|
||||||
|
return !randSeedInt(4) ? 1 : 0;
|
||||||
case SpeciesId.PIKACHU:
|
case SpeciesId.PIKACHU:
|
||||||
if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) {
|
if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) {
|
||||||
return 0; // Ban Cosplay and Partner Pika from Trainers before wave 30
|
return 0; // Ban Cosplay and Partner Pika from Trainers before wave 30
|
||||||
@ -2745,7 +2739,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
if (source && source.isPlayer() !== target.isPlayer()) {
|
if (source && source.isPlayer() !== target.isPlayer()) {
|
||||||
applyAbAttrs(BlockItemTheftAbAttr, source, cancelled);
|
applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
@ -2785,13 +2779,13 @@ export default class BattleScene extends SceneBase {
|
|||||||
if (target.isPlayer()) {
|
if (target.isPlayer()) {
|
||||||
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant);
|
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant);
|
||||||
if (source && itemLost) {
|
if (source && itemLost) {
|
||||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false);
|
applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant);
|
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant);
|
||||||
if (source && itemLost) {
|
if (source && itemLost) {
|
||||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false);
|
applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -2814,7 +2808,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
if (source && source.isPlayer() !== target.isPlayer()) {
|
if (source && source.isPlayer() !== target.isPlayer()) {
|
||||||
applyAbAttrs(BlockItemTheftAbAttr, source, cancelled);
|
applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
import Trainer from "./field/trainer";
|
import Trainer from "./field/trainer";
|
||||||
import { TrainerVariant } from "#enums/trainer-variant";
|
import { TrainerVariant } from "#enums/trainer-variant";
|
||||||
import type { GameMode } from "./game-mode";
|
import type { GameMode } from "./game-mode";
|
||||||
import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
|
import { MoneyMultiplierModifier, type PokemonHeldItemModifier } from "./modifier/modifier";
|
||||||
import type { PokeballType } from "#enums/pokeball";
|
import type { PokeballType } from "#enums/pokeball";
|
||||||
import { trainerConfigs } from "#app/data/trainers/trainer-config";
|
import { trainerConfigs } from "#app/data/trainers/trainer-config";
|
||||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||||
@ -30,7 +30,7 @@ import i18next from "#app/plugins/i18n";
|
|||||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||||
import type { CustomModifierSettings } from "#app/modifier/modifier-type";
|
import type { CustomModifierSettings } from "#app/modifier/modifier-type";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { BattleType } from "#enums/battle-type";
|
import { BattleType } from "#enums/battle-type";
|
||||||
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
||||||
@ -173,7 +173,7 @@ export default class Battle {
|
|||||||
this.postBattleLoot.push(
|
this.postBattleLoot.push(
|
||||||
...globalScene
|
...globalScene
|
||||||
.findModifiers(
|
.findModifiers(
|
||||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable,
|
m => m.is("PokemonHeldItemModifier") && m.pokemonId === enemyPokemon.id && m.isTransferable,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.map(i => {
|
.map(i => {
|
||||||
|
@ -54,3 +54,43 @@ export const defaultStarterSpecies: SpeciesId[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary
|
export const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT
|
||||||
|
*/
|
||||||
|
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The divisor for determining ME spawns, defines the "maximum" weight required for a spawn
|
||||||
|
* If spawn_weight === MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, 100% chance to spawn a ME
|
||||||
|
*/
|
||||||
|
export const MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT = 256;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When an ME spawn roll fails, WEIGHT_INCREMENT_ON_SPAWN_MISS is added to future rolls for ME spawn checks.
|
||||||
|
* These values are cleared whenever the next ME spawns, and spawn weight returns to BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT
|
||||||
|
*/
|
||||||
|
export const WEIGHT_INCREMENT_ON_SPAWN_MISS = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the target average for total ME spawns in a single Classic run.
|
||||||
|
* Used by anti-variance mechanic to check whether a run is above or below the target on a given wave.
|
||||||
|
*/
|
||||||
|
export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 12;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will increase/decrease the chance of spawning a ME based on the current run's total MEs encountered vs AVERAGE_ENCOUNTERS_PER_RUN_TARGET
|
||||||
|
* Example:
|
||||||
|
* AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 17 (expects avg 1 ME every 10 floors)
|
||||||
|
* ANTI_VARIANCE_WEIGHT_MODIFIER = 15
|
||||||
|
*
|
||||||
|
* On wave 20, if 1 ME has been encountered, the difference from expected average is 0 MEs.
|
||||||
|
* So anti-variance adds 0/256 to the spawn weight check for ME spawn.
|
||||||
|
*
|
||||||
|
* On wave 20, if 0 MEs have been encountered, the difference from expected average is 1 ME.
|
||||||
|
* So anti-variance adds 15/256 to the spawn weight check for ME spawn.
|
||||||
|
*
|
||||||
|
* On wave 20, if 2 MEs have been encountered, the difference from expected average is -1 ME.
|
||||||
|
* So anti-variance adds -15/256 to the spawn weight check for ME spawn.
|
||||||
|
*/
|
||||||
|
export const ANTI_VARIANCE_WEIGHT_MODIFIER = 15;
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
import type { AbAttrCondition } from "#app/@types/ability-types";
|
|
||||||
import type Pokemon from "#app/field/pokemon";
|
|
||||||
import type { BooleanHolder } from "#app/utils/common";
|
|
||||||
|
|
||||||
export abstract class AbAttr {
|
|
||||||
public showAbility: boolean;
|
|
||||||
private extraCondition: AbAttrCondition;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param showAbility - Whether to show this ability as a flyout during battle; default `true`.
|
|
||||||
* Should be kept in parity with mainline where possible.
|
|
||||||
*/
|
|
||||||
constructor(showAbility = true) {
|
|
||||||
this.showAbility = showAbility;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies ability effects without checking conditions
|
|
||||||
* @param _pokemon - The pokemon to apply this ability to
|
|
||||||
* @param _passive - Whether or not the ability is a passive
|
|
||||||
* @param _simulated - Whether the call is simulated
|
|
||||||
* @param _args - Extra args passed to the function. Handled by child classes.
|
|
||||||
* @see {@linkcode canApply}
|
|
||||||
*/
|
|
||||||
apply(
|
|
||||||
_pokemon: Pokemon,
|
|
||||||
_passive: boolean,
|
|
||||||
_simulated: boolean,
|
|
||||||
_cancelled: BooleanHolder | null,
|
|
||||||
_args: any[],
|
|
||||||
): void {}
|
|
||||||
|
|
||||||
getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getCondition(): AbAttrCondition | null {
|
|
||||||
return this.extraCondition || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
addCondition(condition: AbAttrCondition): AbAttr {
|
|
||||||
this.extraCondition = condition;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a boolean describing whether the ability can be applied under current conditions
|
|
||||||
* @param _pokemon - The pokemon to apply this ability to
|
|
||||||
* @param _passive - Whether or not the ability is a passive
|
|
||||||
* @param _simulated - Whether the call is simulated
|
|
||||||
* @param _args - Extra args passed to the function. Handled by child classes.
|
|
||||||
* @returns `true` if the ability can be applied, `false` otherwise
|
|
||||||
* @see {@linkcode apply}
|
|
||||||
*/
|
|
||||||
canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,137 +0,0 @@
|
|||||||
import { AbilityId } from "#enums/ability-id";
|
|
||||||
import type { AbAttrCondition } from "#app/@types/ability-types";
|
|
||||||
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
|
|
||||||
import i18next from "i18next";
|
|
||||||
import type { Localizable } from "#app/@types/locales";
|
|
||||||
import type { Constructor } from "#app/utils/common";
|
|
||||||
|
|
||||||
export class Ability implements Localizable {
|
|
||||||
public id: AbilityId;
|
|
||||||
|
|
||||||
private nameAppend: string;
|
|
||||||
public name: string;
|
|
||||||
public description: string;
|
|
||||||
public generation: number;
|
|
||||||
public isBypassFaint: boolean;
|
|
||||||
public isIgnorable: boolean;
|
|
||||||
public isSuppressable = true;
|
|
||||||
public isCopiable = true;
|
|
||||||
public isReplaceable = true;
|
|
||||||
public attrs: AbAttr[];
|
|
||||||
public conditions: AbAttrCondition[];
|
|
||||||
|
|
||||||
constructor(id: AbilityId, generation: number) {
|
|
||||||
this.id = id;
|
|
||||||
|
|
||||||
this.nameAppend = "";
|
|
||||||
this.generation = generation;
|
|
||||||
this.attrs = [];
|
|
||||||
this.conditions = [];
|
|
||||||
|
|
||||||
this.isSuppressable = true;
|
|
||||||
this.isCopiable = true;
|
|
||||||
this.isReplaceable = true;
|
|
||||||
|
|
||||||
this.localize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public get isSwappable(): boolean {
|
|
||||||
return this.isCopiable && this.isReplaceable;
|
|
||||||
}
|
|
||||||
localize(): void {
|
|
||||||
const i18nKey = AbilityId[this.id]
|
|
||||||
.split("_")
|
|
||||||
.filter(f => f)
|
|
||||||
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
|
|
||||||
.join("") as string;
|
|
||||||
|
|
||||||
this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`) as string}${this.nameAppend}` : "";
|
|
||||||
this.description = this.id ? (i18next.t(`ability:${i18nKey}.description`) as string) : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all ability attributes that match `attrType`
|
|
||||||
* @param attrType any attribute that extends {@linkcode AbAttr}
|
|
||||||
* @returns Array of attributes that match `attrType`, Empty Array if none match.
|
|
||||||
*/
|
|
||||||
getAttrs<T extends AbAttr>(attrType: Constructor<T>): T[] {
|
|
||||||
return this.attrs.filter((a): a is T => a instanceof attrType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an ability has an attribute that matches `attrType`
|
|
||||||
* @param attrType any attribute that extends {@linkcode AbAttr}
|
|
||||||
* @returns true if the ability has attribute `attrType`
|
|
||||||
*/
|
|
||||||
hasAttr<T extends AbAttr>(attrType: Constructor<T>): boolean {
|
|
||||||
return this.attrs.some(attr => attr instanceof attrType);
|
|
||||||
}
|
|
||||||
|
|
||||||
attr<T extends Constructor<AbAttr>>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
|
|
||||||
const attr = new AttrType(...args);
|
|
||||||
this.attrs.push(attr);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
conditionalAttr<T extends Constructor<AbAttr>>(
|
|
||||||
condition: AbAttrCondition,
|
|
||||||
AttrType: T,
|
|
||||||
...args: ConstructorParameters<T>
|
|
||||||
): Ability {
|
|
||||||
const attr = new AttrType(...args);
|
|
||||||
attr.addCondition(condition);
|
|
||||||
this.attrs.push(attr);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bypassFaint(): Ability {
|
|
||||||
this.isBypassFaint = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ignorable(): Ability {
|
|
||||||
this.isIgnorable = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsuppressable(): Ability {
|
|
||||||
this.isSuppressable = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
uncopiable(): Ability {
|
|
||||||
this.isCopiable = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
unreplaceable(): Ability {
|
|
||||||
this.isReplaceable = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
condition(condition: AbAttrCondition): Ability {
|
|
||||||
this.conditions.push(condition);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
partial(): this {
|
|
||||||
this.nameAppend += " (P)";
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
unimplemented(): this {
|
|
||||||
this.nameAppend += " (N)";
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case.
|
|
||||||
* @returns the ability
|
|
||||||
*/
|
|
||||||
edgeCase(): this {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
832
src/data/abilities/apply-ab-attrs.ts
Normal file
832
src/data/abilities/apply-ab-attrs.ts
Normal file
@ -0,0 +1,832 @@
|
|||||||
|
import type { AbAttrApplyFunc, AbAttrMap, AbAttrString, AbAttrSuccessFunc } from "#app/@types/ability-types";
|
||||||
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import type { BooleanHolder, NumberHolder } from "#app/utils/common";
|
||||||
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
|
import type { HitResult } from "#enums/hit-result";
|
||||||
|
import type { BattleStat, Stat } from "#enums/stat";
|
||||||
|
import type { StatusEffect } from "#enums/status-effect";
|
||||||
|
import type { WeatherType } from "#enums/weather-type";
|
||||||
|
import type { BattlerTag } from "../battler-tags";
|
||||||
|
import type Move from "../moves/move";
|
||||||
|
import type { PokemonMove } from "../moves/pokemon-move";
|
||||||
|
import type { TerrainType } from "../terrain";
|
||||||
|
import type { Weather } from "../weather";
|
||||||
|
import type {
|
||||||
|
PostBattleInitAbAttr,
|
||||||
|
PreDefendAbAttr,
|
||||||
|
PostDefendAbAttr,
|
||||||
|
PostMoveUsedAbAttr,
|
||||||
|
StatMultiplierAbAttr,
|
||||||
|
AllyStatMultiplierAbAttr,
|
||||||
|
PostSetStatusAbAttr,
|
||||||
|
PostDamageAbAttr,
|
||||||
|
FieldMultiplyStatAbAttr,
|
||||||
|
PreAttackAbAttr,
|
||||||
|
ExecutedMoveAbAttr,
|
||||||
|
PostAttackAbAttr,
|
||||||
|
PostKnockOutAbAttr,
|
||||||
|
PostVictoryAbAttr,
|
||||||
|
PostSummonAbAttr,
|
||||||
|
PreSummonAbAttr,
|
||||||
|
PreSwitchOutAbAttr,
|
||||||
|
PreLeaveFieldAbAttr,
|
||||||
|
PreStatStageChangeAbAttr,
|
||||||
|
PostStatStageChangeAbAttr,
|
||||||
|
PreSetStatusAbAttr,
|
||||||
|
PreApplyBattlerTagAbAttr,
|
||||||
|
PreWeatherEffectAbAttr,
|
||||||
|
PreWeatherDamageAbAttr,
|
||||||
|
PostTurnAbAttr,
|
||||||
|
PostWeatherChangeAbAttr,
|
||||||
|
PostWeatherLapseAbAttr,
|
||||||
|
PostTerrainChangeAbAttr,
|
||||||
|
CheckTrappedAbAttr,
|
||||||
|
PostBattleAbAttr,
|
||||||
|
PostFaintAbAttr,
|
||||||
|
PostItemLostAbAttr,
|
||||||
|
} from "./ability";
|
||||||
|
|
||||||
|
function applySingleAbAttrs<T extends AbAttrString>(
|
||||||
|
pokemon: Pokemon,
|
||||||
|
passive: boolean,
|
||||||
|
attrType: T,
|
||||||
|
applyFunc: AbAttrApplyFunc<AbAttrMap[T]>,
|
||||||
|
successFunc: AbAttrSuccessFunc<AbAttrMap[T]>,
|
||||||
|
args: any[],
|
||||||
|
gainedMidTurn = false,
|
||||||
|
simulated = false,
|
||||||
|
messages: string[] = [],
|
||||||
|
) {
|
||||||
|
if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
|
||||||
|
if (
|
||||||
|
gainedMidTurn &&
|
||||||
|
ability.getAttrs(attrType).some(attr => {
|
||||||
|
attr.is("PostSummonAbAttr") && !attr.shouldActivateOnGain();
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attr of ability.getAttrs(attrType)) {
|
||||||
|
const condition = attr.getCondition();
|
||||||
|
let abShown = false;
|
||||||
|
if ((condition && !condition(pokemon)) || !successFunc(attr, passive)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.setPhaseQueueSplice();
|
||||||
|
|
||||||
|
if (attr.showAbility && !simulated) {
|
||||||
|
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
|
||||||
|
abShown = true;
|
||||||
|
}
|
||||||
|
const message = attr.getTriggerMessage(pokemon, ability.name, args);
|
||||||
|
if (message) {
|
||||||
|
if (!simulated) {
|
||||||
|
globalScene.phaseManager.queueMessage(message);
|
||||||
|
}
|
||||||
|
messages.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
applyFunc(attr, passive);
|
||||||
|
|
||||||
|
if (abShown) {
|
||||||
|
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!simulated) {
|
||||||
|
pokemon.waveData.abilitiesApplied.add(ability.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.clearPhaseQueueSplice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAbAttrsInternal<T extends AbAttrString>(
|
||||||
|
attrType: T,
|
||||||
|
pokemon: Pokemon | null,
|
||||||
|
applyFunc: AbAttrApplyFunc<AbAttrMap[T]>,
|
||||||
|
successFunc: AbAttrSuccessFunc<AbAttrMap[T]>,
|
||||||
|
args: any[],
|
||||||
|
simulated = false,
|
||||||
|
messages: string[] = [],
|
||||||
|
gainedMidTurn = false,
|
||||||
|
) {
|
||||||
|
for (const passive of [false, true]) {
|
||||||
|
if (pokemon) {
|
||||||
|
applySingleAbAttrs(pokemon, passive, attrType, applyFunc, successFunc, args, gainedMidTurn, simulated, messages);
|
||||||
|
globalScene.phaseManager.clearPhaseQueueSplice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyAbAttrs<T extends AbAttrString>(
|
||||||
|
attrType: T,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
cancelled: BooleanHolder | null,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal<T>(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
// @ts-expect-error: TODO: fix the error on `cancelled`
|
||||||
|
(attr, passive) => attr.apply(pokemon, passive, simulated, cancelled, args),
|
||||||
|
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Improve the type signatures of the following methods / refactor the apply methods
|
||||||
|
|
||||||
|
export function applyPostBattleInitAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostBattleInitAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) => (attr as PostBattleInitAbAttr).applyPostBattleInit(pokemon, passive, simulated, args),
|
||||||
|
(attr, passive) => (attr as PostBattleInitAbAttr).canApplyPostBattleInit(pokemon, passive, simulated, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPreDefendAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PreDefendAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
attacker: Pokemon,
|
||||||
|
move: Move | null,
|
||||||
|
cancelled: BooleanHolder | null,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PreDefendAbAttr).applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PreDefendAbAttr).canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostDefendAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostDefendAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
attacker: Pokemon,
|
||||||
|
move: Move,
|
||||||
|
hitResult: HitResult | null,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostDefendAbAttr).applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostDefendAbAttr).canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostMoveUsedAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostMoveUsedAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
move: PokemonMove,
|
||||||
|
source: Pokemon,
|
||||||
|
targets: BattlerIndex[],
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, _passive) => (attr as PostMoveUsedAbAttr).applyPostMoveUsed(pokemon, move, source, targets, simulated, args),
|
||||||
|
(attr, _passive) =>
|
||||||
|
(attr as PostMoveUsedAbAttr).canApplyPostMoveUsed(pokemon, move, source, targets, simulated, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyStatMultiplierAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends StatMultiplierAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
stat: BattleStat,
|
||||||
|
statValue: NumberHolder,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as StatMultiplierAbAttr).applyStatStage(pokemon, passive, simulated, stat, statValue, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as StatMultiplierAbAttr).canApplyStatStage(pokemon, passive, simulated, stat, statValue, args),
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies an ally's Stat multiplier attribute
|
||||||
|
* @param attrType - {@linkcode AllyStatMultiplierAbAttr} should always be AllyStatMultiplierAbAttr for the time being
|
||||||
|
* @param pokemon - The {@linkcode Pokemon} with the ability
|
||||||
|
* @param stat - The type of the checked {@linkcode Stat}
|
||||||
|
* @param statValue - {@linkcode NumberHolder} containing the value of the checked stat
|
||||||
|
* @param checkedPokemon - The {@linkcode Pokemon} with the checked stat
|
||||||
|
* @param ignoreAbility - Whether or not the ability should be ignored by the pokemon or its move.
|
||||||
|
* @param args - unused
|
||||||
|
*/
|
||||||
|
export function applyAllyStatMultiplierAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends AllyStatMultiplierAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
stat: BattleStat,
|
||||||
|
statValue: NumberHolder,
|
||||||
|
simulated = false,
|
||||||
|
checkedPokemon: Pokemon,
|
||||||
|
ignoreAbility: boolean,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as AllyStatMultiplierAbAttr).applyAllyStat(
|
||||||
|
pokemon,
|
||||||
|
passive,
|
||||||
|
simulated,
|
||||||
|
stat,
|
||||||
|
statValue,
|
||||||
|
checkedPokemon,
|
||||||
|
ignoreAbility,
|
||||||
|
args,
|
||||||
|
),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as AllyStatMultiplierAbAttr).canApplyAllyStat(
|
||||||
|
pokemon,
|
||||||
|
passive,
|
||||||
|
simulated,
|
||||||
|
stat,
|
||||||
|
statValue,
|
||||||
|
checkedPokemon,
|
||||||
|
ignoreAbility,
|
||||||
|
args,
|
||||||
|
),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostSetStatusAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostSetStatusAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
effect: StatusEffect,
|
||||||
|
sourcePokemon?: Pokemon | null,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostSetStatusAbAttr).applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostSetStatusAbAttr).canApplyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostDamageAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostDamageAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
damage: number,
|
||||||
|
_passive: boolean,
|
||||||
|
simulated = false,
|
||||||
|
args: any[],
|
||||||
|
source?: Pokemon,
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) => (attr as PostDamageAbAttr).applyPostDamage(pokemon, damage, passive, simulated, args, source),
|
||||||
|
(attr, passive) => (attr as PostDamageAbAttr).canApplyPostDamage(pokemon, damage, passive, simulated, args, source),
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Applies a field Stat multiplier attribute
|
||||||
|
* @param attrType {@linkcode FieldMultiplyStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being
|
||||||
|
* @param pokemon {@linkcode Pokemon} the Pokemon applying this ability
|
||||||
|
* @param stat {@linkcode Stat} the type of the checked stat
|
||||||
|
* @param statValue {@linkcode NumberHolder} the value of the checked stat
|
||||||
|
* @param checkedPokemon {@linkcode Pokemon} the Pokemon with the checked stat
|
||||||
|
* @param hasApplied {@linkcode BooleanHolder} whether or not a FieldMultiplyBattleStatAbAttr has already affected this stat
|
||||||
|
* @param args unused
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function applyFieldStatMultiplierAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends FieldMultiplyStatAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
stat: Stat,
|
||||||
|
statValue: NumberHolder,
|
||||||
|
checkedPokemon: Pokemon,
|
||||||
|
hasApplied: BooleanHolder,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as FieldMultiplyStatAbAttr).applyFieldStat(
|
||||||
|
pokemon,
|
||||||
|
passive,
|
||||||
|
simulated,
|
||||||
|
stat,
|
||||||
|
statValue,
|
||||||
|
checkedPokemon,
|
||||||
|
hasApplied,
|
||||||
|
args,
|
||||||
|
),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as FieldMultiplyStatAbAttr).canApplyFieldStat(
|
||||||
|
pokemon,
|
||||||
|
passive,
|
||||||
|
simulated,
|
||||||
|
stat,
|
||||||
|
statValue,
|
||||||
|
checkedPokemon,
|
||||||
|
hasApplied,
|
||||||
|
args,
|
||||||
|
),
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPreAttackAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PreAttackAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
defender: Pokemon | null,
|
||||||
|
move: Move,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) => (attr as PreAttackAbAttr).applyPreAttack(pokemon, passive, simulated, defender, move, args),
|
||||||
|
(attr, passive) => (attr as PreAttackAbAttr).canApplyPreAttack(pokemon, passive, simulated, defender, move, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyExecutedMoveAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends ExecutedMoveAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
attr => (attr as ExecutedMoveAbAttr).applyExecutedMove(pokemon, simulated),
|
||||||
|
attr => (attr as ExecutedMoveAbAttr).canApplyExecutedMove(pokemon, simulated),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostAttackAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostAttackAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
defender: Pokemon,
|
||||||
|
move: Move,
|
||||||
|
hitResult: HitResult | null,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostAttackAbAttr).applyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostAttackAbAttr).canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostKnockOutAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostKnockOutAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
knockedOut: Pokemon,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) => (attr as PostKnockOutAbAttr).applyPostKnockOut(pokemon, passive, simulated, knockedOut, args),
|
||||||
|
(attr, passive) => (attr as PostKnockOutAbAttr).canApplyPostKnockOut(pokemon, passive, simulated, knockedOut, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostVictoryAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostVictoryAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) => (attr as PostVictoryAbAttr).applyPostVictory(pokemon, passive, simulated, args),
|
||||||
|
(attr, passive) => (attr as PostVictoryAbAttr).canApplyPostVictory(pokemon, passive, simulated, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostSummonAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostSummonAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
passive = false,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applySingleAbAttrs(
|
||||||
|
pokemon,
|
||||||
|
passive,
|
||||||
|
attrType,
|
||||||
|
(attr, passive) => (attr as PostSummonAbAttr).applyPostSummon(pokemon, passive, simulated, args),
|
||||||
|
(attr, passive) => (attr as PostSummonAbAttr).canApplyPostSummon(pokemon, passive, simulated, args),
|
||||||
|
args,
|
||||||
|
false,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPreSummonAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PreSummonAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) => (attr as PreSummonAbAttr).applyPreSummon(pokemon, passive, args),
|
||||||
|
(attr, passive) => (attr as PreSummonAbAttr).canApplyPreSummon(pokemon, passive, args),
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPreSwitchOutAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PreSwitchOutAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) => (attr as PreSwitchOutAbAttr).applyPreSwitchOut(pokemon, passive, simulated, args),
|
||||||
|
(attr, passive) => (attr as PreSwitchOutAbAttr).canApplyPreSwitchOut(pokemon, passive, simulated, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPreLeaveFieldAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PreLeaveFieldAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) => (attr as PreLeaveFieldAbAttr).applyPreLeaveField(pokemon, passive, simulated, args),
|
||||||
|
(attr, passive) => (attr as PreLeaveFieldAbAttr).canApplyPreLeaveField(pokemon, passive, simulated, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPreStatStageChangeAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PreStatStageChangeAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon | null,
|
||||||
|
stat: BattleStat,
|
||||||
|
cancelled: BooleanHolder,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PreStatStageChangeAbAttr).applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PreStatStageChangeAbAttr).canApplyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostStatStageChangeAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostStatStageChangeAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
stats: BattleStat[],
|
||||||
|
stages: number,
|
||||||
|
selfTarget: boolean,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, _passive) =>
|
||||||
|
(attr as PostStatStageChangeAbAttr).applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args),
|
||||||
|
(attr, _passive) =>
|
||||||
|
(attr as PostStatStageChangeAbAttr).canApplyPostStatStageChange(
|
||||||
|
pokemon,
|
||||||
|
simulated,
|
||||||
|
stats,
|
||||||
|
stages,
|
||||||
|
selfTarget,
|
||||||
|
args,
|
||||||
|
),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPreSetStatusAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PreSetStatusAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
effect: StatusEffect | undefined,
|
||||||
|
cancelled: BooleanHolder,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PreSetStatusAbAttr).applyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PreSetStatusAbAttr).canApplyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPreApplyBattlerTagAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PreApplyBattlerTagAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
tag: BattlerTag,
|
||||||
|
cancelled: BooleanHolder,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PreApplyBattlerTagAbAttr).applyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PreApplyBattlerTagAbAttr).canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPreWeatherEffectAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PreWeatherEffectAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
weather: Weather | null,
|
||||||
|
cancelled: BooleanHolder,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PreWeatherDamageAbAttr).applyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PreWeatherDamageAbAttr).canApplyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostTurnAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostTurnAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) => (attr as PostTurnAbAttr).applyPostTurn(pokemon, passive, simulated, args),
|
||||||
|
(attr, passive) => (attr as PostTurnAbAttr).canApplyPostTurn(pokemon, passive, simulated, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostWeatherChangeAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostWeatherChangeAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
weather: WeatherType,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostWeatherChangeAbAttr).applyPostWeatherChange(pokemon, passive, simulated, weather, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostWeatherChangeAbAttr).canApplyPostWeatherChange(pokemon, passive, simulated, weather, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostWeatherLapseAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostWeatherLapseAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
weather: Weather | null,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostWeatherLapseAbAttr).applyPostWeatherLapse(pokemon, passive, simulated, weather, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostWeatherLapseAbAttr).canApplyPostWeatherLapse(pokemon, passive, simulated, weather, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostTerrainChangeAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostTerrainChangeAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
terrain: TerrainType,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostTerrainChangeAbAttr).applyPostTerrainChange(pokemon, passive, simulated, terrain, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostTerrainChangeAbAttr).canApplyPostTerrainChange(pokemon, passive, simulated, terrain, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyCheckTrappedAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends CheckTrappedAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
trapped: BooleanHolder,
|
||||||
|
otherPokemon: Pokemon,
|
||||||
|
messages: string[],
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as CheckTrappedAbAttr).applyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as CheckTrappedAbAttr).canApplyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
messages,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostBattleAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostBattleAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) => (attr as PostBattleAbAttr).applyPostBattle(pokemon, passive, simulated, args),
|
||||||
|
(attr, passive) => (attr as PostBattleAbAttr).canApplyPostBattle(pokemon, passive, simulated, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostFaintAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostFaintAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
attacker?: Pokemon,
|
||||||
|
move?: Move,
|
||||||
|
hitResult?: HitResult,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostFaintAbAttr).applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args),
|
||||||
|
(attr, passive) =>
|
||||||
|
(attr as PostFaintAbAttr).canApplyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args),
|
||||||
|
args,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPostItemLostAbAttrs<K extends AbAttrString>(
|
||||||
|
attrType: AbAttrMap[K] extends PostItemLostAbAttr ? K : never,
|
||||||
|
pokemon: Pokemon,
|
||||||
|
simulated = false,
|
||||||
|
...args: any[]
|
||||||
|
): void {
|
||||||
|
applyAbAttrsInternal(
|
||||||
|
attrType,
|
||||||
|
pokemon,
|
||||||
|
(attr, _passive) => (attr as PostItemLostAbAttr).applyPostItemLost(pokemon, simulated, args),
|
||||||
|
(attr, _passive) => (attr as PostItemLostAbAttr).canApplyPostItemLost(pokemon, simulated, args),
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies abilities when they become active mid-turn (ability switch)
|
||||||
|
*
|
||||||
|
* Ignores passives as they don't change and shouldn't be reapplied when main abilities change
|
||||||
|
*/
|
||||||
|
export function applyOnGainAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
|
||||||
|
applySingleAbAttrs(
|
||||||
|
pokemon,
|
||||||
|
passive,
|
||||||
|
"PostSummonAbAttr",
|
||||||
|
(attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args),
|
||||||
|
(attr, passive) => attr.canApplyPostSummon(pokemon, passive, simulated, args),
|
||||||
|
args,
|
||||||
|
true,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Applies ability attributes which activate when the ability is lost or suppressed (i.e. primal weather)
|
||||||
|
*/
|
||||||
|
export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
|
||||||
|
applySingleAbAttrs(
|
||||||
|
pokemon,
|
||||||
|
passive,
|
||||||
|
"PreLeaveFieldAbAttr",
|
||||||
|
(attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [...args, true]),
|
||||||
|
(attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, [...args, true]),
|
||||||
|
args,
|
||||||
|
true,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
|
||||||
|
applySingleAbAttrs(
|
||||||
|
pokemon,
|
||||||
|
passive,
|
||||||
|
"IllusionBreakAbAttr",
|
||||||
|
(attr, passive) => attr.apply(pokemon, passive, simulated, null, args),
|
||||||
|
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
|
||||||
|
args,
|
||||||
|
true,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
|
}
|
@ -10,15 +10,7 @@ import type Pokemon from "#app/field/pokemon";
|
|||||||
import { HitResult } from "#enums/hit-result";
|
import { HitResult } from "#enums/hit-result";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import {
|
import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "./abilities/apply-ab-attrs";
|
||||||
BlockNonDirectDamageAbAttr,
|
|
||||||
InfiltratorAbAttr,
|
|
||||||
PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr,
|
|
||||||
ProtectStatAbAttr,
|
|
||||||
applyAbAttrs,
|
|
||||||
applyOnGainAbAttrs,
|
|
||||||
applyOnLoseAbAttrs,
|
|
||||||
} from "#app/data/abilities/ability";
|
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { CommonBattleAnim } from "#app/data/battle-anims";
|
import { CommonBattleAnim } from "#app/data/battle-anims";
|
||||||
import { CommonAnim } from "#enums/move-anims-common";
|
import { CommonAnim } from "#enums/move-anims-common";
|
||||||
@ -28,6 +20,7 @@ import { ArenaTagType } from "#enums/arena-tag-type";
|
|||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
|
import { MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
|
||||||
export abstract class ArenaTag {
|
export abstract class ArenaTag {
|
||||||
constructor(
|
constructor(
|
||||||
@ -144,7 +137,7 @@ export class MistTag extends ArenaTag {
|
|||||||
if (attacker) {
|
if (attacker) {
|
||||||
const bypassed = new BooleanHolder(false);
|
const bypassed = new BooleanHolder(false);
|
||||||
// TODO: Allow this to be simulated
|
// TODO: Allow this to be simulated
|
||||||
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed);
|
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
|
||||||
if (bypassed.value) {
|
if (bypassed.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -209,7 +202,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
|
|||||||
): boolean {
|
): boolean {
|
||||||
if (this.weakenedCategories.includes(moveCategory)) {
|
if (this.weakenedCategories.includes(moveCategory)) {
|
||||||
const bypassed = new BooleanHolder(false);
|
const bypassed = new BooleanHolder(false);
|
||||||
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed);
|
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
|
||||||
if (bypassed.value) {
|
if (bypassed.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -765,7 +758,7 @@ class SpikesTag extends ArenaTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||||
if (simulated || cancelled.value) {
|
if (simulated || cancelled.value) {
|
||||||
return !cancelled.value;
|
return !cancelled.value;
|
||||||
}
|
}
|
||||||
@ -883,13 +876,13 @@ export class DelayedAttackTag extends ArenaTag {
|
|||||||
const ret = super.lapse(arena);
|
const ret = super.lapse(arena);
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
|
// TODO: This should not add to move history (for Spite)
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"MoveEffectPhase",
|
"MoveEffectPhase",
|
||||||
this.sourceId!,
|
this.sourceId!,
|
||||||
[this.targetIndex],
|
[this.targetIndex],
|
||||||
allMoves[this.sourceMove!],
|
allMoves[this.sourceMove!],
|
||||||
false,
|
MoveUseMode.FOLLOW_UP,
|
||||||
true,
|
|
||||||
); // TODO: are those bangs correct?
|
); // TODO: are those bangs correct?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -953,7 +946,7 @@ class StealthRockTag extends ArenaTrapTag {
|
|||||||
|
|
||||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1010,7 +1003,7 @@ class StickyWebTag extends ArenaTrapTag {
|
|||||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||||
if (pokemon.isGrounded()) {
|
if (pokemon.isGrounded()) {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
|
applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
|
||||||
|
|
||||||
if (simulated) {
|
if (simulated) {
|
||||||
return !cancelled.value;
|
return !cancelled.value;
|
||||||
@ -1444,8 +1437,8 @@ export class SuppressAbilitiesTag extends ArenaTag {
|
|||||||
// Could have a custom message that plays when a specific pokemon's NG ends? This entire thing exists due to passives after all
|
// Could have a custom message that plays when a specific pokemon's NG ends? This entire thing exists due to passives after all
|
||||||
const setter = globalScene
|
const setter = globalScene
|
||||||
.getField()
|
.getField()
|
||||||
.filter(p => p?.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false))[0];
|
.filter(p => p?.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0];
|
||||||
applyOnGainAbAttrs(setter, setter.getAbility().hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr));
|
applyOnGainAbAttrs(setter, setter.getAbility().hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1457,7 +1450,7 @@ export class SuppressAbilitiesTag extends ArenaTag {
|
|||||||
|
|
||||||
for (const pokemon of globalScene.getField(true)) {
|
for (const pokemon of globalScene.getField(true)) {
|
||||||
// There is only one pokemon with this attr on the field on removal, so its abilities are already active
|
// There is only one pokemon with this attr on the field on removal, so its abilities are already active
|
||||||
if (pokemon && !pokemon.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false)) {
|
if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) {
|
||||||
[true, false].forEach(passive => applyOnGainAbAttrs(pokemon, passive));
|
[true, false].forEach(passive => applyOnGainAbAttrs(pokemon, passive));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import { MoveId } from "#enums/move-id";
|
|||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||||
import { TimeOfDay } from "#enums/time-of-day";
|
import { TimeOfDay } from "#enums/time-of-day";
|
||||||
import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifier, SpeciesStatBoosterModifier, TempExtraModifierModifier } from "#app/modifier/modifier";
|
|
||||||
import type { SpeciesStatBoosterModifierType } from "#app/modifier/modifier-type";
|
import type { SpeciesStatBoosterModifierType } from "#app/modifier/modifier-type";
|
||||||
import { speciesStarterCosts } from "./starters";
|
import { speciesStarterCosts } from "./starters";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
@ -275,9 +274,9 @@ class MoveTypeEvolutionCondition extends SpeciesEvolutionCondition {
|
|||||||
class TreasureEvolutionCondition extends SpeciesEvolutionCondition {
|
class TreasureEvolutionCondition extends SpeciesEvolutionCondition {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(p => p.evoCounter
|
super(p => p.evoCounter
|
||||||
+ p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length
|
+ p.getHeldItems().filter(m => m.is("DamageMoneyRewardModifier")).length
|
||||||
+ globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier
|
+ globalScene.findModifiers(m => m.is("MoneyMultiplierModifier")
|
||||||
|| m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9);
|
|| m.is("ExtraModifierModifier") || m.is("TempExtraModifierModifier")).length > 9);
|
||||||
this.description = i18next.t("pokemonEvolutions:treasure");
|
this.description = i18next.t("pokemonEvolutions:treasure");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1794,8 +1793,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
|
|||||||
],
|
],
|
||||||
[SpeciesId.CLAMPERL]: [
|
[SpeciesId.CLAMPERL]: [
|
||||||
// TODO: Change the SpeciesEvolutionConditions here to use a bespoke HeldItemEvolutionCondition after the modifier rework
|
// TODO: Change the SpeciesEvolutionConditions here to use a bespoke HeldItemEvolutionCondition after the modifier rework
|
||||||
new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m instanceof SpeciesStatBoosterModifier && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_TOOTH")), SpeciesWildEvolutionDelay.VERY_LONG),
|
new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_TOOTH")), SpeciesWildEvolutionDelay.VERY_LONG),
|
||||||
new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m instanceof SpeciesStatBoosterModifier && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_SCALE")), SpeciesWildEvolutionDelay.VERY_LONG)
|
new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_SCALE")), SpeciesWildEvolutionDelay.VERY_LONG)
|
||||||
],
|
],
|
||||||
[SpeciesId.BOLDORE]: [
|
[SpeciesId.BOLDORE]: [
|
||||||
new SpeciesEvolution(SpeciesId.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)
|
new SpeciesEvolution(SpeciesId.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
|
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { allMoves } from "./data-lists";
|
import { allMoves } from "#app/data/data-lists";
|
||||||
import { MoveFlags } from "#enums/MoveFlags";
|
import { MoveFlags } from "#enums/MoveFlags";
|
||||||
import type Pokemon from "../field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common";
|
import {
|
||||||
|
type nil,
|
||||||
|
getFrameMs,
|
||||||
|
getEnumKeys,
|
||||||
|
getEnumValues,
|
||||||
|
animationFileName,
|
||||||
|
coerceArray,
|
||||||
|
isNullOrUndefined,
|
||||||
|
} from "#app/utils/common";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { SubstituteTag } from "./battler-tags";
|
|
||||||
import { isNullOrUndefined } from "../utils/common";
|
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { EncounterAnim } from "#enums/encounter-anims";
|
import { EncounterAnim } from "#enums/encounter-anims";
|
||||||
import { AnimBlendType, AnimFrameTarget, AnimFocus, ChargeAnim, CommonAnim } from "#enums/move-anims-common";
|
import { AnimBlendType, AnimFrameTarget, AnimFocus, ChargeAnim, CommonAnim } from "#enums/move-anims-common";
|
||||||
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
|
|
||||||
export class AnimConfig {
|
export class AnimConfig {
|
||||||
public id: number;
|
public id: number;
|
||||||
@ -519,7 +526,7 @@ function logMissingMoveAnim(move: MoveId, ...optionalParams: any[]) {
|
|||||||
* @param encounterAnim one or more animations to fetch
|
* @param encounterAnim one or more animations to fetch
|
||||||
*/
|
*/
|
||||||
export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
|
export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
|
||||||
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim];
|
const anims = coerceArray(encounterAnim);
|
||||||
const encounterAnimNames = getEnumKeys(EncounterAnim);
|
const encounterAnimNames = getEnumKeys(EncounterAnim);
|
||||||
const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
|
const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
|
||||||
for (const anim of anims) {
|
for (const anim of anims) {
|
||||||
@ -770,7 +777,7 @@ export abstract class BattleAnim {
|
|||||||
const user = !isOppAnim ? this.user : this.target;
|
const user = !isOppAnim ? this.user : this.target;
|
||||||
const target = !isOppAnim ? this.target : this.user;
|
const target = !isOppAnim ? this.target : this.user;
|
||||||
|
|
||||||
const targetSubstitute = onSubstitute && user !== target ? target!.getTag(SubstituteTag) : null;
|
const targetSubstitute = onSubstitute && user !== target ? target!.getTag(BattlerTagType.SUBSTITUTE) : null;
|
||||||
|
|
||||||
const userInitialX = user!.x; // TODO: is this bang correct?
|
const userInitialX = user!.x; // TODO: is this bang correct?
|
||||||
const userInitialY = user!.y; // TODO: is this bang correct?
|
const userInitialY = user!.y; // TODO: is this bang correct?
|
||||||
@ -844,7 +851,7 @@ export abstract class BattleAnim {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetSubstitute = !!onSubstitute && user !== target ? target.getTag(SubstituteTag) : null;
|
const targetSubstitute = !!onSubstitute && user !== target ? target.getTag(BattlerTagType.SUBSTITUTE) : null;
|
||||||
|
|
||||||
const userSprite = user.getSprite();
|
const userSprite = user.getSprite();
|
||||||
const targetSprite = targetSubstitute?.sprite ?? target.getSprite();
|
const targetSprite = targetSubstitute?.sprite ?? target.getSprite();
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import {
|
import { applyAbAttrs } from "./abilities/apply-ab-attrs";
|
||||||
applyAbAttrs,
|
|
||||||
BlockNonDirectDamageAbAttr,
|
|
||||||
FlinchEffectAbAttr,
|
|
||||||
ProtectStatAbAttr,
|
|
||||||
ConditionalUserFieldProtectStatAbAttr,
|
|
||||||
ReverseDrainAbAttr,
|
|
||||||
} from "#app/data/abilities/ability";
|
|
||||||
import { allAbilities } from "./data-lists";
|
import { allAbilities } from "./data-lists";
|
||||||
import { CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims";
|
import { CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims";
|
||||||
import { ChargeAnim, CommonAnim } from "#enums/move-anims-common";
|
import { ChargeAnim, CommonAnim } from "#enums/move-anims-common";
|
||||||
@ -28,7 +21,7 @@ import type { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
|||||||
import type { MovePhase } from "#app/phases/move-phase";
|
import type { MovePhase } from "#app/phases/move-phase";
|
||||||
import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
|
import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
|
||||||
import i18next from "#app/plugins/i18n";
|
import i18next from "#app/plugins/i18n";
|
||||||
import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils/common";
|
import { BooleanHolder, coerceArray, getFrameMs, NumberHolder, toDmgValue } from "#app/utils/common";
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
@ -38,8 +31,15 @@ import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat
|
|||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import { WeatherType } from "#enums/weather-type";
|
import { WeatherType } from "#enums/weather-type";
|
||||||
import { isNullOrUndefined } from "#app/utils/common";
|
import { isNullOrUndefined } from "#app/utils/common";
|
||||||
|
import { MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
import { invalidEncoreMoves } from "./moves/invalid-moves";
|
||||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@linkcode BattlerTag} represents a semi-persistent effect that can be attached to a {@linkcode Pokemon}.
|
||||||
|
* Tags can trigger various effects throughout a turn, and are cleared on switching out
|
||||||
|
* or through their respective {@linkcode BattlerTag.lapse | lapse} methods.
|
||||||
|
*/
|
||||||
export class BattlerTag {
|
export class BattlerTag {
|
||||||
public tagType: BattlerTagType;
|
public tagType: BattlerTagType;
|
||||||
public lapseTypes: BattlerTagLapseType[];
|
public lapseTypes: BattlerTagLapseType[];
|
||||||
@ -57,7 +57,7 @@ export class BattlerTag {
|
|||||||
isBatonPassable = false,
|
isBatonPassable = false,
|
||||||
) {
|
) {
|
||||||
this.tagType = tagType;
|
this.tagType = tagType;
|
||||||
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [lapseType];
|
this.lapseTypes = coerceArray(lapseType);
|
||||||
this.turnCount = turnCount;
|
this.turnCount = turnCount;
|
||||||
this.sourceMove = sourceMove!; // TODO: is this bang correct?
|
this.sourceMove = sourceMove!; // TODO: is this bang correct?
|
||||||
this.sourceId = sourceId;
|
this.sourceId = sourceId;
|
||||||
@ -76,7 +76,7 @@ export class BattlerTag {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tick down this {@linkcode BattlerTag}'s duration.
|
* Tick down this {@linkcode BattlerTag}'s duration.
|
||||||
* @returns `true` if the tag should be kept (`turnCount` > 0`)
|
* @returns `true` if the tag should be kept (`turnCount > 0`)
|
||||||
*/
|
*/
|
||||||
lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
|
lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
|
||||||
// TODO: Maybe flip this (return `true` if tag needs removal)
|
// TODO: Maybe flip this (return `true` if tag needs removal)
|
||||||
@ -132,16 +132,6 @@ export interface TerrainBattlerTag {
|
|||||||
* Players and enemies should not be allowed to select restricted moves.
|
* Players and enemies should not be allowed to select restricted moves.
|
||||||
*/
|
*/
|
||||||
export abstract class MoveRestrictionBattlerTag extends BattlerTag {
|
export abstract class MoveRestrictionBattlerTag extends BattlerTag {
|
||||||
constructor(
|
|
||||||
tagType: BattlerTagType,
|
|
||||||
lapseType: BattlerTagLapseType | BattlerTagLapseType[],
|
|
||||||
turnCount: number,
|
|
||||||
sourceMove?: MoveId,
|
|
||||||
sourceId?: number,
|
|
||||||
) {
|
|
||||||
super(tagType, lapseType, turnCount, sourceMove, sourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
|
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
|
||||||
@ -284,17 +274,18 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
|
|||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
*
|
*
|
||||||
* Ensures that move history exists on `pokemon` and has a valid move. If so, sets the {@linkcode moveId} and shows a message.
|
* Attempt to disable the target's last move by setting this tag's {@linkcode moveId}
|
||||||
* Otherwise the move ID will not get assigned and this tag will get removed next turn.
|
* and showing a message.
|
||||||
*/
|
*/
|
||||||
override onAdd(pokemon: Pokemon): void {
|
override onAdd(pokemon: Pokemon): void {
|
||||||
super.onAdd(pokemon);
|
// Disable fails against struggle or an empty move history
|
||||||
|
// TODO: Confirm if this is redundant given Disable/Cursed Body's disable conditions
|
||||||
const move = pokemon.getLastXMoves(-1).find(m => !m.virtual);
|
const move = pokemon.getLastNonVirtualMove();
|
||||||
if (isNullOrUndefined(move) || move.move === MoveId.STRUGGLE || move.move === MoveId.NONE) {
|
if (isNullOrUndefined(move) || move.move === MoveId.STRUGGLE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onAdd(pokemon);
|
||||||
this.moveId = move.move;
|
this.moveId = move.move;
|
||||||
|
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
@ -344,7 +335,6 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tag used by Gorilla Tactics to restrict the user to using only one move.
|
* Tag used by Gorilla Tactics to restrict the user to using only one move.
|
||||||
* @extends MoveRestrictionBattlerTag
|
|
||||||
*/
|
*/
|
||||||
export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
|
export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
|
||||||
private moveId = MoveId.NONE;
|
private moveId = MoveId.NONE;
|
||||||
@ -353,34 +343,30 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
|
|||||||
super(BattlerTagType.GORILLA_TACTICS, BattlerTagLapseType.CUSTOM, 0);
|
super(BattlerTagType.GORILLA_TACTICS, BattlerTagLapseType.CUSTOM, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
|
||||||
override isMoveRestricted(move: MoveId): boolean {
|
override isMoveRestricted(move: MoveId): boolean {
|
||||||
return move !== this.moveId;
|
return move !== this.moveId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* Ensures that move history exists on {@linkcode Pokemon} and has a valid move to lock into.
|
||||||
* @param {Pokemon} pokemon the {@linkcode Pokemon} to check if the tag can be added
|
* @param pokemon - The {@linkcode Pokemon} to add the tag to
|
||||||
* @returns `true` if the pokemon has a valid move and no existing {@linkcode GorillaTacticsTag}; `false` otherwise
|
* @returns `true` if the tag can be added
|
||||||
*/
|
*/
|
||||||
override canAdd(pokemon: Pokemon): boolean {
|
override canAdd(pokemon: Pokemon): boolean {
|
||||||
return this.getLastValidMove(pokemon) !== undefined && !pokemon.getTag(GorillaTacticsTag);
|
// Choice items ignore struggle, so Gorilla Tactics should too
|
||||||
|
const lastSelectedMove = pokemon.getLastNonVirtualMove();
|
||||||
|
return !isNullOrUndefined(lastSelectedMove) && lastSelectedMove.move !== MoveId.STRUGGLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures that move history exists on {@linkcode Pokemon} and has a valid move.
|
* Sets this tag's {@linkcode moveId} and increases the user's Attack by 50%.
|
||||||
* If so, sets the {@linkcode moveId} and increases the user's Attack by 50%.
|
* @param pokemon - The {@linkcode Pokemon} to add the tag to
|
||||||
* @override
|
|
||||||
* @param {Pokemon} pokemon the {@linkcode Pokemon} to add the tag to
|
|
||||||
*/
|
*/
|
||||||
override onAdd(pokemon: Pokemon): void {
|
override onAdd(pokemon: Pokemon): void {
|
||||||
const lastValidMove = this.getLastValidMove(pokemon);
|
super.onAdd(pokemon);
|
||||||
|
|
||||||
if (!lastValidMove) {
|
// Bang is justified as tag is not added if prior move doesn't exist
|
||||||
return;
|
this.moveId = pokemon.getLastNonVirtualMove()!.move;
|
||||||
}
|
|
||||||
|
|
||||||
this.moveId = lastValidMove;
|
|
||||||
pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false);
|
pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,29 +381,16 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Return the text displayed when a move is restricted.
|
||||||
* @override
|
* @param pokemon - The {@linkcode Pokemon} with this tag.
|
||||||
* @param {Pokemon} pokemon n/a
|
* @returns A string containing the text to display when the move is denied
|
||||||
* @param {MoveId} _move {@linkcode MoveId} ID of the move being denied
|
|
||||||
* @returns {string} text to display when the move is denied
|
|
||||||
*/
|
*/
|
||||||
override selectionDeniedText(pokemon: Pokemon, _move: MoveId): string {
|
override selectionDeniedText(pokemon: Pokemon): string {
|
||||||
return i18next.t("battle:canOnlyUseMove", {
|
return i18next.t("battle:canOnlyUseMove", {
|
||||||
moveName: allMoves[this.moveId].name,
|
moveName: allMoves[this.moveId].name,
|
||||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the last valid move from the pokemon's move history.
|
|
||||||
* @param {Pokemon} pokemon {@linkcode Pokemon} to get the last valid move from
|
|
||||||
* @returns {MoveId | undefined} the last valid move from the pokemon's move history
|
|
||||||
*/
|
|
||||||
getLastValidMove(pokemon: Pokemon): MoveId | undefined {
|
|
||||||
const move = pokemon.getLastXMoves().find(m => m.move !== MoveId.NONE && m.move !== MoveId.STRUGGLE && !m.virtual);
|
|
||||||
|
|
||||||
return move?.move;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -431,8 +404,8 @@ export class RechargingTag extends BattlerTag {
|
|||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
super.onAdd(pokemon);
|
super.onAdd(pokemon);
|
||||||
|
|
||||||
// Queue a placeholder move for the Pokemon to "use" next turn
|
// Queue a placeholder move for the Pokemon to "use" next turn.
|
||||||
pokemon.getMoveQueue().push({ move: MoveId.NONE, targets: [] });
|
pokemon.pushMoveQueue({ move: MoveId.NONE, targets: [], useMode: MoveUseMode.NORMAL });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cancels the source's move this turn and queues a "__ must recharge!" message */
|
/** Cancels the source's move this turn and queues a "__ must recharge!" message */
|
||||||
@ -648,7 +621,7 @@ export class FlinchedTag extends BattlerTag {
|
|||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
applyAbAttrs(FlinchEffectAbAttr, pokemon, null);
|
applyAbAttrs("FlinchEffectAbAttr", pokemon, null);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -677,6 +650,7 @@ export class InterruptedTag extends BattlerTag {
|
|||||||
move: MoveId.NONE,
|
move: MoveId.NONE,
|
||||||
result: MoveResult.OTHER,
|
result: MoveResult.OTHER,
|
||||||
targets: [],
|
targets: [],
|
||||||
|
useMode: MoveUseMode.NORMAL,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -942,7 +916,7 @@ export class SeedTag extends BattlerTag {
|
|||||||
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
|
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
|
||||||
if (source) {
|
if (source) {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
@ -953,7 +927,7 @@ export class SeedTag extends BattlerTag {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
||||||
const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false);
|
const reverseDrain = pokemon.hasAbilityWithAttr("ReverseDrainAbAttr", false);
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"PokemonHealPhase",
|
"PokemonHealPhase",
|
||||||
source.getBattlerIndex(),
|
source.getBattlerIndex(),
|
||||||
@ -1002,42 +976,45 @@ export class PowderTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies Powder's effects before the tag owner uses a Fire-type move.
|
* Applies Powder's effects before the tag owner uses a Fire-type move, damaging and canceling its action.
|
||||||
* Also causes the tag to expire at the end of turn.
|
* Lasts until the end of the turn.
|
||||||
* @param pokemon {@linkcode Pokemon} the owner of this tag
|
* @param pokemon - The {@linkcode Pokemon} with this tag.
|
||||||
* @param lapseType {@linkcode BattlerTagLapseType} the type of lapse functionality to carry out
|
* @param lapseType - The {@linkcode BattlerTagLapseType} dictating how this tag is being activated
|
||||||
* @returns `true` if the tag should not expire after this lapse; `false` otherwise.
|
* @returns `true` if the tag should remain active.
|
||||||
*/
|
*/
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
|
const movePhase = globalScene.phaseManager.getCurrentPhase();
|
||||||
const movePhase = globalScene.phaseManager.getCurrentPhase();
|
if (lapseType !== BattlerTagLapseType.PRE_MOVE || !movePhase?.is("MovePhase")) {
|
||||||
if (movePhase?.is("MovePhase")) {
|
return false;
|
||||||
const move = movePhase.move.getMove();
|
}
|
||||||
const weather = globalScene.arena.weather;
|
|
||||||
if (
|
|
||||||
pokemon.getMoveType(move) === PokemonType.FIRE &&
|
|
||||||
!(weather && weather.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed())
|
|
||||||
) {
|
|
||||||
movePhase.fail();
|
|
||||||
movePhase.showMoveText();
|
|
||||||
|
|
||||||
const idx = pokemon.getBattlerIndex();
|
const move = movePhase.move.getMove();
|
||||||
|
const weather = globalScene.arena.weather;
|
||||||
globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER);
|
if (
|
||||||
|
pokemon.getMoveType(move) !== PokemonType.FIRE ||
|
||||||
const cancelDamage = new BooleanHolder(false);
|
(weather?.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed()) // Heavy rain takes priority over powder
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelDamage);
|
) {
|
||||||
if (!cancelDamage.value) {
|
|
||||||
pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
|
||||||
}
|
|
||||||
|
|
||||||
// "When the flame touched the powder\non the Pokémon, it exploded!"
|
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return super.lapse(pokemon, lapseType);
|
|
||||||
|
// Disable the target's fire type move and damage it (subject to Magic Guard)
|
||||||
|
movePhase.showMoveText();
|
||||||
|
movePhase.fail();
|
||||||
|
|
||||||
|
const idx = pokemon.getBattlerIndex();
|
||||||
|
|
||||||
|
globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER);
|
||||||
|
|
||||||
|
const cancelDamage = new BooleanHolder(false);
|
||||||
|
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelDamage);
|
||||||
|
if (!cancelDamage.value) {
|
||||||
|
pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
||||||
|
}
|
||||||
|
|
||||||
|
// "When the flame touched the powder\non the Pokémon, it exploded!"
|
||||||
|
globalScene.phaseManager.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name }));
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1079,7 +1056,7 @@ export class NightmareTag extends BattlerTag {
|
|||||||
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type
|
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
||||||
@ -1132,34 +1109,22 @@ export class EncoreTag extends MoveRestrictionBattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
const lastMoves = pokemon.getLastXMoves(1);
|
const lastMove = pokemon.getLastNonVirtualMove();
|
||||||
if (!lastMoves.length) {
|
if (!lastMove) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const repeatableMove = lastMoves[0];
|
if (invalidEncoreMoves.has(lastMove.move)) {
|
||||||
|
|
||||||
if (!repeatableMove.move || repeatableMove.virtual) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (repeatableMove.move) {
|
this.moveId = lastMove.move;
|
||||||
case MoveId.MIMIC:
|
|
||||||
case MoveId.MIRROR_MOVE:
|
|
||||||
case MoveId.TRANSFORM:
|
|
||||||
case MoveId.STRUGGLE:
|
|
||||||
case MoveId.SKETCH:
|
|
||||||
case MoveId.SLEEP_TALK:
|
|
||||||
case MoveId.ENCORE:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.moveId = repeatableMove.move;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
|
// TODO: shouldn't this be `onAdd`?
|
||||||
super.onRemove(pokemon);
|
super.onRemove(pokemon);
|
||||||
|
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
@ -1175,7 +1140,13 @@ export class EncoreTag extends MoveRestrictionBattlerTag {
|
|||||||
const lastMove = pokemon.getLastXMoves(1)[0];
|
const lastMove = pokemon.getLastXMoves(1)[0];
|
||||||
globalScene.phaseManager.tryReplacePhase(
|
globalScene.phaseManager.tryReplacePhase(
|
||||||
m => m.is("MovePhase") && m.pokemon === pokemon,
|
m => m.is("MovePhase") && m.pokemon === pokemon,
|
||||||
globalScene.phaseManager.create("MovePhase", pokemon, lastMove.targets ?? [], movesetMove),
|
globalScene.phaseManager.create(
|
||||||
|
"MovePhase",
|
||||||
|
pokemon,
|
||||||
|
lastMove.targets ?? [],
|
||||||
|
movesetMove,
|
||||||
|
MoveUseMode.NORMAL,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1438,7 +1409,7 @@ export abstract class DamagingTrapTag extends TrappedTag {
|
|||||||
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim);
|
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim);
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
||||||
@ -1477,16 +1448,6 @@ export class WrapTag extends DamagingTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class VortexTrapTag extends DamagingTrapTag {
|
export abstract class VortexTrapTag extends DamagingTrapTag {
|
||||||
constructor(
|
|
||||||
tagType: BattlerTagType,
|
|
||||||
commonAnim: CommonAnim,
|
|
||||||
turnCount: number,
|
|
||||||
sourceMove: MoveId,
|
|
||||||
sourceId: number,
|
|
||||||
) {
|
|
||||||
super(tagType, commonAnim, turnCount, sourceMove, sourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTrapMessage(pokemon: Pokemon): string {
|
getTrapMessage(pokemon: Pokemon): string {
|
||||||
return i18next.t("battlerTags:vortexOnTrap", {
|
return i18next.t("battlerTags:vortexOnTrap", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
@ -1681,7 +1642,7 @@ export class ContactDamageProtectedTag extends ContactProtectedTag {
|
|||||||
*/
|
*/
|
||||||
override onContact(attacker: Pokemon, user: Pokemon): void {
|
override onContact(attacker: Pokemon, user: Pokemon): void {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
|
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
|
||||||
result: HitResult.INDIRECT,
|
result: HitResult.INDIRECT,
|
||||||
@ -1911,24 +1872,29 @@ export class TruantTag extends AbilityBattlerTag {
|
|||||||
|
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
if (!pokemon.hasAbility(AbilityId.TRUANT)) {
|
if (!pokemon.hasAbility(AbilityId.TRUANT)) {
|
||||||
|
// remove tag if mon lacks ability
|
||||||
return super.lapse(pokemon, lapseType);
|
return super.lapse(pokemon, lapseType);
|
||||||
}
|
}
|
||||||
const passive = pokemon.getAbility().id !== AbilityId.TRUANT;
|
|
||||||
|
|
||||||
const lastMove = pokemon.getLastXMoves().find(() => true);
|
const lastMove = pokemon.getLastXMoves()[0];
|
||||||
|
|
||||||
if (lastMove && lastMove.move !== MoveId.NONE) {
|
if (!lastMove) {
|
||||||
(globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel();
|
// Don't interrupt move if last move was `Moves.NONE` OR no prior move was found
|
||||||
// TODO: Ability displays should be handled by the ability
|
return true;
|
||||||
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
|
|
||||||
globalScene.phaseManager.queueMessage(
|
|
||||||
i18next.t("battlerTags:truantLapse", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Interrupt move usage in favor of slacking off
|
||||||
|
const passive = pokemon.getAbility().id !== AbilityId.TRUANT;
|
||||||
|
(globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel();
|
||||||
|
// TODO: Ability displays should be handled by the ability
|
||||||
|
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("battlerTags:truantLapse", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2277,7 +2243,7 @@ export class SaltCuredTag extends BattlerTag {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER);
|
const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER);
|
||||||
@ -2331,7 +2297,7 @@ export class CursedTag extends BattlerTag {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
||||||
@ -2666,7 +2632,7 @@ export class GulpMissileTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", attacker, cancelled);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT });
|
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT });
|
||||||
@ -3056,8 +3022,8 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
|
|||||||
|
|
||||||
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
|
applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
|
||||||
applyAbAttrs(ConditionalUserFieldProtectStatAbAttr, pokemon, cancelled, false, pokemon);
|
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", pokemon, cancelled, false, pokemon);
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
if (pokemon.mysteryEncounterBattleEffects) {
|
if (pokemon.mysteryEncounterBattleEffects) {
|
||||||
pokemon.mysteryEncounterBattleEffects(pokemon);
|
pokemon.mysteryEncounterBattleEffects(pokemon);
|
||||||
|
@ -3,7 +3,7 @@ import type Pokemon from "../field/pokemon";
|
|||||||
import { HitResult } from "#enums/hit-result";
|
import { HitResult } from "#enums/hit-result";
|
||||||
import { getStatusEffectHealText } from "./status-effect";
|
import { getStatusEffectHealText } from "./status-effect";
|
||||||
import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common";
|
import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common";
|
||||||
import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./abilities/ability";
|
import { applyAbAttrs } from "./abilities/apply-ab-attrs";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { BerryType } from "#enums/berry-type";
|
import { BerryType } from "#enums/berry-type";
|
||||||
@ -38,25 +38,25 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
|
|||||||
const threshold = new NumberHolder(0.25);
|
const threshold = new NumberHolder(0.25);
|
||||||
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
|
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
|
||||||
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
||||||
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
|
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
|
||||||
return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6;
|
return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6;
|
||||||
};
|
};
|
||||||
case BerryType.LANSAT:
|
case BerryType.LANSAT:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
const threshold = new NumberHolder(0.25);
|
const threshold = new NumberHolder(0.25);
|
||||||
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
|
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
|
||||||
return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST);
|
return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST);
|
||||||
};
|
};
|
||||||
case BerryType.STARF:
|
case BerryType.STARF:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
const threshold = new NumberHolder(0.25);
|
const threshold = new NumberHolder(0.25);
|
||||||
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
|
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
|
||||||
return pokemon.getHpRatio() < 0.25;
|
return pokemon.getHpRatio() < 0.25;
|
||||||
};
|
};
|
||||||
case BerryType.LEPPA:
|
case BerryType.LEPPA:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
const threshold = new NumberHolder(0.25);
|
const threshold = new NumberHolder(0.25);
|
||||||
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
|
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
|
||||||
return !!pokemon.getMoveset().find(m => !m.getPpRatio());
|
return !!pokemon.getMoveset().find(m => !m.getPpRatio());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||||||
case BerryType.ENIGMA:
|
case BerryType.ENIGMA:
|
||||||
{
|
{
|
||||||
const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4));
|
const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4));
|
||||||
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, hpHealed);
|
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, hpHealed);
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"PokemonHealPhase",
|
"PokemonHealPhase",
|
||||||
consumer.getBattlerIndex(),
|
consumer.getBattlerIndex(),
|
||||||
@ -105,7 +105,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||||||
// Offset BerryType such that LIECHI --> Stat.ATK = 1, GANLON --> Stat.DEF = 2, etc etc.
|
// Offset BerryType such that LIECHI --> Stat.ATK = 1, GANLON --> Stat.DEF = 2, etc etc.
|
||||||
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
||||||
const statStages = new NumberHolder(1);
|
const statStages = new NumberHolder(1);
|
||||||
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, statStages);
|
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, statStages);
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"StatStageChangePhase",
|
"StatStageChangePhase",
|
||||||
consumer.getBattlerIndex(),
|
consumer.getBattlerIndex(),
|
||||||
@ -126,7 +126,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||||||
{
|
{
|
||||||
const randStat = randSeedInt(Stat.SPD, Stat.ATK);
|
const randStat = randSeedInt(Stat.SPD, Stat.ATK);
|
||||||
const stages = new NumberHolder(2);
|
const stages = new NumberHolder(2);
|
||||||
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, stages);
|
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, stages);
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"StatStageChangePhase",
|
"StatStageChangePhase",
|
||||||
consumer.getBattlerIndex(),
|
consumer.getBattlerIndex(),
|
||||||
|
@ -21,7 +21,7 @@ import { TrainerType } from "#enums/trainer-type";
|
|||||||
import { Nature } from "#enums/nature";
|
import { Nature } from "#enums/nature";
|
||||||
import type { MoveId } from "#enums/move-id";
|
import type { MoveId } from "#enums/move-id";
|
||||||
import { TypeColor, TypeShadow } from "#enums/color";
|
import { TypeColor, TypeShadow } from "#enums/color";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { pokemonFormChanges } from "./pokemon-forms";
|
import { pokemonFormChanges } from "./pokemon-forms";
|
||||||
import { pokemonEvolutions } from "./balance/pokemon-evolutions";
|
import { pokemonEvolutions } from "./balance/pokemon-evolutions";
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import type { Ability } from "./abilities/ability-class";
|
import type { ModifierTypes } from "#app/modifier/modifier-type";
|
||||||
|
import type { Ability } from "./abilities/ability";
|
||||||
import type Move from "./moves/move";
|
import type Move from "./moves/move";
|
||||||
|
|
||||||
export const allAbilities: Ability[] = [];
|
export const allAbilities: Ability[] = [];
|
||||||
export const allMoves: Move[] = [];
|
export const allMoves: Move[] = [];
|
||||||
|
|
||||||
|
// TODO: Figure out what this is used for and provide an appropriate tsdoc comment
|
||||||
|
export const modifierTypes = {} as ModifierTypes;
|
||||||
|
@ -1723,49 +1723,6 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const doubleBattleDialogue = {
|
|
||||||
blue_red_double: {
|
|
||||||
encounter: ["doubleBattleDialogue:blue_red_double.encounter.1"],
|
|
||||||
victory: ["doubleBattleDialogue:blue_red_double.victory.1"],
|
|
||||||
},
|
|
||||||
red_blue_double: {
|
|
||||||
encounter: ["doubleBattleDialogue:red_blue_double.encounter.1"],
|
|
||||||
victory: ["doubleBattleDialogue:red_blue_double.victory.1"],
|
|
||||||
},
|
|
||||||
tate_liza_double: {
|
|
||||||
encounter: ["doubleBattleDialogue:tate_liza_double.encounter.1"],
|
|
||||||
victory: ["doubleBattleDialogue:tate_liza_double.victory.1"],
|
|
||||||
},
|
|
||||||
liza_tate_double: {
|
|
||||||
encounter: ["doubleBattleDialogue:liza_tate_double.encounter.1"],
|
|
||||||
victory: ["doubleBattleDialogue:liza_tate_double.victory.1"],
|
|
||||||
},
|
|
||||||
wallace_steven_double: {
|
|
||||||
encounter: ["doubleBattleDialogue:wallace_steven_double.encounter.1"],
|
|
||||||
victory: ["doubleBattleDialogue:wallace_steven_double.victory.1"],
|
|
||||||
},
|
|
||||||
steven_wallace_double: {
|
|
||||||
encounter: ["doubleBattleDialogue:steven_wallace_double.encounter.1"],
|
|
||||||
victory: ["doubleBattleDialogue:steven_wallace_double.victory.1"],
|
|
||||||
},
|
|
||||||
alder_iris_double: {
|
|
||||||
encounter: ["doubleBattleDialogue:alder_iris_double.encounter.1"],
|
|
||||||
victory: ["doubleBattleDialogue:alder_iris_double.victory.1"],
|
|
||||||
},
|
|
||||||
iris_alder_double: {
|
|
||||||
encounter: ["doubleBattleDialogue:iris_alder_double.encounter.1"],
|
|
||||||
victory: ["doubleBattleDialogue:iris_alder_double.victory.1"],
|
|
||||||
},
|
|
||||||
marnie_piers_double: {
|
|
||||||
encounter: ["doubleBattleDialogue:marnie_piers_double.encounter.1"],
|
|
||||||
victory: ["doubleBattleDialogue:marnie_piers_double.victory.1"],
|
|
||||||
},
|
|
||||||
piers_marnie_double: {
|
|
||||||
encounter: ["doubleBattleDialogue:piers_marnie_double.encounter.1"],
|
|
||||||
victory: ["doubleBattleDialogue:piers_marnie_double.victory.1"],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const battleSpecDialogue = {
|
export const battleSpecDialogue = {
|
||||||
[BattleSpec.FINAL_BOSS]: {
|
[BattleSpec.FINAL_BOSS]: {
|
||||||
encounter: "battleSpecDialogue:encounter",
|
encounter: "battleSpecDialogue:encounter",
|
||||||
|
44
src/data/double-battle-dialogue.ts
Normal file
44
src/data/double-battle-dialogue.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// TODO: Move this back into `dialogue.ts` after finding a suitable way to remove the circular dependencies
|
||||||
|
// that caused this to be moved out in the first place
|
||||||
|
export const doubleBattleDialogue = {
|
||||||
|
blue_red_double: {
|
||||||
|
encounter: ["doubleBattleDialogue:blue_red_double.encounter.1"],
|
||||||
|
victory: ["doubleBattleDialogue:blue_red_double.victory.1"],
|
||||||
|
},
|
||||||
|
red_blue_double: {
|
||||||
|
encounter: ["doubleBattleDialogue:red_blue_double.encounter.1"],
|
||||||
|
victory: ["doubleBattleDialogue:red_blue_double.victory.1"],
|
||||||
|
},
|
||||||
|
tate_liza_double: {
|
||||||
|
encounter: ["doubleBattleDialogue:tate_liza_double.encounter.1"],
|
||||||
|
victory: ["doubleBattleDialogue:tate_liza_double.victory.1"],
|
||||||
|
},
|
||||||
|
liza_tate_double: {
|
||||||
|
encounter: ["doubleBattleDialogue:liza_tate_double.encounter.1"],
|
||||||
|
victory: ["doubleBattleDialogue:liza_tate_double.victory.1"],
|
||||||
|
},
|
||||||
|
wallace_steven_double: {
|
||||||
|
encounter: ["doubleBattleDialogue:wallace_steven_double.encounter.1"],
|
||||||
|
victory: ["doubleBattleDialogue:wallace_steven_double.victory.1"],
|
||||||
|
},
|
||||||
|
steven_wallace_double: {
|
||||||
|
encounter: ["doubleBattleDialogue:steven_wallace_double.encounter.1"],
|
||||||
|
victory: ["doubleBattleDialogue:steven_wallace_double.victory.1"],
|
||||||
|
},
|
||||||
|
alder_iris_double: {
|
||||||
|
encounter: ["doubleBattleDialogue:alder_iris_double.encounter.1"],
|
||||||
|
victory: ["doubleBattleDialogue:alder_iris_double.victory.1"],
|
||||||
|
},
|
||||||
|
iris_alder_double: {
|
||||||
|
encounter: ["doubleBattleDialogue:iris_alder_double.encounter.1"],
|
||||||
|
victory: ["doubleBattleDialogue:iris_alder_double.victory.1"],
|
||||||
|
},
|
||||||
|
marnie_piers_double: {
|
||||||
|
encounter: ["doubleBattleDialogue:marnie_piers_double.encounter.1"],
|
||||||
|
victory: ["doubleBattleDialogue:marnie_piers_double.victory.1"],
|
||||||
|
},
|
||||||
|
piers_marnie_double: {
|
||||||
|
encounter: ["doubleBattleDialogue:piers_marnie_double.encounter.1"],
|
||||||
|
victory: ["doubleBattleDialogue:piers_marnie_double.victory.1"],
|
||||||
|
},
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
|
|
||||||
/** Set of moves that cannot be called by {@linkcode MoveId.METRONOME Metronome} */
|
/** Set of moves that cannot be called by {@linkcode MoveId.METRONOME | Metronome}. */
|
||||||
export const invalidMetronomeMoves: ReadonlySet<MoveId> = new Set([
|
export const invalidMetronomeMoves: ReadonlySet<MoveId> = new Set([
|
||||||
MoveId.AFTER_YOU,
|
MoveId.AFTER_YOU,
|
||||||
MoveId.ASSIST,
|
MoveId.ASSIST,
|
||||||
@ -255,3 +255,28 @@ export const noAbilityTypeOverrideMoves: ReadonlySet<MoveId> = new Set([
|
|||||||
MoveId.TECHNO_BLAST,
|
MoveId.TECHNO_BLAST,
|
||||||
MoveId.HIDDEN_POWER,
|
MoveId.HIDDEN_POWER,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
/** Set of all moves that cannot be copied by {@linkcode Moves.SKETCH}. */
|
||||||
|
export const invalidSketchMoves: ReadonlySet<MoveId> = new Set([
|
||||||
|
MoveId.NONE,
|
||||||
|
MoveId.CHATTER,
|
||||||
|
MoveId.MIRROR_MOVE,
|
||||||
|
MoveId.SLEEP_TALK,
|
||||||
|
MoveId.STRUGGLE,
|
||||||
|
MoveId.SKETCH,
|
||||||
|
MoveId.REVIVAL_BLESSING,
|
||||||
|
MoveId.TERA_STARSTORM,
|
||||||
|
MoveId.BREAKNECK_BLITZ__PHYSICAL,
|
||||||
|
MoveId.BREAKNECK_BLITZ__SPECIAL,
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** Set of all moves that cannot be locked into by {@linkcode Moves.ENCORE}. */
|
||||||
|
export const invalidEncoreMoves: ReadonlySet<MoveId> = new Set([
|
||||||
|
MoveId.MIMIC,
|
||||||
|
MoveId.MIRROR_MOVE,
|
||||||
|
MoveId.TRANSFORM,
|
||||||
|
MoveId.STRUGGLE,
|
||||||
|
MoveId.SKETCH,
|
||||||
|
MoveId.SLEEP_TALK,
|
||||||
|
MoveId.ENCORE,
|
||||||
|
]);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -21,7 +21,6 @@ export class PokemonMove {
|
|||||||
public moveId: MoveId;
|
public moveId: MoveId;
|
||||||
public ppUsed: number;
|
public ppUsed: number;
|
||||||
public ppUp: number;
|
public ppUp: number;
|
||||||
public virtual: boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform).
|
* If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform).
|
||||||
@ -29,11 +28,10 @@ export class PokemonMove {
|
|||||||
*/
|
*/
|
||||||
public maxPpOverride?: number;
|
public maxPpOverride?: number;
|
||||||
|
|
||||||
constructor(moveId: MoveId, ppUsed = 0, ppUp = 0, virtual = false, maxPpOverride?: number) {
|
constructor(moveId: MoveId, ppUsed = 0, ppUp = 0, maxPpOverride?: number) {
|
||||||
this.moveId = moveId;
|
this.moveId = moveId;
|
||||||
this.ppUsed = ppUsed;
|
this.ppUsed = ppUsed;
|
||||||
this.ppUp = ppUp;
|
this.ppUp = ppUp;
|
||||||
this.virtual = virtual;
|
|
||||||
this.maxPpOverride = maxPpOverride;
|
this.maxPpOverride = maxPpOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +45,7 @@ export class PokemonMove {
|
|||||||
* @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`.
|
* @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`.
|
||||||
*/
|
*/
|
||||||
isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean {
|
isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean {
|
||||||
|
// TODO: Add Sky Drop's 1 turn stall
|
||||||
if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) {
|
if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -88,6 +87,6 @@ export class PokemonMove {
|
|||||||
* @returns A valid {@linkcode PokemonMove} object
|
* @returns A valid {@linkcode PokemonMove} object
|
||||||
*/
|
*/
|
||||||
static loadMove(source: PokemonMove | any): PokemonMove {
|
static loadMove(source: PokemonMove | any): PokemonMove {
|
||||||
return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.virtual, source.maxPpOverride);
|
return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.maxPpOverride);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,8 @@ import i18next from "i18next";
|
|||||||
import type { IEggOptions } from "#app/data/egg";
|
import type { IEggOptions } from "#app/data/egg";
|
||||||
import { EggSourceType } from "#enums/egg-source-types";
|
import { EggSourceType } from "#enums/egg-source-types";
|
||||||
import { EggTier } from "#enums/egg-type";
|
import { EggTier } from "#enums/egg-type";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
|
@ -10,7 +10,7 @@ import type Pokemon from "#app/field/pokemon";
|
|||||||
import { EnemyPokemon } from "#app/field/pokemon";
|
import { EnemyPokemon } from "#app/field/pokemon";
|
||||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||||
import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
@ -38,6 +38,7 @@ import type HeldModifierConfig from "#app/@types/held-modifier-config";
|
|||||||
import type { BerryType } from "#enums/berry-type";
|
import type { BerryType } from "#enums/berry-type";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
|
||||||
/** the i18n namespace for this encounter */
|
/** the i18n namespace for this encounter */
|
||||||
const namespace = "mysteryEncounters/absoluteAvarice";
|
const namespace = "mysteryEncounters/absoluteAvarice";
|
||||||
@ -307,7 +308,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
|||||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
targets: [BattlerIndex.ENEMY],
|
targets: [BattlerIndex.ENEMY],
|
||||||
move: new PokemonMove(MoveId.STUFF_CHEEKS),
|
move: new PokemonMove(MoveId.STUFF_CHEEKS),
|
||||||
ignorePp: true,
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
});
|
});
|
||||||
|
|
||||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
setEncounterExp,
|
setEncounterExp,
|
||||||
updatePlayerMoney,
|
updatePlayerMoney,
|
||||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
@ -12,7 +12,9 @@ import {
|
|||||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
|
import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
|
||||||
import { ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
import { regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
||||||
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
|
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||||
import { randSeedInt } from "#app/utils/common";
|
import { randSeedInt } from "#app/utils/common";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
|
@ -38,7 +38,7 @@ import {
|
|||||||
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||||
import { PokemonType } from "#enums/pokemon-type";
|
import { PokemonType } from "#enums/pokemon-type";
|
||||||
import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
|
import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
|
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||||
import {
|
import {
|
||||||
AttackTypeBoosterModifier,
|
AttackTypeBoosterModifier,
|
||||||
@ -50,7 +50,7 @@ import {
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import MoveInfoOverlay from "#app/ui/move-info-overlay";
|
import MoveInfoOverlay from "#app/ui/move-info-overlay";
|
||||||
import { allMoves } from "#app/data/data-lists";
|
import { allMoves } from "#app/data/data-lists";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||||
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ import {
|
|||||||
import { trainerConfigs } from "#app/data/trainers/trainer-config";
|
import { trainerConfigs } from "#app/data/trainers/trainer-config";
|
||||||
import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate";
|
import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate";
|
||||||
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
|
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type";
|
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
@ -38,7 +38,6 @@ import i18next from "i18next";
|
|||||||
import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
|
import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||||
import { Ability } from "#app/data/abilities/ability-class";
|
|
||||||
import { BerryModifier } from "#app/modifier/modifier";
|
import { BerryModifier } from "#app/modifier/modifier";
|
||||||
import { BerryType } from "#enums/berry-type";
|
import { BerryType } from "#enums/berry-type";
|
||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
@ -49,6 +48,8 @@ import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
|||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||||
import { EncounterAnim } from "#enums/encounter-anims";
|
import { EncounterAnim } from "#enums/encounter-anims";
|
||||||
import { Challenges } from "#enums/challenges";
|
import { Challenges } from "#enums/challenges";
|
||||||
|
import { MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
import { allAbilities, modifierTypes } from "#app/data/data-lists";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounters/clowningAround";
|
const namespace = "mysteryEncounters/clowningAround";
|
||||||
@ -139,7 +140,7 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
|||||||
|
|
||||||
// Generate random ability for Blacephalon from pool
|
// Generate random ability for Blacephalon from pool
|
||||||
const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)];
|
const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)];
|
||||||
encounter.setDialogueToken("ability", new Ability(ability, 3).name);
|
encounter.setDialogueToken("ability", allAbilities[ability].name);
|
||||||
encounter.misc = { ability };
|
encounter.misc = { ability };
|
||||||
|
|
||||||
// Decide the random types for Blacephalon. They should not be the same.
|
// Decide the random types for Blacephalon. They should not be the same.
|
||||||
@ -209,19 +210,19 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
|||||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
targets: [BattlerIndex.ENEMY_2],
|
targets: [BattlerIndex.ENEMY_2],
|
||||||
move: new PokemonMove(MoveId.ROLE_PLAY),
|
move: new PokemonMove(MoveId.ROLE_PLAY),
|
||||||
ignorePp: true,
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||||
targets: [BattlerIndex.PLAYER],
|
targets: [BattlerIndex.PLAYER],
|
||||||
move: new PokemonMove(MoveId.TAUNT),
|
move: new PokemonMove(MoveId.TAUNT),
|
||||||
ignorePp: true,
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||||
targets: [BattlerIndex.PLAYER_2],
|
targets: [BattlerIndex.PLAYER_2],
|
||||||
move: new PokemonMove(MoveId.TAUNT),
|
move: new PokemonMove(MoveId.TAUNT),
|
||||||
ignorePp: true,
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import type Pokemon from "#app/field/pokemon";
|
|||||||
import { EnemyPokemon } from "#app/field/pokemon";
|
import { EnemyPokemon } from "#app/field/pokemon";
|
||||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import PokemonData from "#app/system/pokemon-data";
|
import PokemonData from "#app/system/pokemon-data";
|
||||||
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
@ -40,6 +40,7 @@ import { PokeballType } from "#enums/pokeball";
|
|||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
|
||||||
/** the i18n namespace for this encounter */
|
/** the i18n namespace for this encounter */
|
||||||
const namespace = "mysteryEncounters/dancingLessons";
|
const namespace = "mysteryEncounters/dancingLessons";
|
||||||
@ -214,7 +215,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
|
|||||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
targets: [BattlerIndex.PLAYER],
|
targets: [BattlerIndex.PLAYER],
|
||||||
move: new PokemonMove(MoveId.REVELATION_DANCE),
|
move: new PokemonMove(MoveId.REVELATION_DANCE),
|
||||||
ignorePp: true,
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
});
|
});
|
||||||
|
|
||||||
await hideOricorioPokemon();
|
await hideOricorioPokemon();
|
||||||
|
@ -3,7 +3,7 @@ import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
|
|||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
|
@ -28,7 +28,7 @@ import {
|
|||||||
PreserveBerryModifier,
|
PreserveBerryModifier,
|
||||||
} from "#app/modifier/modifier";
|
} from "#app/modifier/modifier";
|
||||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import i18next from "#app/plugins/i18n";
|
import i18next from "#app/plugins/i18n";
|
||||||
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
import { randSeedItem } from "#app/utils/common";
|
import { randSeedItem } from "#app/utils/common";
|
||||||
|
@ -2,8 +2,8 @@ import {
|
|||||||
leaveEncounterWithoutBattle,
|
leaveEncounterWithoutBattle,
|
||||||
setEncounterRewards,
|
setEncounterRewards,
|
||||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import type { ModifierTypeFunc } from "#app/modifier/modifier-type";
|
import type { ModifierTypeFunc } from "#app/@types/modifier-types";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { randSeedInt } from "#app/utils/common";
|
import { randSeedInt } from "#app/utils/common";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import type { PokemonMove } from "#app/data/moves/pokemon-move";
|
import type { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
generateModifierType,
|
generateModifierType,
|
||||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
|
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
@ -45,8 +44,9 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
|||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { Ability } from "#app/data/abilities/ability-class";
|
|
||||||
import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||||
|
import { MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
import { allAbilities, modifierTypes } from "#app/data/data-lists";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounters/fieryFallout";
|
const namespace = "mysteryEncounters/fieryFallout";
|
||||||
@ -201,13 +201,13 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
|
|||||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
targets: [BattlerIndex.PLAYER],
|
targets: [BattlerIndex.PLAYER],
|
||||||
move: new PokemonMove(MoveId.FIRE_SPIN),
|
move: new PokemonMove(MoveId.FIRE_SPIN),
|
||||||
ignorePp: true,
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||||
targets: [BattlerIndex.PLAYER_2],
|
targets: [BattlerIndex.PLAYER_2],
|
||||||
move: new PokemonMove(MoveId.FIRE_SPIN),
|
move: new PokemonMove(MoveId.FIRE_SPIN),
|
||||||
ignorePp: true,
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
||||||
@ -246,7 +246,7 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
|
|||||||
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
|
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
|
||||||
// Burn applied
|
// Burn applied
|
||||||
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
|
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
|
||||||
encounter.setDialogueToken("abilityName", new Ability(AbilityId.HEATPROOF, 3).name);
|
encounter.setDialogueToken("abilityName", allAbilities[AbilityId.HEATPROOF].name);
|
||||||
queueEncounterMessage(`${namespace}:option.2.target_burned`);
|
queueEncounterMessage(`${namespace}:option.2.target_burned`);
|
||||||
|
|
||||||
// Also permanently change the burned Pokemon's ability to Heatproof
|
// Also permanently change the burned Pokemon's ability to Heatproof
|
||||||
|
@ -9,13 +9,10 @@ import {
|
|||||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import type { ModifierTypeOption } from "#app/modifier/modifier-type";
|
import type { ModifierTypeOption } from "#app/modifier/modifier-type";
|
||||||
import {
|
import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
||||||
getPlayerModifierTypeOptions,
|
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||||
ModifierPoolType,
|
|
||||||
regenerateModifierPoolThresholds,
|
|
||||||
} from "#app/modifier/modifier-type";
|
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
|
@ -26,7 +26,7 @@ import { PlayerGender } from "#enums/player-gender";
|
|||||||
import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball";
|
import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball";
|
||||||
import { addPokeballOpenParticles } from "#app/field/anims";
|
import { addPokeballOpenParticles } from "#app/field/anims";
|
||||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { Nature } from "#enums/nature";
|
import { Nature } from "#enums/nature";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||||
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
|
@ -4,14 +4,11 @@ import {
|
|||||||
setEncounterRewards,
|
setEncounterRewards,
|
||||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import { TrainerSlot } from "#enums/trainer-slot";
|
import { TrainerSlot } from "#enums/trainer-slot";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import { MusicPreference } from "#app/system/settings/settings";
|
import { MusicPreference } from "#app/system/settings/settings";
|
||||||
import type { ModifierTypeOption } from "#app/modifier/modifier-type";
|
import type { ModifierTypeOption } from "#app/modifier/modifier-type";
|
||||||
import {
|
import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
||||||
getPlayerModifierTypeOptions,
|
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||||
ModifierPoolType,
|
|
||||||
regenerateModifierPoolThresholds,
|
|
||||||
} from "#app/modifier/modifier-type";
|
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
|
@ -7,8 +7,8 @@ import { trainerConfigs } from "#app/data/trainers/trainer-config";
|
|||||||
import { trainerPartyTemplates } from "#app/data/trainers/TrainerPartyTemplate";
|
import { trainerPartyTemplates } from "#app/data/trainers/TrainerPartyTemplate";
|
||||||
import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate";
|
import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate";
|
||||||
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
|
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import { randSeedInt } from "#app/utils/common";
|
import { randSeedInt } from "#app/utils/common";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { randSeedInt } from "#app/utils/common";
|
import { randSeedInt } from "#app/utils/common";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
@ -31,6 +31,7 @@ import { BerryType } from "#enums/berry-type";
|
|||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||||
import { randSeedInt } from "#app/utils/common";
|
import { randSeedInt } from "#app/utils/common";
|
||||||
|
import { MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
|
||||||
/** i18n namespace for the encounter */
|
/** i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounters/slumberingSnorlax";
|
const namespace = "mysteryEncounters/slumberingSnorlax";
|
||||||
@ -137,7 +138,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil
|
|||||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
targets: [BattlerIndex.PLAYER],
|
targets: [BattlerIndex.PLAYER],
|
||||||
move: new PokemonMove(MoveId.SNORE),
|
move: new PokemonMove(MoveId.SNORE),
|
||||||
ignorePp: true,
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
});
|
});
|
||||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||||
},
|
},
|
||||||
|
@ -23,7 +23,8 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
|
|||||||
import { BiomeId } from "#enums/biome-id";
|
import { BiomeId } from "#enums/biome-id";
|
||||||
import { getBiomeKey } from "#app/field/arena";
|
import { getBiomeKey } from "#app/field/arena";
|
||||||
import { PokemonType } from "#enums/pokemon-type";
|
import { PokemonType } from "#enums/pokemon-type";
|
||||||
import { getPartyLuckValue, modifierTypes } from "#app/modifier/modifier-type";
|
import { getPartyLuckValue } from "#app/modifier/modifier-type";
|
||||||
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { TrainerSlot } from "#enums/trainer-slot";
|
import { TrainerSlot } from "#enums/trainer-slot";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
|
@ -26,7 +26,7 @@ import { EggSourceType } from "#enums/egg-source-types";
|
|||||||
import { EggTier } from "#enums/egg-type";
|
import { EggTier } from "#enums/egg-type";
|
||||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { PokemonType } from "#enums/pokemon-type";
|
import { PokemonType } from "#enums/pokemon-type";
|
||||||
import { getPokeballTintColor } from "#app/data/pokeball";
|
import { getPokeballTintColor } from "#app/data/pokeball";
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
generateModifierType,
|
generateModifierType,
|
||||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
@ -28,6 +28,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
|||||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||||
|
import { MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounters/theStrongStuff";
|
const namespace = "mysteryEncounters/theStrongStuff";
|
||||||
@ -214,13 +215,13 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
|||||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
targets: [BattlerIndex.PLAYER],
|
targets: [BattlerIndex.PLAYER],
|
||||||
move: new PokemonMove(MoveId.GASTRO_ACID),
|
move: new PokemonMove(MoveId.GASTRO_ACID),
|
||||||
ignorePp: true,
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
targets: [BattlerIndex.PLAYER],
|
targets: [BattlerIndex.PLAYER],
|
||||||
move: new PokemonMove(MoveId.STEALTH_ROCK),
|
move: new PokemonMove(MoveId.STEALTH_ROCK),
|
||||||
ignorePp: true,
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
transitionMysteryEncounterIntroVisuals,
|
transitionMysteryEncounterIntroVisuals,
|
||||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
@ -24,11 +24,11 @@ import { PokemonType } from "#enums/pokemon-type";
|
|||||||
import { BerryType } from "#enums/berry-type";
|
import { BerryType } from "#enums/berry-type";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
||||||
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability";
|
import { applyPostBattleInitAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
|
|
||||||
@ -221,7 +221,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
|
|||||||
|
|
||||||
// Each trainer battle is supposed to be a new fight, so reset all per-battle activation effects
|
// Each trainer battle is supposed to be a new fight, so reset all per-battle activation effects
|
||||||
pokemon.resetBattleAndWaveData();
|
pokemon.resetBattleAndWaveData();
|
||||||
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
|
applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
|
||||||
}
|
}
|
||||||
|
|
||||||
globalScene.phaseManager.unshiftNew("ShowTrainerPhase");
|
globalScene.phaseManager.unshiftNew("ShowTrainerPhase");
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { Ability } from "#app/data/abilities/ability-class";
|
import type { Ability } from "#app/data/abilities/ability";
|
||||||
import { allAbilities } from "#app/data/data-lists";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import {
|
import {
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
transitionMysteryEncounterIntroVisuals,
|
transitionMysteryEncounterIntroVisuals,
|
||||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
@ -21,13 +21,14 @@ import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#app
|
|||||||
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import i18next from "#app/plugins/i18n";
|
import i18next from "#app/plugins/i18n";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||||
import { randSeedInt } from "#app/utils/common";
|
import { randSeedInt } from "#app/utils/common";
|
||||||
|
import { MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
|
||||||
/** the i18n namespace for this encounter */
|
/** the i18n namespace for this encounter */
|
||||||
const namespace = "mysteryEncounters/trashToTreasure";
|
const namespace = "mysteryEncounters/trashToTreasure";
|
||||||
@ -207,13 +208,13 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
|
|||||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
targets: [BattlerIndex.PLAYER],
|
targets: [BattlerIndex.PLAYER],
|
||||||
move: new PokemonMove(MoveId.TOXIC),
|
move: new PokemonMove(MoveId.TOXIC),
|
||||||
ignorePp: true,
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
targets: [BattlerIndex.ENEMY],
|
targets: [BattlerIndex.ENEMY],
|
||||||
move: new PokemonMove(MoveId.STOCKPILE),
|
move: new PokemonMove(MoveId.STOCKPILE),
|
||||||
ignorePp: true,
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||||
|
@ -36,6 +36,7 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun
|
|||||||
import { BerryModifier } from "#app/modifier/modifier";
|
import { BerryModifier } from "#app/modifier/modifier";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||||
|
import { MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounters/uncommonBreed";
|
const namespace = "mysteryEncounters/uncommonBreed";
|
||||||
@ -180,7 +181,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
|||||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
targets: [target],
|
targets: [target],
|
||||||
move: pokemonMove,
|
move: pokemonMove,
|
||||||
ignorePp: true,
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from
|
|||||||
import { achvs } from "#app/system/achv";
|
import { achvs } from "#app/system/achv";
|
||||||
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import i18next from "#app/plugins/i18n";
|
import i18next from "#app/plugins/i18n";
|
||||||
import {
|
import {
|
||||||
doPokemonTransformationSequence,
|
doPokemonTransformationSequence,
|
||||||
@ -34,7 +34,7 @@ import {
|
|||||||
import { getLevelTotalExp } from "#app/data/exp";
|
import { getLevelTotalExp } from "#app/data/exp";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { Challenges } from "#enums/challenges";
|
import { Challenges } from "#enums/challenges";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
import { TrainerType } from "#enums/trainer-type";
|
import { TrainerType } from "#enums/trainer-type";
|
||||||
import PokemonData from "#app/system/pokemon-data";
|
import PokemonData from "#app/system/pokemon-data";
|
||||||
|
@ -11,7 +11,7 @@ import { WeatherType } from "#enums/weather-type";
|
|||||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
|
import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
|
||||||
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
|
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
|
||||||
import { isNullOrUndefined } from "#app/utils/common";
|
import { coerceArray, isNullOrUndefined } from "#app/utils/common";
|
||||||
import type { AbilityId } from "#enums/ability-id";
|
import type { AbilityId } from "#enums/ability-id";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
@ -272,7 +272,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
|
|||||||
|
|
||||||
constructor(timeOfDay: TimeOfDay | TimeOfDay[]) {
|
constructor(timeOfDay: TimeOfDay | TimeOfDay[]) {
|
||||||
super();
|
super();
|
||||||
this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [timeOfDay];
|
this.requiredTimeOfDay = coerceArray(timeOfDay);
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(): boolean {
|
override meetsRequirement(): boolean {
|
||||||
@ -294,7 +294,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
|
|||||||
|
|
||||||
constructor(weather: WeatherType | WeatherType[]) {
|
constructor(weather: WeatherType | WeatherType[]) {
|
||||||
super();
|
super();
|
||||||
this.requiredWeather = Array.isArray(weather) ? weather : [weather];
|
this.requiredWeather = coerceArray(weather);
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(): boolean {
|
override meetsRequirement(): boolean {
|
||||||
@ -360,7 +360,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
|
|||||||
constructor(heldItem: string | string[], minNumberOfItems = 1) {
|
constructor(heldItem: string | string[], minNumberOfItems = 1) {
|
||||||
super();
|
super();
|
||||||
this.minNumberOfItems = minNumberOfItems;
|
this.minNumberOfItems = minNumberOfItems;
|
||||||
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
|
this.requiredHeldItemModifiers = coerceArray(heldItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(): boolean {
|
override meetsRequirement(): boolean {
|
||||||
@ -426,7 +426,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
|
|||||||
super();
|
super();
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredSpecies = Array.isArray(species) ? species : [species];
|
this.requiredSpecies = coerceArray(species);
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(): boolean {
|
override meetsRequirement(): boolean {
|
||||||
@ -466,7 +466,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
|
|||||||
super();
|
super();
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredNature = Array.isArray(nature) ? nature : [nature];
|
this.requiredNature = coerceArray(nature);
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(): boolean {
|
override meetsRequirement(): boolean {
|
||||||
@ -504,7 +504,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
|
|||||||
this.excludeFainted = excludeFainted;
|
this.excludeFainted = excludeFainted;
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredType = Array.isArray(type) ? type : [type];
|
this.requiredType = coerceArray(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(): boolean {
|
override meetsRequirement(): boolean {
|
||||||
@ -558,7 +558,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
|
|||||||
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredMoves = Array.isArray(moves) ? moves : [moves];
|
this.requiredMoves = coerceArray(moves);
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(): boolean {
|
override meetsRequirement(): boolean {
|
||||||
@ -609,7 +609,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
|
|||||||
super();
|
super();
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [learnableMove];
|
this.requiredMoves = coerceArray(learnableMove);
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(): boolean {
|
override meetsRequirement(): boolean {
|
||||||
@ -665,7 +665,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
|
|||||||
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredAbilities = Array.isArray(abilities) ? abilities : [abilities];
|
this.requiredAbilities = coerceArray(abilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(): boolean {
|
override meetsRequirement(): boolean {
|
||||||
@ -710,7 +710,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
|
|||||||
super();
|
super();
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [statusEffect];
|
this.requiredStatusEffect = coerceArray(statusEffect);
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(): boolean {
|
override meetsRequirement(): boolean {
|
||||||
@ -785,7 +785,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
|
|||||||
super();
|
super();
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [formChangeItem];
|
this.requiredFormChangeItem = coerceArray(formChangeItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(): boolean {
|
override meetsRequirement(): boolean {
|
||||||
@ -843,7 +843,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
|
|||||||
super();
|
super();
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [evolutionItems];
|
this.requiredEvolutionItem = coerceArray(evolutionItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(): boolean {
|
override meetsRequirement(): boolean {
|
||||||
@ -908,7 +908,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
|
|||||||
super();
|
super();
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
|
this.requiredHeldItemModifiers = coerceArray(heldItem);
|
||||||
this.requireTransferable = requireTransferable;
|
this.requireTransferable = requireTransferable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -972,7 +972,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
|
|||||||
super();
|
super();
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes];
|
this.requiredHeldItemTypes = coerceArray(heldItemTypes);
|
||||||
this.requireTransferable = requireTransferable;
|
this.requireTransferable = requireTransferable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/data/mystery-encounters/mystery-encounters";
|
import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/constants";
|
||||||
import { isNullOrUndefined } from "#app/utils/common";
|
import { isNullOrUndefined } from "#app/utils/common";
|
||||||
import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encoun
|
|||||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import type { PokemonMove } from "../moves/pokemon-move";
|
import type { PokemonMove } from "../moves/pokemon-move";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils/common";
|
import { capitalizeFirstLetter, coerceArray, isNullOrUndefined } from "#app/utils/common";
|
||||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
|
import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
|
||||||
import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro";
|
import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro";
|
||||||
@ -29,14 +29,14 @@ import type { GameModes } from "#enums/game-modes";
|
|||||||
import type { EncounterAnim } from "#enums/encounter-anims";
|
import type { EncounterAnim } from "#enums/encounter-anims";
|
||||||
import type { Challenges } from "#enums/challenges";
|
import type { Challenges } from "#enums/challenges";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import type { MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
|
||||||
export interface EncounterStartOfBattleEffect {
|
export interface EncounterStartOfBattleEffect {
|
||||||
sourcePokemon?: Pokemon;
|
sourcePokemon?: Pokemon;
|
||||||
sourceBattlerIndex?: BattlerIndex;
|
sourceBattlerIndex?: BattlerIndex;
|
||||||
targets: BattlerIndex[];
|
targets: BattlerIndex[];
|
||||||
move: PokemonMove;
|
move: PokemonMove;
|
||||||
ignorePp: boolean;
|
useMode: MoveUseMode; // TODO: This should always be ignore PP...
|
||||||
followUp?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_MAX_ALLOWED_ENCOUNTERS = 2;
|
const DEFAULT_MAX_ALLOWED_ENCOUNTERS = 2;
|
||||||
@ -254,7 +254,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||||||
*/
|
*/
|
||||||
selectedOption?: MysteryEncounterOption;
|
selectedOption?: MysteryEncounterOption;
|
||||||
/**
|
/**
|
||||||
* Will be set by option select handlers automatically, and can be used to refer to which option was chosen by later phases
|
* Array containing data pertaining to free moves used at the start of a battle mystery envounter.
|
||||||
*/
|
*/
|
||||||
startOfBattleEffects: EncounterStartOfBattleEffect[] = [];
|
startOfBattleEffects: EncounterStartOfBattleEffect[] = [];
|
||||||
/**
|
/**
|
||||||
@ -717,7 +717,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||||||
withAnimations(
|
withAnimations(
|
||||||
...encounterAnimations: EncounterAnim[]
|
...encounterAnimations: EncounterAnim[]
|
||||||
): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
|
): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
|
||||||
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations];
|
const animations = coerceArray(encounterAnimations);
|
||||||
return Object.assign(this, { encounterAnimations: animations });
|
return Object.assign(this, { encounterAnimations: animations });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -729,7 +729,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||||||
withDisallowedGameModes(
|
withDisallowedGameModes(
|
||||||
...disallowedGameModes: GameModes[]
|
...disallowedGameModes: GameModes[]
|
||||||
): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
|
): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
|
||||||
const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes];
|
const gameModes = coerceArray(disallowedGameModes);
|
||||||
return Object.assign(this, { disallowedGameModes: gameModes });
|
return Object.assign(this, { disallowedGameModes: gameModes });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -741,7 +741,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||||||
withDisallowedChallenges(
|
withDisallowedChallenges(
|
||||||
...disallowedChallenges: Challenges[]
|
...disallowedChallenges: Challenges[]
|
||||||
): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
|
): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
|
||||||
const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [disallowedChallenges];
|
const challenges = coerceArray(disallowedChallenges);
|
||||||
return Object.assign(this, { disallowedChallenges: challenges });
|
return Object.assign(this, { disallowedChallenges: challenges });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,42 +34,6 @@ import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encount
|
|||||||
import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter";
|
import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter";
|
||||||
import { getBiomeName } from "#app/data/balance/biomes";
|
import { getBiomeName } from "#app/data/balance/biomes";
|
||||||
|
|
||||||
/**
|
|
||||||
* Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT
|
|
||||||
*/
|
|
||||||
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 3;
|
|
||||||
/**
|
|
||||||
* The divisor for determining ME spawns, defines the "maximum" weight required for a spawn
|
|
||||||
* If spawn_weight === MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, 100% chance to spawn a ME
|
|
||||||
*/
|
|
||||||
export const MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT = 256;
|
|
||||||
/**
|
|
||||||
* When an ME spawn roll fails, WEIGHT_INCREMENT_ON_SPAWN_MISS is added to future rolls for ME spawn checks.
|
|
||||||
* These values are cleared whenever the next ME spawns, and spawn weight returns to BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT
|
|
||||||
*/
|
|
||||||
export const WEIGHT_INCREMENT_ON_SPAWN_MISS = 3;
|
|
||||||
/**
|
|
||||||
* Specifies the target average for total ME spawns in a single Classic run.
|
|
||||||
* Used by anti-variance mechanic to check whether a run is above or below the target on a given wave.
|
|
||||||
*/
|
|
||||||
export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 12;
|
|
||||||
/**
|
|
||||||
* Will increase/decrease the chance of spawning a ME based on the current run's total MEs encountered vs AVERAGE_ENCOUNTERS_PER_RUN_TARGET
|
|
||||||
* Example:
|
|
||||||
* AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 17 (expects avg 1 ME every 10 floors)
|
|
||||||
* ANTI_VARIANCE_WEIGHT_MODIFIER = 15
|
|
||||||
*
|
|
||||||
* On wave 20, if 1 ME has been encountered, the difference from expected average is 0 MEs.
|
|
||||||
* So anti-variance adds 0/256 to the spawn weight check for ME spawn.
|
|
||||||
*
|
|
||||||
* On wave 20, if 0 MEs have been encountered, the difference from expected average is 1 ME.
|
|
||||||
* So anti-variance adds 15/256 to the spawn weight check for ME spawn.
|
|
||||||
*
|
|
||||||
* On wave 20, if 2 MEs have been encountered, the difference from expected average is -1 ME.
|
|
||||||
* So anti-variance adds -15/256 to the spawn weight check for ME spawn.
|
|
||||||
*/
|
|
||||||
export const ANTI_VARIANCE_WEIGHT_MODIFIER = 15;
|
|
||||||
|
|
||||||
export const EXTREME_ENCOUNTER_BIOMES = [
|
export const EXTREME_ENCOUNTER_BIOMES = [
|
||||||
BiomeId.SEA,
|
BiomeId.SEA,
|
||||||
BiomeId.SEABED,
|
BiomeId.SEABED,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { MoveId } from "#enums/move-id";
|
import type { MoveId } from "#enums/move-id";
|
||||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||||
import { isNullOrUndefined } from "#app/utils/common";
|
import { coerceArray, isNullOrUndefined } from "#app/utils/common";
|
||||||
import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
|
|||||||
|
|
||||||
constructor(requiredMoves: MoveId | MoveId[], options: CanLearnMoveRequirementOptions = {}) {
|
constructor(requiredMoves: MoveId | MoveId[], options: CanLearnMoveRequirementOptions = {}) {
|
||||||
super();
|
super();
|
||||||
this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [requiredMoves];
|
this.requiredMoves = coerceArray(requiredMoves);
|
||||||
|
|
||||||
this.excludeLevelMoves = options.excludeLevelMoves ?? false;
|
this.excludeLevelMoves = options.excludeLevelMoves ?? false;
|
||||||
this.excludeTmMoves = options.excludeTmMoves ?? false;
|
this.excludeTmMoves = options.excludeTmMoves ?? false;
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
import type Battle from "#app/battle";
|
import type Battle from "#app/battle";
|
||||||
import { BattlerIndex } from "#enums/battler-index";
|
|
||||||
import { BattleType } from "#enums/battle-type";
|
import { BattleType } from "#enums/battle-type";
|
||||||
import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes";
|
import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes";
|
||||||
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
|
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
import {
|
import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/constants";
|
||||||
AVERAGE_ENCOUNTERS_PER_RUN_TARGET,
|
|
||||||
WEIGHT_INCREMENT_ON_SPAWN_MISS,
|
|
||||||
} from "#app/data/mystery-encounters/mystery-encounters";
|
|
||||||
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import type { AiType } from "#enums/ai-type";
|
import type { AiType } from "#enums/ai-type";
|
||||||
@ -17,18 +13,18 @@ import { FieldPosition } from "#enums/field-position";
|
|||||||
import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type";
|
import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type";
|
||||||
import {
|
import {
|
||||||
getPartyLuckValue,
|
getPartyLuckValue,
|
||||||
ModifierPoolType,
|
|
||||||
ModifierTypeGenerator,
|
ModifierTypeGenerator,
|
||||||
ModifierTypeOption,
|
ModifierTypeOption,
|
||||||
modifierTypes,
|
|
||||||
regenerateModifierPoolThresholds,
|
regenerateModifierPoolThresholds,
|
||||||
} from "#app/modifier/modifier-type";
|
} from "#app/modifier/modifier-type";
|
||||||
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
|
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||||
import type PokemonData from "#app/system/pokemon-data";
|
import type PokemonData from "#app/system/pokemon-data";
|
||||||
import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
|
import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
|
||||||
import { PartyUiMode } from "#app/ui/party-ui-handler";
|
import { PartyUiMode } from "#app/ui/party-ui-handler";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
import { UiMode } from "#enums/ui-mode";
|
||||||
import { isNullOrUndefined, randSeedInt, randomString, randSeedItem } from "#app/utils/common";
|
import { isNullOrUndefined, randSeedInt, randomString, randSeedItem, coerceArray } from "#app/utils/common";
|
||||||
import type { BattlerTagType } from "#enums/battler-tag-type";
|
import type { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { BiomeId } from "#enums/biome-id";
|
import { BiomeId } from "#enums/biome-id";
|
||||||
import type { TrainerType } from "#enums/trainer-type";
|
import type { TrainerType } from "#enums/trainer-type";
|
||||||
@ -452,7 +448,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
|||||||
* @param moves
|
* @param moves
|
||||||
*/
|
*/
|
||||||
export function loadCustomMovesForEncounter(moves: MoveId | MoveId[]) {
|
export function loadCustomMovesForEncounter(moves: MoveId | MoveId[]) {
|
||||||
moves = Array.isArray(moves) ? moves : [moves];
|
moves = coerceArray(moves);
|
||||||
return Promise.all(moves.map(move => initMoveAnim(move))).then(() => loadMoveAnimAssets(moves));
|
return Promise.all(moves.map(move => initMoveAnim(move))).then(() => loadMoveAnimAssets(moves));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -795,7 +791,7 @@ export function setEncounterRewards(
|
|||||||
* @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue
|
* @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue
|
||||||
*/
|
*/
|
||||||
export function setEncounterExp(participantId: number | number[], baseExpValue: number, useWaveIndex = true) {
|
export function setEncounterExp(participantId: number | number[], baseExpValue: number, useWaveIndex = true) {
|
||||||
const participantIds = Array.isArray(participantId) ? participantId : [participantId];
|
const participantIds = coerceArray(participantId);
|
||||||
|
|
||||||
globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => {
|
globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => {
|
||||||
globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds));
|
globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds));
|
||||||
@ -977,33 +973,8 @@ export function handleMysteryEncounterBattleStartEffects() {
|
|||||||
) {
|
) {
|
||||||
const effects = encounter.startOfBattleEffects;
|
const effects = encounter.startOfBattleEffects;
|
||||||
effects.forEach(effect => {
|
effects.forEach(effect => {
|
||||||
let source: EnemyPokemon | Pokemon;
|
const source = effect.sourcePokemon ?? globalScene.getField()[effect.sourceBattlerIndex ?? 0];
|
||||||
if (effect.sourcePokemon) {
|
globalScene.phaseManager.pushNew("MovePhase", source, effect.targets, effect.move, effect.useMode);
|
||||||
source = effect.sourcePokemon;
|
|
||||||
} else if (!isNullOrUndefined(effect.sourceBattlerIndex)) {
|
|
||||||
if (effect.sourceBattlerIndex === BattlerIndex.ATTACKER) {
|
|
||||||
source = globalScene.getEnemyField()[0];
|
|
||||||
} else if (effect.sourceBattlerIndex === BattlerIndex.ENEMY) {
|
|
||||||
source = globalScene.getEnemyField()[0];
|
|
||||||
} else if (effect.sourceBattlerIndex === BattlerIndex.ENEMY_2) {
|
|
||||||
source = globalScene.getEnemyField()[1];
|
|
||||||
} else if (effect.sourceBattlerIndex === BattlerIndex.PLAYER) {
|
|
||||||
source = globalScene.getPlayerField()[0];
|
|
||||||
} else if (effect.sourceBattlerIndex === BattlerIndex.PLAYER_2) {
|
|
||||||
source = globalScene.getPlayerField()[1];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
source = globalScene.getEnemyField()[0];
|
|
||||||
}
|
|
||||||
globalScene.phaseManager.pushNew(
|
|
||||||
"MovePhase",
|
|
||||||
// @ts-expect-error: source is guaranteed to be defined
|
|
||||||
source,
|
|
||||||
effect.targets,
|
|
||||||
effect.move,
|
|
||||||
effect.followUp,
|
|
||||||
effect.ignorePp,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pseudo turn end phase to reset flinch states, Endure, etc.
|
// Pseudo turn end phase to reset flinch states, Endure, etc.
|
||||||
|
@ -29,7 +29,7 @@ import {
|
|||||||
} from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
} from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { Gender } from "#app/data/gender";
|
import { Gender } from "#app/data/gender";
|
||||||
import type { PermanentStat } from "#enums/stat";
|
import type { PermanentStat } from "#enums/stat";
|
||||||
import { SummaryUiMode } from "#app/ui/summary-ui-handler";
|
import { SummaryUiMode } from "#app/ui/summary-ui-handler";
|
||||||
|
97
src/data/phase-priority-queue.ts
Normal file
97
src/data/phase-priority-queue.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import type { Phase } from "#app/phase";
|
||||||
|
import { ActivatePriorityQueuePhase } from "#app/phases/activate-priority-queue-phase";
|
||||||
|
import type { PostSummonPhase } from "#app/phases/post-summon-phase";
|
||||||
|
import { PostSummonActivateAbilityPhase } from "#app/phases/post-summon-activate-ability-phase";
|
||||||
|
import { Stat } from "#enums/stat";
|
||||||
|
import { BooleanHolder } from "#app/utils/common";
|
||||||
|
import { TrickRoomTag } from "#app/data/arena-tag";
|
||||||
|
import { DynamicPhaseType } from "#enums/dynamic-phase-type";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a list of {@linkcode Phase}s
|
||||||
|
*
|
||||||
|
* Dynamically updates ordering to always pop the highest "priority", based on implementation of {@linkcode reorder}
|
||||||
|
*/
|
||||||
|
export abstract class PhasePriorityQueue {
|
||||||
|
protected abstract queue: Phase[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts the elements in the queue
|
||||||
|
*/
|
||||||
|
public abstract reorder(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls {@linkcode reorder} and shifts the queue
|
||||||
|
* @returns The front element of the queue after sorting
|
||||||
|
*/
|
||||||
|
public pop(): Phase | undefined {
|
||||||
|
this.reorder();
|
||||||
|
return this.queue.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a phase to the queue
|
||||||
|
* @param phase The phase to add
|
||||||
|
*/
|
||||||
|
public push(phase: Phase): void {
|
||||||
|
this.queue.push(phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all phases from the queue
|
||||||
|
*/
|
||||||
|
public clear(): void {
|
||||||
|
this.queue.splice(0, this.queue.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Priority Queue for {@linkcode PostSummonPhase} and {@linkcode PostSummonActivateAbilityPhase}
|
||||||
|
*
|
||||||
|
* Orders phases first by ability priority, then by the {@linkcode Pokemon}'s effective speed
|
||||||
|
*/
|
||||||
|
export class PostSummonPhasePriorityQueue extends PhasePriorityQueue {
|
||||||
|
protected override queue: PostSummonPhase[] = [];
|
||||||
|
|
||||||
|
public override reorder(): void {
|
||||||
|
this.queue.sort((phaseA: PostSummonPhase, phaseB: PostSummonPhase) => {
|
||||||
|
if (phaseA.getPriority() === phaseB.getPriority()) {
|
||||||
|
return (
|
||||||
|
(phaseB.getPokemon().getEffectiveStat(Stat.SPD) - phaseA.getPokemon().getEffectiveStat(Stat.SPD)) *
|
||||||
|
(isTrickRoom() ? -1 : 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return phaseB.getPriority() - phaseA.getPriority();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override push(phase: PostSummonPhase): void {
|
||||||
|
super.push(phase);
|
||||||
|
this.queueAbilityPhase(phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queues all necessary {@linkcode PostSummonActivateAbilityPhase}s for each pushed {@linkcode PostSummonPhase}
|
||||||
|
* @param phase The {@linkcode PostSummonPhase} that was pushed onto the queue
|
||||||
|
*/
|
||||||
|
private queueAbilityPhase(phase: PostSummonPhase): void {
|
||||||
|
const phasePokemon = phase.getPokemon();
|
||||||
|
|
||||||
|
phasePokemon.getAbilityPriorities().forEach((priority, idx) => {
|
||||||
|
this.queue.push(new PostSummonActivateAbilityPhase(phasePokemon.getBattlerIndex(), priority, !!idx));
|
||||||
|
globalScene.phaseManager.appendToPhase(
|
||||||
|
new ActivatePriorityQueuePhase(DynamicPhaseType.POST_SUMMON),
|
||||||
|
"ActivatePriorityQueuePhase",
|
||||||
|
(p: ActivatePriorityQueuePhase) => p.getType() === DynamicPhaseType.POST_SUMMON,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTrickRoom(): boolean {
|
||||||
|
const speedReversed = new BooleanHolder(false);
|
||||||
|
globalScene.arena.applyTags(TrickRoomTag, false, speedReversed);
|
||||||
|
return speedReversed.value;
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier";
|
|
||||||
import { NumberHolder } from "#app/utils/common";
|
import { NumberHolder } from "#app/utils/common";
|
||||||
import { PokeballType } from "#enums/pokeball";
|
import { PokeballType } from "#enums/pokeball";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
@ -94,7 +93,7 @@ export function getCriticalCaptureChance(modifiedCatchRate: number): number {
|
|||||||
}
|
}
|
||||||
const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr);
|
const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr);
|
||||||
const catchingCharmMultiplier = new NumberHolder(1);
|
const catchingCharmMultiplier = new NumberHolder(1);
|
||||||
globalScene.findModifier(m => m instanceof CriticalCatchChanceBoosterModifier)?.apply(catchingCharmMultiplier);
|
globalScene.findModifier(m => m.is("CriticalCatchChanceBoosterModifier"))?.apply(catchingCharmMultiplier);
|
||||||
const dexMultiplier =
|
const dexMultiplier =
|
||||||
globalScene.gameMode.isDaily || dexCount > 800
|
globalScene.gameMode.isDaily || dexCount > 800
|
||||||
? 2.5
|
? 2.5
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import type { Constructor } from "#app/utils/common";
|
import { coerceArray, type Constructor } from "#app/utils/common";
|
||||||
import type { TimeOfDay } from "#enums/time-of-day";
|
import type { TimeOfDay } from "#enums/time-of-day";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import type { SpeciesFormChange } from "#app/data/pokemon-forms";
|
import type { SpeciesFormChange } from "#app/data/pokemon-forms";
|
||||||
@ -125,10 +125,7 @@ export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigg
|
|||||||
|
|
||||||
constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
|
constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
|
||||||
super();
|
super();
|
||||||
if (!Array.isArray(statusEffects)) {
|
this.statusEffects = coerceArray(statusEffects);
|
||||||
statusEffects = [statusEffects];
|
|
||||||
}
|
|
||||||
this.statusEffects = statusEffects;
|
|
||||||
this.invert = invert;
|
this.invert = invert;
|
||||||
// this.description = i18next.t("pokemonEvolutions:Forms.statusEffect");
|
// this.description = i18next.t("pokemonEvolutions:Forms.statusEffect");
|
||||||
}
|
}
|
||||||
|
@ -84,13 +84,14 @@ export const normalForm: SpeciesId[] = [
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given
|
* Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given
|
||||||
* @param species The species to fetch
|
* @param species - The {@linkcode SpeciesId} to fetch.
|
||||||
|
* If an array of `SpeciesId`s is passed (such as for named trainer spawn pools),
|
||||||
|
* one will be selected at random.
|
||||||
* @returns The associated {@linkcode PokemonSpecies} object
|
* @returns The associated {@linkcode PokemonSpecies} object
|
||||||
*/
|
*/
|
||||||
export function getPokemonSpecies(species: SpeciesId | SpeciesId[]): PokemonSpecies {
|
export function getPokemonSpecies(species: SpeciesId | SpeciesId[]): PokemonSpecies {
|
||||||
// If a special pool (named trainers) is used here it CAN happen that they have a array as species (which means choose one of those two). So we catch that with this code block
|
|
||||||
if (Array.isArray(species)) {
|
if (Array.isArray(species)) {
|
||||||
// Pick a random species from the list
|
// TODO: this RNG roll should not be handled by this function
|
||||||
species = species[Math.floor(Math.random() * species.length)];
|
species = species[Math.floor(Math.random() * species.length)];
|
||||||
}
|
}
|
||||||
if (species >= 2000) {
|
if (species >= 2000) {
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "../data-lists";
|
||||||
import { PokemonMove } from "../moves/pokemon-move";
|
import { PokemonMove } from "../moves/pokemon-move";
|
||||||
import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt, randSeedIntRange } from "#app/utils/common";
|
import {
|
||||||
|
toReadableString,
|
||||||
|
isNullOrUndefined,
|
||||||
|
randSeedItem,
|
||||||
|
randSeedInt,
|
||||||
|
coerceArray,
|
||||||
|
randSeedIntRange,
|
||||||
|
} from "#app/utils/common";
|
||||||
import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
|
import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
|
||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { tmSpecies } from "#app/data/balance/tms";
|
import { tmSpecies } from "#app/data/balance/tms";
|
||||||
import { doubleBattleDialogue } from "#app/data/dialogue";
|
import { doubleBattleDialogue } from "../double-battle-dialogue";
|
||||||
import { TrainerVariant } from "#enums/trainer-variant";
|
import { TrainerVariant } from "#enums/trainer-variant";
|
||||||
import { getIsInitialized, initI18n } from "#app/plugins/i18n";
|
import { getIsInitialized, initI18n } from "#app/plugins/i18n";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
@ -37,7 +44,7 @@ import { timedEventManager } from "#app/global-event-manager";
|
|||||||
// Type imports
|
// Type imports
|
||||||
import type { PokemonSpeciesFilter } from "#app/data/pokemon-species";
|
import type { PokemonSpeciesFilter } from "#app/data/pokemon-species";
|
||||||
import type PokemonSpecies from "#app/data/pokemon-species";
|
import type PokemonSpecies from "#app/data/pokemon-species";
|
||||||
import type { ModifierTypeFunc } from "#app/modifier/modifier-type";
|
import type { ModifierTypeFunc } from "#app/@types/modifier-types";
|
||||||
import type { EnemyPokemon } from "#app/field/pokemon";
|
import type { EnemyPokemon } from "#app/field/pokemon";
|
||||||
import type { EvilTeam } from "./evil-admin-trainer-pools";
|
import type { EvilTeam } from "./evil-admin-trainer-pools";
|
||||||
import type {
|
import type {
|
||||||
@ -554,10 +561,7 @@ export class TrainerConfig {
|
|||||||
this.speciesPools = evilAdminTrainerPools[poolName];
|
this.speciesPools = evilAdminTrainerPools[poolName];
|
||||||
|
|
||||||
signatureSpecies.forEach((speciesPool, s) => {
|
signatureSpecies.forEach((speciesPool, s) => {
|
||||||
if (!Array.isArray(speciesPool)) {
|
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
|
||||||
speciesPool = [speciesPool];
|
|
||||||
}
|
|
||||||
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
|
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
|
||||||
@ -620,10 +624,7 @@ export class TrainerConfig {
|
|||||||
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
|
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
|
||||||
}
|
}
|
||||||
signatureSpecies.forEach((speciesPool, s) => {
|
signatureSpecies.forEach((speciesPool, s) => {
|
||||||
if (!Array.isArray(speciesPool)) {
|
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
|
||||||
speciesPool = [speciesPool];
|
|
||||||
}
|
|
||||||
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
|
|
||||||
});
|
});
|
||||||
if (!isNullOrUndefined(specialtyType)) {
|
if (!isNullOrUndefined(specialtyType)) {
|
||||||
this.setSpeciesFilter(p => p.isOfType(specialtyType));
|
this.setSpeciesFilter(p => p.isOfType(specialtyType));
|
||||||
@ -668,12 +669,8 @@ export class TrainerConfig {
|
|||||||
|
|
||||||
// Set up party members with their corresponding species.
|
// Set up party members with their corresponding species.
|
||||||
signatureSpecies.forEach((speciesPool, s) => {
|
signatureSpecies.forEach((speciesPool, s) => {
|
||||||
// Ensure speciesPool is an array.
|
|
||||||
if (!Array.isArray(speciesPool)) {
|
|
||||||
speciesPool = [speciesPool];
|
|
||||||
}
|
|
||||||
// Set a function to get a random party member from the species pool.
|
// Set a function to get a random party member from the species pool.
|
||||||
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
|
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
|
||||||
});
|
});
|
||||||
|
|
||||||
// If specialty type is provided, set species filter and specialty type.
|
// If specialty type is provided, set species filter and specialty type.
|
||||||
@ -729,12 +726,8 @@ export class TrainerConfig {
|
|||||||
|
|
||||||
// Set up party members with their corresponding species.
|
// Set up party members with their corresponding species.
|
||||||
signatureSpecies.forEach((speciesPool, s) => {
|
signatureSpecies.forEach((speciesPool, s) => {
|
||||||
// Ensure speciesPool is an array.
|
|
||||||
if (!Array.isArray(speciesPool)) {
|
|
||||||
speciesPool = [speciesPool];
|
|
||||||
}
|
|
||||||
// Set a function to get a random party member from the species pool.
|
// Set a function to get a random party member from the species pool.
|
||||||
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
|
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set species filter and specialty type if provided, otherwise filter by base total.
|
// Set species filter and specialty type if provided, otherwise filter by base total.
|
||||||
|
@ -5,12 +5,12 @@ import type Pokemon from "../field/pokemon";
|
|||||||
import { PokemonType } from "#enums/pokemon-type";
|
import { PokemonType } from "#enums/pokemon-type";
|
||||||
import type Move from "./moves/move";
|
import type Move from "./moves/move";
|
||||||
import { randSeedInt } from "#app/utils/common";
|
import { randSeedInt } from "#app/utils/common";
|
||||||
import { SuppressWeatherEffectAbAttr } from "./abilities/ability";
|
|
||||||
import { TerrainType, getTerrainName } from "./terrain";
|
import { TerrainType, getTerrainName } from "./terrain";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type { Arena } from "#app/field/arena";
|
import type { Arena } from "#app/field/arena";
|
||||||
import { timedEventManager } from "#app/global-event-manager";
|
import { timedEventManager } from "#app/global-event-manager";
|
||||||
|
import type { SuppressWeatherEffectAbAttr } from "./abilities/ability";
|
||||||
|
|
||||||
export class Weather {
|
export class Weather {
|
||||||
public weatherType: WeatherType;
|
public weatherType: WeatherType;
|
||||||
@ -108,10 +108,10 @@ export class Weather {
|
|||||||
for (const pokemon of field) {
|
for (const pokemon of field) {
|
||||||
let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon
|
let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon
|
||||||
.getAbility()
|
.getAbility()
|
||||||
.getAttrs(SuppressWeatherEffectAbAttr)[0];
|
.getAttrs("SuppressWeatherEffectAbAttr")[0];
|
||||||
if (!suppressWeatherEffectAbAttr) {
|
if (!suppressWeatherEffectAbAttr) {
|
||||||
suppressWeatherEffectAbAttr = pokemon.hasPassive()
|
suppressWeatherEffectAbAttr = pokemon.hasPassive()
|
||||||
? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0]
|
? pokemon.getPassiveAbility().getAttrs("SuppressWeatherEffectAbAttr")[0]
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) {
|
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) {
|
||||||
|
@ -1,12 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Enum representing the possible ways a given BattlerTag can activate and/or tick down.
|
||||||
|
* Each tag can have multiple different behaviors attached to different lapse types.
|
||||||
|
*/
|
||||||
export enum BattlerTagLapseType {
|
export enum BattlerTagLapseType {
|
||||||
|
// TODO: This is unused...
|
||||||
FAINT,
|
FAINT,
|
||||||
|
/**
|
||||||
|
* Tag activate before the holder uses a non-virtual move, possibly interrupting its action.
|
||||||
|
* @see MoveUseMode for more information
|
||||||
|
*/
|
||||||
MOVE,
|
MOVE,
|
||||||
|
/** Tag activates before the holder uses **any** move, triggering effects or interrupting its action. */
|
||||||
PRE_MOVE,
|
PRE_MOVE,
|
||||||
|
/** Tag activates immediately after the holder's move finishes triggering (successful or not). */
|
||||||
AFTER_MOVE,
|
AFTER_MOVE,
|
||||||
|
/**
|
||||||
|
* Tag activates before move effects are applied.
|
||||||
|
* TODO: Stop using this as a catch-all "semi-invulnerability" tag
|
||||||
|
*/
|
||||||
MOVE_EFFECT,
|
MOVE_EFFECT,
|
||||||
|
/** Tag activates at the end of the turn. */
|
||||||
TURN_END,
|
TURN_END,
|
||||||
|
/**
|
||||||
|
* Tag activates after the holder is hit by an attack, but before damage is applied.
|
||||||
|
* Occurs even if the user's {@linkcode SubstituteTag | Substitute} is hit.
|
||||||
|
*/
|
||||||
HIT,
|
HIT,
|
||||||
/** Tag lapses AFTER_HIT, applying its effects even if the user faints */
|
/**
|
||||||
|
* Tag activates after the holder is directly hit by an attack.
|
||||||
|
* Does **not** occur on hits to the holder's {@linkcode SubstituteTag | Substitute},
|
||||||
|
* but still triggers on being KO'd.
|
||||||
|
*/
|
||||||
AFTER_HIT,
|
AFTER_HIT,
|
||||||
CUSTOM
|
/** The tag has some other custom activation or removal condition. */
|
||||||
|
CUSTOM,
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
// biome-ignore lint/correctness/noUnusedImports: Used in tsdoc
|
|
||||||
import type ConfirmUiHandler from "#app/ui/confirm-ui-handler";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used by {@linkcode ConfirmUiHandler} to determine whether the cursor should start on Yes or No
|
|
||||||
*/
|
|
||||||
export const ConfirmUiMode = Object.freeze({
|
|
||||||
/** Start cursor on Yes */
|
|
||||||
DEFAULT_YES: 1,
|
|
||||||
/** Start cursor on No */
|
|
||||||
DEFAULT_NO: 2
|
|
||||||
});
|
|
||||||
export type ConfirmUiMode = typeof ConfirmUiMode[keyof typeof ConfirmUiMode];
|
|
6
src/enums/dynamic-phase-type.ts
Normal file
6
src/enums/dynamic-phase-type.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Enum representation of the phase types held by implementations of {@linkcode PhasePriorityQueue}
|
||||||
|
*/
|
||||||
|
export enum DynamicPhaseType {
|
||||||
|
POST_SUMMON
|
||||||
|
}
|
7
src/enums/modifier-pool-type.ts
Normal file
7
src/enums/modifier-pool-type.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export enum ModifierPoolType {
|
||||||
|
PLAYER,
|
||||||
|
WILD,
|
||||||
|
TRAINER,
|
||||||
|
ENEMY_BUFF,
|
||||||
|
DAILY_STARTER
|
||||||
|
}
|
149
src/enums/move-use-mode.ts
Normal file
149
src/enums/move-use-mode.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import type { PostDancingMoveAbAttr } from "#app/data/abilities/ability";
|
||||||
|
import type { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing all the possible means through which a given move can be executed.
|
||||||
|
* Each one inherits the properties (or exclusions) of all types preceding it.
|
||||||
|
* Properties newly found on a given use mode will be **bolded**,
|
||||||
|
* while oddities breaking a previous trend will be listed in _italics_.
|
||||||
|
|
||||||
|
* Callers should refrain from performing non-equality checks on `MoveUseMode`s directly,
|
||||||
|
* instead using the available helper functions
|
||||||
|
* ({@linkcode isVirtual}, {@linkcode isIgnoreStatus}, {@linkcode isIgnorePP} and {@linkcode isReflected}).
|
||||||
|
*/
|
||||||
|
export const MoveUseMode = {
|
||||||
|
/**
|
||||||
|
* This move was used normally (i.e. clicking on the button) or called via Instruct.
|
||||||
|
* It deducts PP from the user's moveset (failing if out of PP), and interacts normally with other moves and abilities.
|
||||||
|
*/
|
||||||
|
NORMAL: 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This move was called by an effect that ignores PP, such as a consecutively executed move (e.g. Outrage).
|
||||||
|
*
|
||||||
|
* PP-ignoring moves (as their name implies) **do not consume PP** when used
|
||||||
|
* and **will not fail** if none is left prior to execution.
|
||||||
|
* All other effects remain identical to {@linkcode MoveUseMode.NORMAL}.
|
||||||
|
*
|
||||||
|
* PP can still be reduced by other effects (such as Spite or Eerie Spell).
|
||||||
|
*/
|
||||||
|
IGNORE_PP: 2,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This move was called indirectly by an out-of-turn effect other than Instruct or the user's previous move.
|
||||||
|
* Currently only used by {@linkcode PostDancingMoveAbAttr | Dancer}.
|
||||||
|
*
|
||||||
|
* Indirect moves ignore PP checks similar to {@linkcode MoveUseMode.IGNORE_PP}, but additionally **cannot be copied**
|
||||||
|
* by all move-copying effects (barring reflection).
|
||||||
|
* They are also **"skipped over" by most moveset and move history-related effects** (PP reduction, Last Resort, etc).
|
||||||
|
*
|
||||||
|
* They still respect the user's volatile status conditions and confusion (though will uniquely _cure freeze and sleep before use_).
|
||||||
|
*/
|
||||||
|
INDIRECT: 3,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This move was called as part of another move's effect (such as for most {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_call_other_moves | Move-calling moves}).
|
||||||
|
|
||||||
|
* Follow-up moves **bypass cancellation** from all **non-volatile status conditions** and **{@linkcode BattlerTagLapseType.MOVE}-type effects**
|
||||||
|
* (having been checked already on the calling move).
|
||||||
|
|
||||||
|
* They are _not ignored_ by other move-calling moves and abilities (unlike {@linkcode MoveUseMode.FOLLOW_UP} and {@linkcode MoveUseMode.REFLECTED}),
|
||||||
|
* but still inherit the former's disregard for moveset-related effects.
|
||||||
|
*/
|
||||||
|
FOLLOW_UP: 4,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This move was reflected by Magic Coat or Magic Bounce.
|
||||||
|
|
||||||
|
* Reflected moves ignore all the same cancellation checks as {@linkcode MoveUseMode.INDIRECT}
|
||||||
|
* and retain the same copy prevention as {@linkcode MoveUseMode.FOLLOW_UP}, but additionally
|
||||||
|
* **cannot be reflected by other reflecting effects**.
|
||||||
|
*/
|
||||||
|
REFLECTED: 5
|
||||||
|
// TODO: Add use type TRANSPARENT for Future Sight and Doom Desire to prevent move history pushing
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type MoveUseMode = (typeof MoveUseMode)[keyof typeof MoveUseMode];
|
||||||
|
|
||||||
|
// # HELPER FUNCTIONS
|
||||||
|
// Please update the markdown tables if any new `MoveUseMode`s get added.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given {@linkcode MoveUseMode} is virtual (i.e. called by another move or effect).
|
||||||
|
* Virtual moves are ignored by most moveset-related effects due to not being executed directly.
|
||||||
|
* @returns Whether {@linkcode useMode} is virtual.
|
||||||
|
* @remarks
|
||||||
|
* This function is equivalent to the following truth table:
|
||||||
|
*
|
||||||
|
* | Use Type | Returns |
|
||||||
|
* |------------------------------------|---------|
|
||||||
|
* | {@linkcode MoveUseMode.NORMAL} | `false` |
|
||||||
|
* | {@linkcode MoveUseMode.IGNORE_PP} | `false` |
|
||||||
|
* | {@linkcode MoveUseMode.INDIRECT} | `true` |
|
||||||
|
* | {@linkcode MoveUseMode.FOLLOW_UP} | `true` |
|
||||||
|
* | {@linkcode MoveUseMode.REFLECTED} | `true` |
|
||||||
|
*/
|
||||||
|
export function isVirtual(useMode: MoveUseMode): boolean {
|
||||||
|
return useMode >= MoveUseMode.INDIRECT
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given {@linkcode MoveUseMode} should ignore pre-move cancellation checks
|
||||||
|
* from {@linkcode StatusEffect.PARALYSIS} and {@linkcode BattlerTagLapseType.MOVE}-type effects.
|
||||||
|
* @param useMode - The {@linkcode MoveUseMode} to check.
|
||||||
|
* @returns Whether {@linkcode useMode} should ignore status and otehr cancellation checks.
|
||||||
|
* @remarks
|
||||||
|
* This function is equivalent to the following truth table:
|
||||||
|
*
|
||||||
|
* | Use Type | Returns |
|
||||||
|
* |------------------------------------|---------|
|
||||||
|
* | {@linkcode MoveUseMode.NORMAL} | `false` |
|
||||||
|
* | {@linkcode MoveUseMode.IGNORE_PP} | `false` |
|
||||||
|
* | {@linkcode MoveUseMode.INDIRECT} | `false` |
|
||||||
|
* | {@linkcode MoveUseMode.FOLLOW_UP} | `true` |
|
||||||
|
* | {@linkcode MoveUseMode.REFLECTED} | `true` |
|
||||||
|
*/
|
||||||
|
export function isIgnoreStatus(useMode: MoveUseMode): boolean {
|
||||||
|
return useMode >= MoveUseMode.FOLLOW_UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given {@linkcode MoveUseMode} should ignore PP.
|
||||||
|
* PP-ignoring moves will ignore normal PP consumption as well as associated failure checks.
|
||||||
|
* @param useMode - The {@linkcode MoveUseMode} to check.
|
||||||
|
* @returns Whether {@linkcode useMode} ignores PP.
|
||||||
|
* @remarks
|
||||||
|
* This function is equivalent to the following truth table:
|
||||||
|
*
|
||||||
|
* | Use Type | Returns |
|
||||||
|
* |------------------------------------|---------|
|
||||||
|
* | {@linkcode MoveUseMode.NORMAL} | `false` |
|
||||||
|
* | {@linkcode MoveUseMode.IGNORE_PP} | `true` |
|
||||||
|
* | {@linkcode MoveUseMode.INDIRECT} | `true` |
|
||||||
|
* | {@linkcode MoveUseMode.FOLLOW_UP} | `true` |
|
||||||
|
* | {@linkcode MoveUseMode.REFLECTED} | `true` |
|
||||||
|
*/
|
||||||
|
export function isIgnorePP(useMode: MoveUseMode): boolean {
|
||||||
|
return useMode >= MoveUseMode.IGNORE_PP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given {@linkcode MoveUseMode} is reflected.
|
||||||
|
* Reflected moves cannot be reflected, copied, or cancelled by status effects,
|
||||||
|
* nor will they trigger {@linkcode PostDancingMoveAbAttr | Dancer}.
|
||||||
|
* @param useMode - The {@linkcode MoveUseMode} to check.
|
||||||
|
* @returns Whether {@linkcode useMode} is reflected.
|
||||||
|
* @remarks
|
||||||
|
* This function is equivalent to the following truth table:
|
||||||
|
*
|
||||||
|
* | Use Type | Returns |
|
||||||
|
* |------------------------------------|---------|
|
||||||
|
* | {@linkcode MoveUseMode.NORMAL} | `false` |
|
||||||
|
* | {@linkcode MoveUseMode.IGNORE_PP} | `false` |
|
||||||
|
* | {@linkcode MoveUseMode.INDIRECT} | `false` |
|
||||||
|
* | {@linkcode MoveUseMode.FOLLOW_UP} | `false` |
|
||||||
|
* | {@linkcode MoveUseMode.REFLECTED} | `true` |
|
||||||
|
*/
|
||||||
|
export function isReflected(useMode: MoveUseMode): boolean {
|
||||||
|
return useMode === MoveUseMode.REFLECTED;
|
||||||
|
}
|
7
src/enums/unlockables.ts
Normal file
7
src/enums/unlockables.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
export enum Unlockables {
|
||||||
|
ENDLESS_MODE,
|
||||||
|
MINI_BLACK_HOLE,
|
||||||
|
SPLICED_ENDLESS_MODE,
|
||||||
|
EVIOLITE
|
||||||
|
}
|
@ -24,10 +24,7 @@ import {
|
|||||||
applyAbAttrs,
|
applyAbAttrs,
|
||||||
applyPostTerrainChangeAbAttrs,
|
applyPostTerrainChangeAbAttrs,
|
||||||
applyPostWeatherChangeAbAttrs,
|
applyPostWeatherChangeAbAttrs,
|
||||||
PostTerrainChangeAbAttr,
|
} from "#app/data/abilities/apply-ab-attrs";
|
||||||
PostWeatherChangeAbAttr,
|
|
||||||
TerrainEventTypeChangeAbAttr,
|
|
||||||
} from "#app/data/abilities/ability";
|
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
||||||
@ -265,7 +262,7 @@ export class Arena {
|
|||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SpeciesId.LYCANROC:
|
case SpeciesId.LYCANROC: {
|
||||||
const timeOfDay = this.getTimeOfDay();
|
const timeOfDay = this.getTimeOfDay();
|
||||||
switch (timeOfDay) {
|
switch (timeOfDay) {
|
||||||
case TimeOfDay.DAY:
|
case TimeOfDay.DAY:
|
||||||
@ -277,6 +274,7 @@ export class Arena {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -374,7 +372,7 @@ export class Arena {
|
|||||||
pokemon.findAndRemoveTags(
|
pokemon.findAndRemoveTags(
|
||||||
t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather),
|
t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather),
|
||||||
);
|
);
|
||||||
applyPostWeatherChangeAbAttrs(PostWeatherChangeAbAttr, pokemon, weather);
|
applyPostWeatherChangeAbAttrs("PostWeatherChangeAbAttr", pokemon, weather);
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -463,8 +461,8 @@ export class Arena {
|
|||||||
pokemon.findAndRemoveTags(
|
pokemon.findAndRemoveTags(
|
||||||
t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain),
|
t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain),
|
||||||
);
|
);
|
||||||
applyPostTerrainChangeAbAttrs(PostTerrainChangeAbAttr, pokemon, terrain);
|
applyPostTerrainChangeAbAttrs("PostTerrainChangeAbAttr", pokemon, terrain);
|
||||||
applyAbAttrs(TerrainEventTypeChangeAbAttr, pokemon, null, false);
|
applyAbAttrs("TerrainEventTypeChangeAbAttr", pokemon, null, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -767,6 +765,9 @@ export class Arena {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add an overload similar to `Array.prototype.find` if the predicate func is of the form
|
||||||
|
// `(x): x is T`
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses {@linkcode findTagsOnSide} to filter (using the parameter function) for specific tags that apply to both sides
|
* Uses {@linkcode findTagsOnSide} to filter (using the parameter function) for specific tags that apply to both sides
|
||||||
* @param tagPredicate a function mapping {@linkcode ArenaTag}s to `boolean`s
|
* @param tagPredicate a function mapping {@linkcode ArenaTag}s to `boolean`s
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import Pokemon from "./pokemon";
|
import Pokemon from "./pokemon";
|
||||||
import { fixedInt, randInt } from "#app/utils/common";
|
import { fixedInt, coerceArray, randInt } from "#app/utils/common";
|
||||||
|
|
||||||
export default class PokemonSpriteSparkleHandler {
|
export default class PokemonSpriteSparkleHandler {
|
||||||
private sprites: Set<Phaser.GameObjects.Sprite>;
|
private sprites: Set<Phaser.GameObjects.Sprite>;
|
||||||
@ -57,9 +57,7 @@ export default class PokemonSpriteSparkleHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void {
|
add(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void {
|
||||||
if (!Array.isArray(sprites)) {
|
sprites = coerceArray(sprites);
|
||||||
sprites = [sprites];
|
|
||||||
}
|
|
||||||
for (const s of sprites) {
|
for (const s of sprites) {
|
||||||
if (this.sprites.has(s)) {
|
if (this.sprites.has(s)) {
|
||||||
continue;
|
continue;
|
||||||
@ -69,9 +67,7 @@ export default class PokemonSpriteSparkleHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void {
|
remove(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void {
|
||||||
if (!Array.isArray(sprites)) {
|
sprites = coerceArray(sprites);
|
||||||
sprites = [sprites];
|
|
||||||
}
|
|
||||||
for (const s of sprites) {
|
for (const s of sprites) {
|
||||||
this.sprites.delete(s);
|
this.sprites.delete(s);
|
||||||
}
|
}
|
||||||
|
@ -38,9 +38,9 @@ import {
|
|||||||
deltaRgb,
|
deltaRgb,
|
||||||
isBetween,
|
isBetween,
|
||||||
randSeedFloat,
|
randSeedFloat,
|
||||||
type nil,
|
|
||||||
type Constructor,
|
type Constructor,
|
||||||
randSeedIntRange,
|
randSeedIntRange,
|
||||||
|
coerceArray,
|
||||||
} from "#app/utils/common";
|
} from "#app/utils/common";
|
||||||
import type { TypeDamageMultiplier } from "#app/data/type";
|
import type { TypeDamageMultiplier } from "#app/data/type";
|
||||||
import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type";
|
import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type";
|
||||||
@ -111,61 +111,23 @@ import { WeatherType } from "#enums/weather-type";
|
|||||||
import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
|
import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
|
||||||
import type { Ability } from "#app/data/abilities/ability-class";
|
import type { Ability } from "#app/data/abilities/ability";
|
||||||
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
|
|
||||||
import {
|
import {
|
||||||
StatMultiplierAbAttr,
|
|
||||||
BlockCritAbAttr,
|
|
||||||
BonusCritAbAttr,
|
|
||||||
BypassBurnDamageReductionAbAttr,
|
|
||||||
FieldPriorityMoveImmunityAbAttr,
|
|
||||||
IgnoreOpponentStatStagesAbAttr,
|
|
||||||
MoveImmunityAbAttr,
|
|
||||||
PreDefendFullHpEndureAbAttr,
|
|
||||||
ReceivedMoveDamageMultiplierAbAttr,
|
|
||||||
StabBoostAbAttr,
|
|
||||||
StatusEffectImmunityAbAttr,
|
|
||||||
TypeImmunityAbAttr,
|
|
||||||
WeightMultiplierAbAttr,
|
|
||||||
applyAbAttrs,
|
applyAbAttrs,
|
||||||
applyStatMultiplierAbAttrs,
|
applyStatMultiplierAbAttrs,
|
||||||
applyPreApplyBattlerTagAbAttrs,
|
applyPreApplyBattlerTagAbAttrs,
|
||||||
applyPreAttackAbAttrs,
|
applyPreAttackAbAttrs,
|
||||||
applyPreDefendAbAttrs,
|
applyPreDefendAbAttrs,
|
||||||
applyPreSetStatusAbAttrs,
|
applyPreSetStatusAbAttrs,
|
||||||
NoFusionAbilityAbAttr,
|
|
||||||
MultCritAbAttr,
|
|
||||||
IgnoreTypeImmunityAbAttr,
|
|
||||||
DamageBoostAbAttr,
|
|
||||||
IgnoreTypeStatusEffectImmunityAbAttr,
|
|
||||||
ConditionalCritAbAttr,
|
|
||||||
applyFieldStatMultiplierAbAttrs,
|
applyFieldStatMultiplierAbAttrs,
|
||||||
FieldMultiplyStatAbAttr,
|
|
||||||
AddSecondStrikeAbAttr,
|
|
||||||
UserFieldStatusEffectImmunityAbAttr,
|
|
||||||
UserFieldBattlerTagImmunityAbAttr,
|
|
||||||
BattlerTagImmunityAbAttr,
|
|
||||||
MoveTypeChangeAbAttr,
|
|
||||||
FullHpResistTypeAbAttr,
|
|
||||||
applyCheckTrappedAbAttrs,
|
applyCheckTrappedAbAttrs,
|
||||||
CheckTrappedAbAttr,
|
|
||||||
InfiltratorAbAttr,
|
|
||||||
AlliedFieldDamageReductionAbAttr,
|
|
||||||
PostDamageAbAttr,
|
|
||||||
applyPostDamageAbAttrs,
|
applyPostDamageAbAttrs,
|
||||||
CommanderAbAttr,
|
|
||||||
applyPostItemLostAbAttrs,
|
applyPostItemLostAbAttrs,
|
||||||
PostItemLostAbAttr,
|
|
||||||
applyOnGainAbAttrs,
|
applyOnGainAbAttrs,
|
||||||
PreLeaveFieldAbAttr,
|
|
||||||
applyPreLeaveFieldAbAttrs,
|
applyPreLeaveFieldAbAttrs,
|
||||||
applyOnLoseAbAttrs,
|
applyOnLoseAbAttrs,
|
||||||
PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr,
|
|
||||||
applyAllyStatMultiplierAbAttrs,
|
applyAllyStatMultiplierAbAttrs,
|
||||||
AllyStatMultiplierAbAttr,
|
} from "#app/data/abilities/apply-ab-attrs";
|
||||||
MoveAbilityBypassAbAttr,
|
|
||||||
PreSummonAbAttr,
|
|
||||||
} from "#app/data/abilities/ability";
|
|
||||||
import { allAbilities } from "#app/data/data-lists";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import type PokemonData from "#app/system/pokemon-data";
|
import type PokemonData from "#app/system/pokemon-data";
|
||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
@ -192,7 +154,7 @@ import type { TrainerSlot } from "#enums/trainer-slot";
|
|||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { speciesEggMoves } from "#app/data/balance/egg-moves";
|
import { speciesEggMoves } from "#app/data/balance/egg-moves";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import { applyChallenges } from "#app/data/challenge";
|
import { applyChallenges } from "#app/data/challenge";
|
||||||
import { ChallengeType } from "#enums/challenge-type";
|
import { ChallengeType } from "#enums/challenge-type";
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
@ -223,12 +185,14 @@ import { doShinySparkleAnim } from "#app/field/anims";
|
|||||||
import { MoveFlags } from "#enums/MoveFlags";
|
import { MoveFlags } from "#enums/MoveFlags";
|
||||||
import { timedEventManager } from "#app/global-event-manager";
|
import { timedEventManager } from "#app/global-event-manager";
|
||||||
import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader";
|
import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader";
|
||||||
|
import { isVirtual, isIgnorePP, MoveUseMode } from "#enums/move-use-mode";
|
||||||
import { FieldPosition } from "#enums/field-position";
|
import { FieldPosition } from "#enums/field-position";
|
||||||
import { LearnMoveSituation } from "#enums/learn-move-situation";
|
import { LearnMoveSituation } from "#enums/learn-move-situation";
|
||||||
import { HitResult } from "#enums/hit-result";
|
import { HitResult } from "#enums/hit-result";
|
||||||
import { AiType } from "#enums/ai-type";
|
import { AiType } from "#enums/ai-type";
|
||||||
import type { MoveResult } from "#enums/move-result";
|
import type { MoveResult } from "#enums/move-result";
|
||||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||||
|
import type { AbAttrMap, AbAttrString } from "#app/@types/ability-types";
|
||||||
|
|
||||||
/** Base typeclass for damage parameter methods, used for DRY */
|
/** Base typeclass for damage parameter methods, used for DRY */
|
||||||
type damageParams = {
|
type damageParams = {
|
||||||
@ -360,7 +324,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
super(globalScene, x, y);
|
super(globalScene, x, y);
|
||||||
|
|
||||||
if (!species.isObtainable() && this.isPlayer()) {
|
if (!species.isObtainable() && this.isPlayer()) {
|
||||||
throw `Cannot create a player Pokemon for species '${species.getName(formIndex)}'`;
|
throw `Cannot create a player Pokemon for species "${species.getName(formIndex)}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.species = species;
|
this.species = species;
|
||||||
@ -1403,7 +1367,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
applyMoveAttrs("HighCritAttr", source, this, move, critStage);
|
applyMoveAttrs("HighCritAttr", source, this, move, critStage);
|
||||||
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
|
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
|
||||||
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
|
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
|
||||||
applyAbAttrs(BonusCritAbAttr, source, null, false, critStage);
|
applyAbAttrs("BonusCritAbAttr", source, null, false, critStage);
|
||||||
const critBoostTag = source.getTag(CritBoostTag);
|
const critBoostTag = source.getTag(CritBoostTag);
|
||||||
if (critBoostTag) {
|
if (critBoostTag) {
|
||||||
if (critBoostTag instanceof DragonCheerTag) {
|
if (critBoostTag instanceof DragonCheerTag) {
|
||||||
@ -1464,19 +1428,27 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
|
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
|
||||||
const fieldApplied = new BooleanHolder(false);
|
const fieldApplied = new BooleanHolder(false);
|
||||||
for (const pokemon of globalScene.getField(true)) {
|
for (const pokemon of globalScene.getField(true)) {
|
||||||
applyFieldStatMultiplierAbAttrs(FieldMultiplyStatAbAttr, pokemon, stat, statValue, this, fieldApplied, simulated);
|
applyFieldStatMultiplierAbAttrs(
|
||||||
|
"FieldMultiplyStatAbAttr",
|
||||||
|
pokemon,
|
||||||
|
stat,
|
||||||
|
statValue,
|
||||||
|
this,
|
||||||
|
fieldApplied,
|
||||||
|
simulated,
|
||||||
|
);
|
||||||
if (fieldApplied.value) {
|
if (fieldApplied.value) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ignoreAbility) {
|
if (!ignoreAbility) {
|
||||||
applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, stat, statValue, simulated);
|
applyStatMultiplierAbAttrs("StatMultiplierAbAttr", this, stat, statValue, simulated);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ally = this.getAlly();
|
const ally = this.getAlly();
|
||||||
if (!isNullOrUndefined(ally)) {
|
if (!isNullOrUndefined(ally)) {
|
||||||
applyAllyStatMultiplierAbAttrs(
|
applyAllyStatMultiplierAbAttrs(
|
||||||
AllyStatMultiplierAbAttr,
|
"AllyStatMultiplierAbAttr",
|
||||||
ally,
|
ally,
|
||||||
stat,
|
stat,
|
||||||
statValue,
|
statValue,
|
||||||
@ -1803,9 +1775,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
let overrideArray: MoveId | Array<MoveId> = this.isPlayer()
|
let overrideArray: MoveId | Array<MoveId> = this.isPlayer()
|
||||||
? Overrides.MOVESET_OVERRIDE
|
? Overrides.MOVESET_OVERRIDE
|
||||||
: Overrides.OPP_MOVESET_OVERRIDE;
|
: Overrides.OPP_MOVESET_OVERRIDE;
|
||||||
if (!Array.isArray(overrideArray)) {
|
overrideArray = coerceArray(overrideArray);
|
||||||
overrideArray = [overrideArray];
|
|
||||||
}
|
|
||||||
if (overrideArray.length > 0) {
|
if (overrideArray.length > 0) {
|
||||||
if (!this.isPlayer()) {
|
if (!this.isPlayer()) {
|
||||||
this.moveset = [];
|
this.moveset = [];
|
||||||
@ -2059,15 +2029,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param ignoreOverride - Whether to ignore ability changing effects; Default `false`
|
* @param ignoreOverride - Whether to ignore ability changing effects; Default `false`
|
||||||
* @returns An array of all the ability attributes on this ability.
|
* @returns An array of all the ability attributes on this ability.
|
||||||
*/
|
*/
|
||||||
public getAbilityAttrs<T extends AbAttr = AbAttr>(
|
public getAbilityAttrs<T extends AbAttrString>(attrType: T, canApply = true, ignoreOverride = false): AbAttrMap[T][] {
|
||||||
attrType: { new (...args: any[]): T },
|
const abilityAttrs: AbAttrMap[T][] = [];
|
||||||
canApply = true,
|
|
||||||
ignoreOverride = false,
|
|
||||||
): T[] {
|
|
||||||
const abilityAttrs: T[] = [];
|
|
||||||
|
|
||||||
if (!canApply || this.canApplyAbility()) {
|
if (!canApply || this.canApplyAbility()) {
|
||||||
abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs<T>(attrType));
|
abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs(attrType));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canApply || this.canApplyAbility(true)) {
|
if (!canApply || this.canApplyAbility(true)) {
|
||||||
@ -2152,7 +2118,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const ability = !passive ? this.getAbility() : this.getPassiveAbility();
|
const ability = !passive ? this.getAbility() : this.getPassiveAbility();
|
||||||
if (this.isFusion() && ability.hasAttr(NoFusionAbilityAbAttr)) {
|
if (this.isFusion() && ability.hasAttr("NoFusionAbilityAbAttr")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const arena = globalScene?.arena;
|
const arena = globalScene?.arena;
|
||||||
@ -2163,10 +2129,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const suppressAbilitiesTag = arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag;
|
const suppressAbilitiesTag = arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag;
|
||||||
const suppressOffField = ability.hasAttr(PreSummonAbAttr);
|
const suppressOffField = ability.hasAttr("PreSummonAbAttr");
|
||||||
if ((this.isOnField() || suppressOffField) && suppressAbilitiesTag && !suppressAbilitiesTag.isBeingRemoved()) {
|
if ((this.isOnField() || suppressOffField) && suppressAbilitiesTag && !suppressAbilitiesTag.isBeingRemoved()) {
|
||||||
const thisAbilitySuppressing = ability.hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr);
|
const thisAbilitySuppressing = ability.hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr");
|
||||||
const hasSuppressingAbility = this.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false);
|
const hasSuppressingAbility = this.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false);
|
||||||
// Neutralizing gas is up - suppress abilities unless they are unsuppressable or this pokemon is responsible for the gas
|
// Neutralizing gas is up - suppress abilities unless they are unsuppressable or this pokemon is responsible for the gas
|
||||||
// (Balance decided that the other ability of a neutralizing gas pokemon should not be neutralized)
|
// (Balance decided that the other ability of a neutralizing gas pokemon should not be neutralized)
|
||||||
// If the ability itself is neutralizing gas, don't suppress it (handled through arena tag)
|
// If the ability itself is neutralizing gas, don't suppress it (handled through arena tag)
|
||||||
@ -2207,13 +2173,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param ignoreOverride Whether to ignore ability changing effects; default `false`
|
* @param ignoreOverride Whether to ignore ability changing effects; default `false`
|
||||||
* @returns `true` if an ability with the given {@linkcode AbAttr} is present and active
|
* @returns `true` if an ability with the given {@linkcode AbAttr} is present and active
|
||||||
*/
|
*/
|
||||||
public hasAbilityWithAttr(attrType: Constructor<AbAttr>, canApply = true, ignoreOverride = false): boolean {
|
public hasAbilityWithAttr(attrType: AbAttrString, canApply = true, ignoreOverride = false): boolean {
|
||||||
if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) {
|
if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType);
|
return this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getAbilityPriorities(): [number, number] {
|
||||||
|
return [this.getAbility().postSummonPriority, this.getPassiveAbility().postSummonPriority];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the weight of the Pokemon with subtractive modifiers (Autotomize) happening first
|
* Gets the weight of the Pokemon with subtractive modifiers (Autotomize) happening first
|
||||||
* and then multiplicative modifiers happening after (Heavy Metal and Light Metal)
|
* and then multiplicative modifiers happening after (Heavy Metal and Light Metal)
|
||||||
@ -2229,7 +2199,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const weight = new NumberHolder(this.species.weight - weightRemoved);
|
const weight = new NumberHolder(this.species.weight - weightRemoved);
|
||||||
|
|
||||||
// This will trigger the ability overlay so only call this function when necessary
|
// This will trigger the ability overlay so only call this function when necessary
|
||||||
applyAbAttrs(WeightMultiplierAbAttr, this, null, false, weight);
|
applyAbAttrs("WeightMultiplierAbAttr", this, null, false, weight);
|
||||||
return Math.max(minWeight, weight.value);
|
return Math.max(minWeight, weight.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2300,7 +2270,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false);
|
const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false);
|
||||||
|
|
||||||
for (const opponent of opposingField) {
|
for (const opponent of opposingField) {
|
||||||
applyCheckTrappedAbAttrs(CheckTrappedAbAttr, opponent, trappedByAbility, this, trappedAbMessages, simulated);
|
applyCheckTrappedAbAttrs("CheckTrappedAbAttr", opponent, trappedByAbility, this, trappedAbMessages, simulated);
|
||||||
}
|
}
|
||||||
|
|
||||||
const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
@ -2322,7 +2292,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const moveTypeHolder = new NumberHolder(move.type);
|
const moveTypeHolder = new NumberHolder(move.type);
|
||||||
|
|
||||||
applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder);
|
applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder);
|
||||||
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder);
|
applyPreAttackAbAttrs("MoveTypeChangeAbAttr", this, null, move, simulated, moveTypeHolder);
|
||||||
|
|
||||||
// If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type,
|
// If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type,
|
||||||
// then bypass the check for ion deluge and electrify
|
// then bypass the check for ion deluge and electrify
|
||||||
@ -2387,16 +2357,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
const cancelledHolder = cancelled ?? new BooleanHolder(false);
|
const cancelledHolder = cancelled ?? new BooleanHolder(false);
|
||||||
if (!ignoreAbility) {
|
if (!ignoreAbility) {
|
||||||
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier);
|
applyPreDefendAbAttrs("TypeImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
|
||||||
|
|
||||||
if (!cancelledHolder.value) {
|
if (!cancelledHolder.value) {
|
||||||
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier);
|
applyPreDefendAbAttrs("MoveImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cancelledHolder.value) {
|
if (!cancelledHolder.value) {
|
||||||
const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||||
defendingSidePlayField.forEach(p =>
|
defendingSidePlayField.forEach(p =>
|
||||||
applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelledHolder),
|
applyPreDefendAbAttrs("FieldPriorityMoveImmunityAbAttr", p, source, move, cancelledHolder),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2411,7 +2381,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
// Apply Tera Shell's effect to attacks after all immunities are accounted for
|
// Apply Tera Shell's effect to attacks after all immunities are accounted for
|
||||||
if (!ignoreAbility && move.category !== MoveCategory.STATUS) {
|
if (!ignoreAbility && move.category !== MoveCategory.STATUS) {
|
||||||
applyPreDefendAbAttrs(FullHpResistTypeAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier);
|
applyPreDefendAbAttrs("FullHpResistTypeAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (move.category === MoveCategory.STATUS && move.hitsSubstitute(source, this)) {
|
if (move.category === MoveCategory.STATUS && move.hitsSubstitute(source, this)) {
|
||||||
@ -2463,8 +2433,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
if (source) {
|
if (source) {
|
||||||
const ignoreImmunity = new BooleanHolder(false);
|
const ignoreImmunity = new BooleanHolder(false);
|
||||||
if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) {
|
if (source.isActive(true) && source.hasAbilityWithAttr("IgnoreTypeImmunityAbAttr")) {
|
||||||
applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, simulated, moveType, defType);
|
applyAbAttrs("IgnoreTypeImmunityAbAttr", source, ignoreImmunity, simulated, moveType, defType);
|
||||||
}
|
}
|
||||||
if (ignoreImmunity.value) {
|
if (ignoreImmunity.value) {
|
||||||
if (multiplier.value === 0) {
|
if (multiplier.value === 0) {
|
||||||
@ -3167,7 +3137,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
while (rand > stabMovePool[index][1]) {
|
while (rand > stabMovePool[index][1]) {
|
||||||
rand -= stabMovePool[index++][1];
|
rand -= stabMovePool[index++][1];
|
||||||
}
|
}
|
||||||
this.moveset.push(new PokemonMove(stabMovePool[index][0], 0, 0));
|
this.moveset.push(new PokemonMove(stabMovePool[index][0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
while (baseWeights.length > this.moveset.length && this.moveset.length < 4) {
|
while (baseWeights.length > this.moveset.length && this.moveset.length < 4) {
|
||||||
@ -3220,7 +3190,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
while (rand > movePool[index][1]) {
|
while (rand > movePool[index][1]) {
|
||||||
rand -= movePool[index++][1];
|
rand -= movePool[index++][1];
|
||||||
}
|
}
|
||||||
this.moveset.push(new PokemonMove(movePool[index][0], 0, 0));
|
this.moveset.push(new PokemonMove(movePool[index][0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger FormChange, except for enemy Pokemon during Mystery Encounters, to avoid crashes
|
// Trigger FormChange, except for enemy Pokemon during Mystery Encounters, to avoid crashes
|
||||||
@ -3415,7 +3385,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ignoreOppAbility) {
|
if (!ignoreOppAbility) {
|
||||||
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, opponent, null, simulated, stat, ignoreStatStage);
|
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", opponent, null, simulated, stat, ignoreStatStage);
|
||||||
}
|
}
|
||||||
if (move) {
|
if (move) {
|
||||||
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage);
|
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage);
|
||||||
@ -3454,8 +3424,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const ignoreAccStatStage = new BooleanHolder(false);
|
const ignoreAccStatStage = new BooleanHolder(false);
|
||||||
const ignoreEvaStatStage = new BooleanHolder(false);
|
const ignoreEvaStatStage = new BooleanHolder(false);
|
||||||
|
|
||||||
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, target, null, false, Stat.ACC, ignoreAccStatStage);
|
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", target, null, false, Stat.ACC, ignoreAccStatStage);
|
||||||
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, this, null, false, Stat.EVA, ignoreEvaStatStage);
|
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", this, null, false, Stat.EVA, ignoreEvaStatStage);
|
||||||
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
|
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
|
||||||
|
|
||||||
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
|
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
|
||||||
@ -3475,16 +3445,33 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
: 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6));
|
: 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6));
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, Stat.ACC, accuracyMultiplier, false, sourceMove);
|
applyStatMultiplierAbAttrs("StatMultiplierAbAttr", this, Stat.ACC, accuracyMultiplier, false, sourceMove);
|
||||||
|
|
||||||
const evasionMultiplier = new NumberHolder(1);
|
const evasionMultiplier = new NumberHolder(1);
|
||||||
applyStatMultiplierAbAttrs(StatMultiplierAbAttr, target, Stat.EVA, evasionMultiplier);
|
applyStatMultiplierAbAttrs("StatMultiplierAbAttr", target, Stat.EVA, evasionMultiplier);
|
||||||
|
|
||||||
const ally = this.getAlly();
|
const ally = this.getAlly();
|
||||||
if (!isNullOrUndefined(ally)) {
|
if (!isNullOrUndefined(ally)) {
|
||||||
const ignore = this.hasAbilityWithAttr(MoveAbilityBypassAbAttr) || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES);
|
const ignore =
|
||||||
applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, Stat.ACC, accuracyMultiplier, false, this, ignore);
|
this.hasAbilityWithAttr("MoveAbilityBypassAbAttr") || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES);
|
||||||
applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, Stat.EVA, evasionMultiplier, false, this, ignore);
|
applyAllyStatMultiplierAbAttrs(
|
||||||
|
"AllyStatMultiplierAbAttr",
|
||||||
|
ally,
|
||||||
|
Stat.ACC,
|
||||||
|
accuracyMultiplier,
|
||||||
|
false,
|
||||||
|
this,
|
||||||
|
ignore,
|
||||||
|
);
|
||||||
|
applyAllyStatMultiplierAbAttrs(
|
||||||
|
"AllyStatMultiplierAbAttr",
|
||||||
|
ally,
|
||||||
|
Stat.EVA,
|
||||||
|
evasionMultiplier,
|
||||||
|
false,
|
||||||
|
this,
|
||||||
|
ignore,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return accuracyMultiplier.value / evasionMultiplier.value;
|
return accuracyMultiplier.value / evasionMultiplier.value;
|
||||||
@ -3599,7 +3586,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier);
|
applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier);
|
||||||
|
|
||||||
if (!ignoreSourceAbility) {
|
if (!ignoreSourceAbility) {
|
||||||
applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier);
|
applyAbAttrs("StabBoostAbAttr", source, null, simulated, stabMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) {
|
if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) {
|
||||||
@ -3748,7 +3735,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
);
|
);
|
||||||
if (!ignoreSourceAbility) {
|
if (!ignoreSourceAbility) {
|
||||||
applyPreAttackAbAttrs(
|
applyPreAttackAbAttrs(
|
||||||
AddSecondStrikeAbAttr,
|
"AddSecondStrikeAbAttr",
|
||||||
source,
|
source,
|
||||||
this,
|
this,
|
||||||
move,
|
move,
|
||||||
@ -3766,7 +3753,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
/** The damage multiplier when the given move critically hits */
|
/** The damage multiplier when the given move critically hits */
|
||||||
const criticalMultiplier = new NumberHolder(isCritical ? 1.5 : 1);
|
const criticalMultiplier = new NumberHolder(isCritical ? 1.5 : 1);
|
||||||
applyAbAttrs(MultCritAbAttr, source, null, simulated, criticalMultiplier);
|
applyAbAttrs("MultCritAbAttr", source, null, simulated, criticalMultiplier);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A multiplier for random damage spread in the range [0.85, 1]
|
* A multiplier for random damage spread in the range [0.85, 1]
|
||||||
@ -3787,7 +3774,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
) {
|
) {
|
||||||
const burnDamageReductionCancelled = new BooleanHolder(false);
|
const burnDamageReductionCancelled = new BooleanHolder(false);
|
||||||
if (!ignoreSourceAbility) {
|
if (!ignoreSourceAbility) {
|
||||||
applyAbAttrs(BypassBurnDamageReductionAbAttr, source, burnDamageReductionCancelled, simulated);
|
applyAbAttrs("BypassBurnDamageReductionAbAttr", source, burnDamageReductionCancelled, simulated);
|
||||||
}
|
}
|
||||||
if (!burnDamageReductionCancelled.value) {
|
if (!burnDamageReductionCancelled.value) {
|
||||||
burnMultiplier = 0.5;
|
burnMultiplier = 0.5;
|
||||||
@ -3851,7 +3838,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
/** Doubles damage if the attacker has Tinted Lens and is using a resisted move */
|
/** Doubles damage if the attacker has Tinted Lens and is using a resisted move */
|
||||||
if (!ignoreSourceAbility) {
|
if (!ignoreSourceAbility) {
|
||||||
applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, move, simulated, damage);
|
applyPreAttackAbAttrs("DamageBoostAbAttr", source, this, move, simulated, damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Apply the enemy's Damage and Resistance tokens */
|
/** Apply the enemy's Damage and Resistance tokens */
|
||||||
@ -3864,12 +3851,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
/** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */
|
/** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */
|
||||||
if (!ignoreAbility) {
|
if (!ignoreAbility) {
|
||||||
applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, simulated, damage);
|
applyPreDefendAbAttrs("ReceivedMoveDamageMultiplierAbAttr", this, source, move, cancelled, simulated, damage);
|
||||||
|
|
||||||
const ally = this.getAlly();
|
const ally = this.getAlly();
|
||||||
/** Additionally apply friend guard damage reduction if ally has it. */
|
/** Additionally apply friend guard damage reduction if ally has it. */
|
||||||
if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) {
|
if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) {
|
||||||
applyPreDefendAbAttrs(AlliedFieldDamageReductionAbAttr, ally, source, move, cancelled, simulated, damage);
|
applyPreDefendAbAttrs("AlliedFieldDamageReductionAbAttr", ally, source, move, cancelled, simulated, damage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3877,7 +3864,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage);
|
applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage);
|
||||||
|
|
||||||
if (this.isFullHp() && !ignoreAbility) {
|
if (this.isFullHp() && !ignoreAbility) {
|
||||||
applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, false, damage);
|
applyPreDefendAbAttrs("PreDefendFullHpEndureAbAttr", this, source, move, cancelled, false, damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// debug message for when damage is applied (i.e. not simulated)
|
// debug message for when damage is applied (i.e. not simulated)
|
||||||
@ -3919,13 +3906,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
isCritical.value = true;
|
isCritical.value = true;
|
||||||
}
|
}
|
||||||
applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical);
|
applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical);
|
||||||
applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move);
|
applyAbAttrs("ConditionalCritAbAttr", source, null, simulated, isCritical, this, move);
|
||||||
if (!isCritical.value) {
|
if (!isCritical.value) {
|
||||||
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))];
|
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))];
|
||||||
isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance);
|
isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyAbAttrs(BlockCritAbAttr, this, null, simulated, isCritical);
|
applyAbAttrs("BlockCritAbAttr", this, null, simulated, isCritical);
|
||||||
|
|
||||||
return isCritical.value;
|
return isCritical.value;
|
||||||
}
|
}
|
||||||
@ -4032,7 +4019,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr
|
* Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr
|
||||||
*/
|
*/
|
||||||
if (!source || source.turnData.hitCount <= 1) {
|
if (!source || source.turnData.hitCount <= 1) {
|
||||||
applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source);
|
applyPostDamageAbAttrs("PostDamageAbAttr", this, damage, this.hasPassive(), false, [], source);
|
||||||
}
|
}
|
||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
@ -4080,11 +4067,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const stubTag = new BattlerTag(tagType, 0, 0);
|
const stubTag = new BattlerTag(tagType, 0, 0);
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, stubTag, cancelled, true);
|
applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, stubTag, cancelled, true);
|
||||||
|
|
||||||
const userField = this.getAlliedField();
|
const userField = this.getAlliedField();
|
||||||
userField.forEach(pokemon =>
|
userField.forEach(pokemon =>
|
||||||
applyPreApplyBattlerTagAbAttrs(UserFieldBattlerTagImmunityAbAttr, pokemon, stubTag, cancelled, true, this),
|
applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, stubTag, cancelled, true, this),
|
||||||
);
|
);
|
||||||
|
|
||||||
return !cancelled.value;
|
return !cancelled.value;
|
||||||
@ -4100,13 +4087,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct?
|
const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct?
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, newTag, cancelled);
|
applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, newTag, cancelled);
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const pokemon of this.getAlliedField()) {
|
for (const pokemon of this.getAlliedField()) {
|
||||||
applyPreApplyBattlerTagAbAttrs(UserFieldBattlerTagImmunityAbAttr, pokemon, newTag, cancelled, false, this);
|
applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, newTag, cancelled, false, this);
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -4122,7 +4109,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**@overload */
|
/**@overload */
|
||||||
getTag(tagType: BattlerTagType.GRUDGE): GrudgeTag | nil;
|
getTag(tagType: BattlerTagType.GRUDGE): GrudgeTag | undefined;
|
||||||
|
|
||||||
|
/** @overload */
|
||||||
|
getTag(tagType: BattlerTagType.SUBSTITUTE): SubstituteTag | undefined;
|
||||||
|
|
||||||
/** @overload */
|
/** @overload */
|
||||||
getTag(tagType: BattlerTagType): BattlerTag | undefined;
|
getTag(tagType: BattlerTagType): BattlerTag | undefined;
|
||||||
@ -4336,10 +4326,41 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return moveHistory.slice(0).reverse();
|
return moveHistory.slice(0).reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the most recently executed {@linkcode TurnMove} this {@linkcode Pokemon} has used that is:
|
||||||
|
* - Not {@linkcode MoveId.NONE}
|
||||||
|
* - Non-virtual ({@linkcode MoveUseMode | useMode} < {@linkcode MoveUseMode.INDIRECT})
|
||||||
|
* @param ignoreStruggle - Whether to additionally ignore {@linkcode Moves.STRUGGLE}; default `false`
|
||||||
|
* @param ignoreFollowUp - Whether to ignore moves with a use type of {@linkcode MoveUseMode.FOLLOW_UP}
|
||||||
|
* (e.g. ones called by Copycat/Mirror Move); default `true`.
|
||||||
|
* @returns The last move this Pokemon has used satisfying the aforementioned conditions,
|
||||||
|
* or `undefined` if no applicable moves have been used since switching in.
|
||||||
|
*/
|
||||||
|
getLastNonVirtualMove(ignoreStruggle = false, ignoreFollowUp = true): TurnMove | undefined {
|
||||||
|
return this.getLastXMoves(-1).find(
|
||||||
|
m =>
|
||||||
|
m.move !== MoveId.NONE &&
|
||||||
|
(!ignoreStruggle || m.move !== MoveId.STRUGGLE) &&
|
||||||
|
(!isVirtual(m.useMode) || (!ignoreFollowUp && m.useMode === MoveUseMode.FOLLOW_UP)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return this Pokemon's move queue, consisting of all the moves it is slated to perform.
|
||||||
|
* @returns An array of {@linkcode TurnMove}, as described above
|
||||||
|
*/
|
||||||
getMoveQueue(): TurnMove[] {
|
getMoveQueue(): TurnMove[] {
|
||||||
return this.summonData.moveQueue;
|
return this.summonData.moveQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new entry to the end of this Pokemon's move queue.
|
||||||
|
* @param queuedMove - A {@linkcode TurnMove} to push to this Pokemon's queue.
|
||||||
|
*/
|
||||||
|
pushMoveQueue(queuedMove: TurnMove): void {
|
||||||
|
this.summonData.moveQueue.push(queuedMove);
|
||||||
|
}
|
||||||
|
|
||||||
changeForm(formChange: SpeciesFormChange): Promise<void> {
|
changeForm(formChange: SpeciesFormChange): Promise<void> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
this.formIndex = Math.max(
|
this.formIndex = Math.max(
|
||||||
@ -4395,14 +4416,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
// biome-ignore lint: there are a ton of issues..
|
// biome-ignore lint: there are a ton of issues..
|
||||||
faintCry(callback: Function): void {
|
faintCry(callback: Function): void {
|
||||||
if (this.fusionSpecies && this.getSpeciesForm() !== this.getFusionSpeciesForm()) {
|
if (this.fusionSpecies && this.getSpeciesForm() !== this.getFusionSpeciesForm()) {
|
||||||
return this.fusionFaintCry(callback);
|
this.fusionFaintCry(callback);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = this.species.getCryKey(this.formIndex);
|
const key = this.species.getCryKey(this.formIndex);
|
||||||
let rate = 0.85;
|
let rate = 0.85;
|
||||||
const cry = globalScene.playSound(key, { rate: rate }) as AnySound;
|
const cry = globalScene.playSound(key, { rate: rate }) as AnySound;
|
||||||
if (!cry || globalScene.fieldVolume === 0) {
|
if (!cry || globalScene.fieldVolume === 0) {
|
||||||
return callback();
|
callback();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const sprite = this.getSprite();
|
const sprite = this.getSprite();
|
||||||
const tintSprite = this.getTintSprite();
|
const tintSprite = this.getTintSprite();
|
||||||
@ -4470,7 +4493,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
rate: rate,
|
rate: rate,
|
||||||
}) as AnySound;
|
}) as AnySound;
|
||||||
if (!cry || !fusionCry || globalScene.fieldVolume === 0) {
|
if (!cry || !fusionCry || globalScene.fieldVolume === 0) {
|
||||||
return callback();
|
callback();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
fusionCry.stop();
|
fusionCry.stop();
|
||||||
duration = Math.min(duration, fusionCry.totalDuration * 1000);
|
duration = Math.min(duration, fusionCry.totalDuration * 1000);
|
||||||
@ -4626,7 +4650,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
// Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity
|
// Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity
|
||||||
const cancelImmunity = new BooleanHolder(false);
|
const cancelImmunity = new BooleanHolder(false);
|
||||||
if (sourcePokemon) {
|
if (sourcePokemon) {
|
||||||
applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType);
|
applyAbAttrs("IgnoreTypeStatusEffectImmunityAbAttr", sourcePokemon, cancelImmunity, false, effect, defType);
|
||||||
if (cancelImmunity.value) {
|
if (cancelImmunity.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -4675,14 +4699,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyPreSetStatusAbAttrs(StatusEffectImmunityAbAttr, this, effect, cancelled, quiet);
|
applyPreSetStatusAbAttrs("StatusEffectImmunityAbAttr", this, effect, cancelled, quiet);
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const pokemon of this.getAlliedField()) {
|
for (const pokemon of this.getAlliedField()) {
|
||||||
applyPreSetStatusAbAttrs(
|
applyPreSetStatusAbAttrs(
|
||||||
UserFieldStatusEffectImmunityAbAttr,
|
"UserFieldStatusEffectImmunityAbAttr",
|
||||||
pokemon,
|
pokemon,
|
||||||
effect,
|
effect,
|
||||||
cancelled,
|
cancelled,
|
||||||
@ -4839,7 +4863,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
|
if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
|
||||||
const bypassed = new BooleanHolder(false);
|
const bypassed = new BooleanHolder(false);
|
||||||
if (attacker) {
|
if (attacker) {
|
||||||
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed);
|
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
|
||||||
}
|
}
|
||||||
return !bypassed.value;
|
return !bypassed.value;
|
||||||
}
|
}
|
||||||
@ -4863,7 +4887,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
// If this Pokemon has Commander and Dondozo as an active ally, hide this Pokemon's sprite.
|
// If this Pokemon has Commander and Dondozo as an active ally, hide this Pokemon's sprite.
|
||||||
if (
|
if (
|
||||||
this.hasAbilityWithAttr(CommanderAbAttr) &&
|
this.hasAbilityWithAttr("CommanderAbAttr") &&
|
||||||
globalScene.currentBattle.double &&
|
globalScene.currentBattle.double &&
|
||||||
this.getAlly()?.species.speciesId === SpeciesId.DONDOZO
|
this.getAlly()?.species.speciesId === SpeciesId.DONDOZO
|
||||||
) {
|
) {
|
||||||
@ -5388,7 +5412,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.hideInfo();
|
this.hideInfo();
|
||||||
}
|
}
|
||||||
// Trigger abilities that activate upon leaving the field
|
// Trigger abilities that activate upon leaving the field
|
||||||
applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, this);
|
applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", this);
|
||||||
this.setSwitchOutStatus(true);
|
this.setSwitchOutStatus(true);
|
||||||
globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true);
|
globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true);
|
||||||
globalScene.field.remove(this, destroy);
|
globalScene.field.remove(this, destroy);
|
||||||
@ -5448,7 +5472,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
globalScene.removeModifier(heldItem, this.isEnemy());
|
globalScene.removeModifier(heldItem, this.isEnemy());
|
||||||
}
|
}
|
||||||
if (forBattle) {
|
if (forBattle) {
|
||||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false);
|
applyPostItemLostAbAttrs("PostItemLostAbAttr", this, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -5994,7 +6018,7 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
copyMoveset(): PokemonMove[] {
|
copyMoveset(): PokemonMove[] {
|
||||||
const newMoveset: PokemonMove[] = [];
|
const newMoveset: PokemonMove[] = [];
|
||||||
this.moveset.forEach(move => {
|
this.moveset.forEach(move => {
|
||||||
newMoveset.push(new PokemonMove(move.moveId, 0, move.ppUp, move.virtual, move.maxPpOverride));
|
newMoveset.push(new PokemonMove(move.moveId, 0, move.ppUp, move.maxPpOverride));
|
||||||
});
|
});
|
||||||
|
|
||||||
return newMoveset;
|
return newMoveset;
|
||||||
@ -6174,33 +6198,39 @@ export class EnemyPokemon extends Pokemon {
|
|||||||
* the Pokemon the move will target.
|
* the Pokemon the move will target.
|
||||||
* @returns this Pokemon's next move in the format {move, moveTargets}
|
* @returns this Pokemon's next move in the format {move, moveTargets}
|
||||||
*/
|
*/
|
||||||
|
// TODO: split this up and move it elsewhere
|
||||||
getNextMove(): TurnMove {
|
getNextMove(): TurnMove {
|
||||||
// If this Pokemon has a move already queued, return it.
|
// If this Pokemon has a usable move already queued, return it,
|
||||||
|
// removing all unusable moves before it in the queue.
|
||||||
const moveQueue = this.getMoveQueue();
|
const moveQueue = this.getMoveQueue();
|
||||||
if (moveQueue.length !== 0) {
|
for (const [i, queuedMove] of moveQueue.entries()) {
|
||||||
const queuedMove = moveQueue[0];
|
const movesetMove = this.getMoveset().find(m => m.moveId === queuedMove.move);
|
||||||
if (queuedMove) {
|
// If the queued move was called indirectly, ignore all PP and usability checks.
|
||||||
const moveIndex = this.getMoveset().findIndex(m => m.moveId === queuedMove.move);
|
// Otherwise, ensure that the move being used is actually usable & in our moveset.
|
||||||
if (
|
// TODO: What should happen if a pokemon forgets a charging move mid-use?
|
||||||
(moveIndex > -1 && this.getMoveset()[moveIndex].isUsable(this, queuedMove.ignorePP)) ||
|
if (isVirtual(queuedMove.useMode) || movesetMove?.isUsable(this, isIgnorePP(queuedMove.useMode))) {
|
||||||
queuedMove.virtual
|
moveQueue.splice(0, i); // TODO: This should not be done here
|
||||||
) {
|
return queuedMove;
|
||||||
return queuedMove;
|
|
||||||
}
|
|
||||||
this.getMoveQueue().shift();
|
|
||||||
return this.getNextMove();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We went through the entire queue without a match; clear the entire thing.
|
||||||
|
this.summonData.moveQueue = [];
|
||||||
|
|
||||||
// Filter out any moves this Pokemon cannot use
|
// Filter out any moves this Pokemon cannot use
|
||||||
let movePool = this.getMoveset().filter(m => m.isUsable(this));
|
let movePool = this.getMoveset().filter(m => m.isUsable(this));
|
||||||
// If no moves are left, use Struggle. Otherwise, continue with move selection
|
// If no moves are left, use Struggle. Otherwise, continue with move selection
|
||||||
if (movePool.length) {
|
if (movePool.length) {
|
||||||
// If there's only 1 move in the move pool, use it.
|
// If there's only 1 move in the move pool, use it.
|
||||||
if (movePool.length === 1) {
|
if (movePool.length === 1) {
|
||||||
return { move: movePool[0].moveId, targets: this.getNextTargets(movePool[0].moveId) };
|
return {
|
||||||
|
move: movePool[0].moveId,
|
||||||
|
targets: this.getNextTargets(movePool[0].moveId),
|
||||||
|
useMode: MoveUseMode.NORMAL,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// If a move is forced because of Encore, use it.
|
// If a move is forced because of Encore, use it.
|
||||||
|
// Said moves are executed normally
|
||||||
const encoreTag = this.getTag(EncoreTag) as EncoreTag;
|
const encoreTag = this.getTag(EncoreTag) as EncoreTag;
|
||||||
if (encoreTag) {
|
if (encoreTag) {
|
||||||
const encoreMove = movePool.find(m => m.moveId === encoreTag.moveId);
|
const encoreMove = movePool.find(m => m.moveId === encoreTag.moveId);
|
||||||
@ -6208,6 +6238,7 @@ export class EnemyPokemon extends Pokemon {
|
|||||||
return {
|
return {
|
||||||
move: encoreMove.moveId,
|
move: encoreMove.moveId,
|
||||||
targets: this.getNextTargets(encoreMove.moveId),
|
targets: this.getNextTargets(encoreMove.moveId),
|
||||||
|
useMode: MoveUseMode.NORMAL,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6215,7 +6246,7 @@ export class EnemyPokemon extends Pokemon {
|
|||||||
// No enemy should spawn with this AI type in-game
|
// No enemy should spawn with this AI type in-game
|
||||||
case AiType.RANDOM: {
|
case AiType.RANDOM: {
|
||||||
const moveId = movePool[globalScene.randBattleSeedInt(movePool.length)].moveId;
|
const moveId = movePool[globalScene.randBattleSeedInt(movePool.length)].moveId;
|
||||||
return { move: moveId, targets: this.getNextTargets(moveId) };
|
return { move: moveId, targets: this.getNextTargets(moveId), useMode: MoveUseMode.NORMAL };
|
||||||
}
|
}
|
||||||
case AiType.SMART_RANDOM:
|
case AiType.SMART_RANDOM:
|
||||||
case AiType.SMART: {
|
case AiType.SMART: {
|
||||||
@ -6384,14 +6415,20 @@ export class EnemyPokemon extends Pokemon {
|
|||||||
r,
|
r,
|
||||||
sortedMovePool.map(m => m.getName()),
|
sortedMovePool.map(m => m.getName()),
|
||||||
);
|
);
|
||||||
return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] };
|
return {
|
||||||
|
move: sortedMovePool[r]!.moveId,
|
||||||
|
targets: moveTargets[sortedMovePool[r]!.moveId],
|
||||||
|
useMode: MoveUseMode.NORMAL,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No moves left means struggle
|
||||||
return {
|
return {
|
||||||
move: MoveId.STRUGGLE,
|
move: MoveId.STRUGGLE,
|
||||||
targets: this.getNextTargets(MoveId.STRUGGLE),
|
targets: this.getNextTargets(MoveId.STRUGGLE),
|
||||||
|
useMode: MoveUseMode.IGNORE_PP,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6737,10 +6774,9 @@ interface IllusionData {
|
|||||||
export interface TurnMove {
|
export interface TurnMove {
|
||||||
move: MoveId;
|
move: MoveId;
|
||||||
targets: BattlerIndex[];
|
targets: BattlerIndex[];
|
||||||
|
useMode: MoveUseMode;
|
||||||
result?: MoveResult;
|
result?: MoveResult;
|
||||||
virtual?: boolean;
|
|
||||||
turn?: number;
|
turn?: number;
|
||||||
ignorePP?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttackMoveResult {
|
export interface AttackMoveResult {
|
||||||
@ -6759,6 +6795,12 @@ export interface AttackMoveResult {
|
|||||||
export class PokemonSummonData {
|
export class PokemonSummonData {
|
||||||
/** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */
|
/** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */
|
||||||
public statStages: number[] = [0, 0, 0, 0, 0, 0, 0];
|
public statStages: number[] = [0, 0, 0, 0, 0, 0, 0];
|
||||||
|
/**
|
||||||
|
* A queue of moves yet to be executed, used by charging, recharging and frenzy moves.
|
||||||
|
* So long as this array is nonempty, this Pokemon's corresponding `CommandPhase` will be skipped over entirely
|
||||||
|
* in favor of using the queued move.
|
||||||
|
* TODO: Clean up a lot of the code surrounding the move queue.
|
||||||
|
*/
|
||||||
public moveQueue: TurnMove[] = [];
|
public moveQueue: TurnMove[] = [];
|
||||||
public tags: BattlerTag[] = [];
|
public tags: BattlerTag[] = [];
|
||||||
public abilitySuppressed = false;
|
public abilitySuppressed = false;
|
||||||
@ -6878,7 +6920,6 @@ export class PokemonWaveData {
|
|||||||
* Resets at the start of a new turn, as well as on switch.
|
* Resets at the start of a new turn, as well as on switch.
|
||||||
*/
|
*/
|
||||||
export class PokemonTurnData {
|
export class PokemonTurnData {
|
||||||
public flinched = false;
|
|
||||||
public acted = false;
|
public acted = false;
|
||||||
/** How many times the current move should hit the target(s) */
|
/** How many times the current move should hit the target(s) */
|
||||||
public hitCount = 0;
|
public hitCount = 0;
|
||||||
@ -6900,8 +6941,9 @@ export class PokemonTurnData {
|
|||||||
public failedRunAway = false;
|
public failedRunAway = false;
|
||||||
public joinedRound = false;
|
public joinedRound = false;
|
||||||
/**
|
/**
|
||||||
|
* The amount of times this Pokemon has acted again and used a move in the current turn.
|
||||||
* Used to make sure multi-hits occur properly when the user is
|
* Used to make sure multi-hits occur properly when the user is
|
||||||
* forced to act again in the same turn
|
* forced to act again in the same turn, and **must be incremented** by any effects that grant extra actions.
|
||||||
*/
|
*/
|
||||||
public extraTurns = 0;
|
public extraTurns = 0;
|
||||||
/**
|
/**
|
||||||
|
@ -21,6 +21,8 @@ import { initVouchers } from "#app/system/voucher";
|
|||||||
import { BiomeId } from "#enums/biome-id";
|
import { BiomeId } from "#enums/biome-id";
|
||||||
import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters";
|
import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
import { timedEventManager } from "./global-event-manager";
|
import { timedEventManager } from "./global-event-manager";
|
||||||
|
import { initModifierPools } from "./modifier/init-modifier-pools";
|
||||||
|
import { initModifierTypes } from "./modifier/modifier-type";
|
||||||
|
|
||||||
export class LoadingScene extends SceneBase {
|
export class LoadingScene extends SceneBase {
|
||||||
public static readonly KEY = "loading";
|
public static readonly KEY = "loading";
|
||||||
@ -363,6 +365,9 @@ export class LoadingScene extends SceneBase {
|
|||||||
|
|
||||||
this.loadLoadingScreen();
|
this.loadLoadingScreen();
|
||||||
|
|
||||||
|
initModifierTypes();
|
||||||
|
initModifierPools();
|
||||||
|
|
||||||
initAchievements();
|
initAchievements();
|
||||||
initVouchers();
|
initVouchers();
|
||||||
initStatsKeys();
|
initStatsKeys();
|
||||||
|
854
src/modifier/init-modifier-pools.ts
Normal file
854
src/modifier/init-modifier-pools.ts
Normal file
@ -0,0 +1,854 @@
|
|||||||
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
import {
|
||||||
|
dailyStarterModifierPool,
|
||||||
|
enemyBuffModifierPool,
|
||||||
|
modifierPool,
|
||||||
|
trainerModifierPool,
|
||||||
|
wildModifierPool,
|
||||||
|
} from "#app/modifier/modifier-pools";
|
||||||
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import { DoubleBattleChanceBoosterModifier, SpeciesCritBoosterModifier, TurnStatusEffectModifier } from "./modifier";
|
||||||
|
import { WeightedModifierType } from "./modifier-type";
|
||||||
|
import { ModifierTier } from "../enums/modifier-tier";
|
||||||
|
import type { WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types";
|
||||||
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
|
import { PokeballType } from "#enums/pokeball";
|
||||||
|
import { BerryModifier } from "./modifier";
|
||||||
|
import { BerryType } from "#enums/berry-type";
|
||||||
|
import { SpeciesId } from "#enums/species-id";
|
||||||
|
import { timedEventManager } from "#app/global-event-manager";
|
||||||
|
import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
|
||||||
|
import { Unlockables } from "#enums/unlockables";
|
||||||
|
import { isNullOrUndefined } from "#app/utils/common";
|
||||||
|
import { MoveId } from "#enums/move-id";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import { AbilityId } from "#enums/ability-id";
|
||||||
|
import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball";
|
||||||
|
// biome-ignore lint/correctness/noUnusedImports: This is used in a tsdoc comment
|
||||||
|
import type { initModifierTypes } from "./modifier-type";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the wild modifier pool
|
||||||
|
*/
|
||||||
|
function initWildModifierPool() {
|
||||||
|
wildModifierPool[ModifierTier.COMMON] = [new WeightedModifierType(modifierTypes.BERRY, 1)].map(m => {
|
||||||
|
m.setTier(ModifierTier.COMMON);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
wildModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1)].map(m => {
|
||||||
|
m.setTier(ModifierTier.GREAT);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
wildModifierPool[ModifierTier.ULTRA] = [
|
||||||
|
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10),
|
||||||
|
new WeightedModifierType(modifierTypes.WHITE_HERB, 0),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.ULTRA);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
wildModifierPool[ModifierTier.ROGUE] = [new WeightedModifierType(modifierTypes.LUCKY_EGG, 4)].map(m => {
|
||||||
|
m.setTier(ModifierTier.ROGUE);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
wildModifierPool[ModifierTier.MASTER] = [new WeightedModifierType(modifierTypes.GOLDEN_EGG, 1)].map(m => {
|
||||||
|
m.setTier(ModifierTier.MASTER);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the common modifier pool
|
||||||
|
*/
|
||||||
|
function initCommonModifierPool() {
|
||||||
|
modifierPool[ModifierTier.COMMON] = [
|
||||||
|
new WeightedModifierType(modifierTypes.POKEBALL, () => (hasMaximumBalls(PokeballType.POKEBALL) ? 0 : 6), 6),
|
||||||
|
new WeightedModifierType(modifierTypes.RARE_CANDY, 2),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.POTION,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
const thresholdPartyMemberCount = Math.min(
|
||||||
|
party.filter(p => p.getInverseHp() >= 10 && p.getHpRatio() <= 0.875 && !p.isFainted()).length,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
return thresholdPartyMemberCount * 3;
|
||||||
|
},
|
||||||
|
9,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.SUPER_POTION,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
const thresholdPartyMemberCount = Math.min(
|
||||||
|
party.filter(p => p.getInverseHp() >= 25 && p.getHpRatio() <= 0.75 && !p.isFainted()).length,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
return thresholdPartyMemberCount;
|
||||||
|
},
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.ETHER,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
const thresholdPartyMemberCount = Math.min(
|
||||||
|
party.filter(
|
||||||
|
p =>
|
||||||
|
p.hp &&
|
||||||
|
!p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) &&
|
||||||
|
p
|
||||||
|
.getMoveset()
|
||||||
|
.filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2))
|
||||||
|
.length,
|
||||||
|
).length,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
return thresholdPartyMemberCount * 3;
|
||||||
|
},
|
||||||
|
9,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.MAX_ETHER,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
const thresholdPartyMemberCount = Math.min(
|
||||||
|
party.filter(
|
||||||
|
p =>
|
||||||
|
p.hp &&
|
||||||
|
!p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) &&
|
||||||
|
p
|
||||||
|
.getMoveset()
|
||||||
|
.filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2))
|
||||||
|
.length,
|
||||||
|
).length,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
return thresholdPartyMemberCount;
|
||||||
|
},
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)),
|
||||||
|
new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4),
|
||||||
|
new WeightedModifierType(modifierTypes.BERRY, 2),
|
||||||
|
new WeightedModifierType(modifierTypes.TM_COMMON, 2),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.COMMON);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Great modifier pool
|
||||||
|
*/
|
||||||
|
function initGreatModifierPool() {
|
||||||
|
modifierPool[ModifierTier.GREAT] = [
|
||||||
|
new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6),
|
||||||
|
new WeightedModifierType(modifierTypes.PP_UP, 2),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.FULL_HEAL,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
const statusEffectPartyMemberCount = Math.min(
|
||||||
|
party.filter(
|
||||||
|
p =>
|
||||||
|
p.hp &&
|
||||||
|
!!p.status &&
|
||||||
|
!p.getHeldItems().some(i => {
|
||||||
|
if (i instanceof TurnStatusEffectModifier) {
|
||||||
|
return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}),
|
||||||
|
).length,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
return statusEffectPartyMemberCount * 6;
|
||||||
|
},
|
||||||
|
18,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.REVIVE,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3);
|
||||||
|
return faintedPartyMemberCount * 9;
|
||||||
|
},
|
||||||
|
27,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.MAX_REVIVE,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3);
|
||||||
|
return faintedPartyMemberCount * 3;
|
||||||
|
},
|
||||||
|
9,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.SACRED_ASH,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0;
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.HYPER_POTION,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
const thresholdPartyMemberCount = Math.min(
|
||||||
|
party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.625 && !p.isFainted()).length,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
return thresholdPartyMemberCount * 3;
|
||||||
|
},
|
||||||
|
9,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.MAX_POTION,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
const thresholdPartyMemberCount = Math.min(
|
||||||
|
party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
return thresholdPartyMemberCount;
|
||||||
|
},
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.FULL_RESTORE,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
const statusEffectPartyMemberCount = Math.min(
|
||||||
|
party.filter(
|
||||||
|
p =>
|
||||||
|
p.hp &&
|
||||||
|
!!p.status &&
|
||||||
|
!p.getHeldItems().some(i => {
|
||||||
|
if (i instanceof TurnStatusEffectModifier) {
|
||||||
|
return (i as TurnStatusEffectModifier).getStatusEffect() === p.status?.effect;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}),
|
||||||
|
).length,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
const thresholdPartyMemberCount = Math.floor(
|
||||||
|
(Math.min(party.filter(p => p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5 && !p.isFainted()).length, 3) +
|
||||||
|
statusEffectPartyMemberCount) /
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
return thresholdPartyMemberCount;
|
||||||
|
},
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.ELIXIR,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
const thresholdPartyMemberCount = Math.min(
|
||||||
|
party.filter(
|
||||||
|
p =>
|
||||||
|
p.hp &&
|
||||||
|
!p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) &&
|
||||||
|
p
|
||||||
|
.getMoveset()
|
||||||
|
.filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2))
|
||||||
|
.length,
|
||||||
|
).length,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
return thresholdPartyMemberCount * 3;
|
||||||
|
},
|
||||||
|
9,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.MAX_ELIXIR,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
const thresholdPartyMemberCount = Math.min(
|
||||||
|
party.filter(
|
||||||
|
p =>
|
||||||
|
p.hp &&
|
||||||
|
!p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) &&
|
||||||
|
p
|
||||||
|
.getMoveset()
|
||||||
|
.filter(m => m.ppUsed && m.getMovePp() - m.ppUsed <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2))
|
||||||
|
.length,
|
||||||
|
).length,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
return thresholdPartyMemberCount;
|
||||||
|
},
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(modifierTypes.DIRE_HIT, 4),
|
||||||
|
new WeightedModifierType(modifierTypes.SUPER_LURE, lureWeightFunc(15, 4)),
|
||||||
|
new WeightedModifierType(modifierTypes.NUGGET, skipInLastClassicWaveOrDefault(5)),
|
||||||
|
new WeightedModifierType(modifierTypes.SPECIES_STAT_BOOSTER, 4),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.EVOLUTION_ITEM,
|
||||||
|
() => {
|
||||||
|
return Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15), 8);
|
||||||
|
},
|
||||||
|
8,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.MAP,
|
||||||
|
() => (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex < 180 ? 2 : 0),
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2),
|
||||||
|
new WeightedModifierType(modifierTypes.TM_GREAT, 3),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.MEMORY_MUSHROOM,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
if (!party.find(p => p.getLearnableLevelMoves().length)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const highestPartyLevel = party
|
||||||
|
.map(p => p.level)
|
||||||
|
.reduce((highestLevel: number, level: number) => Math.max(highestLevel, level), 1);
|
||||||
|
return Math.min(Math.ceil(highestPartyLevel / 20), 4);
|
||||||
|
},
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3),
|
||||||
|
new WeightedModifierType(modifierTypes.TERA_SHARD, (party: Pokemon[]) =>
|
||||||
|
party.filter(
|
||||||
|
p =>
|
||||||
|
!(p.hasSpecies(SpeciesId.TERAPAGOS) || p.hasSpecies(SpeciesId.OGERPON) || p.hasSpecies(SpeciesId.SHEDINJA)),
|
||||||
|
).length > 0
|
||||||
|
? 1
|
||||||
|
: 0,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.DNA_SPLICERS,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
if (party.filter(p => !p.fusionSpecies).length > 1) {
|
||||||
|
if (globalScene.gameMode.isSplicedOnly) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
if (globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.VOUCHER,
|
||||||
|
(_party: Pokemon[], rerollCount: number) => (!globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.GREAT);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Ultra modifier pool
|
||||||
|
*/
|
||||||
|
function initUltraModifierPool() {
|
||||||
|
modifierPool[ModifierTier.ULTRA] = [
|
||||||
|
new WeightedModifierType(modifierTypes.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15),
|
||||||
|
new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)),
|
||||||
|
new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)),
|
||||||
|
new WeightedModifierType(modifierTypes.PP_MAX, 3),
|
||||||
|
new WeightedModifierType(modifierTypes.MINT, 4),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.RARE_EVOLUTION_ITEM,
|
||||||
|
() => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15) * 4, 32),
|
||||||
|
32,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.FORM_CHANGE_ITEM,
|
||||||
|
() => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6,
|
||||||
|
24,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)),
|
||||||
|
new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => {
|
||||||
|
const { gameMode, gameData } = globalScene;
|
||||||
|
if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) {
|
||||||
|
return party.some(p => {
|
||||||
|
// Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd
|
||||||
|
if (
|
||||||
|
!p.isMax() &&
|
||||||
|
(p.getSpeciesForm(true).speciesId in pokemonEvolutions ||
|
||||||
|
(p.isFusion() && p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions))
|
||||||
|
) {
|
||||||
|
// Check if Pokemon is already holding an Eviolite
|
||||||
|
return !p.getHeldItems().some(i => i.type.id === "EVIOLITE");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
? 10
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}),
|
||||||
|
new WeightedModifierType(modifierTypes.RARE_SPECIES_STAT_BOOSTER, 12),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.LEEK,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
const checkedSpecies = [SpeciesId.FARFETCHD, SpeciesId.GALAR_FARFETCHD, SpeciesId.SIRFETCHD];
|
||||||
|
// If a party member doesn't already have a Leek and is one of the relevant species, Leek can appear
|
||||||
|
return party.some(
|
||||||
|
p =>
|
||||||
|
!p.getHeldItems().some(i => i instanceof SpeciesCritBoosterModifier) &&
|
||||||
|
(checkedSpecies.includes(p.getSpeciesForm(true).speciesId) ||
|
||||||
|
(p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId))),
|
||||||
|
)
|
||||||
|
? 12
|
||||||
|
: 0;
|
||||||
|
},
|
||||||
|
12,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.TOXIC_ORB,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
return party.some(p => {
|
||||||
|
const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB");
|
||||||
|
|
||||||
|
if (!isHoldingOrb) {
|
||||||
|
const moveset = p
|
||||||
|
.getMoveset(true)
|
||||||
|
.filter(m => !isNullOrUndefined(m))
|
||||||
|
.map(m => m.moveId);
|
||||||
|
const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true);
|
||||||
|
|
||||||
|
// Moves that take advantage of obtaining the actual status effect
|
||||||
|
const hasStatusMoves = [MoveId.FACADE, MoveId.PSYCHO_SHIFT].some(m => moveset.includes(m));
|
||||||
|
// Moves that take advantage of being able to give the target a status orb
|
||||||
|
// TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented
|
||||||
|
const hasItemMoves = [
|
||||||
|
/* MoveId.TRICK, MoveId.FLING, MoveId.SWITCHEROO */
|
||||||
|
].some(m => moveset.includes(m));
|
||||||
|
|
||||||
|
if (canSetStatus) {
|
||||||
|
// Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb
|
||||||
|
const hasGeneralAbility = [
|
||||||
|
AbilityId.QUICK_FEET,
|
||||||
|
AbilityId.GUTS,
|
||||||
|
AbilityId.MARVEL_SCALE,
|
||||||
|
AbilityId.MAGIC_GUARD,
|
||||||
|
].some(a => p.hasAbility(a, false, true));
|
||||||
|
const hasSpecificAbility = [AbilityId.TOXIC_BOOST, AbilityId.POISON_HEAL].some(a =>
|
||||||
|
p.hasAbility(a, false, true),
|
||||||
|
);
|
||||||
|
const hasOppositeAbility = [AbilityId.FLARE_BOOST].some(a => p.hasAbility(a, false, true));
|
||||||
|
|
||||||
|
return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves;
|
||||||
|
}
|
||||||
|
return hasItemMoves;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
? 10
|
||||||
|
: 0;
|
||||||
|
},
|
||||||
|
10,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.FLAME_ORB,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
return party.some(p => {
|
||||||
|
const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB");
|
||||||
|
|
||||||
|
if (!isHoldingOrb) {
|
||||||
|
const moveset = p
|
||||||
|
.getMoveset(true)
|
||||||
|
.filter(m => !isNullOrUndefined(m))
|
||||||
|
.map(m => m.moveId);
|
||||||
|
const canSetStatus = p.canSetStatus(StatusEffect.BURN, true, true, null, true);
|
||||||
|
|
||||||
|
// Moves that take advantage of obtaining the actual status effect
|
||||||
|
const hasStatusMoves = [MoveId.FACADE, MoveId.PSYCHO_SHIFT].some(m => moveset.includes(m));
|
||||||
|
// Moves that take advantage of being able to give the target a status orb
|
||||||
|
// TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented
|
||||||
|
const hasItemMoves = [
|
||||||
|
/* MoveId.TRICK, MoveId.FLING, MoveId.SWITCHEROO */
|
||||||
|
].some(m => moveset.includes(m));
|
||||||
|
|
||||||
|
if (canSetStatus) {
|
||||||
|
// Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb
|
||||||
|
const hasGeneralAbility = [
|
||||||
|
AbilityId.QUICK_FEET,
|
||||||
|
AbilityId.GUTS,
|
||||||
|
AbilityId.MARVEL_SCALE,
|
||||||
|
AbilityId.MAGIC_GUARD,
|
||||||
|
].some(a => p.hasAbility(a, false, true));
|
||||||
|
const hasSpecificAbility = [AbilityId.FLARE_BOOST].some(a => p.hasAbility(a, false, true));
|
||||||
|
const hasOppositeAbility = [AbilityId.TOXIC_BOOST, AbilityId.POISON_HEAL].some(a =>
|
||||||
|
p.hasAbility(a, false, true),
|
||||||
|
);
|
||||||
|
|
||||||
|
return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves;
|
||||||
|
}
|
||||||
|
return hasItemMoves;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
? 10
|
||||||
|
: 0;
|
||||||
|
},
|
||||||
|
10,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.MYSTICAL_ROCK,
|
||||||
|
(party: Pokemon[]) => {
|
||||||
|
return party.some(p => {
|
||||||
|
let isHoldingMax = false;
|
||||||
|
for (const i of p.getHeldItems()) {
|
||||||
|
if (i.type.id === "MYSTICAL_ROCK") {
|
||||||
|
isHoldingMax = i.getStackCount() === i.getMaxStackCount();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isHoldingMax) {
|
||||||
|
const moveset = p.getMoveset(true).map(m => m.moveId);
|
||||||
|
|
||||||
|
const hasAbility = [
|
||||||
|
AbilityId.DROUGHT,
|
||||||
|
AbilityId.ORICHALCUM_PULSE,
|
||||||
|
AbilityId.DRIZZLE,
|
||||||
|
AbilityId.SAND_STREAM,
|
||||||
|
AbilityId.SAND_SPIT,
|
||||||
|
AbilityId.SNOW_WARNING,
|
||||||
|
AbilityId.ELECTRIC_SURGE,
|
||||||
|
AbilityId.HADRON_ENGINE,
|
||||||
|
AbilityId.PSYCHIC_SURGE,
|
||||||
|
AbilityId.GRASSY_SURGE,
|
||||||
|
AbilityId.SEED_SOWER,
|
||||||
|
AbilityId.MISTY_SURGE,
|
||||||
|
].some(a => p.hasAbility(a, false, true));
|
||||||
|
|
||||||
|
const hasMoves = [
|
||||||
|
MoveId.SUNNY_DAY,
|
||||||
|
MoveId.RAIN_DANCE,
|
||||||
|
MoveId.SANDSTORM,
|
||||||
|
MoveId.SNOWSCAPE,
|
||||||
|
MoveId.HAIL,
|
||||||
|
MoveId.CHILLY_RECEPTION,
|
||||||
|
MoveId.ELECTRIC_TERRAIN,
|
||||||
|
MoveId.PSYCHIC_TERRAIN,
|
||||||
|
MoveId.GRASSY_TERRAIN,
|
||||||
|
MoveId.MISTY_TERRAIN,
|
||||||
|
].some(m => moveset.includes(m));
|
||||||
|
|
||||||
|
return hasAbility || hasMoves;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
? 10
|
||||||
|
: 0;
|
||||||
|
},
|
||||||
|
10,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(modifierTypes.REVIVER_SEED, 4),
|
||||||
|
new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)),
|
||||||
|
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9),
|
||||||
|
new WeightedModifierType(modifierTypes.TM_ULTRA, 11),
|
||||||
|
new WeightedModifierType(modifierTypes.RARER_CANDY, 4),
|
||||||
|
new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)),
|
||||||
|
new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)),
|
||||||
|
new WeightedModifierType(modifierTypes.EXP_CHARM, skipInLastClassicWaveOrDefault(8)),
|
||||||
|
new WeightedModifierType(modifierTypes.EXP_SHARE, skipInLastClassicWaveOrDefault(10)),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.TERA_ORB,
|
||||||
|
() =>
|
||||||
|
!globalScene.gameMode.isClassic
|
||||||
|
? Math.min(Math.max(Math.floor(globalScene.currentBattle.waveIndex / 50) * 2, 1), 4)
|
||||||
|
: 0,
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(modifierTypes.QUICK_CLAW, 3),
|
||||||
|
new WeightedModifierType(modifierTypes.WIDE_LENS, 7),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.ULTRA);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initRogueModifierPool() {
|
||||||
|
modifierPool[ModifierTier.ROGUE] = [
|
||||||
|
new WeightedModifierType(modifierTypes.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL) ? 0 : 16), 16),
|
||||||
|
new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)),
|
||||||
|
new WeightedModifierType(modifierTypes.LEFTOVERS, 3),
|
||||||
|
new WeightedModifierType(modifierTypes.SHELL_BELL, 3),
|
||||||
|
new WeightedModifierType(modifierTypes.BERRY_POUCH, 4),
|
||||||
|
new WeightedModifierType(modifierTypes.GRIP_CLAW, 5),
|
||||||
|
new WeightedModifierType(modifierTypes.SCOPE_LENS, 4),
|
||||||
|
new WeightedModifierType(modifierTypes.BATON, 2),
|
||||||
|
new WeightedModifierType(modifierTypes.SOUL_DEW, 7),
|
||||||
|
new WeightedModifierType(modifierTypes.CATCHING_CHARM, () => (!globalScene.gameMode.isClassic ? 4 : 0), 4),
|
||||||
|
new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)),
|
||||||
|
new WeightedModifierType(modifierTypes.FOCUS_BAND, 5),
|
||||||
|
new WeightedModifierType(modifierTypes.KINGS_ROCK, 3),
|
||||||
|
new WeightedModifierType(modifierTypes.LOCK_CAPSULE, () => (globalScene.gameMode.isClassic ? 0 : 3)),
|
||||||
|
new WeightedModifierType(modifierTypes.SUPER_EXP_CHARM, skipInLastClassicWaveOrDefault(8)),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.RARE_FORM_CHANGE_ITEM,
|
||||||
|
() => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6,
|
||||||
|
24,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.MEGA_BRACELET,
|
||||||
|
() => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9,
|
||||||
|
36,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.DYNAMAX_BAND,
|
||||||
|
() => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9,
|
||||||
|
36,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.VOUCHER_PLUS,
|
||||||
|
(_party: Pokemon[], rerollCount: number) =>
|
||||||
|
!globalScene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0,
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.ROGUE);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Master modifier pool
|
||||||
|
*/
|
||||||
|
function initMasterModifierPool() {
|
||||||
|
modifierPool[ModifierTier.MASTER] = [
|
||||||
|
new WeightedModifierType(modifierTypes.MASTER_BALL, () => (hasMaximumBalls(PokeballType.MASTER_BALL) ? 0 : 24), 24),
|
||||||
|
new WeightedModifierType(modifierTypes.SHINY_CHARM, 14),
|
||||||
|
new WeightedModifierType(modifierTypes.HEALING_CHARM, 18),
|
||||||
|
new WeightedModifierType(modifierTypes.MULTI_LENS, 18),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.VOUCHER_PREMIUM,
|
||||||
|
(_party: Pokemon[], rerollCount: number) =>
|
||||||
|
!globalScene.gameMode.isDaily && !globalScene.gameMode.isEndless && !globalScene.gameMode.isSplicedOnly
|
||||||
|
? Math.max(5 - rerollCount * 2, 0)
|
||||||
|
: 0,
|
||||||
|
5,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.DNA_SPLICERS,
|
||||||
|
(party: Pokemon[]) =>
|
||||||
|
!(globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) &&
|
||||||
|
!globalScene.gameMode.isSplicedOnly &&
|
||||||
|
party.filter(p => !p.fusionSpecies).length > 1
|
||||||
|
? 24
|
||||||
|
: 0,
|
||||||
|
24,
|
||||||
|
),
|
||||||
|
new WeightedModifierType(
|
||||||
|
modifierTypes.MINI_BLACK_HOLE,
|
||||||
|
() =>
|
||||||
|
globalScene.gameMode.isDaily ||
|
||||||
|
(!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE))
|
||||||
|
? 1
|
||||||
|
: 0,
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.MASTER);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initTrainerModifierPool() {
|
||||||
|
trainerModifierPool[ModifierTier.COMMON] = [
|
||||||
|
new WeightedModifierType(modifierTypes.BERRY, 8),
|
||||||
|
new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.COMMON);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
trainerModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3)].map(m => {
|
||||||
|
m.setTier(ModifierTier.GREAT);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
trainerModifierPool[ModifierTier.ULTRA] = [
|
||||||
|
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10),
|
||||||
|
new WeightedModifierType(modifierTypes.WHITE_HERB, 0),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.ULTRA);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
trainerModifierPool[ModifierTier.ROGUE] = [
|
||||||
|
new WeightedModifierType(modifierTypes.FOCUS_BAND, 2),
|
||||||
|
new WeightedModifierType(modifierTypes.LUCKY_EGG, 4),
|
||||||
|
new WeightedModifierType(modifierTypes.QUICK_CLAW, 1),
|
||||||
|
new WeightedModifierType(modifierTypes.GRIP_CLAW, 1),
|
||||||
|
new WeightedModifierType(modifierTypes.WIDE_LENS, 1),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.ROGUE);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
trainerModifierPool[ModifierTier.MASTER] = [
|
||||||
|
new WeightedModifierType(modifierTypes.KINGS_ROCK, 1),
|
||||||
|
new WeightedModifierType(modifierTypes.LEFTOVERS, 1),
|
||||||
|
new WeightedModifierType(modifierTypes.SHELL_BELL, 1),
|
||||||
|
new WeightedModifierType(modifierTypes.SCOPE_LENS, 1),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.MASTER);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the enemy buff modifier pool
|
||||||
|
*/
|
||||||
|
function initEnemyBuffModifierPool() {
|
||||||
|
enemyBuffModifierPool[ModifierTier.COMMON] = [
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 9),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 9),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_ATTACK_POISON_CHANCE, 3),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_ATTACK_PARALYZE_CHANCE, 3),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_ATTACK_BURN_CHANCE, 3),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 9),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 4),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.COMMON);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
enemyBuffModifierPool[ModifierTier.GREAT] = [
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 5),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 5),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 5),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 5),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 1),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.GREAT);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
enemyBuffModifierPool[ModifierTier.ULTRA] = [
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_BOOSTER, 10),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_DAMAGE_REDUCTION, 10),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_HEAL, 10),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_STATUS_EFFECT_HEAL_CHANCE, 10),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_ENDURE_CHANCE, 10),
|
||||||
|
new WeightedModifierType(modifierTypes.ENEMY_FUSED_CHANCE, 5),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.ULTRA);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
enemyBuffModifierPool[ModifierTier.ROGUE] = [].map((m: WeightedModifierType) => {
|
||||||
|
m.setTier(ModifierTier.ROGUE);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
enemyBuffModifierPool[ModifierTier.MASTER] = [].map((m: WeightedModifierType) => {
|
||||||
|
m.setTier(ModifierTier.MASTER);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the daily starter modifier pool
|
||||||
|
*/
|
||||||
|
function initDailyStarterModifierPool() {
|
||||||
|
dailyStarterModifierPool[ModifierTier.COMMON] = [
|
||||||
|
new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 1),
|
||||||
|
new WeightedModifierType(modifierTypes.BERRY, 3),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.COMMON);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
dailyStarterModifierPool[ModifierTier.GREAT] = [new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 5)].map(
|
||||||
|
m => {
|
||||||
|
m.setTier(ModifierTier.GREAT);
|
||||||
|
return m;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
dailyStarterModifierPool[ModifierTier.ULTRA] = [
|
||||||
|
new WeightedModifierType(modifierTypes.REVIVER_SEED, 4),
|
||||||
|
new WeightedModifierType(modifierTypes.SOOTHE_BELL, 1),
|
||||||
|
new WeightedModifierType(modifierTypes.SOUL_DEW, 1),
|
||||||
|
new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, 1),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.ULTRA);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
dailyStarterModifierPool[ModifierTier.ROGUE] = [
|
||||||
|
new WeightedModifierType(modifierTypes.GRIP_CLAW, 5),
|
||||||
|
new WeightedModifierType(modifierTypes.BATON, 2),
|
||||||
|
new WeightedModifierType(modifierTypes.FOCUS_BAND, 5),
|
||||||
|
new WeightedModifierType(modifierTypes.QUICK_CLAW, 3),
|
||||||
|
new WeightedModifierType(modifierTypes.KINGS_ROCK, 3),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.ROGUE);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
dailyStarterModifierPool[ModifierTier.MASTER] = [
|
||||||
|
new WeightedModifierType(modifierTypes.LEFTOVERS, 1),
|
||||||
|
new WeightedModifierType(modifierTypes.SHELL_BELL, 1),
|
||||||
|
].map(m => {
|
||||||
|
m.setTier(ModifierTier.MASTER);
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize {@linkcode modifierPool} with the initial set of modifier types.
|
||||||
|
* {@linkcode initModifierTypes} MUST be called before this function.
|
||||||
|
*/
|
||||||
|
export function initModifierPools() {
|
||||||
|
// The modifier pools the player chooses from during modifier selection
|
||||||
|
initCommonModifierPool();
|
||||||
|
initGreatModifierPool();
|
||||||
|
initUltraModifierPool();
|
||||||
|
initRogueModifierPool();
|
||||||
|
initMasterModifierPool();
|
||||||
|
|
||||||
|
// Modifier pools for specific scenarios
|
||||||
|
initWildModifierPool();
|
||||||
|
initTrainerModifierPool();
|
||||||
|
initEnemyBuffModifierPool();
|
||||||
|
initDailyStarterModifierPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on
|
||||||
|
* classic and skip an ModifierType if current wave is greater or equal to the one passed down
|
||||||
|
* @param wave - Wave where we should stop showing the modifier
|
||||||
|
* @param defaultWeight - ModifierType default weight
|
||||||
|
* @returns A WeightedModifierTypeWeightFunc
|
||||||
|
*/
|
||||||
|
function skipInClassicAfterWave(wave: number, defaultWeight: number): WeightedModifierTypeWeightFunc {
|
||||||
|
return () => {
|
||||||
|
const gameMode = globalScene.gameMode;
|
||||||
|
const currentWave = globalScene.currentBattle.waveIndex;
|
||||||
|
return gameMode.isClassic && currentWave >= wave ? 0 : defaultWeight;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* High order function that returns a WeightedModifierTypeWeightFunc that will only be applied on
|
||||||
|
* classic and it will skip a ModifierType if it is the last wave pull.
|
||||||
|
* @param defaultWeight ModifierType default weight
|
||||||
|
* @returns A WeightedModifierTypeWeightFunc
|
||||||
|
*/
|
||||||
|
function skipInLastClassicWaveOrDefault(defaultWeight: number): WeightedModifierTypeWeightFunc {
|
||||||
|
return skipInClassicAfterWave(199, defaultWeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* High order function that returns a WeightedModifierTypeWeightFunc to ensure Lures don't spawn on Classic 199
|
||||||
|
* or if the lure still has over 60% of its duration left
|
||||||
|
* @param maxBattles The max battles the lure type in question lasts. 10 for green, 15 for Super, 30 for Max
|
||||||
|
* @param weight The desired weight for the lure when it does spawn
|
||||||
|
* @returns A WeightedModifierTypeWeightFunc
|
||||||
|
*/
|
||||||
|
function lureWeightFunc(maxBattles: number, weight: number): WeightedModifierTypeWeightFunc {
|
||||||
|
return () => {
|
||||||
|
const lures = globalScene.getModifiers(DoubleBattleChanceBoosterModifier);
|
||||||
|
return !(globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex === 199) &&
|
||||||
|
(lures.length === 0 ||
|
||||||
|
lures.filter(m => m.getMaxBattles() === maxBattles && m.getBattleCount() >= maxBattles * 0.6).length === 0)
|
||||||
|
? weight
|
||||||
|
: 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to check if the player has max of a given ball type in Classic
|
||||||
|
* @param ballType The {@linkcode PokeballType} being checked
|
||||||
|
* @returns boolean: true if the player has the maximum of a given ball type
|
||||||
|
*/
|
||||||
|
function hasMaximumBalls(ballType: PokeballType): boolean {
|
||||||
|
return globalScene.gameMode.isClassic && globalScene.pokeballCounts[ballType] >= MAX_PER_TYPE_POKEBALLS;
|
||||||
|
}
|
16
src/modifier/modifier-pools.ts
Normal file
16
src/modifier/modifier-pools.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Contains modifier pools for different contexts in the game.
|
||||||
|
* Can be safely imported without worrying about circular dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ModifierPool } from "#app/@types/modifier-types";
|
||||||
|
|
||||||
|
export const modifierPool: ModifierPool = {};
|
||||||
|
|
||||||
|
export const wildModifierPool: ModifierPool = {};
|
||||||
|
|
||||||
|
export const trainerModifierPool: ModifierPool = {};
|
||||||
|
|
||||||
|
export const enemyBuffModifierPool: ModifierPool = {};
|
||||||
|
|
||||||
|
export const dailyStarterModifierPool: ModifierPool = {};
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,13 @@
|
|||||||
import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
|
import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
|
||||||
import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry";
|
import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry";
|
||||||
import { getLevelTotalExp } from "#app/data/exp";
|
import { getLevelTotalExp } from "#app/data/exp";
|
||||||
import { allMoves } from "#app/data/data-lists";
|
import { allMoves, modifierTypes } from "#app/data/data-lists";
|
||||||
import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball";
|
import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball";
|
||||||
import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
||||||
import type { FormChangeItem } from "#enums/form-change-item";
|
import type { FormChangeItem } from "#enums/form-change-item";
|
||||||
import { getStatusEffectHealText } from "#app/data/status-effect";
|
import { getStatusEffectHealText } from "#app/data/status-effect";
|
||||||
import Pokemon, { type PlayerPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||||
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { LearnMoveType } from "#enums/learn-move-type";
|
import { LearnMoveType } from "#enums/learn-move-type";
|
||||||
@ -24,33 +25,26 @@ import { type PermanentStat, type TempBattleStat, BATTLE_STATS, Stat, TEMP_BATTL
|
|||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import type { PokemonType } from "#enums/pokemon-type";
|
import type { PokemonType } from "#enums/pokemon-type";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {
|
import type {
|
||||||
type DoubleBattleChanceBoosterModifierType,
|
DoubleBattleChanceBoosterModifierType,
|
||||||
type EvolutionItemModifierType,
|
EvolutionItemModifierType,
|
||||||
type FormChangeItemModifierType,
|
FormChangeItemModifierType,
|
||||||
type ModifierOverride,
|
ModifierOverride,
|
||||||
type ModifierType,
|
ModifierType,
|
||||||
type PokemonBaseStatTotalModifierType,
|
PokemonBaseStatTotalModifierType,
|
||||||
type PokemonExpBoosterModifierType,
|
PokemonExpBoosterModifierType,
|
||||||
type PokemonFriendshipBoosterModifierType,
|
PokemonFriendshipBoosterModifierType,
|
||||||
type PokemonMoveAccuracyBoosterModifierType,
|
PokemonMoveAccuracyBoosterModifierType,
|
||||||
type PokemonMultiHitModifierType,
|
PokemonMultiHitModifierType,
|
||||||
type TerastallizeModifierType,
|
TerastallizeModifierType,
|
||||||
type TmModifierType,
|
TmModifierType,
|
||||||
getModifierType,
|
|
||||||
ModifierTypeGenerator,
|
|
||||||
modifierTypes,
|
|
||||||
PokemonHeldItemModifierType,
|
|
||||||
} from "./modifier-type";
|
} from "./modifier-type";
|
||||||
|
import { getModifierType } from "#app/utils/modifier-utils";
|
||||||
import { Color, ShadowColor } from "#enums/color";
|
import { Color, ShadowColor } from "#enums/color";
|
||||||
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
|
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
|
||||||
import {
|
import { applyAbAttrs, applyPostItemLostAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyAbAttrs,
|
|
||||||
applyPostItemLostAbAttrs,
|
|
||||||
CommanderAbAttr,
|
|
||||||
PostItemLostAbAttr,
|
|
||||||
} from "#app/data/abilities/ability";
|
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import type { ModifierInstanceMap, ModifierString } from "#app/@types/modifier-types";
|
||||||
|
|
||||||
export type ModifierPredicate = (modifier: Modifier) => boolean;
|
export type ModifierPredicate = (modifier: Modifier) => boolean;
|
||||||
|
|
||||||
@ -164,6 +158,23 @@ export abstract class Modifier {
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether this modifier is of the given class
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Used to avoid requiring the caller to have imported the specific modifier class, avoiding circular dependencies.
|
||||||
|
*
|
||||||
|
* @param modifier - The modifier to check against
|
||||||
|
* @returns Whether the modiifer is an instance of the given type
|
||||||
|
*/
|
||||||
|
public is<T extends ModifierString>(modifier: T): this is ModifierInstanceMap[T] {
|
||||||
|
const targetModifier = ModifierClassMap[modifier];
|
||||||
|
if (!targetModifier) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this instanceof targetModifier;
|
||||||
|
}
|
||||||
|
|
||||||
match(_modifier: Modifier): boolean {
|
match(_modifier: Modifier): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -188,6 +199,11 @@ export abstract class PersistentModifier extends Modifier {
|
|||||||
public stackCount: number;
|
public stackCount: number;
|
||||||
public virtualStackCount: number;
|
public virtualStackCount: number;
|
||||||
|
|
||||||
|
/** This field does not exist at runtime and must not be used.
|
||||||
|
* Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called.
|
||||||
|
*/
|
||||||
|
private declare _: never;
|
||||||
|
|
||||||
constructor(type: ModifierType, stackCount = 1) {
|
constructor(type: ModifierType, stackCount = 1) {
|
||||||
super(type);
|
super(type);
|
||||||
this.stackCount = stackCount;
|
this.stackCount = stackCount;
|
||||||
@ -1593,7 +1609,7 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier {
|
|||||||
doBypassSpeed.value = true;
|
doBypassSpeed.value = true;
|
||||||
const isCommandFight =
|
const isCommandFight =
|
||||||
globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT;
|
globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT;
|
||||||
const hasQuickClaw = this.type instanceof PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW";
|
const hasQuickClaw = this.type.is("PokemonHeldItemModifierType") && this.type.id === "QUICK_CLAW";
|
||||||
|
|
||||||
if (isCommandFight && hasQuickClaw) {
|
if (isCommandFight && hasQuickClaw) {
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
@ -1877,7 +1893,7 @@ export class BerryModifier extends PokemonHeldItemModifier {
|
|||||||
|
|
||||||
// munch the berry and trigger unburden-like effects
|
// munch the berry and trigger unburden-like effects
|
||||||
getBerryEffectFunc(this.berryType)(pokemon);
|
getBerryEffectFunc(this.berryType)(pokemon);
|
||||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false);
|
applyPostItemLostAbAttrs("PostItemLostAbAttr", pokemon, false);
|
||||||
|
|
||||||
// Update berry eaten trackers for Belch, Harvest, Cud Chew, etc.
|
// Update berry eaten trackers for Belch, Harvest, Cud Chew, etc.
|
||||||
// Don't recover it if we proc berry pouch (no item duplication)
|
// Don't recover it if we proc berry pouch (no item duplication)
|
||||||
@ -1965,7 +1981,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
|
|||||||
// Reapply Commander on the Pokemon's side of the field, if applicable
|
// Reapply Commander on the Pokemon's side of the field, if applicable
|
||||||
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||||
for (const p of field) {
|
for (const p of field) {
|
||||||
applyAbAttrs(CommanderAbAttr, p, null, false);
|
applyAbAttrs("CommanderAbAttr", p, null, false);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -3215,7 +3231,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
|
|||||||
* @returns the opponents of the source {@linkcode Pokemon}
|
* @returns the opponents of the source {@linkcode Pokemon}
|
||||||
*/
|
*/
|
||||||
getTargets(pokemon?: Pokemon, ..._args: unknown[]): Pokemon[] {
|
getTargets(pokemon?: Pokemon, ..._args: unknown[]): Pokemon[] {
|
||||||
return pokemon instanceof Pokemon ? pokemon.getOpponents() : [];
|
return pokemon?.getOpponents?.() ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3787,7 +3803,7 @@ export function overrideModifiers(isPlayer = true): void {
|
|||||||
const modifierFunc = modifierTypes[item.name];
|
const modifierFunc = modifierTypes[item.name];
|
||||||
let modifierType: ModifierType | null = modifierFunc();
|
let modifierType: ModifierType | null = modifierFunc();
|
||||||
|
|
||||||
if (modifierType instanceof ModifierTypeGenerator) {
|
if (modifierType.is("ModifierTypeGenerator")) {
|
||||||
const pregenArgs = "type" in item && item.type !== null ? [item.type] : undefined;
|
const pregenArgs = "type" in item && item.type !== null ? [item.type] : undefined;
|
||||||
modifierType = modifierType.generateType([], pregenArgs);
|
modifierType = modifierType.generateType([], pregenArgs);
|
||||||
}
|
}
|
||||||
@ -3829,7 +3845,7 @@ export function overrideHeldItems(pokemon: Pokemon, isPlayer = true): void {
|
|||||||
let modifierType: ModifierType | null = modifierFunc();
|
let modifierType: ModifierType | null = modifierFunc();
|
||||||
const qty = item.count || 1;
|
const qty = item.count || 1;
|
||||||
|
|
||||||
if (modifierType instanceof ModifierTypeGenerator) {
|
if (modifierType.is("ModifierTypeGenerator")) {
|
||||||
const pregenArgs = "type" in item && item.type !== null ? [item.type] : undefined;
|
const pregenArgs = "type" in item && item.type !== null ? [item.type] : undefined;
|
||||||
modifierType = modifierType.generateType([], pregenArgs);
|
modifierType = modifierType.generateType([], pregenArgs);
|
||||||
}
|
}
|
||||||
@ -3847,3 +3863,102 @@ export function overrideHeldItems(pokemon: Pokemon, isPlayer = true): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private map from modifier strings to their constructors.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Used for {@linkcode Modifier.is} to check if a modifier is of a certain type without
|
||||||
|
* requiring modifier types to be imported in every file.
|
||||||
|
*/
|
||||||
|
const ModifierClassMap = Object.freeze({
|
||||||
|
PersistentModifier,
|
||||||
|
ConsumableModifier,
|
||||||
|
AddPokeballModifier,
|
||||||
|
AddVoucherModifier,
|
||||||
|
LapsingPersistentModifier,
|
||||||
|
DoubleBattleChanceBoosterModifier,
|
||||||
|
TempStatStageBoosterModifier,
|
||||||
|
TempCritBoosterModifier,
|
||||||
|
MapModifier,
|
||||||
|
MegaEvolutionAccessModifier,
|
||||||
|
GigantamaxAccessModifier,
|
||||||
|
TerastallizeAccessModifier,
|
||||||
|
PokemonHeldItemModifier,
|
||||||
|
LapsingPokemonHeldItemModifier,
|
||||||
|
BaseStatModifier,
|
||||||
|
EvoTrackerModifier,
|
||||||
|
PokemonBaseStatTotalModifier,
|
||||||
|
PokemonBaseStatFlatModifier,
|
||||||
|
PokemonIncrementingStatModifier,
|
||||||
|
StatBoosterModifier,
|
||||||
|
SpeciesStatBoosterModifier,
|
||||||
|
CritBoosterModifier,
|
||||||
|
SpeciesCritBoosterModifier,
|
||||||
|
AttackTypeBoosterModifier,
|
||||||
|
SurviveDamageModifier,
|
||||||
|
BypassSpeedChanceModifier,
|
||||||
|
FlinchChanceModifier,
|
||||||
|
TurnHealModifier,
|
||||||
|
TurnStatusEffectModifier,
|
||||||
|
HitHealModifier,
|
||||||
|
LevelIncrementBoosterModifier,
|
||||||
|
BerryModifier,
|
||||||
|
PreserveBerryModifier,
|
||||||
|
PokemonInstantReviveModifier,
|
||||||
|
ResetNegativeStatStageModifier,
|
||||||
|
FieldEffectModifier,
|
||||||
|
ConsumablePokemonModifier,
|
||||||
|
TerrastalizeModifier,
|
||||||
|
PokemonHpRestoreModifier,
|
||||||
|
PokemonStatusHealModifier,
|
||||||
|
ConsumablePokemonMoveModifier,
|
||||||
|
PokemonPpRestoreModifier,
|
||||||
|
PokemonAllMovePpRestoreModifier,
|
||||||
|
PokemonPpUpModifier,
|
||||||
|
PokemonNatureChangeModifier,
|
||||||
|
PokemonLevelIncrementModifier,
|
||||||
|
TmModifier,
|
||||||
|
RememberMoveModifier,
|
||||||
|
EvolutionItemModifier,
|
||||||
|
FusePokemonModifier,
|
||||||
|
MultipleParticipantExpBonusModifier,
|
||||||
|
HealingBoosterModifier,
|
||||||
|
ExpBoosterModifier,
|
||||||
|
PokemonExpBoosterModifier,
|
||||||
|
ExpShareModifier,
|
||||||
|
ExpBalanceModifier,
|
||||||
|
PokemonFriendshipBoosterModifier,
|
||||||
|
PokemonNatureWeightModifier,
|
||||||
|
PokemonMoveAccuracyBoosterModifier,
|
||||||
|
PokemonMultiHitModifier,
|
||||||
|
PokemonFormChangeItemModifier,
|
||||||
|
MoneyRewardModifier,
|
||||||
|
DamageMoneyRewardModifier,
|
||||||
|
MoneyInterestModifier,
|
||||||
|
HiddenAbilityRateBoosterModifier,
|
||||||
|
ShinyRateBoosterModifier,
|
||||||
|
CriticalCatchChanceBoosterModifier,
|
||||||
|
LockModifierTiersModifier,
|
||||||
|
HealShopCostModifier,
|
||||||
|
BoostBugSpawnModifier,
|
||||||
|
SwitchEffectTransferModifier,
|
||||||
|
HeldItemTransferModifier,
|
||||||
|
TurnHeldItemTransferModifier,
|
||||||
|
ContactHeldItemTransferChanceModifier,
|
||||||
|
IvScannerModifier,
|
||||||
|
ExtraModifierModifier,
|
||||||
|
TempExtraModifierModifier,
|
||||||
|
EnemyPersistentModifier,
|
||||||
|
EnemyDamageMultiplierModifier,
|
||||||
|
EnemyDamageBoosterModifier,
|
||||||
|
EnemyDamageReducerModifier,
|
||||||
|
EnemyTurnHealModifier,
|
||||||
|
EnemyAttackStatusEffectChanceModifier,
|
||||||
|
EnemyStatusEffectHealChanceModifier,
|
||||||
|
EnemyEndureChanceModifier,
|
||||||
|
EnemyFusionChanceModifier,
|
||||||
|
MoneyMultiplierModifier,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ModifierConstructorMap = typeof ModifierClassMap;
|
||||||
|
@ -4,7 +4,7 @@ import { Gender } from "#app/data/gender";
|
|||||||
import { FormChangeItem } from "#enums/form-change-item";
|
import { FormChangeItem } from "#enums/form-change-item";
|
||||||
import { type ModifierOverride } from "#app/modifier/modifier-type";
|
import { type ModifierOverride } from "#app/modifier/modifier-type";
|
||||||
import { Variant } from "#app/sprites/variant";
|
import { Variant } from "#app/sprites/variant";
|
||||||
import { Unlockables } from "#app/system/unlockables";
|
import { Unlockables } from "#enums/unlockables";
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { BattleType } from "#enums/battle-type";
|
import { BattleType } from "#enums/battle-type";
|
||||||
import { BerryType } from "#enums/berry-type";
|
import { BerryType } from "#enums/berry-type";
|
||||||
@ -285,17 +285,17 @@ export const defaultOverrides = new DefaultOverrides();
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
...defaultOverrides,
|
...defaultOverrides,
|
||||||
...overrides
|
...overrides,
|
||||||
} satisfies InstanceType<typeof DefaultOverrides>;
|
} satisfies InstanceType<typeof DefaultOverrides>;
|
||||||
|
|
||||||
export type BattleStyle = "double" | "single" | "even-doubles" | "odd-doubles";
|
export type BattleStyle = "double" | "single" | "even-doubles" | "odd-doubles";
|
||||||
|
|
||||||
export type RandomTrainerOverride = {
|
export type RandomTrainerOverride = {
|
||||||
/** The Type of trainer to force */
|
/** The Type of trainer to force */
|
||||||
trainerType: Exclude<TrainerType, TrainerType.UNKNOWN>,
|
trainerType: Exclude<TrainerType, TrainerType.UNKNOWN>;
|
||||||
/* If the selected trainer type has a double version, it will always use its double version. */
|
/* If the selected trainer type has a double version, it will always use its double version. */
|
||||||
alwaysDouble?: boolean
|
alwaysDouble?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
/** The type of the {@linkcode DefaultOverrides} class */
|
/** The type of the {@linkcode DefaultOverrides} class */
|
||||||
export type OverridesType = typeof DefaultOverrides;
|
export type OverridesType = typeof DefaultOverrides;
|
@ -2,6 +2,7 @@ import type { Phase } from "#app/phase";
|
|||||||
import type { default as Pokemon } from "#app/field/pokemon";
|
import type { default as Pokemon } from "#app/field/pokemon";
|
||||||
import type { PhaseMap, PhaseString } from "./@types/phase-types";
|
import type { PhaseMap, PhaseString } from "./@types/phase-types";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import { ActivatePriorityQueuePhase } from "#app/phases/activate-priority-queue-phase";
|
||||||
import { AddEnemyBuffModifierPhase } from "#app/phases/add-enemy-buff-modifier-phase";
|
import { AddEnemyBuffModifierPhase } from "#app/phases/add-enemy-buff-modifier-phase";
|
||||||
import { AttemptCapturePhase } from "#app/phases/attempt-capture-phase";
|
import { AttemptCapturePhase } from "#app/phases/attempt-capture-phase";
|
||||||
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
|
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
|
||||||
@ -11,7 +12,9 @@ import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase";
|
|||||||
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
|
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
|
||||||
import { CommandPhase } from "#app/phases/command-phase";
|
import { CommandPhase } from "#app/phases/command-phase";
|
||||||
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
|
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
|
||||||
|
import { coerceArray, type Constructor } from "#app/utils/common";
|
||||||
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
|
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
|
||||||
|
import type { DynamicPhaseType } from "#enums/dynamic-phase-type";
|
||||||
import { EggHatchPhase } from "#app/phases/egg-hatch-phase";
|
import { EggHatchPhase } from "#app/phases/egg-hatch-phase";
|
||||||
import { EggLapsePhase } from "#app/phases/egg-lapse-phase";
|
import { EggLapsePhase } from "#app/phases/egg-lapse-phase";
|
||||||
import { EggSummaryPhase } from "#app/phases/egg-summary-phase";
|
import { EggSummaryPhase } from "#app/phases/egg-summary-phase";
|
||||||
@ -55,6 +58,7 @@ import { NextEncounterPhase } from "#app/phases/next-encounter-phase";
|
|||||||
import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase";
|
import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase";
|
||||||
import { PartyExpPhase } from "#app/phases/party-exp-phase";
|
import { PartyExpPhase } from "#app/phases/party-exp-phase";
|
||||||
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
||||||
|
import { type PhasePriorityQueue, PostSummonPhasePriorityQueue } from "#app/data/phase-priority-queue";
|
||||||
import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase";
|
import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase";
|
||||||
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
||||||
import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase";
|
import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase";
|
||||||
@ -111,6 +115,7 @@ import { WeatherEffectPhase } from "#app/phases/weather-effect-phase";
|
|||||||
* This allows for easy creation of new phases without needing to import each phase individually.
|
* This allows for easy creation of new phases without needing to import each phase individually.
|
||||||
*/
|
*/
|
||||||
const PHASES = Object.freeze({
|
const PHASES = Object.freeze({
|
||||||
|
ActivatePriorityQueuePhase,
|
||||||
AddEnemyBuffModifierPhase,
|
AddEnemyBuffModifierPhase,
|
||||||
AttemptCapturePhase,
|
AttemptCapturePhase,
|
||||||
AttemptRunPhase,
|
AttemptRunPhase,
|
||||||
@ -222,9 +227,19 @@ export class PhaseManager {
|
|||||||
private phaseQueuePrependSpliceIndex = -1;
|
private phaseQueuePrependSpliceIndex = -1;
|
||||||
private nextCommandPhaseQueue: Phase[] = [];
|
private nextCommandPhaseQueue: Phase[] = [];
|
||||||
|
|
||||||
|
/** Storage for {@linkcode PhasePriorityQueue}s which hold phases whose order dynamically changes */
|
||||||
|
private dynamicPhaseQueues: PhasePriorityQueue[];
|
||||||
|
/** Parallel array to {@linkcode dynamicPhaseQueues} - matches phase types to their queues */
|
||||||
|
private dynamicPhaseTypes: Constructor<Phase>[];
|
||||||
|
|
||||||
private currentPhase: Phase | null = null;
|
private currentPhase: Phase | null = null;
|
||||||
private standbyPhase: Phase | null = null;
|
private standbyPhase: Phase | null = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.dynamicPhaseQueues = [new PostSummonPhasePriorityQueue()];
|
||||||
|
this.dynamicPhaseTypes = [PostSummonPhase];
|
||||||
|
}
|
||||||
|
|
||||||
/* Phase Functions */
|
/* Phase Functions */
|
||||||
getCurrentPhase(): Phase | null {
|
getCurrentPhase(): Phase | null {
|
||||||
return this.currentPhase;
|
return this.currentPhase;
|
||||||
@ -254,7 +269,11 @@ export class PhaseManager {
|
|||||||
* @param defer boolean on which queue to add to, defaults to false, and adds to phaseQueue
|
* @param defer boolean on which queue to add to, defaults to false, and adds to phaseQueue
|
||||||
*/
|
*/
|
||||||
pushPhase(phase: Phase, defer = false): void {
|
pushPhase(phase: Phase, defer = false): void {
|
||||||
(!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase);
|
if (this.getDynamicPhaseType(phase) !== undefined) {
|
||||||
|
this.pushDynamicPhase(phase);
|
||||||
|
} else {
|
||||||
|
(!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -283,6 +302,7 @@ export class PhaseManager {
|
|||||||
for (const queue of [this.phaseQueue, this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) {
|
for (const queue of [this.phaseQueue, this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) {
|
||||||
queue.splice(0, queue.length);
|
queue.splice(0, queue.length);
|
||||||
}
|
}
|
||||||
|
this.dynamicPhaseQueues.forEach(queue => queue.clear());
|
||||||
this.currentPhase = null;
|
this.currentPhase = null;
|
||||||
this.standbyPhase = null;
|
this.standbyPhase = null;
|
||||||
this.clearPhaseQueueSplice();
|
this.clearPhaseQueueSplice();
|
||||||
@ -333,8 +353,9 @@ export class PhaseManager {
|
|||||||
|
|
||||||
this.currentPhase = this.phaseQueue.shift() ?? null;
|
this.currentPhase = this.phaseQueue.shift() ?? null;
|
||||||
|
|
||||||
|
const unactivatedConditionalPhases: [() => boolean, Phase][] = [];
|
||||||
// Check if there are any conditional phases queued
|
// Check if there are any conditional phases queued
|
||||||
if (this.conditionalQueue?.length) {
|
while (this.conditionalQueue?.length) {
|
||||||
// Retrieve the first conditional phase from the queue
|
// Retrieve the first conditional phase from the queue
|
||||||
const conditionalPhase = this.conditionalQueue.shift();
|
const conditionalPhase = this.conditionalQueue.shift();
|
||||||
// Evaluate the condition associated with the phase
|
// Evaluate the condition associated with the phase
|
||||||
@ -343,11 +364,12 @@ export class PhaseManager {
|
|||||||
this.pushPhase(conditionalPhase[1]);
|
this.pushPhase(conditionalPhase[1]);
|
||||||
} else if (conditionalPhase) {
|
} else if (conditionalPhase) {
|
||||||
// If the condition is not met, re-add the phase back to the front of the conditional queue
|
// If the condition is not met, re-add the phase back to the front of the conditional queue
|
||||||
this.conditionalQueue.unshift(conditionalPhase);
|
unactivatedConditionalPhases.push(conditionalPhase);
|
||||||
} else {
|
} else {
|
||||||
console.warn("condition phase is undefined/null!", conditionalPhase);
|
console.warn("condition phase is undefined/null!", conditionalPhase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.conditionalQueue.push(...unactivatedConditionalPhases);
|
||||||
|
|
||||||
if (this.currentPhase) {
|
if (this.currentPhase) {
|
||||||
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;");
|
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;");
|
||||||
@ -375,7 +397,7 @@ export class PhaseManager {
|
|||||||
* @returns the found phase or undefined if none found
|
* @returns the found phase or undefined if none found
|
||||||
*/
|
*/
|
||||||
findPhase<P extends Phase = Phase>(phaseFilter: (phase: P) => boolean): P | undefined {
|
findPhase<P extends Phase = Phase>(phaseFilter: (phase: P) => boolean): P | undefined {
|
||||||
return this.phaseQueue.find(phaseFilter) as P;
|
return this.phaseQueue.find(phaseFilter) as P | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean {
|
tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean {
|
||||||
@ -416,9 +438,7 @@ export class PhaseManager {
|
|||||||
* @returns boolean if a targetPhase was found and added
|
* @returns boolean if a targetPhase was found and added
|
||||||
*/
|
*/
|
||||||
prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean {
|
prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean {
|
||||||
if (!Array.isArray(phase)) {
|
phase = coerceArray(phase);
|
||||||
phase = [phase];
|
|
||||||
}
|
|
||||||
const target = PHASES[targetPhase];
|
const target = PHASES[targetPhase];
|
||||||
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target);
|
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target);
|
||||||
|
|
||||||
@ -431,17 +451,16 @@ export class PhaseManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to add the input phase(s) to index after target phase in the {@linkcode phaseQueue}, else simply calls {@linkcode unshiftPhase()}
|
* Tries to add the input phase(s) to index after target phase in the {@linkcode phaseQueue}, else simply calls {@linkcode unshiftPhase()}
|
||||||
* @param phase - The phase(s) to be added
|
* @param phase {@linkcode Phase} the phase(s) to be added
|
||||||
* @param targetPhase - The phase to search for in phaseQueue
|
* @param targetPhase {@linkcode Phase} the type of phase to search for in {@linkcode phaseQueue}
|
||||||
|
* @param condition Condition the target phase must meet to be appended to
|
||||||
* @returns `true` if a `targetPhase` was found to append to
|
* @returns `true` if a `targetPhase` was found to append to
|
||||||
*/
|
*/
|
||||||
appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean {
|
appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString, condition?: (p: Phase) => boolean): boolean {
|
||||||
if (!Array.isArray(phase)) {
|
phase = coerceArray(phase);
|
||||||
phase = [phase];
|
|
||||||
}
|
|
||||||
const target = PHASES[targetPhase];
|
const target = PHASES[targetPhase];
|
||||||
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target);
|
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target && (!condition || condition(ph)));
|
||||||
|
|
||||||
if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) {
|
if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) {
|
||||||
this.phaseQueue.splice(targetIndex + 1, 0, ...phase);
|
this.phaseQueue.splice(targetIndex + 1, 0, ...phase);
|
||||||
@ -451,6 +470,68 @@ export class PhaseManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks a phase and returns the matching {@linkcode DynamicPhaseType}, or undefined if it does not match one
|
||||||
|
* @param phase The phase to check
|
||||||
|
* @returns The corresponding {@linkcode DynamicPhaseType} or `undefined`
|
||||||
|
*/
|
||||||
|
public getDynamicPhaseType(phase: Phase | null): DynamicPhaseType | undefined {
|
||||||
|
let phaseType: DynamicPhaseType | undefined;
|
||||||
|
this.dynamicPhaseTypes.forEach((cls, index) => {
|
||||||
|
if (phase instanceof cls) {
|
||||||
|
phaseType = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return phaseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes a phase onto its corresponding dynamic queue and marks the activation point in {@linkcode phaseQueue}
|
||||||
|
*
|
||||||
|
* The {@linkcode ActivatePriorityQueuePhase} will run the top phase in the dynamic queue (not necessarily {@linkcode phase})
|
||||||
|
* @param phase The phase to push
|
||||||
|
*/
|
||||||
|
public pushDynamicPhase(phase: Phase): void {
|
||||||
|
const type = this.getDynamicPhaseType(phase);
|
||||||
|
if (type === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pushPhase(new ActivatePriorityQueuePhase(type));
|
||||||
|
this.dynamicPhaseQueues[type].push(phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unshifts the top phase from the corresponding dynamic queue onto {@linkcode phaseQueue}
|
||||||
|
* @param type {@linkcode DynamicPhaseType} The type of dynamic phase to start
|
||||||
|
*/
|
||||||
|
public startDynamicPhaseType(type: DynamicPhaseType): void {
|
||||||
|
const phase = this.dynamicPhaseQueues[type].pop();
|
||||||
|
if (phase) {
|
||||||
|
this.unshiftPhase(phase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unshifts an {@linkcode ActivatePriorityQueuePhase} for {@linkcode phase}, then pushes {@linkcode phase} to its dynamic queue
|
||||||
|
*
|
||||||
|
* This is the same as {@linkcode pushDynamicPhase}, except the activation phase is unshifted
|
||||||
|
*
|
||||||
|
* {@linkcode phase} is not guaranteed to be the next phase from the queue to run (if the queue is not empty)
|
||||||
|
* @param phase The phase to add
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public startDynamicPhase(phase: Phase): void {
|
||||||
|
const type = this.getDynamicPhaseType(phase);
|
||||||
|
if (type === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.unshiftPhase(new ActivatePriorityQueuePhase(type));
|
||||||
|
this.dynamicPhaseQueues[type].push(phase);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue
|
* Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue
|
||||||
* @param message - string for MessagePhase
|
* @param message - string for MessagePhase
|
||||||
@ -578,4 +659,11 @@ export class PhaseManager {
|
|||||||
): boolean {
|
): boolean {
|
||||||
return this.appendToPhase(this.create(phase, ...args), targetPhase);
|
return this.appendToPhase(this.create(phase, ...args), targetPhase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public startNewDynamicPhase<T extends PhaseString>(
|
||||||
|
phase: T,
|
||||||
|
...args: ConstructorParameters<PhaseConstructorMap[T]>
|
||||||
|
): void {
|
||||||
|
this.startDynamicPhase(this.create(phase, ...args));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
23
src/phases/activate-priority-queue-phase.ts
Normal file
23
src/phases/activate-priority-queue-phase.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import type { DynamicPhaseType } from "#enums/dynamic-phase-type";
|
||||||
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import { Phase } from "#app/phase";
|
||||||
|
|
||||||
|
export class ActivatePriorityQueuePhase extends Phase {
|
||||||
|
public readonly phaseName = "ActivatePriorityQueuePhase";
|
||||||
|
private type: DynamicPhaseType;
|
||||||
|
|
||||||
|
constructor(type: DynamicPhaseType) {
|
||||||
|
super();
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
override start() {
|
||||||
|
super.start();
|
||||||
|
globalScene.phaseManager.startDynamicPhaseType(this.type);
|
||||||
|
this.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getType(): DynamicPhaseType {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,6 @@
|
|||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import {
|
import { regenerateModifierPoolThresholds, getEnemyBuffModifierForWave } from "#app/modifier/modifier-type";
|
||||||
regenerateModifierPoolThresholds,
|
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||||
ModifierPoolType,
|
|
||||||
getEnemyBuffModifierForWave,
|
|
||||||
} from "#app/modifier/modifier-type";
|
|
||||||
import { EnemyPersistentModifier } from "#app/modifier/modifier";
|
import { EnemyPersistentModifier } from "#app/modifier/modifier";
|
||||||
import { Phase } from "#app/phase";
|
import { Phase } from "#app/phase";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
import {
|
import { applyAbAttrs, applyPreLeaveFieldAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyAbAttrs,
|
|
||||||
applyPreLeaveFieldAbAttrs,
|
|
||||||
PreLeaveFieldAbAttr,
|
|
||||||
RunSuccessAbAttr,
|
|
||||||
} from "#app/data/abilities/ability";
|
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
||||||
@ -30,10 +25,10 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||||||
|
|
||||||
this.attemptRunAway(playerField, enemyField, escapeChance);
|
this.attemptRunAway(playerField, enemyField, escapeChance);
|
||||||
|
|
||||||
applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance);
|
applyAbAttrs("RunSuccessAbAttr", playerPokemon, null, false, escapeChance);
|
||||||
|
|
||||||
if (playerPokemon.randBattleSeedInt(100) < escapeChance.value && !this.forceFailEscape) {
|
if (playerPokemon.randBattleSeedInt(100) < escapeChance.value && !this.forceFailEscape) {
|
||||||
enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, enemyPokemon));
|
enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", enemyPokemon));
|
||||||
|
|
||||||
globalScene.playSound("se/flee");
|
globalScene.playSound("se/flee");
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { applyPostBattleAbAttrs, PostBattleAbAttr } from "#app/data/abilities/ability";
|
import { applyPostBattleAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier";
|
import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ export class BattleEndPhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const pokemon of globalScene.getPokemonAllowedInBattle()) {
|
for (const pokemon of globalScene.getPokemonAllowedInBattle()) {
|
||||||
applyPostBattleAbAttrs(PostBattleAbAttr, pokemon, false, this.isVictory);
|
applyPostBattleAbAttrs("PostBattleAbAttr", pokemon, false, this.isVictory);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalScene.currentBattle.moneyScattered) {
|
if (globalScene.currentBattle.moneyScattered) {
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
import {
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyAbAttrs,
|
|
||||||
PreventBerryUseAbAttr,
|
|
||||||
HealFromBerryUseAbAttr,
|
|
||||||
RepeatBerryNextTurnAbAttr,
|
|
||||||
} from "#app/data/abilities/ability";
|
|
||||||
import { CommonAnim } from "#enums/move-anims-common";
|
import { CommonAnim } from "#enums/move-anims-common";
|
||||||
import { BerryUsedEvent } from "#app/events/battle-scene";
|
import { BerryUsedEvent } from "#app/events/battle-scene";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
@ -25,7 +20,7 @@ export class BerryPhase extends FieldPhase {
|
|||||||
|
|
||||||
this.executeForAll(pokemon => {
|
this.executeForAll(pokemon => {
|
||||||
this.eatBerries(pokemon);
|
this.eatBerries(pokemon);
|
||||||
applyAbAttrs(RepeatBerryNextTurnAbAttr, pokemon, null);
|
applyAbAttrs("RepeatBerryNextTurnAbAttr", pokemon, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
@ -47,7 +42,7 @@ export class BerryPhase extends FieldPhase {
|
|||||||
|
|
||||||
// TODO: If both opponents on field have unnerve, which one displays its message?
|
// TODO: If both opponents on field have unnerve, which one displays its message?
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
pokemon.getOpponents().forEach(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled));
|
pokemon.getOpponents().forEach(opp => applyAbAttrs("PreventBerryUseAbAttr", opp, cancelled));
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("abilityTriggers:preventBerryUse", {
|
i18next.t("abilityTriggers:preventBerryUse", {
|
||||||
@ -75,6 +70,6 @@ export class BerryPhase extends FieldPhase {
|
|||||||
globalScene.updateModifiers(pokemon.isPlayer());
|
globalScene.updateModifiers(pokemon.isPlayer());
|
||||||
|
|
||||||
// AbilityId.CHEEK_POUCH only works once per round of nom noms
|
// AbilityId.CHEEK_POUCH only works once per round of nom noms
|
||||||
applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new BooleanHolder(false));
|
applyAbAttrs("HealFromBerryUseAbAttr", pokemon, new BooleanHolder(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
|||||||
import { isNullOrUndefined } from "#app/utils/common";
|
import { isNullOrUndefined } from "#app/utils/common";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||||
|
import { isVirtual, isIgnorePP, MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
|
||||||
export class CommandPhase extends FieldPhase {
|
export class CommandPhase extends FieldPhase {
|
||||||
public readonly phaseName = "CommandPhase";
|
public readonly phaseName = "CommandPhase";
|
||||||
@ -80,7 +81,7 @@ export class CommandPhase extends FieldPhase {
|
|||||||
) {
|
) {
|
||||||
globalScene.currentBattle.turnCommands[this.fieldIndex] = {
|
globalScene.currentBattle.turnCommands[this.fieldIndex] = {
|
||||||
command: Command.FIGHT,
|
command: Command.FIGHT,
|
||||||
move: { move: MoveId.NONE, targets: [] },
|
move: { move: MoveId.NONE, targets: [], useMode: MoveUseMode.NORMAL },
|
||||||
skip: true,
|
skip: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -103,29 +104,31 @@ export class CommandPhase extends FieldPhase {
|
|||||||
moveQueue.length &&
|
moveQueue.length &&
|
||||||
moveQueue[0] &&
|
moveQueue[0] &&
|
||||||
moveQueue[0].move &&
|
moveQueue[0].move &&
|
||||||
!moveQueue[0].virtual &&
|
!isVirtual(moveQueue[0].useMode) &&
|
||||||
(!playerPokemon.getMoveset().find(m => m.moveId === moveQueue[0].move) ||
|
(!playerPokemon.getMoveset().find(m => m.moveId === moveQueue[0].move) ||
|
||||||
!playerPokemon
|
!playerPokemon
|
||||||
.getMoveset()
|
.getMoveset()
|
||||||
[playerPokemon.getMoveset().findIndex(m => m.moveId === moveQueue[0].move)].isUsable(
|
[playerPokemon.getMoveset().findIndex(m => m.moveId === moveQueue[0].move)].isUsable(
|
||||||
playerPokemon,
|
playerPokemon,
|
||||||
moveQueue[0].ignorePP,
|
isIgnorePP(moveQueue[0].useMode),
|
||||||
))
|
))
|
||||||
) {
|
) {
|
||||||
moveQueue.shift();
|
moveQueue.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Refactor this. I did a few simple find/replace matches but this is just ABHORRENTLY structured
|
||||||
if (moveQueue.length > 0) {
|
if (moveQueue.length > 0) {
|
||||||
const queuedMove = moveQueue[0];
|
const queuedMove = moveQueue[0];
|
||||||
if (!queuedMove.move) {
|
if (!queuedMove.move) {
|
||||||
this.handleCommand(Command.FIGHT, -1);
|
this.handleCommand(Command.FIGHT, -1, MoveUseMode.NORMAL);
|
||||||
} else {
|
} else {
|
||||||
const moveIndex = playerPokemon.getMoveset().findIndex(m => m.moveId === queuedMove.move);
|
const moveIndex = playerPokemon.getMoveset().findIndex(m => m.moveId === queuedMove.move);
|
||||||
if (
|
if (
|
||||||
(moveIndex > -1 && playerPokemon.getMoveset()[moveIndex].isUsable(playerPokemon, queuedMove.ignorePP)) ||
|
(moveIndex > -1 &&
|
||||||
queuedMove.virtual
|
playerPokemon.getMoveset()[moveIndex].isUsable(playerPokemon, isIgnorePP(queuedMove.useMode))) ||
|
||||||
|
isVirtual(queuedMove.useMode)
|
||||||
) {
|
) {
|
||||||
this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, queuedMove);
|
this.handleCommand(Command.FIGHT, moveIndex, queuedMove.useMode, queuedMove);
|
||||||
} else {
|
} else {
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||||
}
|
}
|
||||||
@ -143,18 +146,23 @@ export class CommandPhase extends FieldPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Remove `args` and clean this thing up
|
||||||
|
* Code will need to be copied over from pkty except replacing the `virtual` and `ignorePP` args with a corresponding `MoveUseMode`.
|
||||||
|
*/
|
||||||
handleCommand(command: Command, cursor: number, ...args: any[]): boolean {
|
handleCommand(command: Command, cursor: number, ...args: any[]): boolean {
|
||||||
const playerPokemon = globalScene.getPlayerField()[this.fieldIndex];
|
const playerPokemon = globalScene.getPlayerField()[this.fieldIndex];
|
||||||
let success = false;
|
let success = false;
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
|
// TODO: We don't need 2 args for this - moveUseMode is carried over from queuedMove
|
||||||
case Command.TERA:
|
case Command.TERA:
|
||||||
case Command.FIGHT:
|
case Command.FIGHT: {
|
||||||
let useStruggle = false;
|
let useStruggle = false;
|
||||||
const turnMove: TurnMove | undefined = args.length === 2 ? (args[1] as TurnMove) : undefined;
|
const turnMove: TurnMove | undefined = args.length === 2 ? (args[1] as TurnMove) : undefined;
|
||||||
if (
|
if (
|
||||||
cursor === -1 ||
|
cursor === -1 ||
|
||||||
playerPokemon.trySelectMove(cursor, args[0] as boolean) ||
|
playerPokemon.trySelectMove(cursor, isIgnorePP(args[0] as MoveUseMode)) ||
|
||||||
(useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m.isUsable(playerPokemon)).length)
|
(useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m.isUsable(playerPokemon)).length)
|
||||||
) {
|
) {
|
||||||
let moveId: MoveId;
|
let moveId: MoveId;
|
||||||
@ -171,7 +179,7 @@ export class CommandPhase extends FieldPhase {
|
|||||||
const turnCommand: TurnCommand = {
|
const turnCommand: TurnCommand = {
|
||||||
command: Command.FIGHT,
|
command: Command.FIGHT,
|
||||||
cursor: cursor,
|
cursor: cursor,
|
||||||
move: { move: moveId, targets: [], ignorePP: args[0] },
|
move: { move: moveId, targets: [], useMode: args[0] },
|
||||||
args: args,
|
args: args,
|
||||||
};
|
};
|
||||||
const preTurnCommand: TurnCommand = {
|
const preTurnCommand: TurnCommand = {
|
||||||
@ -233,7 +241,8 @@ export class CommandPhase extends FieldPhase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Command.BALL:
|
}
|
||||||
|
case Command.BALL: {
|
||||||
const notInDex =
|
const notInDex =
|
||||||
globalScene
|
globalScene
|
||||||
.getEnemyField()
|
.getEnemyField()
|
||||||
@ -337,8 +346,9 @@ export class CommandPhase extends FieldPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case Command.POKEMON:
|
case Command.POKEMON:
|
||||||
case Command.RUN:
|
case Command.RUN: {
|
||||||
const isSwitch = command === Command.POKEMON;
|
const isSwitch = command === Command.POKEMON;
|
||||||
const { currentBattle, arena } = globalScene;
|
const { currentBattle, arena } = globalScene;
|
||||||
const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed;
|
const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed;
|
||||||
@ -445,6 +455,7 @@ export class CommandPhase extends FieldPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
|
@ -2,12 +2,7 @@ import { BattlerIndex } from "#enums/battler-index";
|
|||||||
import { BattleType } from "#enums/battle-type";
|
import { BattleType } from "#enums/battle-type";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||||
import {
|
import { applyAbAttrs, applyPreSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyAbAttrs,
|
|
||||||
SyncEncounterNatureAbAttr,
|
|
||||||
applyPreSummonAbAttrs,
|
|
||||||
PreSummonAbAttr,
|
|
||||||
} from "#app/data/abilities/ability";
|
|
||||||
import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims";
|
import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims";
|
||||||
import { getCharVariantFromDialogue } from "#app/data/dialogue";
|
import { getCharVariantFromDialogue } from "#app/data/dialogue";
|
||||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
@ -20,7 +15,8 @@ import type Pokemon from "#app/field/pokemon";
|
|||||||
import { FieldPosition } from "#enums/field-position";
|
import { FieldPosition } from "#enums/field-position";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier";
|
import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier";
|
||||||
import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
import { regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
||||||
|
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { BattlePhase } from "#app/phases/battle-phase";
|
import { BattlePhase } from "#app/phases/battle-phase";
|
||||||
import { achvs } from "#app/system/achv";
|
import { achvs } from "#app/system/achv";
|
||||||
@ -34,7 +30,7 @@ import { PlayerGender } from "#enums/player-gender";
|
|||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier";
|
import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/constants";
|
||||||
import { getNatureName } from "#app/data/nature";
|
import { getNatureName } from "#app/data/nature";
|
||||||
|
|
||||||
export class EncounterPhase extends BattlePhase {
|
export class EncounterPhase extends BattlePhase {
|
||||||
@ -132,7 +128,7 @@ export class EncounterPhase extends BattlePhase {
|
|||||||
.slice(0, !battle.double ? 1 : 2)
|
.slice(0, !battle.double ? 1 : 2)
|
||||||
.reverse()
|
.reverse()
|
||||||
.forEach(playerPokemon => {
|
.forEach(playerPokemon => {
|
||||||
applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, false, battle.enemyParty[e]);
|
applyAbAttrs("SyncEncounterNatureAbAttr", playerPokemon, null, false, battle.enemyParty[e]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,7 +249,7 @@ export class EncounterPhase extends BattlePhase {
|
|||||||
if (e < (battle.double ? 2 : 1)) {
|
if (e < (battle.double ? 2 : 1)) {
|
||||||
if (battle.battleType === BattleType.WILD) {
|
if (battle.battleType === BattleType.WILD) {
|
||||||
for (const pokemon of globalScene.getField()) {
|
for (const pokemon of globalScene.getField()) {
|
||||||
applyPreSummonAbAttrs(PreSummonAbAttr, pokemon, []);
|
applyPreSummonAbAttrs("PreSummonAbAttr", pokemon, []);
|
||||||
}
|
}
|
||||||
globalScene.field.add(enemyPokemon);
|
globalScene.field.add(enemyPokemon);
|
||||||
battle.seenEnemyPartyMemberIds.add(enemyPokemon.id);
|
battle.seenEnemyPartyMemberIds.add(enemyPokemon.id);
|
||||||
|
@ -5,10 +5,7 @@ import {
|
|||||||
applyPostFaintAbAttrs,
|
applyPostFaintAbAttrs,
|
||||||
applyPostKnockOutAbAttrs,
|
applyPostKnockOutAbAttrs,
|
||||||
applyPostVictoryAbAttrs,
|
applyPostVictoryAbAttrs,
|
||||||
PostFaintAbAttr,
|
} from "#app/data/abilities/apply-ab-attrs";
|
||||||
PostKnockOutAbAttr,
|
|
||||||
PostVictoryAbAttr,
|
|
||||||
} from "#app/data/abilities/ability";
|
|
||||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||||
import { battleSpecDialogue } from "#app/data/dialogue";
|
import { battleSpecDialogue } from "#app/data/dialogue";
|
||||||
import { allMoves } from "#app/data/data-lists";
|
import { allMoves } from "#app/data/data-lists";
|
||||||
@ -123,7 +120,7 @@ export class FaintPhase extends PokemonPhase {
|
|||||||
if (pokemon.turnData.attacksReceived?.length) {
|
if (pokemon.turnData.attacksReceived?.length) {
|
||||||
const lastAttack = pokemon.turnData.attacksReceived[0];
|
const lastAttack = pokemon.turnData.attacksReceived[0];
|
||||||
applyPostFaintAbAttrs(
|
applyPostFaintAbAttrs(
|
||||||
PostFaintAbAttr,
|
"PostFaintAbAttr",
|
||||||
pokemon,
|
pokemon,
|
||||||
globalScene.getPokemonById(lastAttack.sourceId)!,
|
globalScene.getPokemonById(lastAttack.sourceId)!,
|
||||||
new PokemonMove(lastAttack.move).getMove(),
|
new PokemonMove(lastAttack.move).getMove(),
|
||||||
@ -131,18 +128,18 @@ export class FaintPhase extends PokemonPhase {
|
|||||||
); // TODO: is this bang correct?
|
); // TODO: is this bang correct?
|
||||||
} else {
|
} else {
|
||||||
//If killed by indirect damage, apply post-faint abilities without providing a last move
|
//If killed by indirect damage, apply post-faint abilities without providing a last move
|
||||||
applyPostFaintAbAttrs(PostFaintAbAttr, pokemon);
|
applyPostFaintAbAttrs("PostFaintAbAttr", pokemon);
|
||||||
}
|
}
|
||||||
|
|
||||||
const alivePlayField = globalScene.getField(true);
|
const alivePlayField = globalScene.getField(true);
|
||||||
for (const p of alivePlayField) {
|
for (const p of alivePlayField) {
|
||||||
applyPostKnockOutAbAttrs(PostKnockOutAbAttr, p, pokemon);
|
applyPostKnockOutAbAttrs("PostKnockOutAbAttr", p, pokemon);
|
||||||
}
|
}
|
||||||
if (pokemon.turnData.attacksReceived?.length) {
|
if (pokemon.turnData.attacksReceived?.length) {
|
||||||
const defeatSource = this.source;
|
const defeatSource = this.source;
|
||||||
|
|
||||||
if (defeatSource?.isOnField()) {
|
if (defeatSource?.isOnField()) {
|
||||||
applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource);
|
applyPostVictoryAbAttrs("PostVictoryAbAttr", defeatSource);
|
||||||
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
|
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
|
||||||
const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr");
|
const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr");
|
||||||
if (pvattrs.length) {
|
if (pvattrs.length) {
|
||||||
|
@ -7,11 +7,11 @@ import type PokemonSpecies from "#app/data/pokemon-species";
|
|||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { trainerConfigs } from "#app/data/trainers/trainer-config";
|
import { trainerConfigs } from "#app/data/trainers/trainer-config";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/data/data-lists";
|
||||||
import { BattlePhase } from "#app/phases/battle-phase";
|
import { BattlePhase } from "#app/phases/battle-phase";
|
||||||
import type { EndCardPhase } from "#app/phases/end-card-phase";
|
import type { EndCardPhase } from "#app/phases/end-card-phase";
|
||||||
import { achvs, ChallengeAchv } from "#app/system/achv";
|
import { achvs, ChallengeAchv } from "#app/system/achv";
|
||||||
import { Unlockables } from "#app/system/unlockables";
|
import { Unlockables } from "#enums/unlockables";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
import { UiMode } from "#enums/ui-mode";
|
||||||
import { isLocal, isLocalServerConnected } from "#app/utils/common";
|
import { isLocal, isLocalServerConnected } from "#app/utils/common";
|
||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
|
@ -12,7 +12,6 @@ import { UiMode } from "#enums/ui-mode";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase";
|
import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { ConfirmUiMode } from "#enums/confirm-ui-mode";
|
|
||||||
import { LearnMoveType } from "#enums/learn-move-type";
|
import { LearnMoveType } from "#enums/learn-move-type";
|
||||||
|
|
||||||
export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
|
export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
|
||||||
@ -164,10 +163,6 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
|
|||||||
globalScene.ui.setMode(this.messageMode);
|
globalScene.ui.setMode(this.messageMode);
|
||||||
this.replaceMoveCheck(move, pokemon);
|
this.replaceMoveCheck(move, pokemon);
|
||||||
},
|
},
|
||||||
false,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
ConfirmUiMode.DEFAULT_NO,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type { ModifierType, ModifierTypeFunc } from "#app/modifier/modifier-type";
|
import type { ModifierType } from "#app/modifier/modifier-type";
|
||||||
import { getModifierType } from "#app/modifier/modifier-type";
|
import type { ModifierTypeFunc } from "#app/@types/modifier-types";
|
||||||
|
import { getModifierType } from "#app/utils/modifier-utils";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
|
|
||||||
|
@ -8,10 +8,11 @@ import { MoveResult } from "#enums/move-result";
|
|||||||
import { BooleanHolder } from "#app/utils/common";
|
import { BooleanHolder } from "#app/utils/common";
|
||||||
import { PokemonPhase } from "#app/phases/pokemon-phase";
|
import { PokemonPhase } from "#app/phases/pokemon-phase";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
|
import type { MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
import type { ChargingMove } from "#app/@types/move-types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Phase for the "charging turn" of two-turn moves (e.g. Dig).
|
* Phase for the "charging turn" of two-turn moves (e.g. Dig).
|
||||||
* @extends {@linkcode PokemonPhase}
|
|
||||||
*/
|
*/
|
||||||
export class MoveChargePhase extends PokemonPhase {
|
export class MoveChargePhase extends PokemonPhase {
|
||||||
public readonly phaseName = "MoveChargePhase";
|
public readonly phaseName = "MoveChargePhase";
|
||||||
@ -20,10 +21,21 @@ export class MoveChargePhase extends PokemonPhase {
|
|||||||
/** The field index targeted by the move (Charging moves assume single target) */
|
/** The field index targeted by the move (Charging moves assume single target) */
|
||||||
public targetIndex: BattlerIndex;
|
public targetIndex: BattlerIndex;
|
||||||
|
|
||||||
constructor(battlerIndex: BattlerIndex, targetIndex: BattlerIndex, move: PokemonMove) {
|
/** The {@linkcode MoveUseMode} of the move that triggered the charge; passed on from move phase */
|
||||||
|
private useMode: MoveUseMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new MoveChargePhase.
|
||||||
|
* @param battlerIndex - The {@linkcode BattlerIndex} of the user.
|
||||||
|
* @param targetIndex - The {@linkcode BattlerIndex} of the target.
|
||||||
|
* @param move - The {@linkcode PokemonMove} being used
|
||||||
|
* @param useMode - The move's {@linkcode MoveUseMode}
|
||||||
|
*/
|
||||||
|
constructor(battlerIndex: BattlerIndex, targetIndex: BattlerIndex, move: PokemonMove, useMode: MoveUseMode) {
|
||||||
super(battlerIndex);
|
super(battlerIndex);
|
||||||
this.move = move;
|
this.move = move;
|
||||||
this.targetIndex = targetIndex;
|
this.targetIndex = targetIndex;
|
||||||
|
this.useMode = useMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override start() {
|
public override start() {
|
||||||
@ -37,7 +49,8 @@ export class MoveChargePhase extends PokemonPhase {
|
|||||||
// immediately end this phase.
|
// immediately end this phase.
|
||||||
if (!target || !move.isChargingMove()) {
|
if (!target || !move.isChargingMove()) {
|
||||||
console.warn("Invalid parameters for MoveChargePhase");
|
console.warn("Invalid parameters for MoveChargePhase");
|
||||||
return super.end();
|
super.end();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
new MoveChargeAnim(move.chargeAnim, move.id, user).play(false, () => {
|
new MoveChargeAnim(move.chargeAnim, move.id, user).play(false, () => {
|
||||||
@ -52,29 +65,30 @@ export class MoveChargePhase extends PokemonPhase {
|
|||||||
/** Checks the move's instant charge conditions, then ends this phase. */
|
/** Checks the move's instant charge conditions, then ends this phase. */
|
||||||
public override end() {
|
public override end() {
|
||||||
const user = this.getUserPokemon();
|
const user = this.getUserPokemon();
|
||||||
const move = this.move.getMove();
|
// Checked for `ChargingMove` in `this.start()`
|
||||||
|
const move = this.move.getMove() as ChargingMove;
|
||||||
|
|
||||||
if (move.isChargingMove()) {
|
const instantCharge = new BooleanHolder(false);
|
||||||
const instantCharge = new BooleanHolder(false);
|
applyMoveChargeAttrs("InstantChargeAttr", user, null, move, instantCharge);
|
||||||
|
|
||||||
applyMoveChargeAttrs("InstantChargeAttr", user, null, move, instantCharge);
|
// If instantly charging, remove the pending MoveEndPhase and queue a new MovePhase for the "attack" portion of the move.
|
||||||
|
// Otherwise, add the attack portion to the user's move queue to execute next turn.
|
||||||
if (instantCharge.value) {
|
// TODO: This checks status twice for a single-turn usage...
|
||||||
// this MoveEndPhase will be duplicated by the queued MovePhase if not removed
|
if (instantCharge.value) {
|
||||||
globalScene.phaseManager.tryRemovePhase(phase => phase.is("MoveEndPhase") && phase.getPokemon() === user);
|
globalScene.phaseManager.tryRemovePhase(phase => phase.is("MoveEndPhase") && phase.getPokemon() === user);
|
||||||
// queue a new MovePhase for this move's attack phase
|
globalScene.phaseManager.unshiftNew("MovePhase", user, [this.targetIndex], this.move, this.useMode);
|
||||||
globalScene.phaseManager.unshiftNew("MovePhase", user, [this.targetIndex], this.move, false);
|
} else {
|
||||||
} else {
|
user.pushMoveQueue({ move: move.id, targets: [this.targetIndex], useMode: this.useMode });
|
||||||
user.getMoveQueue().push({ move: move.id, targets: [this.targetIndex] });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this move's charging phase to the user's move history
|
|
||||||
user.pushMoveHistory({
|
|
||||||
move: this.move.moveId,
|
|
||||||
targets: [this.targetIndex],
|
|
||||||
result: MoveResult.OTHER,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add this move's charging phase to the user's move history
|
||||||
|
user.pushMoveHistory({
|
||||||
|
move: this.move.moveId,
|
||||||
|
targets: [this.targetIndex],
|
||||||
|
result: MoveResult.OTHER,
|
||||||
|
useMode: this.useMode,
|
||||||
|
});
|
||||||
|
|
||||||
super.end();
|
super.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,12 @@
|
|||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import {
|
import {
|
||||||
AddSecondStrikeAbAttr,
|
|
||||||
AlwaysHitAbAttr,
|
|
||||||
applyExecutedMoveAbAttrs,
|
applyExecutedMoveAbAttrs,
|
||||||
applyPostAttackAbAttrs,
|
applyPostAttackAbAttrs,
|
||||||
applyPostDamageAbAttrs,
|
applyPostDamageAbAttrs,
|
||||||
applyPostDefendAbAttrs,
|
applyPostDefendAbAttrs,
|
||||||
applyPreAttackAbAttrs,
|
applyPreAttackAbAttrs,
|
||||||
ExecutedMoveAbAttr,
|
} from "#app/data/abilities/apply-ab-attrs";
|
||||||
IgnoreMoveEffectsAbAttr,
|
|
||||||
MaxMultiHitAbAttr,
|
|
||||||
PostAttackAbAttr,
|
|
||||||
PostDamageAbAttr,
|
|
||||||
PostDefendAbAttr,
|
|
||||||
ReflectStatusMoveAbAttr,
|
|
||||||
} from "#app/data/abilities/ability";
|
|
||||||
import { ConditionalProtectTag } from "#app/data/arena-tag";
|
import { ConditionalProtectTag } from "#app/data/arena-tag";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import { MoveAnim } from "#app/data/battle-anims";
|
import { MoveAnim } from "#app/data/battle-anims";
|
||||||
@ -63,20 +54,25 @@ import { HitCheckResult } from "#enums/hit-check-result";
|
|||||||
import type Move from "#app/data/moves/move";
|
import type Move from "#app/data/moves/move";
|
||||||
import { isFieldTargeted } from "#app/data/moves/move-utils";
|
import { isFieldTargeted } from "#app/data/moves/move-utils";
|
||||||
import { DamageAchv } from "#app/system/achv";
|
import { DamageAchv } from "#app/system/achv";
|
||||||
|
import { isVirtual, isReflected, MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
|
||||||
type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier];
|
export type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier];
|
||||||
|
|
||||||
export class MoveEffectPhase extends PokemonPhase {
|
export class MoveEffectPhase extends PokemonPhase {
|
||||||
public readonly phaseName = "MoveEffectPhase";
|
public readonly phaseName = "MoveEffectPhase";
|
||||||
public move: Move;
|
public move: Move;
|
||||||
private virtual = false;
|
|
||||||
protected targets: BattlerIndex[];
|
protected targets: BattlerIndex[];
|
||||||
protected reflected = false;
|
protected useMode: MoveUseMode;
|
||||||
|
|
||||||
/** The result of the hit check against each target */
|
/** The result of the hit check against each target */
|
||||||
private hitChecks: HitCheckEntry[];
|
private hitChecks: HitCheckEntry[];
|
||||||
|
|
||||||
/** The move history entry for the move */
|
/**
|
||||||
|
* Log to be entered into the user's move history once the move result is resolved.
|
||||||
|
|
||||||
|
* Note that `result` logs whether the move was successfully
|
||||||
|
* used in the sense of "Does it have an effect on the user?".
|
||||||
|
*/
|
||||||
private moveHistoryEntry: TurnMove;
|
private moveHistoryEntry: TurnMove;
|
||||||
|
|
||||||
/** Is this the first strike of a move? */
|
/** Is this the first strike of a move? */
|
||||||
@ -84,19 +80,20 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
/** Is this the last strike of a move? */
|
/** Is this the last strike of a move? */
|
||||||
private lastHit: boolean;
|
private lastHit: boolean;
|
||||||
|
|
||||||
/** Phases queued during moves */
|
/**
|
||||||
|
* Phases queued during moves; used to add a new MovePhase for reflected moves after triggering.
|
||||||
|
* TODO: Remove this and move the reflection logic to ability-side
|
||||||
|
*/
|
||||||
private queuedPhases: Phase[] = [];
|
private queuedPhases: Phase[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param reflected Indicates that the move was reflected by the user due to magic coat or magic bounce
|
* @param useMode - The {@linkcode MoveUseMode} corresponding to how this move was used.
|
||||||
* @param virtual Indicates that the move is a virtual move (i.e. called by metronome)
|
|
||||||
*/
|
*/
|
||||||
constructor(battlerIndex: BattlerIndex, targets: BattlerIndex[], move: Move, reflected = false, virtual = false) {
|
constructor(battlerIndex: BattlerIndex, targets: BattlerIndex[], move: Move, useMode: MoveUseMode) {
|
||||||
super(battlerIndex);
|
super(battlerIndex);
|
||||||
this.move = move;
|
this.move = move;
|
||||||
this.virtual = virtual;
|
this.useMode = useMode;
|
||||||
|
|
||||||
this.reflected = reflected;
|
|
||||||
/**
|
/**
|
||||||
* In double battles, if the right Pokemon selects a spread move and the left Pokemon dies
|
* In double battles, if the right Pokemon selects a spread move and the left Pokemon dies
|
||||||
* with no party members available to switch in, then the right Pokemon takes the index
|
* with no party members available to switch in, then the right Pokemon takes the index
|
||||||
@ -167,7 +164,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* Queue the phaes that should occur when the target reflects the move back to the user
|
* Queue the phaes that should occur when the target reflects the move back to the user
|
||||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||||
* @param target - The {@linkcode Pokemon} that is reflecting the move
|
* @param target - The {@linkcode Pokemon} that is reflecting the move
|
||||||
*
|
* TODO: Rework this to use `onApply` of Magic Coat
|
||||||
*/
|
*/
|
||||||
private queueReflectedMove(user: Pokemon, target: Pokemon): void {
|
private queueReflectedMove(user: Pokemon, target: Pokemon): void {
|
||||||
const newTargets = this.move.isMultiTarget()
|
const newTargets = this.move.isMultiTarget()
|
||||||
@ -179,7 +176,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
globalScene.phaseManager.create(
|
globalScene.phaseManager.create(
|
||||||
"ShowAbilityPhase",
|
"ShowAbilityPhase",
|
||||||
target.getBattlerIndex(),
|
target.getBattlerIndex(),
|
||||||
target.getPassiveAbility().hasAttr(ReflectStatusMoveAbAttr),
|
target.getPassiveAbility().hasAttr("ReflectStatusMoveAbAttr"),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
this.queuedPhases.push(globalScene.phaseManager.create("HideAbilityPhase"));
|
this.queuedPhases.push(globalScene.phaseManager.create("HideAbilityPhase"));
|
||||||
@ -190,10 +187,8 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
"MovePhase",
|
"MovePhase",
|
||||||
target,
|
target,
|
||||||
newTargets,
|
newTargets,
|
||||||
new PokemonMove(this.move.id, 0, 0, true),
|
new PokemonMove(this.move.id),
|
||||||
true,
|
MoveUseMode.REFLECTED,
|
||||||
true,
|
|
||||||
true,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -287,8 +282,18 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
const overridden = new BooleanHolder(false);
|
const overridden = new BooleanHolder(false);
|
||||||
const move = this.move;
|
const move = this.move;
|
||||||
|
|
||||||
// Assume single target for override
|
// Apply effects to override a move effect.
|
||||||
applyMoveAttrs("OverrideMoveEffectAttr", user, this.getFirstTarget() ?? null, move, overridden, this.virtual);
|
// Assuming single target here works as this is (currently)
|
||||||
|
// only used for Future Sight, calling and Pledge moves.
|
||||||
|
// TODO: change if any other move effect overrides are introduced
|
||||||
|
applyMoveAttrs(
|
||||||
|
"OverrideMoveEffectAttr",
|
||||||
|
user,
|
||||||
|
this.getFirstTarget() ?? null,
|
||||||
|
move,
|
||||||
|
overridden,
|
||||||
|
isVirtual(this.useMode),
|
||||||
|
);
|
||||||
|
|
||||||
// If other effects were overriden, stop this phase before they can be applied
|
// If other effects were overriden, stop this phase before they can be applied
|
||||||
if (overridden.value) {
|
if (overridden.value) {
|
||||||
@ -299,8 +304,8 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
// Lapse `MOVE_EFFECT` effects (i.e. semi-invulnerability) when applicable
|
// Lapse `MOVE_EFFECT` effects (i.e. semi-invulnerability) when applicable
|
||||||
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
||||||
|
|
||||||
// If the user is acting again (such as due to Instruct), reset hitsLeft/hitCount so that
|
// If the user is acting again (such as due to Instruct or Dancer), reset hitsLeft/hitCount and
|
||||||
// the move executes correctly (ensures all hits of a multi-hit are properly calculated)
|
// recalculate hit count for multi-hit moves.
|
||||||
if (user.turnData.hitsLeft === 0 && user.turnData.hitCount > 0 && user.turnData.extraTurns > 0) {
|
if (user.turnData.hitsLeft === 0 && user.turnData.hitCount > 0 && user.turnData.extraTurns > 0) {
|
||||||
user.turnData.hitsLeft = -1;
|
user.turnData.hitsLeft = -1;
|
||||||
user.turnData.hitCount = 0;
|
user.turnData.hitCount = 0;
|
||||||
@ -317,7 +322,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
// Assume single target for multi hit
|
// Assume single target for multi hit
|
||||||
applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount);
|
applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount);
|
||||||
// If Parental Bond is applicable, add another hit
|
// If Parental Bond is applicable, add another hit
|
||||||
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null);
|
applyPreAttackAbAttrs("AddSecondStrikeAbAttr", user, null, move, false, hitCount, null);
|
||||||
// If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses
|
// If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses
|
||||||
globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount);
|
globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount);
|
||||||
// Set the user's relevant turnData fields to reflect the final hit count
|
// Set the user's relevant turnData fields to reflect the final hit count
|
||||||
@ -325,16 +330,11 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
user.turnData.hitsLeft = hitCount.value;
|
user.turnData.hitsLeft = hitCount.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Log to be entered into the user's move history once the move result is resolved.
|
|
||||||
* Note that `result` logs whether the move was successfully
|
|
||||||
* used in the sense of "Does it have an effect on the user?".
|
|
||||||
*/
|
|
||||||
this.moveHistoryEntry = {
|
this.moveHistoryEntry = {
|
||||||
move: this.move.id,
|
move: this.move.id,
|
||||||
targets: this.targets,
|
targets: this.targets,
|
||||||
result: MoveResult.PENDING,
|
result: MoveResult.PENDING,
|
||||||
virtual: this.virtual,
|
useMode: this.useMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldMove = isFieldTargeted(move);
|
const fieldMove = isFieldTargeted(move);
|
||||||
@ -370,7 +370,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
// Add to the move history entry
|
// Add to the move history entry
|
||||||
if (this.firstHit) {
|
if (this.firstHit) {
|
||||||
user.pushMoveHistory(this.moveHistoryEntry);
|
user.pushMoveHistory(this.moveHistoryEntry);
|
||||||
applyExecutedMoveAbAttrs(ExecutedMoveAbAttr, user);
|
applyExecutedMoveAbAttrs("ExecutedMoveAbAttr", user);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -399,29 +399,35 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
|
|
||||||
public override end(): void {
|
public override end(): void {
|
||||||
const user = this.getUserPokemon();
|
const user = this.getUserPokemon();
|
||||||
/**
|
if (!user) {
|
||||||
* If this phase isn't for the invoked move's last strike,
|
super.end();
|
||||||
* unshift another MoveEffectPhase for the next strike.
|
return;
|
||||||
* Otherwise, queue a message indicating the number of times the move has struck
|
|
||||||
* (if the move has struck more than once), then apply the heal from Shell Bell
|
|
||||||
* to the user.
|
|
||||||
*/
|
|
||||||
if (user) {
|
|
||||||
if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getFirstTarget()?.isActive()) {
|
|
||||||
globalScene.phaseManager.unshiftPhase(this.getNewHitPhase());
|
|
||||||
} else {
|
|
||||||
// Queue message for number of hits made by multi-move
|
|
||||||
// If multi-hit attack only hits once, still want to render a message
|
|
||||||
const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0);
|
|
||||||
if (hitsTotal > 1 || (user.turnData.hitsLeft && user.turnData.hitsLeft > 0)) {
|
|
||||||
// If there are multiple hits, or if there are hits of the multi-hit move left
|
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal }));
|
|
||||||
}
|
|
||||||
globalScene.applyModifiers(HitHealModifier, this.player, user);
|
|
||||||
this.getTargets().forEach(target => (target.turnData.moveEffectiveness = null));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this phase isn't for the invoked move's last strike (and we still have something to hit),
|
||||||
|
* unshift another MoveEffectPhase for the next strike before ending this phase.
|
||||||
|
*/
|
||||||
|
if (--user.turnData.hitsLeft >= 1 && this.getFirstTarget()) {
|
||||||
|
this.addNextHitPhase();
|
||||||
|
super.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All hits of the move have resolved by now.
|
||||||
|
* Queue message for multi-strike moves before applying Shell Bell heals & proccing Dancer-like effects.
|
||||||
|
*/
|
||||||
|
const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0);
|
||||||
|
if (hitsTotal > 1 || user.turnData.hitsLeft > 0) {
|
||||||
|
// Queue message if multiple hits occurred or were slated to occur (such as a Triple Axel miss)
|
||||||
|
globalScene.phaseManager.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal }));
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.applyModifiers(HitHealModifier, this.player, user);
|
||||||
|
this.getTargets().forEach(target => {
|
||||||
|
target.turnData.moveEffectiveness = null;
|
||||||
|
});
|
||||||
super.end();
|
super.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,10 +437,9 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||||
* @param target - {@linkcode Pokemon} the current target of this phase's invoked move
|
* @param target - {@linkcode Pokemon} the current target of this phase's invoked move
|
||||||
* @param hitResult - The {@linkcode HitResult} of the attempted move
|
* @param hitResult - The {@linkcode HitResult} of the attempted move
|
||||||
* @returns a `Promise` intended to be passed into a `then()` call.
|
|
||||||
*/
|
*/
|
||||||
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void {
|
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void {
|
||||||
applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult);
|
applyPostDefendAbAttrs("PostDefendAbAttr", target, user, this.move, hitResult);
|
||||||
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -443,14 +448,17 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||||
* @param target - {@linkcode Pokemon} the current target of this phase's invoked move
|
* @param target - {@linkcode Pokemon} the current target of this phase's invoked move
|
||||||
* @param dealsDamage - `true` if the attempted move successfully dealt damage
|
* @param dealsDamage - `true` if the attempted move successfully dealt damage
|
||||||
* @returns a function intended to be passed into a `then()` call.
|
|
||||||
*/
|
*/
|
||||||
protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean): void {
|
protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean): void {
|
||||||
if (this.move.hasAttr("FlinchAttr")) {
|
if (this.move.hasAttr("FlinchAttr")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !this.move.hitsSubstitute(user, target)) {
|
if (
|
||||||
|
dealsDamage &&
|
||||||
|
!target.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") &&
|
||||||
|
!this.move.hitsSubstitute(user, target)
|
||||||
|
) {
|
||||||
const flinched = new BooleanHolder(false);
|
const flinched = new BooleanHolder(false);
|
||||||
globalScene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
|
globalScene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
|
||||||
if (flinched.value) {
|
if (flinched.value) {
|
||||||
@ -463,8 +471,9 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||||
* @param target - {@linkcode Pokemon} the target to check for protection
|
* @param target - {@linkcode Pokemon} the target to check for protection
|
||||||
* @param move - The {@linkcode Move} being used
|
* @param move - The {@linkcode Move} being used
|
||||||
|
* @returns Whether the pokemon was protected
|
||||||
*/
|
*/
|
||||||
private protectedCheck(user: Pokemon, target: Pokemon) {
|
private protectedCheck(user: Pokemon, target: Pokemon): boolean {
|
||||||
/** The {@linkcode ArenaTagSide} to which the target belongs */
|
/** The {@linkcode ArenaTagSide} to which the target belongs */
|
||||||
const targetSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const targetSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
/** Has the invoked move been cancelled by conditional protection (e.g Quick Guard)? */
|
/** Has the invoked move been cancelled by conditional protection (e.g Quick Guard)? */
|
||||||
@ -485,14 +494,15 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Break up this chunky boolean to make it more palatable
|
||||||
return (
|
return (
|
||||||
![MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.move.moveTarget) &&
|
![MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.move.moveTarget) &&
|
||||||
(bypassIgnoreProtect.value || !this.move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target })) &&
|
(bypassIgnoreProtect.value || !this.move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target })) &&
|
||||||
(hasConditionalProtectApplied.value ||
|
(hasConditionalProtectApplied.value ||
|
||||||
(!target.findTags(t => t instanceof DamageProtectedTag).length &&
|
(!target.findTags(t => t instanceof DamageProtectedTag).length &&
|
||||||
target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))) ||
|
target.findTags(t => t instanceof ProtectedTag).some(t => target.lapseTag(t.tagType))) ||
|
||||||
(this.move.category !== MoveCategory.STATUS &&
|
(this.move.category !== MoveCategory.STATUS &&
|
||||||
target.findTags(t => t instanceof DamageProtectedTag).find(t => target.lapseTag(t.tagType))))
|
target.findTags(t => t instanceof DamageProtectedTag).some(t => target.lapseTag(t.tagType))))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -552,7 +562,8 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
return [HitCheckResult.PROTECTED, 0];
|
return [HitCheckResult.PROTECTED, 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.reflected && move.doesFlagEffectApply({ flag: MoveFlags.REFLECTABLE, user, target })) {
|
// Reflected moves cannot be reflected again
|
||||||
|
if (!isReflected(this.useMode) && move.doesFlagEffectApply({ flag: MoveFlags.REFLECTABLE, user, target })) {
|
||||||
return [HitCheckResult.REFLECTED, 0];
|
return [HitCheckResult.REFLECTED, 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -580,7 +591,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
// Strikes after the first in a multi-strike move are guaranteed to hit,
|
// Strikes after the first in a multi-strike move are guaranteed to hit,
|
||||||
// unless the move is flagged to check all hits and the user does not have Skill Link.
|
// unless the move is flagged to check all hits and the user does not have Skill Link.
|
||||||
if (user.turnData.hitsLeft < user.turnData.hitCount) {
|
if (user.turnData.hitsLeft < user.turnData.hitCount) {
|
||||||
if (!move.hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr(MaxMultiHitAbAttr)) {
|
if (!move.hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr("MaxMultiHitAbAttr")) {
|
||||||
return [HitCheckResult.HIT, effectiveness];
|
return [HitCheckResult.HIT, effectiveness];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -626,7 +637,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) {
|
if (user.hasAbilityWithAttr("AlwaysHitAbAttr") || target.hasAbilityWithAttr("AlwaysHitAbAttr")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.move.hasAttr("ToxicAccuracyAttr") && user.isOfType(PokemonType.POISON)) {
|
if (this.move.hasAttr("ToxicAccuracyAttr") && user.isOfType(PokemonType.POISON)) {
|
||||||
@ -665,12 +676,17 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
return (this.player ? globalScene.getPlayerField() : globalScene.getEnemyField())[this.fieldIndex];
|
return (this.player ? globalScene.getPlayerField() : globalScene.getEnemyField())[this.fieldIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns An array of all {@linkcode Pokemon} targeted by this phase's invoked move */
|
/**
|
||||||
|
* @returns An array of {@linkcode Pokemon} that are:
|
||||||
|
* - On-field and active
|
||||||
|
* - Non-fainted
|
||||||
|
* - Targeted by this phase's invoked move
|
||||||
|
*/
|
||||||
public getTargets(): Pokemon[] {
|
public getTargets(): Pokemon[] {
|
||||||
return globalScene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1);
|
return globalScene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns The first target of this phase's invoked move */
|
/** @returns The first active, non-fainted target of this phase's invoked move. */
|
||||||
public getFirstTarget(): Pokemon | undefined {
|
public getFirstTarget(): Pokemon | undefined {
|
||||||
return this.getTargets()[0];
|
return this.getTargets()[0];
|
||||||
}
|
}
|
||||||
@ -710,9 +726,12 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns A new `MoveEffectPhase` with the same properties as this phase */
|
/**
|
||||||
protected getNewHitPhase(): MoveEffectPhase {
|
* Unshifts a new `MoveEffectPhase` with the same properties as this phase.
|
||||||
return new MoveEffectPhase(this.battlerIndex, this.targets, this.move, this.reflected, this.virtual);
|
* Used to queue the next hit of multi-strike moves.
|
||||||
|
*/
|
||||||
|
protected addNextHitPhase(): void {
|
||||||
|
globalScene.phaseManager.unshiftNew("MoveEffectPhase", this.battlerIndex, this.targets, this.move, this.useMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Removes all substitutes that were broken by this phase's invoked move */
|
/** Removes all substitutes that were broken by this phase's invoked move */
|
||||||
@ -734,7 +753,6 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* @param firstTarget Whether the target is the first to be hit by the current strike
|
* @param firstTarget Whether the target is the first to be hit by the current strike
|
||||||
* @param selfTarget If defined, limits the effects triggered to either self-targeted
|
* @param selfTarget If defined, limits the effects triggered to either self-targeted
|
||||||
* effects (if set to `true`) or targeted effects (if set to `false`).
|
* effects (if set to `true`) or targeted effects (if set to `false`).
|
||||||
* @returns a `Promise` applying the relevant move effects.
|
|
||||||
*/
|
*/
|
||||||
protected triggerMoveEffects(
|
protected triggerMoveEffects(
|
||||||
triggerType: MoveEffectTrigger,
|
triggerType: MoveEffectTrigger,
|
||||||
@ -780,6 +798,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
|
|
||||||
const hitResult = this.applyMove(user, target, effectiveness);
|
const hitResult = this.applyMove(user, target, effectiveness);
|
||||||
|
|
||||||
|
// Apply effects to the user (always) and the target (if not blocked by substitute).
|
||||||
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, true);
|
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, true);
|
||||||
if (!this.move.hitsSubstitute(user, target)) {
|
if (!this.move.hitsSubstitute(user, target)) {
|
||||||
this.applyOnTargetEffects(user, target, hitResult, firstTarget);
|
this.applyOnTargetEffects(user, target, hitResult, firstTarget);
|
||||||
@ -789,7 +808,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
|
|
||||||
// Multi-hit check for Wimp Out/Emergency Exit
|
// Multi-hit check for Wimp Out/Emergency Exit
|
||||||
if (user.turnData.hitCount > 1) {
|
if (user.turnData.hitCount > 1) {
|
||||||
applyPostDamageAbAttrs(PostDamageAbAttr, target, 0, target.hasPassive(), false, [], user);
|
applyPostDamageAbAttrs("PostDamageAbAttr", target, 0, target.hasPassive(), false, [], user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -983,7 +1002,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
|
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
|
||||||
this.applyHeldItemFlinchCheck(user, target, dealsDamage);
|
this.applyHeldItemFlinchCheck(user, target, dealsDamage);
|
||||||
this.applyOnGetHitAbEffects(user, target, hitResult);
|
this.applyOnGetHitAbEffects(user, target, hitResult);
|
||||||
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult);
|
applyPostAttackAbAttrs("PostAttackAbAttr", user, target, this.move, hitResult);
|
||||||
|
|
||||||
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
|
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
|
||||||
if (!user.isPlayer() && this.move.is("AttackMove")) {
|
if (!user.isPlayer() && this.move.is("AttackMove")) {
|
||||||
|
@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/abilities/ability";
|
import { applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
|
||||||
export class MoveEndPhase extends PokemonPhase {
|
export class MoveEndPhase extends PokemonPhase {
|
||||||
@ -25,12 +25,12 @@ export class MoveEndPhase extends PokemonPhase {
|
|||||||
if (!this.wasFollowUp && pokemon?.isActive(true)) {
|
if (!this.wasFollowUp && pokemon?.isActive(true)) {
|
||||||
pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE);
|
pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE);
|
||||||
}
|
}
|
||||||
globalScene.arena.setIgnoreAbilities(false);
|
|
||||||
|
|
||||||
// Remove effects which were set on a Pokemon which removes them on summon (i.e. via Mold Breaker)
|
// Remove effects which were set on a Pokemon which removes them on summon (i.e. via Mold Breaker)
|
||||||
|
globalScene.arena.setIgnoreAbilities(false);
|
||||||
for (const target of this.targets) {
|
for (const target of this.targets) {
|
||||||
if (target) {
|
if (target) {
|
||||||
applyPostSummonAbAttrs(PostSummonRemoveEffectAbAttr, target);
|
applyPostSummonAbAttrs("PostSummonRemoveEffectAbAttr", target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,10 @@
|
|||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import {
|
import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyAbAttrs,
|
|
||||||
applyPostMoveUsedAbAttrs,
|
|
||||||
applyPreAttackAbAttrs,
|
|
||||||
BlockRedirectAbAttr,
|
|
||||||
IncreasePpAbAttr,
|
|
||||||
PokemonTypeChangeAbAttr,
|
|
||||||
PostMoveUsedAbAttr,
|
|
||||||
RedirectMoveAbAttr,
|
|
||||||
ReduceStatusEffectDurationAbAttr,
|
|
||||||
} from "#app/data/abilities/ability";
|
|
||||||
import type { DelayedAttackTag } from "#app/data/arena-tag";
|
import type { DelayedAttackTag } from "#app/data/arena-tag";
|
||||||
import { CommonAnim } from "#enums/move-anims-common";
|
import { CommonAnim } from "#enums/move-anims-common";
|
||||||
import { CenterOfAttentionTag } from "#app/data/battler-tags";
|
import { CenterOfAttentionTag } from "#app/data/battler-tags";
|
||||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||||
import type { HealStatusEffectAttr } from "#app/data/moves/move";
|
|
||||||
import { applyMoveAttrs } from "#app/data/moves/apply-attrs";
|
import { applyMoveAttrs } from "#app/data/moves/apply-attrs";
|
||||||
import { allMoves } from "#app/data/data-lists";
|
import { allMoves } from "#app/data/data-lists";
|
||||||
import { MoveFlags } from "#enums/MoveFlags";
|
import { MoveFlags } from "#enums/MoveFlags";
|
||||||
@ -30,13 +19,14 @@ import { MoveResult } from "#enums/move-result";
|
|||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { BattlePhase } from "#app/phases/battle-phase";
|
import { BattlePhase } from "#app/phases/battle-phase";
|
||||||
import { NumberHolder } from "#app/utils/common";
|
import { enumValueToKey, NumberHolder } from "#app/utils/common";
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import { isVirtual, isIgnorePP, isReflected, MoveUseMode, isIgnoreStatus } from "#enums/move-use-mode";
|
||||||
import { frenzyMissFunc } from "#app/data/moves/move-utils";
|
import { frenzyMissFunc } from "#app/data/moves/move-utils";
|
||||||
|
|
||||||
export class MovePhase extends BattlePhase {
|
export class MovePhase extends BattlePhase {
|
||||||
@ -44,17 +34,19 @@ export class MovePhase extends BattlePhase {
|
|||||||
protected _pokemon: Pokemon;
|
protected _pokemon: Pokemon;
|
||||||
protected _move: PokemonMove;
|
protected _move: PokemonMove;
|
||||||
protected _targets: BattlerIndex[];
|
protected _targets: BattlerIndex[];
|
||||||
protected followUp: boolean;
|
public readonly useMode: MoveUseMode; // Made public for quash
|
||||||
protected ignorePp: boolean;
|
|
||||||
protected forcedLast: boolean;
|
protected forcedLast: boolean;
|
||||||
|
|
||||||
|
/** Whether the current move should fail but still use PP */
|
||||||
protected failed = false;
|
protected failed = false;
|
||||||
|
/** Whether the current move should cancel and retain PP */
|
||||||
protected cancelled = false;
|
protected cancelled = false;
|
||||||
protected reflected = false;
|
|
||||||
|
|
||||||
public get pokemon(): Pokemon {
|
public get pokemon(): Pokemon {
|
||||||
return this._pokemon;
|
return this._pokemon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Do we need public getters but only protected setters?
|
||||||
protected set pokemon(pokemon: Pokemon) {
|
protected set pokemon(pokemon: Pokemon) {
|
||||||
this._pokemon = pokemon;
|
this._pokemon = pokemon;
|
||||||
}
|
}
|
||||||
@ -76,51 +68,42 @@ export class MovePhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param followUp Indicates that the move being used is a "follow-up" - for example, a move being used by Metronome or Dancer.
|
* Create a new MovePhase for using moves.
|
||||||
* Follow-ups bypass a few failure conditions, including flinches, sleep/paralysis/freeze and volatile status checks, etc.
|
* @param pokemon - The {@linkcode Pokemon} using the move
|
||||||
* @param reflected Indicates that the move was reflected by Magic Coat or Magic Bounce.
|
* @param move - The {@linkcode PokemonMove} to use
|
||||||
* Reflected moves cannot be reflected again and will not trigger Dancer.
|
* @param useMode - The {@linkcode MoveUseMode} corresponding to this move's means of execution (usually `MoveUseMode.NORMAL`).
|
||||||
|
* Not marked optional to ensure callers correctly pass on `useModes`.
|
||||||
|
* @param forcedLast - Whether to force this phase to occur last in order (for {@linkcode MoveId.QUASH}); default `false`
|
||||||
*/
|
*/
|
||||||
|
constructor(pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, useMode: MoveUseMode, forcedLast = false) {
|
||||||
constructor(
|
|
||||||
pokemon: Pokemon,
|
|
||||||
targets: BattlerIndex[],
|
|
||||||
move: PokemonMove,
|
|
||||||
followUp = false,
|
|
||||||
ignorePp = false,
|
|
||||||
reflected = false,
|
|
||||||
forcedLast = false,
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.pokemon = pokemon;
|
this.pokemon = pokemon;
|
||||||
this.targets = targets;
|
this.targets = targets;
|
||||||
this.move = move;
|
this.move = move;
|
||||||
this.followUp = followUp;
|
this.useMode = useMode;
|
||||||
this.ignorePp = ignorePp;
|
|
||||||
this.reflected = reflected;
|
|
||||||
this.forcedLast = forcedLast;
|
this.forcedLast = forcedLast;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the pokemon is active, if the move is usable, and that the move is targetting something.
|
* Checks if the pokemon is active, if the move is usable, and that the move is targeting something.
|
||||||
* @param ignoreDisableTags `true` to not check if the move is disabled
|
* @param ignoreDisableTags `true` to not check if the move is disabled
|
||||||
* @returns `true` if all the checks pass
|
* @returns `true` if all the checks pass
|
||||||
*/
|
*/
|
||||||
public canMove(ignoreDisableTags = false): boolean {
|
public canMove(ignoreDisableTags = false): boolean {
|
||||||
return (
|
return (
|
||||||
this.pokemon.isActive(true) &&
|
this.pokemon.isActive(true) &&
|
||||||
this.move.isUsable(this.pokemon, this.ignorePp, ignoreDisableTags) &&
|
this.move.isUsable(this.pokemon, isIgnorePP(this.useMode), ignoreDisableTags) &&
|
||||||
!!this.targets.length
|
this.targets.length > 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Signifies the current move should fail but still use PP */
|
/** Signifies the current move should fail but still use PP */
|
||||||
public fail(): void {
|
public fail(): void {
|
||||||
this.failed = true;
|
this.failed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Signifies the current move should cancel and retain PP */
|
/** Signifies the current move should cancel and retain PP */
|
||||||
public cancel(): void {
|
public cancel(): void {
|
||||||
this.cancelled = true;
|
this.cancelled = true;
|
||||||
}
|
}
|
||||||
@ -128,7 +111,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
/**
|
/**
|
||||||
* Shows whether the current move has been forced to the end of the turn
|
* Shows whether the current move has been forced to the end of the turn
|
||||||
* Needed for speed order, see {@linkcode MoveId.QUASH}
|
* Needed for speed order, see {@linkcode MoveId.QUASH}
|
||||||
* */
|
*/
|
||||||
public isForcedLast(): boolean {
|
public isForcedLast(): boolean {
|
||||||
return this.forcedLast;
|
return this.forcedLast;
|
||||||
}
|
}
|
||||||
@ -136,35 +119,37 @@ export class MovePhase extends BattlePhase {
|
|||||||
public start(): void {
|
public start(): void {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
console.log(MoveId[this.move.moveId]);
|
console.log(MoveId[this.move.moveId], enumValueToKey(MoveUseMode, this.useMode));
|
||||||
|
|
||||||
// Check if move is unusable (e.g. because it's out of PP due to a mid-turn Spite).
|
// Check if move is unusable (e.g. running out of PP due to a mid-turn Spite
|
||||||
|
// or the user no longer being on field), ending the phase early if not.
|
||||||
if (!this.canMove(true)) {
|
if (!this.canMove(true)) {
|
||||||
if (this.pokemon.isActive(true)) {
|
if (this.pokemon.isActive(true)) {
|
||||||
this.fail();
|
this.fail();
|
||||||
this.showMoveText();
|
this.showMoveText();
|
||||||
this.showFailedText();
|
this.showFailedText();
|
||||||
}
|
}
|
||||||
return this.end();
|
this.end();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pokemon.turnData.acted = true;
|
this.pokemon.turnData.acted = true;
|
||||||
|
|
||||||
// Reset hit-related turn data when starting follow-up moves (e.g. Metronomed moves, Dancer repeats)
|
// Reset hit-related turn data when starting follow-up moves (e.g. Metronomed moves, Dancer repeats)
|
||||||
if (this.followUp) {
|
if (isVirtual(this.useMode)) {
|
||||||
this.pokemon.turnData.hitsLeft = -1;
|
this.pokemon.turnData.hitsLeft = -1;
|
||||||
this.pokemon.turnData.hitCount = 0;
|
this.pokemon.turnData.hitCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check move to see if arena.ignoreAbilities should be true.
|
// Check move to see if arena.ignoreAbilities should be true.
|
||||||
if (!this.followUp || this.reflected) {
|
if (
|
||||||
if (
|
this.move.getMove().doesFlagEffectApply({
|
||||||
this.move
|
flag: MoveFlags.IGNORE_ABILITIES,
|
||||||
.getMove()
|
user: this.pokemon,
|
||||||
.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user: this.pokemon, isFollowUp: this.followUp })
|
isFollowUp: isVirtual(this.useMode), // Sunsteel strike and co. don't work when called indirectly
|
||||||
) {
|
})
|
||||||
globalScene.arena.setIgnoreAbilities(true, this.pokemon.getBattlerIndex());
|
) {
|
||||||
}
|
globalScene.arena.setIgnoreAbilities(true, this.pokemon.getBattlerIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resolveRedirectTarget();
|
this.resolveRedirectTarget();
|
||||||
@ -197,7 +182,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
(targets.length === 0 && !this.move.getMove().hasAttr("AddArenaTrapTagAttr")) ||
|
(targets.length === 0 && !this.move.getMove().hasAttr("AddArenaTrapTagAttr")) ||
|
||||||
(moveQueue.length && moveQueue[0].move === MoveId.NONE)
|
(moveQueue.length > 0 && moveQueue[0].move === MoveId.NONE)
|
||||||
) {
|
) {
|
||||||
this.showMoveText();
|
this.showMoveText();
|
||||||
this.showFailedText();
|
this.showFailedText();
|
||||||
@ -210,83 +195,98 @@ export class MovePhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles {@link StatusEffect.SLEEP Sleep}/{@link StatusEffect.PARALYSIS Paralysis}/{@link StatusEffect.FREEZE Freeze} rolls and side effects.
|
* Handles {@link StatusEffect.SLEEP | Sleep}/{@link StatusEffect.PARALYSIS | Paralysis}/{@link StatusEffect.FREEZE | Freeze} rolls and side effects.
|
||||||
*/
|
*/
|
||||||
protected resolvePreMoveStatusEffects(): void {
|
protected resolvePreMoveStatusEffects(): void {
|
||||||
if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) {
|
// Skip for follow ups/reflected moves, no status condition or post turn statuses (e.g. Poison/Toxic)
|
||||||
this.pokemon.status.incrementTurn();
|
if (!this.pokemon.status?.effect || this.pokemon.status.isPostTurn() || isIgnoreStatus(this.useMode)) {
|
||||||
let activated = false;
|
return;
|
||||||
let healed = false;
|
}
|
||||||
|
|
||||||
switch (this.pokemon.status.effect) {
|
if (
|
||||||
case StatusEffect.PARALYSIS:
|
this.useMode === MoveUseMode.INDIRECT &&
|
||||||
activated =
|
[StatusEffect.SLEEP, StatusEffect.FREEZE].includes(this.pokemon.status.effect)
|
||||||
(!this.pokemon.randBattleSeedInt(4) || Overrides.STATUS_ACTIVATION_OVERRIDE === true) &&
|
) {
|
||||||
Overrides.STATUS_ACTIVATION_OVERRIDE !== false;
|
// Dancer thaws out or wakes up a frozen/sleeping user prior to use
|
||||||
break;
|
this.pokemon.resetStatus(false);
|
||||||
case StatusEffect.SLEEP: {
|
return;
|
||||||
applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove());
|
}
|
||||||
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
|
|
||||||
applyAbAttrs(
|
|
||||||
ReduceStatusEffectDurationAbAttr,
|
|
||||||
this.pokemon,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
this.pokemon.status.effect,
|
|
||||||
turnsRemaining,
|
|
||||||
);
|
|
||||||
this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value;
|
|
||||||
healed = this.pokemon.status.sleepTurnsRemaining <= 0;
|
|
||||||
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case StatusEffect.FREEZE:
|
|
||||||
healed =
|
|
||||||
!!this.move
|
|
||||||
.getMove()
|
|
||||||
.findAttr(
|
|
||||||
attr =>
|
|
||||||
attr.is("HealStatusEffectAttr") &&
|
|
||||||
attr.selfTarget &&
|
|
||||||
(attr as unknown as HealStatusEffectAttr).isOfEffect(StatusEffect.FREEZE),
|
|
||||||
) ||
|
|
||||||
(!this.pokemon.randBattleSeedInt(5) && Overrides.STATUS_ACTIVATION_OVERRIDE !== true) ||
|
|
||||||
Overrides.STATUS_ACTIVATION_OVERRIDE === false;
|
|
||||||
|
|
||||||
activated = !healed;
|
this.pokemon.status.incrementTurn();
|
||||||
break;
|
|
||||||
|
/** Whether to prevent us from using the move */
|
||||||
|
let activated = false;
|
||||||
|
/** Whether to cure the status */
|
||||||
|
let healed = false;
|
||||||
|
|
||||||
|
switch (this.pokemon.status.effect) {
|
||||||
|
case StatusEffect.PARALYSIS:
|
||||||
|
activated =
|
||||||
|
(this.pokemon.randBattleSeedInt(4) === 0 || Overrides.STATUS_ACTIVATION_OVERRIDE === true) &&
|
||||||
|
Overrides.STATUS_ACTIVATION_OVERRIDE !== false;
|
||||||
|
break;
|
||||||
|
case StatusEffect.SLEEP: {
|
||||||
|
applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove());
|
||||||
|
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
|
||||||
|
applyAbAttrs(
|
||||||
|
"ReduceStatusEffectDurationAbAttr",
|
||||||
|
this.pokemon,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
this.pokemon.status.effect,
|
||||||
|
turnsRemaining,
|
||||||
|
);
|
||||||
|
this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value;
|
||||||
|
healed = this.pokemon.status.sleepTurnsRemaining <= 0;
|
||||||
|
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case StatusEffect.FREEZE:
|
||||||
|
healed =
|
||||||
|
!!this.move
|
||||||
|
.getMove()
|
||||||
|
.findAttr(
|
||||||
|
attr => attr.is("HealStatusEffectAttr") && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE),
|
||||||
|
) ||
|
||||||
|
(!this.pokemon.randBattleSeedInt(5) && Overrides.STATUS_ACTIVATION_OVERRIDE !== true) ||
|
||||||
|
Overrides.STATUS_ACTIVATION_OVERRIDE === false;
|
||||||
|
|
||||||
if (activated) {
|
activated = !healed;
|
||||||
this.cancel();
|
break;
|
||||||
globalScene.phaseManager.queueMessage(
|
}
|
||||||
getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)),
|
|
||||||
);
|
if (activated) {
|
||||||
globalScene.phaseManager.unshiftNew(
|
// Cancel move activation and play effect
|
||||||
"CommonAnimPhase",
|
this.cancel();
|
||||||
this.pokemon.getBattlerIndex(),
|
globalScene.phaseManager.queueMessage(
|
||||||
undefined,
|
getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)),
|
||||||
CommonAnim.POISON + (this.pokemon.status.effect - 1),
|
);
|
||||||
);
|
globalScene.phaseManager.unshiftNew(
|
||||||
} else if (healed) {
|
"CommonAnimPhase",
|
||||||
globalScene.phaseManager.queueMessage(
|
this.pokemon.getBattlerIndex(),
|
||||||
getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)),
|
undefined,
|
||||||
);
|
CommonAnim.POISON + (this.pokemon.status.effect - 1), // offset anim # by effect #
|
||||||
this.pokemon.resetStatus();
|
);
|
||||||
this.pokemon.updateInfo();
|
} else if (healed) {
|
||||||
}
|
// cure status and play effect
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)),
|
||||||
|
);
|
||||||
|
this.pokemon.resetStatus();
|
||||||
|
this.pokemon.updateInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lapse {@linkcode BattlerTagLapseType.PRE_MOVE PRE_MOVE} tags that trigger before a move is used, regardless of whether or not it failed.
|
* Lapse {@linkcode BattlerTagLapseType.PRE_MOVE | PRE_MOVE} tags that trigger before a move is used, regardless of whether or not it failed.
|
||||||
* Also lapse {@linkcode BattlerTagLapseType.MOVE MOVE} tags if the move should be successful.
|
* Also lapse {@linkcode BattlerTagLapseType.MOVE | MOVE} tags if the move is successful and not called indirectly.
|
||||||
*/
|
*/
|
||||||
protected lapsePreMoveAndMoveTags(): void {
|
protected lapsePreMoveAndMoveTags(): void {
|
||||||
this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE);
|
this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE);
|
||||||
|
|
||||||
// TODO: does this intentionally happen before the no targets/MoveId.NONE on queue cancellation case is checked?
|
// TODO: does this intentionally happen before the no targets/MoveId.NONE on queue cancellation case is checked?
|
||||||
if (!this.followUp && this.canMove() && !this.cancelled) {
|
// (In other words, check if truant can proc on a move w/o targets)
|
||||||
|
if (!isIgnoreStatus(this.useMode) && this.canMove() && !this.cancelled) {
|
||||||
this.pokemon.lapseTags(BattlerTagLapseType.MOVE);
|
this.pokemon.lapseTags(BattlerTagLapseType.MOVE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -294,11 +294,12 @@ export class MovePhase extends BattlePhase {
|
|||||||
protected useMove(): void {
|
protected useMove(): void {
|
||||||
const targets = this.getActiveTargetPokemon();
|
const targets = this.getActiveTargetPokemon();
|
||||||
const moveQueue = this.pokemon.getMoveQueue();
|
const moveQueue = this.pokemon.getMoveQueue();
|
||||||
|
const move = this.move.getMove();
|
||||||
|
|
||||||
// form changes happen even before we know that the move wll execute.
|
// form changes happen even before we know that the move wll execute.
|
||||||
globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger);
|
globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger);
|
||||||
|
|
||||||
const isDelayedAttack = this.move.getMove().hasAttr("DelayedAttackAttr");
|
const isDelayedAttack = move.hasAttr("DelayedAttackAttr");
|
||||||
if (isDelayedAttack) {
|
if (isDelayedAttack) {
|
||||||
// Check the player side arena if future sight is active
|
// Check the player side arena if future sight is active
|
||||||
const futureSightTags = globalScene.arena.findTags(t => t.tagType === ArenaTagType.FUTURE_SIGHT);
|
const futureSightTags = globalScene.arena.findTags(t => t.tagType === ArenaTagType.FUTURE_SIGHT);
|
||||||
@ -320,7 +321,8 @@ export class MovePhase extends BattlePhase {
|
|||||||
if (fail) {
|
if (fail) {
|
||||||
this.showMoveText();
|
this.showMoveText();
|
||||||
this.showFailedText();
|
this.showFailedText();
|
||||||
return this.end();
|
this.end();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,21 +339,21 @@ export class MovePhase extends BattlePhase {
|
|||||||
this.showMoveText();
|
this.showMoveText();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moveQueue.length > 0) {
|
// Clear out any two turn moves once they've been used.
|
||||||
// Using .shift here clears out two turn moves once they've been used
|
// TODO: Refactor move queues and remove this assignment;
|
||||||
this.ignorePp = moveQueue.shift()?.ignorePP ?? false;
|
// Move queues should be handled by the calling `CommandPhase` or a manager for it
|
||||||
}
|
// @ts-expect-error - useMode is readonly and shouldn't normally be assigned to
|
||||||
|
this.useMode = moveQueue.shift()?.useMode ?? this.useMode;
|
||||||
if (this.pokemon.getTag(BattlerTagType.CHARGING)?.sourceMove === this.move.moveId) {
|
if (this.pokemon.getTag(BattlerTagType.CHARGING)?.sourceMove === this.move.moveId) {
|
||||||
this.pokemon.lapseTag(BattlerTagType.CHARGING);
|
this.pokemon.lapseTag(BattlerTagType.CHARGING);
|
||||||
}
|
}
|
||||||
|
|
||||||
// "commit" to using the move, deducting PP.
|
if (!isIgnorePP(this.useMode)) {
|
||||||
if (!this.ignorePp) {
|
// "commit" to using the move, deducting PP.
|
||||||
const ppUsed = 1 + this.getPpIncreaseFromPressure(targets);
|
const ppUsed = 1 + this.getPpIncreaseFromPressure(targets);
|
||||||
|
|
||||||
this.move.usePp(ppUsed);
|
this.move.usePp(ppUsed);
|
||||||
globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed));
|
globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, move, this.move.ppUsed));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -365,8 +367,6 @@ export class MovePhase extends BattlePhase {
|
|||||||
* TODO: These steps are straightforward, but the implementation below is extremely convoluted.
|
* TODO: These steps are straightforward, but the implementation below is extremely convoluted.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const move = this.move.getMove();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move conditions assume the move has a single target
|
* Move conditions assume the move has a single target
|
||||||
* TODO: is this sustainable?
|
* TODO: is this sustainable?
|
||||||
@ -396,25 +396,24 @@ export class MovePhase extends BattlePhase {
|
|||||||
*/
|
*/
|
||||||
if (success) {
|
if (success) {
|
||||||
const move = this.move.getMove();
|
const move = this.move.getMove();
|
||||||
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, move);
|
applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, move);
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"MoveEffectPhase",
|
"MoveEffectPhase",
|
||||||
this.pokemon.getBattlerIndex(),
|
this.pokemon.getBattlerIndex(),
|
||||||
this.targets,
|
this.targets,
|
||||||
move,
|
move,
|
||||||
this.reflected,
|
this.useMode,
|
||||||
this.move.virtual,
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if ([MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)) {
|
if ([MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)) {
|
||||||
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove());
|
applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pokemon.pushMoveHistory({
|
this.pokemon.pushMoveHistory({
|
||||||
move: this.move.moveId,
|
move: this.move.moveId,
|
||||||
targets: this.targets,
|
targets: this.targets,
|
||||||
result: MoveResult.FAIL,
|
result: MoveResult.FAIL,
|
||||||
virtual: this.move.virtual,
|
useMode: this.useMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
const failureMessage = move.getFailedText(this.pokemon, targets[0], move);
|
const failureMessage = move.getFailedText(this.pokemon, targets[0], move);
|
||||||
@ -434,10 +433,12 @@ export class MovePhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle Dancer, which triggers immediately after a move is used (rather than waiting on `this.end()`).
|
// Handle Dancer, which triggers immediately after a move is used (rather than waiting on `this.end()`).
|
||||||
// Note that the `!this.followUp` check here prevents an infinite Dancer loop.
|
// Note the MoveUseMode check here prevents an infinite Dancer loop.
|
||||||
if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) {
|
const dancerModes: MoveUseMode[] = [MoveUseMode.INDIRECT, MoveUseMode.REFLECTED] as const;
|
||||||
|
if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !dancerModes.includes(this.useMode)) {
|
||||||
|
// TODO: Fix in dancer PR to move to MEP for hit checks
|
||||||
globalScene.getField(true).forEach(pokemon => {
|
globalScene.getField(true).forEach(pokemon => {
|
||||||
applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets);
|
applyPostMoveUsedAbAttrs("PostMoveUsedAbAttr", pokemon, this.move, this.pokemon, this.targets);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -447,23 +448,16 @@ export class MovePhase extends BattlePhase {
|
|||||||
const move = this.move.getMove();
|
const move = this.move.getMove();
|
||||||
const targets = this.getActiveTargetPokemon();
|
const targets = this.getActiveTargetPokemon();
|
||||||
|
|
||||||
if (move.applyConditions(this.pokemon, targets[0], move)) {
|
this.showMoveText();
|
||||||
// Protean and Libero apply on the charging turn of charge moves
|
|
||||||
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove());
|
|
||||||
|
|
||||||
this.showMoveText();
|
// Conditions currently assume single target
|
||||||
globalScene.phaseManager.unshiftNew(
|
// TODO: Is this sustainable?
|
||||||
"MoveChargePhase",
|
if (!move.applyConditions(this.pokemon, targets[0], move)) {
|
||||||
this.pokemon.getBattlerIndex(),
|
|
||||||
this.targets[0],
|
|
||||||
this.move,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.pokemon.pushMoveHistory({
|
this.pokemon.pushMoveHistory({
|
||||||
move: this.move.moveId,
|
move: this.move.moveId,
|
||||||
targets: this.targets,
|
targets: this.targets,
|
||||||
result: MoveResult.FAIL,
|
result: MoveResult.FAIL,
|
||||||
virtual: this.move.virtual,
|
useMode: this.useMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
const failureMessage = move.getFailedText(this.pokemon, targets[0], move);
|
const failureMessage = move.getFailedText(this.pokemon, targets[0], move);
|
||||||
@ -472,7 +466,19 @@ export class MovePhase extends BattlePhase {
|
|||||||
|
|
||||||
// Remove the user from its semi-invulnerable state (if applicable)
|
// Remove the user from its semi-invulnerable state (if applicable)
|
||||||
this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Protean and Libero apply on the charging turn of charge moves
|
||||||
|
applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove());
|
||||||
|
|
||||||
|
globalScene.phaseManager.unshiftNew(
|
||||||
|
"MoveChargePhase",
|
||||||
|
this.pokemon.getBattlerIndex(),
|
||||||
|
this.targets[0],
|
||||||
|
this.move,
|
||||||
|
this.useMode,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -483,7 +489,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
"MoveEndPhase",
|
"MoveEndPhase",
|
||||||
this.pokemon.getBattlerIndex(),
|
this.pokemon.getBattlerIndex(),
|
||||||
this.getActiveTargetPokemon(),
|
this.getActiveTargetPokemon(),
|
||||||
this.followUp,
|
isVirtual(this.useMode),
|
||||||
);
|
);
|
||||||
|
|
||||||
super.end();
|
super.end();
|
||||||
@ -498,7 +504,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
public getPpIncreaseFromPressure(targets: Pokemon[]): number {
|
public getPpIncreaseFromPressure(targets: Pokemon[]): number {
|
||||||
const foesWithPressure = this.pokemon
|
const foesWithPressure = this.pokemon
|
||||||
.getOpponents()
|
.getOpponents()
|
||||||
.filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr(IncreasePpAbAttr));
|
.filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr("IncreasePpAbAttr"));
|
||||||
return foesWithPressure.length;
|
return foesWithPressure.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,7 +522,9 @@ export class MovePhase extends BattlePhase {
|
|||||||
globalScene
|
globalScene
|
||||||
.getField(true)
|
.getField(true)
|
||||||
.filter(p => p !== this.pokemon)
|
.filter(p => p !== this.pokemon)
|
||||||
.forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget, this.pokemon));
|
.forEach(p =>
|
||||||
|
applyAbAttrs("RedirectMoveAbAttr", p, null, false, this.move.moveId, redirectTarget, this.pokemon),
|
||||||
|
);
|
||||||
|
|
||||||
/** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */
|
/** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */
|
||||||
let redirectedByAbility = currentTarget !== redirectTarget.value;
|
let redirectedByAbility = currentTarget !== redirectTarget.value;
|
||||||
@ -545,17 +553,17 @@ export class MovePhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) {
|
if (this.pokemon.hasAbilityWithAttr("BlockRedirectAbAttr")) {
|
||||||
redirectTarget.value = currentTarget;
|
redirectTarget.value = currentTarget;
|
||||||
// TODO: Ability displays should be handled by the ability
|
// TODO: Ability displays should be handled by the ability
|
||||||
globalScene.phaseManager.queueAbilityDisplay(
|
globalScene.phaseManager.queueAbilityDisplay(
|
||||||
this.pokemon,
|
this.pokemon,
|
||||||
this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr),
|
this.pokemon.getPassiveAbility().hasAttr("BlockRedirectAbAttr"),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
globalScene.phaseManager.queueAbilityDisplay(
|
globalScene.phaseManager.queueAbilityDisplay(
|
||||||
this.pokemon,
|
this.pokemon,
|
||||||
this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr),
|
this.pokemon.getPassiveAbility().hasAttr("BlockRedirectAbAttr"),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -613,7 +621,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
protected handlePreMoveFailures(): void {
|
protected handlePreMoveFailures(): void {
|
||||||
if (this.cancelled || this.failed) {
|
if (this.cancelled || this.failed) {
|
||||||
if (this.failed) {
|
if (this.failed) {
|
||||||
const ppUsed = this.ignorePp ? 0 : 1;
|
const ppUsed = isIgnorePP(this.useMode) ? 0 : 1;
|
||||||
|
|
||||||
if (ppUsed) {
|
if (ppUsed) {
|
||||||
this.move.usePp();
|
this.move.usePp();
|
||||||
@ -630,6 +638,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
move: MoveId.NONE,
|
move: MoveId.NONE,
|
||||||
result: MoveResult.FAIL,
|
result: MoveResult.FAIL,
|
||||||
targets: this.targets,
|
targets: this.targets,
|
||||||
|
useMode: this.useMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
||||||
@ -653,7 +662,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t(this.reflected ? "battle:magicCoatActivated" : "battle:useMove", {
|
i18next.t(isReflected(this.useMode) ? "battle:magicCoatActivated" : "battle:useMove", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon),
|
||||||
moveName: this.move.getName(),
|
moveName: this.move.getName(),
|
||||||
}),
|
}),
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user