Merge remote-tracking branch 'upstream/beta' into more-tests

This commit is contained in:
Bertie690 2025-06-17 17:11:13 -04:00
commit e4115a362b
472 changed files with 7257 additions and 5848 deletions

View File

@ -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

View File

@ -1,6 +1,7 @@
VITE_BYPASS_LOGIN=1 VITE_BYPASS_LOGIN=1
VITE_BYPASS_TUTORIAL=0 VITE_BYPASS_TUTORIAL=0
VITE_SERVER_URL=http://localhost:8001 VITE_SERVER_URL=http://localhost:8001
# IDs for discord/google auth go unused due to VITE_BYPASS_LOGIN
VITE_DISCORD_CLIENT_ID=1234567890 VITE_DISCORD_CLIENT_ID=1234567890
VITE_GOOGLE_CLIENT_ID=1234567890 VITE_GOOGLE_CLIENT_ID=1234567890
VITE_I18N_DEBUG=0 VITE_I18N_DEBUG=0

View File

@ -14,13 +14,16 @@ Make sure the title includes categorization (choose the one that best fits):
- [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.)
- [i18n]: If the PR is primarily adding/changing locale keys or key usage (may come with an associated locales PR)
- [Docs]: If the PR is adding or modifying documentation (such as tsdocs/code comments)
- [GitHub]: For changes to GitHub workflows/templates/etc - [GitHub]: For changes to GitHub workflows/templates/etc
- [Misc]: If no other category fits the PR - [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
View 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

View File

@ -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

View File

@ -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"
}
}
}
} }
] ]
} }

View File

@ -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",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

@ -1 +1 @@
Subproject commit 4dab23d6a78b6cf32db43c9953e3c2000f448007 Subproject commit fade123e20ff951e199d7c0466686fe8c5511643

View File

@ -48,7 +48,7 @@ async function promptTestType() {
{ {
type: "list", type: "list",
name: "selectedOption", name: "selectedOption",
message: "What type of test would you like to create:", message: "What type of test would you like to create?",
choices: [...choices.map(choice => ({ name: choice.label, value: choice })), "EXIT"], choices: [...choices.map(choice => ({ name: choice.label, value: choice })), "EXIT"],
}, },
]); ]);

View File

@ -24,7 +24,7 @@ describe("{{description}}", () => {
game.override game.override
.ability(AbilityId.BALL_FETCH) .ability(AbilityId.BALL_FETCH)
.battleStyle("single") .battleStyle("single")
.disableCrits() .criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH) .enemyMoveset(MoveId.SPLASH)

View File

@ -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]>;
};

View 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[];
};

View File

