Merge branch 'beta' of https://github.com/pagefaultgames/pokerogue into held-item-refactor

This commit is contained in:
Wlowscha 2025-06-15 11:08:19 +02:00
commit 75907bc8e3
No known key found for this signature in database
GPG Key ID: 3C8F1AD330565D04
160 changed files with 3636 additions and 2546 deletions

View File

@ -2,25 +2,28 @@
<!-- Feel free to look at other PRs for examples --> <!-- Feel free to look at other PRs for examples -->
<!-- <!--
Make sure the title includes categorization (choose the one that best fits): Make sure the title includes categorization (choose the one that best fits):
- [Bug]: If the PR is primarily a bug fix - [Bug]: If the PR is primarily a bug fix
- [Move]: If a move has new or changed functionality - [Move]: If a move has new or changed functionality
- [Ability]: If an ability has new or changed functionality - [Ability]: If an ability has new or changed functionality
- [Item]: For new or modified items - [Item]: For new or modified items
- [Mystery]: For new or modified Mystery Encounters - [Mystery]: For new or modified Mystery Encounters
- [Test]: If the PR is primarily adding or modifying tests - [Test]: If the PR is primarily adding or modifying tests
- [UI/UX]: If the PR is changing UI/UX elements - [UI/UX]: If the PR is changing UI/UX elements
- [Audio]: If the PR is adding or changing music/sfx - [Audio]: If the PR is adding or changing music/sfx
- [Sprite]: If the PR is adding or changing sprites - [Sprite]: If the PR is adding or changing sprites
- [Balance]: If the PR is related to game balance - [Balance]: If the PR is related to game balance
- [Challenge]: If the PR is adding or modifying challenges - [Challenge]: If the PR is adding or modifying challenges
- [Refactor]: If the PR is primarily rewriting existing code - [Refactor]: If the PR is primarily rewriting existing code
- [Docs]: If the PR is just adding or modifying documentation (such as tsdocs/code comments) - [Dev]: If the PR is primarily changing something pertaining to development (lefthook hooks, linter rules, etc.)
- [GitHub]: For changes to GitHub workflows/templates/etc - [i18n]: If the PR is primarily adding/changing locale keys or key usage (may come with an associated locales PR)
- [Misc]: If no other category fits the PR - [Docs]: If the PR is adding or modifying documentation (such as tsdocs/code comments)
- [GitHub]: For changes to GitHub workflows/templates/etc
- [Misc]: If no other category fits the PR
--> -->
<!-- <!--
Make sure that this PR is not overlapping with someone else's work Make sure that this PR is not overlapping with someone else's work
Please try to keep the PR self-contained (and small) Please try to keep the PR self-contained (and small!)
--> -->
## What are the changes the user will see? ## What are the changes the user will see?
@ -66,11 +69,11 @@ 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)?
Are there any localization additions or changes? If so: Are there any localization additions or changes? If so:
- [ ] Has a locales PR been created on the [locales](https://github.com/pagefaultgames/pokerogue-locales) repo? - [ ] Has a locales PR been created on the [locales](https://github.com/pagefaultgames/pokerogue-locales) repo?
- [ ] If so, please leave a link to it here: - [ ] If so, please leave a link to it here:
- [ ] Has the translation team been contacted for proofreading/translation? - [ ] Has the translation team been contacted for proofreading/translation?

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

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

@ -53,20 +53,12 @@ import {
getEnemyHeldItemsForWave, getEnemyHeldItemsForWave,
getLuckString, getLuckString,
getLuckTextTint, getLuckTextTint,
getModifierPoolForType,
getPartyLuckValue, getPartyLuckValue,
ModifierPoolType,
} from "#app/modifier/modifier-type"; } from "#app/modifier/modifier-type";
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";
@ -138,14 +130,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";
@ -1261,7 +1252,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);
} }
@ -1466,7 +1457,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)
@ -1641,6 +1632,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
@ -2744,7 +2738,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) {
@ -2765,7 +2759,7 @@ export default class BattleScene extends SceneBase {
target.heldItemManager.add(heldItemId, countTaken, data); target.heldItemManager.add(heldItemId, countTaken, data);
if (source.heldItemManager.getStack(heldItemId) === 0 && itemLost) { if (source.heldItemManager.getStack(heldItemId) === 0 && itemLost) {
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
} }
if (source.isPlayer() !== target.isPlayer() && !ignoreUpdate) { if (source.isPlayer() !== target.isPlayer() && !ignoreUpdate) {
@ -2784,7 +2778,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) {

View File

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

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";
@ -144,7 +136,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 +201,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 +757,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;
} }
@ -953,7 +945,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 +1002,7 @@ class StickyWebTag extends ArenaTrapTag {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) { if (pokemon.isGrounded()) {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
if (simulated) { if (simulated) {
return !cancelled.value; return !cancelled.value;
@ -1444,8 +1436,8 @@ export class SuppressAbilitiesTag extends ArenaTag {
// Could have a custom message that plays when a specific pokemon's NG ends? This entire thing exists due to passives after all // Could have a custom message that plays when a specific pokemon's NG ends? This entire thing exists due to passives after all
const setter = globalScene const setter = globalScene
.getField() .getField()
.filter(p => p?.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false))[0]; .filter(p => p?.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0];
applyOnGainAbAttrs(setter, setter.getAbility().hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr)); applyOnGainAbAttrs(setter, setter.getAbility().hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr"));
} }
} }
@ -1457,7 +1449,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));
} }
} }

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

@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
import { allMoves } from "./data-lists"; import { allMoves } from "./data-lists";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common"; import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName, coerceArray } from "../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 { SubstituteTag } from "./battler-tags";
@ -10,6 +10,7 @@ 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 +520,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 +771,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?

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";
@ -57,7 +50,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;
@ -648,7 +641,7 @@ export class FlinchedTag extends BattlerTag {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
); );
applyAbAttrs(FlinchEffectAbAttr, pokemon, null); applyAbAttrs("FlinchEffectAbAttr", pokemon, null);
return true; return true;
} }
@ -942,7 +935,7 @@ export class SeedTag extends BattlerTag {
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex); const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
if (source) { if (source) {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
@ -953,7 +946,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(),
@ -1026,7 +1019,7 @@ export class PowderTag extends BattlerTag {
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 });
} }
@ -1079,7 +1072,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 });
@ -1438,7 +1431,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 });
@ -1681,7 +1674,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,
@ -2277,7 +2270,7 @@ export class SaltCuredTag extends BattlerTag {
); );
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER); const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER);
@ -2331,7 +2324,7 @@ export class CursedTag extends BattlerTag {
); );
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
@ -2666,7 +2659,7 @@ export class GulpMissileTag extends BattlerTag {
} }
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", attacker, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT }); attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT });
@ -3056,8 +3049,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

@ -21,7 +21,7 @@ import { TrainerType } from "#enums/trainer-type";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
import { TypeColor, TypeShadow } from "#enums/color"; import { TypeColor, TypeShadow } from "#enums/color";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { pokemonFormChanges } from "./pokemon-forms"; import { pokemonFormChanges } from "./pokemon-forms";
import { pokemonEvolutions } from "./balance/pokemon-evolutions"; import { pokemonEvolutions } from "./balance/pokemon-evolutions";

View File

@ -1,5 +1,9 @@
import type { Ability } from "./abilities/ability-class"; import type { ModifierTypes } from "#app/modifier/modifier-type";
import type { Ability } from "./abilities/ability";
import type Move from "./moves/move"; import type Move from "./moves/move";
export const allAbilities: Ability[] = []; export const allAbilities: Ability[] = [];
export const allMoves: Move[] = []; export const allMoves: Move[] = [];
// TODO: Figure out what this is used for and provide an appropriate tsdoc comment
export const modifierTypes = {} as ModifierTypes;

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

