Merge remote-tracking branch 'upstream/beta' into phase-interceptor
@ -48,7 +48,7 @@
|
||||
"lefthook": "^1.12.2",
|
||||
"msw": "^2.10.4",
|
||||
"phaser3spectorjs": "^0.0.8",
|
||||
"typedoc": "0.28.7",
|
||||
"typedoc": "^0.28.13",
|
||||
"typedoc-github-theme": "^0.3.1",
|
||||
"typedoc-plugin-coverage": "^4.0.1",
|
||||
"typedoc-plugin-mdn-links": "^5.0.9",
|
||||
@ -74,5 +74,5 @@
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.16.1"
|
||||
"packageManager": "pnpm@10.17.0"
|
||||
}
|
||||
|
@ -88,17 +88,17 @@ importers:
|
||||
specifier: ^0.0.8
|
||||
version: 0.0.8
|
||||
typedoc:
|
||||
specifier: 0.28.7
|
||||
version: 0.28.7(typescript@5.9.2)
|
||||
specifier: ^0.28.13
|
||||
version: 0.28.13(typescript@5.9.2)
|
||||
typedoc-github-theme:
|
||||
specifier: ^0.3.1
|
||||
version: 0.3.1(typedoc@0.28.7(typescript@5.9.2))
|
||||
version: 0.3.1(typedoc@0.28.13(typescript@5.9.2))
|
||||
typedoc-plugin-coverage:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1(typedoc@0.28.7(typescript@5.9.2))
|
||||
version: 4.0.1(typedoc@0.28.13(typescript@5.9.2))
|
||||
typedoc-plugin-mdn-links:
|
||||
specifier: ^5.0.9
|
||||
version: 5.0.9(typedoc@0.28.7(typescript@5.9.2))
|
||||
version: 5.0.9(typedoc@0.28.13(typescript@5.9.2))
|
||||
typescript:
|
||||
specifier: ^5.9.2
|
||||
version: 5.9.2
|
||||
@ -717,17 +717,17 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@shikijs/engine-oniguruma@3.12.2':
|
||||
resolution: {integrity: sha512-hozwnFHsLvujK4/CPVHNo3Bcg2EsnG8krI/ZQ2FlBlCRpPZW4XAEQmEwqegJsypsTAN9ehu2tEYe30lYKSZW/w==}
|
||||
'@shikijs/engine-oniguruma@3.13.0':
|
||||
resolution: {integrity: sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==}
|
||||
|
||||
'@shikijs/langs@3.12.2':
|
||||
resolution: {integrity: sha512-bVx5PfuZHDSHoBal+KzJZGheFuyH4qwwcwG/n+MsWno5cTlKmaNtTsGzJpHYQ8YPbB5BdEdKU1rga5/6JGY8ww==}
|
||||
'@shikijs/langs@3.13.0':
|
||||
resolution: {integrity: sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==}
|
||||
|
||||
'@shikijs/themes@3.12.2':
|
||||
resolution: {integrity: sha512-fTR3QAgnwYpfGczpIbzPjlRnxyONJOerguQv1iwpyQZ9QXX4qy/XFQqXlf17XTsorxnHoJGbH/LXBvwtqDsF5A==}
|
||||
'@shikijs/themes@3.13.0':
|
||||
resolution: {integrity: sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==}
|
||||
|
||||
'@shikijs/types@3.12.2':
|
||||
resolution: {integrity: sha512-K5UIBzxCyv0YoxN3LMrKB9zuhp1bV+LgewxuVwHdl4Gz5oePoUFrr9EfgJlGlDeXCU1b/yhdnXeuRvAnz8HN8Q==}
|
||||
'@shikijs/types@3.13.0':
|
||||
resolution: {integrity: sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==}
|
||||
|
||||
'@shikijs/vscode-textmate@10.0.2':
|
||||
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
|
||||
@ -1828,12 +1828,12 @@ packages:
|
||||
peerDependencies:
|
||||
typedoc: 0.27.x || 0.28.x
|
||||
|
||||
typedoc@0.28.7:
|
||||
resolution: {integrity: sha512-lpz0Oxl6aidFkmS90VQDQjk/Qf2iw0IUvFqirdONBdj7jPSN9mGXhy66BcGNDxx5ZMyKKiBVAREvPEzT6Uxipw==}
|
||||
typedoc@0.28.13:
|
||||
resolution: {integrity: sha512-dNWY8msnYB2a+7Audha+aTF1Pu3euiE7ySp53w8kEsXoYw7dMouV5A1UsTUY345aB152RHnmRMDiovuBi7BD+w==}
|
||||
engines: {node: '>= 18', pnpm: '>= 10'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x
|
||||
typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x
|
||||
|
||||
typescript@5.9.2:
|
||||
resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==}
|
||||
@ -2312,10 +2312,10 @@ snapshots:
|
||||
|
||||
'@gerrit0/mini-shiki@3.12.2':
|
||||
dependencies:
|
||||
'@shikijs/engine-oniguruma': 3.12.2
|
||||
'@shikijs/langs': 3.12.2
|
||||
'@shikijs/themes': 3.12.2
|
||||
'@shikijs/types': 3.12.2
|
||||
'@shikijs/engine-oniguruma': 3.13.0
|
||||
'@shikijs/langs': 3.13.0
|
||||
'@shikijs/themes': 3.13.0
|
||||
'@shikijs/types': 3.13.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
|
||||
'@inquirer/checkbox@4.2.0(@types/node@22.16.5)':
|
||||
@ -2547,20 +2547,20 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc@4.50.1':
|
||||
optional: true
|
||||
|
||||
'@shikijs/engine-oniguruma@3.12.2':
|
||||
'@shikijs/engine-oniguruma@3.13.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.12.2
|
||||
'@shikijs/types': 3.13.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
|
||||
'@shikijs/langs@3.12.2':
|
||||
'@shikijs/langs@3.13.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.12.2
|
||||
'@shikijs/types': 3.13.0
|
||||
|
||||
'@shikijs/themes@3.12.2':
|
||||
'@shikijs/themes@3.13.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.12.2
|
||||
'@shikijs/types': 3.13.0
|
||||
|
||||
'@shikijs/types@3.12.2':
|
||||
'@shikijs/types@3.13.0':
|
||||
dependencies:
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
@ -3682,19 +3682,19 @@ snapshots:
|
||||
|
||||
type-fest@4.41.0: {}
|
||||
|
||||
typedoc-github-theme@0.3.1(typedoc@0.28.7(typescript@5.9.2)):
|
||||
typedoc-github-theme@0.3.1(typedoc@0.28.13(typescript@5.9.2)):
|
||||
dependencies:
|
||||
typedoc: 0.28.7(typescript@5.9.2)
|
||||
typedoc: 0.28.13(typescript@5.9.2)
|
||||
|
||||
typedoc-plugin-coverage@4.0.1(typedoc@0.28.7(typescript@5.9.2)):
|
||||
typedoc-plugin-coverage@4.0.1(typedoc@0.28.13(typescript@5.9.2)):
|
||||
dependencies:
|
||||
typedoc: 0.28.7(typescript@5.9.2)
|
||||
typedoc: 0.28.13(typescript@5.9.2)
|
||||
|
||||
typedoc-plugin-mdn-links@5.0.9(typedoc@0.28.7(typescript@5.9.2)):
|
||||
typedoc-plugin-mdn-links@5.0.9(typedoc@0.28.13(typescript@5.9.2)):
|
||||
dependencies:
|
||||
typedoc: 0.28.7(typescript@5.9.2)
|
||||
typedoc: 0.28.13(typescript@5.9.2)
|
||||
|
||||
typedoc@0.28.7(typescript@5.9.2):
|
||||
typedoc@0.28.13(typescript@5.9.2):
|
||||
dependencies:
|
||||
'@gerrit0/mini-shiki': 3.12.2
|
||||
lunr: 2.3.9
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 131 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 202 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 124 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 235 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 186 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 180 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 181 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 242 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 198 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 196 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 169 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 131 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 202 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 249 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 190 B |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 244 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 195 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 235 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 193 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 180 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 197 B |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 307 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 131 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 202 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 249 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 190 B |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 244 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 195 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 235 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 193 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 180 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 197 B |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 307 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 202 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 124 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 235 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 187 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 180 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 181 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 242 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 198 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 196 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 169 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 124 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 235 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 187 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 180 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 181 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 242 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 198 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 196 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 169 B |
@ -89,7 +89,8 @@ export type AbilityBattlerTagType =
|
||||
| BattlerTagType.QUARK_DRIVE
|
||||
| BattlerTagType.UNBURDEN
|
||||
| BattlerTagType.SLOW_START
|
||||
| BattlerTagType.TRUANT;
|
||||
| BattlerTagType.TRUANT
|
||||
| BattlerTagType.SUPREME_OVERLORD;
|
||||
|
||||
/** Subset of {@linkcode BattlerTagType}s that provide type boosts */
|
||||
export type TypeBoostTagType = BattlerTagType.FIRE_BOOST | BattlerTagType.CHARGED;
|
||||
|
44
src/@types/damage-params.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type { MoveCategory } from "#enums/move-category";
|
||||
import type { Pokemon } from "#field/pokemon";
|
||||
import type { Move } from "#types/move-types";
|
||||
|
||||
/**
|
||||
* Collection of types for methods like {@linkcode Pokemon#getBaseDamage} and {@linkcode Pokemon#getAttackDamage}.
|
||||
* @module
|
||||
*/
|
||||
|
||||
/** Base type for damage parameter methods, used for DRY */
|
||||
export interface damageParams {
|
||||
/** The attacking {@linkcode Pokemon} */
|
||||
source: Pokemon;
|
||||
/** The move used in the attack */
|
||||
move: Move;
|
||||
/** The move's {@linkcode MoveCategory} after variable-category effects are applied */
|
||||
moveCategory: MoveCategory;
|
||||
/** If `true`, ignores this Pokemon's defensive ability effects */
|
||||
ignoreAbility?: boolean;
|
||||
/** If `true`, ignores the attacking Pokemon's ability effects */
|
||||
ignoreSourceAbility?: boolean;
|
||||
/** If `true`, ignores the ally Pokemon's ability effects */
|
||||
ignoreAllyAbility?: boolean;
|
||||
/** If `true`, ignores the ability effects of the attacking pokemon's ally */
|
||||
ignoreSourceAllyAbility?: boolean;
|
||||
/** If `true`, calculates damage for a critical hit */
|
||||
isCritical?: boolean;
|
||||
/** If `true`, suppresses changes to game state during the calculation */
|
||||
simulated?: boolean;
|
||||
/** If defined, used in place of calculated effectiveness values */
|
||||
effectiveness?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type for the parameters of {@linkcode Pokemon#getBaseDamage | getBaseDamage}
|
||||
* @interface
|
||||
*/
|
||||
export type getBaseDamageParams = Omit<damageParams, "effectiveness">;
|
||||
|
||||
/**
|
||||
* Type for the parameters of {@linkcode Pokemon#getAttackDamage | getAttackDamage}
|
||||
* @interface
|
||||
*/
|
||||
export type getAttackDamageParams = Omit<damageParams, "moveCategory">;
|
@ -1,26 +1,27 @@
|
||||
import type { Pokemon } from "#app/field/pokemon";
|
||||
import type { Phase } from "#app/phase";
|
||||
import type { PhaseConstructorMap } from "#app/phase-manager";
|
||||
import type { ObjectValues } from "#types/type-helpers";
|
||||
|
||||
// Intentionally export the types of everything in phase-manager, as this file is meant to be
|
||||
// Intentionally [re-]export the types of everything in phase-manager, as this file is meant to be
|
||||
// the centralized place for type definitions for the phase system.
|
||||
export type * from "#app/phase-manager";
|
||||
|
||||
// This file includes helpful types for the phase system.
|
||||
// It intentionally imports the phase constructor map from the phase manager (and re-exports it)
|
||||
|
||||
/**
|
||||
* Map of phase names to constructors for said phase
|
||||
*/
|
||||
/** Map of phase names to constructors for said phase */
|
||||
export type PhaseMap = {
|
||||
[K in keyof PhaseConstructorMap]: InstanceType<PhaseConstructorMap[K]>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Union type of all phase constructors.
|
||||
*/
|
||||
/** Union type of all phase constructors. */
|
||||
export type PhaseClass = ObjectValues<PhaseConstructorMap>;
|
||||
|
||||
/**
|
||||
* Union type of all phase names as strings.
|
||||
*/
|
||||
/** Union type of all phase names as strings. */
|
||||
export type PhaseString = keyof PhaseMap;
|
||||
|
||||
/** Type for predicate functions operating on a specific type of {@linkcode Phase}. */
|
||||
export type PhaseConditionFunc<T extends PhaseString> = (phase: PhaseMap[T]) => boolean;
|
||||
|
||||
/** Interface type representing the assumption that all phases with pokemon associated are dynamic */
|
||||
export interface DynamicPhase extends Phase {
|
||||
getPokemon(): Pokemon;
|
||||
}
|
||||
|
@ -4,8 +4,10 @@ import type { BattleType } from "#enums/battle-type";
|
||||
import type { GameModes } from "#enums/game-modes";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import type { Nature } from "#enums/nature";
|
||||
import type { PlayerGender } from "#enums/player-gender";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
import type { SpeciesId } from "#enums/species-id";
|
||||
import type { MysteryEncounterSaveData } from "#mystery-encounters/mystery-encounter-save-data";
|
||||
import type { Variant } from "#sprites/variant";
|
||||
import type { ArenaData } from "#system/arena-data";
|
||||
@ -108,6 +110,22 @@ export interface DexAttrProps {
|
||||
formIndex: number;
|
||||
}
|
||||
|
||||
export interface Starter {
|
||||
speciesId: SpeciesId;
|
||||
shiny: boolean;
|
||||
variant: Variant;
|
||||
formIndex: number;
|
||||
female?: boolean;
|
||||
abilityIndex: number;
|
||||
passive: boolean;
|
||||
nature: Nature;
|
||||
moveset?: StarterMoveset;
|
||||
pokerus: boolean;
|
||||
nickname?: string;
|
||||
teraType?: PokemonType;
|
||||
ivs: number[];
|
||||
}
|
||||
|
||||
export type RunHistoryData = Record<number, RunEntry>;
|
||||
|
||||
export interface RunEntry {
|
||||
|
@ -104,7 +104,6 @@ import {
|
||||
import { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterSaveData } from "#mystery-encounters/mystery-encounter-save-data";
|
||||
import { allMysteryEncounters, mysteryEncountersByBiome } from "#mystery-encounters/mystery-encounters";
|
||||
import type { MovePhase } from "#phases/move-phase";
|
||||
import { expSpriteKeys } from "#sprites/sprite-keys";
|
||||
import { hasExpSprite } from "#sprites/sprite-utils";
|
||||
import type { Variant } from "#sprites/variant";
|
||||
@ -799,12 +798,14 @@ export class BattleScene extends SceneBase {
|
||||
|
||||
/**
|
||||
* Returns an array of EnemyPokemon of length 1 or 2 depending on if in a double battle or not.
|
||||
* Does not actually check if the pokemon are on the field or not.
|
||||
* @param active - (Default `false`) Whether to consider only {@linkcode Pokemon.isActive | active} on-field pokemon
|
||||
* @returns array of {@linkcode EnemyPokemon}
|
||||
*/
|
||||
public getEnemyField(): EnemyPokemon[] {
|
||||
public getEnemyField(active = false): EnemyPokemon[] {
|
||||
const party = this.getEnemyParty();
|
||||
return party.slice(0, Math.min(party.length, this.currentBattle?.double ? 2 : 1));
|
||||
return party
|
||||
.slice(0, Math.min(party.length, this.currentBattle?.double ? 2 : 1))
|
||||
.filter(p => !active || p.isActive());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -829,25 +830,7 @@ export class BattleScene extends SceneBase {
|
||||
* @param allyPokemon - The {@linkcode Pokemon} allied with the removed Pokemon; will have moves redirected to it
|
||||
*/
|
||||
redirectPokemonMoves(removedPokemon: Pokemon, allyPokemon: Pokemon): void {
|
||||
// failsafe: if not a double battle just return
|
||||
if (this.currentBattle.double === false) {
|
||||
return;
|
||||
}
|
||||
if (allyPokemon?.isActive(true)) {
|
||||
let targetingMovePhase: MovePhase;
|
||||
do {
|
||||
targetingMovePhase = this.phaseManager.findPhase(
|
||||
mp =>
|
||||
mp.is("MovePhase")
|
||||
&& mp.targets.length === 1
|
||||
&& mp.targets[0] === removedPokemon.getBattlerIndex()
|
||||
&& mp.pokemon.isPlayer() !== allyPokemon.isPlayer(),
|
||||
) as MovePhase;
|
||||
if (targetingMovePhase && targetingMovePhase.targets[0] !== allyPokemon.getBattlerIndex()) {
|
||||
targetingMovePhase.targets[0] = allyPokemon.getBattlerIndex();
|
||||
}
|
||||
} while (targetingMovePhase);
|
||||
}
|
||||
this.phaseManager.redirectMoves(removedPokemon, allyPokemon);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1447,7 +1430,7 @@ export class BattleScene extends SceneBase {
|
||||
}
|
||||
|
||||
if (lastBattle?.double && !newDouble) {
|
||||
this.phaseManager.tryRemovePhase((p: Phase) => p.is("SwitchPhase"));
|
||||
this.phaseManager.tryRemovePhase("SwitchPhase");
|
||||
for (const p of this.getPlayerField()) {
|
||||
p.lapseTag(BattlerTagType.COMMANDED);
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import { CommonAnim } from "#enums/move-anims-common";
|
||||
import { MoveCategory } from "#enums/move-category";
|
||||
import { MoveFlags } from "#enums/move-flags";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
|
||||
import { MoveResult } from "#enums/move-result";
|
||||
import { MoveTarget } from "#enums/move-target";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
@ -2555,7 +2556,7 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr {
|
||||
|
||||
override apply({ pokemon, simulated, cancelled }: AbAttrParamsWithCancel): void {
|
||||
if (!simulated) {
|
||||
globalScene.phaseManager.pushNew(
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"StatStageChangePhase",
|
||||
pokemon.getBattlerIndex(),
|
||||
false,
|
||||
@ -3240,6 +3241,7 @@ export class CommanderAbAttr extends AbAttr {
|
||||
return (
|
||||
globalScene.currentBattle?.double
|
||||
&& ally != null
|
||||
&& ally.isActive(true)
|
||||
&& ally.species.speciesId === SpeciesId.DONDOZO
|
||||
&& !(ally.isFainted() || ally.getTag(BattlerTagType.COMMANDED))
|
||||
);
|
||||
@ -3254,7 +3256,7 @@ export class CommanderAbAttr extends AbAttr {
|
||||
// Apply boosts from this effect to the ally Dondozo
|
||||
pokemon.getAlly()?.addTag(BattlerTagType.COMMANDED, 0, MoveId.NONE, pokemon.id);
|
||||
// Cancel the source Pokemon's next move (if a move is queued)
|
||||
globalScene.phaseManager.tryRemovePhase(phase => phase.is("MovePhase") && phase.pokemon === pokemon);
|
||||
globalScene.phaseManager.tryRemovePhase("MovePhase", phase => phase.pokemon === pokemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5004,7 +5006,14 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr {
|
||||
// If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance
|
||||
if (move.getMove().is("AttackMove") || move.getMove().is("StatusMove")) {
|
||||
const target = this.getTarget(pokemon, source, targets);
|
||||
globalScene.phaseManager.unshiftNew("MovePhase", pokemon, target, move, MoveUseMode.INDIRECT);
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"MovePhase",
|
||||
pokemon,
|
||||
target,
|
||||
move,
|
||||
MoveUseMode.INDIRECT,
|
||||
MovePhaseTimingModifier.FIRST,
|
||||
);
|
||||
} else if (move.getMove().is("SelfStatusMove")) {
|
||||
// If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
@ -5013,6 +5022,7 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr {
|
||||
[pokemon.getBattlerIndex()],
|
||||
move,
|
||||
MoveUseMode.INDIRECT,
|
||||
MovePhaseTimingModifier.FIRST,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -6028,11 +6038,6 @@ export class IllusionPostBattleAbAttr extends PostBattleAbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
export interface BypassSpeedChanceAbAttrParams extends AbAttrBaseParams {
|
||||
/** Holds whether the speed check is bypassed after ability application */
|
||||
bypass: BooleanHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a Pokémon with this Ability selects a damaging move, it has a 30% chance of going first in its priority bracket. If the Ability activates, this is announced at the start of the turn (after move selection).
|
||||
* @sealed
|
||||
@ -6048,26 +6053,28 @@ export class BypassSpeedChanceAbAttr extends AbAttr {
|
||||
this.chance = chance;
|
||||
}
|
||||
|
||||
override canApply({ bypass, simulated, pokemon }: BypassSpeedChanceAbAttrParams): boolean {
|
||||
override canApply({ simulated, pokemon }: AbAttrBaseParams): boolean {
|
||||
// TODO: Consider whether we can move the simulated check to the `apply` method
|
||||
// May be difficult as we likely do not want to modify the randBattleSeed
|
||||
const turnCommand = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
|
||||
const isCommandFight = turnCommand?.command === Command.FIGHT;
|
||||
const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null;
|
||||
const isDamageMove = move?.category === MoveCategory.PHYSICAL || move?.category === MoveCategory.SPECIAL;
|
||||
return (
|
||||
!simulated && !bypass.value && pokemon.randBattleSeedInt(100) < this.chance && isCommandFight && isDamageMove
|
||||
!simulated
|
||||
&& pokemon.randBattleSeedInt(100) < this.chance
|
||||
&& isDamageMove
|
||||
&& pokemon.canAddTag(BattlerTagType.BYPASS_SPEED)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* bypass move order in their priority bracket when pokemon choose damaging move
|
||||
*/
|
||||
override apply({ bypass }: BypassSpeedChanceAbAttrParams): void {
|
||||
bypass.value = true;
|
||||
override apply({ pokemon }: AbAttrBaseParams): void {
|
||||
pokemon.addTag(BattlerTagType.BYPASS_SPEED);
|
||||
}
|
||||
|
||||
override getTriggerMessage({ pokemon }: BypassSpeedChanceAbAttrParams, _abilityName: string): string {
|
||||
override getTriggerMessage({ pokemon }: AbAttrBaseParams, _abilityName: string): string {
|
||||
return i18next.t("abilityTriggers:quickDraw", { pokemonName: getPokemonNameWithAffix(pokemon) });
|
||||
}
|
||||
}
|
||||
@ -6075,8 +6082,6 @@ export class BypassSpeedChanceAbAttr extends AbAttr {
|
||||
export interface PreventBypassSpeedChanceAbAttrParams extends AbAttrBaseParams {
|
||||
/** Holds whether the speed check is bypassed after ability application */
|
||||
bypass: BooleanHolder;
|
||||
/** Holds whether the Pokemon can check held items for Quick Claw's effects */
|
||||
canCheckHeldItems: BooleanHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -6103,9 +6108,8 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr {
|
||||
return isCommandFight && this.condition(pokemon, move!);
|
||||
}
|
||||
|
||||
override apply({ bypass, canCheckHeldItems }: PreventBypassSpeedChanceAbAttrParams): void {
|
||||
override apply({ bypass }: PreventBypassSpeedChanceAbAttrParams): void {
|
||||
bypass.value = false;
|
||||
canCheckHeldItems.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6203,8 +6207,7 @@ class ForceSwitchOutHelper {
|
||||
|
||||
if (switchOutTarget.hp > 0) {
|
||||
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
||||
globalScene.phaseManager.prependNewToPhase(
|
||||
"MoveEndPhase",
|
||||
globalScene.phaseManager.queueDeferred(
|
||||
"SwitchPhase",
|
||||
this.switchType,
|
||||
switchOutTarget.getFieldIndex(),
|
||||
@ -6226,8 +6229,7 @@ class ForceSwitchOutHelper {
|
||||
const summonIndex = globalScene.currentBattle.trainer
|
||||
? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot)
|
||||
: 0;
|
||||
globalScene.phaseManager.prependNewToPhase(
|
||||
"MoveEndPhase",
|
||||
globalScene.phaseManager.queueDeferred(
|
||||
"SwitchSummonPhase",
|
||||
this.switchType,
|
||||
switchOutTarget.getFieldIndex(),
|
||||
@ -6949,7 +6951,7 @@ export function initAbilities() {
|
||||
.attr(TypeImmunityStatStageChangeAbAttr, PokemonType.ELECTRIC, Stat.SPD, 1)
|
||||
.ignorable(),
|
||||
new Ability(AbilityId.RIVALRY, 4)
|
||||
.attr(MovePowerBoostAbAttr, (user, target, _move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender === target?.gender, 1.25, true)
|
||||
.attr(MovePowerBoostAbAttr, (user, target, _move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender === target?.gender, 1.25)
|
||||
.attr(MovePowerBoostAbAttr, (user, target, _move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender !== target?.gender, 0.75),
|
||||
new Ability(AbilityId.STEADFAST, 4)
|
||||
.attr(FlinchStatStageChangeAbAttr, [ Stat.SPD ], 1),
|
||||
@ -7161,7 +7163,7 @@ export function initAbilities() {
|
||||
new Ability(AbilityId.ANALYTIC, 5)
|
||||
.attr(MovePowerBoostAbAttr, (user) =>
|
||||
// Boost power if all other Pokemon have already moved (no other moves are slated to execute)
|
||||
!globalScene.phaseManager.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.id !== user?.id),
|
||||
!globalScene.phaseManager.hasPhaseOfType("MovePhase", phase => phase.pokemon.id !== user?.id),
|
||||
1.3),
|
||||
new Ability(AbilityId.ILLUSION, 5)
|
||||
// The Pokemon generate an illusion if it's available
|
||||
@ -7742,8 +7744,8 @@ export function initAbilities() {
|
||||
new Ability(AbilityId.SHARPNESS, 9)
|
||||
.attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.SLICING_MOVE), 1.5),
|
||||
new Ability(AbilityId.SUPREME_OVERLORD, 9)
|
||||
.attr(VariableMovePowerBoostAbAttr, (user, _target, _move) => 1 + 0.1 * Math.min(user.isPlayer() ? globalScene.arena.playerFaints : globalScene.currentBattle.enemyFaints, 5))
|
||||
.partial(), // Should only boost once, on summon
|
||||
.conditionalAttr((p) => (p.isPlayer() ? globalScene.arena.playerFaints : globalScene.currentBattle.enemyFaints) > 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.SUPREME_OVERLORD, 0, true)
|
||||
.edgeCase(), // Tag is not tied to ability, so suppression/removal etc will not function until a structure to allow this is implemented
|
||||
new Ability(AbilityId.COSTAR, 9, -2)
|
||||
.attr(PostSummonCopyAllyStatsAbAttr),
|
||||
new Ability(AbilityId.TOXIC_DEBRIS, 9)
|
||||
|
@ -74,7 +74,6 @@ function applyAbAttrsInternal<T extends CallableAbAttrString>(
|
||||
for (const passive of [false, true]) {
|
||||
params.passive = passive;
|
||||
applySingleAbAttrs(attrType, params, gainedMidTurn, messages);
|
||||
globalScene.phaseManager.clearPhaseQueueSplice();
|
||||
}
|
||||
// We need to restore passive to its original state in the case that it was undefined on entry
|
||||
// this is necessary in case this method is called with an object that is reused.
|
||||
|
@ -56,6 +56,7 @@ import { allMoves } from "#data/data-lists";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { CommonAnim } from "#enums/move-anims-common";
|
||||
@ -84,6 +85,10 @@ interface BaseArenaTag {
|
||||
* The tag's remaining duration. Setting to any number `<=0` will make the tag's duration effectively infinite.
|
||||
*/
|
||||
turnCount: number;
|
||||
/**
|
||||
* The tag's max duration.
|
||||
*/
|
||||
maxDuration: number;
|
||||
/**
|
||||
* The {@linkcode MoveId} that created this tag, or `undefined` if not set by a move.
|
||||
*/
|
||||
@ -110,12 +115,14 @@ export abstract class ArenaTag implements BaseArenaTag {
|
||||
/** The type of the arena tag */
|
||||
public abstract readonly tagType: ArenaTagType;
|
||||
public turnCount: number;
|
||||
public maxDuration: number;
|
||||
public sourceMove?: MoveId;
|
||||
public sourceId: number | undefined;
|
||||
public side: ArenaTagSide;
|
||||
|
||||
constructor(turnCount: number, sourceMove?: MoveId, sourceId?: number, side: ArenaTagSide = ArenaTagSide.BOTH) {
|
||||
this.turnCount = turnCount;
|
||||
this.maxDuration = turnCount;
|
||||
this.sourceMove = sourceMove;
|
||||
this.sourceId = sourceId;
|
||||
this.side = side;
|
||||
@ -164,6 +171,7 @@ export abstract class ArenaTag implements BaseArenaTag {
|
||||
*/
|
||||
loadTag<const T extends this>(source: BaseArenaTag & Pick<T, "tagType">): void {
|
||||
this.turnCount = source.turnCount;
|
||||
this.maxDuration = source.maxDuration;
|
||||
this.sourceMove = source.sourceMove;
|
||||
this.sourceId = source.sourceId;
|
||||
this.side = source.side;
|
||||
@ -1590,6 +1598,145 @@ export class SuppressAbilitiesTag extends SerializableArenaTag {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface containing data related to a queued healing effect from
|
||||
* {@link https://bulbapedia.bulbagarden.net/wiki/Healing_Wish_(move) | Healing Wish}
|
||||
* or {@link https://bulbapedia.bulbagarden.net/wiki/Lunar_Dance_(move) | Lunar Dance}.
|
||||
*/
|
||||
interface PendingHealEffect {
|
||||
/** The {@linkcode Pokemon.id | PID} of the {@linkcode Pokemon} that created the effect. */
|
||||
readonly sourceId: number;
|
||||
/** The {@linkcode MoveId} of the move that created the effect. */
|
||||
readonly moveId: MoveId;
|
||||
/** If `true`, also restores the target's PP when the effect activates. */
|
||||
readonly restorePP: boolean;
|
||||
/** The message to display when the effect activates */
|
||||
readonly healMessage: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arena tag to contain stored healing effects, namely from
|
||||
* {@link https://bulbapedia.bulbagarden.net/wiki/Healing_Wish_(move) | Healing Wish}
|
||||
* and {@link https://bulbapedia.bulbagarden.net/wiki/Lunar_Dance_(move) | Lunar Dance}.
|
||||
* When a damaged Pokemon first enters the effect's {@linkcode BattlerIndex | field position},
|
||||
* their HP is fully restored, and they are cured of any non-volatile status condition.
|
||||
* If the effect is from Lunar Dance, their PP is also restored.
|
||||
*/
|
||||
export class PendingHealTag extends SerializableArenaTag {
|
||||
public readonly tagType = ArenaTagType.PENDING_HEAL;
|
||||
/** All pending healing effects, organized by {@linkcode BattlerIndex} */
|
||||
public readonly pendingHeals: Partial<Record<BattlerIndex, PendingHealEffect[]>> = {};
|
||||
|
||||
constructor() {
|
||||
super(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a pending healing effect to the field. Effects under the same move *and*
|
||||
* target index as an existing effect are ignored.
|
||||
* @param targetIndex - The {@linkcode BattlerIndex} under which the effect applies
|
||||
* @param healEffect - The {@linkcode PendingHealEffect | data} for the pending heal effect
|
||||
*/
|
||||
public queueHeal(targetIndex: BattlerIndex, healEffect: PendingHealEffect): void {
|
||||
const existingHealEffects = this.pendingHeals[targetIndex];
|
||||
if (existingHealEffects) {
|
||||
if (!existingHealEffects.some(he => he.moveId === healEffect.moveId)) {
|
||||
existingHealEffects.push(healEffect);
|
||||
}
|
||||
} else {
|
||||
this.pendingHeals[targetIndex] = [healEffect];
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes default on-remove message */
|
||||
override onRemove(_arena: Arena): void {}
|
||||
|
||||
/** This arena tag is removed at the end of the turn if no pending healing effects are on the field */
|
||||
override lapse(_arena: Arena): boolean {
|
||||
for (const key in this.pendingHeals) {
|
||||
if (this.pendingHeals[key].length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a pending healing effect on the given target index. If an effect is found for
|
||||
* the index, the Pokemon at that index is healed to full HP, is cured of any non-volatile status,
|
||||
* and has its PP fully restored (if the effect is from Lunar Dance).
|
||||
* @param arena - The {@linkcode Arena} containing this tag
|
||||
* @param simulated - If `true`, suppresses changes to game state
|
||||
* @param pokemon - The {@linkcode Pokemon} receiving the healing effect
|
||||
* @returns `true` if the target Pokemon was healed by this effect
|
||||
* @todo This should also be called when a Pokemon moves into a new position via Ally Switch
|
||||
*/
|
||||
override apply(arena: Arena, simulated: boolean, pokemon: Pokemon): boolean {
|
||||
const targetIndex = pokemon.getBattlerIndex();
|
||||
const targetEffects = this.pendingHeals[targetIndex];
|
||||
|
||||
if (targetEffects == null || targetEffects.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const healEffect = targetEffects.find(effect => this.canApply(effect, pokemon));
|
||||
|
||||
if (healEffect == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (simulated) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { sourceId, moveId, restorePP, healMessage } = healEffect;
|
||||
const sourcePokemon = globalScene.getPokemonById(sourceId);
|
||||
if (!sourcePokemon) {
|
||||
console.warn(`Source of pending ${allMoves[moveId].name} effect is undefined!`);
|
||||
targetEffects.splice(targetEffects.indexOf(healEffect), 1);
|
||||
// Re-evaluate after the invalid heal effect is removed
|
||||
return this.apply(arena, simulated, pokemon);
|
||||
}
|
||||
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"PokemonHealPhase",
|
||||
targetIndex,
|
||||
pokemon.getMaxHp(),
|
||||
healMessage,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
restorePP,
|
||||
);
|
||||
|
||||
targetEffects.splice(targetEffects.indexOf(healEffect), 1);
|
||||
|
||||
return healEffect != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given {@linkcode PendingHealEffect} can immediately heal
|
||||
* the given target {@linkcode Pokemon}.
|
||||
* @param healEffect - The {@linkcode PendingHealEffect} to evaluate
|
||||
* @param pokemon - The {@linkcode Pokemon} to evaluate against
|
||||
* @returns `true` if the Pokemon can be healed by the effect
|
||||
*/
|
||||
private canApply(healEffect: PendingHealEffect, pokemon: Pokemon): boolean {
|
||||
return (
|
||||
!pokemon.isFullHp()
|
||||
|| pokemon.status != null
|
||||
|| (healEffect.restorePP && pokemon.getMoveset().some(mv => mv.ppUsed > 0))
|
||||
);
|
||||
}
|
||||
|
||||
override loadTag(source: BaseArenaTag & Pick<PendingHealTag, "tagType" | "pendingHeals">): void {
|
||||
super.loadTag(source);
|
||||
(this as Mutable<this>).pendingHeals = source.pendingHeals;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter
|
||||
export function getArenaTag(
|
||||
tagType: ArenaTagType,
|
||||
@ -1653,6 +1800,8 @@ export function getArenaTag(
|
||||
return new FairyLockTag(turnCount, sourceId);
|
||||
case ArenaTagType.NEUTRALIZING_GAS:
|
||||
return new SuppressAbilitiesTag(sourceId);
|
||||
case ArenaTagType.PENDING_HEAL:
|
||||
return new PendingHealTag();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -1701,5 +1850,6 @@ export type ArenaTagTypeMap = {
|
||||
[ArenaTagType.GRASS_WATER_PLEDGE]: GrassWaterPledgeTag;
|
||||
[ArenaTagType.FAIRY_LOCK]: FairyLockTag;
|
||||
[ArenaTagType.NEUTRALIZING_GAS]: SuppressAbilitiesTag;
|
||||
[ArenaTagType.PENDING_HEAL]: PendingHealTag;
|
||||
[ArenaTagType.NONE]: NoneTag;
|
||||
};
|
||||
|
@ -1119,7 +1119,7 @@ export const biomePokemonPools: BiomePokemonPools = {
|
||||
},
|
||||
[BiomePoolTier.RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.HITMONLEE, SpeciesId.HITMONCHAN, SpeciesId.LUCARIO, SpeciesId.THROH, SpeciesId.SAWK, { 1: [ SpeciesId.PANCHAM ], 52: [ SpeciesId.PANGORO ] } ] },
|
||||
[BiomePoolTier.SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.HITMONTOP, SpeciesId.GALLADE, SpeciesId.GALAR_FARFETCHD ] },
|
||||
[BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.TERRAKION, { 1: [ SpeciesId.KUBFU ], 60: [ SpeciesId.URSHIFU] }, SpeciesId.GALAR_ZAPDOS ] },
|
||||
[BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.TERRAKION, { 1: [ SpeciesId.KUBFU ], 60: [ SpeciesId.URSHIFU ] }, SpeciesId.GALAR_ZAPDOS ] },
|
||||
[BiomePoolTier.BOSS]: {
|
||||
[TimeOfDay.DAWN]: [],
|
||||
[TimeOfDay.DAY]: [],
|
||||
@ -1128,7 +1128,7 @@ export const biomePokemonPools: BiomePokemonPools = {
|
||||
[TimeOfDay.ALL]: [ SpeciesId.HITMONLEE, SpeciesId.HITMONCHAN, SpeciesId.HARIYAMA, SpeciesId.MEDICHAM, SpeciesId.LUCARIO, SpeciesId.TOXICROAK, SpeciesId.THROH, SpeciesId.SAWK, SpeciesId.SCRAFTY, SpeciesId.MIENSHAO, SpeciesId.BEWEAR, SpeciesId.GRAPPLOCT, SpeciesId.ANNIHILAPE ]
|
||||
},
|
||||
[BiomePoolTier.BOSS_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.HITMONTOP, SpeciesId.GALLADE, SpeciesId.PANGORO, SpeciesId.SIRFETCHD, SpeciesId.HISUI_DECIDUEYE ] },
|
||||
[BiomePoolTier.BOSS_SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.TERRAKION, { 1: [ SpeciesId.KUBFU ], 60: [ SpeciesId.URSHIFU] } ] },
|
||||
[BiomePoolTier.BOSS_SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.TERRAKION, { 1: [ SpeciesId.KUBFU ], 60: [ SpeciesId.URSHIFU ] } ] },
|
||||
[BiomePoolTier.BOSS_ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.ZAMAZENTA, SpeciesId.GALAR_ZAPDOS ] }
|
||||
},
|
||||
[BiomeId.FACTORY]: {
|
||||
@ -1597,10 +1597,10 @@ export const biomePokemonPools: BiomePokemonPools = {
|
||||
[BiomePoolTier.UNCOMMON]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ { 1: [ SpeciesId.SOLOSIS ], 32: [ SpeciesId.DUOSION ], 41: [ SpeciesId.REUNICLUS ] } ] },
|
||||
[BiomePoolTier.RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.DITTO, { 1: [ SpeciesId.PORYGON ], 30: [ SpeciesId.PORYGON2 ] } ] },
|
||||
[BiomePoolTier.SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.ROTOM ] },
|
||||
[BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ { 1: [SpeciesId.TYPE_NULL], 60: [ SpeciesId.SILVALLY ] } ] },
|
||||
[BiomePoolTier.ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ { 1: [ SpeciesId.TYPE_NULL ], 60: [ SpeciesId.SILVALLY ] } ] },
|
||||
[BiomePoolTier.BOSS]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.MUK, SpeciesId.ELECTRODE, SpeciesId.BRONZONG, SpeciesId.MAGNEZONE, SpeciesId.PORYGON_Z, SpeciesId.REUNICLUS, SpeciesId.KLINKLANG ] },
|
||||
[BiomePoolTier.BOSS_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [] },
|
||||
[BiomePoolTier.BOSS_SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.ROTOM, SpeciesId.ZYGARDE, { 1: [SpeciesId.TYPE_NULL], 60: [ SpeciesId.SILVALLY ] } ] },
|
||||
[BiomePoolTier.BOSS_SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.ROTOM, SpeciesId.ZYGARDE, { 1: [ SpeciesId.TYPE_NULL ], 60: [ SpeciesId.SILVALLY ] } ] },
|
||||
[BiomePoolTier.BOSS_ULTRA_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [ SpeciesId.MEWTWO, SpeciesId.MIRAIDON ] }
|
||||
},
|
||||
[BiomeId.END]: {
|
||||
@ -5627,10 +5627,12 @@ export function initBiomes() {
|
||||
]
|
||||
],
|
||||
[ SpeciesId.TYPE_NULL, PokemonType.NORMAL, -1, [
|
||||
[ BiomeId.LABORATORY, BiomePoolTier.ULTRA_RARE ]
|
||||
[ BiomeId.LABORATORY, BiomePoolTier.ULTRA_RARE ],
|
||||
[ BiomeId.LABORATORY, BiomePoolTier.BOSS_SUPER_RARE ]
|
||||
]
|
||||
],
|
||||
[ SpeciesId.SILVALLY, PokemonType.NORMAL, -1, [
|
||||
[ BiomeId.LABORATORY, BiomePoolTier.ULTRA_RARE ],
|
||||
[ BiomeId.LABORATORY, BiomePoolTier.BOSS_SUPER_RARE ]
|
||||
]
|
||||
],
|
||||
@ -5773,10 +5775,12 @@ export function initBiomes() {
|
||||
]
|
||||
],
|
||||
[ SpeciesId.POIPOLE, PokemonType.POISON, -1, [
|
||||
[ BiomeId.SWAMP, BiomePoolTier.ULTRA_RARE ]
|
||||
[ BiomeId.SWAMP, BiomePoolTier.ULTRA_RARE ],
|
||||
[ BiomeId.SWAMP, BiomePoolTier.BOSS_SUPER_RARE ]
|
||||
]
|
||||
],
|
||||
[ SpeciesId.NAGANADEL, PokemonType.POISON, PokemonType.DRAGON, [
|
||||
[ BiomeId.SWAMP, BiomePoolTier.ULTRA_RARE ],
|
||||
[ BiomeId.SWAMP, BiomePoolTier.BOSS_SUPER_RARE ]
|
||||
]
|
||||
],
|
||||
@ -6165,10 +6169,12 @@ export function initBiomes() {
|
||||
]
|
||||
],
|
||||
[ SpeciesId.KUBFU, PokemonType.FIGHTING, -1, [
|
||||
[ BiomeId.DOJO, BiomePoolTier.ULTRA_RARE ]
|
||||
[ BiomeId.DOJO, BiomePoolTier.ULTRA_RARE ],
|
||||
[ BiomeId.DOJO, BiomePoolTier.BOSS_SUPER_RARE ]
|
||||
]
|
||||
],
|
||||
[ SpeciesId.URSHIFU, PokemonType.FIGHTING, PokemonType.DARK, [
|
||||
[ BiomeId.DOJO, BiomePoolTier.ULTRA_RARE ],
|
||||
[ BiomeId.DOJO, BiomePoolTier.BOSS_SUPER_RARE ]
|
||||
]
|
||||
],
|
||||
@ -7209,7 +7215,8 @@ export function initBiomes() {
|
||||
],
|
||||
[ TrainerType.SCIENTIST, [
|
||||
[ BiomeId.DESERT, BiomePoolTier.COMMON ],
|
||||
[ BiomeId.RUINS, BiomePoolTier.COMMON ]
|
||||
[ BiomeId.RUINS, BiomePoolTier.COMMON ],
|
||||
[ BiomeId.LABORATORY, BiomePoolTier.COMMON ]
|
||||
]
|
||||
],
|
||||
[ TrainerType.SMASHER, []],
|
||||
@ -7224,7 +7231,8 @@ export function initBiomes() {
|
||||
]
|
||||
],
|
||||
[ TrainerType.SWIMMER, [
|
||||
[ BiomeId.SEA, BiomePoolTier.COMMON ]
|
||||
[ BiomeId.SEA, BiomePoolTier.COMMON ],
|
||||
[ BiomeId.SEABED, BiomePoolTier.COMMON ]
|
||||
]
|
||||
],
|
||||
[ TrainerType.TWINS, [
|
||||
@ -7590,11 +7598,13 @@ export function initBiomes() {
|
||||
[ TrainerType.ALDER, []],
|
||||
[ TrainerType.IRIS, []],
|
||||
[ TrainerType.DIANTHA, []],
|
||||
[ TrainerType.KUKUI, []],
|
||||
[ TrainerType.HAU, []],
|
||||
[ TrainerType.LEON, []],
|
||||
[ TrainerType.MUSTARD, []],
|
||||
[ TrainerType.GEETA, []],
|
||||
[ TrainerType.NEMONA, []],
|
||||
[ TrainerType.KIERAN, []],
|
||||
[ TrainerType.LEON, []],
|
||||
[ TrainerType.RIVAL, []]
|
||||
];
|
||||
|
||||
|
@ -606,17 +606,7 @@ export class ShellTrapTag extends BattlerTag {
|
||||
|
||||
// Trap should only be triggered by opponent's Physical moves
|
||||
if (phaseData?.move.category === MoveCategory.PHYSICAL && pokemon.isOpponent(phaseData.attacker)) {
|
||||
const shellTrapPhaseIndex = globalScene.phaseManager.phaseQueue.findIndex(
|
||||
phase => phase.is("MovePhase") && phase.pokemon === pokemon,
|
||||
);
|
||||
const firstMovePhaseIndex = globalScene.phaseManager.phaseQueue.findIndex(phase => phase.is("MovePhase"));
|
||||
|
||||
// Only shift MovePhase timing if it's not already next up
|
||||
if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) {
|
||||
const shellTrapMovePhase = globalScene.phaseManager.phaseQueue.splice(shellTrapPhaseIndex, 1)[0];
|
||||
globalScene.phaseManager.prependToPhase(shellTrapMovePhase, "MovePhase");
|
||||
}
|
||||
|
||||
globalScene.phaseManager.forceMoveNext((phase: MovePhase) => phase.pokemon === pokemon);
|
||||
this.activated = true;
|
||||
}
|
||||
|
||||
@ -1279,22 +1269,9 @@ export class EncoreTag extends MoveRestrictionBattlerTag {
|
||||
}),
|
||||
);
|
||||
|
||||
const movePhase = globalScene.phaseManager.findPhase(m => m.is("MovePhase") && m.pokemon === pokemon);
|
||||
if (movePhase) {
|
||||
const movesetMove = pokemon.getMoveset().find(m => m.moveId === this.moveId);
|
||||
if (movesetMove) {
|
||||
const lastMove = pokemon.getLastXMoves(1)[0];
|
||||
globalScene.phaseManager.tryReplacePhase(
|
||||
m => m.is("MovePhase") && m.pokemon === pokemon,
|
||||
globalScene.phaseManager.create(
|
||||
"MovePhase",
|
||||
pokemon,
|
||||
lastMove.targets ?? [],
|
||||
movesetMove,
|
||||
MoveUseMode.NORMAL,
|
||||
),
|
||||
);
|
||||
}
|
||||
globalScene.phaseManager.changePhaseMove((phase: MovePhase) => phase.pokemon === pokemon, movesetMove);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3578,6 +3555,25 @@ export class GrudgeTag extends SerializableBattlerTag {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag to allow the affected Pokemon's move to go first in its priority bracket.
|
||||
* Used for {@link https://bulbapedia.bulbagarden.net/wiki/Quick_Draw_(Ability) | Quick Draw}
|
||||
* and {@link https://bulbapedia.bulbagarden.net/wiki/Quick_Claw | Quick Claw}.
|
||||
*/
|
||||
export class BypassSpeedTag extends BattlerTag {
|
||||
public override readonly tagType = BattlerTagType.BYPASS_SPEED;
|
||||
|
||||
constructor() {
|
||||
super(BattlerTagType.BYPASS_SPEED, BattlerTagLapseType.TURN_END, 1);
|
||||
}
|
||||
|
||||
override canAdd(pokemon: Pokemon): boolean {
|
||||
const bypass = new BooleanHolder(true);
|
||||
applyAbAttrs("PreventBypassSpeedChanceAbAttr", { pokemon, bypass });
|
||||
return bypass.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag used to heal the user of Psycho Shift of its status effect if Psycho Shift succeeds in transferring its status effect to the target Pokemon
|
||||
*/
|
||||
@ -3626,6 +3622,41 @@ export class MagicCoatTag extends BattlerTag {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag associated with {@linkcode AbilityId.SUPREME_OVERLORD}
|
||||
*/
|
||||
export class SupremeOverlordTag extends AbilityBattlerTag {
|
||||
public override readonly tagType = BattlerTagType.SUPREME_OVERLORD;
|
||||
/** The number of faints at the time the user was sent out */
|
||||
public readonly faintCount: number;
|
||||
constructor() {
|
||||
super(BattlerTagType.SUPREME_OVERLORD, AbilityId.SUPREME_OVERLORD, BattlerTagLapseType.FAINT, 0);
|
||||
}
|
||||
|
||||
public override onAdd(pokemon: Pokemon): boolean {
|
||||
(this as Mutable<this>).faintCount = Math.min(
|
||||
pokemon.isPlayer() ? globalScene.arena.playerFaints : globalScene.currentBattle.enemyFaints,
|
||||
5,
|
||||
);
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("battlerTags:supremeOverlordOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The damage multiplier for Supreme Overlord
|
||||
*/
|
||||
public getBoost(): number {
|
||||
return 1 + 0.1 * this.faintCount;
|
||||
}
|
||||
|
||||
public override loadTag(source: BaseBattlerTag & Pick<SupremeOverlordTag, "tagType" | "faintCount">): void {
|
||||
super.loadTag(source);
|
||||
(this as Mutable<this>).faintCount = source.faintCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
|
||||
* @param sourceId - The ID of the pokemon adding the tag
|
||||
@ -3826,6 +3857,10 @@ export function getBattlerTag(
|
||||
return new PsychoShiftTag();
|
||||
case BattlerTagType.MAGIC_COAT:
|
||||
return new MagicCoatTag();
|
||||
case BattlerTagType.SUPREME_OVERLORD:
|
||||
return new SupremeOverlordTag();
|
||||
case BattlerTagType.BYPASS_SPEED:
|
||||
return new BypassSpeedTag();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3960,4 +3995,6 @@ export type BattlerTagTypeMap = {
|
||||
[BattlerTagType.GRUDGE]: GrudgeTag;
|
||||
[BattlerTagType.PSYCHO_SHIFT]: PsychoShiftTag;
|
||||
[BattlerTagType.MAGIC_COAT]: MagicCoatTag;
|
||||
[BattlerTagType.SUPREME_OVERLORD]: SupremeOverlordTag;
|
||||
[BattlerTagType.BYPASS_SPEED]: BypassSpeedTag;
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ import { PokemonSpecies } from "#data/pokemon-species";
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import type { Starter } from "#ui/starter-select-ui-handler";
|
||||
import type { Starter } from "#types/save-data";
|
||||
import { randSeedGauss, randSeedInt, randSeedItem } from "#utils/common";
|
||||
import { getEnumValues } from "#utils/enums";
|
||||
import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils";
|
||||
@ -66,8 +66,11 @@ function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLeve
|
||||
const formIndex = starterSpeciesForm instanceof PokemonSpecies ? undefined : starterSpeciesForm.formIndex;
|
||||
const pokemon = globalScene.addPlayerPokemon(starterSpecies, startingLevel, undefined, formIndex);
|
||||
const starter: Starter = {
|
||||
species: starterSpecies,
|
||||
dexAttr: pokemon.getDexAttr(),
|
||||
speciesId: starterSpecies.speciesId,
|
||||
shiny: pokemon.shiny,
|
||||
variant: pokemon.variant,
|
||||
formIndex: pokemon.formIndex,
|
||||
ivs: pokemon.ivs,
|
||||
abilityIndex: pokemon.abilityIndex,
|
||||
passive: false,
|
||||
nature: pokemon.getNature(),
|
||||
|
@ -6,7 +6,7 @@ import { loggedInUser } from "#app/account";
|
||||
import type { GameMode } from "#app/game-mode";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import type { EntryHazardTag } from "#data/arena-tag";
|
||||
import type { EntryHazardTag, PendingHealTag } from "#data/arena-tag";
|
||||
import { WeakenMoveTypeTag } from "#data/arena-tag";
|
||||
import { MoveChargeAnim } from "#data/battle-anims";
|
||||
import {
|
||||
@ -18,6 +18,7 @@ import {
|
||||
ShellTrapTag,
|
||||
StockpilingTag,
|
||||
SubstituteTag,
|
||||
SupremeOverlordTag,
|
||||
TrappedTag,
|
||||
TypeBoostTag,
|
||||
} from "#data/battler-tags";
|
||||
@ -80,10 +81,8 @@ import { applyMoveAttrs } from "#moves/apply-attrs";
|
||||
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSketchMoves, invalidSleepTalkMoves } from "#moves/invalid-moves";
|
||||
import { frenzyMissFunc, getMoveTargets } from "#moves/move-utils";
|
||||
import { PokemonMove } from "#moves/pokemon-move";
|
||||
import { MoveEndPhase } from "#phases/move-end-phase";
|
||||
import { MovePhase } from "#phases/move-phase";
|
||||
import { PokemonHealPhase } from "#phases/pokemon-heal-phase";
|
||||
import { SwitchSummonPhase } from "#phases/switch-summon-phase";
|
||||
import type { AttackMoveResult } from "#types/attack-move-result";
|
||||
import type { Localizable } from "#types/locales";
|
||||
import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString, MoveMessageFunc } from "#types/move-types";
|
||||
@ -93,6 +92,7 @@ import { getEnumValues } from "#utils/enums";
|
||||
import { toCamelCase, toTitleCase } from "#utils/strings";
|
||||
import i18next from "i18next";
|
||||
import { applyChallenges } from "#utils/challenge-utils";
|
||||
import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
|
||||
import type { AbstractConstructor } from "#types/type-helpers";
|
||||
|
||||
/**
|
||||
@ -879,6 +879,8 @@ export abstract class Move implements Localizable {
|
||||
power.value *= 1.5;
|
||||
}
|
||||
|
||||
power.value *= (source.getTag(BattlerTagType.SUPREME_OVERLORD) as SupremeOverlordTag | undefined)?.getBoost() ?? 1;
|
||||
|
||||
return power.value;
|
||||
}
|
||||
|
||||
@ -888,6 +890,10 @@ export abstract class Move implements Localizable {
|
||||
applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority);
|
||||
applyAbAttrs("ChangeMovePriorityAbAttr", {pokemon: user, simulated, move: this, priority});
|
||||
|
||||
if (user.getTag(BattlerTagType.BYPASS_SPEED)) {
|
||||
priority.value += 0.2;
|
||||
}
|
||||
|
||||
return priority.value;
|
||||
}
|
||||
|
||||
@ -2147,24 +2153,15 @@ export class SacrificialFullRestoreAttr extends SacrificialAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We don't know which party member will be chosen, so pick the highest max HP in the party
|
||||
const party = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty();
|
||||
const maxPartyMemberHp = party.map(p => p.getMaxHp()).reduce((maxHp: number, hp: number) => Math.max(hp, maxHp), 0);
|
||||
|
||||
const pm = globalScene.phaseManager;
|
||||
|
||||
pm.pushPhase(
|
||||
pm.create("PokemonHealPhase",
|
||||
user.getBattlerIndex(),
|
||||
maxPartyMemberHp,
|
||||
i18next.t(this.moveMessage, { pokemonName: getPokemonNameWithAffix(user) }),
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
this.restorePP),
|
||||
true);
|
||||
// Add a tag to the field if it doesn't already exist, then queue a delayed healing effect in the user's current slot.
|
||||
globalScene.arena.addTag(ArenaTagType.PENDING_HEAL, 0, move.id, user.id); // Arguments after first go completely unused
|
||||
const tag = globalScene.arena.getTag(ArenaTagType.PENDING_HEAL) as PendingHealTag;
|
||||
tag.queueHeal(user.getBattlerIndex(), {
|
||||
sourceId: user.id,
|
||||
moveId: move.id,
|
||||
restorePP: this.restorePP,
|
||||
healMessage: i18next.t(this.moveMessage, { pokemonName: getPokemonNameWithAffix(user) }),
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -3304,7 +3301,7 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr {
|
||||
|
||||
const overridden = args[0] as BooleanHolder;
|
||||
|
||||
const allyMovePhase = globalScene.phaseManager.findPhase<MovePhase>((phase) => phase.is("MovePhase") && phase.pokemon.isPlayer() === user.isPlayer());
|
||||
const allyMovePhase = globalScene.phaseManager.getMovePhase((phase) => phase.pokemon.isPlayer() === user.isPlayer());
|
||||
if (allyMovePhase) {
|
||||
const allyMove = allyMovePhase.move.getMove();
|
||||
if (allyMove !== move && allyMove.hasAttr("AwaitCombinedPledgeAttr")) {
|
||||
@ -3317,11 +3314,7 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr {
|
||||
}));
|
||||
|
||||
// Move the ally's MovePhase (if needed) so that the ally moves next
|
||||
const allyMovePhaseIndex = globalScene.phaseManager.phaseQueue.indexOf(allyMovePhase);
|
||||
const firstMovePhaseIndex = globalScene.phaseManager.phaseQueue.findIndex((phase) => phase.is("MovePhase"));
|
||||
if (allyMovePhaseIndex !== firstMovePhaseIndex) {
|
||||
globalScene.phaseManager.prependToPhase(globalScene.phaseManager.phaseQueue.splice(allyMovePhaseIndex, 1)[0], "MovePhase");
|
||||
}
|
||||
globalScene.phaseManager.forceMoveNext((phase: MovePhase) => phase.pokemon === user.getAlly());
|
||||
|
||||
overridden.value = true;
|
||||
return true;
|
||||
@ -4556,28 +4549,7 @@ export class LastMoveDoublePowerAttr extends VariablePowerAttr {
|
||||
*/
|
||||
apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean {
|
||||
const power = args[0] as NumberHolder;
|
||||
const enemy = user.getOpponent(0);
|
||||
const pokemonActed: Pokemon[] = [];
|
||||
|
||||
if (enemy?.turnData.acted) {
|
||||
pokemonActed.push(enemy);
|
||||
}
|
||||
|
||||
if (globalScene.currentBattle.double) {
|
||||
const userAlly = user.getAlly();
|
||||
const enemyAlly = enemy?.getAlly();
|
||||
|
||||
if (userAlly?.turnData.acted) {
|
||||
pokemonActed.push(userAlly);
|
||||
}
|
||||
if (enemyAlly?.turnData.acted) {
|
||||
pokemonActed.push(enemyAlly);
|
||||
}
|
||||
}
|
||||
|
||||
pokemonActed.sort((a, b) => b.turnData.order - a.turnData.order);
|
||||
|
||||
for (const p of pokemonActed) {
|
||||
for (const p of globalScene.phaseManager.dynamicQueueManager.getLastTurnOrder().slice(0, -1).reverse()) {
|
||||
const [ lastMove ] = p.getLastXMoves(1);
|
||||
if (lastMove.result !== MoveResult.FAIL) {
|
||||
if ((lastMove.result === MoveResult.SUCCESS) && (lastMove.move === this.move)) {
|
||||
@ -4659,20 +4631,13 @@ export class CueNextRoundAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
|
||||
const nextRoundPhase = globalScene.phaseManager.findPhase<MovePhase>(phase =>
|
||||
phase.is("MovePhase") && phase.move.moveId === MoveId.ROUND
|
||||
);
|
||||
const nextRoundPhase = globalScene.phaseManager.getMovePhase(phase => phase.move.moveId === MoveId.ROUND);
|
||||
|
||||
if (!nextRoundPhase) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the phase queue so that the next Pokemon using Round moves next
|
||||
const nextRoundIndex = globalScene.phaseManager.phaseQueue.indexOf(nextRoundPhase);
|
||||
const nextMoveIndex = globalScene.phaseManager.phaseQueue.findIndex(phase => phase.is("MovePhase"));
|
||||
if (nextRoundIndex !== nextMoveIndex) {
|
||||
globalScene.phaseManager.prependToPhase(globalScene.phaseManager.phaseQueue.splice(nextRoundIndex, 1)[0], "MovePhase");
|
||||
}
|
||||
globalScene.phaseManager.forceMoveNext(phase => phase.move.moveId === MoveId.ROUND);
|
||||
|
||||
// Mark the corresponding Pokemon as having "joined the Round" (for doubling power later)
|
||||
nextRoundPhase.pokemon.turnData.joinedRound = true;
|
||||
@ -6297,11 +6262,11 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
|
||||
// Handle cases where revived pokemon needs to get switched in on same turn
|
||||
if (allyPokemon.isFainted() || allyPokemon === pokemon) {
|
||||
// Enemy switch phase should be removed and replaced with the revived pkmn switching in
|
||||
globalScene.phaseManager.tryRemovePhase((phase: SwitchSummonPhase) => phase.is("SwitchSummonPhase") && phase.getPokemon() === pokemon);
|
||||
globalScene.phaseManager.tryRemovePhase("SwitchSummonPhase", phase => phase.getFieldIndex() === slotIndex);
|
||||
// If the pokemon being revived was alive earlier in the turn, cancel its move
|
||||
// (revived pokemon can't move in the turn they're brought back)
|
||||
// TODO: might make sense to move this to `FaintPhase` after checking for Rev Seed (rather than handling it in the move)
|
||||
globalScene.phaseManager.findPhase((phase: MovePhase) => phase.pokemon === pokemon)?.cancel();
|
||||
globalScene.phaseManager.getMovePhase((phase: MovePhase) => phase.pokemon === pokemon)?.cancel();
|
||||
if (user.fieldPosition === FieldPosition.CENTER) {
|
||||
user.setFieldPosition(FieldPosition.LEFT);
|
||||
}
|
||||
@ -6382,8 +6347,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
if (this.switchType === SwitchType.FORCE_SWITCH) {
|
||||
switchOutTarget.leaveField(true);
|
||||
const slotIndex = eligibleNewIndices[user.randBattleSeedInt(eligibleNewIndices.length)];
|
||||
globalScene.phaseManager.prependNewToPhase(
|
||||
"MoveEndPhase",
|
||||
globalScene.phaseManager.queueDeferred(
|
||||
"SwitchSummonPhase",
|
||||
this.switchType,
|
||||
switchOutTarget.getFieldIndex(),
|
||||
@ -6393,7 +6357,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
);
|
||||
} else {
|
||||
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
||||
globalScene.phaseManager.prependNewToPhase("MoveEndPhase",
|
||||
globalScene.phaseManager.queueDeferred(
|
||||
"SwitchPhase",
|
||||
this.switchType,
|
||||
switchOutTarget.getFieldIndex(),
|
||||
@ -6422,7 +6386,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
if (this.switchType === SwitchType.FORCE_SWITCH) {
|
||||
switchOutTarget.leaveField(true);
|
||||
const slotIndex = eligibleNewIndices[user.randBattleSeedInt(eligibleNewIndices.length)];
|
||||
globalScene.phaseManager.prependNewToPhase("MoveEndPhase",
|
||||
globalScene.phaseManager.queueDeferred(
|
||||
"SwitchSummonPhase",
|
||||
this.switchType,
|
||||
switchOutTarget.getFieldIndex(),
|
||||
@ -6432,7 +6396,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
);
|
||||
} else {
|
||||
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
||||
globalScene.phaseManager.prependNewToPhase("MoveEndPhase",
|
||||
globalScene.phaseManager.queueDeferred(
|
||||
"SwitchSummonPhase",
|
||||
this.switchType,
|
||||
switchOutTarget.getFieldIndex(),
|
||||
@ -6863,7 +6827,7 @@ class CallMoveAttr extends OverrideMoveEffectAttr {
|
||||
: moveTargets.targets[user.randBattleSeedInt(moveTargets.targets.length)]];
|
||||
|
||||
globalScene.phaseManager.unshiftNew("LoadMoveAnimPhase", move.id);
|
||||
globalScene.phaseManager.unshiftNew("MovePhase", user, targets, new PokemonMove(move.id), MoveUseMode.FOLLOW_UP);
|
||||
globalScene.phaseManager.unshiftNew("MovePhase", user, targets, new PokemonMove(move.id), MoveUseMode.FOLLOW_UP, MovePhaseTimingModifier.FIRST);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -7095,7 +7059,7 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr {
|
||||
|
||||
// Load the move's animation if we didn't already and unshift a new usage phase
|
||||
globalScene.phaseManager.unshiftNew("LoadMoveAnimPhase", moveId);
|
||||
globalScene.phaseManager.unshiftNew("MovePhase", user, [ target.getBattlerIndex() ], new PokemonMove(moveId), MoveUseMode.FOLLOW_UP);
|
||||
globalScene.phaseManager.unshiftNew("MovePhase", user, [ target.getBattlerIndex() ], new PokemonMove(moveId), MoveUseMode.FOLLOW_UP, MovePhaseTimingModifier.FIRST);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -7179,7 +7143,7 @@ export class RepeatMoveAttr extends MoveEffectAttr {
|
||||
targetPokemonName: getPokemonNameWithAffix(target)
|
||||
}));
|
||||
target.turnData.extraTurns++;
|
||||
globalScene.phaseManager.appendNewToPhase("MoveEndPhase", "MovePhase", target, moveTargets, movesetMove, MoveUseMode.NORMAL);
|
||||
globalScene.phaseManager.unshiftNew("MovePhase", target, moveTargets, movesetMove, MoveUseMode.NORMAL, MovePhaseTimingModifier.FIRST);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -7952,12 +7916,7 @@ export class AfterYouAttr extends MoveEffectAttr {
|
||||
*/
|
||||
override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:afterYou", { targetName: getPokemonNameWithAffix(target) }));
|
||||
|
||||
// Will find next acting phase of the targeted pokémon, delete it and queue it right after us.
|
||||
const targetNextPhase = globalScene.phaseManager.findPhase<MovePhase>(phase => phase.pokemon === target);
|
||||
if (targetNextPhase && globalScene.phaseManager.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) {
|
||||
globalScene.phaseManager.prependToPhase(targetNextPhase, "MovePhase");
|
||||
}
|
||||
globalScene.phaseManager.forceMoveNext((phase: MovePhase) => phase.pokemon === target);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -7980,45 +7939,11 @@ export class ForceLastAttr extends MoveEffectAttr {
|
||||
override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:forceLast", { targetPokemonName: getPokemonNameWithAffix(target) }));
|
||||
|
||||
// TODO: Refactor this to be more readable and less janky
|
||||
const targetMovePhase = globalScene.phaseManager.findPhase<MovePhase>((phase) => phase.pokemon === target);
|
||||
if (targetMovePhase && !targetMovePhase.isForcedLast() && globalScene.phaseManager.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) {
|
||||
// Finding the phase to insert the move in front of -
|
||||
// Either the end of the turn or in front of another, slower move which has also been forced last
|
||||
const prependPhase = globalScene.phaseManager.findPhase((phase) =>
|
||||
[ MovePhase, MoveEndPhase ].every(cls => !(phase instanceof cls))
|
||||
|| (phase.is("MovePhase")) && phaseForcedSlower(phase, target, !!globalScene.arena.getTag(ArenaTagType.TRICK_ROOM))
|
||||
);
|
||||
if (prependPhase) {
|
||||
globalScene.phaseManager.phaseQueue.splice(
|
||||
globalScene.phaseManager.phaseQueue.indexOf(prependPhase),
|
||||
0,
|
||||
globalScene.phaseManager.create("MovePhase", target, [ ...targetMovePhase.targets ], targetMovePhase.move, targetMovePhase.useMode, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
globalScene.phaseManager.forceMoveLast((phase: MovePhase) => phase.pokemon === target);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a {@linkcode MovePhase} has been forced last and the corresponding pokemon is slower than {@linkcode target}.
|
||||
|
||||
* TODO:
|
||||
- Make this a class method
|
||||
- Make this look at speed order from TurnStartPhase
|
||||
*/
|
||||
const phaseForcedSlower = (phase: MovePhase, target: Pokemon, trickRoom: boolean): boolean => {
|
||||
let slower: boolean;
|
||||
// quashed pokemon still have speed ties
|
||||
if (phase.pokemon.getEffectiveStat(Stat.SPD) === target.getEffectiveStat(Stat.SPD)) {
|
||||
slower = !!target.randBattleSeedInt(2);
|
||||
} else {
|
||||
slower = !trickRoom ? phase.pokemon.getEffectiveStat(Stat.SPD) < target.getEffectiveStat(Stat.SPD) : phase.pokemon.getEffectiveStat(Stat.SPD) > target.getEffectiveStat(Stat.SPD);
|
||||
}
|
||||
return phase.isForcedLast() && slower;
|
||||
};
|
||||
|
||||
const failOnGravityCondition: MoveConditionFunc = (user, target, move) => !globalScene.arena.getTag(ArenaTagType.GRAVITY);
|
||||
|
||||
const failOnBossCondition: MoveConditionFunc = (user, target, move) => !target.isBossImmune();
|
||||
@ -8042,7 +7967,7 @@ const userSleptOrComatoseCondition: MoveConditionFunc = (user) => user.status?.e
|
||||
|
||||
const targetSleptOrComatoseCondition: MoveConditionFunc = (_user: Pokemon, target: Pokemon, _move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(AbilityId.COMATOSE);
|
||||
|
||||
const failIfLastCondition: MoveConditionFunc = () => globalScene.phaseManager.findPhase(phase => phase.is("MovePhase")) !== undefined;
|
||||
const failIfLastCondition: MoveConditionFunc = () => globalScene.phaseManager.hasPhaseOfType("MovePhase");
|
||||
|
||||
const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => {
|
||||
const party: Pokemon[] = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty();
|
||||
|
@ -414,7 +414,7 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise<void> {
|
||||
pokemon.resetTurnData();
|
||||
|
||||
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
|
||||
globalScene.phaseManager.pushNew("PostSummonPhase", pokemon.getBattlerIndex());
|
||||
globalScene.phaseManager.unshiftNew("PostSummonPhase", pokemon.getBattlerIndex());
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
|
@ -669,7 +669,6 @@ function onGameOver() {
|
||||
|
||||
// Clear any leftover battle phases
|
||||
globalScene.phaseManager.clearPhaseQueue();
|
||||
globalScene.phaseManager.clearPhaseQueueSplice();
|
||||
|
||||
// Return enemy Pokemon
|
||||
const pokemon = globalScene.getEnemyPokemon();
|
||||
|
@ -738,7 +738,7 @@ export function setEncounterRewards(
|
||||
if (customShopRewards) {
|
||||
globalScene.phaseManager.unshiftNew("SelectModifierPhase", 0, undefined, customShopRewards);
|
||||
} else {
|
||||
globalScene.phaseManager.tryRemovePhase(p => p.is("MysteryEncounterRewardsPhase"));
|
||||
globalScene.phaseManager.removeAllPhasesOfType("MysteryEncounterRewardsPhase");
|
||||
}
|
||||
|
||||
if (eggRewards) {
|
||||
@ -812,8 +812,7 @@ export function leaveEncounterWithoutBattle(
|
||||
encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE,
|
||||
) {
|
||||
globalScene.currentBattle.mysteryEncounter!.encounterMode = encounterMode;
|
||||
globalScene.phaseManager.clearPhaseQueue();
|
||||
globalScene.phaseManager.clearPhaseQueueSplice();
|
||||
globalScene.phaseManager.clearPhaseQueue(true);
|
||||
handleMysteryEncounterVictory(addHealPhase);
|
||||
}
|
||||
|
||||
@ -826,7 +825,7 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu
|
||||
const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
|
||||
|
||||
if (allowedPkm.length === 0) {
|
||||
globalScene.phaseManager.clearPhaseQueue();
|
||||
globalScene.phaseManager.clearPhaseQueue(true);
|
||||
globalScene.phaseManager.unshiftNew("GameOverPhase");
|
||||
return;
|
||||
}
|
||||
@ -869,7 +868,7 @@ export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotCo
|
||||
const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
|
||||
|
||||
if (allowedPkm.length === 0) {
|
||||
globalScene.phaseManager.clearPhaseQueue();
|
||||
globalScene.phaseManager.clearPhaseQueue(true);
|
||||
globalScene.phaseManager.unshiftNew("GameOverPhase");
|
||||
return;
|
||||
}
|
||||
|
@ -1,125 +0,0 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { Phase } from "#app/phase";
|
||||
import { TrickRoomTag } from "#data/arena-tag";
|
||||
import { DynamicPhaseType } from "#enums/dynamic-phase-type";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { ActivatePriorityQueuePhase } from "#phases/activate-priority-queue-phase";
|
||||
import { PostSummonActivateAbilityPhase } from "#phases/post-summon-activate-ability-phase";
|
||||
import type { PostSummonPhase } from "#phases/post-summon-phase";
|
||||
import { BooleanHolder } from "#utils/common";
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to remove one or more Phases from the current queue.
|
||||
* @param phaseFilter - The function to select phases for removal
|
||||
* @param removeCount - The maximum number of phases to remove, or `all` to remove all matching phases;
|
||||
* default `1`
|
||||
* @returns The number of successfully removed phases
|
||||
* @todo Remove this eventually once the patchwork bug this is used for is fixed
|
||||
*/
|
||||
public tryRemovePhase(phaseFilter: (phase: Phase) => boolean, removeCount: number | "all" = 1): number {
|
||||
if (removeCount === "all") {
|
||||
removeCount = this.queue.length;
|
||||
} else if (removeCount < 1) {
|
||||
return 0;
|
||||
}
|
||||
let numRemoved = 0;
|
||||
|
||||
do {
|
||||
const phaseIndex = this.queue.findIndex(phaseFilter);
|
||||
if (phaseIndex === -1) {
|
||||
break;
|
||||
}
|
||||
this.queue.splice(phaseIndex, 1);
|
||||
numRemoved++;
|
||||
} while (numRemoved < removeCount && this.queue.length > 0);
|
||||
|
||||
return numRemoved;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
@ -22,10 +22,12 @@ export interface SerializedTerrain {
|
||||
export class Terrain {
|
||||
public terrainType: TerrainType;
|
||||
public turnsLeft: number;
|
||||
public maxDuration: number;
|
||||
|
||||
constructor(terrainType: TerrainType, turnsLeft?: number) {
|
||||
constructor(terrainType: TerrainType, turnsLeft = 0, maxDuration: number = turnsLeft) {
|
||||
this.terrainType = terrainType;
|
||||
this.turnsLeft = turnsLeft || 0;
|
||||
this.turnsLeft = turnsLeft;
|
||||
this.maxDuration = maxDuration;
|
||||
}
|
||||
|
||||
lapse(): boolean {
|
||||
|
@ -19,10 +19,12 @@ export interface SerializedWeather {
|
||||
export class Weather {
|
||||
public weatherType: WeatherType;
|
||||
public turnsLeft: number;
|
||||
public maxDuration: number;
|
||||
|
||||
constructor(weatherType: WeatherType, turnsLeft?: number) {
|
||||
constructor(weatherType: WeatherType, turnsLeft = 0, maxDuration: number = turnsLeft) {
|
||||
this.weatherType = weatherType;
|
||||
this.turnsLeft = !this.isImmutable() ? turnsLeft || 0 : 0;
|
||||
this.turnsLeft = this.isImmutable() ? 0 : turnsLeft;
|
||||
this.maxDuration = this.isImmutable() ? 0 : maxDuration;
|
||||
}
|
||||
|
||||
lapse(): boolean {
|
||||
|
187
src/dynamic-queue-manager.ts
Normal file
@ -0,0 +1,187 @@
|
||||
import type { DynamicPhase, PhaseConditionFunc, PhaseString } from "#app/@types/phase-types";
|
||||
import type { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import type { Pokemon } from "#app/field/pokemon";
|
||||
import type { Phase } from "#app/phase";
|
||||
import type { MovePhase } from "#app/phases/move-phase";
|
||||
import { MovePhasePriorityQueue } from "#app/queues/move-phase-priority-queue";
|
||||
import { PokemonPhasePriorityQueue } from "#app/queues/pokemon-phase-priority-queue";
|
||||
import { PostSummonPhasePriorityQueue } from "#app/queues/post-summon-phase-priority-queue";
|
||||
import type { PriorityQueue } from "#app/queues/priority-queue";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import type { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
|
||||
|
||||
// TODO: might be easier to define which phases should be dynamic instead
|
||||
/** All phases which have defined a `getPokemon` method but should not be sorted dynamically */
|
||||
const nonDynamicPokemonPhases: readonly PhaseString[] = [
|
||||
"SummonPhase",
|
||||
"CommandPhase",
|
||||
"LearnMovePhase",
|
||||
"MoveEffectPhase",
|
||||
"MoveEndPhase",
|
||||
"FaintPhase",
|
||||
"DamageAnimPhase",
|
||||
"VictoryPhase",
|
||||
"PokemonHealPhase",
|
||||
"WeatherEffectPhase",
|
||||
"ShowAbilityPhase",
|
||||
"HideAbilityPhase",
|
||||
"ExpPhase",
|
||||
"ShowPartyExpBarPhase",
|
||||
"HidePartyExpBarPhase",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* The dynamic queue manager holds priority queues for phases which are queued as dynamic.
|
||||
*
|
||||
* Dynamic phases are generally those which hold a pokemon and are unshifted, not pushed. \
|
||||
* Queues work by sorting their entries in speed order (and possibly with more complex ordering) before each time a phase is popped.
|
||||
*
|
||||
* As the holder, this structure is also used to access and modify queued phases.
|
||||
* This is mostly used in redirection, cancellation, etc. of {@linkcode MovePhase}s.
|
||||
*/
|
||||
export class DynamicQueueManager {
|
||||
/** Maps phase types to their corresponding queues */
|
||||
private readonly dynamicPhaseMap: Map<PhaseString, PriorityQueue<Phase>>;
|
||||
|
||||
constructor() {
|
||||
this.dynamicPhaseMap = new Map();
|
||||
// PostSummon and Move phases have specialized queues
|
||||
this.dynamicPhaseMap.set("PostSummonPhase", new PostSummonPhasePriorityQueue());
|
||||
this.dynamicPhaseMap.set("MovePhase", new MovePhasePriorityQueue());
|
||||
}
|
||||
|
||||
/** Removes all phases from the manager */
|
||||
public clearQueues(): void {
|
||||
for (const queue of this.dynamicPhaseMap.values()) {
|
||||
queue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new phase to the manager and creates the priority queue for it if one does not exist.
|
||||
* @param phase - The {@linkcode Phase} to add
|
||||
* @returns `true` if the phase was added, or `false` if it is not dynamic
|
||||
*/
|
||||
public queueDynamicPhase<T extends Phase>(phase: T): boolean {
|
||||
if (!this.isDynamicPhase(phase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.dynamicPhaseMap.has(phase.phaseName)) {
|
||||
// TS can't figure out that T is dynamic at this point, but it does know that `typeof phase` is
|
||||
this.dynamicPhaseMap.set(phase.phaseName, new PokemonPhasePriorityQueue<typeof phase>());
|
||||
}
|
||||
this.dynamicPhaseMap.get(phase.phaseName)?.push(phase);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the highest-priority (generally by speed) {@linkcode Phase} of the specified type
|
||||
* @param type - The {@linkcode PhaseString | type} to pop
|
||||
* @returns The popped {@linkcode Phase}, or `undefined` if none of the specified type exist
|
||||
*/
|
||||
public popNextPhase(type: PhaseString): Phase | undefined {
|
||||
return this.dynamicPhaseMap.get(type)?.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if there is a queued dynamic {@linkcode Phase} meeting the conditions
|
||||
* @param type - The {@linkcode PhaseString | type} of phase to search for
|
||||
* @param condition - An optional {@linkcode PhaseConditionFunc} to add conditions to the search
|
||||
* @returns Whether a matching phase exists
|
||||
*/
|
||||
public exists<T extends PhaseString>(type: T, condition?: PhaseConditionFunc<T>): boolean {
|
||||
return !!this.dynamicPhaseMap.get(type)?.has(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and removes a single queued {@linkcode Phase}
|
||||
* @param type - The {@linkcode PhaseString | type} of phase to search for
|
||||
* @param phaseFilter - A {@linkcode PhaseConditionFunc} to specify conditions for the phase
|
||||
* @returns Whether a removal occurred
|
||||
*/
|
||||
public removePhase<T extends PhaseString>(type: T, condition?: PhaseConditionFunc<T>): boolean {
|
||||
return !!this.dynamicPhaseMap.get(type)?.remove(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timing modifier of a move (i.e. to force it first or last)
|
||||
* @param condition - A {@linkcode PhaseConditionFunc} to specify conditions for the move
|
||||
* @param modifier - The {@linkcode MovePhaseTimingModifier} to switch the move to
|
||||
*/
|
||||
public setMoveTimingModifier(condition: PhaseConditionFunc<"MovePhase">, modifier: MovePhaseTimingModifier): void {
|
||||
this.getMovePhaseQueue().setTimingModifier(condition, modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the {@linkcode MovePhase} meeting the condition and changes its move
|
||||
* @param phaseCondition - The {@linkcode PhaseConditionFunc | condition} function
|
||||
* @param move - The {@linkcode PokemonMove | move} to use in replacement
|
||||
*/
|
||||
public setMoveForPhase(condition: PhaseConditionFunc<"MovePhase">, move: PokemonMove): void {
|
||||
this.getMovePhaseQueue().setMoveForPhase(condition, move);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects moves which were targeted at a {@linkcode Pokemon} that has been removed
|
||||
* @param removedPokemon - The removed {@linkcode Pokemon}
|
||||
* @param allyPokemon - The ally of the removed pokemon
|
||||
*/
|
||||
public redirectMoves(removedPokemon: Pokemon, allyPokemon: Pokemon): void {
|
||||
this.getMovePhaseQueue().redirectMoves(removedPokemon, allyPokemon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a {@linkcode MovePhase} meeting the condition
|
||||
* @param phaseCondition - The {@linkcode PhaseConditionFunc | condition} function
|
||||
* @returns The MovePhase, or `undefined` if it does not exist
|
||||
*/
|
||||
public getMovePhase(condition: PhaseConditionFunc<"MovePhase">): MovePhase | undefined {
|
||||
return this.getMovePhaseQueue().find(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and cancels a {@linkcode MovePhase} meeting the condition
|
||||
* @param phaseCondition - The {@linkcode PhaseConditionFunc | condition} function
|
||||
*/
|
||||
public cancelMovePhase(condition: PhaseConditionFunc<"MovePhase">): void {
|
||||
this.getMovePhaseQueue().cancelMove(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the move order to a static array rather than a dynamic queue
|
||||
* @param order - The order of {@linkcode BattlerIndex}s
|
||||
*/
|
||||
public setMoveOrder(order: BattlerIndex[]): void {
|
||||
this.getMovePhaseQueue().setMoveOrder(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns An in-order array of {@linkcode Pokemon}, representing the turn order as played out in the most recent turn
|
||||
*/
|
||||
public getLastTurnOrder(): Pokemon[] {
|
||||
return this.getMovePhaseQueue().getTurnOrder();
|
||||
}
|
||||
|
||||
/** Clears the stored `Move` turn order */
|
||||
public clearLastTurnOrder(): void {
|
||||
this.getMovePhaseQueue().clearTurnOrder();
|
||||
}
|
||||
|
||||
/** Internal helper to get the {@linkcode MovePhasePriorityQueue} */
|
||||
private getMovePhaseQueue(): MovePhasePriorityQueue {
|
||||
return this.dynamicPhaseMap.get("MovePhase") as MovePhasePriorityQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper to determine if a phase is dynamic.
|
||||
* @param phase - The {@linkcode Phase} to check
|
||||
* @returns Whether `phase` is dynamic
|
||||
* @privateRemarks
|
||||
* Currently, this checks that `phase` has a `getPokemon` method
|
||||
* and is not blacklisted in `nonDynamicPokemonPhases`.
|
||||
*/
|
||||
private isDynamicPhase(phase: Phase): phase is DynamicPhase {
|
||||
return typeof (phase as any).getPokemon === "function" && !nonDynamicPokemonPhases.includes(phase.phaseName);
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
// TODO: rename to something else (this isn't used only for arena tags)
|
||||
export enum ArenaTagSide {
|
||||
BOTH,
|
||||
PLAYER,
|
||||
|
@ -34,4 +34,5 @@ export enum ArenaTagType {
|
||||
GRASS_WATER_PLEDGE = "GRASS_WATER_PLEDGE",
|
||||
FAIRY_LOCK = "FAIRY_LOCK",
|
||||
NEUTRALIZING_GAS = "NEUTRALIZING_GAS",
|
||||
PENDING_HEAL = "PENDING_HEAL",
|
||||
}
|
||||
|
@ -94,4 +94,6 @@ export enum BattlerTagType {
|
||||
ENDURE_TOKEN = "ENDURE_TOKEN",
|
||||
POWDER = "POWDER",
|
||||
MAGIC_COAT = "MAGIC_COAT",
|
||||
SUPREME_OVERLORD = "SUPREME_OVERLORD",
|
||||
BYPASS_SPEED = "BYPASS_SPEED",
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Enum representation of the phase types held by implementations of {@linkcode PhasePriorityQueue}.
|
||||
*/
|
||||
// TODO: We currently assume these are in order
|
||||
export enum DynamicPhaseType {
|
||||
POST_SUMMON,
|
||||
}
|