@ -4,7 +4,8 @@ import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
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 { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { allSpecies } from "#app/data/data-lists";
import { import {
fixedInt, fixedInt,
getIvsFromId, getIvsFromId,
@ -58,23 +59,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 +138,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 +1256,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 +1461,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 +1636,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
@ -2100,12 +2095,15 @@ export default class BattleScene extends SceneBase {
} }
getMaxExpLevel(ignoreLevelCap = false): number { getMaxExpLevel(ignoreLevelCap = false): number {
if (Overrides.LEVEL_CAP_OVERRIDE > 0) { const capOverride = Overrides.LEVEL_CAP_OVERRIDE ?? 0;
return Overrides.LEVEL_CAP_OVERRIDE; if (capOverride > 0) {
return capOverride;
} }
if (ignoreLevelCap || Overrides.LEVEL_CAP_OVERRIDE < 0) {
if (ignoreLevelCap || capOverride < 0) {
return Number.MAX_SAFE_INTEGER; return Number.MAX_SAFE_INTEGER;
} }
const waveIndex = Math.ceil((this.currentBattle?.waveIndex || 1) / 10) * 10; const waveIndex = Math.ceil((this.currentBattle?.waveIndex || 1) / 10) * 10;
const difficultyWaveIndex = this.gameMode.getWaveForDifficulty(waveIndex); const difficultyWaveIndex = this.gameMode.getWaveForDifficulty(waveIndex);
const baseLevel = (1 + difficultyWaveIndex / 2 + Math.pow(difficultyWaveIndex / 25, 2)) * 1.2; const baseLevel = (1 + difficultyWaveIndex / 2 + Math.pow(difficultyWaveIndex / 25, 2)) * 1.2;
@ -2745,7 +2743,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 +2783,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 +2812,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) {
@ -2969,6 +2967,13 @@ export default class BattleScene extends SceneBase {
) { ) {
modifiers.splice(m--, 1); modifiers.splice(m--, 1);
} }
if (
modifier instanceof PokemonHeldItemModifier &&
!isNullOrUndefined(modifier.getSpecies()) &&
!this.getPokemonById(modifier.pokemonId)?.hasSpecies(modifier.getSpecies()!)
) {
modifiers.splice(m--, 1);
}
} }
for (const modifier of modifiers) { for (const modifier of modifiers) {
if (modifier instanceof PersistentModifier) { if (modifier instanceof PersistentModifier) {
@ -3498,17 +3503,13 @@ export default class BattleScene extends SceneBase {
sessionEncounterRate + sessionEncounterRate +
Math.min(currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT / 2); Math.min(currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT / 2);
const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) const successRate = Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE ?? favoredEncounterRate;
? favoredEncounterRate
: Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!;
// If the most recent ME was 3 or fewer waves ago, can never spawn a ME // MEs can only spawn 3 or more waves after the previous ME, barring overrides
const canSpawn = const canSpawn =
encounteredEvents.length === 0 || encounteredEvents.length === 0 || waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex > 3;
waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex > 3 ||
!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE);
if (canSpawn) { if (canSpawn || Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE !== null) {
let roll = MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT; let roll = MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT;
// Always rolls the check on the same offset to ensure no RNG changes from reloading session // Always rolls the check on the same offset to ensure no RNG changes from reloading session
this.executeWithSeedOffset( this.executeWithSeedOffset(

View File

@ -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 => {

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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

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

View File

@ -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;
@ -1442,8 +1435,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"));
} }
} }
@ -1455,7 +1448,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));
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -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";

View File

@ -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();

View File

@ -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;
} }
@ -681,6 +654,7 @@ export class InterruptedTag extends BattlerTag {
move: MoveId.NONE, move: MoveId.NONE,
result: MoveResult.OTHER, result: MoveResult.OTHER,
targets: [], targets: [],
useMode: MoveUseMode.NORMAL,
}); });
} }
@ -946,7 +920,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(
@ -957,7 +931,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(),
@ -1006,43 +980,46 @@ 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 (movePhase?.is("MovePhase")) { if (lapseType !== BattlerTagLapseType.PRE_MOVE || !movePhase?.is("MovePhase")) {
return false;
}
const move = movePhase.move.getMove(); const move = movePhase.move.getMove();
const weather = globalScene.arena.weather; const weather = globalScene.arena.weather;
if ( if (
pokemon.getMoveType(move) === PokemonType.FIRE && pokemon.getMoveType(move) !== PokemonType.FIRE ||
!(weather && weather.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed()) (weather?.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed()) // Heavy rain takes priority over powder
) { ) {
movePhase.fail(); return true;
}
// Disable the target's fire type move and damage it (subject to Magic Guard)
movePhase.showMoveText(); movePhase.showMoveText();
movePhase.fail();
const idx = pokemon.getBattlerIndex(); const idx = pokemon.getBattlerIndex();
globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER); globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER);
const cancelDamage = new BooleanHolder(false); const cancelDamage = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelDamage); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelDamage);
if (!cancelDamage.value) { if (!cancelDamage.value) {
pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
} }
// "When the flame touched the powder\non the Pokémon, it exploded!" // "When the flame touched the powder\non the Pokémon, it exploded!"
globalScene.phaseManager.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name })); globalScene.phaseManager.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name }));
}
}
return true; return true;
} }
return super.lapse(pokemon, lapseType);
}
} }
export class NightmareTag extends BattlerTag { export class NightmareTag extends BattlerTag {
@ -1083,7 +1060,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 });
@ -1136,34 +1113,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(
@ -1179,7 +1144,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,
),
); );
} }
} }
@ -1442,7 +1413,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 });
@ -1481,16 +1452,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),
@ -1685,7 +1646,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,
@ -1915,13 +1876,19 @@ 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 lastMove = pokemon.getLastXMoves()[0];
if (!lastMove) {
// Don't interrupt move if last move was `Moves.NONE` OR no prior move was found
return true;
}
// Interrupt move usage in favor of slacking off
const passive = pokemon.getAbility().id !== AbilityId.TRUANT; const passive = pokemon.getAbility().id !== AbilityId.TRUANT;
const lastMove = pokemon.getLastXMoves().find(() => true);
if (lastMove && lastMove.move !== MoveId.NONE) {
(globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel(); (globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel();
// TODO: Ability displays should be handled by the ability // TODO: Ability displays should be handled by the ability
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true); globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
@ -1931,7 +1898,6 @@ export class TruantTag extends AbilityBattlerTag {
}), }),
); );
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false); globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
}
return true; return true;
} }
@ -2281,7 +2247,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);
@ -2335,7 +2301,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 });
@ -2670,7 +2636,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 });
@ -3060,8 +3026,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);

View File

@ -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(),

View File

@ -4,7 +4,8 @@ import i18next from "i18next";
import type { DexAttrProps, GameData } from "#app/system/game-data"; import type { DexAttrProps, GameData } from "#app/system/game-data";
import { defaultStarterSpecies } from "#app/constants"; import { defaultStarterSpecies } from "#app/constants";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "./moves/pokemon-move"; import { PokemonMove } from "./moves/pokemon-move";
@ -21,7 +22,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";

View File

@ -5,7 +5,8 @@ import { PlayerPokemon } from "#app/field/pokemon";
import type { Starter } from "#app/ui/starter-select-ui-handler"; import type { Starter } from "#app/ui/starter-select-ui-handler";
import { randSeedGauss, randSeedInt, randSeedItem, getEnumValues } from "#app/utils/common"; import { randSeedGauss, randSeedInt, randSeedItem, getEnumValues } from "#app/utils/common";
import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; import type { PokemonSpeciesForm } from "#app/data/pokemon-species";
import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import PokemonSpecies, { getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import { BiomeId } from "#enums/biome-id"; import { BiomeId } from "#enums/biome-id";

View File

@ -1,5 +1,11 @@
import type { Ability } from "./abilities/ability-class"; import type PokemonSpecies from "#app/data/pokemon-species";
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[] = [];
export const allSpecies: PokemonSpecies[] = [];
// TODO: Figure out what this is used for and provide an appropriate tsdoc comment
export const modifierTypes = {} as ModifierTypes;

View File

@ -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",

View 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"],
},
};