@ -33,38 +33,12 @@ import type { ArenaTrapTag } from "../arena-tag";
import { WeakenMoveTypeTag } from "../arena-tag"; import { WeakenMoveTypeTag } from "../arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagSide } from "#enums/arena-tag-side";
import { import {
AllyMoveCategoryPowerBoostAbAttr,
applyAbAttrs, applyAbAttrs,
applyPostAttackAbAttrs, applyPostAttackAbAttrs,
applyPostItemLostAbAttrs, applyPostItemLostAbAttrs,
applyPreAttackAbAttrs, applyPreAttackAbAttrs,
applyPreDefendAbAttrs, applyPreDefendAbAttrs
BlockItemTheftAbAttr, } from "../abilities/apply-ab-attrs";
BlockNonDirectDamageAbAttr,
BlockOneHitKOAbAttr,
BlockRecoilDamageAttr,
ChangeMovePriorityAbAttr,
ConfusionOnStatusEffectAbAttr,
FieldMoveTypePowerBoostAbAttr,
FieldPreventExplosiveMovesAbAttr,
ForceSwitchOutImmunityAbAttr,
HealFromBerryUseAbAttr,
IgnoreContactAbAttr,
IgnoreMoveEffectsAbAttr,
IgnoreProtectOnContactAbAttr,
InfiltratorAbAttr,
MaxMultiHitAbAttr,
MoveAbilityBypassAbAttr,
MoveEffectChanceMultiplierAbAttr,
MoveTypeChangeAbAttr,
PostDamageForceSwitchAbAttr,
PostItemLostAbAttr,
ReflectStatusMoveAbAttr,
ReverseDrainAbAttr,
UserFieldMoveTypePowerBoostAbAttr,
VariableMovePowerAbAttr,
WonderSkinAbAttr,
} from "../abilities/ability";
import { allAbilities, allMoves } from "../data-lists"; import { allAbilities, allMoves } from "../data-lists";
import { import {
PreserveBerryModifier, PreserveBerryModifier,
@ -72,7 +46,7 @@ import {
import type { BattlerIndex } from "#enums/battler-index"; import type { BattlerIndex } from "#enums/battler-index";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import { TerrainType } from "../terrain"; import { TerrainType } from "../terrain";
import { ModifierPoolType } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#enums/modifier-pool-type";
import { Command } from "#enums/command"; import { Command } from "#enums/command";
import i18next from "i18next"; import i18next from "i18next";
import type { Localizable } from "#app/@types/locales"; import type { Localizable } from "#app/@types/locales";
@ -376,7 +350,7 @@ export default abstract class Move implements Localizable {
const bypassed = new BooleanHolder(false); const bypassed = new BooleanHolder(false);
// TODO: Allow this to be simulated // TODO: Allow this to be simulated
applyAbAttrs(InfiltratorAbAttr, user, null, false, bypassed); applyAbAttrs("InfiltratorAbAttr", user, null, false, bypassed);
return !bypassed.value return !bypassed.value
&& !this.hasFlag(MoveFlags.SOUND_BASED) && !this.hasFlag(MoveFlags.SOUND_BASED)
@ -667,14 +641,14 @@ export default abstract class Move implements Localizable {
// special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact // special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact
switch (flag) { switch (flag) {
case MoveFlags.MAKES_CONTACT: case MoveFlags.MAKES_CONTACT:
if (user.hasAbilityWithAttr(IgnoreContactAbAttr) || this.hitsSubstitute(user, target)) { if (user.hasAbilityWithAttr("IgnoreContactAbAttr") || this.hitsSubstitute(user, target)) {
return false; return false;
} }
break; break;
case MoveFlags.IGNORE_ABILITIES: case MoveFlags.IGNORE_ABILITIES:
if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { if (user.hasAbilityWithAttr("MoveAbilityBypassAbAttr")) {
const abilityEffectsIgnored = new BooleanHolder(false); const abilityEffectsIgnored = new BooleanHolder(false);
applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); applyAbAttrs("MoveAbilityBypassAbAttr", user, abilityEffectsIgnored, false, this);
if (abilityEffectsIgnored.value) { if (abilityEffectsIgnored.value) {
return true; return true;
} }
@ -683,7 +657,7 @@ export default abstract class Move implements Localizable {
} }
return this.hasFlag(MoveFlags.IGNORE_ABILITIES) && !isFollowUp; return this.hasFlag(MoveFlags.IGNORE_ABILITIES) && !isFollowUp;
case MoveFlags.IGNORE_PROTECT: case MoveFlags.IGNORE_PROTECT:
if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) if (user.hasAbilityWithAttr("IgnoreProtectOnContactAbAttr")
&& this.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user })) { && this.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user })) {
return true; return true;
} }
@ -694,7 +668,7 @@ export default abstract class Move implements Localizable {
target?.getTag(SemiInvulnerableTag) || target?.getTag(SemiInvulnerableTag) ||
!(target?.getTag(BattlerTagType.MAGIC_COAT) || !(target?.getTag(BattlerTagType.MAGIC_COAT) ||
(!this.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) && (!this.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) &&
target?.hasAbilityWithAttr(ReflectStatusMoveAbAttr))) target?.hasAbilityWithAttr("ReflectStatusMoveAbAttr")))
) { ) {
return false; return false;
} }
@ -791,7 +765,7 @@ export default abstract class Move implements Localizable {
const moveAccuracy = new NumberHolder(this.accuracy); const moveAccuracy = new NumberHolder(this.accuracy);
applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy); applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy);
applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, simulated, moveAccuracy); applyPreDefendAbAttrs("WonderSkinAbAttr", target, user, this, { value: false }, simulated, moveAccuracy);
if (moveAccuracy.value === -1) { if (moveAccuracy.value === -1) {
return moveAccuracy.value; return moveAccuracy.value;
@ -834,25 +808,25 @@ export default abstract class Move implements Localizable {
const typeChangeMovePowerMultiplier = new NumberHolder(1); const typeChangeMovePowerMultiplier = new NumberHolder(1);
const typeChangeHolder = new NumberHolder(this.type); const typeChangeHolder = new NumberHolder(this.type);
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier); applyPreAttackAbAttrs("MoveTypeChangeAbAttr", source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier);
const sourceTeraType = source.getTeraType(); const sourceTeraType = source.getTeraType();
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !source.heldItemManager.hasItem(HeldItemId.MULTI_LENS)) { if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !source.heldItemManager.hasItem(HeldItemId.MULTI_LENS)) {
power.value = 60; power.value = 60;
} }
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, simulated, power); applyPreAttackAbAttrs("VariableMovePowerAbAttr", source, target, this, simulated, power);
const ally = source.getAlly(); const ally = source.getAlly();
if (!isNullOrUndefined(ally)) { if (!isNullOrUndefined(ally)) {
applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, ally, target, this, simulated, power); applyPreAttackAbAttrs("AllyMoveCategoryPowerBoostAbAttr", ally, target, this, simulated, power);
} }
const fieldAuras = new Set( const fieldAuras = new Set(
globalScene.getField(true) globalScene.getField(true)
.map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr).filter(attr => { .map((p) => p.getAbilityAttrs("FieldMoveTypePowerBoostAbAttr").filter(attr => {
const condition = attr.getCondition(); const condition = attr.getCondition();
return (!condition || condition(p)); return (!condition || condition(p));
}) as FieldMoveTypePowerBoostAbAttr[]) }))
.flat(), .flat(),
); );
for (const aura of fieldAuras) { for (const aura of fieldAuras) {
@ -860,7 +834,7 @@ export default abstract class Move implements Localizable {
} }
const alliedField: Pokemon[] = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); const alliedField: Pokemon[] = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, target, this, simulated, power)); alliedField.forEach(p => applyPreAttackAbAttrs("UserFieldMoveTypePowerBoostAbAttr", p, target, this, simulated, power));
power.value *= typeChangeMovePowerMultiplier.value; power.value *= typeChangeMovePowerMultiplier.value;
@ -891,7 +865,7 @@ export default abstract class Move implements Localizable {
const priority = new NumberHolder(this.priority); const priority = new NumberHolder(this.priority);
applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority); applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority);
applyAbAttrs(ChangeMovePriorityAbAttr, user, null, simulated, this, priority); applyAbAttrs("ChangeMovePriorityAbAttr", user, null, simulated, this, priority);
return priority.value; return priority.value;
} }
@ -1343,7 +1317,7 @@ export class MoveEffectAttr extends MoveAttr {
getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number { getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number {
const moveChance = new NumberHolder(this.effectChanceOverride ?? move.chance); const moveChance = new NumberHolder(this.effectChanceOverride ?? move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, !showAbility, moveChance, move); applyAbAttrs("MoveEffectChanceMultiplierAbAttr", user, null, !showAbility, moveChance, move);
if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) { if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) {
const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
@ -1351,7 +1325,7 @@ export class MoveEffectAttr extends MoveAttr {
} }
if (!selfEffect) { if (!selfEffect) {
applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, target, user, null, null, !showAbility, moveChance); applyPreDefendAbAttrs("IgnoreMoveEffectsAbAttr", target, user, null, null, !showAbility, moveChance);
} }
return moveChance.value; return moveChance.value;
} }
@ -1729,8 +1703,8 @@ export class RecoilAttr extends MoveEffectAttr {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
if (!this.unblockable) { if (!this.unblockable) {
applyAbAttrs(BlockRecoilDamageAttr, user, cancelled); applyAbAttrs("BlockRecoilDamageAttr", user, cancelled);
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
} }
if (cancelled.value) { if (cancelled.value) {
@ -1863,7 +1837,7 @@ export class HalfSacrificialAttr extends MoveEffectAttr {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
// Check to see if the Pokemon has an ability that blocks non-direct damage // Check to see if the Pokemon has an ability that blocks non-direct damage
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true }); user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true });
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message
@ -2062,7 +2036,7 @@ export class FlameBurstAttr extends MoveEffectAttr {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
if (!isNullOrUndefined(targetAlly)) { if (!isNullOrUndefined(targetAlly)) {
applyAbAttrs(BlockNonDirectDamageAbAttr, targetAlly, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", targetAlly, cancelled);
} }
if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) { if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) {
@ -2185,6 +2159,7 @@ export class PlantHealAttr extends WeatherHealAttr {
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
case WeatherType.HAIL: case WeatherType.HAIL:
case WeatherType.SNOW: case WeatherType.SNOW:
case WeatherType.FOG:
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
return 0.25; return 0.25;
default: default:
@ -2292,7 +2267,7 @@ export class HitHealAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
let healAmount = 0; let healAmount = 0;
let message = ""; let message = "";
const reverseDrain = target.hasAbilityWithAttr(ReverseDrainAbAttr, false); const reverseDrain = target.hasAbilityWithAttr("ReverseDrainAbAttr", false);
if (this.healStat !== null) { if (this.healStat !== null) {
// Strength Sap formula // Strength Sap formula
healAmount = target.getEffectiveStat(this.healStat); healAmount = target.getEffectiveStat(this.healStat);
@ -2303,7 +2278,7 @@ export class HitHealAttr extends MoveEffectAttr {
message = i18next.t("battle:regainHealth", { pokemonName: getPokemonNameWithAffix(user) }); message = i18next.t("battle:regainHealth", { pokemonName: getPokemonNameWithAffix(user) });
} }
if (reverseDrain) { if (reverseDrain) {
if (user.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { if (user.hasAbilityWithAttr("BlockNonDirectDamageAbAttr")) {
healAmount = 0; healAmount = 0;
message = ""; message = "";
} else { } else {
@ -2433,7 +2408,7 @@ export class MultiHitAttr extends MoveAttr {
{ {
const rand = user.randBattleSeedInt(20); const rand = user.randBattleSeedInt(20);
const hitValue = new NumberHolder(rand); const hitValue = new NumberHolder(rand);
applyAbAttrs(MaxMultiHitAbAttr, user, null, false, hitValue); applyAbAttrs("MaxMultiHitAbAttr", user, null, false, hitValue);
if (hitValue.value >= 13) { if (hitValue.value >= 13) {
return 2; return 2;
} else if (hitValue.value >= 6) { } else if (hitValue.value >= 6) {
@ -2541,7 +2516,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
} }
if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0)) if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0))
&& pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus, quiet)) { && pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus, quiet)) {
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect); applyPostAttackAbAttrs("ConfusionOnStatusEffectAbAttr", user, target, move, null, false, this.effect);
return true; return true;
} }
} }
@ -2699,7 +2674,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
// Check for abilities that block item theft // Check for abilities that block item theft
// TODO: This should not trigger if the target would faint beforehand // TODO: This should not trigger if the target would faint beforehand
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); applyAbAttrs("BlockItemTheftAbAttr", target, cancelled);
if (cancelled.value) { if (cancelled.value) {
return false; return false;
@ -2812,8 +2787,8 @@ export class EatBerryAttr extends MoveEffectAttr {
protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) { protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) {
// consumer eats berry, owner triggers unburden and similar effects // consumer eats berry, owner triggers unburden and similar effects
getBerryEffectFunc(allHeldItems[this.chosenBerry].berryType)(consumer); getBerryEffectFunc(allHeldItems[this.chosenBerry].berryType)(consumer);
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner, false); applyPostItemLostAbAttrs("PostItemLostAbAttr", berryOwner, false);
applyAbAttrs(HealFromBerryUseAbAttr, consumer, new BooleanHolder(false)); applyAbAttrs("HealFromBerryUseAbAttr", consumer, new BooleanHolder(false));
consumer.recordEatenBerry(allHeldItems[this.chosenBerry].berryType, updateHarvest); consumer.recordEatenBerry(allHeldItems[this.chosenBerry].berryType, updateHarvest);
} }
} }
@ -2838,7 +2813,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
// check for abilities that block item theft // check for abilities that block item theft
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); applyAbAttrs("BlockItemTheftAbAttr", target, cancelled);
if (cancelled.value === true) { if (cancelled.value === true) {
return false; return false;
} }
@ -2852,7 +2827,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
// pick a random berry and eat it // pick a random berry and eat it
this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)]; this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)];
applyPostItemLostAbAttrs(PostItemLostAbAttr, target, false); applyPostItemLostAbAttrs("PostItemLostAbAttr", target, false);
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: allHeldItems[this.chosenBerry].name }); const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: allHeldItems[this.chosenBerry].name });
globalScene.phaseManager.queueMessage(message); globalScene.phaseManager.queueMessage(message);
this.reduceBerryModifier(target); this.reduceBerryModifier(target);
@ -2893,7 +2868,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
// Special edge case for shield dust blocking Sparkling Aria curing burn // Special edge case for shield dust blocking Sparkling Aria curing burn
const moveTargets = getMoveTargets(user, move.id); const moveTargets = getMoveTargets(user, move.id);
if (target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && move.id === MoveId.SPARKLING_ARIA && moveTargets.targets.length === 1) { if (target.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") && move.id === MoveId.SPARKLING_ARIA && moveTargets.targets.length === 1) {
return false; return false;
} }
@ -3043,7 +3018,7 @@ export class OneHitKOAttr extends MoveAttr {
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
return (user, target, move) => { return (user, target, move) => {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockOneHitKOAbAttr, target, cancelled); applyAbAttrs("BlockOneHitKOAbAttr", target, cancelled);
return !cancelled.value && user.level >= target.level; return !cancelled.value && user.level >= target.level;
}; };
} }
@ -4184,6 +4159,7 @@ export class AntiSunlightPowerDecreaseAttr extends VariablePowerAttr {
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
case WeatherType.HAIL: case WeatherType.HAIL:
case WeatherType.SNOW: case WeatherType.SNOW:
case WeatherType.FOG:
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
power.value *= 0.5; power.value *= 0.5;
return true; return true;
@ -5443,7 +5419,7 @@ export class NoEffectAttr extends MoveAttr {
const crashDamageFunc = (user: Pokemon, move: Move) => { const crashDamageFunc = (user: Pokemon, move: Move) => {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
if (cancelled.value) { if (cancelled.value) {
return false; return false;
} }
@ -6303,7 +6279,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
* Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch * Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch
* If it did, the user of U-turn or Volt Switch will not be switched out. * If it did, the user of U-turn or Volt Switch will not be switched out.
*/ */
if (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr) if (target.getAbility().hasAttr("PostDamageForceSwitchAbAttr")
&& [ MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN ].includes(move.id) && [ MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN ].includes(move.id)
) { ) {
if (this.hpDroppedBelowHalf(target)) { if (this.hpDroppedBelowHalf(target)) {
@ -6392,7 +6368,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
* Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch * Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch
* If it did, the user of U-turn or Volt Switch will not be switched out. * If it did, the user of U-turn or Volt Switch will not be switched out.
*/ */
if (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr) if (target.getAbility().hasAttr("PostDamageForceSwitchAbAttr")
&& [ MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN ].includes(move.id) && [ MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN ].includes(move.id)
) { ) {
if (this.hpDroppedBelowHalf(target)) { if (this.hpDroppedBelowHalf(target)) {
@ -6432,7 +6408,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined { getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined {
const blockedByAbility = new BooleanHolder(false); const blockedByAbility = new BooleanHolder(false);
applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility); applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility);
if (blockedByAbility.value) { if (blockedByAbility.value) {
return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) }); return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) });
} }
@ -6472,7 +6448,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
} }
const blockedByAbility = new BooleanHolder(false); const blockedByAbility = new BooleanHolder(false);
applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility); applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility);
if (blockedByAbility.value) { if (blockedByAbility.value) {
return false; return false;
} }
@ -7979,7 +7955,7 @@ const failIfSingleBattle: MoveConditionFunc = (user, target, move) => globalScen
const failIfDampCondition: MoveConditionFunc = (user, target, move) => { const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
globalScene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled)); globalScene.getField(true).map(p=>applyAbAttrs("FieldPreventExplosiveMovesAbAttr", p, cancelled));
// Queue a message if an ability prevented usage of the move // Queue a message if an ability prevented usage of the move
if (cancelled.value) { if (cancelled.value) {
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name }));

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

