mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-20 16:42:45 +02:00
9032 lines
291 KiB
TypeScript
9032 lines
291 KiB
TypeScript
import { MoveResult } from "#enums/move-result";
|
|
import { HitResult } from "#enums/hit-result";
|
|
import {
|
|
BooleanHolder,
|
|
NumberHolder,
|
|
toDmgValue,
|
|
isNullOrUndefined,
|
|
randSeedItem,
|
|
randSeedInt,
|
|
randSeedFloat,
|
|
coerceArray,
|
|
} from "#app/utils/common";
|
|
import { getPokemonNameWithAffix } from "#app/messages";
|
|
import { GroundedTag } from "#app/data/battler-tags";
|
|
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
|
import {
|
|
getNonVolatileStatusEffects,
|
|
getStatusEffectDescriptor,
|
|
getStatusEffectHealText,
|
|
} from "#app/data/status-effect";
|
|
import { Gender } from "#app/data/gender";
|
|
import { applyMoveAttrs } from "../moves/apply-attrs";
|
|
import { allMoves } from "../data-lists";
|
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
|
import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
|
|
import { TerrainType } from "#app/data/terrain";
|
|
import { pokemonFormChanges } from "../pokemon-forms";
|
|
import {
|
|
SpeciesFormChangeWeatherTrigger,
|
|
SpeciesFormChangeAbilityTrigger,
|
|
} from "../pokemon-forms/form-change-triggers";
|
|
import i18next from "i18next";
|
|
import { Command } from "#enums/command";
|
|
import { BerryModifierType } from "#app/modifier/modifier-type";
|
|
import { getPokeballName } from "#app/data/pokeball";
|
|
import { BattleType } from "#enums/battle-type";
|
|
import { globalScene } from "#app/global-scene";
|
|
import { allAbilities } from "#app/data/data-lists";
|
|
|
|
// Enum imports
|
|
import { Stat, BATTLE_STATS, EFFECTIVE_STATS, getStatKey } from "#enums/stat";
|
|
import { PokemonType } from "#enums/pokemon-type";
|
|
import { PokemonAnimType } from "#enums/pokemon-anim-type";
|
|
import { StatusEffect } from "#enums/status-effect";
|
|
import { WeatherType } from "#enums/weather-type";
|
|
import { AbilityId } from "#enums/ability-id";
|
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
|
import { MoveId } from "#enums/move-id";
|
|
import { SpeciesId } from "#enums/species-id";
|
|
import { SwitchType } from "#enums/switch-type";
|
|
import { MoveFlags } from "#enums/MoveFlags";
|
|
import { MoveTarget } from "#enums/MoveTarget";
|
|
import { MoveCategory } from "#enums/MoveCategory";
|
|
import { CommonAnim } from "#enums/move-anims-common";
|
|
import { getBerryEffectFunc } from "#app/data/berry";
|
|
import { BerryUsedEvent } from "#app/events/battle-scene";
|
|
import { noAbilityTypeOverrideMoves } from "#app/data/moves/invalid-moves";
|
|
import { MoveUseMode } from "#enums/move-use-mode";
|
|
|
|
// Type imports
|
|
import type { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
|
import type { BattleStat, EffectiveStat } from "#enums/stat";
|
|
import type { BerryType } from "#enums/berry-type";
|
|
import type { EnemyPokemon } from "#app/field/pokemon";
|
|
import type { PokemonMove } from "../moves/pokemon-move";
|
|
import type Pokemon from "#app/field/pokemon";
|
|
import type { Weather } from "#app/data/weather";
|
|
import type { BattlerTag } from "#app/data/battler-tags";
|
|
import type {
|
|
AbAttrCondition,
|
|
PokemonDefendCondition,
|
|
PokemonStatStageChangeCondition,
|
|
PokemonAttackCondition,
|
|
AbAttrString,
|
|
AbAttrMap,
|
|
} from "#app/@types/ability-types";
|
|
import type { BattlerIndex } from "#enums/battler-index";
|
|
import type Move from "#app/data/moves/move";
|
|
import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag";
|
|
import type { Constructor } from "#app/utils/common";
|
|
import type { Localizable } from "#app/@types/locales";
|
|
import { applyAbAttrs } from "./apply-ab-attrs";
|
|
|
|
export class Ability implements Localizable {
|
|
public id: AbilityId;
|
|
|
|
private nameAppend: string;
|
|
public name: string;
|
|
public description: string;
|
|
public generation: number;
|
|
public readonly postSummonPriority: 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, postSummonPriority = 0) {
|
|
this.id = id;
|
|
|
|
this.nameAppend = "";
|
|
this.generation = generation;
|
|
this.postSummonPriority = postSummonPriority;
|
|
this.attrs = [];
|
|
this.conditions = [];
|
|
|
|
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 AbAttrString>(attrType: T): AbAttrMap[T][] {
|
|
const targetAttr = AbilityAttrs[attrType];
|
|
if (!targetAttr) {
|
|
return [];
|
|
}
|
|
return this.attrs.filter((a): a is AbAttrMap[T] => a instanceof targetAttr);
|
|
}
|
|
|
|
/**
|
|
* 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 AbAttrString>(attrType: T): boolean {
|
|
const targetAttr = AbilityAttrs[attrType];
|
|
if (!targetAttr) {
|
|
return false;
|
|
}
|
|
return this.attrs.some(attr => attr instanceof targetAttr);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
export abstract class AbAttr {
|
|
public showAbility: boolean;
|
|
private extraCondition: AbAttrCondition;
|
|
|
|
/**
|
|
* Return whether this attribute is of the given type.
|
|
*
|
|
* @remarks
|
|
* Used to avoid requiring the caller to have imported the specific attribute type, avoiding circular dependencies.
|
|
*
|
|
* @param attr - The attribute to check against
|
|
* @returns Whether the attribute is an instance of the given type
|
|
*/
|
|
public is<K extends AbAttrString>(attr: K): this is AbAttrMap[K] {
|
|
const targetAttr = AbilityAttrs[attr];
|
|
if (!targetAttr) {
|
|
return false;
|
|
}
|
|
return this instanceof targetAttr;
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
}
|
|
}
|
|
|
|
export class BlockRecoilDamageAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute for abilities that increase the chance of a double battle
|
|
* occurring.
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class DoubleBattleChanceAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
/**
|
|
* Increases the chance of a double battle occurring
|
|
* @param args [0] {@linkcode NumberHolder} for double battle chance
|
|
*/
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
const doubleBattleChance = args[0] as NumberHolder;
|
|
// This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using Utils.randSeedInt
|
|
// A double battle will initiate if the generated number is 0
|
|
doubleBattleChance.value = doubleBattleChance.value / 4;
|
|
}
|
|
}
|
|
|
|
export class PostBattleInitAbAttr extends AbAttr {
|
|
canApplyPostBattleInit(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args?: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPostBattleInit(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args?: any[]): void {}
|
|
}
|
|
|
|
export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr {
|
|
private formFunc: (p: Pokemon) => number;
|
|
|
|
constructor(formFunc: (p: Pokemon) => number) {
|
|
super(false);
|
|
|
|
this.formFunc = formFunc;
|
|
}
|
|
|
|
override canApplyPostBattleInit(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: never[]): boolean {
|
|
const formIndex = this.formFunc(pokemon);
|
|
return formIndex !== pokemon.formIndex && !simulated;
|
|
}
|
|
|
|
override applyPostBattleInit(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
|
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false);
|
|
}
|
|
}
|
|
|
|
export class PostTeraFormChangeStatChangeAbAttr extends AbAttr {
|
|
private stats: BattleStat[];
|
|
private stages: number;
|
|
|
|
constructor(stats: BattleStat[], stages: number) {
|
|
super();
|
|
|
|
this.stats = stats;
|
|
this.stages = stages;
|
|
}
|
|
|
|
override apply(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_cancelled: BooleanHolder | null,
|
|
_args: any[],
|
|
): void {
|
|
const statStageChangePhases: StatStageChangePhase[] = [];
|
|
|
|
if (!simulated) {
|
|
const phaseManager = globalScene.phaseManager;
|
|
statStageChangePhases.push(
|
|
phaseManager.create("StatStageChangePhase", pokemon.getBattlerIndex(), true, this.stats, this.stages),
|
|
);
|
|
|
|
for (const statStageChangePhase of statStageChangePhases) {
|
|
phaseManager.unshiftPhase(statStageChangePhase);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clears a specified weather whenever this attribute is called.
|
|
*/
|
|
export class ClearWeatherAbAttr extends AbAttr {
|
|
private weather: WeatherType[];
|
|
|
|
/**
|
|
* @param weather {@linkcode WeatherType[]} - the weather to be removed
|
|
*/
|
|
constructor(weather: WeatherType[]) {
|
|
super(true);
|
|
|
|
this.weather = weather;
|
|
}
|
|
|
|
public override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return globalScene.arena.canSetWeather(WeatherType.NONE);
|
|
}
|
|
|
|
public override apply(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.arena.trySetWeather(WeatherType.NONE, pokemon);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clears a specified terrain whenever this attribute is called.
|
|
*/
|
|
export class ClearTerrainAbAttr extends AbAttr {
|
|
private terrain: TerrainType[];
|
|
|
|
/**
|
|
* @param terrain {@linkcode TerrainType[]} - the terrain to be removed
|
|
*/
|
|
constructor(terrain: TerrainType[]) {
|
|
super(true);
|
|
|
|
this.terrain = terrain;
|
|
}
|
|
|
|
public override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return globalScene.arena.canSetTerrain(TerrainType.NONE);
|
|
}
|
|
|
|
public override apply(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.arena.trySetTerrain(TerrainType.NONE, true, pokemon);
|
|
}
|
|
}
|
|
}
|
|
|
|
type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean;
|
|
|
|
export class PreDefendAbAttr extends AbAttr {
|
|
canApplyPreDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move | null,
|
|
_cancelled: BooleanHolder | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPreDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move | null,
|
|
_cancelled: BooleanHolder | null,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr {
|
|
override canApplyPreDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move | null,
|
|
_cancelled: BooleanHolder | null,
|
|
args: any[],
|
|
): boolean {
|
|
return (
|
|
pokemon.isFullHp() &&
|
|
// Checks if pokemon has wonder_guard (which forces 1hp)
|
|
pokemon.getMaxHp() > 1 &&
|
|
// Damage >= hp
|
|
(args[0] as NumberHolder).value >= pokemon.hp
|
|
);
|
|
}
|
|
|
|
override applyPreDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
pokemon.addTag(BattlerTagType.STURDY, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class BlockItemTheftAbAttr extends AbAttr {
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]) {
|
|
return i18next.t("abilityTriggers:blockItemTheft", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class StabBoostAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
|
return (args[0] as NumberHolder).value > 1;
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as NumberHolder).value += 0.5;
|
|
}
|
|
}
|
|
|
|
export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr {
|
|
protected condition: PokemonDefendCondition;
|
|
private damageMultiplier: number;
|
|
|
|
constructor(condition: PokemonDefendCondition, damageMultiplier: number, showAbility = false) {
|
|
super(showAbility);
|
|
|
|
this.condition = condition;
|
|
this.damageMultiplier = damageMultiplier;
|
|
}
|
|
|
|
override canApplyPreDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_cancelled: BooleanHolder | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return this.condition(pokemon, attacker, move);
|
|
}
|
|
|
|
override applyPreDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as NumberHolder).value = toDmgValue((args[0] as NumberHolder).value * this.damageMultiplier);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reduces the damage dealt to an allied Pokemon. Used by Friend Guard.
|
|
* @see {@linkcode applyPreDefend}
|
|
*/
|
|
export class AlliedFieldDamageReductionAbAttr extends PreDefendAbAttr {
|
|
private damageMultiplier: number;
|
|
|
|
constructor(damageMultiplier: number) {
|
|
super();
|
|
this.damageMultiplier = damageMultiplier;
|
|
}
|
|
|
|
/**
|
|
* Handles the damage reduction
|
|
* @param args
|
|
* - `[0]` {@linkcode NumberHolder} - The damage being dealt
|
|
*/
|
|
override applyPreDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
const damage = args[0] as NumberHolder;
|
|
damage.value = toDmgValue(damage.value * this.damageMultiplier);
|
|
}
|
|
}
|
|
|
|
export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
|
|
constructor(moveType: PokemonType, damageMultiplier: number) {
|
|
super((_target, user, move) => user.getMoveType(move) === moveType, damageMultiplier, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines whether a Pokemon is immune to a move because of an ability.
|
|
* @extends PreDefendAbAttr
|
|
* @see {@linkcode applyPreDefend}
|
|
* @see {@linkcode getCondition}
|
|
*/
|
|
export class TypeImmunityAbAttr extends PreDefendAbAttr {
|
|
private immuneType: PokemonType | null;
|
|
private condition: AbAttrCondition | null;
|
|
|
|
// TODO: `immuneType` shouldn't be able to be `null`
|
|
constructor(immuneType: PokemonType | null, condition?: AbAttrCondition) {
|
|
super(true);
|
|
|
|
this.immuneType = immuneType;
|
|
this.condition = condition ?? null;
|
|
}
|
|
|
|
override canApplyPreDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_cancelled: BooleanHolder | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return (
|
|
![MoveTarget.BOTH_SIDES, MoveTarget.ENEMY_SIDE, MoveTarget.USER_SIDE].includes(move.moveTarget) &&
|
|
attacker !== pokemon &&
|
|
attacker.getMoveType(move) === this.immuneType
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Applies immunity if this ability grants immunity to the type of the given move.
|
|
* @param _pokemon {@linkcode Pokemon} The defending Pokemon.
|
|
* @param _passive - Whether the ability is passive.
|
|
* @param _attacker {@linkcode Pokemon} The attacking Pokemon.
|
|
* @param _move {@linkcode Move} The attacking move.
|
|
* @param _cancelled {@linkcode BooleanHolder} - A holder for a boolean value indicating if the move was cancelled.
|
|
* @param args [0] {@linkcode NumberHolder} gets set to 0 if move is immuned by an ability.
|
|
* @param args [1] - Whether the move is simulated.
|
|
*/
|
|
override applyPreDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as NumberHolder).value = 0;
|
|
}
|
|
|
|
getImmuneType(): PokemonType | null {
|
|
return this.immuneType;
|
|
}
|
|
|
|
override getCondition(): AbAttrCondition | null {
|
|
return this.condition;
|
|
}
|
|
}
|
|
|
|
export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr {
|
|
// biome-ignore lint/complexity/noUselessConstructor: Changes the type of `immuneType`
|
|
constructor(immuneType: PokemonType, condition?: AbAttrCondition) {
|
|
super(immuneType, condition);
|
|
}
|
|
|
|
override canApplyPreDefend(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
cancelled: BooleanHolder | null,
|
|
args: any[],
|
|
): boolean {
|
|
return (
|
|
move.category !== MoveCategory.STATUS &&
|
|
!move.hasAttr("NeutralDamageAgainstFlyingTypeMultiplierAttr") &&
|
|
super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Applies immunity if the move used is not a status move.
|
|
* Type immunity abilities that do not give additional benefits (HP recovery, stat boosts, etc) are not immune to status moves of the type
|
|
* Example: Levitate
|
|
*/
|
|
override applyPreDefend(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
// this is a hacky way to fix the Levitate/Thousand Arrows interaction, but it works for now...
|
|
super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args);
|
|
}
|
|
}
|
|
|
|
export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
|
|
// biome-ignore lint/complexity/noUselessConstructor: Changes the type of `immuneType`
|
|
constructor(immuneType: PokemonType) {
|
|
super(immuneType);
|
|
}
|
|
|
|
override canApplyPreDefend(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
cancelled: BooleanHolder | null,
|
|
args: any[],
|
|
): boolean {
|
|
return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args);
|
|
}
|
|
|
|
override applyPreDefend(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args);
|
|
if (!pokemon.isFullHp() && !simulated) {
|
|
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
|
|
globalScene.phaseManager.unshiftNew(
|
|
"PokemonHealPhase",
|
|
pokemon.getBattlerIndex(),
|
|
toDmgValue(pokemon.getMaxHp() / 4),
|
|
i18next.t("abilityTriggers:typeImmunityHeal", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
}),
|
|
true,
|
|
);
|
|
cancelled.value = true; // Suppresses "No Effect" message
|
|
}
|
|
}
|
|
}
|
|
|
|
class TypeImmunityStatStageChangeAbAttr extends TypeImmunityAbAttr {
|
|
private stat: BattleStat;
|
|
private stages: number;
|
|
|
|
constructor(immuneType: PokemonType, stat: BattleStat, stages: number, condition?: AbAttrCondition) {
|
|
super(immuneType, condition);
|
|
|
|
this.stat = stat;
|
|
this.stages = stages;
|
|
}
|
|
|
|
override canApplyPreDefend(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
cancelled: BooleanHolder | null,
|
|
args: any[],
|
|
): boolean {
|
|
return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args);
|
|
}
|
|
|
|
override applyPreDefend(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args);
|
|
cancelled.value = true; // Suppresses "No Effect" message
|
|
if (!simulated) {
|
|
globalScene.phaseManager.unshiftNew(
|
|
"StatStageChangePhase",
|
|
pokemon.getBattlerIndex(),
|
|
true,
|
|
[this.stat],
|
|
this.stages,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr {
|
|
private tagType: BattlerTagType;
|
|
private turnCount: number;
|
|
|
|
constructor(immuneType: PokemonType, tagType: BattlerTagType, turnCount: number, condition?: AbAttrCondition) {
|
|
super(immuneType, condition);
|
|
|
|
this.tagType = tagType;
|
|
this.turnCount = turnCount;
|
|
}
|
|
|
|
override canApplyPreDefend(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
cancelled: BooleanHolder | null,
|
|
args: any[],
|
|
): boolean {
|
|
return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args);
|
|
}
|
|
|
|
override applyPreDefend(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args);
|
|
cancelled.value = true; // Suppresses "No Effect" message
|
|
if (!simulated) {
|
|
pokemon.addTag(this.tagType, this.turnCount, undefined, pokemon.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
|
|
constructor(condition?: AbAttrCondition) {
|
|
super(null, condition);
|
|
}
|
|
|
|
override canApplyPreDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_cancelled: BooleanHolder | null,
|
|
args: any[],
|
|
): boolean {
|
|
const modifierValue =
|
|
args.length > 0
|
|
? (args[0] as NumberHolder).value
|
|
: pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker, undefined, undefined, move);
|
|
return move.is("AttackMove") && modifierValue < 2;
|
|
}
|
|
|
|
override applyPreDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
cancelled.value = true; // Suppresses "No Effect" message
|
|
(args[0] as NumberHolder).value = 0;
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:nonSuperEffectiveImmunity", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Tera_Shell_(Ability) | Tera Shell}
|
|
* When the source is at full HP, incoming attacks will have a maximum 0.5x type effectiveness multiplier.
|
|
* @extends PreDefendAbAttr
|
|
*/
|
|
export class FullHpResistTypeAbAttr extends PreDefendAbAttr {
|
|
override canApplyPreDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
move: Move | null,
|
|
_cancelled: BooleanHolder | null,
|
|
args: any[],
|
|
): boolean {
|
|
const typeMultiplier = args[0];
|
|
return (
|
|
typeMultiplier instanceof NumberHolder &&
|
|
!move?.hasAttr("FixedDamageAttr") &&
|
|
pokemon.isFullHp() &&
|
|
typeMultiplier.value > 0.5
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Reduces a type multiplier to 0.5 if the source is at full HP.
|
|
* @param pokemon {@linkcode Pokemon} the Pokemon with this ability
|
|
* @param _passive n/a
|
|
* @param _simulated n/a (this doesn't change game state)
|
|
* @param _attacker n/a
|
|
* @param _move {@linkcode Move} the move being used on the source
|
|
* @param _cancelled n/a
|
|
* @param args `[0]` a container for the move's current type effectiveness multiplier
|
|
*/
|
|
override applyPreDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move | null,
|
|
_cancelled: BooleanHolder | null,
|
|
args: any[],
|
|
): void {
|
|
const typeMultiplier = args[0];
|
|
typeMultiplier.value = 0.5;
|
|
pokemon.turnData.moveEffectiveness = 0.5;
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:fullHpResistType", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
});
|
|
}
|
|
}
|
|
|
|
export class PostDefendAbAttr extends AbAttr {
|
|
canApplyPostDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPostDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr {
|
|
override canApplyPreDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_cancelled: BooleanHolder | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return (
|
|
!(move.moveTarget === MoveTarget.USER || move.moveTarget === MoveTarget.NEAR_ALLY) &&
|
|
move.getPriority(attacker) > 0 &&
|
|
!move.isMultiTarget()
|
|
);
|
|
}
|
|
|
|
override applyPreDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
export class PostStatStageChangeAbAttr extends AbAttr {
|
|
canApplyPostStatStageChange(
|
|
_pokemon: Pokemon,
|
|
_simulated: boolean,
|
|
_statsChanged: BattleStat[],
|
|
_stagesChanged: number,
|
|
_selfTarget: boolean,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPostStatStageChange(
|
|
_pokemon: Pokemon,
|
|
_simulated: boolean,
|
|
_statsChanged: BattleStat[],
|
|
_stagesChanged: number,
|
|
_selfTarget: boolean,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
export class MoveImmunityAbAttr extends PreDefendAbAttr {
|
|
private immuneCondition: PreDefendAbAttrCondition;
|
|
|
|
constructor(immuneCondition: PreDefendAbAttrCondition) {
|
|
super(true);
|
|
|
|
this.immuneCondition = immuneCondition;
|
|
}
|
|
|
|
override canApplyPreDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_cancelled: BooleanHolder | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return this.immuneCondition(pokemon, attacker, move);
|
|
}
|
|
|
|
override applyPreDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:moveImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reduces the accuracy of status moves used against the Pokémon with this ability to 50%.
|
|
* Used by Wonder Skin.
|
|
*
|
|
* @extends PreDefendAbAttr
|
|
*/
|
|
export class WonderSkinAbAttr extends PreDefendAbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
override canApplyPreDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
move: Move,
|
|
_cancelled: BooleanHolder | null,
|
|
args: any[],
|
|
): boolean {
|
|
const moveAccuracy = args[0] as NumberHolder;
|
|
return move.category === MoveCategory.STATUS && moveAccuracy.value >= 50;
|
|
}
|
|
|
|
override applyPreDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
const moveAccuracy = args[0] as NumberHolder;
|
|
moveAccuracy.value = 50;
|
|
}
|
|
}
|
|
|
|
export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr {
|
|
private stat: BattleStat;
|
|
private stages: number;
|
|
|
|
constructor(immuneCondition: PreDefendAbAttrCondition, stat: BattleStat, stages: number) {
|
|
super(immuneCondition);
|
|
this.stat = stat;
|
|
this.stages = stages;
|
|
}
|
|
|
|
override canApplyPreDefend(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
cancelled: BooleanHolder | null,
|
|
args: any[],
|
|
): boolean {
|
|
return !simulated && super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args);
|
|
}
|
|
|
|
override applyPreDefend(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args);
|
|
globalScene.phaseManager.unshiftNew(
|
|
"StatStageChangePhase",
|
|
pokemon.getBattlerIndex(),
|
|
true,
|
|
[this.stat],
|
|
this.stages,
|
|
);
|
|
}
|
|
}
|
|
/**
|
|
* Class for abilities that make drain moves deal damage to user instead of healing them.
|
|
* @extends PostDefendAbAttr
|
|
* @see {@linkcode applyPostDefend}
|
|
*/
|
|
export class ReverseDrainAbAttr extends PostDefendAbAttr {
|
|
override canApplyPostDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return move.hasAttr("HitHealAttr");
|
|
}
|
|
|
|
/**
|
|
* Determines if a damage and draining move was used to check if this ability should stop the healing.
|
|
* Examples include: Absorb, Draining Kiss, Bitter Blade, etc.
|
|
* Also displays a message to show this ability was activated.
|
|
* @param _pokemon {@linkcode Pokemon} with this ability
|
|
* @param _passive N/A
|
|
* @param attacker {@linkcode Pokemon} that is attacking this Pokemon
|
|
* @param _move {@linkcode PokemonMove} that is being used
|
|
* @param _hitResult N/A
|
|
* @param _args N/A
|
|
*/
|
|
override applyPostDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) }),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr {
|
|
private condition: PokemonDefendCondition;
|
|
private stat: BattleStat;
|
|
private stages: number;
|
|
private selfTarget: boolean;
|
|
private allOthers: boolean;
|
|
|
|
constructor(
|
|
condition: PokemonDefendCondition,
|
|
stat: BattleStat,
|
|
stages: number,
|
|
selfTarget = true,
|
|
allOthers = false,
|
|
) {
|
|
super(true);
|
|
|
|
this.condition = condition;
|
|
this.stat = stat;
|
|
this.stages = stages;
|
|
this.selfTarget = selfTarget;
|
|
this.allOthers = allOthers;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return this.condition(pokemon, attacker, move);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (simulated) {
|
|
return;
|
|
}
|
|
|
|
if (this.allOthers) {
|
|
const ally = pokemon.getAlly();
|
|
const otherPokemon = !isNullOrUndefined(ally) ? pokemon.getOpponents().concat([ally]) : pokemon.getOpponents();
|
|
for (const other of otherPokemon) {
|
|
globalScene.phaseManager.unshiftNew(
|
|
"StatStageChangePhase",
|
|
other.getBattlerIndex(),
|
|
false,
|
|
[this.stat],
|
|
this.stages,
|
|
);
|
|
}
|
|
} else {
|
|
globalScene.phaseManager.unshiftNew(
|
|
"StatStageChangePhase",
|
|
(this.selfTarget ? pokemon : attacker).getBattlerIndex(),
|
|
this.selfTarget,
|
|
[this.stat],
|
|
this.stages,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
|
|
private condition: PokemonDefendCondition;
|
|
private hpGate: number;
|
|
private stats: BattleStat[];
|
|
private stages: number;
|
|
private selfTarget: boolean;
|
|
|
|
constructor(
|
|
condition: PokemonDefendCondition,
|
|
hpGate: number,
|
|
stats: BattleStat[],
|
|
stages: number,
|
|
selfTarget = true,
|
|
) {
|
|
super(true);
|
|
|
|
this.condition = condition;
|
|
this.hpGate = hpGate;
|
|
this.stats = stats;
|
|
this.stages = stages;
|
|
this.selfTarget = selfTarget;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate);
|
|
const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1];
|
|
const damageReceived = lastAttackReceived?.damage || 0;
|
|
return (
|
|
this.condition(pokemon, attacker, move) && pokemon.hp <= hpGateFlat && pokemon.hp + damageReceived > hpGateFlat
|
|
);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.phaseManager.unshiftNew(
|
|
"StatStageChangePhase",
|
|
(this.selfTarget ? pokemon : attacker).getBattlerIndex(),
|
|
true,
|
|
this.stats,
|
|
this.stages,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr {
|
|
private condition: PokemonDefendCondition;
|
|
private tagType: ArenaTagType;
|
|
|
|
constructor(condition: PokemonDefendCondition, tagType: ArenaTagType) {
|
|
super(true);
|
|
|
|
this.condition = condition;
|
|
this.tagType = tagType;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
const tag = globalScene.arena.getTag(this.tagType) as ArenaTrapTag;
|
|
return (
|
|
this.condition(pokemon, attacker, move) && (!globalScene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers)
|
|
);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.arena.addTag(
|
|
this.tagType,
|
|
0,
|
|
undefined,
|
|
pokemon.id,
|
|
pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
|
|
private condition: PokemonDefendCondition;
|
|
private tagType: BattlerTagType;
|
|
constructor(condition: PokemonDefendCondition, tagType: BattlerTagType) {
|
|
super(true);
|
|
|
|
this.condition = condition;
|
|
this.tagType = tagType;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return this.condition(pokemon, attacker, move);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!pokemon.getTag(this.tagType) && !simulated) {
|
|
pokemon.addTag(this.tagType, undefined, undefined, pokemon.id);
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("abilityTriggers:windPowerCharged", {
|
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
|
moveName: move.name,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
|
|
private type: PokemonType;
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
hitResult: HitResult,
|
|
_args: any[],
|
|
): boolean {
|
|
this.type = attacker.getMoveType(move);
|
|
const pokemonTypes = pokemon.getTypes(true);
|
|
return hitResult < HitResult.NO_EFFECT && (simulated || pokemonTypes.length !== 1 || pokemonTypes[0] !== this.type);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
const type = attacker.getMoveType(move);
|
|
pokemon.summonData.types = [type];
|
|
}
|
|
|
|
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:postDefendTypeChange", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
typeName: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`),
|
|
});
|
|
}
|
|
}
|
|
|
|
export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
|
|
private terrainType: TerrainType;
|
|
|
|
constructor(terrainType: TerrainType) {
|
|
super();
|
|
|
|
this.terrainType = terrainType;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
hitResult: HitResult,
|
|
_args: any[],
|
|
): boolean {
|
|
return hitResult < HitResult.NO_EFFECT && globalScene.arena.canSetTerrain(this.terrainType);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.arena.trySetTerrain(this.terrainType, false, pokemon);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
|
|
public chance: number;
|
|
private effects: StatusEffect[];
|
|
|
|
constructor(chance: number, ...effects: StatusEffect[]) {
|
|
super(true);
|
|
|
|
this.chance = chance;
|
|
this.effects = effects;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
const effect =
|
|
this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)];
|
|
return (
|
|
move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) &&
|
|
!attacker.status &&
|
|
(this.chance === -1 || pokemon.randBattleSeedInt(100) < this.chance) &&
|
|
attacker.canSetStatus(effect, true, false, pokemon)
|
|
);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
const effect =
|
|
this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)];
|
|
attacker.trySetStatus(effect, true, pokemon);
|
|
}
|
|
}
|
|
|
|
export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr {
|
|
constructor() {
|
|
super(10, StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP);
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
hitResult: HitResult | null,
|
|
args: any[],
|
|
): boolean {
|
|
return (
|
|
!(attacker.hasAbility(AbilityId.OVERCOAT) || attacker.isOfType(PokemonType.GRASS)) &&
|
|
super.canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args)
|
|
);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
hitResult: HitResult,
|
|
args: any[],
|
|
): void {
|
|
super.applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args);
|
|
}
|
|
}
|
|
|
|
export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
|
|
private chance: number;
|
|
private tagType: BattlerTagType;
|
|
private turnCount: number | undefined;
|
|
|
|
constructor(chance: number, tagType: BattlerTagType, turnCount?: number) {
|
|
super();
|
|
|
|
this.tagType = tagType;
|
|
this.chance = chance;
|
|
this.turnCount = turnCount;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return (
|
|
move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) &&
|
|
pokemon.randBattleSeedInt(100) < this.chance &&
|
|
attacker.canAddTag(this.tagType)
|
|
);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
attacker.addTag(this.tagType, this.turnCount, move.id, attacker.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
|
|
private stat: BattleStat;
|
|
private stages: number;
|
|
|
|
constructor(stat: BattleStat, stages: number) {
|
|
super();
|
|
|
|
this.stat = stat;
|
|
this.stages = stages;
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.phaseManager.unshiftNew(
|
|
"StatStageChangePhase",
|
|
pokemon.getBattlerIndex(),
|
|
true,
|
|
[this.stat],
|
|
this.stages,
|
|
);
|
|
}
|
|
}
|
|
|
|
override getCondition(): AbAttrCondition {
|
|
return (pokemon: Pokemon) =>
|
|
pokemon.turnData.attacksReceived.length !== 0 &&
|
|
pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical;
|
|
}
|
|
}
|
|
|
|
export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
|
|
private damageRatio: number;
|
|
|
|
constructor(damageRatio: number) {
|
|
super();
|
|
|
|
this.damageRatio = damageRatio;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return (
|
|
!simulated &&
|
|
move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) &&
|
|
!attacker.hasAbilityWithAttr("BlockNonDirectDamageAbAttr")
|
|
);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), { result: HitResult.INDIRECT });
|
|
attacker.turnData.damageTaken += toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio));
|
|
}
|
|
|
|
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:postDefendContactDamage", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* @description: This ability applies the Perish Song tag to the attacking pokemon
|
|
* and the defending pokemon if the move makes physical contact and neither pokemon
|
|
* already has the Perish Song tag.
|
|
* @class PostDefendPerishSongAbAttr
|
|
* @extends {PostDefendAbAttr}
|
|
*/
|
|
export class PostDefendPerishSongAbAttr extends PostDefendAbAttr {
|
|
private turns: number;
|
|
|
|
constructor(turns: number) {
|
|
super();
|
|
|
|
this.turns = turns;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return (
|
|
move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) &&
|
|
!attacker.getTag(BattlerTagType.PERISH_SONG)
|
|
);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
attacker.addTag(BattlerTagType.PERISH_SONG, this.turns);
|
|
pokemon.addTag(BattlerTagType.PERISH_SONG, this.turns);
|
|
}
|
|
}
|
|
|
|
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:perishBody", {
|
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
|
abilityName: abilityName,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr {
|
|
private weatherType: WeatherType;
|
|
protected condition?: PokemonDefendCondition;
|
|
|
|
constructor(weatherType: WeatherType, condition?: PokemonDefendCondition) {
|
|
super();
|
|
|
|
this.weatherType = weatherType;
|
|
this.condition = condition;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return (
|
|
!(this.condition && !this.condition(pokemon, attacker, move)) &&
|
|
!globalScene.arena.weather?.isImmutable() &&
|
|
globalScene.arena.canSetWeather(this.weatherType)
|
|
);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.arena.trySetWeather(this.weatherType, pokemon);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return (
|
|
move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) &&
|
|
attacker.getAbility().isSwappable
|
|
);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
const tempAbility = attacker.getAbility();
|
|
attacker.setTempAbility(pokemon.getAbility());
|
|
pokemon.setTempAbility(tempAbility);
|
|
}
|
|
}
|
|
|
|
override getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:postDefendAbilitySwap", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
});
|
|
}
|
|
}
|
|
|
|
export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
|
|
private ability: AbilityId;
|
|
|
|
constructor(ability: AbilityId) {
|
|
super();
|
|
this.ability = ability;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return (
|
|
move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) &&
|
|
attacker.getAbility().isSuppressable &&
|
|
!attacker.getAbility().hasAttr("PostDefendAbilityGiveAbAttr")
|
|
);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
attacker.setTempAbility(allAbilities[this.ability]);
|
|
}
|
|
}
|
|
|
|
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:postDefendAbilityGive", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
|
|
private chance: number;
|
|
private attacker: Pokemon;
|
|
private move: Move;
|
|
|
|
constructor(chance: number) {
|
|
super();
|
|
|
|
this.chance = chance;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return (
|
|
isNullOrUndefined(attacker.getTag(BattlerTagType.DISABLED)) &&
|
|
move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) &&
|
|
(this.chance === -1 || pokemon.randBattleSeedInt(100) < this.chance)
|
|
);
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
this.attacker = attacker;
|
|
this.move = move;
|
|
this.attacker.addTag(BattlerTagType.DISABLED, 4, 0, pokemon.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChangeAbAttr {
|
|
private condition: PokemonStatStageChangeCondition;
|
|
private statsToChange: BattleStat[];
|
|
private stages: number;
|
|
|
|
constructor(condition: PokemonStatStageChangeCondition, statsToChange: BattleStat[], stages: number) {
|
|
super(true);
|
|
|
|
this.condition = condition;
|
|
this.statsToChange = statsToChange;
|
|
this.stages = stages;
|
|
}
|
|
|
|
override canApplyPostStatStageChange(
|
|
pokemon: Pokemon,
|
|
_simulated: boolean,
|
|
statStagesChanged: BattleStat[],
|
|
stagesChanged: number,
|
|
selfTarget: boolean,
|
|
_args: any[],
|
|
): boolean {
|
|
return this.condition(pokemon, statStagesChanged, stagesChanged) && !selfTarget;
|
|
}
|
|
|
|
override applyPostStatStageChange(
|
|
pokemon: Pokemon,
|
|
simulated: boolean,
|
|
_statStagesChanged: BattleStat[],
|
|
_stagesChanged: number,
|
|
_selfTarget: boolean,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.phaseManager.unshiftNew(
|
|
"StatStageChangePhase",
|
|
pokemon.getBattlerIndex(),
|
|
true,
|
|
this.statsToChange,
|
|
this.stages,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PreAttackAbAttr extends AbAttr {
|
|
canApplyPreAttack(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_defender: Pokemon | null,
|
|
_move: Move,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPreAttack(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_defender: Pokemon | null,
|
|
_move: Move,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
/**
|
|
* Modifies moves additional effects with multipliers, ie. Sheer Force, Serene Grace.
|
|
* @extends AbAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class MoveEffectChanceMultiplierAbAttr extends AbAttr {
|
|
private chanceMultiplier: number;
|
|
|
|
constructor(chanceMultiplier: number) {
|
|
super(false);
|
|
this.chanceMultiplier = chanceMultiplier;
|
|
}
|
|
|
|
override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
|
const exceptMoves = [MoveId.ORDER_UP, MoveId.ELECTRO_SHOT];
|
|
return !((args[0] as NumberHolder).value <= 0 || exceptMoves.includes((args[1] as Move).id));
|
|
}
|
|
|
|
/**
|
|
* @param args [0]: {@linkcode NumberHolder} Move additional effect chance. Has to be higher than or equal to 0.
|
|
* [1]: {@linkcode MoveId } Move used by the ability user.
|
|
*/
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as NumberHolder).value *= this.chanceMultiplier;
|
|
(args[0] as NumberHolder).value = Math.min((args[0] as NumberHolder).value, 100);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets incoming moves additional effect chance to zero, ignoring all effects from moves. ie. Shield Dust.
|
|
* @extends PreDefendAbAttr
|
|
* @see {@linkcode applyPreDefend}
|
|
*/
|
|
export class IgnoreMoveEffectsAbAttr extends PreDefendAbAttr {
|
|
constructor(showAbility = false) {
|
|
super(showAbility);
|
|
}
|
|
|
|
override canApplyPreDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move | null,
|
|
_cancelled: BooleanHolder | null,
|
|
args: any[],
|
|
): boolean {
|
|
return (args[0] as NumberHolder).value > 0;
|
|
}
|
|
|
|
/**
|
|
* @param args [0]: {@linkcode NumberHolder} Move additional effect chance.
|
|
*/
|
|
override applyPreDefend(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as NumberHolder).value = 0;
|
|
}
|
|
}
|
|
|
|
export class VariableMovePowerAbAttr extends PreAttackAbAttr {
|
|
override canApplyPreAttack(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_defender: Pokemon,
|
|
_move: Move,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class FieldPreventExplosiveMovesAbAttr extends AbAttr {
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Multiplies a Stat if the checked Pokemon lacks this ability.
|
|
* If this ability cannot stack, a BooleanHolder can be used to prevent this from stacking.
|
|
* @see {@link applyFieldStatMultiplierAbAttrs}
|
|
* @see {@link applyFieldStat}
|
|
* @see {@link BooleanHolder}
|
|
*/
|
|
export class FieldMultiplyStatAbAttr extends AbAttr {
|
|
private stat: Stat;
|
|
private multiplier: number;
|
|
private canStack: boolean;
|
|
|
|
constructor(stat: Stat, multiplier: number, canStack = false) {
|
|
super(false);
|
|
|
|
this.stat = stat;
|
|
this.multiplier = multiplier;
|
|
this.canStack = canStack;
|
|
}
|
|
|
|
canApplyFieldStat(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
stat: Stat,
|
|
_statValue: NumberHolder,
|
|
checkedPokemon: Pokemon,
|
|
hasApplied: BooleanHolder,
|
|
_args: any[],
|
|
): boolean {
|
|
return (
|
|
this.canStack ||
|
|
(!hasApplied.value &&
|
|
this.stat === stat &&
|
|
checkedPokemon.getAbilityAttrs("FieldMultiplyStatAbAttr").every(attr => attr.stat !== stat))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* applyFieldStat: Tries to multiply a Pokemon's Stat
|
|
* @param _pokemon {@linkcode Pokemon} the Pokemon using this ability
|
|
* @param _passive {@linkcode boolean} unused
|
|
* @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 this ability is targeting
|
|
* @param hasApplied {@linkcode BooleanHolder} whether or not another multiplier has been applied to this stat
|
|
* @param _args {any[]} unused
|
|
*/
|
|
applyFieldStat(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_stat: Stat,
|
|
statValue: NumberHolder,
|
|
_checkedPokemon: Pokemon,
|
|
hasApplied: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
statValue.value *= this.multiplier;
|
|
hasApplied.value = true;
|
|
}
|
|
}
|
|
|
|
export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
|
|
constructor(
|
|
private newType: PokemonType,
|
|
private powerMultiplier: number,
|
|
private condition?: PokemonAttackCondition,
|
|
) {
|
|
super(false);
|
|
}
|
|
|
|
/**
|
|
* Determine if the move type change attribute can be applied
|
|
*
|
|
* Can be applied if:
|
|
* - The ability's condition is met, e.g. pixilate only boosts normal moves,
|
|
* - The move is not forbidden from having its type changed by an ability, e.g. {@linkcode MoveId.MULTI_ATTACK}
|
|
* - The user is not terastallized and using tera blast
|
|
* - The user is not a terastallized terapagos with tera stellar using tera starstorm
|
|
* @param pokemon - The pokemon that has the move type changing ability and is using the attacking move
|
|
* @param _passive - Unused
|
|
* @param _simulated - Unused
|
|
* @param _defender - The pokemon being attacked (unused)
|
|
* @param move - The move being used
|
|
* @param _args - args[0] holds the type that the move is changed to, args[1] holds the multiplier
|
|
* @returns whether the move type change attribute can be applied
|
|
*/
|
|
override canApplyPreAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_defender: Pokemon | null,
|
|
move: Move,
|
|
_args: [NumberHolder?, NumberHolder?, ...any],
|
|
): boolean {
|
|
return (
|
|
(!this.condition || this.condition(pokemon, _defender, move)) &&
|
|
!noAbilityTypeOverrideMoves.has(move.id) &&
|
|
(!pokemon.isTerastallized ||
|
|
(move.id !== MoveId.TERA_BLAST &&
|
|
(move.id !== MoveId.TERA_STARSTORM ||
|
|
pokemon.getTeraType() !== PokemonType.STELLAR ||
|
|
!pokemon.hasSpecies(SpeciesId.TERAPAGOS))))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param _pokemon - The pokemon that has the move type changing ability and is using the attacking move
|
|
* @param _passive - Unused
|
|
* @param _simulated - Unused
|
|
* @param _defender - The pokemon being attacked (unused)
|
|
* @param _move - The move being used
|
|
* @param args - args[0] holds the type that the move is changed to, args[1] holds the multiplier
|
|
*/
|
|
override applyPreAttack(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_defender: Pokemon,
|
|
_move: Move,
|
|
args: [NumberHolder?, NumberHolder?, ...any],
|
|
): void {
|
|
if (args[0] && args[0] instanceof NumberHolder) {
|
|
args[0].value = this.newType;
|
|
}
|
|
if (args[1] && args[1] instanceof NumberHolder) {
|
|
args[1].value *= this.powerMultiplier;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Ability attribute for changing a pokemon's type before using a move */
|
|
export class PokemonTypeChangeAbAttr extends PreAttackAbAttr {
|
|
private moveType: PokemonType;
|
|
|
|
constructor() {
|
|
super(true);
|
|
}
|
|
|
|
override canApplyPreAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_defender: Pokemon | null,
|
|
move: Move,
|
|
_args: any[],
|
|
): boolean {
|
|
if (
|
|
!pokemon.isTerastallized &&
|
|
move.id !== MoveId.STRUGGLE &&
|
|
/**
|
|
* Skip moves that call other moves because these moves generate a following move that will trigger this ability attribute
|
|
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_call_other_moves}
|
|
*/
|
|
!move.findAttr(
|
|
attr =>
|
|
attr.is("RandomMovesetMoveAttr") ||
|
|
attr.is("RandomMoveAttr") ||
|
|
attr.is("NaturePowerAttr") ||
|
|
attr.is("CopyMoveAttr"),
|
|
)
|
|
) {
|
|
const moveType = pokemon.getMoveType(move);
|
|
if (pokemon.getTypes().some(t => t !== moveType)) {
|
|
this.moveType = moveType;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
override applyPreAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_defender: Pokemon,
|
|
move: Move,
|
|
_args: any[],
|
|
): void {
|
|
const moveType = pokemon.getMoveType(move);
|
|
|
|
if (!simulated) {
|
|
this.moveType = moveType;
|
|
pokemon.summonData.types = [moveType];
|
|
pokemon.updateInfo();
|
|
}
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:pokemonTypeChange", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`),
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class for abilities that convert single-strike moves to two-strike moves (i.e. Parental Bond).
|
|
* @param damageMultiplier the damage multiplier for the second strike, relative to the first.
|
|
*/
|
|
export class AddSecondStrikeAbAttr extends PreAttackAbAttr {
|
|
private damageMultiplier: number;
|
|
|
|
constructor(damageMultiplier: number) {
|
|
super(false);
|
|
|
|
this.damageMultiplier = damageMultiplier;
|
|
}
|
|
|
|
override canApplyPreAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_defender: Pokemon | null,
|
|
move: Move,
|
|
_args: any[],
|
|
): boolean {
|
|
return move.canBeMultiStrikeEnhanced(pokemon, true);
|
|
}
|
|
|
|
/**
|
|
* If conditions are met, this doubles the move's hit count (via args[1])
|
|
* or multiplies the damage of secondary strikes (via args[2])
|
|
* @param pokemon the {@linkcode Pokemon} using the move
|
|
* @param _passive n/a
|
|
* @param _defender n/a
|
|
* @param _move the {@linkcode Move} used by the ability source
|
|
* @param args Additional arguments:
|
|
* - `[0]` the number of strikes this move currently has ({@linkcode NumberHolder})
|
|
* - `[1]` the damage multiplier for the current strike ({@linkcode NumberHolder})
|
|
*/
|
|
override applyPreAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_defender: Pokemon,
|
|
_move: Move,
|
|
args: any[],
|
|
): void {
|
|
const hitCount = args[0] as NumberHolder;
|
|
const multiplier = args[1] as NumberHolder;
|
|
if (hitCount?.value) {
|
|
hitCount.value += 1;
|
|
}
|
|
|
|
if (multiplier?.value && pokemon.turnData.hitsLeft === 1) {
|
|
multiplier.value = this.damageMultiplier;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class for abilities that boost the damage of moves
|
|
* For abilities that boost the base power of moves, see VariableMovePowerAbAttr
|
|
* @param damageMultiplier the amount to multiply the damage by
|
|
* @param condition the condition for this ability to be applied
|
|
*/
|
|
export class DamageBoostAbAttr extends PreAttackAbAttr {
|
|
private damageMultiplier: number;
|
|
private condition: PokemonAttackCondition;
|
|
|
|
constructor(damageMultiplier: number, condition: PokemonAttackCondition) {
|
|
super(false);
|
|
this.damageMultiplier = damageMultiplier;
|
|
this.condition = condition;
|
|
}
|
|
|
|
override canApplyPreAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
defender: Pokemon | null,
|
|
move: Move,
|
|
_args: any[],
|
|
): boolean {
|
|
return this.condition(pokemon, defender, move);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param _pokemon the attacker pokemon
|
|
* @param _passive N/A
|
|
* @param _defender the target pokemon
|
|
* @param _move the move used by the attacker pokemon
|
|
* @param args Utils.NumberHolder as damage
|
|
*/
|
|
override applyPreAttack(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_defender: Pokemon,
|
|
_move: Move,
|
|
args: any[],
|
|
): void {
|
|
const power = args[0] as NumberHolder;
|
|
power.value = toDmgValue(power.value * this.damageMultiplier);
|
|
}
|
|
}
|
|
|
|
export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr {
|
|
private condition: PokemonAttackCondition;
|
|
private powerMultiplier: number;
|
|
|
|
constructor(condition: PokemonAttackCondition, powerMultiplier: number, showAbility = false) {
|
|
super(showAbility);
|
|
this.condition = condition;
|
|
this.powerMultiplier = powerMultiplier;
|
|
}
|
|
|
|
override canApplyPreAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
defender: Pokemon | null,
|
|
move: Move,
|
|
_args: any[],
|
|
): boolean {
|
|
return this.condition(pokemon, defender, move);
|
|
}
|
|
|
|
override applyPreAttack(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_defender: Pokemon,
|
|
_move: Move,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as NumberHolder).value *= this.powerMultiplier;
|
|
}
|
|
}
|
|
|
|
export class MoveTypePowerBoostAbAttr extends MovePowerBoostAbAttr {
|
|
constructor(boostedType: PokemonType, powerMultiplier?: number) {
|
|
super((pokemon, _defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5, false);
|
|
}
|
|
}
|
|
|
|
export class LowHpMoveTypePowerBoostAbAttr extends MoveTypePowerBoostAbAttr {
|
|
// biome-ignore lint/complexity/noUselessConstructor: Changes the constructor params
|
|
constructor(boostedType: PokemonType) {
|
|
super(boostedType);
|
|
}
|
|
|
|
getCondition(): AbAttrCondition {
|
|
return pokemon => pokemon.getHpRatio() <= 0.33;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Abilities which cause a variable amount of power increase.
|
|
* @extends VariableMovePowerAbAttr
|
|
* @see {@link applyPreAttack}
|
|
*/
|
|
export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr {
|
|
private mult: (user: Pokemon, target: Pokemon, move: Move) => number;
|
|
|
|
/**
|
|
* @param mult A function which takes the user, target, and move, and returns the power multiplier. 1 means no multiplier.
|
|
* @param {boolean} showAbility Whether to show the ability when it activates.
|
|
*/
|
|
constructor(mult: (user: Pokemon, target: Pokemon, move: Move) => number, showAbility = true) {
|
|
super(showAbility);
|
|
this.mult = mult;
|
|
}
|
|
|
|
override canApplyPreAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
defender: Pokemon,
|
|
move: Move,
|
|
_args: any[],
|
|
): boolean {
|
|
return this.mult(pokemon, defender, move) !== 1;
|
|
}
|
|
|
|
override applyPreAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
defender: Pokemon,
|
|
move: Move,
|
|
args: any[],
|
|
): void {
|
|
const multiplier = this.mult(pokemon, defender, move);
|
|
(args[0] as NumberHolder).value *= multiplier;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Boosts the power of a Pokémon's move under certain conditions.
|
|
* @extends AbAttr
|
|
*/
|
|
export class FieldMovePowerBoostAbAttr extends AbAttr {
|
|
// TODO: Refactor this class? It extends from base AbAttr but has preAttack methods and gets called directly instead of going through applyAbAttrsInternal
|
|
private condition: PokemonAttackCondition;
|
|
private powerMultiplier: number;
|
|
|
|
/**
|
|
* @param condition - A function that determines whether the power boost condition is met.
|
|
* @param powerMultiplier - The multiplier to apply to the move's power when the condition is met.
|
|
*/
|
|
constructor(condition: PokemonAttackCondition, powerMultiplier: number) {
|
|
super(false);
|
|
this.condition = condition;
|
|
this.powerMultiplier = powerMultiplier;
|
|
}
|
|
|
|
canApplyPreAttack(
|
|
_pokemon: Pokemon | null,
|
|
_passive: boolean | null,
|
|
_simulated: boolean,
|
|
_defender: Pokemon | null,
|
|
_move: Move,
|
|
_args: any[],
|
|
): boolean {
|
|
return true; // logic for this attr is handled in move.ts instead of normally
|
|
}
|
|
|
|
applyPreAttack(
|
|
pokemon: Pokemon | null,
|
|
_passive: boolean | null,
|
|
_simulated: boolean,
|
|
defender: Pokemon | null,
|
|
move: Move,
|
|
args: any[],
|
|
): void {
|
|
if (this.condition(pokemon, defender, move)) {
|
|
(args[0] as NumberHolder).value *= this.powerMultiplier;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Boosts the power of a specific type of move.
|
|
* @extends FieldMovePowerBoostAbAttr
|
|
*/
|
|
export class PreAttackFieldMoveTypePowerBoostAbAttr extends FieldMovePowerBoostAbAttr {
|
|
/**
|
|
* @param boostedType - The type of move that will receive the power boost.
|
|
* @param powerMultiplier - The multiplier to apply to the move's power, defaults to 1.5 if not provided.
|
|
*/
|
|
constructor(boostedType: PokemonType, powerMultiplier?: number) {
|
|
super((pokemon, _defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Boosts the power of a specific type of move for all Pokemon in the field.
|
|
* @extends PreAttackFieldMoveTypePowerBoostAbAttr
|
|
*/
|
|
export class FieldMoveTypePowerBoostAbAttr extends PreAttackFieldMoveTypePowerBoostAbAttr {}
|
|
|
|
/**
|
|
* Boosts the power of a specific type of move for the user and its allies.
|
|
* @extends PreAttackFieldMoveTypePowerBoostAbAttr
|
|
*/
|
|
export class UserFieldMoveTypePowerBoostAbAttr extends PreAttackFieldMoveTypePowerBoostAbAttr {}
|
|
|
|
/**
|
|
* Boosts the power of moves in specified categories.
|
|
* @extends FieldMovePowerBoostAbAttr
|
|
*/
|
|
export class AllyMoveCategoryPowerBoostAbAttr extends FieldMovePowerBoostAbAttr {
|
|
/**
|
|
* @param boostedCategories - The categories of moves that will receive the power boost.
|
|
* @param powerMultiplier - The multiplier to apply to the move's power.
|
|
*/
|
|
constructor(boostedCategories: MoveCategory[], powerMultiplier: number) {
|
|
super((_pokemon, _defender, move) => boostedCategories.includes(move.category), powerMultiplier);
|
|
}
|
|
}
|
|
|
|
export class StatMultiplierAbAttr extends AbAttr {
|
|
private stat: BattleStat;
|
|
private multiplier: number;
|
|
private condition: PokemonAttackCondition | null;
|
|
|
|
constructor(stat: BattleStat, multiplier: number, condition?: PokemonAttackCondition) {
|
|
super(false);
|
|
|
|
this.stat = stat;
|
|
this.multiplier = multiplier;
|
|
this.condition = condition ?? null;
|
|
}
|
|
|
|
canApplyStatStage(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
stat: BattleStat,
|
|
_statValue: NumberHolder,
|
|
args: any[],
|
|
): boolean {
|
|
const move = args[0] as Move;
|
|
return stat === this.stat && (!this.condition || this.condition(pokemon, null, move));
|
|
}
|
|
|
|
applyStatStage(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_stat: BattleStat,
|
|
statValue: NumberHolder,
|
|
_args: any[],
|
|
): void {
|
|
statValue.value *= this.multiplier;
|
|
}
|
|
}
|
|
|
|
export class PostAttackAbAttr extends AbAttr {
|
|
private attackCondition: PokemonAttackCondition;
|
|
|
|
/** The default attackCondition requires that the selected move is a damaging move */
|
|
constructor(
|
|
attackCondition: PokemonAttackCondition = (_user, _target, move) => move.category !== MoveCategory.STATUS,
|
|
showAbility = true,
|
|
) {
|
|
super(showAbility);
|
|
|
|
this.attackCondition = attackCondition;
|
|
}
|
|
|
|
/**
|
|
* By default, this method checks that the move used is a damaging attack before
|
|
* applying the effect of any inherited class. This can be changed by providing a different {@link attackCondition} to the constructor. See {@link ConfusionOnStatusEffectAbAttr}
|
|
* for an example of an effect that does not require a damaging move.
|
|
*/
|
|
canApplyPostAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
defender: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): boolean {
|
|
// When attackRequired is true, we require the move to be an attack move and to deal damage before checking secondary requirements.
|
|
// If attackRequired is false, we always defer to the secondary requirements.
|
|
return this.attackCondition(pokemon, defender, move);
|
|
}
|
|
|
|
applyPostAttack(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_defender: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult | null,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
/**
|
|
* Multiplies a Stat from an ally pokemon's ability.
|
|
* @see {@link applyAllyStatMultiplierAbAttrs}
|
|
* @see {@link applyAllyStat}
|
|
*/
|
|
export class AllyStatMultiplierAbAttr extends AbAttr {
|
|
private stat: BattleStat;
|
|
private multiplier: number;
|
|
private ignorable: boolean;
|
|
|
|
/**
|
|
* @param stat - The stat being modified
|
|
* @param multipler - The multiplier to apply to the stat
|
|
* @param ignorable - Whether the multiplier can be ignored by mold breaker-like moves and abilities
|
|
*/
|
|
constructor(stat: BattleStat, multiplier: number, ignorable = true) {
|
|
super(false);
|
|
|
|
this.stat = stat;
|
|
this.multiplier = multiplier;
|
|
this.ignorable = ignorable;
|
|
}
|
|
|
|
/**
|
|
* Multiply a Pokemon's Stat due to an Ally's ability.
|
|
* @param _pokemon - The ally {@linkcode Pokemon} with the ability (unused)
|
|
* @param passive - unused
|
|
* @param _simulated - Whether the ability is being simulated (unused)
|
|
* @param _stat - The type of the checked {@linkcode Stat} (unused)
|
|
* @param statValue - {@linkcode NumberHolder} containing the value of the checked stat
|
|
* @param _checkedPokemon - The {@linkcode Pokemon} this ability is targeting (unused)
|
|
* @param _ignoreAbility - Whether the ability should be ignored if possible
|
|
* @param _args - unused
|
|
* @returns `true` if this changed the checked stat, `false` otherwise.
|
|
*/
|
|
applyAllyStat(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_stat: BattleStat,
|
|
statValue: NumberHolder,
|
|
_checkedPokemon: Pokemon,
|
|
_ignoreAbility: boolean,
|
|
_args: any[],
|
|
) {
|
|
statValue.value *= this.multiplier;
|
|
}
|
|
|
|
/**
|
|
* Check if this ability can apply to the checked stat.
|
|
* @param _pokemon - The ally {@linkcode Pokemon} with the ability (unused)
|
|
* @param passive - unused
|
|
* @param _simulated - Whether the ability is being simulated (unused)
|
|
* @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} this ability is targeting (unused)
|
|
* @param ignoreAbility - Whether the ability should be ignored if possible
|
|
* @param _args - unused
|
|
* @returns `true` if this can apply to the checked stat, `false` otherwise.
|
|
*/
|
|
canApplyAllyStat(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
stat: BattleStat,
|
|
_statValue: NumberHolder,
|
|
_checkedPokemon: Pokemon,
|
|
ignoreAbility: boolean,
|
|
_args: any[],
|
|
): boolean {
|
|
return stat === this.stat && !(ignoreAbility && this.ignorable);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Takes effect whenever a move succesfully executes, such as gorilla tactics' move-locking.
|
|
* (More specifically, whenever a move is pushed to the move history)
|
|
*/
|
|
export class ExecutedMoveAbAttr extends AbAttr {
|
|
canApplyExecutedMove(_pokemon: Pokemon, _simulated: boolean): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyExecutedMove(_pokemon: Pokemon, _simulated: boolean): void {}
|
|
}
|
|
|
|
/**
|
|
* Ability attribute for {@linkcode AbilityId.GORILLA_TACTICS | Gorilla Tactics}
|
|
* to lock the user into its first selected move.
|
|
*/
|
|
export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr {
|
|
constructor(showAbility = false) {
|
|
super(showAbility);
|
|
}
|
|
|
|
override canApplyExecutedMove(pokemon: Pokemon, _simulated: boolean): boolean {
|
|
return !pokemon.getTag(BattlerTagType.GORILLA_TACTICS);
|
|
}
|
|
|
|
override applyExecutedMove(pokemon: Pokemon, simulated: boolean): void {
|
|
if (!simulated) {
|
|
pokemon.addTag(BattlerTagType.GORILLA_TACTICS);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
|
|
private stealCondition: PokemonAttackCondition | null;
|
|
private stolenItem?: PokemonHeldItemModifier;
|
|
|
|
constructor(stealCondition?: PokemonAttackCondition) {
|
|
super();
|
|
|
|
this.stealCondition = stealCondition ?? null;
|
|
}
|
|
|
|
override canApplyPostAttack(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
defender: Pokemon,
|
|
move: Move,
|
|
hitResult: HitResult,
|
|
args: any[],
|
|
): boolean {
|
|
if (
|
|
super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) &&
|
|
!simulated &&
|
|
hitResult < HitResult.NO_EFFECT &&
|
|
(!this.stealCondition || this.stealCondition(pokemon, defender, move))
|
|
) {
|
|
const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable);
|
|
if (heldItems.length) {
|
|
// Ensure that the stolen item in testing is the same as when the effect is applied
|
|
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
|
|
if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
this.stolenItem = undefined;
|
|
return false;
|
|
}
|
|
|
|
override applyPostAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
defender: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable);
|
|
if (!this.stolenItem) {
|
|
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
|
|
}
|
|
if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) {
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("abilityTriggers:postAttackStealHeldItem", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
defenderName: defender.name,
|
|
stolenItemType: this.stolenItem.type.name,
|
|
}),
|
|
);
|
|
}
|
|
this.stolenItem = undefined;
|
|
}
|
|
|
|
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
|
return globalScene.findModifiers(
|
|
m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id,
|
|
target.isPlayer(),
|
|
) as PokemonHeldItemModifier[];
|
|
}
|
|
}
|
|
|
|
export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
|
|
private contactRequired: boolean;
|
|
private chance: number;
|
|
private effects: StatusEffect[];
|
|
|
|
constructor(contactRequired: boolean, chance: number, ...effects: StatusEffect[]) {
|
|
super();
|
|
|
|
this.contactRequired = contactRequired;
|
|
this.chance = chance;
|
|
this.effects = effects;
|
|
}
|
|
|
|
override canApplyPostAttack(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
hitResult: HitResult | null,
|
|
args: any[],
|
|
): boolean {
|
|
if (
|
|
super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) &&
|
|
(simulated ||
|
|
(!attacker.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") &&
|
|
pokemon !== attacker &&
|
|
(!this.contactRequired ||
|
|
move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon })) &&
|
|
pokemon.randBattleSeedInt(100) < this.chance &&
|
|
!pokemon.status))
|
|
) {
|
|
const effect =
|
|
this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)];
|
|
return simulated || attacker.canSetStatus(effect, true, false, pokemon);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
applyPostAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
const effect =
|
|
this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)];
|
|
attacker.trySetStatus(effect, true, pokemon);
|
|
}
|
|
}
|
|
|
|
export class PostAttackContactApplyStatusEffectAbAttr extends PostAttackApplyStatusEffectAbAttr {
|
|
constructor(chance: number, ...effects: StatusEffect[]) {
|
|
super(true, chance, ...effects);
|
|
}
|
|
}
|
|
|
|
export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
|
|
private contactRequired: boolean;
|
|
private chance: (user: Pokemon, target: Pokemon, move: Move) => number;
|
|
private effects: BattlerTagType[];
|
|
|
|
constructor(
|
|
contactRequired: boolean,
|
|
chance: (user: Pokemon, target: Pokemon, move: Move) => number,
|
|
...effects: BattlerTagType[]
|
|
) {
|
|
super(undefined, false);
|
|
|
|
this.contactRequired = contactRequired;
|
|
this.chance = chance;
|
|
this.effects = effects;
|
|
}
|
|
|
|
override canApplyPostAttack(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
hitResult: HitResult | null,
|
|
args: any[],
|
|
): boolean {
|
|
/**Battler tags inflicted by abilities post attacking are also considered additional effects.*/
|
|
return (
|
|
super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) &&
|
|
!attacker.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") &&
|
|
pokemon !== attacker &&
|
|
(!this.contactRequired ||
|
|
move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon })) &&
|
|
pokemon.randBattleSeedInt(100) < this.chance(attacker, pokemon, move) &&
|
|
!pokemon.status
|
|
);
|
|
}
|
|
|
|
override applyPostAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
const effect =
|
|
this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)];
|
|
attacker.addTag(effect);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
|
|
private condition?: PokemonDefendCondition;
|
|
private stolenItem?: PokemonHeldItemModifier;
|
|
|
|
constructor(condition?: PokemonDefendCondition) {
|
|
super();
|
|
|
|
this.condition = condition;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
hitResult: HitResult,
|
|
_args: any[],
|
|
): boolean {
|
|
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) {
|
|
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable);
|
|
if (heldItems.length) {
|
|
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
|
|
if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable);
|
|
if (!this.stolenItem) {
|
|
this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)];
|
|
}
|
|
if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) {
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("abilityTriggers:postDefendStealHeldItem", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
attackerName: attacker.name,
|
|
stolenItemType: this.stolenItem.type.name,
|
|
}),
|
|
);
|
|
}
|
|
this.stolenItem = undefined;
|
|
}
|
|
|
|
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
|
return globalScene.findModifiers(
|
|
m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id,
|
|
target.isPlayer(),
|
|
) as PokemonHeldItemModifier[];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Base class for defining all {@linkcode Ability} Attributes after a status effect has been set.
|
|
* @see {@linkcode applyPostSetStatus()}.
|
|
*/
|
|
export class PostSetStatusAbAttr extends AbAttr {
|
|
canApplyPostSetStatus(
|
|
_pokemon: Pokemon,
|
|
_sourcePokemon: Pokemon | null = null,
|
|
_passive: boolean,
|
|
_effect: StatusEffect,
|
|
_simulated: boolean,
|
|
_rgs: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Does nothing after a status condition is set.
|
|
* @param _pokemon {@linkcode Pokemon} that status condition was set on.
|
|
* @param _sourcePokemon {@linkcode Pokemon} that that set the status condition. Is `null` if status was not set by a Pokemon.
|
|
* @param _passive Whether this ability is a passive.
|
|
* @param _effect {@linkcode StatusEffect} that was set.
|
|
* @param _args Set of unique arguments needed by this attribute.
|
|
*/
|
|
applyPostSetStatus(
|
|
_pokemon: Pokemon,
|
|
_sourcePokemon: Pokemon | null = null,
|
|
_passive: boolean,
|
|
_effect: StatusEffect,
|
|
_simulated: boolean,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
/**
|
|
* If another Pokemon burns, paralyzes, poisons, or badly poisons this Pokemon,
|
|
* that Pokemon receives the same non-volatile status condition as part of this
|
|
* ability attribute. For Synchronize ability.
|
|
*/
|
|
export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr {
|
|
override canApplyPostSetStatus(
|
|
_pokemon: Pokemon,
|
|
sourcePokemon: (Pokemon | null) | undefined,
|
|
_passive: boolean,
|
|
effect: StatusEffect,
|
|
_simulated: boolean,
|
|
_args: any[],
|
|
): boolean {
|
|
/** Synchronizable statuses */
|
|
const syncStatuses = new Set<StatusEffect>([
|
|
StatusEffect.BURN,
|
|
StatusEffect.PARALYSIS,
|
|
StatusEffect.POISON,
|
|
StatusEffect.TOXIC,
|
|
]);
|
|
|
|
// synchronize does not need to check canSetStatus because the ability shows even if it fails to set the status
|
|
return (sourcePokemon ?? false) && syncStatuses.has(effect);
|
|
}
|
|
|
|
/**
|
|
* If the `StatusEffect` that was set is Burn, Paralysis, Poison, or Toxic, and the status
|
|
* was set by a source Pokemon, set the source Pokemon's status to the same `StatusEffect`.
|
|
* @param pokemon {@linkcode Pokemon} that status condition was set on.
|
|
* @param sourcePokemon {@linkcode Pokemon} that that set the status condition. Is null if status was not set by a Pokemon.
|
|
* @param _passive Whether this ability is a passive.
|
|
* @param effect {@linkcode StatusEffect} that was set.
|
|
* @param _args Set of unique arguments needed by this attribute.
|
|
*/
|
|
override applyPostSetStatus(
|
|
pokemon: Pokemon,
|
|
sourcePokemon: Pokemon | null = null,
|
|
_passive: boolean,
|
|
effect: StatusEffect,
|
|
simulated: boolean,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated && sourcePokemon) {
|
|
sourcePokemon.trySetStatus(effect, true, pokemon);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostVictoryAbAttr extends AbAttr {
|
|
canApplyPostVictory(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPostVictory(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {}
|
|
}
|
|
|
|
class PostVictoryStatStageChangeAbAttr extends PostVictoryAbAttr {
|
|
private stat: BattleStat | ((p: Pokemon) => BattleStat);
|
|
private stages: number;
|
|
|
|
constructor(stat: BattleStat | ((p: Pokemon) => BattleStat), stages: number) {
|
|
super();
|
|
|
|
this.stat = stat;
|
|
this.stages = stages;
|
|
}
|
|
|
|
override applyPostVictory(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat;
|
|
if (!simulated) {
|
|
globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [stat], this.stages);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostVictoryFormChangeAbAttr extends PostVictoryAbAttr {
|
|
private formFunc: (p: Pokemon) => number;
|
|
|
|
constructor(formFunc: (p: Pokemon) => number) {
|
|
super(true);
|
|
|
|
this.formFunc = formFunc;
|
|
}
|
|
|
|
override canApplyPostVictory(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
const formIndex = this.formFunc(pokemon);
|
|
return formIndex !== pokemon.formIndex;
|
|
}
|
|
|
|
override applyPostVictory(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostKnockOutAbAttr extends AbAttr {
|
|
canApplyPostKnockOut(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_knockedOut: Pokemon,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPostKnockOut(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_knockedOut: Pokemon,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
export class PostKnockOutStatStageChangeAbAttr extends PostKnockOutAbAttr {
|
|
private stat: BattleStat | ((p: Pokemon) => BattleStat);
|
|
private stages: number;
|
|
|
|
constructor(stat: BattleStat | ((p: Pokemon) => BattleStat), stages: number) {
|
|
super();
|
|
|
|
this.stat = stat;
|
|
this.stages = stages;
|
|
}
|
|
|
|
override applyPostKnockOut(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_knockedOut: Pokemon,
|
|
_args: any[],
|
|
): void {
|
|
const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat;
|
|
if (!simulated) {
|
|
globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [stat], this.stages);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class CopyFaintedAllyAbilityAbAttr extends PostKnockOutAbAttr {
|
|
override canApplyPostKnockOut(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
knockedOut: Pokemon,
|
|
_args: any[],
|
|
): boolean {
|
|
return pokemon.isPlayer() === knockedOut.isPlayer() && knockedOut.getAbility().isCopiable;
|
|
}
|
|
|
|
override applyPostKnockOut(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
knockedOut: Pokemon,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
pokemon.setTempAbility(knockedOut.getAbility());
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("abilityTriggers:copyFaintedAllyAbility", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut),
|
|
abilityName: allAbilities[knockedOut.getAbility().id].name,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ability attribute for ignoring the opponent's stat changes
|
|
* @param stats the stats that should be ignored
|
|
*/
|
|
export class IgnoreOpponentStatStagesAbAttr extends AbAttr {
|
|
private stats: readonly BattleStat[];
|
|
|
|
constructor(stats?: BattleStat[]) {
|
|
super(false);
|
|
|
|
this.stats = stats ?? BATTLE_STATS;
|
|
}
|
|
|
|
override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
|
return this.stats.includes(args[0]);
|
|
}
|
|
|
|
/**
|
|
* Modifies a BooleanHolder and returns the result to see if a stat is ignored or not
|
|
* @param _pokemon n/a
|
|
* @param _passive n/a
|
|
* @param _simulated n/a
|
|
* @param _cancelled n/a
|
|
* @param args A BooleanHolder that represents whether or not to ignore a stat's stat changes
|
|
*/
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[1] as BooleanHolder).value = true;
|
|
}
|
|
}
|
|
|
|
export class IntimidateImmunityAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:intimidateImmunity", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class PostIntimidateStatStageChangeAbAttr extends AbAttr {
|
|
private stats: BattleStat[];
|
|
private stages: number;
|
|
private overwrites: boolean;
|
|
|
|
constructor(stats: BattleStat[], stages: number, overwrites?: boolean) {
|
|
super(true);
|
|
this.stats = stats;
|
|
this.stages = stages;
|
|
this.overwrites = !!overwrites;
|
|
}
|
|
|
|
override apply(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.phaseManager.pushNew(
|
|
"StatStageChangePhase",
|
|
pokemon.getBattlerIndex(),
|
|
false,
|
|
this.stats,
|
|
this.stages,
|
|
);
|
|
}
|
|
cancelled.value = this.overwrites;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Base class for defining all {@linkcode Ability} Attributes post summon
|
|
* @see {@linkcode applyPostSummon()}
|
|
*/
|
|
export class PostSummonAbAttr extends AbAttr {
|
|
/** Should the ability activate when gained in battle? This will almost always be true */
|
|
private activateOnGain: boolean;
|
|
|
|
constructor(showAbility = true, activateOnGain = true) {
|
|
super(showAbility);
|
|
this.activateOnGain = activateOnGain;
|
|
}
|
|
|
|
/**
|
|
* @returns Whether the ability should activate when gained in battle
|
|
*/
|
|
shouldActivateOnGain(): boolean {
|
|
return this.activateOnGain;
|
|
}
|
|
|
|
canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Applies ability post summon (after switching in)
|
|
* @param _pokemon {@linkcode Pokemon} with this ability
|
|
* @param _passive Whether this ability is a passive
|
|
* @param _args Set of unique arguments needed by this attribute
|
|
*/
|
|
applyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {}
|
|
}
|
|
|
|
/**
|
|
* Base class for ability attributes which remove an effect on summon
|
|
*/
|
|
export class PostSummonRemoveEffectAbAttr extends PostSummonAbAttr {}
|
|
|
|
/**
|
|
* Removes specified arena tags when a Pokemon is summoned.
|
|
*/
|
|
export class PostSummonRemoveArenaTagAbAttr extends PostSummonAbAttr {
|
|
private arenaTags: ArenaTagType[];
|
|
|
|
/**
|
|
* @param arenaTags {@linkcode ArenaTagType[]} - the arena tags to be removed
|
|
*/
|
|
constructor(arenaTags: ArenaTagType[]) {
|
|
super(true);
|
|
|
|
this.arenaTags = arenaTags;
|
|
}
|
|
|
|
override canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return globalScene.arena.tags.some(tag => this.arenaTags.includes(tag.tagType));
|
|
}
|
|
|
|
override applyPostSummon(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
for (const arenaTag of this.arenaTags) {
|
|
globalScene.arena.removeTag(arenaTag);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generic class to add an arena tag upon switching in
|
|
*/
|
|
export class PostSummonAddArenaTagAbAttr extends PostSummonAbAttr {
|
|
private readonly tagType: ArenaTagType;
|
|
private readonly turnCount: number;
|
|
private readonly side?: ArenaTagSide;
|
|
private readonly quiet?: boolean;
|
|
private sourceId: number;
|
|
|
|
constructor(showAbility: boolean, tagType: ArenaTagType, turnCount: number, side?: ArenaTagSide, quiet?: boolean) {
|
|
super(showAbility);
|
|
this.tagType = tagType;
|
|
this.turnCount = turnCount;
|
|
this.side = side;
|
|
this.quiet = quiet;
|
|
}
|
|
|
|
public override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
this.sourceId = pokemon.id;
|
|
if (!simulated) {
|
|
globalScene.arena.addTag(this.tagType, this.turnCount, undefined, this.sourceId, this.side, this.quiet);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostSummonMessageAbAttr extends PostSummonAbAttr {
|
|
private messageFunc: (pokemon: Pokemon) => string;
|
|
|
|
constructor(messageFunc: (pokemon: Pokemon) => string) {
|
|
super(true);
|
|
|
|
this.messageFunc = messageFunc;
|
|
}
|
|
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
globalScene.phaseManager.queueMessage(this.messageFunc(pokemon));
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr {
|
|
//Attr doesn't force pokemon name on the message
|
|
private message: string;
|
|
|
|
constructor(message: string) {
|
|
super(true);
|
|
|
|
this.message = message;
|
|
}
|
|
|
|
override applyPostSummon(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
globalScene.phaseManager.queueMessage(this.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostSummonAddBattlerTagAbAttr extends PostSummonAbAttr {
|
|
private tagType: BattlerTagType;
|
|
private turnCount: number;
|
|
|
|
constructor(tagType: BattlerTagType, turnCount: number, showAbility?: boolean) {
|
|
super(showAbility);
|
|
|
|
this.tagType = tagType;
|
|
this.turnCount = turnCount;
|
|
}
|
|
|
|
override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return pokemon.canAddTag(this.tagType);
|
|
}
|
|
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
pokemon.addTag(this.tagType, this.turnCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes Specific battler tags when a Pokemon is summoned
|
|
*
|
|
* This should realistically only ever activate on gain rather than on summon
|
|
*/
|
|
export class PostSummonRemoveBattlerTagAbAttr extends PostSummonRemoveEffectAbAttr {
|
|
private immuneTags: BattlerTagType[];
|
|
|
|
/**
|
|
* @param immuneTags - The {@linkcode BattlerTagType | battler tags} the Pokémon is immune to.
|
|
*/
|
|
constructor(...immuneTags: BattlerTagType[]) {
|
|
super();
|
|
this.immuneTags = immuneTags;
|
|
}
|
|
|
|
public override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return this.immuneTags.some(tagType => !!pokemon.getTag(tagType));
|
|
}
|
|
|
|
public override applyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
|
this.immuneTags.forEach(tagType => pokemon.removeTag(tagType));
|
|
}
|
|
}
|
|
|
|
export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr {
|
|
private stats: BattleStat[];
|
|
private stages: number;
|
|
private selfTarget: boolean;
|
|
private intimidate: boolean;
|
|
|
|
constructor(stats: BattleStat[], stages: number, selfTarget?: boolean, intimidate?: boolean) {
|
|
super(true);
|
|
|
|
this.stats = stats;
|
|
this.stages = stages;
|
|
this.selfTarget = !!selfTarget;
|
|
this.intimidate = !!intimidate;
|
|
}
|
|
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (simulated) {
|
|
return;
|
|
}
|
|
|
|
if (this.selfTarget) {
|
|
// we unshift the StatStageChangePhase to put it right after the showAbility and not at the end of the
|
|
// phase list (which could be after CommandPhase for example)
|
|
globalScene.phaseManager.unshiftNew(
|
|
"StatStageChangePhase",
|
|
pokemon.getBattlerIndex(),
|
|
true,
|
|
this.stats,
|
|
this.stages,
|
|
);
|
|
} else {
|
|
for (const opponent of pokemon.getOpponents()) {
|
|
const cancelled = new BooleanHolder(false);
|
|
if (this.intimidate) {
|
|
applyAbAttrs("IntimidateImmunityAbAttr", opponent, cancelled, simulated);
|
|
applyAbAttrs("PostIntimidateStatStageChangeAbAttr", opponent, cancelled, simulated);
|
|
|
|
if (opponent.getTag(BattlerTagType.SUBSTITUTE)) {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
if (!cancelled.value) {
|
|
globalScene.phaseManager.unshiftNew(
|
|
"StatStageChangePhase",
|
|
opponent.getBattlerIndex(),
|
|
false,
|
|
this.stats,
|
|
this.stages,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostSummonAllyHealAbAttr extends PostSummonAbAttr {
|
|
private healRatio: number;
|
|
private showAnim: boolean;
|
|
|
|
constructor(healRatio: number, showAnim = false) {
|
|
super();
|
|
|
|
this.healRatio = healRatio || 4;
|
|
this.showAnim = showAnim;
|
|
}
|
|
|
|
override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return pokemon.getAlly()?.isActive(true) ?? false;
|
|
}
|
|
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
const target = pokemon.getAlly();
|
|
if (!simulated && !isNullOrUndefined(target)) {
|
|
globalScene.phaseManager.unshiftNew(
|
|
"PokemonHealPhase",
|
|
target.getBattlerIndex(),
|
|
toDmgValue(pokemon.getMaxHp() / this.healRatio),
|
|
i18next.t("abilityTriggers:postSummonAllyHeal", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(target),
|
|
pokemonName: pokemon.name,
|
|
}),
|
|
true,
|
|
!this.showAnim,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets an ally's temporary stat boots to zero with no regard to
|
|
* whether this is a positive or negative change
|
|
* @param pokemon The {@link Pokemon} with this {@link AbAttr}
|
|
* @param passive N/A
|
|
* @param args N/A
|
|
* @returns if the move was successful
|
|
*/
|
|
export class PostSummonClearAllyStatStagesAbAttr extends PostSummonAbAttr {
|
|
override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return pokemon.getAlly()?.isActive(true) ?? false;
|
|
}
|
|
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
const target = pokemon.getAlly();
|
|
if (!simulated && !isNullOrUndefined(target)) {
|
|
for (const s of BATTLE_STATS) {
|
|
target.setStatStage(s, 0);
|
|
}
|
|
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("abilityTriggers:postSummonClearAllyStats", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(target),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Download raises either the Attack stat or Special Attack stat by one stage depending on the foe's currently lowest defensive stat:
|
|
* it will raise Attack if the foe's current Defense is lower than its current Special Defense stat;
|
|
* otherwise, it will raise Special Attack.
|
|
* @extends PostSummonAbAttr
|
|
* @see {applyPostSummon}
|
|
*/
|
|
export class DownloadAbAttr extends PostSummonAbAttr {
|
|
private enemyDef: number;
|
|
private enemySpDef: number;
|
|
private enemyCountTally: number;
|
|
private stats: BattleStat[];
|
|
|
|
override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
this.enemyDef = 0;
|
|
this.enemySpDef = 0;
|
|
this.enemyCountTally = 0;
|
|
|
|
for (const opponent of pokemon.getOpponents()) {
|
|
this.enemyCountTally++;
|
|
this.enemyDef += opponent.getEffectiveStat(Stat.DEF);
|
|
this.enemySpDef += opponent.getEffectiveStat(Stat.SPDEF);
|
|
}
|
|
this.enemyDef = Math.round(this.enemyDef / this.enemyCountTally);
|
|
this.enemySpDef = Math.round(this.enemySpDef / this.enemyCountTally);
|
|
return this.enemyDef > 0 && this.enemySpDef > 0;
|
|
}
|
|
|
|
/**
|
|
* Checks to see if it is the opening turn (starting a new game), if so, Download won't work. This is because Download takes into account
|
|
* vitamins and items, so it needs to use the Stat and the stat alone.
|
|
* @param {Pokemon} pokemon Pokemon that is using the move, as well as seeing the opposing pokemon.
|
|
* @param {boolean} _passive N/A
|
|
* @param {any[]} _args N/A
|
|
*/
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (this.enemyDef < this.enemySpDef) {
|
|
this.stats = [Stat.ATK];
|
|
} else {
|
|
this.stats = [Stat.SPATK];
|
|
}
|
|
|
|
if (!simulated) {
|
|
globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), false, this.stats, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostSummonWeatherChangeAbAttr extends PostSummonAbAttr {
|
|
private weatherType: WeatherType;
|
|
|
|
constructor(weatherType: WeatherType) {
|
|
super();
|
|
|
|
this.weatherType = weatherType;
|
|
}
|
|
|
|
override canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
const weatherReplaceable =
|
|
this.weatherType === WeatherType.HEAVY_RAIN ||
|
|
this.weatherType === WeatherType.HARSH_SUN ||
|
|
this.weatherType === WeatherType.STRONG_WINDS ||
|
|
!globalScene.arena.weather?.isImmutable();
|
|
return weatherReplaceable && globalScene.arena.canSetWeather(this.weatherType);
|
|
}
|
|
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
globalScene.arena.trySetWeather(this.weatherType, pokemon);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostSummonTerrainChangeAbAttr extends PostSummonAbAttr {
|
|
private terrainType: TerrainType;
|
|
|
|
constructor(terrainType: TerrainType) {
|
|
super();
|
|
|
|
this.terrainType = terrainType;
|
|
}
|
|
|
|
override canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return globalScene.arena.canSetTerrain(this.terrainType);
|
|
}
|
|
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
globalScene.arena.trySetTerrain(this.terrainType, false, pokemon);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Heals a status effect if the Pokemon is afflicted with it upon switch in (or gain)
|
|
*/
|
|
export class PostSummonHealStatusAbAttr extends PostSummonRemoveEffectAbAttr {
|
|
private immuneEffects: StatusEffect[];
|
|
private statusHealed: StatusEffect;
|
|
|
|
/**
|
|
* @param immuneEffects - The {@linkcode StatusEffect}s the Pokémon is immune to.
|
|
*/
|
|
constructor(...immuneEffects: StatusEffect[]) {
|
|
super();
|
|
this.immuneEffects = immuneEffects;
|
|
}
|
|
|
|
public override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
const status = pokemon.status?.effect;
|
|
return !isNullOrUndefined(status) && (this.immuneEffects.length < 1 || this.immuneEffects.includes(status));
|
|
}
|
|
|
|
public override applyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
|
const status = pokemon.status?.effect;
|
|
if (!isNullOrUndefined(status)) {
|
|
this.statusHealed = status;
|
|
pokemon.resetStatus(false);
|
|
pokemon.updateInfo();
|
|
}
|
|
}
|
|
|
|
public override getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null {
|
|
if (this.statusHealed) {
|
|
return getStatusEffectHealText(this.statusHealed, getPokemonNameWithAffix(_pokemon));
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export class PostSummonFormChangeAbAttr extends PostSummonAbAttr {
|
|
private formFunc: (p: Pokemon) => number;
|
|
|
|
constructor(formFunc: (p: Pokemon) => number) {
|
|
super(true);
|
|
|
|
this.formFunc = formFunc;
|
|
}
|
|
|
|
override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return this.formFunc(pokemon) !== pokemon.formIndex;
|
|
}
|
|
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Attempts to copy a pokemon's ability */
|
|
export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr {
|
|
private target: Pokemon;
|
|
private targetAbilityName: string;
|
|
|
|
override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
const targets = pokemon.getOpponents();
|
|
if (!targets.length) {
|
|
return false;
|
|
}
|
|
|
|
let target: Pokemon;
|
|
if (targets.length > 1) {
|
|
globalScene.executeWithSeedOffset(() => (target = randSeedItem(targets)), globalScene.currentBattle.waveIndex);
|
|
} else {
|
|
target = targets[0];
|
|
}
|
|
|
|
if (
|
|
!target!.getAbility().isCopiable &&
|
|
// Wonder Guard is normally uncopiable so has the attribute, but Trace specifically can copy it
|
|
!(pokemon.hasAbility(AbilityId.TRACE) && target!.getAbility().id === AbilityId.WONDER_GUARD)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
this.target = target!;
|
|
this.targetAbilityName = allAbilities[target!.getAbility().id].name;
|
|
return true;
|
|
}
|
|
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
pokemon.setTempAbility(this.target!.getAbility());
|
|
setAbilityRevealed(this.target!);
|
|
pokemon.updateInfo();
|
|
}
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:trace", {
|
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
|
targetName: getPokemonNameWithAffix(this.target),
|
|
abilityName: this.targetAbilityName,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes supplied status effects from the user's field.
|
|
*/
|
|
export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAttr {
|
|
private statusEffect: StatusEffect[];
|
|
|
|
/**
|
|
* @param statusEffect - The status effects to be removed from the user's field.
|
|
*/
|
|
constructor(...statusEffect: StatusEffect[]) {
|
|
super(false);
|
|
|
|
this.statusEffect = statusEffect;
|
|
}
|
|
|
|
override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
const party = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
|
return party.filter(p => p.isAllowedInBattle()).length > 0;
|
|
}
|
|
|
|
/**
|
|
* Removes supplied status effect from the user's field when user of the ability is summoned.
|
|
*
|
|
* @param pokemon - The Pokémon that triggered the ability.
|
|
* @param _passive - n/a
|
|
* @param _args - n/a
|
|
*/
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
const party = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
|
const allowedParty = party.filter(p => p.isAllowedInBattle());
|
|
|
|
if (!simulated) {
|
|
for (const pokemon of allowedParty) {
|
|
if (pokemon.status && this.statusEffect.includes(pokemon.status.effect)) {
|
|
globalScene.phaseManager.queueMessage(
|
|
getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)),
|
|
);
|
|
pokemon.resetStatus(false);
|
|
pokemon.updateInfo();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Attempt to copy the stat changes on an ally pokemon */
|
|
export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr {
|
|
override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
if (!globalScene.currentBattle.double) {
|
|
return false;
|
|
}
|
|
|
|
const ally = pokemon.getAlly();
|
|
return !(isNullOrUndefined(ally) || ally.getStatStages().every(s => s === 0));
|
|
}
|
|
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
const ally = pokemon.getAlly();
|
|
if (!simulated && !isNullOrUndefined(ally)) {
|
|
for (const s of BATTLE_STATS) {
|
|
pokemon.setStatStage(s, ally.getStatStage(s));
|
|
}
|
|
pokemon.updateInfo();
|
|
}
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:costar", {
|
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
|
allyName: getPokemonNameWithAffix(pokemon.getAlly()),
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used by {@linkcode AbilityId.IMPOSTER} to transform into a random opposing pokemon on entry.
|
|
*/
|
|
export class PostSummonTransformAbAttr extends PostSummonAbAttr {
|
|
constructor() {
|
|
super(true, false);
|
|
}
|
|
|
|
private getTarget(targets: Pokemon[]): Pokemon {
|
|
let target: Pokemon = targets[0];
|
|
if (targets.length > 1) {
|
|
globalScene.executeWithSeedOffset(() => {
|
|
// in a double battle, if one of the opposing pokemon is fused the other one will be chosen
|
|
// if both are fused, then Imposter will fail below
|
|
if (targets[0].fusionSpecies) {
|
|
target = targets[1];
|
|
return;
|
|
}
|
|
if (targets[1].fusionSpecies) {
|
|
target = targets[0];
|
|
return;
|
|
}
|
|
target = randSeedItem(targets);
|
|
}, globalScene.currentBattle.waveIndex);
|
|
} else {
|
|
target = targets[0];
|
|
}
|
|
|
|
target = target!;
|
|
|
|
return target;
|
|
}
|
|
|
|
override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean {
|
|
const targets = pokemon.getOpponents();
|
|
const target = this.getTarget(targets);
|
|
|
|
if (target.summonData.illusion) {
|
|
return false;
|
|
}
|
|
|
|
if (simulated || !targets.length) {
|
|
return simulated;
|
|
}
|
|
|
|
// transforming from or into fusion pokemon causes various problems (including crashes and save corruption)
|
|
return !(this.getTarget(targets).fusionSpecies || pokemon.fusionSpecies);
|
|
}
|
|
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
|
const target = this.getTarget(pokemon.getOpponents());
|
|
|
|
globalScene.phaseManager.unshiftNew(
|
|
"PokemonTransformPhase",
|
|
pokemon.getBattlerIndex(),
|
|
target.getBattlerIndex(),
|
|
true,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reverts weather-based forms to their normal forms when the user is summoned.
|
|
* Used by Cloud Nine and Air Lock.
|
|
* @extends PostSummonAbAttr
|
|
*/
|
|
export class PostSummonWeatherSuppressedFormChangeAbAttr extends PostSummonAbAttr {
|
|
override canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return getPokemonWithWeatherBasedForms().length > 0;
|
|
}
|
|
|
|
/**
|
|
* Triggers {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal}
|
|
* @param {Pokemon} _pokemon the Pokemon with this ability
|
|
* @param _passive n/a
|
|
* @param _args n/a
|
|
*/
|
|
override applyPostSummon(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
globalScene.arena.triggerWeatherBasedFormChangesToNormal();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggers weather-based form change when summoned into an active weather.
|
|
* Used by Forecast and Flower Gift.
|
|
* @extends PostSummonAbAttr
|
|
*/
|
|
export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr {
|
|
private ability: AbilityId;
|
|
|
|
constructor(ability: AbilityId) {
|
|
super(true);
|
|
|
|
this.ability = ability;
|
|
}
|
|
|
|
/**
|
|
* Determine if the pokemon has a forme change that is triggered by the weather
|
|
*
|
|
* @param pokemon - The pokemon with the forme change ability
|
|
* @param _passive - unused
|
|
* @param _simulated - unused
|
|
* @param _args - unused
|
|
*/
|
|
override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return !!pokemonFormChanges[pokemon.species.speciesId]?.some(
|
|
fc => fc.findTrigger(SpeciesFormChangeWeatherTrigger) && fc.canChange(pokemon),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Trigger the pokemon's forme change by invoking
|
|
* {@linkcode BattleScene.triggerPokemonFormChange | triggerPokemonFormChange}
|
|
*
|
|
* @param pokemon - The Pokemon with this ability
|
|
* @param _passive - unused
|
|
* @param simulated - unused
|
|
* @param _args - unused
|
|
*/
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeWeatherTrigger);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Commander_(Ability) | Commander}.
|
|
* When the source of an ability with this attribute detects a Dondozo as their active ally, the source "jumps
|
|
* into the Dondozo's mouth," sharply boosting the Dondozo's stats, cancelling the source's moves, and
|
|
* causing attacks that target the source to always miss.
|
|
*/
|
|
export class CommanderAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(true);
|
|
}
|
|
|
|
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
// If the ally Dondozo is fainted or was previously "commanded" by
|
|
// another Pokemon, this effect cannot apply.
|
|
|
|
// TODO: Should this work with X + Dondozo fusions?
|
|
const ally = pokemon.getAlly();
|
|
return (
|
|
globalScene.currentBattle?.double &&
|
|
!isNullOrUndefined(ally) &&
|
|
ally.species.speciesId === SpeciesId.DONDOZO &&
|
|
!(ally.isFainted() || ally.getTag(BattlerTagType.COMMANDED))
|
|
);
|
|
}
|
|
|
|
override apply(pokemon: Pokemon, _passive: boolean, simulated: boolean, _cancelled: null, _args: any[]): void {
|
|
if (!simulated) {
|
|
// Lapse the source's semi-invulnerable tags (to avoid visual inconsistencies)
|
|
pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
|
// Play an animation of the source jumping into the ally Dondozo's mouth
|
|
globalScene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.COMMANDER_APPLY);
|
|
// Apply boosts from this effect to the ally Dondozo
|
|
pokemon.getAlly()?.addTag(BattlerTagType.COMMANDED, 0, MoveId.NONE, pokemon.id);
|
|
// Cancel the source Pokemon's next move (if a move is queued)
|
|
globalScene.phaseManager.tryRemovePhase(phase => phase.is("MovePhase") && phase.pokemon === pokemon);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PreSwitchOutAbAttr extends AbAttr {
|
|
constructor(showAbility = true) {
|
|
super(showAbility);
|
|
}
|
|
|
|
canApplyPreSwitchOut(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPreSwitchOut(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {}
|
|
}
|
|
|
|
export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
override canApplyPreSwitchOut(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return !isNullOrUndefined(pokemon.status);
|
|
}
|
|
|
|
override applyPreSwitchOut(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
pokemon.resetStatus();
|
|
pokemon.updateInfo();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon switching out.
|
|
*/
|
|
export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr {
|
|
/**
|
|
* @param pokemon The {@linkcode Pokemon} with the ability
|
|
* @param _passive N/A
|
|
* @param _args N/A
|
|
* @returns {boolean} Returns true if the weather clears, otherwise false.
|
|
*/
|
|
override applyPreSwitchOut(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean {
|
|
const weatherType = globalScene.arena.weather?.weatherType;
|
|
let turnOffWeather = false;
|
|
|
|
// Clear weather only if user's ability matches the weather and no other pokemon has the ability.
|
|
switch (weatherType) {
|
|
case WeatherType.HARSH_SUN:
|
|
if (
|
|
pokemon.hasAbility(AbilityId.DESOLATE_LAND) &&
|
|
globalScene
|
|
.getField(true)
|
|
.filter(p => p !== pokemon)
|
|
.filter(p => p.hasAbility(AbilityId.DESOLATE_LAND)).length === 0
|
|
) {
|
|
turnOffWeather = true;
|
|
}
|
|
break;
|
|
case WeatherType.HEAVY_RAIN:
|
|
if (
|
|
pokemon.hasAbility(AbilityId.PRIMORDIAL_SEA) &&
|
|
globalScene
|
|
.getField(true)
|
|
.filter(p => p !== pokemon)
|
|
.filter(p => p.hasAbility(AbilityId.PRIMORDIAL_SEA)).length === 0
|
|
) {
|
|
turnOffWeather = true;
|
|
}
|
|
break;
|
|
case WeatherType.STRONG_WINDS:
|
|
if (
|
|
pokemon.hasAbility(AbilityId.DELTA_STREAM) &&
|
|
globalScene
|
|
.getField(true)
|
|
.filter(p => p !== pokemon)
|
|
.filter(p => p.hasAbility(AbilityId.DELTA_STREAM)).length === 0
|
|
) {
|
|
turnOffWeather = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (simulated) {
|
|
return turnOffWeather;
|
|
}
|
|
|
|
if (turnOffWeather) {
|
|
globalScene.arena.trySetWeather(WeatherType.NONE);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr {
|
|
override canApplyPreSwitchOut(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return !pokemon.isFullHp();
|
|
}
|
|
|
|
override applyPreSwitchOut(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
const healAmount = toDmgValue(pokemon.getMaxHp() * 0.33);
|
|
pokemon.heal(healAmount);
|
|
pokemon.updateInfo();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute for form changes that occur on switching out
|
|
* @extends PreSwitchOutAbAttr
|
|
* @see {@linkcode applyPreSwitchOut}
|
|
*/
|
|
export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr {
|
|
private formFunc: (p: Pokemon) => number;
|
|
|
|
constructor(formFunc: (p: Pokemon) => number) {
|
|
super();
|
|
|
|
this.formFunc = formFunc;
|
|
}
|
|
|
|
override canApplyPreSwitchOut(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return this.formFunc(pokemon) !== pokemon.formIndex;
|
|
}
|
|
|
|
/**
|
|
* On switch out, trigger the form change to the one defined in the ability
|
|
* @param pokemon The pokemon switching out and changing form {@linkcode Pokemon}
|
|
* @param _passive N/A
|
|
* @param _args N/A
|
|
*/
|
|
override applyPreSwitchOut(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PreLeaveFieldAbAttr extends AbAttr {
|
|
canApplyPreLeaveField(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPreLeaveField(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {}
|
|
}
|
|
|
|
/**
|
|
* Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon switching out.
|
|
*/
|
|
export class PreLeaveFieldClearWeatherAbAttr extends PreLeaveFieldAbAttr {
|
|
override canApplyPreLeaveField(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
const weatherType = globalScene.arena.weather?.weatherType;
|
|
// Clear weather only if user's ability matches the weather and no other pokemon has the ability.
|
|
switch (weatherType) {
|
|
case WeatherType.HARSH_SUN:
|
|
if (
|
|
pokemon.hasAbility(AbilityId.DESOLATE_LAND) &&
|
|
globalScene
|
|
.getField(true)
|
|
.filter(p => p !== pokemon)
|
|
.filter(p => p.hasAbility(AbilityId.DESOLATE_LAND)).length === 0
|
|
) {
|
|
return true;
|
|
}
|
|
break;
|
|
case WeatherType.HEAVY_RAIN:
|
|
if (
|
|
pokemon.hasAbility(AbilityId.PRIMORDIAL_SEA) &&
|
|
globalScene
|
|
.getField(true)
|
|
.filter(p => p !== pokemon)
|
|
.filter(p => p.hasAbility(AbilityId.PRIMORDIAL_SEA)).length === 0
|
|
) {
|
|
return true;
|
|
}
|
|
break;
|
|
case WeatherType.STRONG_WINDS:
|
|
if (
|
|
pokemon.hasAbility(AbilityId.DELTA_STREAM) &&
|
|
globalScene
|
|
.getField(true)
|
|
.filter(p => p !== pokemon)
|
|
.filter(p => p.hasAbility(AbilityId.DELTA_STREAM)).length === 0
|
|
) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param _pokemon The {@linkcode Pokemon} with the ability
|
|
* @param _passive N/A
|
|
* @param _args N/A
|
|
*/
|
|
override applyPreLeaveField(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
globalScene.arena.trySetWeather(WeatherType.NONE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the active {@linkcode SuppressAbilitiesTag} when a pokemon with {@linkcode AbilityId.NEUTRALIZING_GAS} leaves the field
|
|
*/
|
|
export class PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr extends PreLeaveFieldAbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
public override canApplyPreLeaveField(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_args: any[],
|
|
): boolean {
|
|
return !!globalScene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS);
|
|
}
|
|
|
|
public override applyPreLeaveField(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
|
const suppressTag = globalScene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag;
|
|
suppressTag.onSourceLeave(globalScene.arena);
|
|
}
|
|
}
|
|
|
|
export class PreStatStageChangeAbAttr extends AbAttr {
|
|
canApplyPreStatStageChange(
|
|
_pokemon: Pokemon | null,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_stat: BattleStat,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPreStatStageChange(
|
|
_pokemon: Pokemon | null,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_stat: BattleStat,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
/**
|
|
* Reflect all {@linkcode BattleStat} reductions caused by other Pokémon's moves and Abilities.
|
|
* Currently only applies to Mirror Armor.
|
|
*/
|
|
export class ReflectStatStageChangeAbAttr extends PreStatStageChangeAbAttr {
|
|
/** {@linkcode BattleStat} to reflect */
|
|
private reflectedStat?: BattleStat;
|
|
|
|
/**
|
|
* Apply the {@linkcode ReflectStatStageChangeAbAttr} to an interaction
|
|
* @param _pokemon The user pokemon
|
|
* @param _passive N/A
|
|
* @param simulated `true` if the ability is being simulated by the AI
|
|
* @param stat the {@linkcode BattleStat} being affected
|
|
* @param cancelled The {@linkcode BooleanHolder} that will be set to true due to reflection
|
|
* @param args
|
|
*/
|
|
override applyPreStatStageChange(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
stat: BattleStat,
|
|
cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
const attacker: Pokemon = args[0];
|
|
const stages = args[1];
|
|
this.reflectedStat = stat;
|
|
if (!simulated) {
|
|
globalScene.phaseManager.unshiftNew(
|
|
"StatStageChangePhase",
|
|
attacker.getBattlerIndex(),
|
|
false,
|
|
[stat],
|
|
stages,
|
|
true,
|
|
false,
|
|
true,
|
|
null,
|
|
true,
|
|
);
|
|
}
|
|
cancelled.value = true;
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:protectStat", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
statName: this.reflectedStat ? i18next.t(getStatKey(this.reflectedStat)) : i18next.t("battle:stats"),
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Protect one or all {@linkcode BattleStat} from reductions caused by other Pokémon's moves and Abilities
|
|
*/
|
|
export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
|
|
/** {@linkcode BattleStat} to protect or `undefined` if **all** {@linkcode BattleStat} are protected */
|
|
private protectedStat?: BattleStat;
|
|
|
|
constructor(protectedStat?: BattleStat) {
|
|
super();
|
|
|
|
this.protectedStat = protectedStat;
|
|
}
|
|
|
|
override canApplyPreStatStageChange(
|
|
_pokemon: Pokemon | null,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
stat: BattleStat,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): boolean {
|
|
return isNullOrUndefined(this.protectedStat) || stat === this.protectedStat;
|
|
}
|
|
|
|
/**
|
|
* Apply the {@linkcode ProtectedStatAbAttr} to an interaction
|
|
* @param _pokemon
|
|
* @param _passive
|
|
* @param simulated
|
|
* @param _stat the {@linkcode BattleStat} being affected
|
|
* @param cancelled The {@linkcode BooleanHolder} that will be set to true if the stat is protected
|
|
* @param _args
|
|
*/
|
|
override applyPreStatStageChange(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_stat: BattleStat,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:protectStat", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
statName: this.protectedStat ? i18next.t(getStatKey(this.protectedStat)) : i18next.t("battle:stats"),
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This attribute applies confusion to the target whenever the user
|
|
* directly poisons them with a move, e.g. Poison Puppeteer.
|
|
* Called in {@linkcode StatusEffectAttr}.
|
|
* @extends PostAttackAbAttr
|
|
* @see {@linkcode applyPostAttack}
|
|
*/
|
|
export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr {
|
|
/** List of effects to apply confusion after */
|
|
private effects: StatusEffect[];
|
|
|
|
constructor(...effects: StatusEffect[]) {
|
|
/** This effect does not require a damaging move */
|
|
super((_user, _target, _move) => true);
|
|
this.effects = effects;
|
|
}
|
|
|
|
override canApplyPostAttack(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
defender: Pokemon,
|
|
move: Move,
|
|
hitResult: HitResult | null,
|
|
args: any[],
|
|
): boolean {
|
|
return (
|
|
super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) &&
|
|
this.effects.indexOf(args[0]) > -1 &&
|
|
!defender.isFainted() &&
|
|
defender.canAddTag(BattlerTagType.CONFUSED)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Applies confusion to the target pokemon.
|
|
* @param pokemon {@link Pokemon} attacking
|
|
* @param _passive N/A
|
|
* @param defender {@link Pokemon} defending
|
|
* @param move {@link Move} used to apply status effect and confusion
|
|
* @param _hitResult N/A
|
|
* @param _args [0] {@linkcode StatusEffect} applied by move
|
|
*/
|
|
override applyPostAttack(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
defender: Pokemon,
|
|
move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
defender.addTag(BattlerTagType.CONFUSED, pokemon.randBattleSeedIntRange(2, 5), move.id, defender.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PreSetStatusAbAttr extends AbAttr {
|
|
/** Return whether the ability attribute can be applied */
|
|
canApplyPreSetStatus(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_effect: StatusEffect | undefined,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPreSetStatus(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_effect: StatusEffect | undefined,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
/**
|
|
* Provides immunity to status effects to specified targets.
|
|
*/
|
|
export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr {
|
|
protected immuneEffects: StatusEffect[];
|
|
private lastEffect: StatusEffect;
|
|
|
|
/**
|
|
* @param immuneEffects - The status effects to which the Pokémon is immune.
|
|
*/
|
|
constructor(...immuneEffects: StatusEffect[]) {
|
|
super();
|
|
|
|
this.immuneEffects = immuneEffects;
|
|
}
|
|
|
|
override canApplyPreSetStatus(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
effect: StatusEffect,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): boolean {
|
|
return (effect !== StatusEffect.FAINT && this.immuneEffects.length < 1) || this.immuneEffects.includes(effect);
|
|
}
|
|
|
|
/**
|
|
* Applies immunity to supplied status effects.
|
|
*
|
|
* @param _pokemon - The Pokémon to which the status is being applied.
|
|
* @param _passive - n/a
|
|
* @param effect - The status effect being applied.
|
|
* @param cancelled - A holder for a boolean value indicating if the status application was cancelled.
|
|
* @param _args - n/a
|
|
*/
|
|
override applyPreSetStatus(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
effect: StatusEffect,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
this.lastEffect = effect;
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return this.immuneEffects.length
|
|
? i18next.t("abilityTriggers:statusEffectImmunityWithName", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
statusEffectName: getStatusEffectDescriptor(this.lastEffect),
|
|
})
|
|
: i18next.t("abilityTriggers:statusEffectImmunity", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provides immunity to status effects to the user.
|
|
* @extends PreSetStatusEffectImmunityAbAttr
|
|
*/
|
|
export class StatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr {}
|
|
|
|
/**
|
|
* Provides immunity to status effects to the user's field.
|
|
* @extends PreSetStatusEffectImmunityAbAttr
|
|
*/
|
|
export class UserFieldStatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr {}
|
|
|
|
/**
|
|
* Conditionally provides immunity to status effects to the user's field.
|
|
*
|
|
* Used by {@linkcode AbilityId.FLOWER_VEIL | Flower Veil}.
|
|
* @extends UserFieldStatusEffectImmunityAbAttr
|
|
*
|
|
*/
|
|
export class ConditionalUserFieldStatusEffectImmunityAbAttr extends UserFieldStatusEffectImmunityAbAttr {
|
|
/**
|
|
* The condition for the field immunity to be applied.
|
|
* @param target The target of the status effect
|
|
* @param source The source of the status effect
|
|
*/
|
|
protected condition: (target: Pokemon, source: Pokemon | null) => boolean;
|
|
|
|
/**
|
|
* Evaluate the condition to determine if the {@linkcode ConditionalUserFieldStatusEffectImmunityAbAttr} can be applied.
|
|
* @param _pokemon The pokemon with the ability
|
|
* @param _passive unused
|
|
* @param _simulated Whether the ability is being simulated
|
|
* @param effect The status effect being applied
|
|
* @param cancelled Holds whether the status effect was cancelled by a prior effect
|
|
* @param args `Args[0]` is the target of the status effect, `Args[1]` is the source.
|
|
* @returns Whether the ability can be applied to cancel the status effect.
|
|
*/
|
|
override canApplyPreSetStatus(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
effect: StatusEffect,
|
|
cancelled: BooleanHolder,
|
|
args: [Pokemon, Pokemon | null, ...any],
|
|
): boolean {
|
|
return (
|
|
((!cancelled.value && effect !== StatusEffect.FAINT && this.immuneEffects.length < 1) ||
|
|
this.immuneEffects.includes(effect)) &&
|
|
this.condition(args[0], args[1])
|
|
);
|
|
}
|
|
|
|
constructor(condition: (target: Pokemon, source: Pokemon | null) => boolean, ...immuneEffects: StatusEffect[]) {
|
|
super(...immuneEffects);
|
|
|
|
this.condition = condition;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Conditionally provides immunity to stat drop effects to the user's field.
|
|
*
|
|
* Used by {@linkcode AbilityId.FLOWER_VEIL | Flower Veil}.
|
|
*/
|
|
export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbAttr {
|
|
/** {@linkcode BattleStat} to protect or `undefined` if **all** {@linkcode BattleStat} are protected */
|
|
protected protectedStat?: BattleStat;
|
|
|
|
/** If the method evaluates to true, the stat will be protected. */
|
|
protected condition: (target: Pokemon) => boolean;
|
|
|
|
constructor(condition: (target: Pokemon) => boolean, _protectedStat?: BattleStat) {
|
|
super();
|
|
this.condition = condition;
|
|
}
|
|
|
|
/**
|
|
* Determine whether the {@linkcode ConditionalUserFieldProtectStatAbAttr} can be applied.
|
|
* @param _pokemon The pokemon with the ability
|
|
* @param _passive unused
|
|
* @param _simulated Unused
|
|
* @param stat The stat being affected
|
|
* @param cancelled Holds whether the stat change was already prevented.
|
|
* @param args Args[0] is the target pokemon of the stat change.
|
|
* @returns `true` if the ability can be applied
|
|
*/
|
|
override canApplyPreStatStageChange(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
stat: BattleStat,
|
|
cancelled: BooleanHolder,
|
|
args: [Pokemon, ...any],
|
|
): boolean {
|
|
const target = args[0];
|
|
if (!target) {
|
|
return false;
|
|
}
|
|
return (
|
|
!cancelled.value &&
|
|
(isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) &&
|
|
this.condition(target)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Apply the {@linkcode ConditionalUserFieldStatusEffectImmunityAbAttr} to an interaction
|
|
* @param _pokemon The pokemon the stat change is affecting (unused)
|
|
* @param _passive unused
|
|
* @param _simulated unused
|
|
* @param stat The stat being affected
|
|
* @param cancelled Will be set to true if the stat change is prevented
|
|
* @param _args unused
|
|
*/
|
|
override applyPreStatStageChange(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_stat: BattleStat,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
export class PreApplyBattlerTagAbAttr extends AbAttr {
|
|
canApplyPreApplyBattlerTag(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_tag: BattlerTag,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPreApplyBattlerTag(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_tag: BattlerTag,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
/**
|
|
* Provides immunity to BattlerTags {@linkcode BattlerTag} to specified targets.
|
|
*/
|
|
export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr {
|
|
protected immuneTagTypes: BattlerTagType[];
|
|
protected battlerTag: BattlerTag;
|
|
|
|
constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) {
|
|
super(true);
|
|
|
|
this.immuneTagTypes = coerceArray(immuneTagTypes);
|
|
}
|
|
|
|
override canApplyPreApplyBattlerTag(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
tag: BattlerTag,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): boolean {
|
|
this.battlerTag = tag;
|
|
|
|
return !cancelled.value && this.immuneTagTypes.includes(tag.tagType);
|
|
}
|
|
|
|
override applyPreApplyBattlerTag(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_tag: BattlerTag,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:battlerTagImmunity", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
battlerTagName: this.battlerTag.getDescriptor(),
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provides immunity to BattlerTags {@linkcode BattlerTag} to the user.
|
|
* @extends PreApplyBattlerTagImmunityAbAttr
|
|
*/
|
|
export class BattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr {}
|
|
|
|
/**
|
|
* Provides immunity to BattlerTags {@linkcode BattlerTag} to the user's field.
|
|
* @extends PreApplyBattlerTagImmunityAbAttr
|
|
*/
|
|
export class UserFieldBattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr {}
|
|
|
|
export class ConditionalUserFieldBattlerTagImmunityAbAttr extends UserFieldBattlerTagImmunityAbAttr {
|
|
private condition: (target: Pokemon) => boolean;
|
|
|
|
/**
|
|
* Determine whether the {@linkcode ConditionalUserFieldBattlerTagImmunityAbAttr} can be applied by passing the target pokemon to the condition.
|
|
* @param pokemon The pokemon owning the ability
|
|
* @param passive unused
|
|
* @param simulated whether the ability is being simulated (unused)
|
|
* @param tag The {@linkcode BattlerTag} being applied
|
|
* @param cancelled Holds whether the tag was previously cancelled (unused)
|
|
* @param args Args[0] is the target that the tag is attempting to be applied to
|
|
* @returns Whether the ability can be used to cancel the battler tag
|
|
*/
|
|
override canApplyPreApplyBattlerTag(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
tag: BattlerTag,
|
|
cancelled: BooleanHolder,
|
|
args: [Pokemon, ...any],
|
|
): boolean {
|
|
return (
|
|
super.canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args) && this.condition(args[0])
|
|
);
|
|
}
|
|
|
|
constructor(condition: (target: Pokemon) => boolean, immuneTagTypes: BattlerTagType | BattlerTagType[]) {
|
|
super(immuneTagTypes);
|
|
|
|
this.condition = condition;
|
|
}
|
|
}
|
|
|
|
export class BlockCritAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
/**
|
|
* Apply the block crit ability by setting the value in the provided boolean holder to `true`.
|
|
* @param args - `[0]`: A {@linkcode BooleanHolder} containing whether the attack is prevented from critting.
|
|
*/
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: [BooleanHolder],
|
|
): void {
|
|
args[0].value = true;
|
|
}
|
|
}
|
|
|
|
export class BonusCritAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
/**
|
|
* Apply the bonus crit ability by increasing the value in the provided number holder by 1
|
|
*
|
|
* @param _pokemon The pokemon with the BonusCrit ability (unused)
|
|
* @param _passive Unused
|
|
* @param _simulated Unused
|
|
* @param _cancelled Unused
|
|
* @param args Args[0] is a number holder containing the crit stage.
|
|
*/
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: [NumberHolder, ...any],
|
|
): void {
|
|
(args[0] as NumberHolder).value += 1;
|
|
}
|
|
}
|
|
|
|
export class MultCritAbAttr extends AbAttr {
|
|
public multAmount: number;
|
|
|
|
constructor(multAmount: number) {
|
|
super(false);
|
|
|
|
this.multAmount = multAmount;
|
|
}
|
|
|
|
override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
|
const critMult = args[0] as NumberHolder;
|
|
return critMult.value > 1;
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
const critMult = args[0] as NumberHolder;
|
|
critMult.value *= this.multAmount;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Guarantees a critical hit according to the given condition, except if target prevents critical hits. ie. Merciless
|
|
* @extends AbAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class ConditionalCritAbAttr extends AbAttr {
|
|
private condition: PokemonAttackCondition;
|
|
|
|
constructor(condition: PokemonAttackCondition, _checkUser?: boolean) {
|
|
super(false);
|
|
|
|
this.condition = condition;
|
|
}
|
|
|
|
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
|
const target = args[1] as Pokemon;
|
|
const move = args[2] as Move;
|
|
return this.condition(pokemon, target, move);
|
|
}
|
|
|
|
/**
|
|
* @param _pokemon {@linkcode Pokemon} user.
|
|
* @param args [0] {@linkcode BooleanHolder} If true critical hit is guaranteed.
|
|
* [1] {@linkcode Pokemon} Target.
|
|
* [2] {@linkcode Move} used by ability user.
|
|
*/
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as BooleanHolder).value = true;
|
|
}
|
|
}
|
|
|
|
export class BlockNonDirectDamageAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This attribute will block any status damage that you put in the parameter.
|
|
*/
|
|
export class BlockStatusDamageAbAttr extends AbAttr {
|
|
private effects: StatusEffect[];
|
|
|
|
/**
|
|
* @param {StatusEffect[]} effects The status effect(s) that will be blocked from damaging the ability pokemon
|
|
*/
|
|
constructor(...effects: StatusEffect[]) {
|
|
super(false);
|
|
|
|
this.effects = effects;
|
|
}
|
|
|
|
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return !!pokemon.status?.effect && this.effects.includes(pokemon.status.effect);
|
|
}
|
|
|
|
/**
|
|
* @param {Pokemon} _pokemon The pokemon with the ability
|
|
* @param {boolean} _passive N/A
|
|
* @param {BooleanHolder} cancelled Whether to cancel the status damage
|
|
* @param {any[]} _args N/A
|
|
*/
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
export class BlockOneHitKOAbAttr extends AbAttr {
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This governs abilities that alter the priority of moves
|
|
* Abilities: Prankster, Gale Wings, Triage, Mycelium Might, Stall
|
|
* Note - Quick Claw has a separate and distinct implementation outside of priority
|
|
*/
|
|
export class ChangeMovePriorityAbAttr extends AbAttr {
|
|
private moveFunc: (pokemon: Pokemon, move: Move) => boolean;
|
|
private changeAmount: number;
|
|
|
|
/**
|
|
* @param {(pokemon, move) => boolean} moveFunc applies priority-change to moves within a provided category
|
|
* @param {number} changeAmount the amount of priority added or subtracted
|
|
*/
|
|
constructor(moveFunc: (pokemon: Pokemon, move: Move) => boolean, changeAmount: number) {
|
|
super(false);
|
|
|
|
this.moveFunc = moveFunc;
|
|
this.changeAmount = changeAmount;
|
|
}
|
|
|
|
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
|
return this.moveFunc(pokemon, args[0] as Move);
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[1] as NumberHolder).value += this.changeAmount;
|
|
}
|
|
}
|
|
|
|
export class IgnoreContactAbAttr extends AbAttr {}
|
|
|
|
export class PreWeatherEffectAbAttr extends AbAttr {
|
|
canApplyPreWeatherEffect(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_weather: Weather | null,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPreWeatherEffect(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_weather: Weather | null,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
export class PreWeatherDamageAbAttr extends PreWeatherEffectAbAttr {}
|
|
|
|
export class BlockWeatherDamageAttr extends PreWeatherDamageAbAttr {
|
|
private weatherTypes: WeatherType[];
|
|
|
|
constructor(...weatherTypes: WeatherType[]) {
|
|
super(false);
|
|
|
|
this.weatherTypes = weatherTypes;
|
|
}
|
|
|
|
override canApplyPreWeatherEffect(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
weather: Weather,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): boolean {
|
|
return !this.weatherTypes.length || this.weatherTypes.indexOf(weather?.weatherType) > -1;
|
|
}
|
|
|
|
override applyPreWeatherEffect(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_weather: Weather,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
export class SuppressWeatherEffectAbAttr extends PreWeatherEffectAbAttr {
|
|
public affectsImmutable: boolean;
|
|
|
|
constructor(affectsImmutable?: boolean) {
|
|
super(true);
|
|
|
|
this.affectsImmutable = !!affectsImmutable;
|
|
}
|
|
|
|
override canApplyPreWeatherEffect(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
weather: Weather,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): boolean {
|
|
return this.affectsImmutable || weather.isImmutable();
|
|
}
|
|
|
|
override applyPreWeatherEffect(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_weather: Weather,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Condition function to applied to abilities related to Sheer Force.
|
|
* Checks if last move used against target was affected by a Sheer Force user and:
|
|
* Disables: Color Change, Pickpocket, Berserk, Anger Shell
|
|
* @returns An {@linkcode AbAttrCondition} to disable the ability under the proper conditions.
|
|
*/
|
|
function getSheerForceHitDisableAbCondition(): AbAttrCondition {
|
|
return (pokemon: Pokemon) => {
|
|
const lastReceivedAttack = pokemon.turnData.attacksReceived[0];
|
|
if (!lastReceivedAttack) {
|
|
return true;
|
|
}
|
|
|
|
const lastAttacker = pokemon.getOpponents().find(p => p.id === lastReceivedAttack.sourceId);
|
|
if (!lastAttacker) {
|
|
return true;
|
|
}
|
|
|
|
/** `true` if the last move's chance is above 0 and the last attacker's ability is sheer force */
|
|
const SheerForceAffected =
|
|
allMoves[lastReceivedAttack.move].chance >= 0 && lastAttacker.hasAbility(AbilityId.SHEER_FORCE);
|
|
|
|
return !SheerForceAffected;
|
|
};
|
|
}
|
|
|
|
function getWeatherCondition(...weatherTypes: WeatherType[]): AbAttrCondition {
|
|
return () => {
|
|
if (!globalScene?.arena) {
|
|
return false;
|
|
}
|
|
if (globalScene.arena.weather?.isEffectSuppressed()) {
|
|
return false;
|
|
}
|
|
const weatherType = globalScene.arena.weather?.weatherType;
|
|
return !!weatherType && weatherTypes.indexOf(weatherType) > -1;
|
|
};
|
|
}
|
|
|
|
function getAnticipationCondition(): AbAttrCondition {
|
|
return (pokemon: Pokemon) => {
|
|
for (const opponent of pokemon.getOpponents()) {
|
|
for (const move of opponent.moveset) {
|
|
// ignore null/undefined moves
|
|
if (!move) {
|
|
continue;
|
|
}
|
|
// the move's base type (not accounting for variable type changes) is super effective
|
|
if (
|
|
move.getMove().is("AttackMove") &&
|
|
pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true, undefined, move.getMove()) >= 2
|
|
) {
|
|
return true;
|
|
}
|
|
// move is a OHKO
|
|
if (move.getMove().hasAttr("OneHitKOAttr")) {
|
|
return true;
|
|
}
|
|
// edge case for hidden power, type is computed
|
|
if (move.getMove().id === MoveId.HIDDEN_POWER) {
|
|
const iv_val = Math.floor(
|
|
(((opponent.ivs[Stat.HP] & 1) +
|
|
(opponent.ivs[Stat.ATK] & 1) * 2 +
|
|
(opponent.ivs[Stat.DEF] & 1) * 4 +
|
|
(opponent.ivs[Stat.SPD] & 1) * 8 +
|
|
(opponent.ivs[Stat.SPATK] & 1) * 16 +
|
|
(opponent.ivs[Stat.SPDEF] & 1) * 32) *
|
|
15) /
|
|
63,
|
|
);
|
|
|
|
const type = [
|
|
PokemonType.FIGHTING,
|
|
PokemonType.FLYING,
|
|
PokemonType.POISON,
|
|
PokemonType.GROUND,
|
|
PokemonType.ROCK,
|
|
PokemonType.BUG,
|
|
PokemonType.GHOST,
|
|
PokemonType.STEEL,
|
|
PokemonType.FIRE,
|
|
PokemonType.WATER,
|
|
PokemonType.GRASS,
|
|
PokemonType.ELECTRIC,
|
|
PokemonType.PSYCHIC,
|
|
PokemonType.ICE,
|
|
PokemonType.DRAGON,
|
|
PokemonType.DARK,
|
|
][iv_val];
|
|
|
|
if (pokemon.getAttackTypeEffectiveness(type, opponent) >= 2) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates an ability condition that causes the ability to fail if that ability
|
|
* has already been used by that pokemon that battle. It requires an ability to
|
|
* be specified due to current limitations in how conditions on abilities work.
|
|
* @param {AbilityId} ability The ability to check if it's already been applied
|
|
* @returns {AbAttrCondition} The condition
|
|
*/
|
|
function getOncePerBattleCondition(ability: AbilityId): AbAttrCondition {
|
|
return (pokemon: Pokemon) => {
|
|
return !pokemon.waveData.abilitiesApplied.has(ability);
|
|
};
|
|
}
|
|
|
|
export class ForewarnAbAttr extends PostSummonAbAttr {
|
|
constructor() {
|
|
super(true);
|
|
}
|
|
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
let maxPowerSeen = 0;
|
|
let maxMove = "";
|
|
let movePower = 0;
|
|
for (const opponent of pokemon.getOpponents()) {
|
|
for (const move of opponent.moveset) {
|
|
if (move?.getMove().is("StatusMove")) {
|
|
movePower = 1;
|
|
} else if (move?.getMove().hasAttr("OneHitKOAttr")) {
|
|
movePower = 150;
|
|
} else if (
|
|
move?.getMove().id === MoveId.COUNTER ||
|
|
move?.getMove().id === MoveId.MIRROR_COAT ||
|
|
move?.getMove().id === MoveId.METAL_BURST
|
|
) {
|
|
movePower = 120;
|
|
} else if (move?.getMove().power === -1) {
|
|
movePower = 80;
|
|
} else {
|
|
movePower = move?.getMove().power ?? 0;
|
|
}
|
|
|
|
if (movePower > maxPowerSeen) {
|
|
maxPowerSeen = movePower;
|
|
maxMove = move?.getName() ?? "";
|
|
}
|
|
}
|
|
}
|
|
if (!simulated) {
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("abilityTriggers:forewarn", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
moveName: maxMove,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class FriskAbAttr extends PostSummonAbAttr {
|
|
constructor() {
|
|
super(true);
|
|
}
|
|
|
|
override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
for (const opponent of pokemon.getOpponents()) {
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("abilityTriggers:frisk", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
opponentName: opponent.name,
|
|
opponentAbilityName: opponent.getAbility().name,
|
|
}),
|
|
);
|
|
setAbilityRevealed(opponent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostWeatherChangeAbAttr extends AbAttr {
|
|
canApplyPostWeatherChange(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_weather: WeatherType,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPostWeatherChange(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_weather: WeatherType,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
/**
|
|
* Triggers weather-based form change when weather changes.
|
|
* Used by Forecast and Flower Gift.
|
|
* @extends PostWeatherChangeAbAttr
|
|
*/
|
|
export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr {
|
|
private ability: AbilityId;
|
|
private formRevertingWeathers: WeatherType[];
|
|
|
|
constructor(ability: AbilityId, formRevertingWeathers: WeatherType[]) {
|
|
super(false);
|
|
|
|
this.ability = ability;
|
|
this.formRevertingWeathers = formRevertingWeathers;
|
|
}
|
|
|
|
override canApplyPostWeatherChange(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_weather: WeatherType,
|
|
_args: any[],
|
|
): boolean {
|
|
const isCastformWithForecast =
|
|
pokemon.species.speciesId === SpeciesId.CASTFORM && this.ability === AbilityId.FORECAST;
|
|
const isCherrimWithFlowerGift =
|
|
pokemon.species.speciesId === SpeciesId.CHERRIM && this.ability === AbilityId.FLOWER_GIFT;
|
|
|
|
return isCastformWithForecast || isCherrimWithFlowerGift;
|
|
}
|
|
|
|
/**
|
|
* Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the
|
|
* weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges}
|
|
* @param _pokemon - The Pokemon with this ability
|
|
* @param _passive - unused
|
|
* @param simulated - unused
|
|
* @param _weather - unused
|
|
* @param _args - unused
|
|
*/
|
|
override applyPostWeatherChange(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_weather: WeatherType,
|
|
_args: any[],
|
|
): void {
|
|
if (simulated) {
|
|
return;
|
|
}
|
|
|
|
const weatherType = globalScene.arena.weather?.weatherType;
|
|
|
|
if (weatherType && this.formRevertingWeathers.includes(weatherType)) {
|
|
globalScene.arena.triggerWeatherBasedFormChangesToNormal();
|
|
} else {
|
|
globalScene.arena.triggerWeatherBasedFormChanges();
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostWeatherChangeAddBattlerTagAttr extends PostWeatherChangeAbAttr {
|
|
private tagType: BattlerTagType;
|
|
private turnCount: number;
|
|
private weatherTypes: WeatherType[];
|
|
|
|
constructor(tagType: BattlerTagType, turnCount: number, ...weatherTypes: WeatherType[]) {
|
|
super();
|
|
|
|
this.tagType = tagType;
|
|
this.turnCount = turnCount;
|
|
this.weatherTypes = weatherTypes;
|
|
}
|
|
|
|
override canApplyPostWeatherChange(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
weather: WeatherType,
|
|
_args: any[],
|
|
): boolean {
|
|
return !!this.weatherTypes.find(w => weather === w) && pokemon.canAddTag(this.tagType);
|
|
}
|
|
|
|
override applyPostWeatherChange(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_weather: WeatherType,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
pokemon.addTag(this.tagType, this.turnCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostWeatherLapseAbAttr extends AbAttr {
|
|
protected weatherTypes: WeatherType[];
|
|
|
|
constructor(...weatherTypes: WeatherType[]) {
|
|
super();
|
|
|
|
this.weatherTypes = weatherTypes;
|
|
}
|
|
|
|
canApplyPostWeatherLapse(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_weather: Weather | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPostWeatherLapse(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_weather: Weather | null,
|
|
_args: any[],
|
|
): void {}
|
|
|
|
getCondition(): AbAttrCondition {
|
|
return getWeatherCondition(...this.weatherTypes);
|
|
}
|
|
}
|
|
|
|
export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr {
|
|
private healFactor: number;
|
|
|
|
constructor(healFactor: number, ...weatherTypes: WeatherType[]) {
|
|
super(...weatherTypes);
|
|
|
|
this.healFactor = healFactor;
|
|
}
|
|
|
|
override canApplyPostWeatherLapse(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_weather: Weather | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return !pokemon.isFullHp();
|
|
}
|
|
|
|
override applyPostWeatherLapse(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
_weather: Weather,
|
|
_args: any[],
|
|
): void {
|
|
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
|
|
if (!simulated) {
|
|
globalScene.phaseManager.unshiftNew(
|
|
"PokemonHealPhase",
|
|
pokemon.getBattlerIndex(),
|
|
toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)),
|
|
i18next.t("abilityTriggers:postWeatherLapseHeal", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
}),
|
|
true,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr {
|
|
private damageFactor: number;
|
|
|
|
constructor(damageFactor: number, ...weatherTypes: WeatherType[]) {
|
|
super(...weatherTypes);
|
|
|
|
this.damageFactor = damageFactor;
|
|
}
|
|
|
|
override canApplyPostWeatherLapse(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_weather: Weather | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return !pokemon.hasAbilityWithAttr("BlockNonDirectDamageAbAttr");
|
|
}
|
|
|
|
override applyPostWeatherLapse(
|
|
pokemon: Pokemon,
|
|
passive: boolean,
|
|
simulated: boolean,
|
|
_weather: Weather,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("abilityTriggers:postWeatherLapseDamage", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
}),
|
|
);
|
|
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / (16 / this.damageFactor)), {
|
|
result: HitResult.INDIRECT,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostTerrainChangeAbAttr extends AbAttr {
|
|
canApplyPostTerrainChange(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_terrain: TerrainType,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPostTerrainChange(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_terrain: TerrainType,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr {
|
|
private tagType: BattlerTagType;
|
|
private turnCount: number;
|
|
private terrainTypes: TerrainType[];
|
|
|
|
constructor(tagType: BattlerTagType, turnCount: number, ...terrainTypes: TerrainType[]) {
|
|
super();
|
|
|
|
this.tagType = tagType;
|
|
this.turnCount = turnCount;
|
|
this.terrainTypes = terrainTypes;
|
|
}
|
|
|
|
override canApplyPostTerrainChange(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
terrain: TerrainType,
|
|
_args: any[],
|
|
): boolean {
|
|
return !!this.terrainTypes.find(t => t === terrain) && pokemon.canAddTag(this.tagType);
|
|
}
|
|
|
|
override applyPostTerrainChange(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_terrain: TerrainType,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
pokemon.addTag(this.tagType, this.turnCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition {
|
|
return (_pokemon: Pokemon) => {
|
|
const terrainType = globalScene.arena.terrain?.terrainType;
|
|
return !!terrainType && terrainTypes.indexOf(terrainType) > -1;
|
|
};
|
|
}
|
|
|
|
export class PostTurnAbAttr extends AbAttr {
|
|
canApplyPostTurn(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPostTurn(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {}
|
|
}
|
|
|
|
/**
|
|
* This attribute will heal 1/8th HP if the ability pokemon has the correct status.
|
|
*/
|
|
export class PostTurnStatusHealAbAttr extends PostTurnAbAttr {
|
|
private effects: StatusEffect[];
|
|
|
|
/**
|
|
* @param {StatusEffect[]} effects The status effect(s) that will qualify healing the ability pokemon
|
|
*/
|
|
constructor(...effects: StatusEffect[]) {
|
|
super(false);
|
|
|
|
this.effects = effects;
|
|
}
|
|
|
|
override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return !isNullOrUndefined(pokemon.status) && this.effects.includes(pokemon.status.effect) && !pokemon.isFullHp();
|
|
}
|
|
|
|
/**
|
|
* @param {Pokemon} pokemon The pokemon with the ability that will receive the healing
|
|
* @param {Boolean} passive N/A
|
|
* @param {any[]} _args N/A
|
|
*/
|
|
override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
|
|
globalScene.phaseManager.unshiftNew(
|
|
"PokemonHealPhase",
|
|
pokemon.getBattlerIndex(),
|
|
toDmgValue(pokemon.getMaxHp() / 8),
|
|
i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }),
|
|
true,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* After the turn ends, resets the status of either the ability holder or their ally
|
|
* @param allyTarget Whether to target ally, defaults to false (self-target)
|
|
*/
|
|
export class PostTurnResetStatusAbAttr extends PostTurnAbAttr {
|
|
private allyTarget: boolean;
|
|
private target: Pokemon | undefined;
|
|
|
|
constructor(allyTarget = false) {
|
|
super(true);
|
|
this.allyTarget = allyTarget;
|
|
}
|
|
|
|
override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
if (this.allyTarget) {
|
|
this.target = pokemon.getAlly();
|
|
} else {
|
|
this.target = pokemon;
|
|
}
|
|
|
|
const effect = this.target?.status?.effect;
|
|
return !!effect && effect !== StatusEffect.FAINT;
|
|
}
|
|
|
|
override applyPostTurn(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated && this.target?.status) {
|
|
globalScene.phaseManager.queueMessage(
|
|
getStatusEffectHealText(this.target.status?.effect, getPokemonNameWithAffix(this.target)),
|
|
);
|
|
this.target.resetStatus(false);
|
|
this.target.updateInfo();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute to try and restore eaten berries after the turn ends.
|
|
* Used by {@linkcode AbilityId.HARVEST}.
|
|
*/
|
|
export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr {
|
|
/**
|
|
* Array containing all {@linkcode BerryType | BerryTypes} that are under cap and able to be restored.
|
|
* Stored inside the class for a minor performance boost
|
|
*/
|
|
private berriesUnderCap: BerryType[];
|
|
|
|
/**
|
|
* @param procChance - function providing chance to restore an item
|
|
* @see {@linkcode createEatenBerry()}
|
|
*/
|
|
constructor(private procChance: (pokemon: Pokemon) => number) {
|
|
super();
|
|
}
|
|
|
|
override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
// Ensure we have at least 1 recoverable berry (at least 1 berry in berriesEaten is not capped)
|
|
const cappedBerries = new Set(
|
|
globalScene
|
|
.getModifiers(BerryModifier, pokemon.isPlayer())
|
|
.filter(bm => bm.pokemonId === pokemon.id && bm.getCountUnderMax() < 1)
|
|
.map(bm => bm.berryType),
|
|
);
|
|
|
|
this.berriesUnderCap = pokemon.battleData.berriesEaten.filter(bt => !cappedBerries.has(bt));
|
|
|
|
if (!this.berriesUnderCap.length) {
|
|
return false;
|
|
}
|
|
|
|
// Clamp procChance to [0, 1]. Skip if didn't proc (less than pass)
|
|
const pass = randSeedFloat();
|
|
return this.procChance(pokemon) >= pass;
|
|
}
|
|
|
|
override applyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
this.createEatenBerry(pokemon);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new berry chosen randomly from all berries the pokemon ate this battle
|
|
* @param pokemon - The {@linkcode Pokemon} with this ability
|
|
* @returns `true` if a new berry was created
|
|
*/
|
|
createEatenBerry(pokemon: Pokemon): boolean {
|
|
// Pick a random available berry to yoink
|
|
const randomIdx = randSeedInt(this.berriesUnderCap.length);
|
|
const chosenBerryType = this.berriesUnderCap[randomIdx];
|
|
pokemon.battleData.berriesEaten.splice(randomIdx, 1); // Remove berry from memory
|
|
const chosenBerry = new BerryModifierType(chosenBerryType);
|
|
|
|
// Add the randomly chosen berry or update the existing one
|
|
const berryModifier = globalScene.findModifier(
|
|
m => m instanceof BerryModifier && m.berryType === chosenBerryType && m.pokemonId === pokemon.id,
|
|
pokemon.isPlayer(),
|
|
) as BerryModifier | undefined;
|
|
|
|
if (berryModifier) {
|
|
berryModifier.stackCount++;
|
|
} else {
|
|
const newBerry = new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1);
|
|
if (pokemon.isPlayer()) {
|
|
globalScene.addModifier(newBerry);
|
|
} else {
|
|
globalScene.addEnemyModifier(newBerry);
|
|
}
|
|
}
|
|
|
|
globalScene.updateModifiers(pokemon.isPlayer());
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("abilityTriggers:postTurnLootCreateEatenBerry", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
berryName: chosenBerry.name,
|
|
}),
|
|
);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute to track and re-trigger last turn's berries at the end of the `BerryPhase`.
|
|
* Used by {@linkcode AbilityId.CUD_CHEW}.
|
|
*/
|
|
export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr {
|
|
/**
|
|
* @returns `true` if the pokemon ate anything last turn
|
|
*/
|
|
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
// force ability popup for ability triggers on normal turns.
|
|
// Still not used if ability doesn't proc
|
|
this.showAbility = true;
|
|
return !!pokemon.summonData.berriesEatenLast.length;
|
|
}
|
|
|
|
/**
|
|
* Cause this {@linkcode Pokemon} to regurgitate and eat all berries inside its `berriesEatenLast` array.
|
|
* Triggers a berry use animation, but does *not* count for other berry or item-related abilities.
|
|
* @param pokemon - The {@linkcode Pokemon} having a bad tummy ache
|
|
* @param _passive - N/A
|
|
* @param _simulated - N/A
|
|
* @param _cancelled - N/A
|
|
* @param _args - N/A
|
|
*/
|
|
override apply(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder | null,
|
|
_args: any[],
|
|
): void {
|
|
globalScene.phaseManager.unshiftNew(
|
|
"CommonAnimPhase",
|
|
pokemon.getBattlerIndex(),
|
|
pokemon.getBattlerIndex(),
|
|
CommonAnim.USE_ITEM,
|
|
);
|
|
|
|
// Re-apply effects of all berries previously scarfed.
|
|
// This doesn't count as "eating" a berry (for unnerve/stuff cheeks/unburden) as no item is consumed.
|
|
for (const berryType of pokemon.summonData.berriesEatenLast) {
|
|
getBerryEffectFunc(berryType)(pokemon);
|
|
const bMod = new BerryModifier(new BerryModifierType(berryType), pokemon.id, berryType, 1);
|
|
globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(bMod)); // trigger message
|
|
}
|
|
|
|
// uncomment to make cheek pouch work with cud chew
|
|
// applyAbAttrs("HealFromBerryUseAbAttr", pokemon, new BooleanHolder(false));
|
|
}
|
|
|
|
/**
|
|
* @returns always `true` as we always want to move berries into summon data
|
|
*/
|
|
override canApplyPostTurn(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
this.showAbility = false; // don't show popup for turn end berry moving (should ideally be hidden)
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Move this {@linkcode Pokemon}'s `berriesEaten` array from `PokemonTurnData`
|
|
* into `PokemonSummonData` on turn end.
|
|
* Both arrays are cleared on switch.
|
|
* @param pokemon - The {@linkcode Pokemon} having a nice snack
|
|
* @param _passive - N/A
|
|
* @param _simulated - N/A
|
|
* @param _args - N/A
|
|
*/
|
|
override applyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
|
pokemon.summonData.berriesEatenLast = pokemon.turnData.berriesEaten;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for {@linkcode AbilityId.MOODY} to randomly raise and lower stats at turn end.
|
|
*/
|
|
export class MoodyAbAttr extends PostTurnAbAttr {
|
|
constructor() {
|
|
super(true);
|
|
}
|
|
/**
|
|
* Randomly increases one stat stage by 2 and decreases a different stat stage by 1
|
|
* @param {Pokemon} pokemon Pokemon that has this ability
|
|
* @param _passive N/A
|
|
* @param simulated true if applying in a simulated call.
|
|
* @param _args N/A
|
|
*
|
|
* Any stat stages at +6 or -6 are excluded from being increased or decreased, respectively
|
|
* If the pokemon already has all stat stages raised to 6, it will only decrease one stat stage by 1
|
|
* If the pokemon already has all stat stages lowered to -6, it will only increase one stat stage by 2
|
|
*/
|
|
override applyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
const canRaise = EFFECTIVE_STATS.filter(s => pokemon.getStatStage(s) < 6);
|
|
let canLower = EFFECTIVE_STATS.filter(s => pokemon.getStatStage(s) > -6);
|
|
|
|
if (!simulated) {
|
|
if (canRaise.length > 0) {
|
|
const raisedStat = canRaise[pokemon.randBattleSeedInt(canRaise.length)];
|
|
canLower = canRaise.filter(s => s !== raisedStat);
|
|
globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [raisedStat], 2);
|
|
}
|
|
if (canLower.length > 0) {
|
|
const loweredStat = canLower[pokemon.randBattleSeedInt(canLower.length)];
|
|
globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [loweredStat], -1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export class SpeedBoostAbAttr extends PostTurnAbAttr {
|
|
constructor() {
|
|
super(true);
|
|
}
|
|
|
|
override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean {
|
|
return simulated || (!pokemon.turnData.switchedInThisTurn && !pokemon.turnData.failedRunAway);
|
|
}
|
|
|
|
override applyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
|
globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [Stat.SPD], 1);
|
|
}
|
|
}
|
|
|
|
export class PostTurnHealAbAttr extends PostTurnAbAttr {
|
|
override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return !pokemon.isFullHp();
|
|
}
|
|
|
|
override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
|
|
globalScene.phaseManager.unshiftNew(
|
|
"PokemonHealPhase",
|
|
pokemon.getBattlerIndex(),
|
|
toDmgValue(pokemon.getMaxHp() / 16),
|
|
i18next.t("abilityTriggers:postTurnHeal", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
}),
|
|
true,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostTurnFormChangeAbAttr extends PostTurnAbAttr {
|
|
private formFunc: (p: Pokemon) => number;
|
|
|
|
constructor(formFunc: (p: Pokemon) => number) {
|
|
super(true);
|
|
|
|
this.formFunc = formFunc;
|
|
}
|
|
|
|
override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return this.formFunc(pokemon) !== pokemon.formIndex;
|
|
}
|
|
|
|
override applyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
if (!simulated) {
|
|
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for abilities (Bad Dreams) that damages the opponents for being asleep
|
|
*/
|
|
export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr {
|
|
override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return pokemon
|
|
.getOpponents()
|
|
.some(
|
|
opp =>
|
|
(opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(AbilityId.COMATOSE)) &&
|
|
!opp.hasAbilityWithAttr("BlockNonDirectDamageAbAttr") &&
|
|
!opp.switchOutStatus,
|
|
);
|
|
}
|
|
/**
|
|
* Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1)
|
|
* @param pokemon {@linkcode Pokemon} with this ability
|
|
* @param _passive N/A
|
|
* @param simulated `true` if applying in a simulated call.
|
|
* @param _args N/A
|
|
*/
|
|
override applyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void {
|
|
for (const opp of pokemon.getOpponents()) {
|
|
if (
|
|
(opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(AbilityId.COMATOSE)) &&
|
|
!opp.hasAbilityWithAttr("BlockNonDirectDamageAbAttr") &&
|
|
!opp.switchOutStatus
|
|
) {
|
|
if (!simulated) {
|
|
opp.damageAndUpdate(toDmgValue(opp.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) }),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Grabs the last failed Pokeball used
|
|
* @extends PostTurnAbAttr
|
|
* @see {@linkcode applyPostTurn} */
|
|
export class FetchBallAbAttr extends PostTurnAbAttr {
|
|
override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean {
|
|
return !simulated && !isNullOrUndefined(globalScene.currentBattle.lastUsedPokeball) && !!pokemon.isPlayer;
|
|
}
|
|
|
|
/**
|
|
* Adds the last used Pokeball back into the player's inventory
|
|
* @param pokemon {@linkcode Pokemon} with this ability
|
|
* @param _passive N/A
|
|
* @param _args N/A
|
|
*/
|
|
override applyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
|
const lastUsed = globalScene.currentBattle.lastUsedPokeball;
|
|
globalScene.pokeballCounts[lastUsed!]++;
|
|
globalScene.currentBattle.lastUsedPokeball = null;
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("abilityTriggers:fetchBall", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
pokeballName: getPokeballName(lastUsed!),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class PostBiomeChangeAbAttr extends AbAttr {}
|
|
|
|
export class PostBiomeChangeWeatherChangeAbAttr extends PostBiomeChangeAbAttr {
|
|
private weatherType: WeatherType;
|
|
|
|
constructor(weatherType: WeatherType) {
|
|
super();
|
|
|
|
this.weatherType = weatherType;
|
|
}
|
|
|
|
override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return (globalScene.arena.weather?.isImmutable() ?? false) && globalScene.arena.canSetWeather(this.weatherType);
|
|
}
|
|
|
|
override apply(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.arena.trySetWeather(this.weatherType, pokemon);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr {
|
|
private terrainType: TerrainType;
|
|
|
|
constructor(terrainType: TerrainType) {
|
|
super();
|
|
|
|
this.terrainType = terrainType;
|
|
}
|
|
|
|
override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return globalScene.arena.canSetTerrain(this.terrainType);
|
|
}
|
|
|
|
override apply(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.arena.trySetTerrain(this.terrainType, false, pokemon);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggers just after a move is used either by the opponent or the player
|
|
* @extends AbAttr
|
|
*/
|
|
export class PostMoveUsedAbAttr extends AbAttr {
|
|
canApplyPostMoveUsed(
|
|
_pokemon: Pokemon,
|
|
_move: PokemonMove,
|
|
_source: Pokemon,
|
|
_targets: BattlerIndex[],
|
|
_simulated: boolean,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPostMoveUsed(
|
|
_pokemon: Pokemon,
|
|
_move: PokemonMove,
|
|
_source: Pokemon,
|
|
_targets: BattlerIndex[],
|
|
_simulated: boolean,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
/**
|
|
* Triggers after a dance move is used either by the opponent or the player
|
|
* @extends PostMoveUsedAbAttr
|
|
*/
|
|
export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr {
|
|
override canApplyPostMoveUsed(
|
|
dancer: Pokemon,
|
|
_move: PokemonMove,
|
|
source: Pokemon,
|
|
_targets: BattlerIndex[],
|
|
_simulated: boolean,
|
|
_args: any[],
|
|
): boolean {
|
|
// List of tags that prevent the Dancer from replicating the move
|
|
const forbiddenTags = [
|
|
BattlerTagType.FLYING,
|
|
BattlerTagType.UNDERWATER,
|
|
BattlerTagType.UNDERGROUND,
|
|
BattlerTagType.HIDDEN,
|
|
];
|
|
// The move to replicate cannot come from the Dancer
|
|
return (
|
|
source.getBattlerIndex() !== dancer.getBattlerIndex() &&
|
|
!dancer.summonData.tags.some(tag => forbiddenTags.includes(tag.tagType))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Resolves the Dancer ability by replicating the move used by the source of the dance
|
|
* either on the source itself or on the target of the dance
|
|
* @param dancer {@linkcode Pokemon} with Dancer ability
|
|
* @param move {@linkcode PokemonMove} Dancing move used by the source
|
|
* @param source {@linkcode Pokemon} that used the dancing move
|
|
* @param targets {@linkcode BattlerIndex}Targets of the dancing move
|
|
* @param _args N/A
|
|
*/
|
|
override applyPostMoveUsed(
|
|
dancer: Pokemon,
|
|
move: PokemonMove,
|
|
source: Pokemon,
|
|
targets: BattlerIndex[],
|
|
simulated: boolean,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
dancer.turnData.extraTurns++;
|
|
// If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance
|
|
if (move.getMove().is("AttackMove") || move.getMove().is("StatusMove")) {
|
|
const target = this.getTarget(dancer, source, targets);
|
|
globalScene.phaseManager.unshiftNew("MovePhase", dancer, target, move, MoveUseMode.INDIRECT);
|
|
} else if (move.getMove().is("SelfStatusMove")) {
|
|
// If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself
|
|
globalScene.phaseManager.unshiftNew(
|
|
"MovePhase",
|
|
dancer,
|
|
[dancer.getBattlerIndex()],
|
|
move,
|
|
MoveUseMode.INDIRECT,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the correct targets of Dancer ability
|
|
*
|
|
* @param dancer {@linkcode Pokemon} Pokemon with Dancer ability
|
|
* @param source {@linkcode Pokemon} Source of the dancing move
|
|
* @param targets {@linkcode BattlerIndex} Targets of the dancing move
|
|
*/
|
|
getTarget(dancer: Pokemon, source: Pokemon, targets: BattlerIndex[]): BattlerIndex[] {
|
|
if (dancer.isPlayer()) {
|
|
return source.isPlayer() ? targets : [source.getBattlerIndex()];
|
|
}
|
|
return source.isPlayer() ? [source.getBattlerIndex()] : targets;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggers after the Pokemon loses or consumes an item
|
|
* @extends AbAttr
|
|
*/
|
|
export class PostItemLostAbAttr extends AbAttr {
|
|
canApplyPostItemLost(_pokemon: Pokemon, _simulated: boolean, _args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPostItemLost(_pokemon: Pokemon, _simulated: boolean, _args: any[]): void {}
|
|
}
|
|
|
|
/**
|
|
* Applies a Battler Tag to the Pokemon after it loses or consumes an item
|
|
* @extends PostItemLostAbAttr
|
|
*/
|
|
export class PostItemLostApplyBattlerTagAbAttr extends PostItemLostAbAttr {
|
|
private tagType: BattlerTagType;
|
|
constructor(tagType: BattlerTagType) {
|
|
super(false);
|
|
this.tagType = tagType;
|
|
}
|
|
|
|
override canApplyPostItemLost(pokemon: Pokemon, simulated: boolean, _args: any[]): boolean {
|
|
return !pokemon.getTag(this.tagType) && !simulated;
|
|
}
|
|
|
|
/**
|
|
* Adds the last used Pokeball back into the player's inventory
|
|
* @param pokemon {@linkcode Pokemon} with this ability
|
|
* @param _args N/A
|
|
*/
|
|
override applyPostItemLost(pokemon: Pokemon, _simulated: boolean, _args: any[]): void {
|
|
pokemon.addTag(this.tagType);
|
|
}
|
|
}
|
|
|
|
export class StatStageChangeMultiplierAbAttr extends AbAttr {
|
|
private multiplier: number;
|
|
|
|
constructor(multiplier: number) {
|
|
super(false);
|
|
|
|
this.multiplier = multiplier;
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as NumberHolder).value *= this.multiplier;
|
|
}
|
|
}
|
|
|
|
export class StatStageChangeCopyAbAttr extends AbAttr {
|
|
override apply(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.phaseManager.unshiftNew(
|
|
"StatStageChangePhase",
|
|
pokemon.getBattlerIndex(),
|
|
true,
|
|
args[0] as BattleStat[],
|
|
args[1] as number,
|
|
true,
|
|
false,
|
|
false,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class BypassBurnDamageReductionAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Causes Pokemon to take reduced damage from the {@linkcode StatusEffect.BURN | Burn} status
|
|
* @param multiplier Multiplied with the damage taken
|
|
*/
|
|
export class ReduceBurnDamageAbAttr extends AbAttr {
|
|
constructor(protected multiplier: number) {
|
|
super(false);
|
|
}
|
|
|
|
/**
|
|
* Applies the damage reduction
|
|
* @param _pokemon N/A
|
|
* @param _passive N/A
|
|
* @param _cancelled N/A
|
|
* @param args `[0]` {@linkcode NumberHolder} The damage value being modified
|
|
*/
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as NumberHolder).value = toDmgValue((args[0] as NumberHolder).value * this.multiplier);
|
|
}
|
|
}
|
|
|
|
export class DoubleBerryEffectAbAttr extends AbAttr {
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as NumberHolder).value *= 2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute to prevent opposing berry use while on the field.
|
|
* Used by {@linkcode AbilityId.UNNERVE}, {@linkcode AbilityId.AS_ONE_GLASTRIER} and {@linkcode AbilityId.AS_ONE_SPECTRIER}
|
|
*/
|
|
export class PreventBerryUseAbAttr extends AbAttr {
|
|
/**
|
|
* Prevent use of opposing berries.
|
|
* @param _pokemon - Unused
|
|
* @param _passive - Unused
|
|
* @param _simulated - Unused
|
|
* @param cancelled - {@linkcode BooleanHolder} containing whether to block berry use
|
|
*/
|
|
override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, cancelled: BooleanHolder): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A Pokemon with this ability heals by a percentage of their maximum hp after eating a berry
|
|
* @param healPercent - Percent of Max HP to heal
|
|
* @see {@linkcode apply()} for implementation
|
|
*/
|
|
export class HealFromBerryUseAbAttr extends AbAttr {
|
|
/** Percent of Max HP to heal */
|
|
private healPercent: number;
|
|
|
|
constructor(healPercent: number) {
|
|
super();
|
|
|
|
// Clamp healPercent so its between [0,1].
|
|
this.healPercent = Math.max(Math.min(healPercent, 1), 0);
|
|
}
|
|
|
|
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, ..._args: [BooleanHolder, any[]]): void {
|
|
if (simulated) {
|
|
return;
|
|
}
|
|
|
|
const { name: abilityName } = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
|
|
globalScene.phaseManager.unshiftNew(
|
|
"PokemonHealPhase",
|
|
pokemon.getBattlerIndex(),
|
|
toDmgValue(pokemon.getMaxHp() * this.healPercent),
|
|
i18next.t("abilityTriggers:healFromBerryUse", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
}),
|
|
true,
|
|
);
|
|
}
|
|
}
|
|
|
|
export class RunSuccessAbAttr extends AbAttr {
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as NumberHolder).value = 256;
|
|
}
|
|
}
|
|
|
|
type ArenaTrapCondition = (user: Pokemon, target: Pokemon) => boolean;
|
|
|
|
/**
|
|
* Base class for checking if a Pokemon is trapped by arena trap
|
|
* @extends AbAttr
|
|
* @field {@linkcode arenaTrapCondition} Conditional for trapping abilities.
|
|
* For example, Magnet Pull will only activate if opponent is Steel type.
|
|
* @see {@linkcode applyCheckTrapped}
|
|
*/
|
|
export class CheckTrappedAbAttr extends AbAttr {
|
|
protected arenaTrapCondition: ArenaTrapCondition;
|
|
constructor(condition: ArenaTrapCondition) {
|
|
super(false);
|
|
this.arenaTrapCondition = condition;
|
|
}
|
|
|
|
canApplyCheckTrapped(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_trapped: BooleanHolder,
|
|
_otherPokemon: Pokemon,
|
|
_args: any[],
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyCheckTrapped(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_trapped: BooleanHolder,
|
|
_otherPokemon: Pokemon,
|
|
_args: any[],
|
|
): void {}
|
|
}
|
|
|
|
/**
|
|
* Determines whether a Pokemon is blocked from switching/running away
|
|
* because of a trapping ability or move.
|
|
* @extends CheckTrappedAbAttr
|
|
* @see {@linkcode applyCheckTrapped}
|
|
*/
|
|
export class ArenaTrapAbAttr extends CheckTrappedAbAttr {
|
|
override canApplyCheckTrapped(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_trapped: BooleanHolder,
|
|
otherPokemon: Pokemon,
|
|
_args: any[],
|
|
): boolean {
|
|
return (
|
|
this.arenaTrapCondition(pokemon, otherPokemon) &&
|
|
!(
|
|
otherPokemon.getTypes(true).includes(PokemonType.GHOST) ||
|
|
(otherPokemon.getTypes(true).includes(PokemonType.STELLAR) &&
|
|
otherPokemon.getTypes().includes(PokemonType.GHOST))
|
|
) &&
|
|
!otherPokemon.hasAbility(AbilityId.RUN_AWAY)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Checks if enemy Pokemon is trapped by an Arena Trap-esque ability
|
|
* If the enemy is a Ghost type, it is not trapped
|
|
* If the enemy has the ability Run Away, it is not trapped.
|
|
* If the user has Magnet Pull and the enemy is not a Steel type, it is not trapped.
|
|
* If the user has Arena Trap and the enemy is not grounded, it is not trapped.
|
|
* @param _pokemon The {@link Pokemon} with this {@link AbAttr}
|
|
* @param _passive N/A
|
|
* @param trapped {@link BooleanHolder} indicating whether the other Pokemon is trapped or not
|
|
* @param _otherPokemon The {@link Pokemon} that is affected by an Arena Trap ability
|
|
* @param _args N/A
|
|
*/
|
|
override applyCheckTrapped(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
trapped: BooleanHolder,
|
|
_otherPokemon: Pokemon,
|
|
_args: any[],
|
|
): void {
|
|
trapped.value = true;
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:arenaTrap", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class MaxMultiHitAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as NumberHolder).value = 0;
|
|
}
|
|
}
|
|
|
|
export class PostBattleAbAttr extends AbAttr {
|
|
constructor(showAbility = true) {
|
|
super(showAbility);
|
|
}
|
|
|
|
canApplyPostBattle(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPostBattle(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {}
|
|
}
|
|
|
|
export class PostBattleLootAbAttr extends PostBattleAbAttr {
|
|
private randItem?: PokemonHeldItemModifier;
|
|
|
|
override canApplyPostBattle(pokemon: Pokemon, _passive: boolean, simulated: boolean, args: any[]): boolean {
|
|
const postBattleLoot = globalScene.currentBattle.postBattleLoot;
|
|
if (!simulated && postBattleLoot.length && args[0]) {
|
|
this.randItem = randSeedItem(postBattleLoot);
|
|
return globalScene.canTransferHeldItemModifier(this.randItem, pokemon, 1);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param _args - `[0]`: boolean for if the battle ended in a victory
|
|
*/
|
|
override applyPostBattle(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
|
const postBattleLoot = globalScene.currentBattle.postBattleLoot;
|
|
if (!this.randItem) {
|
|
this.randItem = randSeedItem(postBattleLoot);
|
|
}
|
|
|
|
if (globalScene.tryTransferHeldItemModifier(this.randItem, pokemon, true, 1, true, undefined, false)) {
|
|
postBattleLoot.splice(postBattleLoot.indexOf(this.randItem), 1);
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("abilityTriggers:postBattleLoot", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
itemName: this.randItem.type.name,
|
|
}),
|
|
);
|
|
}
|
|
this.randItem = undefined;
|
|
}
|
|
}
|
|
|
|
export class PostFaintAbAttr extends AbAttr {
|
|
canApplyPostFaint(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker?: Pokemon,
|
|
_move?: Move,
|
|
_hitResult?: HitResult,
|
|
..._args: any[]
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
applyPostFaint(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker?: Pokemon,
|
|
_move?: Move,
|
|
_hitResult?: HitResult,
|
|
..._args: any[]
|
|
): void {}
|
|
}
|
|
|
|
/**
|
|
* Used for weather suppressing abilities to trigger weather-based form changes upon being fainted.
|
|
* Used by Cloud Nine and Air Lock.
|
|
* @extends PostFaintAbAttr
|
|
*/
|
|
export class PostFaintUnsuppressedWeatherFormChangeAbAttr extends PostFaintAbAttr {
|
|
override canApplyPostFaint(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker?: Pokemon,
|
|
_move?: Move,
|
|
_hitResult?: HitResult,
|
|
..._args: any[]
|
|
): boolean {
|
|
return getPokemonWithWeatherBasedForms().length > 0;
|
|
}
|
|
|
|
/**
|
|
* Triggers {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges}
|
|
* when the user of the ability faints
|
|
* @param {Pokemon} _pokemon the fainted Pokemon
|
|
* @param _passive n/a
|
|
* @param _attacker n/a
|
|
* @param _move n/a
|
|
* @param _hitResult n/a
|
|
* @param _args n/a
|
|
*/
|
|
override applyPostFaint(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.arena.triggerWeatherBasedFormChanges();
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PostFaintContactDamageAbAttr extends PostFaintAbAttr {
|
|
private damageRatio: number;
|
|
|
|
constructor(damageRatio: number) {
|
|
super(true);
|
|
|
|
this.damageRatio = damageRatio;
|
|
}
|
|
|
|
override canApplyPostFaint(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker?: Pokemon,
|
|
move?: Move,
|
|
_hitResult?: HitResult,
|
|
..._args: any[]
|
|
): boolean {
|
|
const diedToDirectDamage =
|
|
move !== undefined &&
|
|
attacker !== undefined &&
|
|
move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon });
|
|
const cancelled = new BooleanHolder(false);
|
|
globalScene.getField(true).map(p => applyAbAttrs("FieldPreventExplosiveMovesAbAttr", p, cancelled, simulated));
|
|
return !(!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr("BlockNonDirectDamageAbAttr"));
|
|
}
|
|
|
|
override applyPostFaint(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker?: Pokemon,
|
|
_move?: Move,
|
|
_hitResult?: HitResult,
|
|
..._args: any[]
|
|
): void {
|
|
if (!simulated) {
|
|
attacker!.damageAndUpdate(toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio)), {
|
|
result: HitResult.INDIRECT,
|
|
});
|
|
attacker!.turnData.damageTaken += toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio));
|
|
}
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:postFaintContactDamage", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for abilities (Innards Out) that damage the opponent based on how much HP the last attack used to knock out the owner of the ability.
|
|
*/
|
|
export class PostFaintHPDamageAbAttr extends PostFaintAbAttr {
|
|
override applyPostFaint(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
attacker?: Pokemon,
|
|
move?: Move,
|
|
_hitResult?: HitResult,
|
|
..._args: any[]
|
|
): void {
|
|
//If the mon didn't die to indirect damage
|
|
if (move !== undefined && attacker !== undefined && !simulated) {
|
|
const damage = pokemon.turnData.attacksReceived[0].damage;
|
|
attacker.damageAndUpdate(damage, { result: HitResult.INDIRECT });
|
|
attacker.turnData.damageTaken += damage;
|
|
}
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:postFaintHpDamage", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
abilityName,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Redirects a move to the pokemon with this ability if it meets the conditions
|
|
*/
|
|
export class RedirectMoveAbAttr extends AbAttr {
|
|
/**
|
|
* @param pokemon - The Pokemon with the redirection ability
|
|
* @param args - The args passed to the `AbAttr`:
|
|
* - `[0]` - The id of the {@linkcode Move} used
|
|
* - `[1]` - The target's battler index (before redirection)
|
|
* - `[2]` - The Pokemon that used the move being redirected
|
|
*/
|
|
|
|
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
|
if (!this.canRedirect(args[0] as MoveId, args[2] as Pokemon)) {
|
|
return false;
|
|
}
|
|
const target = args[1] as NumberHolder;
|
|
const newTarget = pokemon.getBattlerIndex();
|
|
return target.value !== newTarget;
|
|
}
|
|
|
|
override apply(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
const target = args[1] as NumberHolder;
|
|
const newTarget = pokemon.getBattlerIndex();
|
|
target.value = newTarget;
|
|
}
|
|
|
|
canRedirect(moveId: MoveId, _user: Pokemon): boolean {
|
|
const move = allMoves[moveId];
|
|
return !![MoveTarget.NEAR_OTHER, MoveTarget.OTHER].find(t => move.moveTarget === t);
|
|
}
|
|
}
|
|
|
|
export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr {
|
|
public type: PokemonType;
|
|
|
|
constructor(type: PokemonType) {
|
|
super();
|
|
this.type = type;
|
|
}
|
|
|
|
canRedirect(moveId: MoveId, user: Pokemon): boolean {
|
|
return super.canRedirect(moveId, user) && user.getMoveType(allMoves[moveId]) === this.type;
|
|
}
|
|
}
|
|
|
|
export class BlockRedirectAbAttr extends AbAttr {}
|
|
|
|
/**
|
|
* Used by Early Bird, makes the pokemon wake up faster
|
|
* @param statusEffect - The {@linkcode StatusEffect} to check for
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class ReduceStatusEffectDurationAbAttr extends AbAttr {
|
|
private statusEffect: StatusEffect;
|
|
|
|
constructor(statusEffect: StatusEffect) {
|
|
super(false);
|
|
|
|
this.statusEffect = statusEffect;
|
|
}
|
|
|
|
override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
|
return args[1] instanceof NumberHolder && args[0] === this.statusEffect;
|
|
}
|
|
|
|
/**
|
|
* Reduces the number of sleep turns remaining by an extra 1 when applied
|
|
* @param args - The args passed to the `AbAttr`:
|
|
* - `[0]` - The {@linkcode StatusEffect} of the Pokemon
|
|
* - `[1]` - The number of turns remaining until the status is healed
|
|
*/
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
args[1].value -= 1;
|
|
}
|
|
}
|
|
|
|
export class FlinchEffectAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(true);
|
|
}
|
|
}
|
|
|
|
export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr {
|
|
private stats: BattleStat[];
|
|
private stages: number;
|
|
|
|
constructor(stats: BattleStat[], stages: number) {
|
|
super();
|
|
|
|
this.stats = stats;
|
|
this.stages = stages;
|
|
}
|
|
|
|
override apply(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
globalScene.phaseManager.unshiftNew(
|
|
"StatStageChangePhase",
|
|
pokemon.getBattlerIndex(),
|
|
true,
|
|
this.stats,
|
|
this.stages,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class IncreasePpAbAttr extends AbAttr {}
|
|
|
|
export class ForceSwitchOutImmunityAbAttr extends AbAttr {
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
export class ReduceBerryUseThresholdAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
|
const hpRatio = pokemon.getHpRatio();
|
|
return args[0].value < hpRatio;
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
args[0].value *= 2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ability attribute used for abilites that change the ability owner's weight
|
|
* Used for Heavy Metal (doubling weight) and Light Metal (halving weight)
|
|
*/
|
|
export class WeightMultiplierAbAttr extends AbAttr {
|
|
private multiplier: number;
|
|
|
|
constructor(multiplier: number) {
|
|
super(false);
|
|
|
|
this.multiplier = multiplier;
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as NumberHolder).value *= this.multiplier;
|
|
}
|
|
}
|
|
|
|
export class SyncEncounterNatureAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
override apply(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
(args[0] as Pokemon).setNature(pokemon.getNature());
|
|
}
|
|
}
|
|
|
|
export class MoveAbilityBypassAbAttr extends AbAttr {
|
|
private moveIgnoreFunc: (pokemon: Pokemon, move: Move) => boolean;
|
|
|
|
constructor(moveIgnoreFunc?: (pokemon: Pokemon, move: Move) => boolean) {
|
|
super(false);
|
|
|
|
this.moveIgnoreFunc = moveIgnoreFunc || ((_pokemon, _move) => true);
|
|
}
|
|
|
|
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
|
return this.moveIgnoreFunc(pokemon, args[0] as Move);
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
export class AlwaysHitAbAttr extends AbAttr {}
|
|
|
|
/** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */
|
|
export class IgnoreProtectOnContactAbAttr extends AbAttr {}
|
|
|
|
/**
|
|
* Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Infiltrator_(Ability) | Infiltrator}.
|
|
* Allows the source's moves to bypass the effects of opposing Light Screen, Reflect, Aurora Veil, Safeguard, Mist, and Substitute.
|
|
*/
|
|
export class InfiltratorAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
|
return args[0] instanceof BooleanHolder;
|
|
}
|
|
|
|
/**
|
|
* Sets a flag to bypass screens, Substitute, Safeguard, and Mist
|
|
* @param _pokemon n/a
|
|
* @param _passive n/a
|
|
* @param _simulated n/a
|
|
* @param _cancelled n/a
|
|
* @param args `[0]` a {@linkcode BooleanHolder | BooleanHolder} containing the flag
|
|
*/
|
|
override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: null, args: any[]): void {
|
|
const bypassed = args[0];
|
|
bypassed.value = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Magic_Bounce_(ability) | Magic Bounce}.
|
|
* Allows the source to bounce back {@linkcode MoveFlags.REFLECTABLE | Reflectable}
|
|
* moves as if the user had used {@linkcode MoveId.MAGIC_COAT | Magic Coat}.
|
|
*/
|
|
export class ReflectStatusMoveAbAttr extends AbAttr {}
|
|
|
|
export class NoTransformAbilityAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
}
|
|
|
|
export class NoFusionAbilityAbAttr extends AbAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
}
|
|
|
|
export class IgnoreTypeImmunityAbAttr extends AbAttr {
|
|
private defenderType: PokemonType;
|
|
private allowedMoveTypes: PokemonType[];
|
|
|
|
constructor(defenderType: PokemonType, allowedMoveTypes: PokemonType[]) {
|
|
super(false);
|
|
this.defenderType = defenderType;
|
|
this.allowedMoveTypes = allowedMoveTypes;
|
|
}
|
|
|
|
override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
|
return this.defenderType === (args[1] as PokemonType) && this.allowedMoveTypes.includes(args[0] as PokemonType);
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ignores the type immunity to Status Effects of the defender if the defender is of a certain type
|
|
*/
|
|
export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr {
|
|
private statusEffect: StatusEffect[];
|
|
private defenderType: PokemonType[];
|
|
|
|
constructor(statusEffect: StatusEffect[], defenderType: PokemonType[]) {
|
|
super(false);
|
|
|
|
this.statusEffect = statusEffect;
|
|
this.defenderType = defenderType;
|
|
}
|
|
|
|
override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean {
|
|
return this.statusEffect.includes(args[0] as StatusEffect) && this.defenderType.includes(args[1] as PokemonType);
|
|
}
|
|
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
cancelled.value = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gives money to the user after the battle.
|
|
*
|
|
* @extends PostBattleAbAttr
|
|
* @see {@linkcode applyPostBattle}
|
|
*/
|
|
export class MoneyAbAttr extends PostBattleAbAttr {
|
|
override canApplyPostBattle(_pokemon: Pokemon, _passive: boolean, simulated: boolean, args: any[]): boolean {
|
|
return !simulated && args[0];
|
|
}
|
|
|
|
/**
|
|
* @param _pokemon {@linkcode Pokemon} that is the user of this ability.
|
|
* @param _passive N/A
|
|
* @param _args - `[0]`: boolean for if the battle ended in a victory
|
|
*/
|
|
override applyPostBattle(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
|
globalScene.currentBattle.moneyScattered += globalScene.getWaveMoneyAmount(0.2);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies a stat change after a Pokémon is summoned,
|
|
* conditioned on the presence of a specific arena tag.
|
|
*
|
|
* @extends PostSummonStatStageChangeAbAttr
|
|
*/
|
|
export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageChangeAbAttr {
|
|
/**
|
|
* The type of arena tag that conditions the stat change.
|
|
* @private
|
|
*/
|
|
private tagType: ArenaTagType;
|
|
|
|
/**
|
|
* Creates an instance of PostSummonStatStageChangeOnArenaAbAttr.
|
|
* Initializes the stat change to increase Attack by 1 stage if the specified arena tag is present.
|
|
*
|
|
* @param {ArenaTagType} tagType - The type of arena tag to check for.
|
|
*/
|
|
constructor(tagType: ArenaTagType) {
|
|
super([Stat.ATK], 1, true, false);
|
|
this.tagType = tagType;
|
|
}
|
|
|
|
override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
|
const side = pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
|
return (
|
|
(globalScene.arena.getTagOnSide(this.tagType, side) ?? false) &&
|
|
super.canApplyPostSummon(pokemon, passive, simulated, args)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Applies the post-summon stat change if the specified arena tag is present on pokemon's side.
|
|
* This is used in Wind Rider ability.
|
|
*
|
|
* @param {Pokemon} pokemon - The Pokémon being summoned.
|
|
* @param {boolean} passive - Whether the effect is passive.
|
|
* @param {any[]} args - Additional arguments.
|
|
*/
|
|
override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {
|
|
super.applyPostSummon(pokemon, passive, simulated, args);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Takes no damage from the first hit of a damaging move.
|
|
* This is used in the Disguise and Ice Face abilities.
|
|
*
|
|
* Does not apply to a user's substitute
|
|
* @extends ReceivedMoveDamageMultiplierAbAttr
|
|
*/
|
|
export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
|
|
private multiplier: number;
|
|
private tagType: BattlerTagType;
|
|
private recoilDamageFunc?: (pokemon: Pokemon) => number;
|
|
private triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string;
|
|
|
|
constructor(
|
|
condition: PokemonDefendCondition,
|
|
multiplier: number,
|
|
tagType: BattlerTagType,
|
|
triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string,
|
|
recoilDamageFunc?: (pokemon: Pokemon) => number,
|
|
) {
|
|
super(condition, multiplier);
|
|
|
|
this.multiplier = multiplier;
|
|
this.tagType = tagType;
|
|
this.recoilDamageFunc = recoilDamageFunc;
|
|
this.triggerMessageFunc = triggerMessageFunc;
|
|
}
|
|
|
|
override canApplyPreDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
attacker: Pokemon,
|
|
move: Move,
|
|
_cancelled: BooleanHolder | null,
|
|
_args: any[],
|
|
): boolean {
|
|
return this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon);
|
|
}
|
|
|
|
/**
|
|
* Applies the pre-defense ability to the Pokémon.
|
|
* Removes the appropriate `BattlerTagType` when hit by an attack and is in its defense form.
|
|
*
|
|
* @param pokemon The Pokémon with the ability.
|
|
* @param _passive n/a
|
|
* @param _attacker The attacking Pokémon.
|
|
* @param _move The move being used.
|
|
* @param _cancelled n/a
|
|
* @param args Additional arguments.
|
|
*/
|
|
override applyPreDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
if (!simulated) {
|
|
(args[0] as NumberHolder).value = this.multiplier;
|
|
pokemon.removeTag(this.tagType);
|
|
if (this.recoilDamageFunc) {
|
|
pokemon.damageAndUpdate(this.recoilDamageFunc(pokemon), {
|
|
result: HitResult.INDIRECT,
|
|
ignoreSegments: true,
|
|
ignoreFaintPhase: true,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the message triggered when the Pokémon avoids damage using the form-changing ability.
|
|
* @param pokemon The Pokémon with the ability.
|
|
* @param abilityName The name of the ability.
|
|
* @param _args n/a
|
|
* @returns The trigger message.
|
|
*/
|
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
|
|
return this.triggerMessageFunc(pokemon, abilityName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Base class for defining {@linkcode Ability} attributes before summon
|
|
* (should use {@linkcode PostSummonAbAttr} for most ability)
|
|
* @see {@linkcode applyPreSummon()}
|
|
*/
|
|
export class PreSummonAbAttr extends AbAttr {
|
|
applyPreSummon(_pokemon: Pokemon, _passive: boolean, _args: any[]): void {}
|
|
|
|
canApplyPreSummon(_pokemon: Pokemon, _passive: boolean, _args: any[]): boolean {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class IllusionPreSummonAbAttr extends PreSummonAbAttr {
|
|
/**
|
|
* Apply a new illusion when summoning Zoroark if the illusion is available
|
|
*
|
|
* @param pokemon - The Pokémon with the Illusion ability.
|
|
* @param _passive - N/A
|
|
* @param _args - N/A
|
|
* @returns Whether the illusion was applied.
|
|
*/
|
|
override applyPreSummon(pokemon: Pokemon, _passive: boolean, _args: any[]): void {
|
|
const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(
|
|
p => p.isAllowedInBattle(),
|
|
);
|
|
const lastPokemon: Pokemon = party.filter(p => p !== pokemon).at(-1) || pokemon;
|
|
pokemon.setIllusion(lastPokemon);
|
|
}
|
|
|
|
override canApplyPreSummon(pokemon: Pokemon, _passive: boolean, _args: any[]): boolean {
|
|
if (pokemon.hasTrainer()) {
|
|
const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(
|
|
p => p.isAllowedInBattle(),
|
|
);
|
|
const lastPokemon: Pokemon = party.filter(p => p !== pokemon).at(-1) || pokemon;
|
|
const speciesId = lastPokemon.species.speciesId;
|
|
|
|
// If the last conscious Pokémon in the party is a Terastallized Ogerpon or Terapagos, Illusion will not activate.
|
|
// Illusion will also not activate if the Pokémon with Illusion is Terastallized and the last Pokémon in the party is Ogerpon or Terapagos.
|
|
if (
|
|
lastPokemon === pokemon ||
|
|
((speciesId === SpeciesId.OGERPON || speciesId === SpeciesId.TERAPAGOS) &&
|
|
(lastPokemon.isTerastallized || pokemon.isTerastallized))
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
return !pokemon.summonData.illusionBroken;
|
|
}
|
|
}
|
|
|
|
export class IllusionBreakAbAttr extends AbAttr {
|
|
override apply(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder | null,
|
|
_args: any[],
|
|
): void {
|
|
pokemon.breakIllusion();
|
|
pokemon.summonData.illusionBroken = true;
|
|
}
|
|
}
|
|
|
|
export class PostDefendIllusionBreakAbAttr extends PostDefendAbAttr {
|
|
/**
|
|
* Destroy the illusion upon taking damage
|
|
*
|
|
* @param pokemon - The Pokémon with the Illusion ability.
|
|
* @param _passive - unused
|
|
* @param _attacker - The attacking Pokémon.
|
|
* @param _move - The move being used.
|
|
* @param _hitResult - The type of hitResult the pokemon got
|
|
* @param _args - unused
|
|
* @returns - Whether the illusion was destroyed.
|
|
*/
|
|
override applyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
_hitResult: HitResult,
|
|
_args: any[],
|
|
): void {
|
|
pokemon.breakIllusion();
|
|
pokemon.summonData.illusionBroken = true;
|
|
}
|
|
|
|
override canApplyPostDefend(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_attacker: Pokemon,
|
|
_move: Move,
|
|
hitResult: HitResult,
|
|
_args: any[],
|
|
): boolean {
|
|
const breakIllusion: HitResult[] = [
|
|
HitResult.EFFECTIVE,
|
|
HitResult.SUPER_EFFECTIVE,
|
|
HitResult.NOT_VERY_EFFECTIVE,
|
|
HitResult.ONE_HIT_KO,
|
|
];
|
|
return breakIllusion.includes(hitResult) && !!pokemon.summonData.illusion;
|
|
}
|
|
}
|
|
|
|
export class IllusionPostBattleAbAttr extends PostBattleAbAttr {
|
|
/**
|
|
* Break the illusion once the battle ends
|
|
*
|
|
* @param pokemon - The Pokémon with the Illusion ability.
|
|
* @param _passive - Unused
|
|
* @param _args - Unused
|
|
* @returns - Whether the illusion was applied.
|
|
*/
|
|
override applyPostBattle(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {
|
|
pokemon.breakIllusion();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If a Pokémon with this Ability selects a damaging move, it has a 30% chance of going first in its priority bracket. If the Ability activates, this is announced at the start of the turn (after move selection).
|
|
*
|
|
* @extends AbAttr
|
|
*/
|
|
export class BypassSpeedChanceAbAttr extends AbAttr {
|
|
public chance: number;
|
|
|
|
/**
|
|
* @param {number} chance probability of ability being active.
|
|
*/
|
|
constructor(chance: number) {
|
|
super(true);
|
|
this.chance = chance;
|
|
}
|
|
|
|
override canApply(pokemon: Pokemon, _passive: boolean, simulated: boolean, args: any[]): boolean {
|
|
const bypassSpeed = args[0] as BooleanHolder;
|
|
const turnCommand = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
|
|
const isCommandFight = turnCommand?.command === Command.FIGHT;
|
|
const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null;
|
|
const isDamageMove = move?.category === MoveCategory.PHYSICAL || move?.category === MoveCategory.SPECIAL;
|
|
return (
|
|
!simulated && !bypassSpeed.value && pokemon.randBattleSeedInt(100) < this.chance && isCommandFight && isDamageMove
|
|
);
|
|
}
|
|
|
|
/**
|
|
* bypass move order in their priority bracket when pokemon choose damaging move
|
|
* @param {Pokemon} _pokemon {@linkcode Pokemon} the Pokemon applying this ability
|
|
* @param {boolean} _passive N/A
|
|
* @param {BooleanHolder} _cancelled N/A
|
|
* @param {any[]} args [0] {@linkcode BooleanHolder} set to true when the ability activated
|
|
*/
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
const bypassSpeed = args[0] as BooleanHolder;
|
|
bypassSpeed.value = true;
|
|
}
|
|
|
|
getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string {
|
|
return i18next.t("abilityTriggers:quickDraw", { pokemonName: getPokemonNameWithAffix(pokemon) });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This attribute checks if a Pokemon's move meets a provided condition to determine if the Pokemon can use Quick Claw
|
|
* It was created because Pokemon with the ability Mycelium Might cannot access Quick Claw's benefits when using status moves.
|
|
*/
|
|
export class PreventBypassSpeedChanceAbAttr extends AbAttr {
|
|
private condition: (pokemon: Pokemon, move: Move) => boolean;
|
|
|
|
/**
|
|
* @param {function} condition - checks if a move meets certain conditions
|
|
*/
|
|
constructor(condition: (pokemon: Pokemon, move: Move) => boolean) {
|
|
super(true);
|
|
this.condition = condition;
|
|
}
|
|
|
|
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
const turnCommand = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
|
|
const isCommandFight = turnCommand?.command === Command.FIGHT;
|
|
const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null;
|
|
return isCommandFight && this.condition(pokemon, move!);
|
|
}
|
|
|
|
/**
|
|
* @argument {boolean} bypassSpeed - determines if a Pokemon is able to bypass speed at the moment
|
|
* @argument {boolean} canCheckHeldItems - determines if a Pokemon has access to Quick Claw's effects or not
|
|
*/
|
|
override apply(
|
|
_pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
args: any[],
|
|
): void {
|
|
const bypassSpeed = args[0] as BooleanHolder;
|
|
const canCheckHeldItems = args[1] as BooleanHolder;
|
|
bypassSpeed.value = false;
|
|
canCheckHeldItems.value = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This applies a terrain-based type change to the Pokemon.
|
|
* Used by Mimicry.
|
|
*/
|
|
export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr {
|
|
constructor() {
|
|
super(true);
|
|
}
|
|
|
|
override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
|
return !pokemon.isTerastallized;
|
|
}
|
|
|
|
override apply(
|
|
pokemon: Pokemon,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_cancelled: BooleanHolder,
|
|
_args: any[],
|
|
): void {
|
|
const currentTerrain = globalScene.arena.getTerrainType();
|
|
const typeChange: PokemonType[] = this.determineTypeChange(pokemon, currentTerrain);
|
|
if (typeChange.length !== 0) {
|
|
if (pokemon.summonData.addedType && typeChange.includes(pokemon.summonData.addedType)) {
|
|
pokemon.summonData.addedType = null;
|
|
}
|
|
pokemon.summonData.types = typeChange;
|
|
pokemon.updateInfo();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves the type(s) the Pokemon should change to in response to a terrain
|
|
* @param pokemon
|
|
* @param currentTerrain {@linkcode TerrainType}
|
|
* @returns a list of type(s)
|
|
*/
|
|
private determineTypeChange(pokemon: Pokemon, currentTerrain: TerrainType): PokemonType[] {
|
|
const typeChange: PokemonType[] = [];
|
|
switch (currentTerrain) {
|
|
case TerrainType.ELECTRIC:
|
|
typeChange.push(PokemonType.ELECTRIC);
|
|
break;
|
|
case TerrainType.MISTY:
|
|
typeChange.push(PokemonType.FAIRY);
|
|
break;
|
|
case TerrainType.GRASSY:
|
|
typeChange.push(PokemonType.GRASS);
|
|
break;
|
|
case TerrainType.PSYCHIC:
|
|
typeChange.push(PokemonType.PSYCHIC);
|
|
break;
|
|
default:
|
|
pokemon.getTypes(false, false, true).forEach(t => {
|
|
typeChange.push(t);
|
|
});
|
|
break;
|
|
}
|
|
return typeChange;
|
|
}
|
|
|
|
override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
|
return globalScene.arena.getTerrainType() !== TerrainType.NONE && this.canApply(pokemon, passive, simulated, args);
|
|
}
|
|
|
|
/**
|
|
* Checks if the Pokemon should change types if summoned into an active terrain
|
|
*/
|
|
override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, _args: any[]): void {
|
|
this.apply(pokemon, passive, simulated, new BooleanHolder(false), []);
|
|
}
|
|
|
|
override getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]) {
|
|
const currentTerrain = globalScene.arena.getTerrainType();
|
|
const pokemonNameWithAffix = getPokemonNameWithAffix(pokemon);
|
|
if (currentTerrain === TerrainType.NONE) {
|
|
return i18next.t("abilityTriggers:pokemonTypeChangeRevert", { pokemonNameWithAffix });
|
|
}
|
|
const moveType = i18next.t(`pokemonInfo:Type.${PokemonType[this.determineTypeChange(pokemon, currentTerrain)[0]]}`);
|
|
return i18next.t("abilityTriggers:pokemonTypeChange", { pokemonNameWithAffix, moveType });
|
|
}
|
|
}
|
|
|
|
class ForceSwitchOutHelper {
|
|
constructor(private switchType: SwitchType) {}
|
|
|
|
/**
|
|
* Handles the logic for switching out a Pokémon based on battle conditions, HP, and the switch type.
|
|
*
|
|
* @param pokemon The {@linkcode Pokemon} attempting to switch out.
|
|
* @returns `true` if the switch is successful
|
|
*/
|
|
// TODO: Make this cancel pending move phases on the switched out target
|
|
public switchOutLogic(pokemon: Pokemon): boolean {
|
|
const switchOutTarget = pokemon;
|
|
/**
|
|
* If the switch-out target is a player-controlled Pokémon, the function checks:
|
|
* - Whether there are available party members to switch in.
|
|
* - If the Pokémon is still alive (hp > 0), and if so, it leaves the field and a new SwitchPhase is initiated.
|
|
*/
|
|
if (switchOutTarget.isPlayer()) {
|
|
if (globalScene.getPlayerParty().filter(p => p.isAllowedInBattle() && !p.isOnField()).length < 1) {
|
|
return false;
|
|
}
|
|
|
|
if (switchOutTarget.hp > 0) {
|
|
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
|
globalScene.phaseManager.prependNewToPhase(
|
|
"MoveEndPhase",
|
|
"SwitchPhase",
|
|
this.switchType,
|
|
switchOutTarget.getFieldIndex(),
|
|
true,
|
|
true,
|
|
);
|
|
return true;
|
|
}
|
|
/**
|
|
* For non-wild battles, it checks if the opposing party has any available Pokémon to switch in.
|
|
* If yes, the Pokémon leaves the field and a new SwitchSummonPhase is initiated.
|
|
*/
|
|
} else if (globalScene.currentBattle.battleType !== BattleType.WILD) {
|
|
if (globalScene.getEnemyParty().filter(p => p.isAllowedInBattle() && !p.isOnField()).length < 1) {
|
|
return false;
|
|
}
|
|
if (switchOutTarget.hp > 0) {
|
|
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
|
const summonIndex = globalScene.currentBattle.trainer
|
|
? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot)
|
|
: 0;
|
|
globalScene.phaseManager.prependNewToPhase(
|
|
"MoveEndPhase",
|
|
"SwitchSummonPhase",
|
|
this.switchType,
|
|
switchOutTarget.getFieldIndex(),
|
|
summonIndex,
|
|
false,
|
|
false,
|
|
);
|
|
return true;
|
|
}
|
|
/**
|
|
* For wild Pokémon battles, the Pokémon will flee if the conditions are met (waveIndex and double battles).
|
|
* It will not flee if it is a Mystery Encounter with fleeing disabled (checked in `getSwitchOutCondition()`) or if it is a wave 10x wild boss
|
|
*/
|
|
} else {
|
|
const allyPokemon = switchOutTarget.getAlly();
|
|
|
|
if (!globalScene.currentBattle.waveIndex || globalScene.currentBattle.waveIndex % 10 === 0) {
|
|
return false;
|
|
}
|
|
|
|
if (switchOutTarget.hp > 0) {
|
|
switchOutTarget.leaveField(false);
|
|
globalScene.phaseManager.queueMessage(
|
|
i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }),
|
|
null,
|
|
true,
|
|
500,
|
|
);
|
|
if (globalScene.currentBattle.double && !isNullOrUndefined(allyPokemon)) {
|
|
globalScene.redirectPokemonMoves(switchOutTarget, allyPokemon);
|
|
}
|
|
}
|
|
|
|
if (!allyPokemon?.isActive(true)) {
|
|
globalScene.clearEnemyHeldItemModifiers();
|
|
|
|
if (switchOutTarget.hp) {
|
|
globalScene.phaseManager.pushNew("BattleEndPhase", false);
|
|
|
|
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
|
|
globalScene.phaseManager.pushNew("SelectBiomePhase");
|
|
}
|
|
|
|
globalScene.phaseManager.pushNew("NewBattlePhase");
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Determines if a Pokémon can switch out based on its status, the opponent's status, and battle conditions.
|
|
*
|
|
* @param pokemon The Pokémon attempting to switch out.
|
|
* @param opponent The opponent Pokémon.
|
|
* @returns `true` if the switch-out condition is met
|
|
*/
|
|
public getSwitchOutCondition(pokemon: Pokemon, opponent: Pokemon): boolean {
|
|
const switchOutTarget = pokemon;
|
|
const player = switchOutTarget.isPlayer();
|
|
|
|
if (player) {
|
|
const blockedByAbility = new BooleanHolder(false);
|
|
applyAbAttrs("ForceSwitchOutImmunityAbAttr", opponent, blockedByAbility);
|
|
return !blockedByAbility.value;
|
|
}
|
|
|
|
if (!player && globalScene.currentBattle.battleType === BattleType.WILD) {
|
|
if (!globalScene.currentBattle.waveIndex && globalScene.currentBattle.waveIndex % 10 === 0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (
|
|
!player &&
|
|
globalScene.currentBattle.isBattleMysteryEncounter() &&
|
|
!globalScene.currentBattle.mysteryEncounter?.fleeAllowed
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
const party = player ? globalScene.getPlayerParty() : globalScene.getEnemyParty();
|
|
return (
|
|
(!player && globalScene.currentBattle.battleType === BattleType.WILD) ||
|
|
party.filter(
|
|
p =>
|
|
p.isAllowedInBattle() &&
|
|
!p.isOnField() &&
|
|
(player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot),
|
|
).length > 0
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns a message if the switch-out attempt fails due to ability effects.
|
|
*
|
|
* @param target The target Pokémon.
|
|
* @returns The failure message, or `null` if no failure.
|
|
*/
|
|
public getFailedText(target: Pokemon): string | null {
|
|
const blockedByAbility = new BooleanHolder(false);
|
|
applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility);
|
|
return blockedByAbility.value
|
|
? i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) })
|
|
: null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates the amount of recovery from the Shell Bell item.
|
|
*
|
|
* If the Pokémon is holding a Shell Bell, this function computes the amount of health
|
|
* recovered based on the damage dealt in the current turn. The recovery is multiplied by the
|
|
* Shell Bell's modifier (if any).
|
|
*
|
|
* @param pokemon - The Pokémon whose Shell Bell recovery is being calculated.
|
|
* @returns The amount of health recovered by Shell Bell.
|
|
*/
|
|
function calculateShellBellRecovery(pokemon: Pokemon): number {
|
|
const shellBellModifier = pokemon.getHeldItems().find(m => m instanceof HitHealModifier);
|
|
if (shellBellModifier) {
|
|
return toDmgValue(pokemon.turnData.totalDamageDealt / 8) * shellBellModifier.stackCount;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Triggers after the Pokemon takes any damage
|
|
* @extends AbAttr
|
|
*/
|
|
export class PostDamageAbAttr extends AbAttr {
|
|
public canApplyPostDamage(
|
|
_pokemon: Pokemon,
|
|
_damage: number,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_args: any[],
|
|
_source?: Pokemon,
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
public applyPostDamage(
|
|
_pokemon: Pokemon,
|
|
_damage: number,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_args: any[],
|
|
_source?: Pokemon,
|
|
): void {}
|
|
}
|
|
|
|
/**
|
|
* Ability attribute for forcing a Pokémon to switch out after its health drops below half.
|
|
* This attribute checks various conditions related to the damage received, the moves used by the Pokémon
|
|
* and its opponents, and determines whether a forced switch-out should occur.
|
|
*
|
|
* Used by Wimp Out and Emergency Exit
|
|
*
|
|
* @extends PostDamageAbAttr
|
|
* @see {@linkcode applyPostDamage}
|
|
*/
|
|
export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr {
|
|
private helper: ForceSwitchOutHelper = new ForceSwitchOutHelper(SwitchType.SWITCH);
|
|
private hpRatio: number;
|
|
|
|
constructor(hpRatio = 0.5) {
|
|
super();
|
|
this.hpRatio = hpRatio;
|
|
}
|
|
|
|
// TODO: Refactor to use more early returns
|
|
public override canApplyPostDamage(
|
|
pokemon: Pokemon,
|
|
damage: number,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_args: any[],
|
|
source?: Pokemon,
|
|
): boolean {
|
|
const moveHistory = pokemon.getMoveHistory();
|
|
// Will not activate when the Pokémon's HP is lowered by cutting its own HP
|
|
const fordbiddenAttackingMoves = [MoveId.BELLY_DRUM, MoveId.SUBSTITUTE, MoveId.CURSE, MoveId.PAIN_SPLIT];
|
|
if (moveHistory.length > 0) {
|
|
const lastMoveUsed = moveHistory[moveHistory.length - 1];
|
|
if (fordbiddenAttackingMoves.includes(lastMoveUsed.move)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Dragon Tail and Circle Throw switch out Pokémon before the Ability activates.
|
|
const fordbiddenDefendingMoves = [MoveId.DRAGON_TAIL, MoveId.CIRCLE_THROW];
|
|
if (source) {
|
|
const enemyMoveHistory = source.getMoveHistory();
|
|
if (enemyMoveHistory.length > 0) {
|
|
const enemyLastMoveUsed = enemyMoveHistory[enemyMoveHistory.length - 1];
|
|
// Will not activate if the Pokémon's HP falls below half while it is in the air during Sky Drop.
|
|
if (
|
|
fordbiddenDefendingMoves.includes(enemyLastMoveUsed.move) ||
|
|
(enemyLastMoveUsed.move === MoveId.SKY_DROP && enemyLastMoveUsed.result === MoveResult.OTHER)
|
|
) {
|
|
return false;
|
|
// Will not activate if the Pokémon's HP falls below half by a move affected by Sheer Force.
|
|
// TODO: Make this use the sheer force disable condition
|
|
}
|
|
if (allMoves[enemyLastMoveUsed.move].chance >= 0 && source.hasAbility(AbilityId.SHEER_FORCE)) {
|
|
return false;
|
|
}
|
|
// Activate only after the last hit of multistrike moves
|
|
if (source.turnData.hitsLeft > 1) {
|
|
return false;
|
|
}
|
|
if (source.turnData.hitCount > 1) {
|
|
damage = pokemon.turnData.damageTaken;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pokemon.hp + damage >= pokemon.getMaxHp() * this.hpRatio) {
|
|
const shellBellHeal = calculateShellBellRecovery(pokemon);
|
|
if (pokemon.hp - shellBellHeal < pokemon.getMaxHp() * this.hpRatio) {
|
|
for (const opponent of pokemon.getOpponents()) {
|
|
if (!this.helper.getSwitchOutCondition(pokemon, opponent)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Applies the switch-out logic after the Pokémon takes damage.
|
|
* Checks various conditions based on the moves used by the Pokémon, the opponents' moves, and
|
|
* the Pokémon's health after damage to determine whether the switch-out should occur.
|
|
*
|
|
* @param pokemon The Pokémon that took damage.
|
|
* @param _damage N/A
|
|
* @param _passive N/A
|
|
* @param _simulated Whether the ability is being simulated.
|
|
* @param _args N/A
|
|
* @param _source N/A
|
|
*/
|
|
public override applyPostDamage(
|
|
pokemon: Pokemon,
|
|
_damage: number,
|
|
_passive: boolean,
|
|
_simulated: boolean,
|
|
_args: any[],
|
|
_source?: Pokemon,
|
|
): void {
|
|
this.helper.switchOutLogic(pokemon);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Map of all ability attribute constructors, for use with the `.is` method.
|
|
*/
|
|
const AbilityAttrs = Object.freeze({
|
|
BlockRecoilDamageAttr,
|
|
DoubleBattleChanceAbAttr,
|
|
PostBattleInitAbAttr,
|
|
PostBattleInitFormChangeAbAttr,
|
|
PostTeraFormChangeStatChangeAbAttr,
|
|
ClearWeatherAbAttr,
|
|
ClearTerrainAbAttr,
|
|
PreDefendAbAttr,
|
|
PreDefendFullHpEndureAbAttr,
|
|
BlockItemTheftAbAttr,
|
|
StabBoostAbAttr,
|
|
ReceivedMoveDamageMultiplierAbAttr,
|
|
AlliedFieldDamageReductionAbAttr,
|
|
ReceivedTypeDamageMultiplierAbAttr,
|
|
TypeImmunityAbAttr,
|
|
AttackTypeImmunityAbAttr,
|
|
TypeImmunityHealAbAttr,
|
|
NonSuperEffectiveImmunityAbAttr,
|
|
FullHpResistTypeAbAttr,
|
|
PostDefendAbAttr,
|
|
FieldPriorityMoveImmunityAbAttr,
|
|
PostStatStageChangeAbAttr,
|
|
MoveImmunityAbAttr,
|
|
WonderSkinAbAttr,
|
|
MoveImmunityStatStageChangeAbAttr,
|
|
ReverseDrainAbAttr,
|
|
PostDefendStatStageChangeAbAttr,
|
|
PostDefendHpGatedStatStageChangeAbAttr,
|
|
PostDefendApplyArenaTrapTagAbAttr,
|
|
PostDefendApplyBattlerTagAbAttr,
|
|
PostDefendTypeChangeAbAttr,
|
|
PostDefendTerrainChangeAbAttr,
|
|
PostDefendContactApplyStatusEffectAbAttr,
|
|
EffectSporeAbAttr,
|
|
PostDefendContactApplyTagChanceAbAttr,
|
|
PostDefendCritStatStageChangeAbAttr,
|
|
PostDefendContactDamageAbAttr,
|
|
PostDefendPerishSongAbAttr,
|
|
PostDefendWeatherChangeAbAttr,
|
|
PostDefendAbilitySwapAbAttr,
|
|
PostDefendAbilityGiveAbAttr,
|
|
PostDefendMoveDisableAbAttr,
|
|
PostStatStageChangeStatStageChangeAbAttr,
|
|
PreAttackAbAttr,
|
|
MoveEffectChanceMultiplierAbAttr,
|
|
IgnoreMoveEffectsAbAttr,
|
|
VariableMovePowerAbAttr,
|
|
FieldPreventExplosiveMovesAbAttr,
|
|
FieldMultiplyStatAbAttr,
|
|
MoveTypeChangeAbAttr,
|
|
PokemonTypeChangeAbAttr,
|
|
AddSecondStrikeAbAttr,
|
|
DamageBoostAbAttr,
|
|
MovePowerBoostAbAttr,
|
|
MoveTypePowerBoostAbAttr,
|
|
LowHpMoveTypePowerBoostAbAttr,
|
|
VariableMovePowerBoostAbAttr,
|
|
FieldMovePowerBoostAbAttr,
|
|
PreAttackFieldMoveTypePowerBoostAbAttr,
|
|
FieldMoveTypePowerBoostAbAttr,
|
|
UserFieldMoveTypePowerBoostAbAttr,
|
|
AllyMoveCategoryPowerBoostAbAttr,
|
|
StatMultiplierAbAttr,
|
|
PostAttackAbAttr,
|
|
AllyStatMultiplierAbAttr,
|
|
ExecutedMoveAbAttr,
|
|
GorillaTacticsAbAttr,
|
|
PostAttackStealHeldItemAbAttr,
|
|
PostAttackApplyStatusEffectAbAttr,
|
|
PostAttackContactApplyStatusEffectAbAttr,
|
|
PostAttackApplyBattlerTagAbAttr,
|
|
PostDefendStealHeldItemAbAttr,
|
|
PostSetStatusAbAttr,
|
|
SynchronizeStatusAbAttr,
|
|
PostVictoryAbAttr,
|
|
PostVictoryFormChangeAbAttr,
|
|
PostKnockOutAbAttr,
|
|
PostKnockOutStatStageChangeAbAttr,
|
|
CopyFaintedAllyAbilityAbAttr,
|
|
IgnoreOpponentStatStagesAbAttr,
|
|
IntimidateImmunityAbAttr,
|
|
PostIntimidateStatStageChangeAbAttr,
|
|
PostSummonAbAttr,
|
|
PostSummonRemoveEffectAbAttr,
|
|
PostSummonRemoveArenaTagAbAttr,
|
|
PostSummonAddArenaTagAbAttr,
|
|
PostSummonMessageAbAttr,
|
|
PostSummonUnnamedMessageAbAttr,
|
|
PostSummonAddBattlerTagAbAttr,
|
|
PostSummonRemoveBattlerTagAbAttr,
|
|
PostSummonStatStageChangeAbAttr,
|
|
PostSummonAllyHealAbAttr,
|
|
PostSummonClearAllyStatStagesAbAttr,
|
|
DownloadAbAttr,
|
|
PostSummonWeatherChangeAbAttr,
|
|
PostSummonTerrainChangeAbAttr,
|
|
PostSummonHealStatusAbAttr,
|
|
PostSummonFormChangeAbAttr,
|
|
PostSummonCopyAbilityAbAttr,
|
|
PostSummonUserFieldRemoveStatusEffectAbAttr,
|
|
PostSummonCopyAllyStatsAbAttr,
|
|
PostSummonTransformAbAttr,
|
|
PostSummonWeatherSuppressedFormChangeAbAttr,
|
|
PostSummonFormChangeByWeatherAbAttr,
|
|
CommanderAbAttr,
|
|
PreSwitchOutAbAttr,
|
|
PreSwitchOutResetStatusAbAttr,
|
|
PreSwitchOutClearWeatherAbAttr,
|
|
PreSwitchOutHealAbAttr,
|
|
PreSwitchOutFormChangeAbAttr,
|
|
PreLeaveFieldAbAttr,
|
|
PreLeaveFieldClearWeatherAbAttr,
|
|
PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr,
|
|
PreStatStageChangeAbAttr,
|
|
ReflectStatStageChangeAbAttr,
|
|
ProtectStatAbAttr,
|
|
ConfusionOnStatusEffectAbAttr,
|
|
PreSetStatusAbAttr,
|
|
PreSetStatusEffectImmunityAbAttr,
|
|
StatusEffectImmunityAbAttr,
|
|
UserFieldStatusEffectImmunityAbAttr,
|
|
ConditionalUserFieldStatusEffectImmunityAbAttr,
|
|
ConditionalUserFieldProtectStatAbAttr,
|
|
PreApplyBattlerTagAbAttr,
|
|
PreApplyBattlerTagImmunityAbAttr,
|
|
BattlerTagImmunityAbAttr,
|
|
UserFieldBattlerTagImmunityAbAttr,
|
|
ConditionalUserFieldBattlerTagImmunityAbAttr,
|
|
BlockCritAbAttr,
|
|
BonusCritAbAttr,
|
|
MultCritAbAttr,
|
|
ConditionalCritAbAttr,
|
|
BlockNonDirectDamageAbAttr,
|
|
BlockStatusDamageAbAttr,
|
|
BlockOneHitKOAbAttr,
|
|
ChangeMovePriorityAbAttr,
|
|
IgnoreContactAbAttr,
|
|
PreWeatherEffectAbAttr,
|
|
PreWeatherDamageAbAttr,
|
|
SuppressWeatherEffectAbAttr,
|
|
ForewarnAbAttr,
|
|
FriskAbAttr,
|
|
PostWeatherChangeAbAttr,
|
|
PostWeatherChangeFormChangeAbAttr,
|
|
PostWeatherLapseAbAttr,
|
|
PostWeatherLapseHealAbAttr,
|
|
PostWeatherLapseDamageAbAttr,
|
|
PostTerrainChangeAbAttr,
|
|
PostTurnAbAttr,
|
|
PostTurnStatusHealAbAttr,
|
|
PostTurnResetStatusAbAttr,
|
|
PostTurnRestoreBerryAbAttr,
|
|
RepeatBerryNextTurnAbAttr,
|
|
MoodyAbAttr,
|
|
SpeedBoostAbAttr,
|
|
PostTurnHealAbAttr,
|
|
PostTurnFormChangeAbAttr,
|
|
PostTurnHurtIfSleepingAbAttr,
|
|
FetchBallAbAttr,
|
|
PostBiomeChangeAbAttr,
|
|
PostBiomeChangeWeatherChangeAbAttr,
|
|
PostBiomeChangeTerrainChangeAbAttr,
|
|
PostMoveUsedAbAttr,
|
|
PostDancingMoveAbAttr,
|
|
PostItemLostAbAttr,
|
|
PostItemLostApplyBattlerTagAbAttr,
|
|
StatStageChangeMultiplierAbAttr,
|
|
StatStageChangeCopyAbAttr,
|
|
BypassBurnDamageReductionAbAttr,
|
|
ReduceBurnDamageAbAttr,
|
|
DoubleBerryEffectAbAttr,
|
|
PreventBerryUseAbAttr,
|
|
HealFromBerryUseAbAttr,
|
|
RunSuccessAbAttr,
|
|
CheckTrappedAbAttr,
|
|
ArenaTrapAbAttr,
|
|
MaxMultiHitAbAttr,
|
|
PostBattleAbAttr,
|
|
PostBattleLootAbAttr,
|
|
PostFaintAbAttr,
|
|
PostFaintUnsuppressedWeatherFormChangeAbAttr,
|
|
PostFaintContactDamageAbAttr,
|
|
PostFaintHPDamageAbAttr,
|
|
RedirectMoveAbAttr,
|
|
RedirectTypeMoveAbAttr,
|
|
BlockRedirectAbAttr,
|
|
ReduceStatusEffectDurationAbAttr,
|
|
FlinchEffectAbAttr,
|
|
FlinchStatStageChangeAbAttr,
|
|
IncreasePpAbAttr,
|
|
ForceSwitchOutImmunityAbAttr,
|
|
ReduceBerryUseThresholdAbAttr,
|
|
WeightMultiplierAbAttr,
|
|
SyncEncounterNatureAbAttr,
|
|
MoveAbilityBypassAbAttr,
|
|
AlwaysHitAbAttr,
|
|
IgnoreProtectOnContactAbAttr,
|
|
InfiltratorAbAttr,
|
|
ReflectStatusMoveAbAttr,
|
|
NoTransformAbilityAbAttr,
|
|
NoFusionAbilityAbAttr,
|
|
IgnoreTypeImmunityAbAttr,
|
|
IgnoreTypeStatusEffectImmunityAbAttr,
|
|
MoneyAbAttr,
|
|
PostSummonStatStageChangeOnArenaAbAttr,
|
|
FormBlockDamageAbAttr,
|
|
PreSummonAbAttr,
|
|
IllusionPreSummonAbAttr,
|
|
IllusionBreakAbAttr,
|
|
PostDefendIllusionBreakAbAttr,
|
|
IllusionPostBattleAbAttr,
|
|
BypassSpeedChanceAbAttr,
|
|
PreventBypassSpeedChanceAbAttr,
|
|
TerrainEventTypeChangeAbAttr,
|
|
PostDamageAbAttr,
|
|
PostDamageForceSwitchAbAttr,
|
|
});
|
|
|
|
/**
|
|
* A map of of all {@linkcode AbAttr} constructors
|
|
*/
|
|
export type AbAttrConstructorMap = typeof AbilityAttrs;
|
|
|
|
/**
|
|
* Sets the ability of a Pokémon as revealed.
|
|
* @param pokemon - The Pokémon whose ability is being revealed.
|
|
*/
|
|
function setAbilityRevealed(pokemon: Pokemon): void {
|
|
pokemon.waveData.abilityRevealed = true;
|
|
}
|
|
|
|
/**
|
|
* Returns all Pokemon on field with weather-based forms
|
|
*/
|
|
function getPokemonWithWeatherBasedForms() {
|
|
return globalScene
|
|
.getField(true)
|
|
.filter(
|
|
p =>
|
|
(p.hasAbility(AbilityId.FORECAST) && p.species.speciesId === SpeciesId.CASTFORM) ||
|
|
(p.hasAbility(AbilityId.FLOWER_GIFT) && p.species.speciesId === SpeciesId.CHERRIM),
|
|
);
|
|
}
|
|
|
|
// biome-ignore format: prevent biome from removing the newlines (e.g. prevent `new Ability(...).attr(...)`)
|
|
export function initAbilities() {
|
|
allAbilities.push(
|
|
new Ability(AbilityId.NONE, 3),
|
|
new Ability(AbilityId.STENCH, 3)
|
|
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => !move.hasAttr("FlinchAttr") && !move.hitsSubstitute(user, target) ? 10 : 0, BattlerTagType.FLINCHED),
|
|
new Ability(AbilityId.DRIZZLE, 3)
|
|
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN)
|
|
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN),
|
|
new Ability(AbilityId.SPEED_BOOST, 3)
|
|
.attr(SpeedBoostAbAttr),
|
|
new Ability(AbilityId.BATTLE_ARMOR, 3)
|
|
.attr(BlockCritAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.STURDY, 3)
|
|
.attr(PreDefendFullHpEndureAbAttr)
|
|
.attr(BlockOneHitKOAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.DAMP, 3)
|
|
.attr(FieldPreventExplosiveMovesAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.LIMBER, 3)
|
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.PARALYSIS)
|
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.PARALYSIS)
|
|
.ignorable(),
|
|
new Ability(AbilityId.SAND_VEIL, 3)
|
|
.attr(StatMultiplierAbAttr, Stat.EVA, 1.2)
|
|
.attr(BlockWeatherDamageAttr, WeatherType.SANDSTORM)
|
|
.condition(getWeatherCondition(WeatherType.SANDSTORM))
|
|
.ignorable(),
|
|
new Ability(AbilityId.STATIC, 3)
|
|
.attr(PostDefendContactApplyStatusEffectAbAttr, 30, StatusEffect.PARALYSIS)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.VOLT_ABSORB, 3)
|
|
.attr(TypeImmunityHealAbAttr, PokemonType.ELECTRIC)
|
|
.ignorable(),
|
|
new Ability(AbilityId.WATER_ABSORB, 3)
|
|
.attr(TypeImmunityHealAbAttr, PokemonType.WATER)
|
|
.ignorable(),
|
|
new Ability(AbilityId.OBLIVIOUS, 3)
|
|
.attr(BattlerTagImmunityAbAttr, [ BattlerTagType.INFATUATED, BattlerTagType.TAUNT ])
|
|
.attr(PostSummonRemoveBattlerTagAbAttr, BattlerTagType.INFATUATED, BattlerTagType.TAUNT)
|
|
.attr(IntimidateImmunityAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.CLOUD_NINE, 3)
|
|
.attr(SuppressWeatherEffectAbAttr, true)
|
|
.attr(PostSummonUnnamedMessageAbAttr, i18next.t("abilityTriggers:weatherEffectDisappeared"))
|
|
.attr(PostSummonWeatherSuppressedFormChangeAbAttr)
|
|
.attr(PostFaintUnsuppressedWeatherFormChangeAbAttr)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.COMPOUND_EYES, 3)
|
|
.attr(StatMultiplierAbAttr, Stat.ACC, 1.3),
|
|
new Ability(AbilityId.INSOMNIA, 3)
|
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP)
|
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.SLEEP)
|
|
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
|
.ignorable(),
|
|
new Ability(AbilityId.COLOR_CHANGE, 3)
|
|
.attr(PostDefendTypeChangeAbAttr)
|
|
.condition(getSheerForceHitDisableAbCondition()),
|
|
new Ability(AbilityId.IMMUNITY, 3)
|
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
|
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
|
|
.ignorable(),
|
|
new Ability(AbilityId.FLASH_FIRE, 3)
|
|
.attr(TypeImmunityAddBattlerTagAbAttr, PokemonType.FIRE, BattlerTagType.FIRE_BOOST, 1)
|
|
.ignorable(),
|
|
new Ability(AbilityId.SHIELD_DUST, 3)
|
|
.attr(IgnoreMoveEffectsAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.OWN_TEMPO, 3)
|
|
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
|
|
.attr(PostSummonRemoveBattlerTagAbAttr, BattlerTagType.CONFUSED)
|
|
.attr(IntimidateImmunityAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.SUCTION_CUPS, 3)
|
|
.attr(ForceSwitchOutImmunityAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.INTIMIDATE, 3)
|
|
.attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], -1, false, true),
|
|
new Ability(AbilityId.SHADOW_TAG, 3)
|
|
.attr(ArenaTrapAbAttr, (_user, target) => !target.hasAbility(AbilityId.SHADOW_TAG)),
|
|
new Ability(AbilityId.ROUGH_SKIN, 3)
|
|
.attr(PostDefendContactDamageAbAttr, 8)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.WONDER_GUARD, 3)
|
|
.attr(NonSuperEffectiveImmunityAbAttr)
|
|
.uncopiable()
|
|
.ignorable(),
|
|
new Ability(AbilityId.LEVITATE, 3)
|
|
.attr(AttackTypeImmunityAbAttr, PokemonType.GROUND, (pokemon: Pokemon) => !pokemon.getTag(GroundedTag) && !globalScene.arena.getTag(ArenaTagType.GRAVITY))
|
|
.ignorable(),
|
|
new Ability(AbilityId.EFFECT_SPORE, 3)
|
|
.attr(EffectSporeAbAttr),
|
|
new Ability(AbilityId.SYNCHRONIZE, 3)
|
|
.attr(SyncEncounterNatureAbAttr)
|
|
.attr(SynchronizeStatusAbAttr),
|
|
new Ability(AbilityId.CLEAR_BODY, 3)
|
|
.attr(ProtectStatAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.NATURAL_CURE, 3)
|
|
.attr(PreSwitchOutResetStatusAbAttr),
|
|
new Ability(AbilityId.LIGHTNING_ROD, 3)
|
|
.attr(RedirectTypeMoveAbAttr, PokemonType.ELECTRIC)
|
|
.attr(TypeImmunityStatStageChangeAbAttr, PokemonType.ELECTRIC, Stat.SPATK, 1)
|
|
.ignorable(),
|
|
new Ability(AbilityId.SERENE_GRACE, 3)
|
|
.attr(MoveEffectChanceMultiplierAbAttr, 2),
|
|
new Ability(AbilityId.SWIFT_SWIM, 3)
|
|
.attr(StatMultiplierAbAttr, Stat.SPD, 2)
|
|
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),
|
|
new Ability(AbilityId.CHLOROPHYLL, 3)
|
|
.attr(StatMultiplierAbAttr, Stat.SPD, 2)
|
|
.condition(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)),
|
|
new Ability(AbilityId.ILLUMINATE, 3)
|
|
.attr(ProtectStatAbAttr, Stat.ACC)
|
|
.attr(DoubleBattleChanceAbAttr)
|
|
.attr(IgnoreOpponentStatStagesAbAttr, [ Stat.EVA ])
|
|
.ignorable(),
|
|
new Ability(AbilityId.TRACE, 3)
|
|
.attr(PostSummonCopyAbilityAbAttr)
|
|
.uncopiable(),
|
|
new Ability(AbilityId.HUGE_POWER, 3)
|
|
.attr(StatMultiplierAbAttr, Stat.ATK, 2),
|
|
new Ability(AbilityId.POISON_POINT, 3)
|
|
.attr(PostDefendContactApplyStatusEffectAbAttr, 30, StatusEffect.POISON)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.INNER_FOCUS, 3)
|
|
.attr(BattlerTagImmunityAbAttr, BattlerTagType.FLINCHED)
|
|
.attr(IntimidateImmunityAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.MAGMA_ARMOR, 3)
|
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE)
|
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.FREEZE)
|
|
.ignorable(),
|
|
new Ability(AbilityId.WATER_VEIL, 3)
|
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
|
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.BURN)
|
|
.ignorable(),
|
|
new Ability(AbilityId.MAGNET_PULL, 3)
|
|
.attr(ArenaTrapAbAttr, (_user, target) => {
|
|
return target.getTypes(true).includes(PokemonType.STEEL) || (target.getTypes(true).includes(PokemonType.STELLAR) && target.getTypes().includes(PokemonType.STEEL));
|
|
}),
|
|
new Ability(AbilityId.SOUNDPROOF, 3)
|
|
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.SOUND_BASED))
|
|
.ignorable(),
|
|
new Ability(AbilityId.RAIN_DISH, 3)
|
|
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.RAIN, WeatherType.HEAVY_RAIN),
|
|
new Ability(AbilityId.SAND_STREAM, 3)
|
|
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SANDSTORM)
|
|
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SANDSTORM),
|
|
new Ability(AbilityId.PRESSURE, 3)
|
|
.attr(IncreasePpAbAttr)
|
|
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonPressure", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })),
|
|
new Ability(AbilityId.THICK_FAT, 3)
|
|
.attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.FIRE, 0.5)
|
|
.attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.ICE, 0.5)
|
|
.ignorable(),
|
|
new Ability(AbilityId.EARLY_BIRD, 3)
|
|
.attr(ReduceStatusEffectDurationAbAttr, StatusEffect.SLEEP),
|
|
new Ability(AbilityId.FLAME_BODY, 3)
|
|
.attr(PostDefendContactApplyStatusEffectAbAttr, 30, StatusEffect.BURN)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.RUN_AWAY, 3)
|
|
.attr(RunSuccessAbAttr),
|
|
new Ability(AbilityId.KEEN_EYE, 3)
|
|
.attr(ProtectStatAbAttr, Stat.ACC)
|
|
.ignorable(),
|
|
new Ability(AbilityId.HYPER_CUTTER, 3)
|
|
.attr(ProtectStatAbAttr, Stat.ATK)
|
|
.ignorable(),
|
|
new Ability(AbilityId.PICKUP, 3)
|
|
.attr(PostBattleLootAbAttr)
|
|
.unsuppressable(),
|
|
new Ability(AbilityId.TRUANT, 3)
|
|
.attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.TRUANT, 1, false),
|
|
new Ability(AbilityId.HUSTLE, 3)
|
|
.attr(StatMultiplierAbAttr, Stat.ATK, 1.5)
|
|
.attr(StatMultiplierAbAttr, Stat.ACC, 0.8, (_user, _target, move) => move.category === MoveCategory.PHYSICAL),
|
|
new Ability(AbilityId.CUTE_CHARM, 3)
|
|
.attr(PostDefendContactApplyTagChanceAbAttr, 30, BattlerTagType.INFATUATED),
|
|
new Ability(AbilityId.PLUS, 3)
|
|
.conditionalAttr(p => globalScene.currentBattle.double && [ AbilityId.PLUS, AbilityId.MINUS ].some(a => (p.getAlly()?.hasAbility(a) ?? false)), StatMultiplierAbAttr, Stat.SPATK, 1.5),
|
|
new Ability(AbilityId.MINUS, 3)
|
|
.conditionalAttr(p => globalScene.currentBattle.double && [ AbilityId.PLUS, AbilityId.MINUS ].some(a => (p.getAlly()?.hasAbility(a) ?? false)), StatMultiplierAbAttr, Stat.SPATK, 1.5),
|
|
new Ability(AbilityId.FORECAST, 3, -2)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.attr(NoFusionAbilityAbAttr)
|
|
.attr(PostSummonFormChangeByWeatherAbAttr, AbilityId.FORECAST)
|
|
.attr(PostWeatherChangeFormChangeAbAttr, AbilityId.FORECAST, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG ]),
|
|
new Ability(AbilityId.STICKY_HOLD, 3)
|
|
.attr(BlockItemTheftAbAttr)
|
|
.bypassFaint()
|
|
.ignorable(),
|
|
new Ability(AbilityId.SHED_SKIN, 3)
|
|
.conditionalAttr(_pokemon => !randSeedInt(3), PostTurnResetStatusAbAttr),
|
|
new Ability(AbilityId.GUTS, 3)
|
|
.attr(BypassBurnDamageReductionAbAttr)
|
|
.conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(AbilityId.COMATOSE), StatMultiplierAbAttr, Stat.ATK, 1.5),
|
|
new Ability(AbilityId.MARVEL_SCALE, 3)
|
|
.conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(AbilityId.COMATOSE), StatMultiplierAbAttr, Stat.DEF, 1.5)
|
|
.ignorable(),
|
|
new Ability(AbilityId.LIQUID_OOZE, 3)
|
|
.attr(ReverseDrainAbAttr),
|
|
new Ability(AbilityId.OVERGROW, 3)
|
|
.attr(LowHpMoveTypePowerBoostAbAttr, PokemonType.GRASS),
|
|
new Ability(AbilityId.BLAZE, 3)
|
|
.attr(LowHpMoveTypePowerBoostAbAttr, PokemonType.FIRE),
|
|
new Ability(AbilityId.TORRENT, 3)
|
|
.attr(LowHpMoveTypePowerBoostAbAttr, PokemonType.WATER),
|
|
new Ability(AbilityId.SWARM, 3)
|
|
.attr(LowHpMoveTypePowerBoostAbAttr, PokemonType.BUG),
|
|
new Ability(AbilityId.ROCK_HEAD, 3)
|
|
.attr(BlockRecoilDamageAttr),
|
|
new Ability(AbilityId.DROUGHT, 3)
|
|
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SUNNY)
|
|
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SUNNY),
|
|
new Ability(AbilityId.ARENA_TRAP, 3)
|
|
.attr(ArenaTrapAbAttr, (_user, target) => target.isGrounded())
|
|
.attr(DoubleBattleChanceAbAttr),
|
|
new Ability(AbilityId.VITAL_SPIRIT, 3)
|
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP)
|
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.SLEEP)
|
|
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
|
.ignorable(),
|
|
new Ability(AbilityId.WHITE_SMOKE, 3)
|
|
.attr(ProtectStatAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.PURE_POWER, 3)
|
|
.attr(StatMultiplierAbAttr, Stat.ATK, 2),
|
|
new Ability(AbilityId.SHELL_ARMOR, 3)
|
|
.attr(BlockCritAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.AIR_LOCK, 3)
|
|
.attr(SuppressWeatherEffectAbAttr, true)
|
|
.attr(PostSummonUnnamedMessageAbAttr, i18next.t("abilityTriggers:weatherEffectDisappeared"))
|
|
.attr(PostSummonWeatherSuppressedFormChangeAbAttr)
|
|
.attr(PostFaintUnsuppressedWeatherFormChangeAbAttr)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.TANGLED_FEET, 4)
|
|
.conditionalAttr(pokemon => !!pokemon.getTag(BattlerTagType.CONFUSED), StatMultiplierAbAttr, Stat.EVA, 2)
|
|
.ignorable(),
|
|
new Ability(AbilityId.MOTOR_DRIVE, 4)
|
|
.attr(TypeImmunityStatStageChangeAbAttr, PokemonType.ELECTRIC, Stat.SPD, 1)
|
|
.ignorable(),
|
|
new Ability(AbilityId.RIVALRY, 4)
|
|
.attr(MovePowerBoostAbAttr, (user, target, _move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender === target?.gender, 1.25, true)
|
|
.attr(MovePowerBoostAbAttr, (user, target, _move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender !== target?.gender, 0.75),
|
|
new Ability(AbilityId.STEADFAST, 4)
|
|
.attr(FlinchStatStageChangeAbAttr, [ Stat.SPD ], 1),
|
|
new Ability(AbilityId.SNOW_CLOAK, 4)
|
|
.attr(StatMultiplierAbAttr, Stat.EVA, 1.2)
|
|
.attr(BlockWeatherDamageAttr, WeatherType.HAIL)
|
|
.condition(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW))
|
|
.ignorable(),
|
|
new Ability(AbilityId.GLUTTONY, 4)
|
|
.attr(ReduceBerryUseThresholdAbAttr),
|
|
new Ability(AbilityId.ANGER_POINT, 4)
|
|
.attr(PostDefendCritStatStageChangeAbAttr, Stat.ATK, 6),
|
|
new Ability(AbilityId.UNBURDEN, 4)
|
|
.attr(PostItemLostApplyBattlerTagAbAttr, BattlerTagType.UNBURDEN)
|
|
.bypassFaint() // Allows reviver seed to activate Unburden
|
|
.edgeCase(), // Should not restore Unburden boost if Pokemon loses then regains Unburden ability
|
|
new Ability(AbilityId.HEATPROOF, 4)
|
|
.attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.FIRE, 0.5)
|
|
.attr(ReduceBurnDamageAbAttr, 0.5)
|
|
.ignorable(),
|
|
new Ability(AbilityId.SIMPLE, 4)
|
|
.attr(StatStageChangeMultiplierAbAttr, 2)
|
|
.ignorable(),
|
|
new Ability(AbilityId.DRY_SKIN, 4)
|
|
.attr(PostWeatherLapseDamageAbAttr, 2, WeatherType.SUNNY, WeatherType.HARSH_SUN)
|
|
.attr(PostWeatherLapseHealAbAttr, 2, WeatherType.RAIN, WeatherType.HEAVY_RAIN)
|
|
.attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.FIRE, 1.25)
|
|
.attr(TypeImmunityHealAbAttr, PokemonType.WATER)
|
|
.ignorable(),
|
|
new Ability(AbilityId.DOWNLOAD, 4)
|
|
.attr(DownloadAbAttr),
|
|
new Ability(AbilityId.IRON_FIST, 4)
|
|
.attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.PUNCHING_MOVE), 1.2),
|
|
new Ability(AbilityId.POISON_HEAL, 4)
|
|
.attr(PostTurnStatusHealAbAttr, StatusEffect.TOXIC, StatusEffect.POISON)
|
|
.attr(BlockStatusDamageAbAttr, StatusEffect.TOXIC, StatusEffect.POISON),
|
|
new Ability(AbilityId.ADAPTABILITY, 4)
|
|
.attr(StabBoostAbAttr),
|
|
new Ability(AbilityId.SKILL_LINK, 4)
|
|
.attr(MaxMultiHitAbAttr),
|
|
new Ability(AbilityId.HYDRATION, 4)
|
|
.attr(PostTurnResetStatusAbAttr)
|
|
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),
|
|
new Ability(AbilityId.SOLAR_POWER, 4)
|
|
.attr(PostWeatherLapseDamageAbAttr, 2, WeatherType.SUNNY, WeatherType.HARSH_SUN)
|
|
.attr(StatMultiplierAbAttr, Stat.SPATK, 1.5)
|
|
.condition(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)),
|
|
new Ability(AbilityId.QUICK_FEET, 4)
|
|
.conditionalAttr(pokemon => pokemon.status ? pokemon.status.effect === StatusEffect.PARALYSIS : false, StatMultiplierAbAttr, Stat.SPD, 2)
|
|
.conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(AbilityId.COMATOSE), StatMultiplierAbAttr, Stat.SPD, 1.5),
|
|
new Ability(AbilityId.NORMALIZE, 4)
|
|
.attr(MoveTypeChangeAbAttr, PokemonType.NORMAL, 1.2),
|
|
new Ability(AbilityId.SNIPER, 4)
|
|
.attr(MultCritAbAttr, 1.5),
|
|
new Ability(AbilityId.MAGIC_GUARD, 4)
|
|
.attr(BlockNonDirectDamageAbAttr),
|
|
new Ability(AbilityId.NO_GUARD, 4)
|
|
.attr(AlwaysHitAbAttr)
|
|
.attr(DoubleBattleChanceAbAttr),
|
|
new Ability(AbilityId.STALL, 4)
|
|
.attr(ChangeMovePriorityAbAttr, (_pokemon, _move: Move) => true, -0.2),
|
|
new Ability(AbilityId.TECHNICIAN, 4)
|
|
.attr(MovePowerBoostAbAttr, (user, target, move) => {
|
|
const power = new NumberHolder(move.power);
|
|
applyMoveAttrs("VariablePowerAttr", user, target, move, power);
|
|
return power.value <= 60;
|
|
}, 1.5),
|
|
new Ability(AbilityId.LEAF_GUARD, 4)
|
|
.attr(StatusEffectImmunityAbAttr)
|
|
.condition(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN))
|
|
.ignorable(),
|
|
new Ability(AbilityId.KLUTZ, 4, 1)
|
|
.unimplemented(),
|
|
new Ability(AbilityId.MOLD_BREAKER, 4)
|
|
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonMoldBreaker", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
|
|
.attr(MoveAbilityBypassAbAttr),
|
|
new Ability(AbilityId.SUPER_LUCK, 4)
|
|
.attr(BonusCritAbAttr),
|
|
new Ability(AbilityId.AFTERMATH, 4)
|
|
.attr(PostFaintContactDamageAbAttr, 4)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.ANTICIPATION, 4)
|
|
.conditionalAttr(getAnticipationCondition(), PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAnticipation", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })),
|
|
new Ability(AbilityId.FOREWARN, 4)
|
|
.attr(ForewarnAbAttr),
|
|
new Ability(AbilityId.UNAWARE, 4)
|
|
.attr(IgnoreOpponentStatStagesAbAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA ])
|
|
.ignorable(),
|
|
new Ability(AbilityId.TINTED_LENS, 4)
|
|
.attr(DamageBoostAbAttr, 2, (user, target, move) => (target?.getMoveEffectiveness(user!, move) ?? 1) <= 0.5),
|
|
new Ability(AbilityId.FILTER, 4)
|
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75)
|
|
.ignorable(),
|
|
new Ability(AbilityId.SLOW_START, 4)
|
|
.attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.SLOW_START, 5),
|
|
new Ability(AbilityId.SCRAPPY, 4)
|
|
.attr(IgnoreTypeImmunityAbAttr, PokemonType.GHOST, [ PokemonType.NORMAL, PokemonType.FIGHTING ])
|
|
.attr(IntimidateImmunityAbAttr),
|
|
new Ability(AbilityId.STORM_DRAIN, 4)
|
|
.attr(RedirectTypeMoveAbAttr, PokemonType.WATER)
|
|
.attr(TypeImmunityStatStageChangeAbAttr, PokemonType.WATER, Stat.SPATK, 1)
|
|
.ignorable(),
|
|
new Ability(AbilityId.ICE_BODY, 4)
|
|
.attr(BlockWeatherDamageAttr, WeatherType.HAIL)
|
|
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL, WeatherType.SNOW),
|
|
new Ability(AbilityId.SOLID_ROCK, 4)
|
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75)
|
|
.ignorable(),
|
|
new Ability(AbilityId.SNOW_WARNING, 4)
|
|
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SNOW)
|
|
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SNOW),
|
|
new Ability(AbilityId.HONEY_GATHER, 4)
|
|
.attr(MoneyAbAttr)
|
|
.unsuppressable(),
|
|
new Ability(AbilityId.FRISK, 4)
|
|
.attr(FriskAbAttr),
|
|
new Ability(AbilityId.RECKLESS, 4)
|
|
.attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.RECKLESS_MOVE), 1.2),
|
|
new Ability(AbilityId.MULTITYPE, 4)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
.uncopiable()
|
|
.unsuppressable()
|
|
.unreplaceable(),
|
|
new Ability(AbilityId.FLOWER_GIFT, 4, -2)
|
|
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.ATK, 1.5)
|
|
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.SPDEF, 1.5)
|
|
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), AllyStatMultiplierAbAttr, Stat.ATK, 1.5)
|
|
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), AllyStatMultiplierAbAttr, Stat.SPDEF, 1.5)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
.attr(PostSummonFormChangeByWeatherAbAttr, AbilityId.FLOWER_GIFT)
|
|
.attr(PostWeatherChangeFormChangeAbAttr, AbilityId.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ])
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.ignorable(),
|
|
new Ability(AbilityId.BAD_DREAMS, 4)
|
|
.attr(PostTurnHurtIfSleepingAbAttr),
|
|
new Ability(AbilityId.PICKPOCKET, 5)
|
|
.attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target}))
|
|
.condition(getSheerForceHitDisableAbCondition()),
|
|
new Ability(AbilityId.SHEER_FORCE, 5)
|
|
.attr(MovePowerBoostAbAttr, (_user, _target, move) => move.chance >= 1, 1.3)
|
|
.attr(MoveEffectChanceMultiplierAbAttr, 0), // This attribute does not seem to function - Should disable life orb, eject button, red card, kee/maranga berry if they get implemented
|
|
new Ability(AbilityId.CONTRARY, 5)
|
|
.attr(StatStageChangeMultiplierAbAttr, -1)
|
|
.ignorable(),
|
|
new Ability(AbilityId.UNNERVE, 5, 1)
|
|
.attr(PreventBerryUseAbAttr),
|
|
new Ability(AbilityId.DEFIANT, 5)
|
|
.attr(PostStatStageChangeStatStageChangeAbAttr, (_target, _statsChanged, stages) => stages < 0, [ Stat.ATK ], 2),
|
|
new Ability(AbilityId.DEFEATIST, 5)
|
|
.attr(StatMultiplierAbAttr, Stat.ATK, 0.5)
|
|
.attr(StatMultiplierAbAttr, Stat.SPATK, 0.5)
|
|
.condition((pokemon) => pokemon.getHpRatio() <= 0.5),
|
|
new Ability(AbilityId.CURSED_BODY, 5)
|
|
.attr(PostDefendMoveDisableAbAttr, 30)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.HEALER, 5)
|
|
.conditionalAttr(pokemon => !isNullOrUndefined(pokemon.getAlly()) && randSeedInt(10) < 3, PostTurnResetStatusAbAttr, true),
|
|
new Ability(AbilityId.FRIEND_GUARD, 5)
|
|
.attr(AlliedFieldDamageReductionAbAttr, 0.75)
|
|
.ignorable(),
|
|
new Ability(AbilityId.WEAK_ARMOR, 5)
|
|
.attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.category === MoveCategory.PHYSICAL, Stat.DEF, -1)
|
|
.attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.category === MoveCategory.PHYSICAL, Stat.SPD, 2),
|
|
new Ability(AbilityId.HEAVY_METAL, 5)
|
|
.attr(WeightMultiplierAbAttr, 2)
|
|
.ignorable(),
|
|
new Ability(AbilityId.LIGHT_METAL, 5)
|
|
.attr(WeightMultiplierAbAttr, 0.5)
|
|
.ignorable(),
|
|
new Ability(AbilityId.MULTISCALE, 5)
|
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, _user, _move) => target.isFullHp(), 0.5)
|
|
.ignorable(),
|
|
new Ability(AbilityId.TOXIC_BOOST, 5)
|
|
.attr(MovePowerBoostAbAttr, (user, _target, move) => move.category === MoveCategory.PHYSICAL && (user?.status?.effect === StatusEffect.POISON || user?.status?.effect === StatusEffect.TOXIC), 1.5),
|
|
new Ability(AbilityId.FLARE_BOOST, 5)
|
|
.attr(MovePowerBoostAbAttr, (user, _target, move) => move.category === MoveCategory.SPECIAL && user?.status?.effect === StatusEffect.BURN, 1.5),
|
|
new Ability(AbilityId.HARVEST, 5)
|
|
.attr(
|
|
PostTurnRestoreBerryAbAttr,
|
|
/** Rate is doubled when under sun {@link https://dex.pokemonshowdown.com/abilities/harvest} */
|
|
(pokemon) => 0.5 * (getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)(pokemon) ? 2 : 1)
|
|
)
|
|
.edgeCase(), // Cannot recover berries used up by fling or natural gift (unimplemented)
|
|
new Ability(AbilityId.TELEPATHY, 5)
|
|
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move.is("AttackMove"))
|
|
.ignorable(),
|
|
new Ability(AbilityId.MOODY, 5)
|
|
.attr(MoodyAbAttr),
|
|
new Ability(AbilityId.OVERCOAT, 5)
|
|
.attr(BlockWeatherDamageAttr)
|
|
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.POWDER_MOVE))
|
|
.ignorable(),
|
|
new Ability(AbilityId.POISON_TOUCH, 5)
|
|
.attr(PostAttackContactApplyStatusEffectAbAttr, 30, StatusEffect.POISON),
|
|
new Ability(AbilityId.REGENERATOR, 5)
|
|
.attr(PreSwitchOutHealAbAttr),
|
|
new Ability(AbilityId.BIG_PECKS, 5)
|
|
.attr(ProtectStatAbAttr, Stat.DEF)
|
|
.ignorable(),
|
|
new Ability(AbilityId.SAND_RUSH, 5)
|
|
.attr(StatMultiplierAbAttr, Stat.SPD, 2)
|
|
.attr(BlockWeatherDamageAttr, WeatherType.SANDSTORM)
|
|
.condition(getWeatherCondition(WeatherType.SANDSTORM)),
|
|
new Ability(AbilityId.WONDER_SKIN, 5)
|
|
.attr(WonderSkinAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.ANALYTIC, 5)
|
|
.attr(MovePowerBoostAbAttr, (user) =>
|
|
// Boost power if all other Pokemon have already moved (no other moves are slated to execute)
|
|
!globalScene.phaseManager.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.id !== user?.id),
|
|
1.3),
|
|
new Ability(AbilityId.ILLUSION, 5)
|
|
// The Pokemon generate an illusion if it's available
|
|
.attr(IllusionPreSummonAbAttr, false)
|
|
.attr(IllusionBreakAbAttr)
|
|
// The Pokemon loses its illusion when damaged by a move
|
|
.attr(PostDefendIllusionBreakAbAttr, true)
|
|
// Disable Illusion in fusions
|
|
.attr(NoFusionAbilityAbAttr)
|
|
// Illusion is available again after a battle
|
|
.conditionalAttr((pokemon) => pokemon.isAllowedInBattle(), IllusionPostBattleAbAttr, false)
|
|
.uncopiable()
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.IMPOSTER, 5)
|
|
.attr(PostSummonTransformAbAttr)
|
|
.uncopiable(),
|
|
new Ability(AbilityId.INFILTRATOR, 5)
|
|
.attr(InfiltratorAbAttr)
|
|
.partial(), // does not bypass Mist
|
|
new Ability(AbilityId.MUMMY, 5)
|
|
.attr(PostDefendAbilityGiveAbAttr, AbilityId.MUMMY)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.MOXIE, 5)
|
|
.attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1),
|
|
new Ability(AbilityId.JUSTIFIED, 5)
|
|
.attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.DARK && move.category !== MoveCategory.STATUS, Stat.ATK, 1),
|
|
new Ability(AbilityId.RATTLED, 5)
|
|
.attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => {
|
|
const moveType = user.getMoveType(move);
|
|
return move.category !== MoveCategory.STATUS
|
|
&& (moveType === PokemonType.DARK || moveType === PokemonType.BUG || moveType === PokemonType.GHOST);
|
|
}, Stat.SPD, 1)
|
|
.attr(PostIntimidateStatStageChangeAbAttr, [ Stat.SPD ], 1),
|
|
new Ability(AbilityId.MAGIC_BOUNCE, 5)
|
|
.attr(ReflectStatusMoveAbAttr)
|
|
.ignorable()
|
|
// Interactions with stomping tantrum, instruct, encore, and probably other moves that
|
|
// rely on move history
|
|
.edgeCase(),
|
|
new Ability(AbilityId.SAP_SIPPER, 5)
|
|
.attr(TypeImmunityStatStageChangeAbAttr, PokemonType.GRASS, Stat.ATK, 1)
|
|
.ignorable(),
|
|
new Ability(AbilityId.PRANKSTER, 5)
|
|
.attr(ChangeMovePriorityAbAttr, (_pokemon, move: Move) => move.category === MoveCategory.STATUS, 1),
|
|
new Ability(AbilityId.SAND_FORCE, 5)
|
|
.attr(MoveTypePowerBoostAbAttr, PokemonType.ROCK, 1.3)
|
|
.attr(MoveTypePowerBoostAbAttr, PokemonType.GROUND, 1.3)
|
|
.attr(MoveTypePowerBoostAbAttr, PokemonType.STEEL, 1.3)
|
|
.attr(BlockWeatherDamageAttr, WeatherType.SANDSTORM)
|
|
.condition(getWeatherCondition(WeatherType.SANDSTORM)),
|
|
new Ability(AbilityId.IRON_BARBS, 5)
|
|
.attr(PostDefendContactDamageAbAttr, 8)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.ZEN_MODE, 5)
|
|
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
|
.attr(PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 ? 1 : 0)
|
|
.attr(PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 ? 1 : 0)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable()
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.VICTORY_STAR, 5)
|
|
.attr(StatMultiplierAbAttr, Stat.ACC, 1.1)
|
|
.attr(AllyStatMultiplierAbAttr, Stat.ACC, 1.1, false),
|
|
new Ability(AbilityId.TURBOBLAZE, 5)
|
|
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTurboblaze", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
|
|
.attr(MoveAbilityBypassAbAttr),
|
|
new Ability(AbilityId.TERAVOLT, 5)
|
|
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTeravolt", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
|
|
.attr(MoveAbilityBypassAbAttr),
|
|
new Ability(AbilityId.AROMA_VEIL, 6)
|
|
.attr(UserFieldBattlerTagImmunityAbAttr, [ BattlerTagType.INFATUATED, BattlerTagType.TAUNT, BattlerTagType.DISABLED, BattlerTagType.TORMENT, BattlerTagType.HEAL_BLOCK ])
|
|
.ignorable(),
|
|
new Ability(AbilityId.FLOWER_VEIL, 6)
|
|
.attr(ConditionalUserFieldStatusEffectImmunityAbAttr, (target: Pokemon, source: Pokemon | null) => {
|
|
return source ? target.getTypes().includes(PokemonType.GRASS) && target.id !== source.id : false;
|
|
})
|
|
.attr(ConditionalUserFieldBattlerTagImmunityAbAttr,
|
|
(target: Pokemon) => {
|
|
return target.getTypes().includes(PokemonType.GRASS);
|
|
},
|
|
[ BattlerTagType.DROWSY ],
|
|
)
|
|
.attr(ConditionalUserFieldProtectStatAbAttr, (target: Pokemon) => {
|
|
return target.getTypes().includes(PokemonType.GRASS);
|
|
})
|
|
.ignorable(),
|
|
new Ability(AbilityId.CHEEK_POUCH, 6)
|
|
.attr(HealFromBerryUseAbAttr, 1 / 3),
|
|
new Ability(AbilityId.PROTEAN, 6)
|
|
.attr(PokemonTypeChangeAbAttr),
|
|
//.condition((p) => !p.summonData.abilitiesApplied.includes(AbilityId.PROTEAN)), //Gen 9 Implementation
|
|
new Ability(AbilityId.FUR_COAT, 6)
|
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (_target, _user, move) => move.category === MoveCategory.PHYSICAL, 0.5)
|
|
.ignorable(),
|
|
new Ability(AbilityId.MAGICIAN, 6)
|
|
.attr(PostAttackStealHeldItemAbAttr),
|
|
new Ability(AbilityId.BULLETPROOF, 6)
|
|
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.BALLBOMB_MOVE))
|
|
.ignorable(),
|
|
new Ability(AbilityId.COMPETITIVE, 6)
|
|
.attr(PostStatStageChangeStatStageChangeAbAttr, (_target, _statsChanged, stages) => stages < 0, [ Stat.SPATK ], 2),
|
|
new Ability(AbilityId.STRONG_JAW, 6)
|
|
.attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5),
|
|
new Ability(AbilityId.REFRIGERATE, 6)
|
|
.attr(MoveTypeChangeAbAttr, PokemonType.ICE, 1.2, (_user, _target, move) => move.type === PokemonType.NORMAL),
|
|
new Ability(AbilityId.SWEET_VEIL, 6)
|
|
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.SLEEP)
|
|
.attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.SLEEP)
|
|
.attr(UserFieldBattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
|
.ignorable()
|
|
.partial(), // Mold Breaker ally should not be affected by Sweet Veil
|
|
new Ability(AbilityId.STANCE_CHANGE, 6)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable(),
|
|
new Ability(AbilityId.GALE_WINGS, 6)
|
|
.attr(ChangeMovePriorityAbAttr, (pokemon, move) => pokemon.isFullHp() && pokemon.getMoveType(move) === PokemonType.FLYING, 1),
|
|
new Ability(AbilityId.MEGA_LAUNCHER, 6)
|
|
.attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.PULSE_MOVE), 1.5),
|
|
new Ability(AbilityId.GRASS_PELT, 6)
|
|
.conditionalAttr(getTerrainCondition(TerrainType.GRASSY), StatMultiplierAbAttr, Stat.DEF, 1.5)
|
|
.ignorable(),
|
|
new Ability(AbilityId.SYMBIOSIS, 6)
|
|
.unimplemented(),
|
|
new Ability(AbilityId.TOUGH_CLAWS, 6)
|
|
.attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 1.3),
|
|
new Ability(AbilityId.PIXILATE, 6)
|
|
.attr(MoveTypeChangeAbAttr, PokemonType.FAIRY, 1.2, (_user, _target, move) => move.type === PokemonType.NORMAL),
|
|
new Ability(AbilityId.GOOEY, 6)
|
|
.attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), Stat.SPD, -1, false),
|
|
new Ability(AbilityId.AERILATE, 6)
|
|
.attr(MoveTypeChangeAbAttr, PokemonType.FLYING, 1.2, (_user, _target, move) => move.type === PokemonType.NORMAL),
|
|
new Ability(AbilityId.PARENTAL_BOND, 6)
|
|
.attr(AddSecondStrikeAbAttr, 0.25),
|
|
new Ability(AbilityId.DARK_AURA, 6)
|
|
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonDarkAura", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
|
|
.attr(FieldMoveTypePowerBoostAbAttr, PokemonType.DARK, 4 / 3),
|
|
new Ability(AbilityId.FAIRY_AURA, 6)
|
|
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonFairyAura", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
|
|
.attr(FieldMoveTypePowerBoostAbAttr, PokemonType.FAIRY, 4 / 3),
|
|
new Ability(AbilityId.AURA_BREAK, 6)
|
|
.ignorable()
|
|
.conditionalAttr(_pokemon => globalScene.getField(true).some(p => p.hasAbility(AbilityId.DARK_AURA)), FieldMoveTypePowerBoostAbAttr, PokemonType.DARK, 9 / 16)
|
|
.conditionalAttr(_pokemon => globalScene.getField(true).some(p => p.hasAbility(AbilityId.FAIRY_AURA)), FieldMoveTypePowerBoostAbAttr, PokemonType.FAIRY, 9 / 16)
|
|
.conditionalAttr(_pokemon => globalScene.getField(true).some(p => p.hasAbility(AbilityId.DARK_AURA) || p.hasAbility(AbilityId.FAIRY_AURA)),
|
|
PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAuraBreak", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })),
|
|
new Ability(AbilityId.PRIMORDIAL_SEA, 6)
|
|
.attr(PostSummonWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)
|
|
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)
|
|
.attr(PreLeaveFieldClearWeatherAbAttr)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.DESOLATE_LAND, 6)
|
|
.attr(PostSummonWeatherChangeAbAttr, WeatherType.HARSH_SUN)
|
|
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HARSH_SUN)
|
|
.attr(PreLeaveFieldClearWeatherAbAttr)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.DELTA_STREAM, 6)
|
|
.attr(PostSummonWeatherChangeAbAttr, WeatherType.STRONG_WINDS)
|
|
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.STRONG_WINDS)
|
|
.attr(PreLeaveFieldClearWeatherAbAttr)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.STAMINA, 7)
|
|
.attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1),
|
|
new Ability(AbilityId.WIMP_OUT, 7)
|
|
.attr(PostDamageForceSwitchAbAttr)
|
|
.edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode
|
|
new Ability(AbilityId.EMERGENCY_EXIT, 7)
|
|
.attr(PostDamageForceSwitchAbAttr)
|
|
.edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode
|
|
new Ability(AbilityId.WATER_COMPACTION, 7)
|
|
.attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2),
|
|
new Ability(AbilityId.MERCILESS, 7)
|
|
.attr(ConditionalCritAbAttr, (_user, target, _move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON),
|
|
new Ability(AbilityId.SHIELDS_DOWN, 7, -1)
|
|
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
|
.attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0))
|
|
.attr(PostTurnFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0))
|
|
.conditionalAttr(p => p.formIndex !== 7, StatusEffectImmunityAbAttr)
|
|
.conditionalAttr(p => p.formIndex !== 7, BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
.attr(NoTransformAbilityAbAttr)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable()
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.STAKEOUT, 7)
|
|
.attr(MovePowerBoostAbAttr, (_user, target, _move) => !!target?.turnData.switchedInThisTurn, 2),
|
|
new Ability(AbilityId.WATER_BUBBLE, 7)
|
|
.attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.FIRE, 0.5)
|
|
.attr(MoveTypePowerBoostAbAttr, PokemonType.WATER, 2)
|
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
|
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.BURN)
|
|
.ignorable(),
|
|
new Ability(AbilityId.STEELWORKER, 7)
|
|
.attr(MoveTypePowerBoostAbAttr, PokemonType.STEEL),
|
|
new Ability(AbilityId.BERSERK, 7)
|
|
.attr(PostDefendHpGatedStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.SPATK ], 1)
|
|
.condition(getSheerForceHitDisableAbCondition()),
|
|
new Ability(AbilityId.SLUSH_RUSH, 7)
|
|
.attr(StatMultiplierAbAttr, Stat.SPD, 2)
|
|
.condition(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW)),
|
|
new Ability(AbilityId.LONG_REACH, 7)
|
|
.attr(IgnoreContactAbAttr),
|
|
new Ability(AbilityId.LIQUID_VOICE, 7)
|
|
.attr(MoveTypeChangeAbAttr, PokemonType.WATER, 1, (_user, _target, move) => move.hasFlag(MoveFlags.SOUND_BASED)),
|
|
new Ability(AbilityId.TRIAGE, 7)
|
|
.attr(ChangeMovePriorityAbAttr, (_pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3),
|
|
new Ability(AbilityId.GALVANIZE, 7)
|
|
.attr(MoveTypeChangeAbAttr, PokemonType.ELECTRIC, 1.2, (_user, _target, move) => move.type === PokemonType.NORMAL),
|
|
new Ability(AbilityId.SURGE_SURFER, 7)
|
|
.conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), StatMultiplierAbAttr, Stat.SPD, 2),
|
|
new Ability(AbilityId.SCHOOLING, 7, -1)
|
|
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
|
.attr(PostSummonFormChangeAbAttr, p => p.level < 20 || p.getHpRatio() <= 0.25 ? 0 : 1)
|
|
.attr(PostTurnFormChangeAbAttr, p => p.level < 20 || p.getHpRatio() <= 0.25 ? 0 : 1)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable()
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.DISGUISE, 7)
|
|
.attr(NoTransformAbilityAbAttr)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
// Add BattlerTagType.DISGUISE if the pokemon is in its disguised form
|
|
.conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false)
|
|
.attr(FormBlockDamageAbAttr,
|
|
(target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE,
|
|
(pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }),
|
|
(pokemon) => toDmgValue(pokemon.getMaxHp() / 8))
|
|
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable()
|
|
.bypassFaint()
|
|
.ignorable(),
|
|
new Ability(AbilityId.BATTLE_BOND, 7)
|
|
.attr(PostVictoryFormChangeAbAttr, () => 2)
|
|
.attr(PostBattleInitFormChangeAbAttr, () => 1)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable()
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.POWER_CONSTRUCT, 7)
|
|
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostBattleInitFormChangeAbAttr, () => 2)
|
|
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostBattleInitFormChangeAbAttr, () => 3)
|
|
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
|
|
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
|
|
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3)
|
|
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable()
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.CORROSION, 7)
|
|
.attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ PokemonType.STEEL, PokemonType.POISON ])
|
|
.edgeCase(), // Should poison itself with toxic orb.
|
|
new Ability(AbilityId.COMATOSE, 7)
|
|
.attr(StatusEffectImmunityAbAttr, ...getNonVolatileStatusEffects())
|
|
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable(),
|
|
new Ability(AbilityId.QUEENLY_MAJESTY, 7)
|
|
.attr(FieldPriorityMoveImmunityAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.INNARDS_OUT, 7)
|
|
.attr(PostFaintHPDamageAbAttr)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.DANCER, 7)
|
|
.attr(PostDancingMoveAbAttr)
|
|
/* Incorrect interations with:
|
|
* Petal Dance (should not lock in or count down timer; currently does both)
|
|
* Flinches (due to tag being removed earlier)
|
|
* Failed/protected moves (should not trigger if original move is protected against)
|
|
*/
|
|
.edgeCase(),
|
|
new Ability(AbilityId.BATTERY, 7)
|
|
.attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL ], 1.3),
|
|
new Ability(AbilityId.FLUFFY, 7)
|
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target}), 0.5)
|
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.FIRE, 2)
|
|
.ignorable(),
|
|
new Ability(AbilityId.DAZZLING, 7)
|
|
.attr(FieldPriorityMoveImmunityAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.SOUL_HEART, 7)
|
|
.attr(PostKnockOutStatStageChangeAbAttr, Stat.SPATK, 1),
|
|
new Ability(AbilityId.TANGLING_HAIR, 7)
|
|
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target}), Stat.SPD, -1, false),
|
|
new Ability(AbilityId.RECEIVER, 7)
|
|
.attr(CopyFaintedAllyAbilityAbAttr)
|
|
.uncopiable(),
|
|
new Ability(AbilityId.POWER_OF_ALCHEMY, 7)
|
|
.attr(CopyFaintedAllyAbilityAbAttr)
|
|
.uncopiable(),
|
|
new Ability(AbilityId.BEAST_BOOST, 7)
|
|
.attr(PostVictoryStatStageChangeAbAttr, p => {
|
|
let highestStat: EffectiveStat;
|
|
let highestValue = 0;
|
|
for (const s of EFFECTIVE_STATS) {
|
|
const value = p.getStat(s, false);
|
|
if (value > highestValue) {
|
|
highestStat = s;
|
|
highestValue = value;
|
|
}
|
|
}
|
|
return highestStat!;
|
|
}, 1),
|
|
new Ability(AbilityId.RKS_SYSTEM, 7)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable(),
|
|
new Ability(AbilityId.ELECTRIC_SURGE, 7)
|
|
.attr(PostSummonTerrainChangeAbAttr, TerrainType.ELECTRIC)
|
|
.attr(PostBiomeChangeTerrainChangeAbAttr, TerrainType.ELECTRIC),
|
|
new Ability(AbilityId.PSYCHIC_SURGE, 7)
|
|
.attr(PostSummonTerrainChangeAbAttr, TerrainType.PSYCHIC)
|
|
.attr(PostBiomeChangeTerrainChangeAbAttr, TerrainType.PSYCHIC),
|
|
new Ability(AbilityId.MISTY_SURGE, 7)
|
|
.attr(PostSummonTerrainChangeAbAttr, TerrainType.MISTY)
|
|
.attr(PostBiomeChangeTerrainChangeAbAttr, TerrainType.MISTY),
|
|
new Ability(AbilityId.GRASSY_SURGE, 7)
|
|
.attr(PostSummonTerrainChangeAbAttr, TerrainType.GRASSY)
|
|
.attr(PostBiomeChangeTerrainChangeAbAttr, TerrainType.GRASSY),
|
|
new Ability(AbilityId.FULL_METAL_BODY, 7)
|
|
.attr(ProtectStatAbAttr),
|
|
new Ability(AbilityId.SHADOW_SHIELD, 7)
|
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, _user, _move) => target.isFullHp(), 0.5),
|
|
new Ability(AbilityId.PRISM_ARMOR, 7)
|
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75),
|
|
new Ability(AbilityId.NEUROFORCE, 7)
|
|
.attr(MovePowerBoostAbAttr, (user, target, move) => (target?.getMoveEffectiveness(user!, move) ?? 1) >= 2, 1.25),
|
|
new Ability(AbilityId.INTREPID_SWORD, 8)
|
|
.attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], 1, true),
|
|
new Ability(AbilityId.DAUNTLESS_SHIELD, 8)
|
|
.attr(PostSummonStatStageChangeAbAttr, [ Stat.DEF ], 1, true),
|
|
new Ability(AbilityId.LIBERO, 8)
|
|
.attr(PokemonTypeChangeAbAttr),
|
|
//.condition((p) => !p.summonData.abilitiesApplied.includes(AbilityId.LIBERO)), //Gen 9 Implementation
|
|
new Ability(AbilityId.BALL_FETCH, 8)
|
|
.attr(FetchBallAbAttr)
|
|
.condition(getOncePerBattleCondition(AbilityId.BALL_FETCH)),
|
|
new Ability(AbilityId.COTTON_DOWN, 8)
|
|
.attr(PostDefendStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, Stat.SPD, -1, false, true)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.PROPELLER_TAIL, 8)
|
|
.attr(BlockRedirectAbAttr),
|
|
new Ability(AbilityId.MIRROR_ARMOR, 8)
|
|
.attr(ReflectStatStageChangeAbAttr)
|
|
.ignorable(),
|
|
/**
|
|
* Right now, the logic is attached to Surf and Dive moves. Ideally, the post-defend/hit should be an
|
|
* ability attribute but the current implementation of move effects for BattlerTag does not support this- in the case
|
|
* where Cramorant is fainted.
|
|
* @see {@linkcode GulpMissileTagAttr} and {@linkcode GulpMissileTag} for Gulp Missile implementation
|
|
*/
|
|
new Ability(AbilityId.GULP_MISSILE, 8)
|
|
.attr(NoTransformAbilityAbAttr)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
.unsuppressable()
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.STALWART, 8)
|
|
.attr(BlockRedirectAbAttr),
|
|
new Ability(AbilityId.STEAM_ENGINE, 8)
|
|
.attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => {
|
|
const moveType = user.getMoveType(move);
|
|
return move.category !== MoveCategory.STATUS
|
|
&& (moveType === PokemonType.FIRE || moveType === PokemonType.WATER);
|
|
}, Stat.SPD, 6),
|
|
new Ability(AbilityId.PUNK_ROCK, 8)
|
|
.attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.SOUND_BASED), 1.3)
|
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (_target, _user, move) => move.hasFlag(MoveFlags.SOUND_BASED), 0.5)
|
|
.ignorable(),
|
|
new Ability(AbilityId.SAND_SPIT, 8)
|
|
.attr(PostDefendWeatherChangeAbAttr, WeatherType.SANDSTORM, (_target, _user, move) => move.category !== MoveCategory.STATUS)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.ICE_SCALES, 8)
|
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (_target, _user, move) => move.category === MoveCategory.SPECIAL, 0.5)
|
|
.ignorable(),
|
|
new Ability(AbilityId.RIPEN, 8)
|
|
.attr(DoubleBerryEffectAbAttr),
|
|
new Ability(AbilityId.ICE_FACE, 8, -2)
|
|
.attr(NoTransformAbilityAbAttr)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
// Add BattlerTagType.ICE_FACE if the pokemon is in ice face form
|
|
.conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0, false)
|
|
// When summoned with active HAIL or SNOW, add BattlerTagType.ICE_FACE
|
|
.conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0)
|
|
// When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE
|
|
.attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW)
|
|
.attr(FormBlockDamageAbAttr,
|
|
(target, _user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE,
|
|
(pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }))
|
|
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable()
|
|
.bypassFaint()
|
|
.ignorable(),
|
|
new Ability(AbilityId.POWER_SPOT, 8)
|
|
.attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL, MoveCategory.PHYSICAL ], 1.3),
|
|
new Ability(AbilityId.MIMICRY, 8, -1)
|
|
.attr(TerrainEventTypeChangeAbAttr),
|
|
new Ability(AbilityId.SCREEN_CLEANER, 8)
|
|
.attr(PostSummonRemoveArenaTagAbAttr, [ ArenaTagType.AURORA_VEIL, ArenaTagType.LIGHT_SCREEN, ArenaTagType.REFLECT ]),
|
|
new Ability(AbilityId.STEELY_SPIRIT, 8)
|
|
.attr(UserFieldMoveTypePowerBoostAbAttr, PokemonType.STEEL),
|
|
new Ability(AbilityId.PERISH_BODY, 8)
|
|
.attr(PostDefendPerishSongAbAttr, 4)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.WANDERING_SPIRIT, 8)
|
|
.attr(PostDefendAbilitySwapAbAttr)
|
|
.bypassFaint()
|
|
.edgeCase(), // interacts incorrectly with rock head. It's meant to switch abilities before recoil would apply so that a pokemon with rock head would lose rock head first and still take the recoil
|
|
new Ability(AbilityId.GORILLA_TACTICS, 8)
|
|
.attr(GorillaTacticsAbAttr)
|
|
// TODO: Verify whether Gorilla Tactics increases struggle's power or not
|
|
.edgeCase(),
|
|
new Ability(AbilityId.NEUTRALIZING_GAS, 8, 2)
|
|
.attr(PostSummonAddArenaTagAbAttr, true, ArenaTagType.NEUTRALIZING_GAS, 0)
|
|
.attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr)
|
|
.uncopiable()
|
|
.attr(NoTransformAbilityAbAttr)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.PASTEL_VEIL, 8)
|
|
.attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
|
|
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
|
|
.ignorable(),
|
|
new Ability(AbilityId.HUNGER_SWITCH, 8)
|
|
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey() ? 0 : 1)
|
|
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey() ? 1 : 0)
|
|
.attr(NoTransformAbilityAbAttr)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
.condition((pokemon) => !pokemon.isTerastallized)
|
|
.uncopiable()
|
|
.unreplaceable(),
|
|
new Ability(AbilityId.QUICK_DRAW, 8)
|
|
.attr(BypassSpeedChanceAbAttr, 30),
|
|
new Ability(AbilityId.UNSEEN_FIST, 8)
|
|
.attr(IgnoreProtectOnContactAbAttr),
|
|
new Ability(AbilityId.CURIOUS_MEDICINE, 8)
|
|
.attr(PostSummonClearAllyStatStagesAbAttr),
|
|
new Ability(AbilityId.TRANSISTOR, 8)
|
|
.attr(MoveTypePowerBoostAbAttr, PokemonType.ELECTRIC, 1.3),
|
|
new Ability(AbilityId.DRAGONS_MAW, 8)
|
|
.attr(MoveTypePowerBoostAbAttr, PokemonType.DRAGON),
|
|
new Ability(AbilityId.CHILLING_NEIGH, 8)
|
|
.attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1),
|
|
new Ability(AbilityId.GRIM_NEIGH, 8)
|
|
.attr(PostVictoryStatStageChangeAbAttr, Stat.SPATK, 1),
|
|
new Ability(AbilityId.AS_ONE_GLASTRIER, 8, 1)
|
|
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAsOneGlastrier", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
|
|
.attr(PreventBerryUseAbAttr)
|
|
.attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable(),
|
|
new Ability(AbilityId.AS_ONE_SPECTRIER, 8, 1)
|
|
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAsOneSpectrier", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
|
|
.attr(PreventBerryUseAbAttr)
|
|
.attr(PostVictoryStatStageChangeAbAttr, Stat.SPATK, 1)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable(),
|
|
new Ability(AbilityId.LINGERING_AROMA, 9)
|
|
.attr(PostDefendAbilityGiveAbAttr, AbilityId.LINGERING_AROMA)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.SEED_SOWER, 9)
|
|
.attr(PostDefendTerrainChangeAbAttr, TerrainType.GRASSY)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.THERMAL_EXCHANGE, 9)
|
|
.attr(PostDefendStatStageChangeAbAttr, (_target, user, move) => user.getMoveType(move) === PokemonType.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1)
|
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
|
|
.attr(PostSummonHealStatusAbAttr, StatusEffect.BURN)
|
|
.ignorable(),
|
|
new Ability(AbilityId.ANGER_SHELL, 9)
|
|
.attr(PostDefendHpGatedStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 1)
|
|
.attr(PostDefendHpGatedStatStageChangeAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.DEF, Stat.SPDEF ], -1)
|
|
.condition(getSheerForceHitDisableAbCondition()),
|
|
new Ability(AbilityId.PURIFYING_SALT, 9)
|
|
.attr(StatusEffectImmunityAbAttr)
|
|
.attr(ReceivedTypeDamageMultiplierAbAttr, PokemonType.GHOST, 0.5)
|
|
.ignorable(),
|
|
new Ability(AbilityId.WELL_BAKED_BODY, 9)
|
|
.attr(TypeImmunityStatStageChangeAbAttr, PokemonType.FIRE, Stat.DEF, 2)
|
|
.ignorable(),
|
|
new Ability(AbilityId.WIND_RIDER, 9)
|
|
.attr(MoveImmunityStatStageChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.WIND_MOVE) && move.category !== MoveCategory.STATUS, Stat.ATK, 1)
|
|
.attr(PostSummonStatStageChangeOnArenaAbAttr, ArenaTagType.TAILWIND)
|
|
.ignorable(),
|
|
new Ability(AbilityId.GUARD_DOG, 9)
|
|
.attr(PostIntimidateStatStageChangeAbAttr, [ Stat.ATK ], 1, true)
|
|
.attr(ForceSwitchOutImmunityAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.ROCKY_PAYLOAD, 9)
|
|
.attr(MoveTypePowerBoostAbAttr, PokemonType.ROCK),
|
|
new Ability(AbilityId.WIND_POWER, 9)
|
|
.attr(PostDefendApplyBattlerTagAbAttr, (_target, _user, move) => move.hasFlag(MoveFlags.WIND_MOVE), BattlerTagType.CHARGED),
|
|
new Ability(AbilityId.ZERO_TO_HERO, 9)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable()
|
|
.attr(NoTransformAbilityAbAttr)
|
|
.attr(NoFusionAbilityAbAttr)
|
|
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
|
.attr(PreSwitchOutFormChangeAbAttr, (pokemon) => !pokemon.isFainted() ? 1 : pokemon.formIndex)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.COMMANDER, 9)
|
|
.attr(CommanderAbAttr)
|
|
.attr(DoubleBattleChanceAbAttr)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.edgeCase(), // Encore, Frenzy, and other non-`TURN_END` tags don't lapse correctly on the commanding Pokemon.
|
|
new Ability(AbilityId.ELECTROMORPHOSIS, 9)
|
|
.attr(PostDefendApplyBattlerTagAbAttr, (_target, _user, move) => move.category !== MoveCategory.STATUS, BattlerTagType.CHARGED),
|
|
new Ability(AbilityId.PROTOSYNTHESIS, 9, -2)
|
|
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN), PostSummonAddBattlerTagAbAttr, BattlerTagType.PROTOSYNTHESIS, 0, true)
|
|
.attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.PROTOSYNTHESIS, 0, WeatherType.SUNNY, WeatherType.HARSH_SUN)
|
|
.uncopiable()
|
|
.attr(NoTransformAbilityAbAttr),
|
|
new Ability(AbilityId.QUARK_DRIVE, 9, -2)
|
|
.conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), PostSummonAddBattlerTagAbAttr, BattlerTagType.QUARK_DRIVE, 0, true)
|
|
.attr(PostTerrainChangeAddBattlerTagAttr, BattlerTagType.QUARK_DRIVE, 0, TerrainType.ELECTRIC)
|
|
.uncopiable()
|
|
.attr(NoTransformAbilityAbAttr),
|
|
new Ability(AbilityId.GOOD_AS_GOLD, 9)
|
|
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) =>
|
|
pokemon !== attacker
|
|
&& move.category === MoveCategory.STATUS
|
|
&& ![ MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES, MoveTarget.USER_SIDE ].includes(move.moveTarget)
|
|
)
|
|
.edgeCase() // Heal Bell should not cure the status of a Pokemon with Good As Gold
|
|
.ignorable(),
|
|
new Ability(AbilityId.VESSEL_OF_RUIN, 9)
|
|
.attr(FieldMultiplyStatAbAttr, Stat.SPATK, 0.75)
|
|
.attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonVesselOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.SPATK)) }))
|
|
.ignorable(),
|
|
new Ability(AbilityId.SWORD_OF_RUIN, 9)
|
|
.attr(FieldMultiplyStatAbAttr, Stat.DEF, 0.75)
|
|
.attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonSwordOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.DEF)) })),
|
|
new Ability(AbilityId.TABLETS_OF_RUIN, 9)
|
|
.attr(FieldMultiplyStatAbAttr, Stat.ATK, 0.75)
|
|
.attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonTabletsOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.ATK)) }))
|
|
.ignorable(),
|
|
new Ability(AbilityId.BEADS_OF_RUIN, 9)
|
|
.attr(FieldMultiplyStatAbAttr, Stat.SPDEF, 0.75)
|
|
.attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonBeadsOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.SPDEF)) })),
|
|
new Ability(AbilityId.ORICHALCUM_PULSE, 9)
|
|
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SUNNY)
|
|
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SUNNY)
|
|
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.ATK, 4 / 3),
|
|
new Ability(AbilityId.HADRON_ENGINE, 9)
|
|
.attr(PostSummonTerrainChangeAbAttr, TerrainType.ELECTRIC)
|
|
.attr(PostBiomeChangeTerrainChangeAbAttr, TerrainType.ELECTRIC)
|
|
.conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), StatMultiplierAbAttr, Stat.SPATK, 4 / 3),
|
|
new Ability(AbilityId.OPPORTUNIST, 9)
|
|
.attr(StatStageChangeCopyAbAttr),
|
|
new Ability(AbilityId.CUD_CHEW, 9)
|
|
.attr(RepeatBerryNextTurnAbAttr),
|
|
new Ability(AbilityId.SHARPNESS, 9)
|
|
.attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.SLICING_MOVE), 1.5),
|
|
new Ability(AbilityId.SUPREME_OVERLORD, 9)
|
|
.attr(VariableMovePowerBoostAbAttr, (user, _target, _move) => 1 + 0.1 * Math.min(user.isPlayer() ? globalScene.arena.playerFaints : globalScene.currentBattle.enemyFaints, 5))
|
|
.partial(), // Should only boost once, on summon
|
|
new Ability(AbilityId.COSTAR, 9, -2)
|
|
.attr(PostSummonCopyAllyStatsAbAttr),
|
|
new Ability(AbilityId.TOXIC_DEBRIS, 9)
|
|
.attr(PostDefendApplyArenaTrapTagAbAttr, (_target, _user, move) => move.category === MoveCategory.PHYSICAL, ArenaTagType.TOXIC_SPIKES)
|
|
.bypassFaint(),
|
|
new Ability(AbilityId.ARMOR_TAIL, 9)
|
|
.attr(FieldPriorityMoveImmunityAbAttr)
|
|
.ignorable(),
|
|
new Ability(AbilityId.EARTH_EATER, 9)
|
|
.attr(TypeImmunityHealAbAttr, PokemonType.GROUND)
|
|
.ignorable(),
|
|
new Ability(AbilityId.MYCELIUM_MIGHT, 9)
|
|
.attr(ChangeMovePriorityAbAttr, (_pokemon, move) => move.category === MoveCategory.STATUS, -0.2)
|
|
.attr(PreventBypassSpeedChanceAbAttr, (_pokemon, move) => move.category === MoveCategory.STATUS)
|
|
.attr(MoveAbilityBypassAbAttr, (_pokemon, move: Move) => move.category === MoveCategory.STATUS),
|
|
new Ability(AbilityId.MINDS_EYE, 9)
|
|
.attr(IgnoreTypeImmunityAbAttr, PokemonType.GHOST, [ PokemonType.NORMAL, PokemonType.FIGHTING ])
|
|
.attr(ProtectStatAbAttr, Stat.ACC)
|
|
.attr(IgnoreOpponentStatStagesAbAttr, [ Stat.EVA ])
|
|
.ignorable(),
|
|
new Ability(AbilityId.SUPERSWEET_SYRUP, 9)
|
|
.attr(PostSummonStatStageChangeAbAttr, [ Stat.EVA ], -1),
|
|
new Ability(AbilityId.HOSPITALITY, 9, -2)
|
|
.attr(PostSummonAllyHealAbAttr, 4, true),
|
|
new Ability(AbilityId.TOXIC_CHAIN, 9)
|
|
.attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC),
|
|
new Ability(AbilityId.EMBODY_ASPECT_TEAL, 9)
|
|
.attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.SPD ], 1)
|
|
.uncopiable()
|
|
.unreplaceable() // TODO is this true?
|
|
.attr(NoTransformAbilityAbAttr),
|
|
new Ability(AbilityId.EMBODY_ASPECT_WELLSPRING, 9)
|
|
.attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.SPDEF ], 1)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.attr(NoTransformAbilityAbAttr),
|
|
new Ability(AbilityId.EMBODY_ASPECT_HEARTHFLAME, 9)
|
|
.attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.ATK ], 1)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.attr(NoTransformAbilityAbAttr),
|
|
new Ability(AbilityId.EMBODY_ASPECT_CORNERSTONE, 9)
|
|
.attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.DEF ], 1)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.attr(NoTransformAbilityAbAttr),
|
|
new Ability(AbilityId.TERA_SHIFT, 9, 2)
|
|
.attr(PostSummonFormChangeAbAttr, p => p.getFormKey() ? 0 : 1)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.unsuppressable()
|
|
.attr(NoTransformAbilityAbAttr)
|
|
.attr(NoFusionAbilityAbAttr),
|
|
new Ability(AbilityId.TERA_SHELL, 9)
|
|
.attr(FullHpResistTypeAbAttr)
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.ignorable(),
|
|
new Ability(AbilityId.TERAFORM_ZERO, 9)
|
|
.attr(ClearWeatherAbAttr, [ WeatherType.SUNNY, WeatherType.RAIN, WeatherType.SANDSTORM, WeatherType.HAIL, WeatherType.SNOW, WeatherType.FOG, WeatherType.HEAVY_RAIN, WeatherType.HARSH_SUN, WeatherType.STRONG_WINDS ])
|
|
.attr(ClearTerrainAbAttr, [ TerrainType.MISTY, TerrainType.ELECTRIC, TerrainType.GRASSY, TerrainType.PSYCHIC ])
|
|
.uncopiable()
|
|
.unreplaceable()
|
|
.condition(getOncePerBattleCondition(AbilityId.TERAFORM_ZERO)),
|
|
new Ability(AbilityId.POISON_PUPPETEER, 9)
|
|
.uncopiable()
|
|
.unreplaceable() // TODO is this true?
|
|
.attr(ConfusionOnStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
|
|
);
|
|
}
|