View File

@ -1,7 +1,7 @@
import type BattleScene from "#app/battle-scene"; import type BattleScene from "#app/battle-scene";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { VariantTier } from "#enums/variant-tier"; import { VariantTier } from "#enums/variant-tier";
import { randInt, randomString, randSeedInt, getIvsFromId } from "#app/utils/common"; import { randInt, randomString, randSeedInt, getIvsFromId } from "#app/utils/common";

View File

@ -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

View File

@ -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);
} }
} }

View File

@ -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 */

View File

@ -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";
@ -22,7 +22,7 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { randInt } from "#app/utils/common"; import { randInt } from "#app/utils/common";
@ -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);

View File

@ -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";
@ -18,7 +18,7 @@ import {
} from "#app/data/mystery-encounters/mystery-encounter-requirements"; } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";

View File

@ -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";

View File

@ -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";

View File

@ -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";
@ -22,7 +22,7 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { import {
applyAbilityOverrideToPokemon, applyAbilityOverrideToPokemon,
@ -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,
}, },
); );

View File

@ -19,14 +19,14 @@ import {
getEncounterPokemonLevelForWave, getEncounterPokemonLevelForWave,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
} 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/utils/pokemon-utils";
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon } 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();

View File

@ -3,8 +3,8 @@ 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/utils/pokemon-utils";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";

View File

@ -15,7 +15,7 @@ import {
updatePlayerMoney, updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-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 { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";
@ -21,7 +20,7 @@ import {
TypeRequirement, TypeRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements"; } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
@ -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

View File

@ -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";

View File

@ -14,7 +14,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { FieldPosition } from "#enums/field-position"; import { FieldPosition } from "#enums/field-position";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -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";

View File

@ -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";
@ -19,7 +16,8 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { allSpecies } from "#app/data/data-lists";
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";

View File

@ -1,4 +1,4 @@
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";

View File

@ -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";

View File

@ -14,9 +14,9 @@ import {
getHighestLevelPlayerPokemon, getHighestLevelPlayerPokemon,
koPlayerPokemon, koPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
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";

View File

@ -17,7 +17,7 @@ import { PokeballType } from "#enums/pokeball";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { NumberHolder, randSeedInt } from "#app/utils/common"; import { NumberHolder, randSeedInt } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { import {
doPlayerFlee, doPlayerFlee,

View File

@ -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";

View File

@ -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";
@ -24,13 +24,14 @@ 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 { AiType } from "#enums/ai-type"; import { AiType } from "#enums/ai-type";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { BerryType } from "#enums/berry-type"; 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]);
}, },

View File

@ -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";

View File

@ -15,7 +15,7 @@ import { BiomeId } from "#enums/biome-id";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import i18next from "i18next"; import i18next from "i18next";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
@ -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";

View File

@ -15,7 +15,7 @@ import {
getSpriteKeysFromPokemon, getSpriteKeysFromPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";

View File

@ -8,12 +8,12 @@ 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";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
@ -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,
}, },
); );

View File

@ -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";
@ -17,18 +17,18 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { PokemonType } from "#enums/pokemon-type"; 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");

View File

@ -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 {

View File

@ -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/utils/pokemon-utils";
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]);

View File

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

View File

@ -19,13 +19,14 @@ import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common"; import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { allSpecies } from "#app/data/data-lists";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#app/modifier/modifier";
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 +35,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";

View File

@ -1,6 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { allAbilities } from "../data-lists"; import { allAbilities } from "../data-lists";
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { pokemonFormChanges } from "#app/data/pokemon-forms"; import { pokemonFormChanges } from "#app/data/pokemon-forms";
import { SpeciesFormChangeItemTrigger } from "../pokemon-forms/form-change-triggers"; import { SpeciesFormChangeItemTrigger } from "../pokemon-forms/form-change-triggers";
@ -11,12 +10,11 @@ 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";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { SpeciesFormKey } from "#enums/species-form-key";
import { TimeOfDay } from "#enums/time-of-day"; import { TimeOfDay } from "#enums/time-of-day";
export interface EncounterRequirement { export interface EncounterRequirement {
@ -272,7 +270,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 +292,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 +358,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 +424,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 +464,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 +502,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 +556,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 +607,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 +663,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 +708,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 +783,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 {
@ -834,70 +832,6 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
} }
} }
export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
requiredEvolutionItem: EvolutionItem[];
minNumberOfPokemon: number;
invertQuery: boolean;
constructor(evolutionItems: EvolutionItem | EvolutionItem[], minNumberOfPokemon = 1, invertQuery = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [evolutionItems];
}
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon) || this.requiredEvolutionItem?.length < 0) {
return false;
}
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
}
filterByEvo(pokemon, evolutionItem) {
if (
pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) &&
pokemonEvolutions[pokemon.species.speciesId].filter(
e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)),
).length &&
pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX
) {
return true;
}
return (
pokemon.isFusion() &&
pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) &&
pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(
e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)),
).length &&
pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX
);
}
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter(
pokemon =>
this.requiredEvolutionItem.filter(evolutionItem => this.filterByEvo(pokemon, evolutionItem)).length > 0,
);
}
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed evolutionItemss
return partyPokemon.filter(
pokemon =>
this.requiredEvolutionItem.filter(evolutionItems => this.filterByEvo(pokemon, evolutionItems)).length === 0,
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
const requiredItems = this.requiredEvolutionItem.filter(evoItem => this.filterByEvo(pokemon, evoItem));
if (requiredItems.length > 0) {
return ["evolutionItem", EvolutionItem[requiredItems[0]]];
}
return ["evolutionItem", ""];
}
}
export class HeldItemRequirement extends EncounterPokemonRequirement { export class HeldItemRequirement extends EncounterPokemonRequirement {
requiredHeldItemModifiers: string[]; requiredHeldItemModifiers: string[];
minNumberOfPokemon: number; minNumberOfPokemon: number;
@ -908,7 +842,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 +906,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;
} }