@ -9,7 +9,7 @@ import {
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 { 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";

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

View File

@ -11,7 +11,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 { ModifierTypeOption } from "#app/modifier/modifier-type"; import type { 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

@ -36,12 +36,12 @@ 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 { ModifierTypeOption } from "#app/modifier/modifier-type"; import type { ModifierTypeOption } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
import { GigantamaxAccessModifier, MegaEvolutionAccessModifier } from "#app/modifier/modifier"; import { GigantamaxAccessModifier, MegaEvolutionAccessModifier } from "#app/modifier/modifier";
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";
import { HeldItemId } from "#enums/held-item-id"; import { HeldItemId } from "#enums/held-item-id";

View File

@ -11,9 +11,10 @@ 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 { modifierTypes } from "#app/data/data-lists";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -38,7 +39,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 +49,7 @@ 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 { allAbilities } 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.

View File

@ -26,7 +26,7 @@ import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon } from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";

View File

@ -3,7 +3,7 @@ import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";

View File

@ -24,7 +24,7 @@ import {
MoneyMultiplierModifier, MoneyMultiplierModifier,
PreserveBerryModifier, PreserveBerryModifier,
} from "#app/modifier/modifier"; } from "#app/modifier/modifier";
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,7 @@ 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 { 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";
@ -45,8 +45,8 @@ 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 { allAbilities } 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";
@ -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,13 @@ 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, getPlayerModifierTypeOptions,
ModifierPoolType,
regenerateModifierPoolThresholds, regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type"; } 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 { 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

@ -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,14 @@ 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, getPlayerModifierTypeOptions,
ModifierPoolType,
regenerateModifierPoolThresholds, regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type"; } 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 { 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

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

@ -16,7 +16,7 @@ import {
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";

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,5 +1,5 @@
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
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";

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

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

@ -6,7 +6,7 @@ import {
setEncounterRewards, setEncounterRewards,
transitionMysteryEncounterIntroVisuals, transitionMysteryEncounterIntroVisuals,
} 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 { 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

@ -7,7 +7,7 @@ import {
setEncounterRewards, setEncounterRewards,
transitionMysteryEncounterIntroVisuals, transitionMysteryEncounterIntroVisuals,
} 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 { 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";
@ -23,11 +23,11 @@ import { PokemonType } from "#enums/pokemon-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability"; import { applyPostBattleInitAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import i18next from "i18next"; import i18next from "i18next";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
@ -220,7 +220,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

@ -7,7 +7,7 @@ import {
setEncounterRewards, setEncounterRewards,
transitionMysteryEncounterIntroVisuals, transitionMysteryEncounterIntroVisuals,
} 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 { 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 +19,7 @@ import { SpeciesId } from "#enums/species-id";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";

View File

@ -25,7 +25,7 @@ import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import { import {
doPokemonTransformationSequence, doPokemonTransformationSequence,
@ -34,7 +34,7 @@ import {
import { getLevelTotalExp } from "#app/data/exp"; import { getLevelTotalExp } from "#app/data/exp";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";

View File

@ -9,7 +9,7 @@ import { StatusEffect } from "#enums/status-effect";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { isNullOrUndefined } from "#app/utils/common"; import { coerceArray, isNullOrUndefined } from "#app/utils/common";
import type { AbilityId } from "#enums/ability-id"; import type { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -272,7 +272,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
constructor(timeOfDay: TimeOfDay | TimeOfDay[]) { constructor(timeOfDay: TimeOfDay | TimeOfDay[]) {
super(); super();
this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [timeOfDay]; this.requiredTimeOfDay = coerceArray(timeOfDay);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -294,7 +294,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
constructor(weather: WeatherType | WeatherType[]) { constructor(weather: WeatherType | WeatherType[]) {
super(); super();
this.requiredWeather = Array.isArray(weather) ? weather : [weather]; this.requiredWeather = coerceArray(weather);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -360,7 +360,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
constructor(heldItem: string | string[], minNumberOfItems = 1) { constructor(heldItem: string | string[], minNumberOfItems = 1) {
super(); super();
this.minNumberOfItems = minNumberOfItems; this.minNumberOfItems = minNumberOfItems;
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem]; this.requiredHeldItemModifiers = coerceArray(heldItem);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -426,7 +426,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredSpecies = Array.isArray(species) ? species : [species]; this.requiredSpecies = coerceArray(species);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -466,7 +466,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredNature = Array.isArray(nature) ? nature : [nature]; this.requiredNature = coerceArray(nature);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -504,7 +504,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
this.excludeFainted = excludeFainted; this.excludeFainted = excludeFainted;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredType = Array.isArray(type) ? type : [type]; this.requiredType = coerceArray(type);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -558,7 +558,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(moves) ? moves : [moves]; this.requiredMoves = coerceArray(moves);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -609,7 +609,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [learnableMove]; this.requiredMoves = coerceArray(learnableMove);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -665,7 +665,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredAbilities = Array.isArray(abilities) ? abilities : [abilities]; this.requiredAbilities = coerceArray(abilities);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -710,7 +710,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [statusEffect]; this.requiredStatusEffect = coerceArray(statusEffect);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -785,7 +785,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [formChangeItem]; this.requiredFormChangeItem = coerceArray(formChangeItem);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -843,7 +843,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [evolutionItems]; this.requiredEvolutionItem = coerceArray(evolutionItems);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -913,7 +913,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredHeldItems = Array.isArray(heldItem) ? heldItem : [heldItem]; this.requiredHeldItems = coerceArray(heldItem);
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";
@ -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

@ -3,10 +3,7 @@ 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 +14,18 @@ import { FieldPosition } from "#enums/field-position";
import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type"; import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type";
import { import {
getPartyLuckValue, getPartyLuckValue,
ModifierPoolType,
ModifierTypeGenerator, ModifierTypeGenerator,
ModifierTypeOption, ModifierTypeOption,
modifierTypes,
regenerateModifierPoolThresholds, regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type"; } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/data/data-lists";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import type PokemonData from "#app/system/pokemon-data"; import type PokemonData from "#app/system/pokemon-data";
import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
import { PartyUiMode } from "#app/ui/party-ui-handler"; import { PartyUiMode } from "#app/ui/party-ui-handler";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import { isNullOrUndefined, randSeedInt, randomString, randSeedItem } from "#app/utils/common"; import { isNullOrUndefined, randSeedInt, randomString, randSeedItem, coerceArray } from "#app/utils/common";
import type { BattlerTagType } from "#enums/battler-tag-type"; import type { BattlerTagType } from "#enums/battler-tag-type";
import { BiomeId } from "#enums/biome-id"; import { BiomeId } from "#enums/biome-id";
import type { TrainerType } from "#enums/trainer-type"; import type { TrainerType } from "#enums/trainer-type";
@ -452,7 +449,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 +792,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));

View File

@ -28,7 +28,7 @@ import {
showEncounterText, showEncounterText,
} 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 { 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";
@ -119,10 +119,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

@ -1,11 +1,18 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "../data-lists";
import { PokemonMove } from "../moves/pokemon-move"; import { PokemonMove } from "../moves/pokemon-move";
import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt, randSeedIntRange } from "#app/utils/common"; import {
toReadableString,
isNullOrUndefined,
randSeedItem,
randSeedInt,
coerceArray,
randSeedIntRange,
} from "#app/utils/common";
import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { tmSpecies } from "#app/data/balance/tms"; import { tmSpecies } from "#app/data/balance/tms";
import { doubleBattleDialogue } from "#app/data/dialogue"; import { doubleBattleDialogue } from "../double-battle-dialogue";
import { TrainerVariant } from "#enums/trainer-variant"; import { TrainerVariant } from "#enums/trainer-variant";
import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import { getIsInitialized, initI18n } from "#app/plugins/i18n";
import i18next from "i18next"; import i18next from "i18next";
@ -37,7 +44,7 @@ import { timedEventManager } from "#app/global-event-manager";
// Type imports // Type imports
import type { PokemonSpeciesFilter } from "#app/data/pokemon-species"; import type { PokemonSpeciesFilter } from "#app/data/pokemon-species";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; import type { ModifierTypeFunc } from "#app/@types/modifier-types";
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import type { EvilTeam } from "./evil-admin-trainer-pools"; import type { EvilTeam } from "./evil-admin-trainer-pools";
import type { import type {
@ -554,10 +561,7 @@ export class TrainerConfig {
this.speciesPools = evilAdminTrainerPools[poolName]; this.speciesPools = evilAdminTrainerPools[poolName];
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) { this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
speciesPool = [speciesPool];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
}); });
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_"); const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
@ -620,10 +624,7 @@ export class TrainerConfig {
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5); this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
} }
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) { this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
speciesPool = [speciesPool];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
}); });
if (!isNullOrUndefined(specialtyType)) { if (!isNullOrUndefined(specialtyType)) {
this.setSpeciesFilter(p => p.isOfType(specialtyType)); this.setSpeciesFilter(p => p.isOfType(specialtyType));
@ -668,12 +669,8 @@ export class TrainerConfig {
// Set up party members with their corresponding species. // Set up party members with their corresponding species.
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
// Ensure speciesPool is an array.
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
}
// Set a function to get a random party member from the species pool. // Set a function to get a random party member from the species pool.
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool)); this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
}); });
// If specialty type is provided, set species filter and specialty type. // If specialty type is provided, set species filter and specialty type.
@ -729,12 +726,8 @@ export class TrainerConfig {
// Set up party members with their corresponding species. // Set up party members with their corresponding species.
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
// Ensure speciesPool is an array.
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
}
// Set a function to get a random party member from the species pool. // Set a function to get a random party member from the species pool.
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool)); this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
}); });
// Set species filter and specialty type if provided, otherwise filter by base total. // Set species filter and specialty type if provided, otherwise filter by base total.

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

@ -0,0 +1,13 @@
// biome-ignore lint/correctness/noUnusedImports: Used in tsdoc
import type ConfirmUiHandler from "#app/ui/confirm-ui-handler";
/**
* Used by {@linkcode ConfirmUiHandler} to determine whether the cursor should start on Yes or No
*/
export const ConfirmUiMode = Object.freeze({
/** Start cursor on Yes */
DEFAULT_YES: 1,
/** Start cursor on No */
DEFAULT_NO: 2
});
export type ConfirmUiMode = typeof ConfirmUiMode[keyof typeof ConfirmUiMode];

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
}

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

@ -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";
@ -375,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;
@ -464,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;

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

@ -41,6 +41,7 @@ import {
type nil, 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";
@ -99,61 +100,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";
@ -180,7 +143,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";
@ -221,6 +184,7 @@ 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 = {
@ -1393,7 +1357,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs("HighCritAttr", source, this, move, critStage); applyMoveAttrs("HighCritAttr", source, this, move, critStage);
applyHeldItems(ITEM_EFFECT.CRIT_BOOST, { pokemon: source, critStage: critStage }); applyHeldItems(ITEM_EFFECT.CRIT_BOOST, { pokemon: source, critStage: critStage });
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage); globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
applyAbAttrs(BonusCritAbAttr, source, null, false, critStage); applyAbAttrs("BonusCritAbAttr", source, null, false, critStage);
const critBoostTag = source.getTag(CritBoostTag); const critBoostTag = source.getTag(CritBoostTag);
if (critBoostTag) { if (critBoostTag) {
if (critBoostTag instanceof DragonCheerTag) { if (critBoostTag instanceof DragonCheerTag) {
@ -1454,19 +1418,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,
@ -1793,9 +1765,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 = [];
@ -2049,15 +2019,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)) {
@ -2142,7 +2108,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;
@ -2153,10 +2119,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)
@ -2197,13 +2163,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)
@ -2219,7 +2189,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);
} }
@ -2290,7 +2260,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;
@ -2312,7 +2282,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
@ -2377,16 +2347,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),
); );
} }
} }
@ -2401,7 +2371,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)) {
@ -2453,8 +2423,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) {
@ -3405,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);
@ -3444,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);
@ -3465,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;
@ -3589,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) {
@ -3732,7 +3719,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}); });
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
applyPreAttackAbAttrs( applyPreAttackAbAttrs(
AddSecondStrikeAbAttr, "AddSecondStrikeAbAttr",
source, source,
this, this,
move, move,
@ -3750,7 +3737,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]
@ -3771,7 +3758,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;
@ -3835,7 +3822,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 */
@ -3848,12 +3835,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);
} }
} }
@ -3861,7 +3848,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)
@ -3903,13 +3890,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isCritical.value = true; isCritical.value = true;
} }
applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical); applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical);
applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move); applyAbAttrs("ConditionalCritAbAttr", source, null, simulated, isCritical, this, move);
if (!isCritical.value) { if (!isCritical.value) {
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))]; const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))];
isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance); isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance);
} }
applyAbAttrs(BlockCritAbAttr, this, null, simulated, isCritical); applyAbAttrs("BlockCritAbAttr", this, null, simulated, isCritical);
return isCritical.value; return isCritical.value;
} }
@ -4016,7 +4003,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;
} }
@ -4064,11 +4051,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;
@ -4084,13 +4071,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;
} }
@ -4108,6 +4095,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/**@overload */ /**@overload */
getTag(tagType: BattlerTagType.GRUDGE): GrudgeTag | nil; getTag(tagType: BattlerTagType.GRUDGE): GrudgeTag | nil;
/** @overload */
getTag(tagType: BattlerTagType.SUBSTITUTE): SubstituteTag | undefined;
/** @overload */ /** @overload */
getTag(tagType: BattlerTagType): BattlerTag | undefined; getTag(tagType: BattlerTagType): BattlerTag | undefined;
@ -4610,7 +4600,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;
} }
@ -4659,14 +4649,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,
@ -4823,7 +4813,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;
} }
@ -4847,7 +4837,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
) { ) {
@ -5372,7 +5362,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);
@ -5430,7 +5420,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.heldItemManager.remove(heldItemId); this.heldItemManager.remove(heldItemId);
if (forBattle) { if (forBattle) {
applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); applyPostItemLostAbAttrs("PostItemLostAbAttr", this, false);
} }
return true; return true;

View File