View File

@ -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";

View File

@ -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 });
} }

View File

@ -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,

View File

@ -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;

View File

@ -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";
@ -52,7 +48,7 @@ import type HeldModifierConfig from "#app/@types/held-modifier-config";
import type { Variant } from "#app/sprites/variant"; import type { Variant } from "#app/sprites/variant";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { getNatureName } from "#app/data/nature"; import { getNatureName } from "#app/data/nature";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
@ -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.

View File

@ -20,7 +20,7 @@ import { PartyUiMode } from "#app/ui/party-ui-handler";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { import {
getEncounterText, getEncounterText,
@ -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";

View 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;
}

View File

@ -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

View File

@ -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");
} }

View File

@ -42,6 +42,8 @@ import { starterPassiveAbilities } from "#app/data/balance/passives";
import { loadPokemonVariantAssets } from "#app/sprites/pokemon-sprite"; import { loadPokemonVariantAssets } from "#app/sprites/pokemon-sprite";
import { hasExpSprite } from "#app/sprites/sprite-utils"; import { hasExpSprite } from "#app/sprites/sprite-utils";
import { Gender } from "./gender"; import { Gender } from "./gender";
import { allSpecies } from "#app/data/data-lists";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
export enum Region { export enum Region {
NORMAL, NORMAL,
@ -82,23 +84,6 @@ export const normalForm: SpeciesId[] = [
SpeciesId.CALYREX, SpeciesId.CALYREX,
]; ];
/**
* Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given
* @param species The species to fetch
* @returns The associated {@linkcode PokemonSpecies} object
*/
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)) {
// Pick a random species from the list
species = species[Math.floor(Math.random() * species.length)];
}
if (species >= 2000) {
return allSpecies.find(s => s.speciesId === species)!; // TODO: is this bang correct?
}
return allSpecies[species - 1];
}
export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): PokemonSpeciesForm { export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): PokemonSpeciesForm {
const retSpecies: PokemonSpecies = const retSpecies: PokemonSpecies =
species >= 2000 species >= 2000
@ -1448,8 +1433,6 @@ export function getPokerusStarters(): PokemonSpecies[] {
return pokerusStarters; return pokerusStarters;
} }
export const allSpecies: PokemonSpecies[] = [];
// biome-ignore format: manually formatted // biome-ignore format: manually formatted
export function initSpecies() { export function initSpecies() {
allSpecies.push( allSpecies.push(

View File

@ -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/utils/pokemon-utils";
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.

View File

@ -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)) {

View File

@ -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,
} }

View File

@ -0,0 +1,6 @@
/**
* Enum representation of the phase types held by implementations of {@linkcode PhasePriorityQueue}
*/
export enum DynamicPhaseType {
POST_SUMMON
}

View File

@ -0,0 +1,7 @@
export enum ModifierPoolType {
PLAYER,
WILD,
TRAINER,
ENEMY_BUFF,
DAILY_STARTER
}

149
src/enums/move-use-mode.ts Normal file
View 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
View File

@ -0,0 +1,7 @@
export enum Unlockables {
ENDLESS_MODE,
MINI_BLACK_HOLE,
SPLICED_ENDLESS_MODE,
EVIOLITE
}

View File

@ -3,7 +3,7 @@ import type { BiomeTierTrainerPools, PokemonPools } from "#app/data/balance/biom
import { biomePokemonPools, BiomePoolTier, biomeTrainerPools } from "#app/data/balance/biomes"; import { biomePokemonPools, BiomePoolTier, biomeTrainerPools } from "#app/data/balance/biomes";
import { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from "#app/utils/common"; import { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { import {
getTerrainClearMessage, getTerrainClearMessage,
getTerrainStartMessage, getTerrainStartMessage,
@ -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:
@ -278,6 +275,7 @@ export class Arena {
} }
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

View File

@ -1,6 +1,6 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import Pokemon from "./pokemon"; import Pokemon from "./pokemon";
import { fixedInt, randInt } from "#app/utils/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);
} }

View File

@ -15,12 +15,8 @@ import { allMoves } from "#app/data/data-lists";
import { MoveTarget } from "#enums/MoveTarget"; import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; import type { PokemonSpeciesForm } from "#app/data/pokemon-species";
import { import { default as PokemonSpecies, getFusedSpeciesName, getPokemonSpeciesForm } from "#app/data/pokemon-species";
default as PokemonSpecies, import { getPokemonSpecies } from "#app/utils/pokemon-utils";
getFusedSpeciesName,
getPokemonSpecies,
getPokemonSpeciesForm,
} from "#app/data/pokemon-species";
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
import { import {
NumberHolder, NumberHolder,
@ -38,9 +34,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";
@ -79,11 +75,12 @@ import {
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { Status, getRandomStatus } from "#app/data/status-effect"; import { Status, getRandomStatus } from "#app/data/status-effect";
import type { SpeciesFormEvolution, SpeciesEvolutionCondition } from "#app/data/balance/pokemon-evolutions"; import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import { import {
pokemonEvolutions, pokemonEvolutions,
pokemonPrevolutions, pokemonPrevolutions,
FusionSpeciesFormEvolution, FusionSpeciesFormEvolution,
validateShedinjaEvo,
} from "#app/data/balance/pokemon-evolutions"; } from "#app/data/balance/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms";
import { import {
@ -111,61 +108,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 +151,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 +182,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 +321,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;
@ -406,7 +367,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.metWave = dataSource.metWave ?? (this.metBiome === -1 ? -1 : 0); this.metWave = dataSource.metWave ?? (this.metBiome === -1 ? -1 : 0);
this.pauseEvolutions = dataSource.pauseEvolutions; this.pauseEvolutions = dataSource.pauseEvolutions;
this.pokerus = !!dataSource.pokerus; this.pokerus = !!dataSource.pokerus;
this.evoCounter = dataSource.evoCounter ?? 0;
this.fusionSpecies = this.fusionSpecies =
dataSource.fusionSpecies instanceof PokemonSpecies dataSource.fusionSpecies instanceof PokemonSpecies
? dataSource.fusionSpecies ? dataSource.fusionSpecies
@ -1392,8 +1352,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
/** /**
* Calculate the critical-hit stage of a move used against this pokemon by * Calculate the critical-hit stage of a move used **against** this pokemon by
* the given source * the given source.
*
* @param source - The {@linkcode Pokemon} using the move * @param source - The {@linkcode Pokemon} using the move
* @param move - The {@linkcode Move} being used * @param move - The {@linkcode Move} being used
* @returns The final critical-hit stage value * @returns The final critical-hit stage value
@ -1403,14 +1364,12 @@ 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) { // Dragon cheer only gives +1 crit stage to non-dragon types
critStage.value += critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 2 : 1; critStage.value +=
} else { critBoostTag instanceof DragonCheerTag && !critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 1 : 2;
critStage.value += 2;
}
} }
console.log(`crit stage: +${critStage.value}`); console.log(`crit stage: +${critStage.value}`);
@ -1464,19 +1423,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 +1770,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 +2024,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 +2113,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 +2124,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 +2168,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 +2194,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);
} }
@ -2316,7 +2281,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;
@ -2338,7 +2303,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
@ -2403,16 +2368,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),
); );
} }
} }
@ -2427,7 +2392,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)) {
@ -2480,8 +2445,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) {
@ -2566,34 +2531,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (pokemonEvolutions.hasOwnProperty(this.species.speciesId)) { if (pokemonEvolutions.hasOwnProperty(this.species.speciesId)) {
const evolutions = pokemonEvolutions[this.species.speciesId]; const evolutions = pokemonEvolutions[this.species.speciesId];
for (const e of evolutions) { for (const e of evolutions) {
if ( if (e.validate(this)) {
!e.item &&
this.level >= e.level &&
(isNullOrUndefined(e.preFormKey) || this.getFormKey() === e.preFormKey)
) {
if (e.condition === null || (e.condition as SpeciesEvolutionCondition).predicate(this)) {
return e; return e;
} }
} }
} }
}
if (this.isFusion() && this.fusionSpecies && pokemonEvolutions.hasOwnProperty(this.fusionSpecies.speciesId)) { if (this.isFusion() && this.fusionSpecies && pokemonEvolutions.hasOwnProperty(this.fusionSpecies.speciesId)) {
const fusionEvolutions = pokemonEvolutions[this.fusionSpecies.speciesId].map( const fusionEvolutions = pokemonEvolutions[this.fusionSpecies.speciesId].map(
e => new FusionSpeciesFormEvolution(this.species.speciesId, e), e => new FusionSpeciesFormEvolution(this.species.speciesId, e),
); );
for (const fe of fusionEvolutions) { for (const fe of fusionEvolutions) {
if ( if (fe.validate(this)) {
!fe.item &&
this.level >= fe.level &&
(isNullOrUndefined(fe.preFormKey) || this.getFusionFormKey() === fe.preFormKey)
) {
if (fe.condition === null || (fe.condition as SpeciesEvolutionCondition).predicate(this)) {
return fe; return fe;
} }
} }
} }
}
return null; return null;
} }
@ -2832,17 +2785,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
public trySetShinySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean { public trySetShinySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean {
if (!this.shiny) { if (!this.shiny) {
const shinyThreshold = new NumberHolder(BASE_SHINY_CHANCE); const shinyThreshold = new NumberHolder(thresholdOverride ?? BASE_SHINY_CHANCE);
if (thresholdOverride === undefined || applyModifiersToOverride) { if (applyModifiersToOverride) {
if (thresholdOverride !== undefined && applyModifiersToOverride) {
shinyThreshold.value = thresholdOverride;
}
if (timedEventManager.isEventActive()) { if (timedEventManager.isEventActive()) {
shinyThreshold.value *= timedEventManager.getShinyMultiplier(); shinyThreshold.value *= timedEventManager.getShinyMultiplier();
} }
globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
} else {
shinyThreshold.value = thresholdOverride;
} }
this.shiny = randSeedInt(65536) < shinyThreshold.value; this.shiny = randSeedInt(65536) < shinyThreshold.value;
@ -2911,16 +2859,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!this.species.abilityHidden) { if (!this.species.abilityHidden) {
return false; return false;
} }
const haThreshold = new NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); const haThreshold = new NumberHolder(thresholdOverride ?? BASE_HIDDEN_ABILITY_CHANCE);
if (thresholdOverride === undefined || applyModifiersToOverride) { if (applyModifiersToOverride) {
if (thresholdOverride !== undefined && applyModifiersToOverride) {
haThreshold.value = thresholdOverride;
}
if (!this.hasTrainer()) { if (!this.hasTrainer()) {
globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold); globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold);
} }
} else {
haThreshold.value = thresholdOverride;
} }
if (randSeedInt(65536) < haThreshold.value) { if (randSeedInt(65536) < haThreshold.value) {
@ -3184,7 +3127,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) {
@ -3237,7 +3180,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
@ -3432,7 +3375,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);
@ -3471,8 +3414,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);
@ -3492,16 +3435,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;
@ -3616,7 +3576,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) {
@ -3765,7 +3725,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
); );
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
applyPreAttackAbAttrs( applyPreAttackAbAttrs(
AddSecondStrikeAbAttr, "AddSecondStrikeAbAttr",
source, source,
this, this,
move, move,
@ -3783,7 +3743,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]
@ -3804,7 +3764,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;
@ -3868,7 +3828,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 */
@ -3881,12 +3841,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);
} }
} }
@ -3894,7 +3854,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)
@ -3918,33 +3878,39 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}; };
} }
/** Calculate whether the given move critically hits this pokemon /**
* Determine whether the given move will score a critical hit **against** this Pokemon.
* @param source - The {@linkcode Pokemon} using the move * @param source - The {@linkcode Pokemon} using the move
* @param move - The {@linkcode Move} being used * @param move - The {@linkcode Move} being used
* @param simulated - If `true`, suppresses changes to game state during calculation (defaults to `true`) * @returns Whether the move will critically hit the defender.
* @returns whether the move critically hits the pokemon
*/ */
getCriticalHitResult(source: Pokemon, move: Move, simulated = true): boolean { getCriticalHitResult(source: Pokemon, move: Move): boolean {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (move.hasAttr("FixedDamageAttr")) {
const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide); // fixed damage moves (Dragon Rage, etc.) will nevet crit
if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr("FixedDamageAttr")) {
return false; return false;
} }
const isCritical = new BooleanHolder(false);
if (source.getTag(BattlerTagType.ALWAYS_CRIT)) { const alwaysCrit = new BooleanHolder(false);
isCritical.value = true; applyMoveAttrs("CritOnlyAttr", source, this, move, alwaysCrit);
} applyAbAttrs("ConditionalCritAbAttr", source, null, false, alwaysCrit, this, move);
applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical); const alwaysCritTag = !!source.getTag(BattlerTagType.ALWAYS_CRIT);
applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move); const critChance = [24, 8, 2, 1][Phaser.Math.Clamp(this.getCritStage(source, move), 0, 3)];
if (!isCritical.value) {
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))];
isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance);
}
applyAbAttrs(BlockCritAbAttr, this, null, simulated, isCritical); let isCritical = alwaysCrit.value || alwaysCritTag || critChance === 1;
return isCritical.value; // If we aren't already guaranteed to crit, do a random roll & check overrides
isCritical ||= Overrides.CRITICAL_HIT_OVERRIDE ?? globalScene.randBattleSeedInt(critChance) === 0;
// apply crit block effects from lucky chant & co., overriding previous effects
const blockCrit = new BooleanHolder(false);
applyAbAttrs("BlockCritAbAttr", this, null, false, blockCrit);
const blockCritTag = globalScene.arena.getTagOnSide(
NoCritTag,
this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY,
);
isCritical &&= !blockCritTag && !blockCrit.value; // need to roll a crit and not be blocked by either crit prevention effect
return isCritical;
} }
/** /**
@ -4049,7 +4015,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;
} }
@ -4097,11 +4063,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;
@ -4117,13 +4083,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;
} }
@ -4139,7 +4105,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;
@ -4353,10 +4322,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(
@ -4412,14 +4412,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();
@ -4487,7 +4489,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);
@ -4643,7 +4646,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;
} }
@ -4692,14 +4695,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,
@ -4856,7 +4859,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;
} }
@ -4880,7 +4883,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
) { ) {
@ -5405,7 +5408,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);
@ -5465,7 +5468,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;
@ -5485,6 +5488,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
this.turnData.berriesEaten.push(berryType); this.turnData.berriesEaten.push(berryType);
} }
getPersistentTreasureCount(): number {
return (
this.getHeldItems().filter(m => m.is("DamageMoneyRewardModifier")).length +
globalScene.findModifiers(m => m.is("MoneyMultiplierModifier") || m.is("ExtraModifierModifier")).length
);
}
} }
export class PlayerPokemon extends Pokemon { export class PlayerPokemon extends Pokemon {
@ -5823,7 +5833,7 @@ export class PlayerPokemon extends Pokemon {
if (evoSpecies?.speciesId === SpeciesId.NINCADA && evolution.speciesId === SpeciesId.NINJASK) { if (evoSpecies?.speciesId === SpeciesId.NINCADA && evolution.speciesId === SpeciesId.NINJASK) {
const newEvolution = pokemonEvolutions[evoSpecies.speciesId][1]; const newEvolution = pokemonEvolutions[evoSpecies.speciesId][1];
if (newEvolution.condition?.predicate(this)) { if (validateShedinjaEvo()) {
const newPokemon = globalScene.addPlayerPokemon( const newPokemon = globalScene.addPlayerPokemon(
this.species, this.species,
this.level, this.level,
@ -5853,7 +5863,6 @@ export class PlayerPokemon extends Pokemon {
newPokemon.fusionLuck = this.fusionLuck; newPokemon.fusionLuck = this.fusionLuck;
newPokemon.fusionTeraType = this.fusionTeraType; newPokemon.fusionTeraType = this.fusionTeraType;
newPokemon.usedTMs = this.usedTMs; newPokemon.usedTMs = this.usedTMs;
newPokemon.evoCounter = this.evoCounter;
globalScene.getPlayerParty().push(newPokemon); globalScene.getPlayerParty().push(newPokemon);
newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies); newPokemon.evolve(!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution), evoSpecies);
@ -5942,7 +5951,6 @@ export class PlayerPokemon extends Pokemon {
this.fusionGender = pokemon.gender; this.fusionGender = pokemon.gender;
this.fusionLuck = pokemon.luck; this.fusionLuck = pokemon.luck;
this.fusionCustomPokemonData = pokemon.customPokemonData; this.fusionCustomPokemonData = pokemon.customPokemonData;
this.evoCounter = Math.max(pokemon.evoCounter, this.evoCounter);
if (pokemon.pauseEvolutions || this.pauseEvolutions) { if (pokemon.pauseEvolutions || this.pauseEvolutions) {
this.pauseEvolutions = true; this.pauseEvolutions = true;
} }
@ -6011,7 +6019,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;
@ -6098,18 +6106,6 @@ export class EnemyPokemon extends Pokemon {
this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0); this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0);
let prevolution: SpeciesId;
let speciesId = species.speciesId;
while ((prevolution = pokemonPrevolutions[speciesId])) {
const evolution = pokemonEvolutions[prevolution].find(
pe => pe.speciesId === speciesId && (!pe.evoFormKey || pe.evoFormKey === this.getFormKey()),
);
if (evolution?.condition?.enforceFunc) {
evolution.condition.enforceFunc(this);
}
speciesId = prevolution;
}
if (this.hasTrainer() && globalScene.currentBattle) { if (this.hasTrainer() && globalScene.currentBattle) {
const { waveIndex } = globalScene.currentBattle; const { waveIndex } = globalScene.currentBattle;
const ivs: number[] = []; const ivs: number[] = [];
@ -6191,33 +6187,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);
@ -6225,6 +6227,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,
}; };
} }
} }
@ -6232,7 +6235,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: {
@ -6401,14 +6404,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,
}; };
} }
@ -6754,10 +6763,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 {
@ -6776,6 +6784,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;
@ -6895,7 +6909,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;
@ -6917,8 +6930,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;
/** /**

View File

@ -1,7 +1,7 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import type { TrainerConfig } from "#app/data/trainers/trainer-config"; import type { TrainerConfig } from "#app/data/trainers/trainer-config";
import type { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import type { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";

View File

@ -5,7 +5,7 @@ import type { Challenge } from "./data/challenge";
import { allChallenges, applyChallenges, copyChallenge } from "./data/challenge"; import { allChallenges, applyChallenges, copyChallenge } from "./data/challenge";
import { ChallengeType } from "#enums/challenge-type"; import { ChallengeType } from "#enums/challenge-type";
import type PokemonSpecies from "./data/pokemon-species"; import type PokemonSpecies from "./data/pokemon-species";
import { allSpecies } from "./data/pokemon-species"; import { allSpecies } from "#app/data/data-lists";
import type { Arena } from "./field/arena"; import type { Arena } from "./field/arena";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils/common"; import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils/common";
@ -90,13 +90,14 @@ export class GameMode implements GameModeConfig {
} }
/** /**
* Helper function to get starting level for game mode.
* @returns either: * @returns either:
* - override from overrides.ts * - starting level override from overrides.ts
* - 20 for Daily Runs * - 20 for Daily Runs
* - 5 for all other modes * - 5 for all other modes
*/ */
getStartingLevel(): number { getStartingLevel(): number {
if (Overrides.STARTING_LEVEL_OVERRIDE) { if (Overrides.STARTING_LEVEL_OVERRIDE > 0) {
return Overrides.STARTING_LEVEL_OVERRIDE; return Overrides.STARTING_LEVEL_OVERRIDE;
} }
switch (this.modeId) { switch (this.modeId) {

View File

@ -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();

View 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;
}

View 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

View File

@ -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;
@ -756,6 +772,10 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
return this.getMaxHeldItemCount(pokemon); return this.getMaxHeldItemCount(pokemon);
} }
getSpecies(): SpeciesId | null {
return null;
}
abstract getMaxHeldItemCount(pokemon?: Pokemon): number; abstract getMaxHeldItemCount(pokemon?: Pokemon): number;
} }
@ -902,27 +922,14 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier {
return true; return true;
} }
getIconStackText(virtual?: boolean): Phaser.GameObjects.BitmapText | null { getIconStackText(_virtual?: boolean): Phaser.GameObjects.BitmapText | null {
if (this.getMaxStackCount() === 1 || (virtual && !this.virtualStackCount)) { const pokemon = this.getPokemon();
return null;
}
const pokemon = globalScene.getPokemonById(this.pokemonId); const count = (pokemon?.getPersistentTreasureCount() || 0) + this.getStackCount();
this.stackCount = pokemon const text = globalScene.add.bitmapText(10, 15, "item-count", count.toString(), 11);
? pokemon.evoCounter +
pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length +
globalScene.findModifiers(
m =>
m instanceof MoneyMultiplierModifier ||
m instanceof ExtraModifierModifier ||
m instanceof TempExtraModifierModifier,
).length
: this.stackCount;
const text = globalScene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11);
text.letterSpacing = -0.5; text.letterSpacing = -0.5;
if (this.getStackCount() >= this.required) { if (count >= this.required) {
text.setTint(0xf89890); text.setTint(0xf89890);
} }
text.setOrigin(0, 0); text.setOrigin(0, 0);
@ -930,18 +937,13 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier {
return text; return text;
} }
getMaxHeldItemCount(pokemon: Pokemon): number { getMaxHeldItemCount(_pokemon: Pokemon): number {
this.stackCount =
pokemon.evoCounter +
pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length +
globalScene.findModifiers(
m =>
m instanceof MoneyMultiplierModifier ||
m instanceof ExtraModifierModifier ||
m instanceof TempExtraModifierModifier,
).length;
return 999; return 999;
} }
override getSpecies(): SpeciesId {
return this.species;
}
} }
/** /**
@ -1593,7 +1595,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 +1879,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 +1967,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;
} }
@ -2386,19 +2388,13 @@ export class EvolutionItemModifier extends ConsumablePokemonModifier {
override apply(playerPokemon: PlayerPokemon): boolean { override apply(playerPokemon: PlayerPokemon): boolean {
let matchingEvolution = pokemonEvolutions.hasOwnProperty(playerPokemon.species.speciesId) let matchingEvolution = pokemonEvolutions.hasOwnProperty(playerPokemon.species.speciesId)
? pokemonEvolutions[playerPokemon.species.speciesId].find( ? pokemonEvolutions[playerPokemon.species.speciesId].find(
e => e => e.evoItem === this.type.evolutionItem && e.validate(playerPokemon, false, e.item!),
e.item === this.type.evolutionItem &&
(e.evoFormKey === null || (e.preFormKey || "") === playerPokemon.getFormKey()) &&
(!e.condition || e.condition.predicate(playerPokemon)),
) )
: null; : null;
if (!matchingEvolution && playerPokemon.isFusion()) { if (!matchingEvolution && playerPokemon.isFusion()) {
matchingEvolution = pokemonEvolutions[playerPokemon.fusionSpecies!.speciesId].find( matchingEvolution = pokemonEvolutions[playerPokemon.fusionSpecies!.speciesId].find(
e => e => e.evoItem === this.type.evolutionItem && e.validate(playerPokemon, true, e.item!),
e.item === this.type.evolutionItem && // TODO: is the bang correct?
(e.evoFormKey === null || (e.preFormKey || "") === playerPokemon.getFusionFormKey()) &&
(!e.condition || e.condition.predicate(playerPokemon)),
); );
if (matchingEvolution) { if (matchingEvolution) {
matchingEvolution = new FusionSpeciesFormEvolution(playerPokemon.species.speciesId, matchingEvolution); matchingEvolution = new FusionSpeciesFormEvolution(playerPokemon.species.speciesId, matchingEvolution);
@ -2918,11 +2914,10 @@ export class MoneyRewardModifier extends ConsumableModifier {
globalScene.getPlayerParty().map(p => { globalScene.getPlayerParty().map(p => {
if (p.species?.speciesId === SpeciesId.GIMMIGHOUL || p.fusionSpecies?.speciesId === SpeciesId.GIMMIGHOUL) { if (p.species?.speciesId === SpeciesId.GIMMIGHOUL || p.fusionSpecies?.speciesId === SpeciesId.GIMMIGHOUL) {
p.evoCounter const factor = Math.min(Math.floor(this.moneyMultiplier), 3);
? (p.evoCounter += Math.min(Math.floor(this.moneyMultiplier), 3))
: (p.evoCounter = Math.min(Math.floor(this.moneyMultiplier), 3));
const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier( const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier(
p, p,
factor,
) as EvoTrackerModifier; ) as EvoTrackerModifier;
globalScene.addModifier(modifier); globalScene.addModifier(modifier);
} }
@ -3215,7 +3210,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 +3782,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 +3824,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 +3842,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;

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