@ -22,6 +22,8 @@ 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 { initHeldItems } from "./items/all-held-items"; import { initHeldItems } from "./items/all-held-items";
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";
@ -364,6 +366,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,5 +1,6 @@
import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { getLevelTotalExp } from "#app/data/exp"; import { getLevelTotalExp } from "#app/data/exp";
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 { getStatusEffectHealText } from "#app/data/status-effect"; import { getStatusEffectHealText } from "#app/data/status-effect";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
@ -36,11 +37,117 @@ import {
ModifierTypeGenerator, ModifierTypeGenerator,
modifierTypes, modifierTypes,
} from "./modifier-type"; } from "./modifier-type";
import { getModifierType } from "#app/utils/modifier-utils";
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
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;
const iconOverflowIndex = 24;
export const modifierSortFunc = (a: Modifier, b: Modifier): number => {
const itemNameMatch = a.type.name.localeCompare(b.type.name);
const typeNameMatch = a.constructor.name.localeCompare(b.constructor.name);
const aId = a instanceof PokemonHeldItemModifier && a.pokemonId ? a.pokemonId : 4294967295;
const bId = b instanceof PokemonHeldItemModifier && b.pokemonId ? b.pokemonId : 4294967295;
//First sort by pokemonID
if (aId < bId) {
return 1;
}
if (aId > bId) {
return -1;
}
if (aId === bId) {
//Then sort by item type
if (typeNameMatch === 0) {
return itemNameMatch;
//Finally sort by item name
}
return typeNameMatch;
}
return 0;
};
export class ModifierBar extends Phaser.GameObjects.Container {
private player: boolean;
private modifierCache: PersistentModifier[];
constructor(enemy?: boolean) {
super(globalScene, 1 + (enemy ? 302 : 0), 2);
this.player = !enemy;
this.setScale(0.5);
}
/**
* Method to update content displayed in {@linkcode ModifierBar}
* @param {PersistentModifier[]} modifiers - The list of modifiers to be displayed in the {@linkcode ModifierBar}
* @param {boolean} hideHeldItems - If set to "true", only modifiers not assigned to a Pokémon are displayed
*/
updateModifiers(modifiers: PersistentModifier[], hideHeldItems = false) {
this.removeAll(true);
const visibleIconModifiers = modifiers.filter(m => m.isIconVisible());
const nonPokemonSpecificModifiers = visibleIconModifiers
.filter(m => !(m as PokemonHeldItemModifier).pokemonId)
.sort(modifierSortFunc);
const pokemonSpecificModifiers = visibleIconModifiers
.filter(m => (m as PokemonHeldItemModifier).pokemonId)
.sort(modifierSortFunc);
const sortedVisibleIconModifiers = hideHeldItems
? nonPokemonSpecificModifiers
: nonPokemonSpecificModifiers.concat(pokemonSpecificModifiers);
sortedVisibleIconModifiers.forEach((modifier: PersistentModifier, i: number) => {
const icon = modifier.getIcon();
if (i >= iconOverflowIndex) {
icon.setVisible(false);
}
this.add(icon);
this.setModifierIconPosition(icon, sortedVisibleIconModifiers.length);
icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 24), Phaser.Geom.Rectangle.Contains);
icon.on("pointerover", () => {
globalScene.ui.showTooltip(modifier.type.name, modifier.type.getDescription());
if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) {
this.updateModifierOverflowVisibility(true);
}
});
icon.on("pointerout", () => {
globalScene.ui.hideTooltip();
if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) {
this.updateModifierOverflowVisibility(false);
}
});
});
for (const icon of this.getAll()) {
this.sendToBack(icon);
}
this.modifierCache = modifiers;
}
updateModifierOverflowVisibility(ignoreLimit: boolean) {
const modifierIcons = this.getAll().reverse();
for (const modifier of modifierIcons.map(m => m as Phaser.GameObjects.Container).slice(iconOverflowIndex)) {
modifier.setVisible(ignoreLimit);
}
}
setModifierIconPosition(icon: Phaser.GameObjects.Container, modifierCount: number) {
const rowIcons: number = 12 + 6 * Math.max(Math.ceil(Math.min(modifierCount, 24) / 12) - 2, 0);
const x = ((this.getIndex(icon) % rowIcons) * 26) / (rowIcons / 12);
const y = Math.floor(this.getIndex(icon) / rowIcons) * 20;
icon.setPosition(this.player ? x : -x, y);
}
}
export abstract class Modifier { export abstract class Modifier {
public type: ModifierType; public type: ModifierType;
@ -48,6 +155,40 @@ 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;
}
/**
* 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;
} }
@ -72,6 +213,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;
@ -1828,7 +1974,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);
} }
@ -1870,7 +2016,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);
} }
@ -1888,3 +2034,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;

View File

@ -4,7 +4,7 @@ import { Gender } from "#app/data/gender";
import { FormChangeItem } from "#enums/form-change-item"; import { FormChangeItem } from "#enums/form-change-item";
import { type ModifierOverride } from "#app/modifier/modifier-type"; import { type ModifierOverride } from "#app/modifier/modifier-type";
import { Variant } from "#app/sprites/variant"; import { Variant } from "#app/sprites/variant";
import { Unlockables } from "#app/system/unlockables"; import { Unlockables } from "#enums/unlockables";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";

View File

@ -2,6 +2,7 @@ import type { Phase } from "#app/phase";
import type { default as Pokemon } from "#app/field/pokemon"; import type { default as Pokemon } from "#app/field/pokemon";
import type { PhaseMap, PhaseString } from "./@types/phase-types"; import type { PhaseMap, PhaseString } from "./@types/phase-types";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { ActivatePriorityQueuePhase } from "#app/phases/activate-priority-queue-phase";
import { AddEnemyBuffModifierPhase } from "#app/phases/add-enemy-buff-modifier-phase"; import { AddEnemyBuffModifierPhase } from "#app/phases/add-enemy-buff-modifier-phase";
import { AttemptCapturePhase } from "#app/phases/attempt-capture-phase"; import { AttemptCapturePhase } from "#app/phases/attempt-capture-phase";
import { AttemptRunPhase } from "#app/phases/attempt-run-phase"; import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
@ -11,7 +12,9 @@ import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase";
import { CheckSwitchPhase } from "#app/phases/check-switch-phase"; import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
import { CommandPhase } from "#app/phases/command-phase"; import { CommandPhase } from "#app/phases/command-phase";
import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import { coerceArray, type Constructor } from "#app/utils/common";
import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
import type { DynamicPhaseType } from "#enums/dynamic-phase-type";
import { EggHatchPhase } from "#app/phases/egg-hatch-phase"; import { EggHatchPhase } from "#app/phases/egg-hatch-phase";
import { EggLapsePhase } from "#app/phases/egg-lapse-phase"; import { EggLapsePhase } from "#app/phases/egg-lapse-phase";
import { EggSummaryPhase } from "#app/phases/egg-summary-phase"; import { EggSummaryPhase } from "#app/phases/egg-summary-phase";
@ -55,6 +58,7 @@ import { NextEncounterPhase } from "#app/phases/next-encounter-phase";
import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase"; import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase";
import { PartyExpPhase } from "#app/phases/party-exp-phase"; import { PartyExpPhase } from "#app/phases/party-exp-phase";
import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { type PhasePriorityQueue, PostSummonPhasePriorityQueue } from "#app/data/phase-priority-queue";
import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase"; import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase"; import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase";
@ -111,6 +115,7 @@ import { WeatherEffectPhase } from "#app/phases/weather-effect-phase";
* This allows for easy creation of new phases without needing to import each phase individually. * This allows for easy creation of new phases without needing to import each phase individually.
*/ */
const PHASES = Object.freeze({ const PHASES = Object.freeze({
ActivatePriorityQueuePhase,
AddEnemyBuffModifierPhase, AddEnemyBuffModifierPhase,
AttemptCapturePhase, AttemptCapturePhase,
AttemptRunPhase, AttemptRunPhase,
@ -222,9 +227,19 @@ export class PhaseManager {
private phaseQueuePrependSpliceIndex = -1; private phaseQueuePrependSpliceIndex = -1;
private nextCommandPhaseQueue: Phase[] = []; private nextCommandPhaseQueue: Phase[] = [];
/** Storage for {@linkcode PhasePriorityQueue}s which hold phases whose order dynamically changes */
private dynamicPhaseQueues: PhasePriorityQueue[];
/** Parallel array to {@linkcode dynamicPhaseQueues} - matches phase types to their queues */
private dynamicPhaseTypes: Constructor<Phase>[];
private currentPhase: Phase | null = null; private currentPhase: Phase | null = null;
private standbyPhase: Phase | null = null; private standbyPhase: Phase | null = null;
constructor() {
this.dynamicPhaseQueues = [new PostSummonPhasePriorityQueue()];
this.dynamicPhaseTypes = [PostSummonPhase];
}
/* Phase Functions */ /* Phase Functions */
getCurrentPhase(): Phase | null { getCurrentPhase(): Phase | null {
return this.currentPhase; return this.currentPhase;
@ -254,7 +269,11 @@ export class PhaseManager {
* @param defer boolean on which queue to add to, defaults to false, and adds to phaseQueue * @param defer boolean on which queue to add to, defaults to false, and adds to phaseQueue
*/ */
pushPhase(phase: Phase, defer = false): void { pushPhase(phase: Phase, defer = false): void {
(!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase); if (this.getDynamicPhaseType(phase) !== undefined) {
this.pushDynamicPhase(phase);
} else {
(!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase);
}
} }
/** /**
@ -283,6 +302,7 @@ export class PhaseManager {
for (const queue of [this.phaseQueue, this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) { for (const queue of [this.phaseQueue, this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) {
queue.splice(0, queue.length); queue.splice(0, queue.length);
} }
this.dynamicPhaseQueues.forEach(queue => queue.clear());
this.currentPhase = null; this.currentPhase = null;
this.standbyPhase = null; this.standbyPhase = null;
this.clearPhaseQueueSplice(); this.clearPhaseQueueSplice();
@ -333,8 +353,9 @@ export class PhaseManager {
this.currentPhase = this.phaseQueue.shift() ?? null; this.currentPhase = this.phaseQueue.shift() ?? null;
const unactivatedConditionalPhases: [() => boolean, Phase][] = [];
// Check if there are any conditional phases queued // Check if there are any conditional phases queued
if (this.conditionalQueue?.length) { while (this.conditionalQueue?.length) {
// Retrieve the first conditional phase from the queue // Retrieve the first conditional phase from the queue
const conditionalPhase = this.conditionalQueue.shift(); const conditionalPhase = this.conditionalQueue.shift();
// Evaluate the condition associated with the phase // Evaluate the condition associated with the phase
@ -343,11 +364,12 @@ export class PhaseManager {
this.pushPhase(conditionalPhase[1]); this.pushPhase(conditionalPhase[1]);
} else if (conditionalPhase) { } else if (conditionalPhase) {
// If the condition is not met, re-add the phase back to the front of the conditional queue // If the condition is not met, re-add the phase back to the front of the conditional queue
this.conditionalQueue.unshift(conditionalPhase); unactivatedConditionalPhases.push(conditionalPhase);
} else { } else {
console.warn("condition phase is undefined/null!", conditionalPhase); console.warn("condition phase is undefined/null!", conditionalPhase);
} }
} }
this.conditionalQueue.push(...unactivatedConditionalPhases);
if (this.currentPhase) { if (this.currentPhase) {
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;"); console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;");
@ -416,9 +438,7 @@ export class PhaseManager {
* @returns boolean if a targetPhase was found and added * @returns boolean if a targetPhase was found and added
*/ */
prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean { prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean {
if (!Array.isArray(phase)) { phase = coerceArray(phase);
phase = [phase];
}
const target = PHASES[targetPhase]; const target = PHASES[targetPhase];
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target); const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target);
@ -431,17 +451,16 @@ export class PhaseManager {
} }
/** /**
* Attempt to add the input phase(s) to index after target phase in the {@linkcode phaseQueue}, else simply calls {@linkcode unshiftPhase()} * Tries to add the input phase(s) to index after target phase in the {@linkcode phaseQueue}, else simply calls {@linkcode unshiftPhase()}
* @param phase - The phase(s) to be added * @param phase {@linkcode Phase} the phase(s) to be added
* @param targetPhase - The phase to search for in phaseQueue * @param targetPhase {@linkcode Phase} the type of phase to search for in {@linkcode phaseQueue}
* @param condition Condition the target phase must meet to be appended to
* @returns `true` if a `targetPhase` was found to append to * @returns `true` if a `targetPhase` was found to append to
*/ */
appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean { appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString, condition?: (p: Phase) => boolean): boolean {
if (!Array.isArray(phase)) { phase = coerceArray(phase);
phase = [phase];
}
const target = PHASES[targetPhase]; const target = PHASES[targetPhase];
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target); const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target && (!condition || condition(ph)));
if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) { if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) {
this.phaseQueue.splice(targetIndex + 1, 0, ...phase); this.phaseQueue.splice(targetIndex + 1, 0, ...phase);
@ -451,6 +470,68 @@ export class PhaseManager {
return false; return false;
} }
/**
* Checks a phase and returns the matching {@linkcode DynamicPhaseType}, or undefined if it does not match one
* @param phase The phase to check
* @returns The corresponding {@linkcode DynamicPhaseType} or `undefined`
*/
public getDynamicPhaseType(phase: Phase | null): DynamicPhaseType | undefined {
let phaseType: DynamicPhaseType | undefined;
this.dynamicPhaseTypes.forEach((cls, index) => {
if (phase instanceof cls) {
phaseType = index;
}
});
return phaseType;
}
/**
* Pushes a phase onto its corresponding dynamic queue and marks the activation point in {@linkcode phaseQueue}
*
* The {@linkcode ActivatePriorityQueuePhase} will run the top phase in the dynamic queue (not necessarily {@linkcode phase})
* @param phase The phase to push
*/
public pushDynamicPhase(phase: Phase): void {
const type = this.getDynamicPhaseType(phase);
if (type === undefined) {
return;
}
this.pushPhase(new ActivatePriorityQueuePhase(type));
this.dynamicPhaseQueues[type].push(phase);
}
/**
* Unshifts the top phase from the corresponding dynamic queue onto {@linkcode phaseQueue}
* @param type {@linkcode DynamicPhaseType} The type of dynamic phase to start
*/
public startDynamicPhaseType(type: DynamicPhaseType): void {
const phase = this.dynamicPhaseQueues[type].pop();
if (phase) {
this.unshiftPhase(phase);
}
}
/**
* Unshifts an {@linkcode ActivatePriorityQueuePhase} for {@linkcode phase}, then pushes {@linkcode phase} to its dynamic queue
*
* This is the same as {@linkcode pushDynamicPhase}, except the activation phase is unshifted
*
* {@linkcode phase} is not guaranteed to be the next phase from the queue to run (if the queue is not empty)
* @param phase The phase to add
* @returns
*/
public startDynamicPhase(phase: Phase): void {
const type = this.getDynamicPhaseType(phase);
if (type === undefined) {
return;
}
this.unshiftPhase(new ActivatePriorityQueuePhase(type));
this.dynamicPhaseQueues[type].push(phase);
}
/** /**
* Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue * Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue
* @param message - string for MessagePhase * @param message - string for MessagePhase
@ -578,4 +659,11 @@ export class PhaseManager {
): boolean { ): boolean {
return this.appendToPhase(this.create(phase, ...args), targetPhase); return this.appendToPhase(this.create(phase, ...args), targetPhase);
} }
public startNewDynamicPhase<T extends PhaseString>(
phase: T,
...args: ConstructorParameters<PhaseConstructorMap[T]>
): void {
this.startDynamicPhase(this.create(phase, ...args));
}
} }

View File

@ -0,0 +1,23 @@
import type { DynamicPhaseType } from "#enums/dynamic-phase-type";
import { globalScene } from "#app/global-scene";
import { Phase } from "#app/phase";
export class ActivatePriorityQueuePhase extends Phase {
public readonly phaseName = "ActivatePriorityQueuePhase";
private type: DynamicPhaseType;
constructor(type: DynamicPhaseType) {
super();
this.type = type;
}
override start() {
super.start();
globalScene.phaseManager.startDynamicPhaseType(this.type);
this.end();
}
public getType(): DynamicPhaseType {
return this.type;
}
}

View File

@ -1,9 +1,9 @@
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { import {
regenerateModifierPoolThresholds, regenerateModifierPoolThresholds,
ModifierPoolType,
getEnemyBuffModifierForWave, getEnemyBuffModifierForWave,
} from "#app/modifier/modifier-type"; } from "#app/modifier/modifier-type";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import { EnemyPersistentModifier } from "#app/modifier/modifier"; import { EnemyPersistentModifier } from "#app/modifier/modifier";
import { Phase } from "#app/phase"; import { Phase } from "#app/phase";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";

View File

@ -1,9 +1,4 @@
import { import { applyAbAttrs, applyPreLeaveFieldAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
applyPreLeaveFieldAbAttrs,
PreLeaveFieldAbAttr,
RunSuccessAbAttr,
} from "#app/data/abilities/ability";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon"; import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
@ -30,10 +25,10 @@ export class AttemptRunPhase extends PokemonPhase {
this.attemptRunAway(playerField, enemyField, escapeChance); this.attemptRunAway(playerField, enemyField, escapeChance);
applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance); applyAbAttrs("RunSuccessAbAttr", playerPokemon, null, false, escapeChance);
if (playerPokemon.randBattleSeedInt(100) < escapeChance.value && !this.forceFailEscape) { if (playerPokemon.randBattleSeedInt(100) < escapeChance.value && !this.forceFailEscape) {
enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, enemyPokemon)); enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", enemyPokemon));
globalScene.playSound("se/flee"); globalScene.playSound("se/flee");
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500); globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { applyPostBattleAbAttrs, PostBattleAbAttr } from "#app/data/abilities/ability"; import { applyPostBattleAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier"; import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
@ -65,7 +65,7 @@ export class BattleEndPhase extends BattlePhase {
} }
for (const pokemon of globalScene.getPokemonAllowedInBattle()) { for (const pokemon of globalScene.getPokemonAllowedInBattle()) {
applyPostBattleAbAttrs(PostBattleAbAttr, pokemon, false, this.isVictory); applyPostBattleAbAttrs("PostBattleAbAttr", pokemon, false, this.isVictory);
} }
if (globalScene.currentBattle.moneyScattered) { if (globalScene.currentBattle.moneyScattered) {

View File

@ -1,9 +1,4 @@
import { import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
PreventBerryUseAbAttr,
HealFromBerryUseAbAttr,
RepeatBerryNextTurnAbAttr,
} from "#app/data/abilities/ability";
import { CommonAnim } from "#enums/move-anims-common"; import { CommonAnim } from "#enums/move-anims-common";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import i18next from "i18next"; import i18next from "i18next";
@ -26,7 +21,7 @@ export class BerryPhase extends FieldPhase {
this.executeForAll(pokemon => { this.executeForAll(pokemon => {
this.eatBerries(pokemon); this.eatBerries(pokemon);
applyAbAttrs(RepeatBerryNextTurnAbAttr, pokemon, null); applyAbAttrs("RepeatBerryNextTurnAbAttr", pokemon, null);
}); });
this.end(); this.end();
@ -48,7 +43,7 @@ export class BerryPhase extends FieldPhase {
// TODO: If both opponents on field have unnerve, which one displays its message? // TODO: If both opponents on field have unnerve, which one displays its message?
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
pokemon.getOpponents().forEach(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled)); pokemon.getOpponents().forEach(opp => applyAbAttrs("PreventBerryUseAbAttr", opp, cancelled));
if (cancelled.value) { if (cancelled.value) {
globalScene.phaseManager.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("abilityTriggers:preventBerryUse", { i18next.t("abilityTriggers:preventBerryUse", {
@ -69,6 +64,6 @@ export class BerryPhase extends FieldPhase {
globalScene.updateModifiers(pokemon.isPlayer()); globalScene.updateModifiers(pokemon.isPlayer());
// AbilityId.CHEEK_POUCH only works once per round of nom noms // AbilityId.CHEEK_POUCH only works once per round of nom noms
applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new BooleanHolder(false)); applyAbAttrs("HealFromBerryUseAbAttr", pokemon, new BooleanHolder(false));
} }
} }

View File

@ -2,12 +2,7 @@ import { BattlerIndex } from "#enums/battler-index";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
import { import { applyAbAttrs, applyPreSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
SyncEncounterNatureAbAttr,
applyPreSummonAbAttrs,
PreSummonAbAttr,
} from "#app/data/abilities/ability";
import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims"; import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims";
import { getCharVariantFromDialogue } from "#app/data/dialogue"; import { getCharVariantFromDialogue } from "#app/data/dialogue";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -20,7 +15,8 @@ import type Pokemon from "#app/field/pokemon";
import { FieldPosition } from "#enums/field-position"; import { FieldPosition } from "#enums/field-position";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { BoostBugSpawnModifier, IvScannerModifier } from "#app/modifier/modifier"; import { BoostBugSpawnModifier, IvScannerModifier } from "#app/modifier/modifier";
import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { BattlePhase } from "#app/phases/battle-phase"; import { BattlePhase } from "#app/phases/battle-phase";
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
@ -34,7 +30,7 @@ import { PlayerGender } from "#enums/player-gender";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier"; import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier";
import i18next from "i18next"; import i18next from "i18next";
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/constants";
import { getNatureName } from "#app/data/nature"; import { getNatureName } from "#app/data/nature";
export class EncounterPhase extends BattlePhase { export class EncounterPhase extends BattlePhase {
@ -132,7 +128,7 @@ export class EncounterPhase extends BattlePhase {
.slice(0, !battle.double ? 1 : 2) .slice(0, !battle.double ? 1 : 2)
.reverse() .reverse()
.forEach(playerPokemon => { .forEach(playerPokemon => {
applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, false, battle.enemyParty[e]); applyAbAttrs("SyncEncounterNatureAbAttr", playerPokemon, null, false, battle.enemyParty[e]);
}); });
} }
} }
@ -253,7 +249,7 @@ export class EncounterPhase extends BattlePhase {
if (e < (battle.double ? 2 : 1)) { if (e < (battle.double ? 2 : 1)) {
if (battle.battleType === BattleType.WILD) { if (battle.battleType === BattleType.WILD) {
for (const pokemon of globalScene.getField()) { for (const pokemon of globalScene.getField()) {
applyPreSummonAbAttrs(PreSummonAbAttr, pokemon, []); applyPreSummonAbAttrs("PreSummonAbAttr", pokemon, []);
} }
globalScene.field.add(enemyPokemon); globalScene.field.add(enemyPokemon);
battle.seenEnemyPartyMemberIds.add(enemyPokemon.id); battle.seenEnemyPartyMemberIds.add(enemyPokemon.id);

View File

@ -5,10 +5,7 @@ import {
applyPostFaintAbAttrs, applyPostFaintAbAttrs,
applyPostKnockOutAbAttrs, applyPostKnockOutAbAttrs,
applyPostVictoryAbAttrs, applyPostVictoryAbAttrs,
PostFaintAbAttr, } from "#app/data/abilities/apply-ab-attrs";
PostKnockOutAbAttr,
PostVictoryAbAttr,
} from "#app/data/abilities/ability";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { battleSpecDialogue } from "#app/data/dialogue"; import { battleSpecDialogue } from "#app/data/dialogue";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
@ -114,7 +111,7 @@ export class FaintPhase extends PokemonPhase {
if (pokemon.turnData.attacksReceived?.length) { if (pokemon.turnData.attacksReceived?.length) {
const lastAttack = pokemon.turnData.attacksReceived[0]; const lastAttack = pokemon.turnData.attacksReceived[0];
applyPostFaintAbAttrs( applyPostFaintAbAttrs(
PostFaintAbAttr, "PostFaintAbAttr",
pokemon, pokemon,
globalScene.getPokemonById(lastAttack.sourceId)!, globalScene.getPokemonById(lastAttack.sourceId)!,
new PokemonMove(lastAttack.move).getMove(), new PokemonMove(lastAttack.move).getMove(),
@ -122,18 +119,18 @@ export class FaintPhase extends PokemonPhase {
); // TODO: is this bang correct? ); // TODO: is this bang correct?
} else { } else {
//If killed by indirect damage, apply post-faint abilities without providing a last move //If killed by indirect damage, apply post-faint abilities without providing a last move
applyPostFaintAbAttrs(PostFaintAbAttr, pokemon); applyPostFaintAbAttrs("PostFaintAbAttr", pokemon);
} }
const alivePlayField = globalScene.getField(true); const alivePlayField = globalScene.getField(true);
for (const p of alivePlayField) { for (const p of alivePlayField) {
applyPostKnockOutAbAttrs(PostKnockOutAbAttr, p, pokemon); applyPostKnockOutAbAttrs("PostKnockOutAbAttr", p, pokemon);
} }
if (pokemon.turnData.attacksReceived?.length) { if (pokemon.turnData.attacksReceived?.length) {
const defeatSource = this.source; const defeatSource = this.source;
if (defeatSource?.isOnField()) { if (defeatSource?.isOnField()) {
applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); applyPostVictoryAbAttrs("PostVictoryAbAttr", defeatSource);
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr"); const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr");
if (pvattrs.length) { if (pvattrs.length) {

View File

@ -7,11 +7,11 @@ import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
import { BattlePhase } from "#app/phases/battle-phase"; import { BattlePhase } from "#app/phases/battle-phase";
import type { EndCardPhase } from "#app/phases/end-card-phase"; import type { EndCardPhase } from "#app/phases/end-card-phase";
import { achvs, ChallengeAchv } from "#app/system/achv"; import { achvs, ChallengeAchv } from "#app/system/achv";
import { Unlockables } from "#app/system/unlockables"; import { Unlockables } from "#enums/unlockables";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import { isLocal, isLocalServerConnected } from "#app/utils/common"; import { isLocal, isLocalServerConnected } from "#app/utils/common";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";

View File

@ -12,6 +12,7 @@ import { UiMode } from "#enums/ui-mode";
import i18next from "i18next"; import i18next from "i18next";
import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { ConfirmUiMode } from "#enums/confirm-ui-mode";
import { LearnMoveType } from "#enums/learn-move-type"; import { LearnMoveType } from "#enums/learn-move-type";
export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
@ -163,6 +164,10 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
globalScene.ui.setMode(this.messageMode); globalScene.ui.setMode(this.messageMode);
this.replaceMoveCheck(move, pokemon); this.replaceMoveCheck(move, pokemon);
}, },
false,
0,
0,
ConfirmUiMode.DEFAULT_NO,
); );
} }

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { ModifierType, ModifierTypeFunc } from "#app/modifier/modifier-type"; import type { ModifierType } from "#app/modifier/modifier-type";
import { getModifierType } from "#app/modifier/modifier-type"; import type { ModifierTypeFunc } from "#app/@types/modifier-types";
import { getModifierType } from "#app/utils/modifier-utils";
import i18next from "i18next"; import i18next from "i18next";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";

View File

@ -1,21 +1,12 @@
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { import {
AddSecondStrikeAbAttr,
AlwaysHitAbAttr,
applyExecutedMoveAbAttrs, applyExecutedMoveAbAttrs,
applyPostAttackAbAttrs, applyPostAttackAbAttrs,
applyPostDamageAbAttrs, applyPostDamageAbAttrs,
applyPostDefendAbAttrs, applyPostDefendAbAttrs,
applyPreAttackAbAttrs, applyPreAttackAbAttrs,
ExecutedMoveAbAttr, } from "#app/data/abilities/apply-ab-attrs";
IgnoreMoveEffectsAbAttr,
MaxMultiHitAbAttr,
PostAttackAbAttr,
PostDamageAbAttr,
PostDefendAbAttr,
ReflectStatusMoveAbAttr,
} from "#app/data/abilities/ability";
import { ConditionalProtectTag } from "#app/data/arena-tag"; import { ConditionalProtectTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagSide } from "#enums/arena-tag-side";
import { MoveAnim } from "#app/data/battle-anims"; import { MoveAnim } from "#app/data/battle-anims";
@ -173,7 +164,7 @@ export class MoveEffectPhase extends PokemonPhase {
globalScene.phaseManager.create( globalScene.phaseManager.create(
"ShowAbilityPhase", "ShowAbilityPhase",
target.getBattlerIndex(), target.getBattlerIndex(),
target.getPassiveAbility().hasAttr(ReflectStatusMoveAbAttr), target.getPassiveAbility().hasAttr("ReflectStatusMoveAbAttr"),
), ),
); );
this.queuedPhases.push(globalScene.phaseManager.create("HideAbilityPhase")); this.queuedPhases.push(globalScene.phaseManager.create("HideAbilityPhase"));
@ -311,7 +302,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Assume single target for multi hit // Assume single target for multi hit
applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount); applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount);
// If Parental Bond is applicable, add another hit // If Parental Bond is applicable, add another hit
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null); applyPreAttackAbAttrs("AddSecondStrikeAbAttr", user, null, move, false, hitCount, null);
// If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses
applyHeldItems(ITEM_EFFECT.MULTI_HIT, { pokemon: user, moveId: move.id, count: hitCount }); applyHeldItems(ITEM_EFFECT.MULTI_HIT, { pokemon: user, moveId: move.id, count: hitCount });
// Set the user's relevant turnData fields to reflect the final hit count // Set the user's relevant turnData fields to reflect the final hit count
@ -364,7 +355,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Add to the move history entry // Add to the move history entry
if (this.firstHit) { if (this.firstHit) {
user.pushMoveHistory(this.moveHistoryEntry); user.pushMoveHistory(this.moveHistoryEntry);
applyExecutedMoveAbAttrs(ExecutedMoveAbAttr, user); applyExecutedMoveAbAttrs("ExecutedMoveAbAttr", user);
} }
try { try {
@ -428,7 +419,7 @@ export class MoveEffectPhase extends PokemonPhase {
* @returns a `Promise` intended to be passed into a `then()` call. * @returns a `Promise` intended to be passed into a `then()` call.
*/ */
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void { protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void {
applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult); applyPostDefendAbAttrs("PostDefendAbAttr", target, user, this.move, hitResult);
target.lapseTags(BattlerTagLapseType.AFTER_HIT); target.lapseTags(BattlerTagLapseType.AFTER_HIT);
} }
@ -444,7 +435,11 @@ export class MoveEffectPhase extends PokemonPhase {
return; return;
} }
if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !this.move.hitsSubstitute(user, target)) { if (
dealsDamage &&
!target.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") &&
!this.move.hitsSubstitute(user, target)
) {
const flinched = new BooleanHolder(false); const flinched = new BooleanHolder(false);
applyHeldItems(ITEM_EFFECT.FLINCH_CHANCE, { pokemon: user, flinched: flinched }); applyHeldItems(ITEM_EFFECT.FLINCH_CHANCE, { pokemon: user, flinched: flinched });
if (flinched.value) { if (flinched.value) {
@ -574,7 +569,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Strikes after the first in a multi-strike move are guaranteed to hit, // Strikes after the first in a multi-strike move are guaranteed to hit,
// unless the move is flagged to check all hits and the user does not have Skill Link. // unless the move is flagged to check all hits and the user does not have Skill Link.
if (user.turnData.hitsLeft < user.turnData.hitCount) { if (user.turnData.hitsLeft < user.turnData.hitCount) {
if (!move.hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr(MaxMultiHitAbAttr)) { if (!move.hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr("MaxMultiHitAbAttr")) {
return [HitCheckResult.HIT, effectiveness]; return [HitCheckResult.HIT, effectiveness];
} }
} }
@ -620,7 +615,7 @@ export class MoveEffectPhase extends PokemonPhase {
if (!user) { if (!user) {
return false; return false;
} }
if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) { if (user.hasAbilityWithAttr("AlwaysHitAbAttr") || target.hasAbilityWithAttr("AlwaysHitAbAttr")) {
return true; return true;
} }
if (this.move.hasAttr("ToxicAccuracyAttr") && user.isOfType(PokemonType.POISON)) { if (this.move.hasAttr("ToxicAccuracyAttr") && user.isOfType(PokemonType.POISON)) {
@ -783,7 +778,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Multi-hit check for Wimp Out/Emergency Exit // Multi-hit check for Wimp Out/Emergency Exit
if (user.turnData.hitCount > 1) { if (user.turnData.hitCount > 1) {
applyPostDamageAbAttrs(PostDamageAbAttr, target, 0, target.hasPassive(), false, [], user); applyPostDamageAbAttrs("PostDamageAbAttr", target, 0, target.hasPassive(), false, [], user);
} }
} }
} }
@ -977,7 +972,7 @@ export class MoveEffectPhase extends PokemonPhase {
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false); this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
this.applyHeldItemFlinchCheck(user, target, dealsDamage); this.applyHeldItemFlinchCheck(user, target, dealsDamage);
this.applyOnGetHitAbEffects(user, target, hitResult); this.applyOnGetHitAbEffects(user, target, hitResult);
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult); applyPostAttackAbAttrs("PostAttackAbAttr", user, target, this.move, hitResult);
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens // We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
if (!user.isPlayer() && this.move.is("AttackMove")) { if (!user.isPlayer() && this.move.is("AttackMove")) {

View File

@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
import type { BattlerIndex } from "#enums/battler-index"; import type { BattlerIndex } from "#enums/battler-index";
import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/abilities/ability"; import { applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
export class MoveEndPhase extends PokemonPhase { export class MoveEndPhase extends PokemonPhase {
@ -30,7 +30,7 @@ export class MoveEndPhase extends PokemonPhase {
// Remove effects which were set on a Pokemon which removes them on summon (i.e. via Mold Breaker) // Remove effects which were set on a Pokemon which removes them on summon (i.e. via Mold Breaker)
for (const target of this.targets) { for (const target of this.targets) {
if (target) { if (target) {
applyPostSummonAbAttrs(PostSummonRemoveEffectAbAttr, target); applyPostSummonAbAttrs("PostSummonRemoveEffectAbAttr", target);
} }
} }

View File

@ -1,16 +1,6 @@
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
applyPostMoveUsedAbAttrs,
applyPreAttackAbAttrs,
BlockRedirectAbAttr,
IncreasePpAbAttr,
PokemonTypeChangeAbAttr,
PostMoveUsedAbAttr,
RedirectMoveAbAttr,
ReduceStatusEffectDurationAbAttr,
} from "#app/data/abilities/ability";
import type { DelayedAttackTag } from "#app/data/arena-tag"; import type { DelayedAttackTag } from "#app/data/arena-tag";
import { CommonAnim } from "#enums/move-anims-common"; import { CommonAnim } from "#enums/move-anims-common";
import { CenterOfAttentionTag } from "#app/data/battler-tags"; import { CenterOfAttentionTag } from "#app/data/battler-tags";
@ -228,7 +218,7 @@ export class MovePhase extends BattlePhase {
applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove()); applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove());
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0); const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
applyAbAttrs( applyAbAttrs(
ReduceStatusEffectDurationAbAttr, "ReduceStatusEffectDurationAbAttr",
this.pokemon, this.pokemon,
null, null,
false, false,
@ -396,7 +386,7 @@ export class MovePhase extends BattlePhase {
*/ */
if (success) { if (success) {
const move = this.move.getMove(); const move = this.move.getMove();
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, move); applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, move);
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"MoveEffectPhase", "MoveEffectPhase",
this.pokemon.getBattlerIndex(), this.pokemon.getBattlerIndex(),
@ -407,7 +397,7 @@ export class MovePhase extends BattlePhase {
); );
} else { } else {
if ([MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)) { if ([MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)) {
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove());
} }
this.pokemon.pushMoveHistory({ this.pokemon.pushMoveHistory({
@ -437,7 +427,7 @@ export class MovePhase extends BattlePhase {
// Note that the `!this.followUp` check here prevents an infinite Dancer loop. // Note that the `!this.followUp` check here prevents an infinite Dancer loop.
if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) { if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) {
globalScene.getField(true).forEach(pokemon => { globalScene.getField(true).forEach(pokemon => {
applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); applyPostMoveUsedAbAttrs("PostMoveUsedAbAttr", pokemon, this.move, this.pokemon, this.targets);
}); });
} }
} }
@ -449,7 +439,7 @@ export class MovePhase extends BattlePhase {
if (move.applyConditions(this.pokemon, targets[0], move)) { if (move.applyConditions(this.pokemon, targets[0], move)) {
// Protean and Libero apply on the charging turn of charge moves // Protean and Libero apply on the charging turn of charge moves
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove());
this.showMoveText(); this.showMoveText();
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
@ -498,7 +488,7 @@ export class MovePhase extends BattlePhase {
public getPpIncreaseFromPressure(targets: Pokemon[]): number { public getPpIncreaseFromPressure(targets: Pokemon[]): number {
const foesWithPressure = this.pokemon const foesWithPressure = this.pokemon
.getOpponents() .getOpponents()
.filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr(IncreasePpAbAttr)); .filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr("IncreasePpAbAttr"));
return foesWithPressure.length; return foesWithPressure.length;
} }
@ -516,7 +506,9 @@ export class MovePhase extends BattlePhase {
globalScene globalScene
.getField(true) .getField(true)
.filter(p => p !== this.pokemon) .filter(p => p !== this.pokemon)
.forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget, this.pokemon)); .forEach(p =>
applyAbAttrs("RedirectMoveAbAttr", p, null, false, this.move.moveId, redirectTarget, this.pokemon),
);
/** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */ /** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */
let redirectedByAbility = currentTarget !== redirectTarget.value; let redirectedByAbility = currentTarget !== redirectTarget.value;
@ -545,17 +537,17 @@ export class MovePhase extends BattlePhase {
} }
}); });
if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) { if (this.pokemon.hasAbilityWithAttr("BlockRedirectAbAttr")) {
redirectTarget.value = currentTarget; redirectTarget.value = currentTarget;
// TODO: Ability displays should be handled by the ability // TODO: Ability displays should be handled by the ability
globalScene.phaseManager.queueAbilityDisplay( globalScene.phaseManager.queueAbilityDisplay(
this.pokemon, this.pokemon,
this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr), this.pokemon.getPassiveAbility().hasAttr("BlockRedirectAbAttr"),
true, true,
); );
globalScene.phaseManager.queueAbilityDisplay( globalScene.phaseManager.queueAbilityDisplay(
this.pokemon, this.pokemon,
this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr), this.pokemon.getPassiveAbility().hasAttr("BlockRedirectAbAttr"),
false, false,
); );
} }

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { applyAbAttrs, PostBiomeChangeAbAttr } from "#app/data/abilities/ability"; import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { getRandomWeatherType } from "#app/data/weather"; import { getRandomWeatherType } from "#app/data/weather";
import { NextEncounterPhase } from "./next-encounter-phase"; import { NextEncounterPhase } from "./next-encounter-phase";
@ -14,7 +14,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
if (pokemon) { if (pokemon) {
pokemon.resetBattleAndWaveData(); pokemon.resetBattleAndWaveData();
if (pokemon.isOnField()) { if (pokemon.isOnField()) {
applyAbAttrs(PostBiomeChangeAbAttr, pokemon, null); applyAbAttrs("PostBiomeChangeAbAttr", pokemon, null);
} }
} }
} }

View File

@ -8,7 +8,7 @@ import type Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/abilities/ability"; import { applyPostSetStatusAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
export class ObtainStatusEffectPhase extends PokemonPhase { export class ObtainStatusEffectPhase extends PokemonPhase {
@ -53,7 +53,7 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true);
// If mold breaker etc was used to set this status, it shouldn't apply to abilities activated afterwards // If mold breaker etc was used to set this status, it shouldn't apply to abilities activated afterwards
globalScene.arena.setIgnoreAbilities(false); globalScene.arena.setIgnoreAbilities(false);
applyPostSetStatusAbAttrs(PostSetStatusAbAttr, pokemon, this.statusEffect, this.sourcePokemon); applyPostSetStatusAbAttrs("PostSetStatusAbAttr", pokemon, this.statusEffect, this.sourcePokemon);
} }
this.end(); this.end();
}); });

View File

@ -0,0 +1,27 @@
import { applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { PostSummonPhase } from "#app/phases/post-summon-phase";
import type { BattlerIndex } from "#enums/battler-index";
/**
* Helper to {@linkcode PostSummonPhase} which applies abilities
*/
export class PostSummonActivateAbilityPhase extends PostSummonPhase {
private priority: number;
private passive: boolean;
constructor(battlerIndex: BattlerIndex, priority: number, passive: boolean) {
super(battlerIndex);
this.priority = priority;
this.passive = passive;
}
start() {
applyPostSummonAbAttrs("PostSummonAbAttr", this.getPokemon(), this.passive, false);
this.end();
}
public override getPriority() {
return this.priority;
}
}

View File

@ -1,10 +1,10 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { applyAbAttrs, applyPostSummonAbAttrs, CommanderAbAttr, PostSummonAbAttr } from "#app/data/abilities/ability";
import { ArenaTrapTag } from "#app/data/arena-tag"; import { ArenaTrapTag } from "#app/data/arena-tag";
import { StatusEffect } from "#app/enums/status-effect"; import { StatusEffect } from "#app/enums/status-effect";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
import { MysteryEncounterPostSummonTag } from "#app/data/battler-tags"; import { MysteryEncounterPostSummonTag } from "#app/data/battler-tags";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
export class PostSummonPhase extends PokemonPhase { export class PostSummonPhase extends PokemonPhase {
public readonly phaseName = "PostSummonPhase"; public readonly phaseName = "PostSummonPhase";
@ -26,12 +26,15 @@ export class PostSummonPhase extends PokemonPhase {
pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON); pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON);
} }
applyPostSummonAbAttrs(PostSummonAbAttr, pokemon);
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);
} }
this.end(); this.end();
} }
public getPriority() {
return 0;
}
} }

View File

@ -1,13 +1,6 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#enums/battler-index"; import type { BattlerIndex } from "#enums/battler-index";
import { import { applyAbAttrs, applyPostDamageAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
applyPostDamageAbAttrs,
BlockNonDirectDamageAbAttr,
BlockStatusDamageAbAttr,
PostDamageAbAttr,
ReduceBurnDamageAbAttr,
} from "#app/data/abilities/ability";
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";
import { getStatusEffectActivationText } from "#app/data/status-effect"; import { getStatusEffectActivationText } from "#app/data/status-effect";
@ -29,8 +22,8 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) { if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) {
pokemon.status.incrementTurn(); pokemon.status.incrementTurn();
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs(BlockStatusDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockStatusDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
globalScene.phaseManager.queueMessage( globalScene.phaseManager.queueMessage(
@ -46,14 +39,14 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
break; break;
case StatusEffect.BURN: case StatusEffect.BURN:
damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); damage.value = Math.max(pokemon.getMaxHp() >> 4, 1);
applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage); applyAbAttrs("ReduceBurnDamageAbAttr", pokemon, null, false, damage);
break; break;
} }
if (damage.value) { if (damage.value) {
// Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ...
globalScene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true)); globalScene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true));
pokemon.updateInfo(); pokemon.updateInfo();
applyPostDamageAbAttrs(PostDamageAbAttr, pokemon, damage.value, pokemon.hasPassive(), false, []); applyPostDamageAbAttrs("PostDamageAbAttr", pokemon, damage.value, pokemon.hasPassive(), false, []);
} }
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(false, () => this.end()); new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(false, () => this.end());
} else { } else {

View File

@ -12,12 +12,7 @@ import type Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
import type { MovePhase } from "./move-phase"; import type { MovePhase } from "./move-phase";
import { import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
ClearTerrainAbAttr,
ClearWeatherAbAttr,
PostTeraFormChangeStatChangeAbAttr,
} from "#app/data/abilities/ability";
export class QuietFormChangePhase extends BattlePhase { export class QuietFormChangePhase extends BattlePhase {
public readonly phaseName = "QuietFormChangePhase"; public readonly phaseName = "QuietFormChangePhase";
@ -185,9 +180,9 @@ export class QuietFormChangePhase extends BattlePhase {
} }
} }
if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) { if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) {
applyAbAttrs(PostTeraFormChangeStatChangeAbAttr, this.pokemon, null); applyAbAttrs("PostTeraFormChangeStatChangeAbAttr", this.pokemon, null);
applyAbAttrs(ClearWeatherAbAttr, this.pokemon, null); applyAbAttrs("ClearWeatherAbAttr", this.pokemon, null);
applyAbAttrs(ClearTerrainAbAttr, this.pokemon, null); applyAbAttrs("ClearTerrainAbAttr", this.pokemon, null);
} }
super.end(); super.end();

View File

@ -1,6 +1,6 @@
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 type { ModifierTypeFunc } from "#app/modifier/modifier-type"; import type { ModifierTypeFunc } from "#app/@types/modifier-types";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import i18next from "i18next"; import i18next from "i18next";
import { ModifierRewardPhase } from "./modifier-reward-phase"; import { ModifierRewardPhase } from "./modifier-reward-phase";

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