diff --git a/.gitattributes b/.gitattributes index dfe0770424b..a6bfb838587 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ # Auto detect text files and perform LF normalization * text=auto +* -crlf diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index eb270dc8f80..81d8427b5db 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,7 @@ name: Bug Report description: Create a report to help us improve title: "[Bug] " +type: bug labels: ["Bug", "Triage"] body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 29a79b38158..08d720aea7b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,7 @@ name: Feature Request description: Suggest an idea for this project title: "[Feature] " +type: 'feature' labels: ["Enhancement", "Triage"] body: - type: markdown diff --git a/public/exp-sprites.json b/public/exp-sprites.json index 50e10a8822d..c83fb910010 100644 --- a/public/exp-sprites.json +++ b/public/exp-sprites.json @@ -13,10 +13,10 @@ "1005", "1006", "1006", - "1007-apex", - "1007-apex", - "1008-ultimate", - "1008-ultimate", + "1007-apex-build-Disabled", + "1007-apex-build-Disabled", + "1008-ultimate-mode-Disabled", + "1008-ultimate-mode-Disabled", "115-mega", "115-mega", "127-mega", @@ -185,9 +185,9 @@ "531-mega", "569-gigantamax", "569-gigantamax", - "6-mega", - "6-mega", "6-mega-x", + "6-mega-x", + "6-mega-y", "6-mega-y", "6058", "6058", @@ -825,8 +825,8 @@ "873", "874", "874", - "875-no", - "875-no", + "875-no-ice", + "875-no-ice", "875", "875", "876-female", @@ -963,26 +963,26 @@ "929", "930", "930", - "931-blue", - "931-blue", - "931-green", - "931-green", - "931-white", - "931-white", - "931-yellow", - "931-yellow", + "931-blue-plumage-Disabled", + "931-blue-plumage-Disabled", + "931-green-plumage-Disabled", + "931-green-plumage-Disabled", + "931-white-plumage-Disabled", + "931-white-plumage-Disabled", + "931-yellow-plumage-Disabled", + "931-yellow-plumage-Disabled", "932", "932", "933", "933", "934", "934", - "935", - "935", - "936", - "936", - "937", - "937", + "935-Disabled", + "935-Disabled", + "936-Disabled", + "936-Disabled", + "937-Disabled", + "937-Disabled", "938", "938", "939", @@ -1073,6 +1073,8 @@ "978-droopy", "978-stretchy", "978-stretchy", + "979-Disabled", + "979-Disabled", "980", "980", "981", @@ -1131,10 +1133,10 @@ "1005b", "1006b", "1006b", - "1007b-apex", - "1007b-apex", - "1008b-ultimate", - "1008b-ultimate", + "1007b-apex-build-Disabled", + "1007b-apex-build-Disabled", + "1008b-ultimate-mode-Disabled", + "1008b-ultimate-mode-Disabled", "115b-mega", "115b-mega", "127b-mega", @@ -1303,9 +1305,9 @@ "531b-mega", "569b-gigantamax", "569b-gigantamax", - "6b-mega", - "6b-mega", "6b-mega-x", + "6b-mega-x", + "6b-mega-y", "6b-mega-y", "6058b", "6058b", @@ -1943,8 +1945,8 @@ "873b", "874b", "874b", - "875b-no", - "875b-no", + "875b-no-ice", + "875b-no-ice", "875b", "875b", "876b-female", @@ -2083,26 +2085,26 @@ "929b", "930b", "930b", - "931b-blue", - "931b-blue", - "931b-green", - "931b-green", - "931b-white", - "931b-white", - "931b-yellow", - "931b-yellow", + "931b-blue-plumage-Disabled", + "931b-blue-plumage-Disabled", + "931b-green-plumage-Disabled", + "931b-green-plumage-Disabled", + "931b-white-plumage-Disabled", + "931b-white-plumage-Disabled", + "931b-yellow-plumage-Disabled", + "931b-yellow-plumage-Disabled", "932b", "932b", "933b", "933b", "934b", "934b", - "935b", - "935b", - "936b", - "936b", - "937b", - "937b", + "935b-Disabled", + "935b-Disabled", + "936b-Disabled", + "936b-Disabled", + "937b-Disabled", + "937b-Disabled", "938b", "938b", "939b", @@ -2251,10 +2253,10 @@ "1005sb", "1006sb", "1006sb", - "1007sb-apex", - "1007sb-apex", - "1008sb-ultimate", - "1008sb-ultimate", + "1007sb-apex-build-Disabled", + "1007sb-apex-build-Disabled", + "1008sb-ultimate-mode-Disabled", + "1008sb-ultimate-mode-Disabled", "115sb-mega", "115sb-mega", "127sb-mega", @@ -3063,8 +3065,8 @@ "873sb", "874sb", "874sb", - "875sb-no", - "875sb-no", + "875sb-no-ice", + "875sb-no-ice", "875sb", "875sb", "876sb-female", @@ -3203,26 +3205,26 @@ "929sb", "930sb", "930sb", - "931sb-blue", - "931sb-blue", - "931sb-green", - "931sb-green", - "931sb-white", - "931sb-white", - "931sb-yellow", - "931sb-yellow", + "931sb-blue-plumage-Disabled", + "931sb-blue-plumage-Disabled", + "931sb-green-plumage-Disabled", + "931sb-green-plumage-Disabled", + "931sb-white-plumage-Disabled", + "931sb-white-plumage-Disabled", + "931sb-yellow-plumage-Disabled", + "931sb-yellow-plumage-Disabled", "932sb", "932sb", "933sb", "933sb", "934sb", "934sb", - "935sb", - "935sb", - "936sb", - "936sb", - "937sb", - "937sb", + "935sb-Disabled", + "935sb-Disabled", + "936sb-Disabled", + "936sb-Disabled", + "937sb-Disabled", + "937sb-Disabled", "938sb", "938sb", "939sb", @@ -3376,10 +3378,10 @@ "1005s", "1006s", "1006s", - "1007s-apex", - "1007s-apex", - "1008s-ultimate", - "1008s-ultimate", + "1007s-apex-build-Disabled", + "1007s-apex-build-Disabled", + "1008s-ultimate-mode-Disabled", + "1008s-ultimate-mode-Disabled", "115s-mega", "115s-mega", "127s-mega", @@ -4188,8 +4190,8 @@ "873s", "874s", "874s", - "875s-no", - "875s-no", + "875s-no-ice", + "875s-no-ice", "875s", "875s", "876s-female", @@ -4328,26 +4330,26 @@ "929s", "930s", "930s", - "931s-blue", - "931s-blue", - "931s-green", - "931s-green", - "931s-white", - "931s-white", - "931s-yellow", - "931s-yellow", + "931s-blue-plumage-Disabled", + "931s-blue-plumage-Disabled", + "931s-green-plumage-Disabled", + "931s-green-plumage-Disabled", + "931s-white-plumage-Disabled", + "931s-white-plumage-Disabled", + "931s-yellow-plumage-Disabled", + "931s-yellow-plumage-Disabled", "932s", "932s", "933s", "933s", "934s", "934s", - "935s", - "935s", - "936s", - "936s", - "937s", - "937s", + "935s-Disabled", + "935s-Disabled", + "936s-Disabled", + "936s-Disabled", + "937s-Disabled", + "937s-Disabled", "938s", "938s", "939s", @@ -4438,6 +4440,8 @@ "978s-droopy", "978s-stretchy", "978s-stretchy", + "979s-Disabled", + "979s-Disabled", "980s", "980s", "981s", @@ -4485,11 +4489,10 @@ "1000", "1001", "1004", - "1007-apex", - "1007-apex", - "1007-apex", - "1007-apex", - "1008-ultimate", + "1007-apex-build-Disabled", + "1007-apex-build-Disabled", + "1008-ultimate-mode-Disabled", + "1008-ultimate-mode-Disabled", "127-mega", "142-mega", "150-mega", @@ -4698,21 +4701,21 @@ "933_3", "933_3", "934", - "935", - "935_3", - "935_3", - "936_1", - "936_1", - "936_2", - "936_2", - "936_3", - "936_3", - "937_1", - "937_1", - "937_2", - "937_2", - "937_3", - "937_3", + "935-Disabled", + "935_3-Disabled", + "935_3-Disabled", + "936_1-Disabled", + "936_1-Disabled", + "936_2-Disabled", + "936_2-Disabled", + "936_3-Disabled", + "936_3-Disabled", + "937_1-Disabled", + "937_1-Disabled", + "937_2-Disabled", + "937_2-Disabled", + "937_3-Disabled", + "937_3-Disabled", "94-mega_1", "94-mega_1", "94-mega_2", @@ -4755,11 +4758,10 @@ "1000b", "1001b", "1004b", - "1007b-apex", - "1007b-apex", - "1007b-apex", - "1007b-apex", - "1008b-ultimate", + "1007b-apex-build-Disabled", + "1007b-apex-build-Disabled", + "1008b-ultimate-mode-Disabled", + "1008b-ultimate-mode-Disabled", "127b-mega", "142b-mega", "150b-mega", @@ -4920,24 +4922,24 @@ "932b", "933b", "934b", - "935_1b", - "935_1b", - "935_2b", - "935_2b", - "935_3b", - "935_3b", - "936_1b", - "936_1b", - "936_2b", - "936_2b", - "936_3b", - "936_3b", - "937_1b", - "937_1b", - "937_2b", - "937_2b", - "937_3b", - "937_3b", + "935_1b-Disabled", + "935_1b-Disabled", + "935_2b-Disabled", + "935_2b-Disabled", + "935_3b-Disabled", + "935_3b-Disabled", + "936_1b-Disabled", + "936_1b-Disabled", + "936_2b-Disabled", + "936_2b-Disabled", + "936_3b-Disabled", + "936_3b-Disabled", + "937_1b-Disabled", + "937_1b-Disabled", + "937_2b-Disabled", + "937_2b-Disabled", + "937_3b-Disabled", + "937_3b-Disabled", "94b-mega", "948b", "949b", diff --git a/public/images/types_de.png b/public/images/types_de.png old mode 100644 new mode 100755 index 62ad3a332bc..a033554029c Binary files a/public/images/types_de.png and b/public/images/types_de.png differ diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 1712cf9236f..a6c986d3d0f 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -168,6 +168,8 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; import { StatusEffect } from "#enums/status-effect"; import { initGlobalScene } from "#app/global-scene"; +import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; +import { HideAbilityPhase } from "#app/phases/hide-ability-phase"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -2904,6 +2906,21 @@ export default class BattleScene extends SceneBase { } } + /** + * Queues an ability bar flyout phase + * @param pokemon The pokemon who has the ability + * @param passive Whether the ability is a passive + * @param show Whether to show or hide the bar + */ + public queueAbilityDisplay(pokemon: Pokemon, passive: boolean, show: boolean): void { + this.unshiftPhase( + show + ? new ShowAbilityPhase(pokemon.getBattlerIndex(), passive) + : new HideAbilityPhase(pokemon.getBattlerIndex(), passive), + ); + this.clearPhaseQueueSplice(); + } + /** * Moves everything from nextCommandPhaseQueue to phaseQueue (keeping order) */ @@ -3133,6 +3150,41 @@ export default class BattleScene extends SceneBase { return false; } + canTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, transferQuantity = 1): boolean { + const mod = itemModifier.clone() as PokemonHeldItemModifier; + const source = mod.pokemonId ? mod.getPokemon() : null; + const cancelled = new Utils.BooleanHolder(false); + + if (source && source.isPlayer() !== target.isPlayer()) { + applyAbAttrs(BlockItemTheftAbAttr, source, cancelled); + } + + if (cancelled.value) { + return false; + } + + const matchingModifier = this.findModifier( + m => m instanceof PokemonHeldItemModifier && m.matchType(mod) && m.pokemonId === target.id, + target.isPlayer(), + ) as PokemonHeldItemModifier; + + if (matchingModifier) { + const maxStackCount = matchingModifier.getMaxStackCount(); + if (matchingModifier.stackCount >= maxStackCount) { + return false; + } + const countTaken = Math.min(transferQuantity, mod.stackCount, maxStackCount - matchingModifier.stackCount); + mod.stackCount -= countTaken; + } else { + const countTaken = Math.min(transferQuantity, mod.stackCount); + mod.stackCount -= countTaken; + } + + const removeOld = mod.stackCount === 0; + + return !removeOld || !source || this.hasModifier(itemModifier, !source.isPlayer()); + } + removePartyMemberModifiers(partyMemberIndex: number): Promise { return new Promise(resolve => { const pokemonId = this.getPlayerParty()[partyMemberIndex].id; @@ -3290,6 +3342,11 @@ export default class BattleScene extends SceneBase { }); } + hasModifier(modifier: PersistentModifier, enemy = false): boolean { + const modifiers = !enemy ? this.modifiers : this.enemyModifiers; + return modifiers.indexOf(modifier) > -1; + } + /** * Removes a currently owned item. If the item is stacked, the entire item stack * gets removed. This function does NOT apply in-battle effects, such as Unburden. diff --git a/src/data/ability.ts b/src/data/ability.ts index 0b4e5ddb2c4..d9e29c5506c 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -35,7 +35,6 @@ import { Species } from "#enums/species"; import { Stat, type BattleStat, type EffectiveStat, BATTLE_STATS, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat"; import { MovePhase } from "#app/phases/move-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; -import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { globalScene } from "#app/global-scene"; import { SwitchType } from "#app/enums/switch-type"; @@ -147,7 +146,8 @@ export class Ability implements Localizable { } } -type AbAttrApplyFunc = (attr: TAttr, passive: boolean) => boolean; +type AbAttrApplyFunc = (attr: TAttr, passive: boolean) => void; +type AbAttrSuccessFunc = (attr: TAttr, passive: boolean) => boolean; type AbAttrCondition = (pokemon: Pokemon) => boolean; // TODO: Can this be improved? @@ -163,9 +163,15 @@ export abstract class AbAttr { this.showAbility = showAbility; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { - return false; - } + /** + * 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: Utils.BooleanHolder | null, args: any[]): void {} getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null { return null; @@ -179,13 +185,28 @@ export abstract class 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 { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - cancelled.value = true; + constructor() { + super(false); + } - return true; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + cancelled.value = true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]) { @@ -196,7 +217,7 @@ export class BlockRecoilDamageAttr extends AbAttr { /** * Attribute for abilities that increase the chance of a double battle * occurring. - * @see apply + * @see {@linkcode apply} */ export class DoubleBattleChanceAbAttr extends AbAttr { constructor() { @@ -206,40 +227,39 @@ export class DoubleBattleChanceAbAttr extends AbAttr { /** * Increases the chance of a double battle occurring * @param args [0] {@linkcode Utils.NumberHolder} for double battle chance - * @returns true if the ability was applied */ - apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]): void { const doubleBattleChance = args[0] as Utils.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; - - return true; } } export class PostBattleInitAbAttr extends AbAttr { - applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return false; + 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(true); + super(false); this.formFunc = formFunc; } - applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const formIndex = this.formFunc(pokemon); - if (formIndex !== pokemon.formIndex && !simulated) { - return globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); - } + return formIndex !== pokemon.formIndex && !simulated; + } - return false; + override applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } } @@ -254,7 +274,7 @@ export class PostTeraFormChangeStatChangeAbAttr extends AbAttr { this.stages = stages; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): void { const statStageChangePhases: StatStageChangePhase[] = []; if (!simulated) { @@ -264,8 +284,6 @@ export class PostTeraFormChangeStatChangeAbAttr extends AbAttr { globalScene.unshiftPhase(statStageChangePhase); } } - - return true; } } @@ -284,11 +302,14 @@ export class ClearWeatherAbAttr extends AbAttr { this.weather = weather; } - apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + 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: Utils.BooleanHolder, args: any[]): void { if (!simulated) { globalScene.arena.trySetWeather(WeatherType.NONE, true); } - return true; } } @@ -307,17 +328,31 @@ export class ClearTerrainAbAttr extends AbAttr { this.terrain = terrain; } - apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + 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: Utils.BooleanHolder, args: any[]): void { if (!simulated) { globalScene.arena.trySetTerrain(TerrainType.NONE, true, true); } - return true; } } 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: Utils.BooleanHolder | null, + args: any[]): boolean { + return true; + } + applyPreDefend( pokemon: Pokemon, passive: boolean, @@ -326,28 +361,26 @@ export class PreDefendAbAttr extends AbAttr { move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[], - ): boolean { - return false; - } + ): void {} } export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr { - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (pokemon.isFullHp() - && pokemon.getMaxHp() > 1 //Checks if pokemon has wonder_guard (which forces 1hp) - && (args[0] as Utils.NumberHolder).value >= pokemon.hp) { //Damage >= hp - return simulated || pokemon.addTag(BattlerTagType.STURDY, 1); - } + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + return pokemon.isFullHp() + && pokemon.getMaxHp() > 1 //Checks if pokemon has wonder_guard (which forces 1hp) + && (args[0] as Utils.NumberHolder).value >= pokemon.hp; //Damage >= hp + } - return false; + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + if (!simulated) { + pokemon.addTag(BattlerTagType.STURDY, 1); + } } } export class BlockItemTheftAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { cancelled.value = true; - - return true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]) { @@ -359,13 +392,16 @@ export class BlockItemTheftAbAttr extends AbAttr { } export class StabBoostAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if ((args[0] as Utils.NumberHolder).value > 1) { - (args[0] as Utils.NumberHolder).value += 0.5; - return true; - } + constructor() { + super(false); + } - return false; + override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return (args[0] as Utils.NumberHolder).value > 1; + } + + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + (args[0] as Utils.NumberHolder).value += 0.5; } } @@ -373,21 +409,19 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr { protected condition: PokemonDefendCondition; private damageMultiplier: number; - constructor(condition: PokemonDefendCondition, damageMultiplier: number) { - super(); + constructor(condition: PokemonDefendCondition, damageMultiplier: number, showAbility: boolean = false) { + super(showAbility); this.condition = condition; this.damageMultiplier = damageMultiplier; } - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.condition(pokemon, attacker, move)) { - (args[0] as Utils.NumberHolder).value = Utils.toDmgValue((args[0] as Utils.NumberHolder).value * this.damageMultiplier); + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + return this.condition(pokemon, attacker, move); + } - return true; - } - - return false; + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + (args[0] as Utils.NumberHolder).value = Utils.toDmgValue((args[0] as Utils.NumberHolder).value * this.damageMultiplier); } } @@ -408,16 +442,15 @@ export class AlliedFieldDamageReductionAbAttr extends PreDefendAbAttr { * @param args * - `[0]` {@linkcode Utils.NumberHolder} - The damage being dealt */ - override applyPreDefend(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _attacker: Pokemon, _move: Move, _cancelled: Utils.BooleanHolder, args: any[]): boolean { + override applyPreDefend(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _attacker: Pokemon, _move: Move, _cancelled: Utils.BooleanHolder, args: any[]): void { const damage = args[0] as Utils.NumberHolder; damage.value = Utils.toDmgValue(damage.value * this.damageMultiplier); - return true; } } export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultiplierAbAttr { constructor(moveType: PokemonType, damageMultiplier: number) { - super((target, user, move) => user.getMoveType(move) === moveType, damageMultiplier); + super((target, user, move) => user.getMoveType(move) === moveType, damageMultiplier, false); } } @@ -432,12 +465,16 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr { private condition: AbAttrCondition | null; constructor(immuneType: PokemonType | null, condition?: AbAttrCondition) { - super(); + super(true); this.immuneType = immuneType; this.condition = condition ?? null; } + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.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. @@ -448,16 +485,8 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr { * @param args [0] {@linkcode Utils.NumberHolder} gets set to 0 if move is immuned by an ability. * @param args [1] - Whether the move is simulated. */ - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - // Field moves should ignore immunity - if ([ MoveTarget.BOTH_SIDES, MoveTarget.ENEMY_SIDE, MoveTarget.USER_SIDE ].includes(move.moveTarget)) { - return false; - } - if (attacker !== pokemon && attacker.getMoveType(move) === this.immuneType) { - (args[0] as Utils.NumberHolder).value = 0; - return true; - } - return false; + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + (args[0] as Utils.NumberHolder).value = 0; } getImmuneType(): PokemonType | null { @@ -474,17 +503,19 @@ export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr { super(immuneType, condition); } + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.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 */ - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { // this is a hacky way to fix the Levitate/Thousand Arrows interaction, but it works for now... - if (move.category !== MoveCategory.STATUS && !move.hasAttr(NeutralDamageAgainstFlyingTypeMultiplierAttr)) { - return super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); - } - return false; + super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); } } @@ -493,20 +524,18 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr { super(immuneType); } - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const ret = super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + } - if (ret) { - if (!pokemon.isFullHp() && !simulated) { - const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / 4), i18next.t("abilityTriggers:typeImmunityHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); - cancelled.value = true; // Suppresses "No Effect" message - } - return true; + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.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.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), + Utils.toDmgValue(pokemon.getMaxHp() / 4), i18next.t("abilityTriggers:typeImmunityHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); + cancelled.value = true; // Suppresses "No Effect" message } - - return false; } } @@ -521,17 +550,16 @@ class TypeImmunityStatStageChangeAbAttr extends TypeImmunityAbAttr { this.stages = stages; } - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const ret = super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + } - if (ret) { - cancelled.value = true; // Suppresses "No Effect" message - if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); - } + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + cancelled.value = true; // Suppresses "No Effect" message + if (!simulated) { + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } - - return ret; } } @@ -546,17 +574,16 @@ class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr { this.turnCount = turnCount; } - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const ret = super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + } - if (ret) { - cancelled.value = true; // Suppresses "No Effect" message - if (!simulated) { - pokemon.addTag(this.tagType, this.turnCount, undefined, pokemon.id); - } + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.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); } - - return ret; } } @@ -565,18 +592,16 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { super(null, condition); } - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { const modifierValue = args.length > 0 ? (args[0] as Utils.NumberHolder).value : pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker, undefined, undefined, move); + return move instanceof AttackMove && modifierValue < 2; + } - if (move instanceof AttackMove && modifierValue < 2) { - cancelled.value = true; // Suppresses "No Effect" message - (args[0] as Utils.NumberHolder).value = 0; - return true; - } - - return false; + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + cancelled.value = true; // Suppresses "No Effect" message + (args[0] as Utils.NumberHolder).value = 0; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -593,6 +618,12 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { * @extends PreDefendAbAttr */ export class FullHpResistTypeAbAttr extends PreDefendAbAttr { + + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + const typeMultiplier = args[0]; + return (typeMultiplier && typeMultiplier instanceof Utils.NumberHolder) && !(move && 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 @@ -602,7 +633,6 @@ export class FullHpResistTypeAbAttr extends PreDefendAbAttr { * @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 - * @returns `true` if the move's effectiveness is reduced; `false` otherwise */ override applyPreDefend( pokemon: Pokemon, @@ -611,23 +641,10 @@ export class FullHpResistTypeAbAttr extends PreDefendAbAttr { attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, - args: any[], - ): boolean { + args: any[]): void { const typeMultiplier = args[0]; - if (!(typeMultiplier && typeMultiplier instanceof Utils.NumberHolder)) { - return false; - } - - if (move && move.hasAttr(FixedDamageAttr)) { - return false; - } - - if (pokemon.isFullHp() && typeMultiplier.value > 0.5) { - typeMultiplier.value = 0.5; - pokemon.turnData.moveEffectiveness = 0.5; - return true; - } - return false; + typeMultiplier.value = 0.5; + pokemon.turnData.moveEffectiveness = 0.5; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -638,6 +655,17 @@ export class FullHpResistTypeAbAttr extends PreDefendAbAttr { } 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, @@ -646,37 +674,39 @@ export class PostDefendAbAttr extends AbAttr { move: Move, hitResult: HitResult | null, args: any[], - ): boolean { - return false; - } + ): void {} } export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (move.moveTarget === MoveTarget.USER || move.moveTarget === MoveTarget.NEAR_ALLY) { - return false; - } - if (move.getPriority(attacker) > 0 && !move.isMultiTarget()) { - cancelled.value = true; - return true; - } + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + return !(move.moveTarget === MoveTarget.USER || move.moveTarget === MoveTarget.NEAR_ALLY) && move.getPriority(attacker) > 0 && !move.isMultiTarget(); + } - return false; + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.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: integer, + stagesChanged: number, selfTarget: boolean, args: any[], - ): boolean { - return false; - } + ): void {} } export class MoveImmunityAbAttr extends PreDefendAbAttr { @@ -688,13 +718,12 @@ export class MoveImmunityAbAttr extends PreDefendAbAttr { this.immuneCondition = immuneCondition; } - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.immuneCondition(pokemon, attacker, move)) { - cancelled.value = true; - return true; - } + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + return this.immuneCondition(pokemon, attacker, move); + } - return false; + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + cancelled.value = true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -709,14 +738,19 @@ export class MoveImmunityAbAttr extends PreDefendAbAttr { * @extends PreDefendAbAttr */ export class WonderSkinAbAttr extends PreDefendAbAttr { - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const moveAccuracy = args[0] as Utils.NumberHolder; - if (move.category === MoveCategory.STATUS && moveAccuracy.value >= 50) { - moveAccuracy.value = 50; - return true; - } - return false; + constructor() { + super(false); + } + + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + const moveAccuracy = args[0] as Utils.NumberHolder; + return move.category === MoveCategory.STATUS && moveAccuracy.value >= 50; + } + + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + const moveAccuracy = args[0] as Utils.NumberHolder; + moveAccuracy.value = 50; } } @@ -730,13 +764,13 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr { this.stages = stages; } - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const ret = super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); - if (ret && !simulated) { - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); - } + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + return !simulated && super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + } - return ret; + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { + super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } } /** @@ -745,6 +779,11 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr { * @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) && !move.hitsSubstitute(attacker, pokemon); + } + /** * 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. @@ -755,16 +794,11 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr { * @param move {@linkcode PokemonMove} that is being used * @param _hitResult N/A * @param _args N/A - * @returns true if healing should be reversed on a healing move, false otherwise. */ - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (move.hasAttr(HitHealAttr) && !move.hitsSubstitute(attacker, pokemon)) { - if (!simulated) { - globalScene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); - } - return true; + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + if (!simulated) { + globalScene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); } - return false; } } @@ -785,24 +819,23 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { this.allOthers = allOthers; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { - if (simulated) { - return true; - } + override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + return this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon); + } - if (this.allOthers) { - const otherPokemon = pokemon.getAlly() ? pokemon.getOpponents().concat([ pokemon.getAlly() ]) : pokemon.getOpponents(); - for (const other of otherPokemon) { - globalScene.unshiftPhase(new StatStageChangePhase((other).getBattlerIndex(), false, [ this.stat ], this.stages)); - } - return true; - } - globalScene.unshiftPhase(new StatStageChangePhase((this.selfTarget ? pokemon : attacker).getBattlerIndex(), this.selfTarget, [ this.stat ], this.stages)); - return true; + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + if (simulated) { + return; } - return false; + if (this.allOthers) { + const otherPokemon = pokemon.getAlly() ? pokemon.getOpponents().concat([ pokemon.getAlly() ]) : pokemon.getOpponents(); + for (const other of otherPokemon) { + globalScene.unshiftPhase(new StatStageChangePhase((other).getBattlerIndex(), false, [ this.stat ], this.stages)); + } + } else { + globalScene.unshiftPhase(new StatStageChangePhase((this.selfTarget ? pokemon : attacker).getBattlerIndex(), this.selfTarget, [ this.stat ], this.stages)); + } } } @@ -823,19 +856,17 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { this.selfTarget = selfTarget; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + 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) && !move.hitsSubstitute(attacker, pokemon); + } - if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat) && !move.hitsSubstitute(attacker, pokemon)) { - if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase((this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages)); - } - return true; + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + if (!simulated) { + globalScene.unshiftPhase(new StatStageChangePhase((this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages)); } - - return false; } } @@ -850,17 +881,16 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { - const tag = globalScene.arena.getTag(this.tagType) as ArenaTrapTag; - if (!globalScene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) { - if (!simulated) { - globalScene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); - } - return true; - } + 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) && !move.hitsSubstitute(attacker, pokemon)) + && (!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); } - return false; } } @@ -874,40 +904,37 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { - if (!pokemon.getTag(this.tagType) && !simulated) { - pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); - globalScene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name })); - } - return true; + override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + return this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon); + } + + 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.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name })); } - return false; } } export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean { - if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) { - if (simulated) { - return true; - } - const type = attacker.getMoveType(move); - const pokemonTypes = pokemon.getTypes(true); - if (pokemonTypes.length !== 1 || pokemonTypes[0] !== type) { - pokemon.summonData.types = [ type ]; - return true; - } - } + private type: PokemonType; - return false; + 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 && !move.hitsSubstitute(attacker, pokemon) && (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[pokemon.getTypes(true)[0]]}`) + typeName: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) }); } } @@ -921,16 +948,14 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr { this.terrainType = terrainType; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean { - if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) { - if (simulated) { - return globalScene.arena.terrain?.terrainType !== (this.terrainType || undefined); - } else { - return globalScene.arena.trySetTerrain(this.terrainType, true); - } - } + override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + return hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon) && globalScene.arena.canSetTerrain(this.terrainType); + } - return false; + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): void { + if (!simulated) { + globalScene.arena.trySetTerrain(this.terrainType, true); + } } } @@ -939,24 +964,22 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { private effects: StatusEffect[]; constructor(chance: number, ...effects: StatusEffect[]) { - super(); + super(true); this.chance = chance; this.effects = effects; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status - && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon)) { - const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; - if (simulated) { - return attacker.canSetStatus(effect, true, false, pokemon); - } else { - return attacker.trySetStatus(effect, true, pokemon); - } - } + 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.randSeedInt(this.effects.length)]; + return move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status + && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon) + && attacker.canSetStatus(effect, true, false, pokemon); + } - return false; + 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.randSeedInt(this.effects.length)]; + attacker.trySetStatus(effect, true, pokemon); } } @@ -965,11 +988,13 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr super(10, StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP); } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (attacker.hasAbility(Abilities.OVERCOAT) || attacker.isOfType(PokemonType.GRASS)) { - return false; - } - return super.applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args); + override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + return !(attacker.hasAbility(Abilities.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); } } @@ -986,16 +1011,15 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { this.turnCount = turnCount; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance && !move.hitsSubstitute(attacker, pokemon)) { - if (simulated) { - return attacker.canAddTag(this.tagType); - } else { - return attacker.addTag(this.tagType, this.turnCount, move.id, attacker.id); - } - } + override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + return move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance + && !move.hitsSubstitute(attacker, pokemon) && attacker.canAddTag(this.tagType); + } - return false; + 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); + } } } @@ -1010,16 +1034,14 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { this.stages = stages; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (move.hitsSubstitute(attacker, pokemon)) { - return false; - } + override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + return !move.hitsSubstitute(attacker, pokemon); + } + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { if (!simulated) { globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } - - return true; } override getCondition(): AbAttrCondition { @@ -1036,15 +1058,14 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { this.damageRatio = damageRatio; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) - && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { - attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); - attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); - return true; - } + override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + return !simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) + && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon); + } - return false; + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); + attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); } override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { @@ -1070,19 +1091,16 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { this.turns = turns; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !move.hitsSubstitute(attacker, pokemon)) { - if (attacker.getTag(BattlerTagType.PERISH_SONG)) { - return false; - } else { - if (!simulated) { - attacker.addTag(BattlerTagType.PERISH_SONG, this.turns); - pokemon.addTag(BattlerTagType.PERISH_SONG, this.turns); - } - return true; - } + override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + return (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !move.hitsSubstitute(attacker, 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); } - return false; } override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { @@ -1101,18 +1119,15 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { this.condition = condition; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon)) { - return false; - } - if (!globalScene.arena.weather?.isImmutable()) { - if (simulated) { - return globalScene.arena.weather?.weatherType !== this.weatherType; - } - return globalScene.arena.trySetWeather(this.weatherType, true); - } + 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) || move.hitsSubstitute(attacker, pokemon)) + && !globalScene.arena.weather?.isImmutable() && globalScene.arena.canSetWeather(this.weatherType)); + } - return false; + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + if (!simulated) { + globalScene.arena.trySetWeather(this.weatherType, true); + } } } @@ -1121,18 +1136,17 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { super(); } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) - && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { - if (!simulated) { - const tempAbility = attacker.getAbility(); - attacker.setTempAbility(pokemon.getAbility()); - pokemon.setTempAbility(tempAbility); - } - return true; - } + override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + return move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) + && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr) && !move.hitsSubstitute(attacker, pokemon); + } - return false; + 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 { @@ -1148,17 +1162,15 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { this.ability = ability; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) - && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { - if (!simulated) { - attacker.setTempAbility(allAbilities[this.ability]); - } + override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + return move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) + && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon); + } - return true; + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void { + if (!simulated) { + attacker.setTempAbility(allAbilities[this.ability]); } - - return false; } override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { @@ -1180,20 +1192,17 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { this.chance = chance; } - override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon)) { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { - if (simulated) { - return true; - } + override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean { + return attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon) + && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance); + } - this.attacker = attacker; - this.move = move; - this.attacker.addTag(BattlerTagType.DISABLED, 4, 0, pokemon.id); - return true; - } + 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); } - return false; } } @@ -1210,19 +1219,28 @@ export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChang this.stages = stages; } - applyPostStatStageChange(pokemon: Pokemon, simulated: boolean, statStagesChanged: BattleStat[], stagesChanged: number, selfTarget: boolean, args: any[]): boolean { - if (this.condition(pokemon, statStagesChanged, stagesChanged) && !selfTarget) { - if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase((pokemon).getBattlerIndex(), true, this.statsToChange, this.stages)); - } - return true; - } + override canApplyPostStatStageChange(pokemon: Pokemon, simulated: boolean, statStagesChanged: BattleStat[], stagesChanged: integer, selfTarget: boolean, args: any[]): boolean { + return this.condition(pokemon, statStagesChanged, stagesChanged) && !selfTarget; + } - return false; + override applyPostStatStageChange(pokemon: Pokemon, simulated: boolean, statStagesChanged: BattleStat[], stagesChanged: number, selfTarget: boolean, args: any[]): void { + if (!simulated) { + globalScene.unshiftPhase(new 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, @@ -1230,9 +1248,7 @@ export class PreAttackAbAttr extends AbAttr { defender: Pokemon | null, move: Move, args: any[], - ): boolean { - return false; - } + ): void {} } /** @@ -1244,26 +1260,22 @@ export class MoveEffectChanceMultiplierAbAttr extends AbAttr { private chanceMultiplier: number; constructor(chanceMultiplier: number) { - super(true); + super(false); this.chanceMultiplier = chanceMultiplier; } + + override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + const exceptMoves = [ Moves.ORDER_UP, Moves.ELECTRO_SHOT ]; + return !((args[0] as Utils.NumberHolder).value <= 0 || exceptMoves.includes((args[1] as Move).id)); + } + /** * @param args [0]: {@linkcode Utils.NumberHolder} Move additional effect chance. Has to be higher than or equal to 0. * [1]: {@linkcode Moves } Move used by the ability user. */ - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - // Disable showAbility during getTargetBenefitScore - this.showAbility = args[4]; - - const exceptMoves = [ Moves.ORDER_UP, Moves.ELECTRO_SHOT ]; - if ((args[0] as Utils.NumberHolder).value <= 0 || exceptMoves.includes((args[1] as Move).id)) { - return false; - } - + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { (args[0] as Utils.NumberHolder).value *= this.chanceMultiplier; (args[0] as Utils.NumberHolder).value = Math.min((args[0] as Utils.NumberHolder).value, 100); - return true; - } } @@ -1273,25 +1285,22 @@ export class MoveEffectChanceMultiplierAbAttr extends AbAttr { * @see {@linkcode applyPreDefend} */ export class IgnoreMoveEffectsAbAttr extends PreDefendAbAttr { + + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean { + return (args[0] as Utils.NumberHolder).value > 0; + } + /** * @param args [0]: {@linkcode Utils.NumberHolder} Move additional effect chance. */ - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - - if ((args[0] as Utils.NumberHolder).value <= 0) { - return false; - } - + override applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): void { (args[0] as Utils.NumberHolder).value = 0; - return true; - } } export class VariableMovePowerAbAttr extends PreAttackAbAttr { - applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { - //const power = args[0] as Utils.NumberHolder; - return false; + override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + return true; } } @@ -1302,9 +1311,8 @@ export class FieldPreventExplosiveMovesAbAttr extends AbAttr { simulated: boolean, cancelled: Utils.BooleanHolder, args: any[], - ): boolean { + ): void { cancelled.value = true; - return true; } } @@ -1328,6 +1336,11 @@ export class FieldMultiplyStatAbAttr extends AbAttr { this.canStack = canStack; } + canApplyFieldStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, args: any[]): boolean { + return this.canStack || !hasApplied.value + && this.stat === stat && checkedPokemon.getAbilityAttrs(FieldMultiplyStatAbAttr).every(attr => (attr as FieldMultiplyStatAbAttr).stat !== stat); + } + /** * applyFieldStat: Tries to multiply a Pokemon's Stat * @param pokemon {@linkcode Pokemon} the Pokemon using this ability @@ -1337,19 +1350,10 @@ export class FieldMultiplyStatAbAttr extends AbAttr { * @param checkedPokemon {@linkcode Pokemon} the Pokemon this ability is targeting * @param hasApplied {@linkcode Utils.BooleanHolder} whether or not another multiplier has been applied to this stat * @param args {any[]} unused - * @returns true if this changed the checked stat, false otherwise. */ - applyFieldStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, args: any[]): boolean { - if (!this.canStack && hasApplied.value) { - return false; - } - - if (this.stat === stat && checkedPokemon.getAbilityAttrs(FieldMultiplyStatAbAttr).every(attr => (attr as FieldMultiplyStatAbAttr).stat !== stat)) { - statValue.value *= this.multiplier; - hasApplied.value = true; - return true; - } - return false; + applyFieldStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, args: any[]): void { + statValue.value *= this.multiplier; + hasApplied.value = true; } } @@ -1360,22 +1364,21 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { private powerMultiplier: number, private condition?: PokemonAttackCondition ) { - super(true); + super(false); + } + + override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean { + return (this.condition && this.condition(pokemon, defender, move)) ?? false; } // TODO: Decouple this into two attributes (type change / power boost) - applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { - if (this.condition && this.condition(pokemon, defender, move)) { - if (args[0] && args[0] instanceof Utils.NumberHolder) { - args[0].value = this.newType; - } - if (args[1] && args[1] instanceof Utils.NumberHolder) { - args[1].value *= this.powerMultiplier; - } - return true; + override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { + if (args[0] && args[0] instanceof Utils.NumberHolder) { + args[0].value = this.newType; + } + if (args[1] && args[1] instanceof Utils.NumberHolder) { + args[1].value *= this.powerMultiplier; } - - return false; } } @@ -1387,37 +1390,37 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { super(true); } - applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { - if ( - !pokemon.isTerastallized && - move.id !== Moves.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 instanceof RandomMovesetMoveAttr || - attr instanceof RandomMoveAttr || - attr instanceof NaturePowerAttr || - attr instanceof CopyMoveAttr - ) - ) { + override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean { + if (!pokemon.isTerastallized && + move.id !== Moves.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 instanceof RandomMovesetMoveAttr || + attr instanceof RandomMoveAttr || + attr instanceof NaturePowerAttr || + attr instanceof CopyMoveAttr)) { const moveType = pokemon.getMoveType(move); - if (pokemon.getTypes().some((t) => t !== moveType)) { - if (!simulated) { - this.moveType = moveType; - pokemon.summonData.types = [ moveType ]; - pokemon.updateInfo(); - } - + 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), @@ -1439,6 +1442,10 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { 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]) @@ -1449,24 +1456,17 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { * @param args Additional arguments: * - `[0]` the number of strikes this move currently has ({@linkcode Utils.NumberHolder}) * - `[1]` the damage multiplier for the current strike ({@linkcode Utils.NumberHolder}) - * @returns */ - applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { const hitCount = args[0] as Utils.NumberHolder; const multiplier = args[1] as Utils.NumberHolder; - - if (move.canBeMultiStrikeEnhanced(pokemon, true)) { - this.showAbility = !!hitCount?.value; - if (hitCount?.value) { - hitCount.value += 1; - } - - if (multiplier?.value && pokemon.turnData.hitsLeft === 1) { - multiplier.value = this.damageMultiplier; - } - return true; + if (hitCount?.value) { + hitCount.value += 1; + } + + if (multiplier?.value && pokemon.turnData.hitsLeft === 1) { + multiplier.value = this.damageMultiplier; } - return false; } } @@ -1481,11 +1481,15 @@ export class DamageBoostAbAttr extends PreAttackAbAttr { private condition: PokemonAttackCondition; constructor(damageMultiplier: number, condition: PokemonAttackCondition) { - super(true); + 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 @@ -1493,16 +1497,10 @@ export class DamageBoostAbAttr extends PreAttackAbAttr { * @param defender the target pokemon * @param move the move used by the attacker pokemon * @param args Utils.NumberHolder as damage - * @returns true if the function succeeds */ - applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { - if (this.condition(pokemon, defender, move)) { - const power = args[0] as Utils.NumberHolder; - power.value = Utils.toDmgValue(power.value * this.damageMultiplier); - return true; - } - - return false; + override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { + const power = args[0] as Utils.NumberHolder; + power.value = Utils.toDmgValue(power.value * this.damageMultiplier); } } @@ -1510,26 +1508,24 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr { private condition: PokemonAttackCondition; private powerMultiplier: number; - constructor(condition: PokemonAttackCondition, powerMultiplier: number, showAbility = true) { + constructor(condition: PokemonAttackCondition, powerMultiplier: number, showAbility: boolean = false) { super(showAbility); this.condition = condition; this.powerMultiplier = powerMultiplier; } - applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { - if (this.condition(pokemon, defender, move)) { - (args[0] as Utils.NumberHolder).value *= this.powerMultiplier; + override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean { + return this.condition(pokemon, defender, move); + } - return true; - } - - return false; + override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { + (args[0] as Utils.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); + super((pokemon, defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5, false); } } @@ -1560,17 +1556,13 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr { this.mult = mult; } - /** - * @override - */ - applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move, args: any[]): boolean { - const multiplier = this.mult(pokemon, defender, move); - if (multiplier !== 1) { - (args[0] as Utils.NumberHolder).value *= multiplier; - return true; - } + override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { + return this.mult(pokemon, defender, move) !== 1; + } - return false; + 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 Utils.NumberHolder).value *= multiplier; } } @@ -1579,6 +1571,7 @@ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr { * @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; @@ -1592,14 +1585,14 @@ export class FieldMovePowerBoostAbAttr extends AbAttr { this.powerMultiplier = powerMultiplier; } - applyPreAttack(pokemon: Pokemon | null, passive: boolean | null, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean { + 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 Utils.NumberHolder).value *= this.powerMultiplier; - - return true; } - - return false; } } @@ -1656,21 +1649,25 @@ export class StatMultiplierAbAttr extends AbAttr { this.condition = condition ?? null; } + canApplyStatStage( + pokemon: Pokemon, + _passive: boolean, + simulated: boolean, + stat: BattleStat, + statValue: Utils.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: Utils.NumberHolder, - args: any[], - ): boolean { - const move = args[0] as Move; - if (stat === this.stat && (!this.condition || this.condition(pokemon, null, move))) { - statValue.value *= this.multiplier; - return true; - } - - return false; + args: any[]): void { + statValue.value *= this.multiplier; } } @@ -1685,10 +1682,23 @@ export class PostAttackAbAttr extends AbAttr { } /** - * Please override {@link applyPostAttackAfterMoveTypeCheck} instead of this method. By default, this method checks that the move used is a damaging attack before + * 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, @@ -1696,31 +1706,7 @@ export class PostAttackAbAttr extends AbAttr { 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. - if (this.attackCondition(pokemon, defender, move)) { - return this.applyPostAttackAfterMoveTypeCheck(pokemon, passive, simulated, defender, move, hitResult, args); - } else { - return false; - } - } - - /** - * This method is only called after {@link applyPostAttack} has already been applied. Use this for handling checks specific to the ability in question. - */ - applyPostAttackAfterMoveTypeCheck( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - defender: Pokemon, - move: Move, - hitResult: HitResult | null, - args: any[], - ): boolean { - return false; - } + args: any[]): void {} } /** @@ -1732,6 +1718,18 @@ export class GorillaTacticsAbAttr extends PostAttackAbAttr { super((user, target, move) => true, false); } + 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) + && simulated || !pokemon.getTag(BattlerTagType.GORILLA_TACTICS); + } + /** * * @param {Pokemon} pokemon the {@linkcode Pokemon} with this ability @@ -1741,32 +1739,24 @@ export class GorillaTacticsAbAttr extends PostAttackAbAttr { * @param move n/a * @param hitResult n/a * @param args n/a - * @returns `true` if the ability is applied */ - override applyPostAttackAfterMoveTypeCheck( + override applyPostAttack( pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult | null, - args: any[], - ): boolean { - if (simulated) { - return simulated; + args: any[]): void { + if (!simulated) { + pokemon.addTag(BattlerTagType.GORILLA_TACTICS); } - - if (pokemon.getTag(BattlerTagType.GORILLA_TACTICS)) { - return false; - } - - pokemon.addTag(BattlerTagType.GORILLA_TACTICS); - return true; } } export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { private stealCondition: PokemonAttackCondition | null; + private stolenItem?: PokemonHeldItemModifier; constructor(stealCondition?: PokemonAttackCondition) { super(); @@ -1774,7 +1764,34 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { this.stealCondition = stealCondition ?? null; } - override applyPostAttackAfterMoveTypeCheck( + 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.randSeedInt(heldItems.length)]; + if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) { + return true; + } + } + } + this.stolenItem = undefined; + return false; + } + + override applyPostAttack( pokemon: Pokemon, passive: boolean, simulated: boolean, @@ -1782,28 +1799,21 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { move: Move, hitResult: HitResult, args: any[], - ): boolean { - if ( - !simulated && - hitResult < HitResult.NO_EFFECT && - (!this.stealCondition || this.stealCondition(pokemon, defender, move)) - ) { - const heldItems = this.getTargetHeldItems(defender).filter((i) => i.isTransferable); - if (heldItems.length) { - const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; - if (globalScene.tryTransferHeldItemModifier(stolenItem, pokemon, false)) { - globalScene.queueMessage( - i18next.t("abilityTriggers:postAttackStealHeldItem", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - defenderName: defender.name, - stolenItemType: stolenItem.type.name, - }), - ); - return true; - } - } + ): void { + const heldItems = this.getTargetHeldItems(defender).filter((i) => i.isTransferable); + if (!this.stolenItem) { + this.stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; } - return false; + if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) { + globalScene.queueMessage( + i18next.t("abilityTriggers:postAttackStealHeldItem", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + defenderName: defender.name, + stolenItemType: this.stolenItem.type.name, + }), + ); + } + this.stolenItem = undefined; } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { @@ -1825,18 +1835,23 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { this.effects = effects; } - applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (pokemon !== attacker && move.hitsSubstitute(attacker, pokemon)) { - return false; - } - - /**Status inflicted by abilities post attacking are also considered additional effects.*/ - if (!attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !simulated && pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) { + 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) + && !(pokemon !== attacker && move.hitsSubstitute(attacker, pokemon)) + && (simulated || !attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker + && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) + ) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; - return attacker.trySetStatus(effect, true, pokemon); + return simulated || attacker.canSetStatus(effect, true, false, pokemon); } - return simulated; + 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.randSeedInt(this.effects.length)]; + attacker.trySetStatus(effect, true, pokemon); } } @@ -1853,26 +1868,32 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { constructor(contactRequired: boolean, chance: (user: Pokemon, target: Pokemon, move: Move) => number, ...effects: BattlerTagType[]) { - super(); + super(undefined, false); this.contactRequired = contactRequired; this.chance = chance; this.effects = effects; } - applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + 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.*/ - if (!attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) { - const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; - return simulated || attacker.addTag(effect); - } + return super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) && + !attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker && + (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && + pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status; + } - return false; + 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.randSeedInt(this.effects.length)]; + attacker.addTag(effect); + } } } export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { private condition?: PokemonDefendCondition; + private stolenItem?: PokemonHeldItemModifier; constructor(condition?: PokemonDefendCondition) { super(); @@ -1880,6 +1901,24 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { 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)) && + !move.hitsSubstitute(attacker, pokemon) + ) { + const heldItems = this.getTargetHeldItems(attacker).filter((i) => i.isTransferable); + if (heldItems.length) { + this.stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; + if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) { + return true; + } + } + } + return false; + } + override applyPostDefend( pokemon: Pokemon, _passive: boolean, @@ -1888,29 +1927,22 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { move: Move, hitResult: HitResult, _args: any[], - ): boolean { - if ( - !simulated && - hitResult < HitResult.NO_EFFECT && - (!this.condition || this.condition(pokemon, attacker, move)) && - !move.hitsSubstitute(attacker, pokemon) - ) { - const heldItems = this.getTargetHeldItems(attacker).filter((i) => i.isTransferable); - if (heldItems.length) { - const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; - if (globalScene.tryTransferHeldItemModifier(stolenItem, pokemon, false)) { - globalScene.queueMessage( - i18next.t("abilityTriggers:postDefendStealHeldItem", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - attackerName: attacker.name, - stolenItemType: stolenItem.type.name, - }), - ); - return true; - } - } + ): void { + + const heldItems = this.getTargetHeldItems(attacker).filter((i) => i.isTransferable); + if (!this.stolenItem) { + this.stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; } - return false; + if (globalScene.tryTransferHeldItemModifier(this.stolenItem, pokemon, false)) { + globalScene.queueMessage( + i18next.t("abilityTriggers:postDefendStealHeldItem", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + attackerName: attacker.name, + stolenItemType: this.stolenItem.type.name, + }), + ); + } + this.stolenItem = undefined; } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { @@ -1924,6 +1956,16 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { * @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. @@ -1931,7 +1973,6 @@ export class PostSetStatusAbAttr extends AbAttr { * @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. - * @returns `true` if application of the ability succeeds. */ applyPostSetStatus( pokemon: Pokemon, @@ -1940,9 +1981,7 @@ export class PostSetStatusAbAttr extends AbAttr { effect: StatusEffect, simulated: boolean, args: any[], - ): boolean { - return false; - } + ): void {} } /** @@ -1951,17 +1990,7 @@ export class PostSetStatusAbAttr extends AbAttr { * ability attribute. For Synchronize ability. */ export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr { - /** - * 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. - * @returns `true` if application of the ability succeeds. - */ - override applyPostSetStatus(pokemon: Pokemon, sourcePokemon: Pokemon | null = null, passive: boolean, effect: StatusEffect, simulated: boolean, args: any[]): boolean { + override canApplyPostSetStatus(pokemon: Pokemon, sourcePokemon: (Pokemon | null) | undefined, passive: boolean, effect: StatusEffect, simulated: boolean, args: any[]): boolean { /** Synchronizable statuses */ const syncStatuses = new Set([ StatusEffect.BURN, @@ -1970,21 +1999,32 @@ export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr { StatusEffect.TOXIC ]); - if (sourcePokemon && syncStatuses.has(effect)) { - if (!simulated) { - sourcePokemon.trySetStatus(effect, true, pokemon); - } - return true; - } + // 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)); + } - return false; + /** + * 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 { - applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return false; + 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 { @@ -1998,12 +2038,11 @@ class PostVictoryStatStageChangeAbAttr extends PostVictoryAbAttr { this.stages = stages; } - applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + 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.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], this.stages)); } - return true; } } @@ -2016,23 +2055,24 @@ export class PostVictoryFormChangeAbAttr extends PostVictoryAbAttr { this.formFunc = formFunc; } - applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const formIndex = this.formFunc(pokemon); - if (formIndex !== pokemon.formIndex) { - if (!simulated) { - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); - } - return true; - } + return formIndex !== pokemon.formIndex; + } - return false; + override applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + if (!simulated) { + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); + } } } export class PostKnockOutAbAttr extends AbAttr { - applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean { - return false; + 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 { @@ -2046,12 +2086,11 @@ export class PostKnockOutStatStageChangeAbAttr extends PostKnockOutAbAttr { this.stages = stages; } - applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean { + 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.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], this.stages)); } - return true; } } @@ -2060,16 +2099,15 @@ export class CopyFaintedAllyAbilityAbAttr extends PostKnockOutAbAttr { super(); } - applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean { - if (pokemon.isPlayer() === knockedOut.isPlayer() && !knockedOut.getAbility().hasAttr(UncopiableAbilityAbAttr)) { - if (!simulated) { - globalScene.queueMessage(i18next.t("abilityTriggers:copyFaintedAllyAbility", { pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), abilityName: allAbilities[knockedOut.getAbility().id].name })); - pokemon.setTempAbility(knockedOut.getAbility()); - } - return true; - } + override canApplyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean { + return pokemon.isPlayer() === knockedOut.isPlayer() && !knockedOut.getAbility().hasAttr(UncopiableAbilityAbAttr); + } - return false; + override applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): void { + if (!simulated) { + pokemon.setTempAbility(knockedOut.getAbility()); + globalScene.queueMessage(i18next.t("abilityTriggers:copyFaintedAllyAbility", { pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), abilityName: allAbilities[knockedOut.getAbility().id].name })); + } } } @@ -2086,6 +2124,10 @@ export class IgnoreOpponentStatStagesAbAttr extends AbAttr { 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 @@ -2093,14 +2135,9 @@ export class IgnoreOpponentStatStagesAbAttr extends AbAttr { * @param simulated n/a * @param _cancelled n/a * @param args A BooleanHolder that represents whether or not to ignore a stat's stat changes - * @returns true if the stat is ignored, false otherwise */ - apply(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]) { - if (this.stats.includes(args[0])) { - (args[1] as Utils.BooleanHolder).value = true; - return true; - } - return false; + override apply(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]): void { + (args[1] as Utils.BooleanHolder).value = true; } } @@ -2109,9 +2146,8 @@ export class IntimidateImmunityAbAttr extends AbAttr { super(false); } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { cancelled.value = true; - return true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -2134,12 +2170,11 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { this.overwrites = !!overwrites; } - apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: Utils.BooleanHolder, args: any[]): void { if (!simulated) { globalScene.pushPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages)); } cancelled.value = this.overwrites; - return true; } } @@ -2163,16 +2198,17 @@ export class PostSummonAbAttr extends AbAttr { 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 - * @returns true if application of the ability succeeds */ - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return false; - } + applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {} } /** @@ -2190,13 +2226,16 @@ export class PostSummonRemoveArenaTagAbAttr extends PostSummonAbAttr { this.arenaTags = arenaTags; } - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + 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); } } - return true; } } @@ -2211,20 +2250,19 @@ export class PostSummonAddArenaTagAbAttr extends PostSummonAbAttr { private sourceId: number; - constructor(tagType: ArenaTagType, turnCount: number, side?: ArenaTagSide, quiet?: boolean) { - super(false); + 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[]): boolean { + 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); } - return true; } } @@ -2237,12 +2275,10 @@ export class PostSummonMessageAbAttr extends PostSummonAbAttr { this.messageFunc = messageFunc; } - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { if (!simulated) { globalScene.queueMessage(this.messageFunc(pokemon)); } - - return true; } } @@ -2256,12 +2292,10 @@ export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr { this.message = message; } - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { if (!simulated) { globalScene.queueMessage(this.message); } - - return true; } } @@ -2276,11 +2310,13 @@ export class PostSummonAddBattlerTagAbAttr extends PostSummonAbAttr { this.turnCount = turnCount; } - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (simulated) { - return pokemon.canAddTag(this.tagType); - } else { - return pokemon.addTag(this.tagType, this.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); } } } @@ -2292,7 +2328,7 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { private intimidate: boolean; constructor(stats: BattleStat[], stages: number, selfTarget?: boolean, intimidate?: boolean) { - super(false); + super(true); this.stats = stats; this.stages = stages; @@ -2300,33 +2336,31 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { this.intimidate = !!intimidate; } - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { if (simulated) { - return true; + return; } - queueShowAbility(pokemon, passive); // TODO: Better solution than manually showing the ability here 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.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); - return true; - } - for (const opponent of pokemon.getOpponents()) { - const cancelled = new Utils.BooleanHolder(false); - if (this.intimidate) { - applyAbAttrs(IntimidateImmunityAbAttr, opponent, cancelled, simulated); - applyAbAttrs(PostIntimidateStatStageChangeAbAttr, opponent, cancelled, simulated); + } else { + for (const opponent of pokemon.getOpponents()) { + const cancelled = new Utils.BooleanHolder(false); + if (this.intimidate) { + applyAbAttrs(IntimidateImmunityAbAttr, opponent, cancelled, simulated); + applyAbAttrs(PostIntimidateStatStageChangeAbAttr, opponent, cancelled, simulated); - if (opponent.getTag(BattlerTagType.SUBSTITUTE)) { - cancelled.value = true; + if (opponent.getTag(BattlerTagType.SUBSTITUTE)) { + cancelled.value = true; + } + } + if (!cancelled.value) { + globalScene.unshiftPhase(new StatStageChangePhase(opponent.getBattlerIndex(), false, this.stats, this.stages)); } } - if (!cancelled.value) { - globalScene.unshiftPhase(new StatStageChangePhase(opponent.getBattlerIndex(), false, this.stats, this.stages)); - } } - return true; } } @@ -2341,18 +2375,16 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr { this.showAnim = showAnim; } - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return pokemon.getAlly()?.isActive(true); + } + + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { const target = pokemon.getAlly(); - if (target?.isActive(true)) { - if (!simulated) { - globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / this.healRatio), i18next.t("abilityTriggers:postSummonAllyHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(target), pokemonName: pokemon.name }), true, !this.showAnim)); - } - - return true; + if (!simulated) { + globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), + Utils.toDmgValue(pokemon.getMaxHp() / this.healRatio), i18next.t("abilityTriggers:postSummonAllyHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(target), pokemonName: pokemon.name }), true, !this.showAnim)); } - - return false; } } @@ -2369,21 +2401,19 @@ export class PostSummonClearAllyStatStagesAbAttr extends PostSummonAbAttr { super(); } - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const target = pokemon.getAlly(); - if (target?.isActive(true)) { - if (!simulated) { - for (const s of BATTLE_STATS) { - target.setStatStage(s, 0); - } + override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return pokemon.getAlly()?.isActive(true); + } - globalScene.queueMessage(i18next.t("abilityTriggers:postSummonClearAllyStats", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + const target = pokemon.getAlly(); + if (!simulated) { + for (const s of BATTLE_STATS) { + target.setStatStage(s, 0); } - return true; + globalScene.queueMessage(i18next.t("abilityTriggers:postSummonClearAllyStats", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); } - - return false; } } @@ -2400,15 +2430,7 @@ export class DownloadAbAttr extends PostSummonAbAttr { private enemyCountTally: number; private stats: BattleStat[]; - /** - * 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 - * @returns Returns true if ability is used successful, false if not. - */ - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { this.enemyDef = 0; this.enemySpDef = 0; this.enemyCountTally = 0; @@ -2420,21 +2442,26 @@ export class DownloadAbAttr extends PostSummonAbAttr { } 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 (this.enemyDef > 0 && this.enemySpDef > 0) { // only activate if there's actually an enemy to download from - if (!simulated) { - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, 1)); - } - return true; + if (!simulated) { + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, 1)); } - - return false; } } @@ -2447,18 +2474,17 @@ export class PostSummonWeatherChangeAbAttr extends PostSummonAbAttr { this.weatherType = weatherType; } - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if ((this.weatherType === WeatherType.HEAVY_RAIN || + 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()) { - if (simulated) { - return globalScene.arena.weather?.weatherType !== this.weatherType; - } else { - return globalScene.arena.trySetWeather(this.weatherType, true); - } - } + this.weatherType === WeatherType.STRONG_WINDS) || !globalScene.arena.weather?.isImmutable(); + return weatherReplaceable && globalScene.arena.canSetWeather(this.weatherType); + } - return false; + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + if (!simulated) { + globalScene.arena.trySetWeather(this.weatherType, true); + } } } @@ -2471,11 +2497,13 @@ export class PostSummonTerrainChangeAbAttr extends PostSummonAbAttr { this.terrainType = terrainType; } - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (simulated) { - return globalScene.arena.terrain?.terrainType !== this.terrainType; - } else { - return globalScene.arena.trySetTerrain(this.terrainType, true); + 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, true); } } } @@ -2489,13 +2517,14 @@ export class PostSummonFormChangeAbAttr extends PostSummonAbAttr { this.formFunc = formFunc; } - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const formIndex = this.formFunc(pokemon); - if (formIndex !== pokemon.formIndex) { - return simulated || globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); - } + override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return this.formFunc(pokemon) !== pokemon.formIndex; + } - return false; + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + if (!simulated) { + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); + } } } @@ -2504,7 +2533,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr { private target: Pokemon; private targetAbilityName: string; - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const targets = pokemon.getOpponents(); if (!targets.length) { return false; @@ -2525,15 +2554,17 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr { 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) { - this.target = target!; - this.targetAbilityName = allAbilities[target!.getAbility().id].name; - pokemon.setTempAbility(target!.getAbility()); - setAbilityRevealed(target!); + pokemon.setTempAbility(this.target!.getAbility()); + setAbilityRevealed(this.target!); pokemon.updateInfo(); } - - return true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -2560,22 +2591,22 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt this.statusEffect = statusEffect; } + override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + const party = pokemon instanceof PlayerPokemon ? 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 - * @returns A boolean that resolves to a boolean indicating the result of the ability application. */ - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { const party = pokemon instanceof PlayerPokemon ? globalScene.getPlayerField() : globalScene.getEnemyField(); const allowedParty = party.filter(p => p.isAllowedInBattle()); - if (allowedParty.length < 1) { - return false; - } - if (!simulated) { for (const pokemon of allowedParty) { if (pokemon.status && this.statusEffect.includes(pokemon.status.effect)) { @@ -2585,14 +2616,13 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt } } } - return true; } } /** Attempt to copy the stat changes on an ally pokemon */ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (!globalScene.currentBattle.double) { return false; } @@ -2602,14 +2632,17 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { return false; } + return true; + } + + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + const ally = pokemon.getAlly(); if (!simulated) { for (const s of BATTLE_STATS) { pokemon.setStatStage(s, ally.getStatStage(s)); } pokemon.updateInfo(); } - - return true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -2628,12 +2661,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { super(true, false); } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean { - const targets = pokemon.getOpponents(); - if (simulated || !targets.length) { - return simulated; - } - + private getTarget(targets: Pokemon[]): Pokemon { let target: Pokemon; if (targets.length > 1) { globalScene.executeWithSeedOffset(() => { @@ -2653,21 +2681,28 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { } target = target!; + return target; + } + + override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + const targets = pokemon.getOpponents(); + if (simulated || !targets.length) { + return simulated; + } + // transforming from or into fusion pokemon causes various problems (including crashes and save corruption) - if (target.fusionSpecies || pokemon.fusionSpecies) { + if (this.getTarget(targets).fusionSpecies || pokemon.fusionSpecies) { return false; } + return true; + } + + override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + const target = this.getTarget(pokemon.getOpponents()); + globalScene.unshiftPhase(new PokemonTransformPhase(pokemon.getBattlerIndex(), target.getBattlerIndex(), true)); - globalScene.queueMessage( - i18next.t("abilityTriggers:postSummonTransform", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - targetName: target.name, - }), - ); - - return true; } } @@ -2677,25 +2712,20 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { * @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 - * @returns whether a Pokemon was reverted to its normal form */ - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]) { - const pokemonToTransform = getPokemonWithWeatherBasedForms(); - - if (pokemonToTransform.length < 1) { - return false; - } - + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { if (!simulated) { globalScene.arena.triggerWeatherBasedFormChangesToNormal(); } - - return true; } } @@ -2713,6 +2743,12 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr { this.ability = ability; } + override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + const isCastformWithForecast = (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST); + const isCherrimWithFlowerGift = (pokemon.species.speciesId === Species.CHERRIM && this.ability === Abilities.FLOWER_GIFT); + return isCastformWithForecast || isCherrimWithFlowerGift; + } + /** * Calls the {@linkcode BattleScene.triggerPokemonFormChange | triggerPokemonFormChange} for both * {@linkcode SpeciesFormChange.SpeciesFormChangeWeatherTrigger | SpeciesFormChangeWeatherTrigger} and @@ -2721,23 +2757,13 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr { * @param {Pokemon} pokemon the Pokemon with this ability * @param passive n/a * @param args n/a - * @returns whether the form change was triggered */ - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const isCastformWithForecast = (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST); - const isCherrimWithFlowerGift = (pokemon.species.speciesId === Species.CHERRIM && this.ability === Abilities.FLOWER_GIFT); - - if (isCastformWithForecast || isCherrimWithFlowerGift) { - if (simulated) { - return simulated; - } - + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + if (!simulated) { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeWeatherTrigger); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeRevertWeatherFormTrigger); - queueShowAbility(pokemon, passive); - return true; + globalScene.queueAbilityDisplay(pokemon, passive, true); } - return false; } } @@ -2752,53 +2778,55 @@ export class CommanderAbAttr extends AbAttr { super(true); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: null, args: any[]): boolean { - // TODO: Should this work with X + Dondozo fusions? - if (globalScene.currentBattle?.double && pokemon.getAlly()?.species.speciesId === Species.DONDOZO) { - // If the ally Dondozo is fainted or was previously "commanded" by - // another Pokemon, this effect cannot apply. - if (pokemon.getAlly().isFainted() || pokemon.getAlly().getTag(BattlerTagType.COMMANDED)) { - return false; - } + 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. - 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, Moves.NONE, pokemon.id); - // Cancel the source Pokemon's next move (if a move is queued) - globalScene.tryRemovePhase((phase) => phase instanceof MovePhase && phase.pokemon === pokemon); - } - return true; + // TODO: Should this work with X + Dondozo fusions? + return globalScene.currentBattle?.double && pokemon.getAlly()?.species.speciesId === Species.DONDOZO + && !(pokemon.getAlly().isFainted() || pokemon.getAlly().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, Moves.NONE, pokemon.id); + // Cancel the source Pokemon's next move (if a move is queued) + globalScene.tryRemovePhase((phase) => phase instanceof MovePhase && phase.pokemon === pokemon); } - return false; } } export class PreSwitchOutAbAttr extends AbAttr { - constructor() { - super(true); + constructor(showAbility: boolean = true) { + super(showAbility); } - applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return false; + 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 { - override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (pokemon.status) { - if (!simulated) { - pokemon.resetStatus(); - pokemon.updateInfo(); - } + constructor() { + super(false); + } - return true; + override canApplyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return !Utils.isNullOrUndefined(pokemon.status); + } + + override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + if (!simulated) { + pokemon.resetStatus(); + pokemon.updateInfo(); } - - return false; } } @@ -2867,18 +2895,16 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { } export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr { - override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (!pokemon.isFullHp()) { - if (!simulated) { - const healAmount = Utils.toDmgValue(pokemon.getMaxHp() * 0.33); - pokemon.heal(healAmount); - pokemon.updateInfo(); - } + override canApplyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return !pokemon.isFullHp(); + } - return true; + override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + if (!simulated) { + const healAmount = Utils.toDmgValue(pokemon.getMaxHp() * 0.33); + pokemon.heal(healAmount); + pokemon.updateInfo(); } - - return false; } } @@ -2896,80 +2922,73 @@ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr { 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 - * @returns true if the form change was successful */ - override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const formIndex = this.formFunc(pokemon); - if (formIndex !== pokemon.formIndex) { - if (!simulated) { - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); - } - return true; + override applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + if (!simulated) { + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } - - return false; } } export class PreLeaveFieldAbAttr extends AbAttr { - applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return false; + 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 { - /** - * @param pokemon The {@linkcode Pokemon} with the ability - * @param passive N/A - * @param args N/A - * @returns Returns `true` if the weather clears, otherwise `false`. - */ - applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const weatherType = globalScene.arena.weather?.weatherType; - let turnOffWeather = false; + 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(Abilities.DESOLATE_LAND) && globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { - turnOffWeather = true; + return true; } break; case (WeatherType.HEAVY_RAIN): if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) && globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { - turnOffWeather = true; + return true; } break; case (WeatherType.STRONG_WINDS): if (pokemon.hasAbility(Abilities.DELTA_STREAM) && globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { - turnOffWeather = true; + return true; } break; } - - if (simulated) { - return turnOffWeather; - } - - if (turnOffWeather) { - globalScene.arena.trySetWeather(WeatherType.NONE, false); - return true; - } - 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, false); + } + } } /** @@ -2980,19 +2999,27 @@ export class PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr extends PreLeaveFi super(false); } - public override applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (!simulated) { - const suppressTag = globalScene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag; - if (suppressTag) { - suppressTag.onSourceLeave(globalScene.arena); - return true; - } - } - return simulated; + 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: Utils.BooleanHolder, + args: any[]): boolean { + return true; + } + applyPreStatStageChange( pokemon: Pokemon | null, passive: boolean, @@ -3000,9 +3027,7 @@ export class PreStatStageChangeAbAttr extends AbAttr { stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[], - ): boolean { - return false; - } + ): void {} } /** @@ -3021,9 +3046,8 @@ export class ReflectStatStageChangeAbAttr extends PreStatStageChangeAbAttr { * @param stat the {@linkcode BattleStat} being affected * @param cancelled The {@linkcode Utils.BooleanHolder} that will be set to true due to reflection * @param args - * @returns true because it reflects any stat being lowered */ - applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): void { const attacker: Pokemon = args[0]; const stages = args[1]; this.reflectedStat = stat; @@ -3031,7 +3055,6 @@ export class ReflectStatStageChangeAbAttr extends PreStatStageChangeAbAttr { globalScene.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [ stat ], stages, true, false, true, null, true)); } cancelled.value = true; - return true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { @@ -3056,6 +3079,10 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { this.protectedStat = protectedStat; } + override canApplyPreStatStageChange(pokemon: Pokemon | null, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean { + return Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat; + } + /** * Apply the {@linkcode ProtectedStatAbAttr} to an interaction * @param _pokemon @@ -3064,15 +3091,9 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { * @param stat the {@linkcode BattleStat} being affected * @param cancelled The {@linkcode Utils.BooleanHolder} that will be set to true if the stat is protected * @param _args - * @returns true if the stat is protected, false otherwise */ - applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): boolean { - if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) { - cancelled.value = true; - return true; - } - - return false; + override applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): void { + cancelled.value = true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { @@ -3100,6 +3121,13 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr { 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 @@ -3108,21 +3136,25 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr { * @param move {@link Move} used to apply status effect and confusion * @param hitResult N/A * @param args [0] {@linkcode StatusEffect} applied by move - * @returns true if defender is confused */ - applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (this.effects.indexOf(args[0]) > -1 && !defender.isFainted()) { - if (simulated) { - return defender.canAddTag(BattlerTagType.CONFUSED); - } else { - return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedIntRange(2, 5), move.id, defender.id); - } + override applyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): void { + if (!simulated) { + defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedIntRange(2, 5), move.id, defender.id); } - return false; } } export class PreSetStatusAbAttr extends AbAttr { + canApplyPreSetStatus( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + effect: StatusEffect | undefined, + cancelled: Utils.BooleanHolder, + args: any[]): boolean { + return true; + } + applyPreSetStatus( pokemon: Pokemon, passive: boolean, @@ -3130,9 +3162,7 @@ export class PreSetStatusAbAttr extends AbAttr { effect: StatusEffect | undefined, cancelled: Utils.BooleanHolder, args: any[], - ): boolean { - return false; - } + ): void {} } /** @@ -3150,6 +3180,10 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { this.immuneEffects = immuneEffects; } + override canApplyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: any[]): boolean { + return effect !== StatusEffect.FAINT && this.immuneEffects.length < 1 || this.immuneEffects.includes(effect); + } + /** * Applies immunity to supplied status effects. * @@ -3158,15 +3192,9 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { * @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 - * @returns A boolean indicating the result of the status application. */ - applyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (effect !== StatusEffect.FAINT && this.immuneEffects.length < 1 || this.immuneEffects.includes(effect)) { - cancelled.value = true; - return true; - } - - return false; + override applyPreSetStatus(pokemon: Pokemon, passive: boolean, simulated: boolean, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: any[]): void { + cancelled.value = true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -3196,7 +3224,7 @@ export class StatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr export class UserFieldStatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr { } export class PreApplyBattlerTagAbAttr extends AbAttr { - applyPreApplyBattlerTag( + canApplyPreApplyBattlerTag( pokemon: Pokemon, passive: boolean, simulated: boolean, @@ -3204,8 +3232,17 @@ export class PreApplyBattlerTagAbAttr extends AbAttr { cancelled: Utils.BooleanHolder, args: any[], ): boolean { - return false; + return true; } + + applyPreApplyBattlerTag( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + tag: BattlerTag, + cancelled: Utils.BooleanHolder, + args: any[], + ): void {} } /** @@ -3216,21 +3253,19 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { private battlerTag: BattlerTag; constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) { - super(); + super(true); this.immuneTagTypes = Array.isArray(immuneTagTypes) ? immuneTagTypes : [ immuneTagTypes ]; } - applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.immuneTagTypes.includes(tag.tagType)) { - cancelled.value = true; - if (!simulated) { - this.battlerTag = tag; - } - return true; - } + override canApplyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean { + this.battlerTag = tag; - return false; + return this.immuneTagTypes.includes(tag.tagType); + } + + override applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): void { + cancelled.value = true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -3255,16 +3290,21 @@ export class BattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr { export class UserFieldBattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr { } export class BlockCritAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + constructor() { + super(false); + } + + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { (args[0] as Utils.BooleanHolder).value = true; - return true; } } export class BonusCritAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + constructor() { + super(false); + } + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { (args[0] as Utils.BooleanHolder).value = true; - return true; } } @@ -3272,19 +3312,19 @@ export class MultCritAbAttr extends AbAttr { public multAmount: number; constructor(multAmount: number) { - super(true); + super(false); this.multAmount = multAmount; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const critMult = args[0] as Utils.NumberHolder; - if (critMult.value > 1) { - critMult.value *= this.multAmount; - return true; - } + return critMult.value > 1; + } - return false; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + const critMult = args[0] as Utils.NumberHolder; + critMult.value *= this.multAmount; } } @@ -3297,33 +3337,35 @@ export class ConditionalCritAbAttr extends AbAttr { private condition: PokemonAttackCondition; constructor(condition: PokemonAttackCondition, checkUser?: boolean) { - super(); + 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 Utils.BooleanHolder} If true critical hit is guaranteed. * [1] {@linkcode Pokemon} Target. * [2] {@linkcode Move} used by ability user. */ - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const target = (args[1] as Pokemon); - const move = (args[2] as Move); - if (!this.condition(pokemon, target, move)) { - return false; - } - + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { (args[0] as Utils.BooleanHolder).value = true; - return true; } } export class BlockNonDirectDamageAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + constructor() { + super(false); + } + + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { cancelled.value = true; - return true; } } @@ -3342,26 +3384,27 @@ export class BlockStatusDamageAbAttr extends AbAttr { this.effects = effects; } + override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + if (pokemon.status && this.effects.includes(pokemon.status.effect)) { + return true; + } + return false; + } + /** * @param {Pokemon} pokemon The pokemon with the ability * @param {boolean} passive N/A * @param {Utils.BooleanHolder} cancelled Whether to cancel the status damage * @param {any[]} args N/A - * @returns Returns true if status damage is blocked */ - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (pokemon.status && this.effects.includes(pokemon.status.effect)) { - cancelled.value = true; - return true; - } - return false; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + cancelled.value = true; } } export class BlockOneHitKOAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { cancelled.value = true; - return true; } } @@ -3379,25 +3422,34 @@ export class ChangeMovePriorityAbAttr extends AbAttr { * @param {number} changeAmount the amount of priority added or subtracted */ constructor(moveFunc: (pokemon: Pokemon, move: Move) => boolean, changeAmount: number) { - super(true); + super(false); this.moveFunc = moveFunc; this.changeAmount = changeAmount; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (!this.moveFunc(pokemon, args[0] as Move)) { - return false; - } + 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: Utils.BooleanHolder, args: any[]): void { (args[1] as Utils.NumberHolder).value += this.changeAmount; - return true; } } export class IgnoreContactAbAttr extends AbAttr { } export class PreWeatherEffectAbAttr extends AbAttr { + canApplyPreWeatherEffect( + pokemon: Pokemon, + passive: Boolean, + simulated: boolean, + weather: Weather | null, + cancelled: Utils.BooleanHolder, + args: any[]): boolean { + return true; + } + applyPreWeatherEffect( pokemon: Pokemon, passive: boolean, @@ -3405,9 +3457,7 @@ export class PreWeatherEffectAbAttr extends AbAttr { weather: Weather | null, cancelled: Utils.BooleanHolder, args: any[], - ): boolean { - return false; - } + ): void {} } export class PreWeatherDamageAbAttr extends PreWeatherEffectAbAttr { } @@ -3416,17 +3466,17 @@ export class BlockWeatherDamageAttr extends PreWeatherDamageAbAttr { private weatherTypes: WeatherType[]; constructor(...weatherTypes: WeatherType[]) { - super(); + super(false); this.weatherTypes = weatherTypes; } - applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (!this.weatherTypes.length || this.weatherTypes.indexOf(weather?.weatherType) > -1) { - cancelled.value = true; - } + override canApplyPreWeatherEffect(pokemon: Pokemon, passive: Boolean, simulated: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean { + return !this.weatherTypes.length || this.weatherTypes.indexOf(weather?.weatherType) > -1; + } - return true; + override applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): void { + cancelled.value = true; } } @@ -3434,18 +3484,17 @@ export class SuppressWeatherEffectAbAttr extends PreWeatherEffectAbAttr { public affectsImmutable: boolean; constructor(affectsImmutable?: boolean) { - super(); + super(true); this.affectsImmutable = !!affectsImmutable; } - applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.affectsImmutable || weather.isImmutable()) { - cancelled.value = true; - return true; - } + override canApplyPreWeatherEffect(pokemon: Pokemon, passive: Boolean, simulated: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): boolean { + return this.affectsImmutable || weather.isImmutable(); + } - return false; + override applyPreWeatherEffect(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, cancelled: Utils.BooleanHolder, args: any[]): void { + cancelled.value = true; } } @@ -3550,7 +3599,7 @@ export class ForewarnAbAttr extends PostSummonAbAttr { super(true); } - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { let maxPowerSeen = 0; let maxMove = ""; let movePower = 0; @@ -3577,7 +3626,6 @@ export class ForewarnAbAttr extends PostSummonAbAttr { if (!simulated) { globalScene.queueMessage(i18next.t("abilityTriggers:forewarn", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: maxMove })); } - return true; } } @@ -3586,21 +3634,22 @@ export class FriskAbAttr extends PostSummonAbAttr { super(true); } - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { if (!simulated) { for (const opponent of pokemon.getOpponents()) { globalScene.queueMessage(i18next.t("abilityTriggers:frisk", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), opponentName: opponent.name, opponentAbilityName: opponent.getAbility().name })); setAbilityRevealed(opponent); } } - return true; } } export class PostWeatherChangeAbAttr extends AbAttr { - applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean { - return false; + 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 {} } /** @@ -3619,6 +3668,13 @@ export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr { this.formRevertingWeathers = formRevertingWeathers; } + override canApplyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean { + const isCastformWithForecast = (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST); + const isCherrimWithFlowerGift = (pokemon.species.speciesId === Species.CHERRIM && this.ability === Abilities.FLOWER_GIFT); + + return isCastformWithForecast || isCherrimWithFlowerGift; + } + /** * Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the * weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges} @@ -3626,27 +3682,19 @@ export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr { * @param passive n/a * @param weather n/a * @param args n/a - * @returns whether the form change was triggered */ - applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean { - const isCastformWithForecast = (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST); - const isCherrimWithFlowerGift = (pokemon.species.speciesId === Species.CHERRIM && this.ability === Abilities.FLOWER_GIFT); - - if (isCastformWithForecast || isCherrimWithFlowerGift) { - if (simulated) { - return simulated; - } - - const weatherType = globalScene.arena.weather?.weatherType; - - if (weatherType && this.formRevertingWeathers.includes(weatherType)) { - globalScene.arena.triggerWeatherBasedFormChangesToNormal(); - } else { - globalScene.arena.triggerWeatherBasedFormChanges(); - } - return true; + 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(); } - return false; } } @@ -3663,16 +3711,13 @@ export class PostWeatherChangeAddBattlerTagAttr extends PostWeatherChangeAbAttr this.weatherTypes = weatherTypes; } - applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean { - console.log(this.weatherTypes.find(w => weather === w), WeatherType[weather]); - if (!this.weatherTypes.find(w => weather === w)) { - return false; - } + override canApplyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean { + return !!this.weatherTypes.find(w => weather === w) && pokemon.canAddTag(this.tagType); + } - if (simulated) { - return pokemon.canAddTag(this.tagType); - } else { - return pokemon.addTag(this.tagType, this.turnCount); + override applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): void { + if (!simulated) { + pokemon.addTag(this.tagType, this.turnCount); } } } @@ -3686,15 +3731,22 @@ export class PostWeatherLapseAbAttr extends AbAttr { 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[], - ): boolean { - return false; - } + ): void {} getCondition(): AbAttrCondition { return getWeatherCondition(...this.weatherTypes); @@ -3710,17 +3762,16 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr { this.healFactor = healFactor; } - applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, args: any[]): boolean { - if (!pokemon.isFullHp()) { - const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - if (!simulated) { - globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)), i18next.t("abilityTriggers:postWeatherLapseHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); - } - return true; - } + override canApplyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather | null, args: any[]): boolean { + return !pokemon.isFullHp(); + } - return false; + override applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, args: any[]): void { + const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; + if (!simulated) { + globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), + Utils.toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)), i18next.t("abilityTriggers:postWeatherLapseHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); + } } } @@ -3733,25 +3784,25 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr { this.damageFactor = damageFactor; } - applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, args: any[]): boolean { - if (pokemon.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { - return false; - } + 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.queueMessage(i18next.t("abilityTriggers:postWeatherLapseDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName })); pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / (16 / this.damageFactor)), HitResult.OTHER); } - - return true; } } export class PostTerrainChangeAbAttr extends AbAttr { - applyPostTerrainChange(pokemon: Pokemon, passive: boolean, simulated: boolean, terrain: TerrainType, args: any[]): boolean { - return false; + 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 { @@ -3767,15 +3818,13 @@ export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr this.terrainTypes = terrainTypes; } - applyPostTerrainChange(pokemon: Pokemon, passive: boolean, simulated: boolean, terrain: TerrainType, args: any[]): boolean { - if (!this.terrainTypes.find(t => t === terrain)) { - return false; - } + override canApplyPostTerrainChange(pokemon: Pokemon, passive: boolean, simulated: boolean, terrain: TerrainType, args: any[]): boolean { + return !!this.terrainTypes.find(t => t === terrain) && pokemon.canAddTag(this.tagType); + } - if (simulated) { - return pokemon.canAddTag(this.tagType); - } else { - return pokemon.addTag(this.tagType, this.turnCount); + override applyPostTerrainChange(pokemon: Pokemon, passive: boolean, simulated: boolean, terrain: TerrainType, args: any[]): void { + if (!simulated) { + pokemon.addTag(this.tagType, this.turnCount); } } } @@ -3788,9 +3837,11 @@ function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition { } export class PostTurnAbAttr extends AbAttr { - applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return false; + canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return true; } + + applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {} } /** @@ -3808,24 +3859,21 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { this.effects = effects; } + override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return !Utils.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 - * @returns Returns true if healed from status, false if not */ - override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (pokemon.status && this.effects.includes(pokemon.status.effect)) { - if (!pokemon.isFullHp()) { - if (!simulated) { - const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / 8), i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), true)); - } - return true; - } + override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + if (!simulated) { + const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; + globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), + Utils.toDmgValue(pokemon.getMaxHp() / 8), i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), true)); } - return false; } } @@ -3842,23 +3890,21 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr { this.allyTarget = allyTarget; } - applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (this.allyTarget) { this.target = pokemon.getAlly(); } else { this.target = pokemon; } - if (this.target?.status) { - if (!simulated) { - globalScene.queueMessage(getStatusEffectHealText(this.target.status?.effect, getPokemonNameWithAffix(this.target))); - this.target.resetStatus(false); - this.target.updateInfo(); - } + return !Utils.isNullOrUndefined(this.target.status); + } - return true; + override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + if (!simulated && this.target.status) { + globalScene.queueMessage(getStatusEffectHealText(this.target.status?.effect, getPokemonNameWithAffix(this.target))); + this.target.resetStatus(false); + this.target.updateInfo(); } - - return false; } } @@ -3879,18 +3925,14 @@ export class PostTurnLootAbAttr extends PostTurnAbAttr { super(); } - applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const pass = Phaser.Math.RND.realInRange(0, 1); + override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { // Clamp procChance to [0, 1]. Skip if didn't proc (less than pass) - if (Math.max(Math.min(this.procChance(pokemon), 1), 0) < pass) { - return false; - } + const pass = Phaser.Math.RND.realInRange(0, 1); + return !(Math.max(Math.min(this.procChance(pokemon), 1), 0) < pass) && this.itemType === "EATEN_BERRIES" && !!pokemon.battleData.berriesEaten; + } - if (this.itemType === "EATEN_BERRIES") { - return this.createEatenBerry(pokemon, simulated); - } else { - return false; - } + override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + this.createEatenBerry(pokemon, simulated); } /** @@ -3951,13 +3993,12 @@ export class MoodyAbAttr extends PostTurnAbAttr { * @param passive N/A * @param simulated true if applying in a simulated call. * @param args N/A - * @returns true * * 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 */ - applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + 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); @@ -3972,8 +4013,6 @@ export class MoodyAbAttr extends PostTurnAbAttr { globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ loweredStat ], -1)); } } - - return true; } } @@ -3983,31 +4022,26 @@ export class SpeedBoostAbAttr extends PostTurnAbAttr { super(true); } - applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (!simulated) { - if (!pokemon.turnData.switchedInThisTurn && !pokemon.turnData.failedRunAway) { - globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPD ], 1)); - } else { - return false; - } - } - return 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.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPD ], 1)); } } export class PostTurnHealAbAttr extends PostTurnAbAttr { - applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (!pokemon.isFullHp()) { - if (!simulated) { - const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), - Utils.toDmgValue(pokemon.getMaxHp() / 16), i18next.t("abilityTriggers:postTurnHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); - } + override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return !pokemon.isFullHp(); + } - return true; + override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + if (!simulated) { + const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; + globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), + Utils.toDmgValue(pokemon.getMaxHp() / 16), i18next.t("abilityTriggers:postTurnHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); } - - return false; } } @@ -4020,17 +4054,14 @@ export class PostTurnFormChangeAbAttr extends PostTurnAbAttr { this.formFunc = formFunc; } - applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const formIndex = this.formFunc(pokemon); - if (formIndex !== pokemon.formIndex) { - if (!simulated) { - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); - } + override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return this.formFunc(pokemon) !== pokemon.formIndex; + } - return true; + override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + if (!simulated) { + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } - - return false; } } @@ -4039,28 +4070,25 @@ export class PostTurnFormChangeAbAttr extends PostTurnAbAttr { * 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(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus); + } /** * Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1) * @param pokemon Pokemon that has this ability * @param passive N/A * @param simulated `true` if applying in a simulated call. * @param args N/A - * @returns `true` if any opponents are sleeping */ - override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - let hadEffect = false; + 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(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) { if (!simulated) { opp.damageAndUpdate(Utils.toDmgValue(opp.getMaxHp() / 8), HitResult.OTHER); globalScene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) })); } - hadEffect = true; } - } - return hadEffect; } } @@ -4073,25 +4101,22 @@ export class FetchBallAbAttr extends PostTurnAbAttr { constructor() { super(); } + + override canApplyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return !simulated && !Utils.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 - * @returns true if player has used a pokeball and this pokemon is owned by the player */ - applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (simulated) { - return false; - } + override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { const lastUsed = globalScene.currentBattle.lastUsedPokeball; - if (lastUsed !== null && !!pokemon.isPlayer) { - globalScene.pokeballCounts[lastUsed]++; - globalScene.currentBattle.lastUsedPokeball = null; - globalScene.queueMessage(i18next.t("abilityTriggers:fetchBall", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokeballName: getPokeballName(lastUsed) })); - return true; - } - return false; + globalScene.pokeballCounts[lastUsed!]++; + globalScene.currentBattle.lastUsedPokeball = null; + globalScene.queueMessage(i18next.t("abilityTriggers:fetchBall", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokeballName: getPokeballName(lastUsed!) })); } } @@ -4106,16 +4131,14 @@ export class PostBiomeChangeWeatherChangeAbAttr extends PostBiomeChangeAbAttr { this.weatherType = weatherType; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (!globalScene.arena.weather?.isImmutable()) { - if (simulated) { - return globalScene.arena.weather?.weatherType !== this.weatherType; - } else { - return globalScene.arena.trySetWeather(this.weatherType, true); - } - } + override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return ((globalScene.arena.weather?.isImmutable() ?? false) && globalScene.arena.canSetWeather(this.weatherType)); + } - return false; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + if (!simulated) { + globalScene.arena.trySetWeather(this.weatherType, true); + } } } @@ -4128,11 +4151,13 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { this.terrainType = terrainType; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (simulated) { - return globalScene.arena.terrain?.terrainType !== this.terrainType; - } else { - return globalScene.arena.trySetTerrain(this.terrainType, true); + 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: Utils.BooleanHolder, args: any[]): void { + if (!simulated) { + globalScene.arena.trySetTerrain(this.terrainType, true); } } } @@ -4142,6 +4167,16 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { * @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, @@ -4149,9 +4184,7 @@ export class PostMoveUsedAbAttr extends AbAttr { targets: BattlerIndex[], simulated: boolean, args: any[], - ): boolean { - return false; - } + ): void {} } /** @@ -4159,6 +4192,15 @@ export class PostMoveUsedAbAttr extends AbAttr { * @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 @@ -4167,8 +4209,6 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { * @param source {@linkcode Pokemon} that used the dancing move * @param targets {@linkcode BattlerIndex}Targets of the dancing move * @param args N/A - * - * @return true if the Dancer ability was resolved */ override applyPostMoveUsed( dancer: Pokemon, @@ -4176,27 +4216,17 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { 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 - if (source.getBattlerIndex() !== dancer.getBattlerIndex() - && !dancer.summonData.tags.some(tag => forbiddenTags.includes(tag.tagType))) { - if (!simulated) { - // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance - if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) { - const target = this.getTarget(dancer, source, targets); - globalScene.unshiftPhase(new MovePhase(dancer, target, move, true, true)); - } else if (move.getMove() instanceof SelfStatusMove) { - // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself - globalScene.unshiftPhase(new MovePhase(dancer, [ dancer.getBattlerIndex() ], move, true, true)); - } + args: any[]): void { + if (!simulated) { + // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance + if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) { + const target = this.getTarget(dancer, source, targets); + globalScene.unshiftPhase(new MovePhase(dancer, target, move, true, true)); + } else if (move.getMove() instanceof SelfStatusMove) { + // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself + globalScene.unshiftPhase(new MovePhase(dancer, [ dancer.getBattlerIndex() ], move, true, true)); } - return true; } - return false; } /** @@ -4219,9 +4249,11 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { * @extends AbAttr */ export class PostItemLostAbAttr extends AbAttr { - applyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): boolean { - return false; + canApplyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): boolean { + return true; } + + applyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): void {} } /** @@ -4231,21 +4263,21 @@ export class PostItemLostAbAttr extends AbAttr { export class PostItemLostApplyBattlerTagAbAttr extends PostItemLostAbAttr { private tagType: BattlerTagType; constructor(tagType: BattlerTagType) { - super(true); + 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 - * @returns true if BattlerTag was applied */ - override applyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): boolean { - if (!pokemon.getTag(this.tagType) && !simulated) { - pokemon.addTag(this.tagType); - return true; - } - return false; + override applyPostItemLost(pokemon: Pokemon, simulated: boolean, args: any[]): void { + pokemon.addTag(this.tagType); } } @@ -4253,15 +4285,13 @@ export class StatStageChangeMultiplierAbAttr extends AbAttr { private multiplier: number; constructor(multiplier: number) { - super(true); + super(false); this.multiplier = multiplier; } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { (args[0] as Utils.NumberHolder).value *= this.multiplier; - - return true; } } @@ -4272,11 +4302,10 @@ export class StatStageChangeCopyAbAttr extends AbAttr { simulated: boolean, cancelled: Utils.BooleanHolder, args: any[], - ): boolean { + ): void { if (!simulated) { globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, (args[0] as BattleStat[]), (args[1] as number), true, false, false)); } - return true; } } @@ -4285,10 +4314,8 @@ export class BypassBurnDamageReductionAbAttr extends AbAttr { super(false); } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { cancelled.value = true; - - return true; } } @@ -4307,28 +4334,21 @@ export class ReduceBurnDamageAbAttr extends AbAttr { * @param passive N/A * @param cancelled N/A * @param args `[0]` {@linkcode Utils.NumberHolder} The damage value being modified - * @returns `true` */ - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { (args[0] as Utils.NumberHolder).value = Utils.toDmgValue((args[0] as Utils.NumberHolder).value * this.multiplier); - - return true; } } export class DoubleBerryEffectAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { (args[0] as Utils.NumberHolder).value *= 2; - - return true; } } export class PreventBerryUseAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { cancelled.value = true; - - return true; } } @@ -4348,7 +4368,7 @@ export class HealFromBerryUseAbAttr extends AbAttr { this.healPercent = Math.max(Math.min(healPercent, 1), 0); } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, ...args: [Utils.BooleanHolder, any[]]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, ...args: [Utils.BooleanHolder, any[]]): void { const { name: abilityName } = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); if (!simulated) { globalScene.unshiftPhase( @@ -4360,15 +4380,12 @@ export class HealFromBerryUseAbAttr extends AbAttr { ) ); } - return true; } } export class RunSuccessAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { (args[0] as Utils.NumberHolder).value = 256; - - return true; } } @@ -4388,6 +4405,16 @@ export class CheckTrappedAbAttr extends AbAttr { this.arenaTrapCondition = condition; } + canApplyCheckTrapped( + pokemon: Pokemon, + passive: boolean, + simulated: boolean, + trapped: Utils.BooleanHolder, + otherPokemon: Pokemon, + args: any[]): boolean { + return true; + } + applyCheckTrapped( pokemon: Pokemon, passive: boolean, @@ -4395,9 +4422,7 @@ export class CheckTrappedAbAttr extends AbAttr { trapped: Utils.BooleanHolder, otherPokemon: Pokemon, args: any[], - ): boolean { - return false; - } + ): void {} } /** @@ -4407,6 +4432,12 @@ export class CheckTrappedAbAttr extends AbAttr { * @see {@linkcode applyCheckTrapped} */ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { + override canApplyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: Utils.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(Abilities.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 @@ -4418,22 +4449,9 @@ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { * @param trapped {@link Utils.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 - * @returns if enemy Pokemon is trapped or not */ - applyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, args: any[]): boolean { - if (this.arenaTrapCondition(pokemon, otherPokemon)) { - if (otherPokemon.getTypes(true).includes(PokemonType.GHOST) || (otherPokemon.getTypes(true).includes(PokemonType.STELLAR) && otherPokemon.getTypes().includes(PokemonType.GHOST))) { - trapped.value = false; - return false; - } else if (otherPokemon.hasAbility(Abilities.RUN_AWAY)) { - trapped.value = false; - return false; - } - trapped.value = true; - return true; - } - trapped.value = false; - return false; + override applyCheckTrapped(pokemon: Pokemon, passive: boolean, simulated: boolean, trapped: Utils.BooleanHolder, otherPokemon: Pokemon, args: any[]): void { + trapped.value = true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -4442,10 +4460,12 @@ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { } export class MaxMultiHitAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value = 0; + constructor() { + super(false); + } - return true; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + (args[0] as Utils.NumberHolder).value = 0; } } @@ -4454,36 +4474,48 @@ export class PostBattleAbAttr extends AbAttr { super(true); } - applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return false; + 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 { - /** - * @param args - `[0]`: boolean for if the battle ended in a victory - * @returns `true` if successful - */ - applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + 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]) { - const randItem = Utils.randSeedItem(postBattleLoot); - //@ts-ignore - TODO see below - if (globalScene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true, undefined, false)) { - postBattleLoot.splice(postBattleLoot.indexOf(randItem), 1); - globalScene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: randItem.type.name })); - return true; - } + this.randItem = Utils.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 = Utils.randSeedItem(postBattleLoot); } - return false; + if (globalScene.tryTransferHeldItemModifier(this.randItem, pokemon, true, 1, true, undefined, false)) { + postBattleLoot.splice(postBattleLoot.indexOf(this.randItem), 1); + globalScene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: this.randItem.type.name })); + } + this.randItem = undefined; } } export class PostFaintAbAttr extends AbAttr { - applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean { - return false; + 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 {} } /** @@ -4492,6 +4524,10 @@ export class PostFaintAbAttr extends AbAttr { * @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 @@ -4501,20 +4537,11 @@ export class PostFaintUnsuppressedWeatherFormChangeAbAttr extends PostFaintAbAtt * @param move n/a * @param hitResult n/a * @param args n/a - * @returns whether the form change was triggered */ - applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - const pokemonToTransform = getPokemonWithWeatherBasedForms(); - - if (pokemonToTransform.length < 1) { - return false; - } - + override applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): void { if (!simulated) { globalScene.arena.triggerWeatherBasedFormChanges(); } - - return true; } } @@ -4522,26 +4549,27 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { private damageRatio: number; constructor(damageRatio: number) { - super(); + super(true); this.damageRatio = damageRatio; } - applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean { - if (move !== undefined && attacker !== undefined && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { //If the mon didn't die to indirect damage - const cancelled = new Utils.BooleanHolder(false); - globalScene.getField(true).map(p => applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled, simulated)); - if (cancelled.value || attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { - return false; - } - if (!simulated) { - attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); - attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); - } - return true; + override canApplyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean { + const diedToDirectDamage = move !== undefined && attacker !== undefined && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon); + const cancelled = new Utils.BooleanHolder(false); + globalScene.getField(true).map(p => applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled, simulated)); + if (!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { + return false; } - return false; + return true; + } + + override applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): void { + if (!simulated) { + attacker!.damageAndUpdate(Utils.toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); + attacker!.turnData.damageTaken += Utils.toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio)); + } } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -4557,13 +4585,12 @@ export class PostFaintHPDamageAbAttr extends PostFaintAbAttr { super (); } - applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean { + override applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): void { if (move !== undefined && attacker !== undefined && !simulated) { //If the mon didn't die to indirect damage const damage = pokemon.turnData.attacksReceived[0].damage; attacker.damageAndUpdate((damage), HitResult.OTHER); attacker.turnData.damageTaken += damage; } - return true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -4571,21 +4598,34 @@ export class PostFaintHPDamageAbAttr extends PostFaintAbAttr { } } +/** + * Redirects a move to the pokemon with this ability if it meets the conditions + */ export class RedirectMoveAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.canRedirect(args[0] as Moves)) { - const target = args[1] as Utils.NumberHolder; - const newTarget = pokemon.getBattlerIndex(); - if (target.value !== newTarget) { - target.value = newTarget; - return true; - } - } + /** + * @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 + */ - return false; + override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + if (!this.canRedirect(args[0] as Moves, args[2] as Pokemon)) { + return false; + } + const target = args[1] as Utils.NumberHolder; + const newTarget = pokemon.getBattlerIndex(); + return target.value !== newTarget; } - canRedirect(moveId: Moves): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + const target = args[1] as Utils.NumberHolder; + const newTarget = pokemon.getBattlerIndex(); + target.value = newTarget; + } + + canRedirect(moveId: Moves, user: Pokemon): boolean { const move = allMoves[moveId]; return !![ MoveTarget.NEAR_OTHER, MoveTarget.OTHER ].find(t => move.moveTarget === t); } @@ -4599,8 +4639,8 @@ export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr { this.type = type; } - canRedirect(moveId: Moves): boolean { - return super.canRedirect(moveId) && allMoves[moveId].type === this.type; + canRedirect(moveId: Moves, user: Pokemon): boolean { + return super.canRedirect(moveId, user) && user.getMoveType(allMoves[moveId]) === this.type; } } @@ -4615,28 +4655,23 @@ export class ReduceStatusEffectDurationAbAttr extends AbAttr { private statusEffect: StatusEffect; constructor(statusEffect: StatusEffect) { - super(true); + super(false); this.statusEffect = statusEffect; } + override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return args[1] instanceof Utils.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 - * @returns `true` if the ability was applied */ - apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (!(args[1] instanceof Utils.NumberHolder)) { - return false; - } - if (args[0] === this.statusEffect) { - args[1].value -= 1; - return true; - } - - return false; + override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]): void { + args[1].value -= 1; } } @@ -4659,20 +4694,18 @@ export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr { this.stages = stages; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { if (!simulated) { globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); } - return true; } } export class IncreasePpAbAttr extends AbAttr { } export class ForceSwitchOutImmunityAbAttr extends AbAttr { - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { cancelled.value = true; - return true; } } @@ -4681,15 +4714,13 @@ export class ReduceBerryUseThresholdAbAttr extends AbAttr { super(); } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const hpRatio = pokemon.getHpRatio(); + return args[0].value < hpRatio; + } - if (args[0].value < hpRatio) { - args[0].value *= 2; - return args[0].value >= hpRatio; - } - - return false; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + args[0].value *= 2; } } @@ -4701,15 +4732,13 @@ export class WeightMultiplierAbAttr extends AbAttr { private multiplier: number; constructor(multiplier: number) { - super(); + super(false); this.multiplier = multiplier; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { (args[0] as Utils.NumberHolder).value *= this.multiplier; - - return true; } } @@ -4718,10 +4747,8 @@ export class SyncEncounterNatureAbAttr extends AbAttr { super(false); } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { (args[0] as Pokemon).setNature(pokemon.getNature()); - - return true; } } @@ -4734,12 +4761,12 @@ export class MoveAbilityBypassAbAttr extends AbAttr { this.moveIgnoreFunc = moveIgnoreFunc || ((pokemon, move) => true); } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.moveIgnoreFunc(pokemon, (args[0] as Move))) { - cancelled.value = true; - return true; - } - return false; + 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: Utils.BooleanHolder, args: any[]): void { + cancelled.value = true; } } @@ -4753,6 +4780,14 @@ export class IgnoreProtectOnContactAbAttr extends AbAttr { } * 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 Utils.BooleanHolder; + } + /** * Sets a flag to bypass screens, Substitute, Safeguard, and Mist * @param pokemon n/a @@ -4760,15 +4795,10 @@ export class InfiltratorAbAttr extends AbAttr { * @param simulated n/a * @param cancelled n/a * @param args `[0]` a {@linkcode Utils.BooleanHolder | BooleanHolder} containing the flag - * @returns `true` if the bypass flag was successfully set; `false` otherwise. */ - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: null, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: null, args: any[]): void { const bypassed = args[0]; - if (args[0] instanceof Utils.BooleanHolder) { - bypassed.value = true; - return true; - } - return false; + bypassed.value = true; } } @@ -4814,17 +4844,17 @@ export class IgnoreTypeImmunityAbAttr extends AbAttr { private allowedMoveTypes: PokemonType[]; constructor(defenderType: PokemonType, allowedMoveTypes: PokemonType[]) { - super(true); + super(false); this.defenderType = defenderType; this.allowedMoveTypes = allowedMoveTypes; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.defenderType === (args[1] as PokemonType) && this.allowedMoveTypes.includes(args[0] as PokemonType)) { - cancelled.value = true; - return true; - } - return false; + 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: Utils.BooleanHolder, args: any[]): void { + cancelled.value = true; } } @@ -4836,19 +4866,18 @@ export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr { private defenderType: PokemonType[]; constructor(statusEffect: StatusEffect[], defenderType: PokemonType[]) { - super(true); + super(false); this.statusEffect = statusEffect; this.defenderType = defenderType; } - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.statusEffect.includes(args[0] as StatusEffect) && this.defenderType.includes(args[1] as PokemonType)) { - cancelled.value = true; - return true; - } + 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); + } - return false; + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { + cancelled.value = true; } } @@ -4863,18 +4892,17 @@ export class MoneyAbAttr extends PostBattleAbAttr { super(); } + 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 - * @returns `true` if successful */ - applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (!simulated && args[0]) { - globalScene.currentBattle.moneyScattered += globalScene.getWaveMoneyAmount(0.2); - return true; - } - return false; + override applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + globalScene.currentBattle.moneyScattered += globalScene.getWaveMoneyAmount(0.2); } } @@ -4902,6 +4930,12 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC 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. @@ -4909,15 +4943,9 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC * @param {Pokemon} pokemon - The Pokémon being summoned. * @param {boolean} passive - Whether the effect is passive. * @param {any[]} args - Additional arguments. - * @returns {boolean} - Returns true if the stat change was applied, otherwise false. */ - applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const side = pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - - if (globalScene.arena.getTagOnSide(this.tagType, side)) { - return super.applyPostSummon(pokemon, passive, simulated, args); - } - return false; + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + super.applyPostSummon(pokemon, passive, simulated, args); } } @@ -4941,6 +4969,10 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { this.triggerMessageFunc = triggerMessageFunc; } + override canApplyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.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. @@ -4951,21 +4983,15 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { * @param move The move being used. * @param _cancelled n/a * @param args Additional arguments. - * @returns `true` if the immunity was applied. */ - override applyPreDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { - if (!simulated) { - (args[0] as Utils.NumberHolder).value = this.multiplier; - pokemon.removeTag(this.tagType); - if (this.recoilDamageFunc) { - pokemon.damageAndUpdate(this.recoilDamageFunc(pokemon), HitResult.OTHER, false, false, true, true); - } + override applyPreDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _cancelled: Utils.BooleanHolder, args: any[]): void { + if (!simulated) { + (args[0] as Utils.NumberHolder).value = this.multiplier; + pokemon.removeTag(this.tagType); + if (this.recoilDamageFunc) { + pokemon.damageAndUpdate(this.recoilDamageFunc(pokemon), HitResult.OTHER, false, false, true, true); } - return true; } - - return false; } /** @@ -4996,34 +5022,25 @@ export class BypassSpeedChanceAbAttr extends AbAttr { this.chance = chance; } + override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + const bypassSpeed = args[0] as Utils.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.randSeedInt(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 {Utils.BooleanHolder} cancelled N/A * @param {any[]} args [0] {@linkcode Utils.BooleanHolder} set to true when the ability activated - * @returns {boolean} - whether the ability was activated. */ - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (simulated) { - return false; - } + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { const bypassSpeed = args[0] as Utils.BooleanHolder; - - if (!bypassSpeed.value && pokemon.randSeedInt(100) < this.chance) { - 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; - - if (isCommandFight && isDamageMove) { - bypassSpeed.value = true; - return true; - } - } - - return false; + bypassSpeed.value = true; } getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { @@ -5046,23 +5063,22 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr { 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 */ - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void { const bypassSpeed = args[0] as Utils.BooleanHolder; const canCheckHeldItems = args[1] as Utils.BooleanHolder; - - const turnCommand = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; - const isCommandFight = turnCommand?.command === Command.FIGHT; - const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null; - if (isCommandFight && this.condition(pokemon, move!)) { - bypassSpeed.value = false; - canCheckHeldItems.value = false; - return false; - } - return true; + bypassSpeed.value = false; + canCheckHeldItems.value = false; } } @@ -5075,10 +5091,11 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { super(true); } - override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, _args: any[]): boolean { - if (pokemon.isTerastallized) { - return false; - } + override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + return !pokemon.isTerastallized; + } + + override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, _args: any[]): void { const currentTerrain = globalScene.arena.getTerrainType(); const typeChange: PokemonType[] = this.determineTypeChange(pokemon, currentTerrain); if (typeChange.length !== 0) { @@ -5088,7 +5105,6 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { pokemon.summonData.types = typeChange; pokemon.updateInfo(); } - return true; } /** @@ -5121,15 +5137,16 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { 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 - * @returns `true` if there is an active terrain requiring a type change | `false` if not */ - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (globalScene.arena.getTerrainType() !== TerrainType.NONE) { - return this.apply(pokemon, passive, simulated, new Utils.BooleanHolder(false), []); - } - return false; + override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { + this.apply(pokemon, passive, simulated, new Utils.BooleanHolder(false), []); } override getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]) { @@ -5149,10 +5166,10 @@ function applySingleAbAttrs( passive: boolean, attrType: Constructor, applyFunc: AbAttrApplyFunc, + successFunc: AbAttrSuccessFunc, args: any[], - gainedMidTurn = false, - simulated = false, - showAbilityInstant = false, + gainedMidTurn: boolean = false, + simulated: boolean = false, messages: string[] = [] ) { if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) { @@ -5166,33 +5183,39 @@ function applySingleAbAttrs( for (const attr of ability.getAttrs(attrType)) { const condition = attr.getCondition(); - if (condition && !condition(pokemon)) { + let abShown = false; + if (condition && !condition(pokemon) || !successFunc(attr, passive)) { continue; } globalScene.setPhaseQueueSplice(); - if (applyFunc(attr, passive)) { - if (pokemon.summonData && !pokemon.summonData.abilitiesApplied.includes(ability.id)) { - pokemon.summonData.abilitiesApplied.push(ability.id); - } - if (pokemon.battleData && !simulated && !pokemon.battleData.abilitiesApplied.includes(ability.id)) { - pokemon.battleData.abilitiesApplied.push(ability.id); - } - if (attr.showAbility && !simulated) { - if (showAbilityInstant) { - globalScene.abilityBar.showAbility(pokemon, passive); - } else { - queueShowAbility(pokemon, passive); - } - } - const message = attr.getTriggerMessage(pokemon, ability.name, args); - if (message) { - if (!simulated) { - globalScene.queueMessage(message); - } - } - messages.push(message!); + + if (attr.showAbility && !simulated) { + globalScene.queueAbilityDisplay(pokemon, passive, true); + abShown = true; } + const message = attr.getTriggerMessage(pokemon, ability.name, args); + if (message) { + if (!simulated) { + globalScene.queueMessage(message); + } + messages.push(message); + } + + applyFunc(attr, passive); + + if (abShown) { + globalScene.queueAbilityDisplay(pokemon, passive, false); + } + + if (pokemon.summonData && !pokemon.summonData.abilitiesApplied.includes(ability.id)) { + pokemon.summonData.abilitiesApplied.push(ability.id); + } + if (pokemon.battleData && !simulated && !pokemon.battleData.abilitiesApplied.includes(ability.id)) { + pokemon.battleData.abilitiesApplied.push(ability.id); + } + + globalScene.clearPhaseQueueSplice(); } } @@ -5336,6 +5359,16 @@ function calculateShellBellRecovery(pokemon: Pokemon): number { * @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, @@ -5343,9 +5376,7 @@ export class PostDamageAbAttr extends AbAttr { simulated: boolean, args: any[], source?: Pokemon, - ): boolean { - return false; - } + ): void {} } /** @@ -5367,27 +5398,13 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { this.hpRatio = hpRatio; } - /** - * 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 The amount of damage taken by the Pokémon. - * @param passive N/A - * @param simulated Whether the ability is being simulated. - * @param args N/A - * @param source The Pokemon that dealt damage - * @returns `true` if the switch-out logic was successfully applied - */ - public override applyPostDamage( + public override canApplyPostDamage( pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean, args: any[], - source?: Pokemon, - ): boolean { + 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 = [ Moves.BELLY_DRUM, Moves.SUBSTITUTE, Moves.CURSE, Moves.PAIN_SPLIT ]; @@ -5421,7 +5438,6 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { } if (pokemon.hp + damage >= pokemon.getMaxHp() * this.hpRatio) { - // Activates if it falls below half and recovers back above half from a Shell Bell const shellBellHeal = calculateShellBellRecovery(pokemon); if (pokemon.hp - shellBellHeal < pokemon.getMaxHp() * this.hpRatio) { for (const opponent of pokemon.getOpponents()) { @@ -5429,31 +5445,42 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { return false; } } - return this.helper.switchOutLogic(pokemon); - } else { - return false; + return true; } - } else { - return false; } + + return false; } - public getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null { - return this.helper.getFailedText(target); + + /** + * 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); } } function applyAbAttrsInternal( attrType: Constructor, pokemon: Pokemon | null, applyFunc: AbAttrApplyFunc, + successFunc: AbAttrSuccessFunc, args: any[], - showAbilityInstant = false, - simulated = false, + simulated: boolean = false, messages: string[] = [], gainedMidTurn = false ) { for (const passive of [ false, true ]) { if (pokemon) { - applySingleAbAttrs(pokemon, passive, attrType, applyFunc, args, gainedMidTurn, simulated, showAbilityInstant, messages); + applySingleAbAttrs(pokemon, passive, attrType, applyFunc, successFunc, args, gainedMidTurn, simulated, messages); globalScene.clearPhaseQueueSplice(); } } @@ -5470,8 +5497,8 @@ export function applyAbAttrs( attrType, pokemon, (attr, passive) => attr.apply(pokemon, passive, simulated, cancelled, args), + (attr, passive) => attr.canApply(pokemon, passive, simulated, args), args, - false, simulated, ); } @@ -5486,8 +5513,8 @@ export function applyPostBattleInitAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostBattleInit(pokemon, passive, simulated, args), + (attr, passive) => attr.canApplyPostBattleInit(pokemon, passive, simulated, args), args, - false, simulated, ); } @@ -5505,8 +5532,8 @@ export function applyPreDefendAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args), + (attr, passive) => attr.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args), args, - false, simulated, ); } @@ -5524,8 +5551,7 @@ export function applyPostDefendAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), - args, - false, + (attr, passive) => attr.canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), args, simulated, ); } @@ -5543,8 +5569,8 @@ export function applyPostMoveUsedAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostMoveUsed(pokemon, move, source, targets, simulated, args), + (attr, passive) => attr.canApplyPostMoveUsed(pokemon, move, source, targets, simulated, args), args, - false, simulated, ); } @@ -5561,6 +5587,7 @@ export function applyStatMultiplierAbAttrs( attrType, pokemon, (attr, passive) => attr.applyStatStage(pokemon, passive, simulated, stat, statValue, args), + (attr, passive) => attr.canApplyStatStage(pokemon, passive, simulated, stat, statValue, args), args, ); } @@ -5576,8 +5603,7 @@ export function applyPostSetStatusAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), - args, - false, + (attr, passive) => attr.canApplyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), args, simulated, ); } @@ -5595,6 +5621,7 @@ export function applyPostDamageAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostDamage(pokemon, damage, passive, simulated, args, source), + (attr, passive) => attr.canApplyPostDamage(pokemon, damage, passive, simulated, args, source), args, ); } @@ -5622,9 +5649,8 @@ export function applyFieldStatMultiplierAbAttrs( applyAbAttrsInternal( attrType, pokemon, - (attr, passive) => - attr.applyFieldStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), - args, + (attr, passive) => attr.applyFieldStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), + (attr, passive) => attr.canApplyFieldStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), args, ); } @@ -5640,8 +5666,8 @@ export function applyPreAttackAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPreAttack(pokemon, passive, simulated, defender, move, args), + (attr, passive) => attr.canApplyPreAttack(pokemon, passive, simulated, defender, move, args), args, - false, simulated, ); } @@ -5659,8 +5685,7 @@ export function applyPostAttackAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), - args, - false, + (attr, passive) => attr.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), args, simulated, ); } @@ -5676,8 +5701,8 @@ export function applyPostKnockOutAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostKnockOut(pokemon, passive, simulated, knockedOut, args), + (attr, passive) => attr.canApplyPostKnockOut(pokemon, passive, simulated, knockedOut, args), args, - false, simulated, ); } @@ -5692,8 +5717,8 @@ export function applyPostVictoryAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostVictory(pokemon, passive, simulated, args), + (attr, passive) => attr.canApplyPostVictory(pokemon, passive, simulated, args), args, - false, simulated, ); } @@ -5708,8 +5733,8 @@ export function applyPostSummonAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), + (attr, passive) => attr.canApplyPostSummon(pokemon, passive, simulated, args), args, - false, simulated, ); } @@ -5724,8 +5749,8 @@ export function applyPreSwitchOutAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPreSwitchOut(pokemon, passive, simulated, args), + (attr, passive) => attr.canApplyPreSwitchOut(pokemon, passive, simulated, args), args, - true, simulated, ); } @@ -5741,8 +5766,8 @@ export function applyPreLeaveFieldAbAttrs( pokemon, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, args), + (attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, args), args, - true, simulated ); } @@ -5759,8 +5784,7 @@ export function applyPreStatStageChangeAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args), - args, - false, + (attr, passive) => attr.canApplyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args), args, simulated, ); } @@ -5778,8 +5802,7 @@ export function applyPostStatStageChangeAbAttrs( attrType, pokemon, (attr, _passive) => attr.applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args), - args, - false, + (attr, _passive) => attr.canApplyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args), args, simulated, ); } @@ -5796,8 +5819,8 @@ export function applyPreSetStatusAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args), + (attr, passive) => attr.canApplyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args), args, - false, simulated, ); } @@ -5814,8 +5837,7 @@ export function applyPreApplyBattlerTagAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args), - args, - false, + (attr, passive) => attr.canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args), args, simulated, ); } @@ -5832,8 +5854,7 @@ export function applyPreWeatherEffectAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args), - args, - true, + (attr, passive) => attr.canApplyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args), args, simulated, ); } @@ -5848,8 +5869,8 @@ export function applyPostTurnAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostTurn(pokemon, passive, simulated, args), + (attr, passive) => attr.canApplyPostTurn(pokemon, passive, simulated, args), args, - false, simulated, ); } @@ -5865,8 +5886,8 @@ export function applyPostWeatherChangeAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostWeatherChange(pokemon, passive, simulated, weather, args), + (attr, passive) => attr.canApplyPostWeatherChange(pokemon, passive, simulated, weather, args), args, - false, simulated, ); } @@ -5882,8 +5903,8 @@ export function applyPostWeatherLapseAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostWeatherLapse(pokemon, passive, simulated, weather, args), + (attr, passive) => attr.canApplyPostWeatherLapse(pokemon, passive, simulated, weather, args), args, - false, simulated, ); } @@ -5899,8 +5920,8 @@ export function applyPostTerrainChangeAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostTerrainChange(pokemon, passive, simulated, terrain, args), + (attr, passive) => attr.canApplyPostTerrainChange(pokemon, passive, simulated, terrain, args), args, - false, simulated, ); } @@ -5918,8 +5939,7 @@ export function applyCheckTrappedAbAttrs( attrType, pokemon, (attr, passive) => attr.applyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), - args, - false, + (attr, passive) => attr.canApplyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), args, simulated, messages, ); @@ -5935,8 +5955,8 @@ export function applyPostBattleAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostBattle(pokemon, passive, simulated, args), + (attr, passive) => attr.canApplyPostBattle(pokemon, passive, simulated, args), args, - false, simulated, ); } @@ -5954,8 +5974,8 @@ export function applyPostFaintAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args), + (attr, passive) => attr.canApplyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args), args, - false, simulated, ); } @@ -5970,6 +5990,7 @@ export function applyPostItemLostAbAttrs( attrType, pokemon, (attr, passive) => attr.applyPostItemLost(pokemon, simulated, args), + (attr, passive) => attr.canApplyPostItemLost(pokemon, simulated, args), args, ); } @@ -5979,19 +6000,36 @@ export function applyPostItemLostAbAttrs( * * Ignores passives as they don't change and shouldn't be reapplied when main abilities change */ -export function applyOnGainAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void { - applySingleAbAttrs(pokemon, passive, PostSummonAbAttr, (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), args, true, simulated); +export function applyOnGainAbAttrs( + pokemon: Pokemon, + passive: boolean = false, + simulated: boolean = false, + ...args: any[]): void { + applySingleAbAttrs( + pokemon, + passive, + PostSummonAbAttr, + (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), + (attr, passive) => attr.canApplyPostSummon(pokemon, passive, simulated, args), + args, + true, + simulated, + ); } /** * Clears primal weather/neutralizing gas during the turn if {@linkcode pokemon}'s ability corresponds to one */ export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void { - applySingleAbAttrs(pokemon, passive, PreLeaveFieldAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated); -} -function queueShowAbility(pokemon: Pokemon, passive: boolean): void { - globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.id, passive)); - globalScene.clearPhaseQueueSplice(); + applySingleAbAttrs( + pokemon, + passive, + PreLeaveFieldAbAttr, + (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), + (attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), + args, + true, + simulated); } /** @@ -6320,7 +6358,7 @@ export function initAbilities() { .conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), StatMultiplierAbAttr, Stat.SPD, 1.5), new Ability(Abilities.NORMALIZE, 4) .attr(MoveTypeChangeAbAttr, PokemonType.NORMAL, 1.2, (user, target, move) => { - return ![ Moves.HIDDEN_POWER, Moves.WEATHER_BALL, Moves.NATURAL_GIFT, Moves.JUDGMENT, Moves.TECHNO_BLAST ].includes(move.id); + return ![ Moves.MULTI_ATTACK, Moves.REVELATION_DANCE, Moves.TERRAIN_PULSE, Moves.HIDDEN_POWER, Moves.WEATHER_BALL, Moves.NATURAL_GIFT, Moves.JUDGMENT, Moves.TECHNO_BLAST ].includes(move.id); }), new Ability(Abilities.SNIPER, 4) .attr(MultCritAbAttr, 1.5), @@ -6878,7 +6916,7 @@ export function initAbilities() { new Ability(Abilities.GORILLA_TACTICS, 8) .attr(GorillaTacticsAbAttr), new Ability(Abilities.NEUTRALIZING_GAS, 8) - .attr(PostSummonAddArenaTagAbAttr, ArenaTagType.NEUTRALIZING_GAS, 0) + .attr(PostSummonAddArenaTagAbAttr, true, ArenaTagType.NEUTRALIZING_GAS, 0) .attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 4219b9a3ba2..6e8339dd54c 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -64,7 +64,7 @@ export abstract class ArenaTag { } } - onOverlap(_arena: Arena): void {} + onOverlap(_arena: Arena, _source: Pokemon | null): void {} lapse(_arena: Arena): boolean { return this.turnCount < 1 || !!--this.turnCount; @@ -706,7 +706,7 @@ export class ArenaTrapTag extends ArenaTag { this.maxLayers = maxLayers; } - onOverlap(arena: Arena): void { + onOverlap(arena: Arena, _source: Pokemon | null): void { if (this.layers < this.maxLayers) { this.layers++; @@ -1427,13 +1427,9 @@ export class SuppressAbilitiesTag extends ArenaTag { public override onAdd(_arena: Arena): void { const pokemon = this.getSourcePokemon(); if (pokemon) { - globalScene.queueMessage( - i18next.t("arenaTag:neutralizingGasOnAdd", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - }), - ); + this.playActivationMessage(pokemon); - for (const fieldPokemon of globalScene.getField()) { + for (const fieldPokemon of globalScene.getField(true)) { if (fieldPokemon && fieldPokemon.id !== pokemon.id) { [true, false].forEach(passive => applyOnLoseAbAttrs(fieldPokemon, passive)); } @@ -1441,8 +1437,9 @@ export class SuppressAbilitiesTag extends ArenaTag { } } - public override onOverlap(_arena: Arena): void { + public override onOverlap(_arena: Arena, source: Pokemon | null): void { this.sourceCount++; + this.playActivationMessage(source); } public onSourceLeave(arena: Arena): void { @@ -1466,7 +1463,7 @@ export class SuppressAbilitiesTag extends ArenaTag { globalScene.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove")); } - for (const pokemon of globalScene.getField()) { + for (const pokemon of globalScene.getField(true)) { // There is only one pokemon with this attr on the field on removal, so its abilities are already active if (pokemon && !pokemon.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false)) { [true, false].forEach(passive => applyOnGainAbAttrs(pokemon, passive)); @@ -1481,6 +1478,16 @@ export class SuppressAbilitiesTag extends ArenaTag { public isBeingRemoved() { return this.beingRemoved; } + + private playActivationMessage(pokemon: Pokemon | null) { + if (pokemon) { + globalScene.queueMessage( + i18next.t("arenaTag:neutralizingGasOnAdd", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + }), + ); + } + } } // TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter diff --git a/src/data/balance/biomes.ts b/src/data/balance/biomes.ts index 71a48162e57..a4e051d80c9 100644 --- a/src/data/balance/biomes.ts +++ b/src/data/balance/biomes.ts @@ -1680,7 +1680,7 @@ export const biomeTrainerPools: BiomeTrainerPools = { [BiomePoolTier.BOSS_ULTRA_RARE]: [] }, [Biome.METROPOLIS]: { - [BiomePoolTier.COMMON]: [ TrainerType.CLERK, TrainerType.CYCLIST, TrainerType.OFFICER, TrainerType.WAITER ], + [BiomePoolTier.COMMON]: [ TrainerType.CLERK, TrainerType.CYCLIST, TrainerType.OFFICER, TrainerType.WAITER, TrainerType.BEAUTY ], [BiomePoolTier.UNCOMMON]: [ TrainerType.BREEDER, TrainerType.DEPOT_AGENT, TrainerType.GUITARIST ], [BiomePoolTier.RARE]: [ TrainerType.ARTIST ], [BiomePoolTier.SUPER_RARE]: [], @@ -1713,7 +1713,7 @@ export const biomeTrainerPools: BiomeTrainerPools = { [BiomePoolTier.BOSS_ULTRA_RARE]: [] }, [Biome.SWAMP]: { - [BiomePoolTier.COMMON]: [], + [BiomePoolTier.COMMON]: [ TrainerType.PARASOL_LADY ], [BiomePoolTier.UNCOMMON]: [ TrainerType.ACE_TRAINER ], [BiomePoolTier.RARE]: [ TrainerType.BLACK_BELT ], [BiomePoolTier.SUPER_RARE]: [], @@ -1724,7 +1724,7 @@ export const biomeTrainerPools: BiomeTrainerPools = { [BiomePoolTier.BOSS_ULTRA_RARE]: [] }, [Biome.BEACH]: { - [BiomePoolTier.COMMON]: [ TrainerType.FISHERMAN, TrainerType.PARASOL_LADY, TrainerType.SAILOR ], + [BiomePoolTier.COMMON]: [ TrainerType.FISHERMAN, TrainerType.SAILOR ], [BiomePoolTier.UNCOMMON]: [ TrainerType.ACE_TRAINER, TrainerType.BREEDER ], [BiomePoolTier.RARE]: [ TrainerType.BLACK_BELT ], [BiomePoolTier.SUPER_RARE]: [], @@ -1735,7 +1735,7 @@ export const biomeTrainerPools: BiomeTrainerPools = { [BiomePoolTier.BOSS_ULTRA_RARE]: [] }, [Biome.LAKE]: { - [BiomePoolTier.COMMON]: [ TrainerType.BREEDER, TrainerType.FISHERMAN ], + [BiomePoolTier.COMMON]: [ TrainerType.BREEDER, TrainerType.FISHERMAN, TrainerType.PARASOL_LADY ], [BiomePoolTier.UNCOMMON]: [ TrainerType.ACE_TRAINER ], [BiomePoolTier.RARE]: [ TrainerType.BLACK_BELT ], [BiomePoolTier.SUPER_RARE]: [], @@ -1790,7 +1790,7 @@ export const biomeTrainerPools: BiomeTrainerPools = { [BiomePoolTier.BOSS_ULTRA_RARE]: [] }, [Biome.DESERT]: { - [BiomePoolTier.COMMON]: [ TrainerType.SCIENTIST ], + [BiomePoolTier.COMMON]: [ TrainerType.SCIENTIST, TrainerType.BACKPACKER ], [BiomePoolTier.UNCOMMON]: [], [BiomePoolTier.RARE]: [], [BiomePoolTier.SUPER_RARE]: [], @@ -1812,8 +1812,8 @@ export const biomeTrainerPools: BiomeTrainerPools = { [BiomePoolTier.BOSS_ULTRA_RARE]: [] }, [Biome.MEADOW]: { - [BiomePoolTier.COMMON]: [ TrainerType.PARASOL_LADY ], - [BiomePoolTier.UNCOMMON]: [ TrainerType.ACE_TRAINER, TrainerType.BREEDER ], + [BiomePoolTier.COMMON]: [ TrainerType.PARASOL_LADY, TrainerType.BEAUTY ], + [BiomePoolTier.UNCOMMON]: [ TrainerType.ACE_TRAINER, TrainerType.BREEDER, TrainerType.BAKER ], [BiomePoolTier.RARE]: [], [BiomePoolTier.SUPER_RARE]: [], [BiomePoolTier.ULTRA_RARE]: [], @@ -1879,7 +1879,7 @@ export const biomeTrainerPools: BiomeTrainerPools = { }, [Biome.RUINS]: { [BiomePoolTier.COMMON]: [ TrainerType.PSYCHIC, TrainerType.SCIENTIST ], - [BiomePoolTier.UNCOMMON]: [ TrainerType.ACE_TRAINER, TrainerType.BLACK_BELT ], + [BiomePoolTier.UNCOMMON]: [ TrainerType.ACE_TRAINER, TrainerType.BLACK_BELT, TrainerType.HEX_MANIAC ], [BiomePoolTier.RARE]: [], [BiomePoolTier.SUPER_RARE]: [], [BiomePoolTier.ULTRA_RARE]: [], @@ -7165,14 +7165,18 @@ export function initBiomes() { [ Biome.MOUNTAIN, BiomePoolTier.COMMON ], [ Biome.CAVE, BiomePoolTier.COMMON ], [ Biome.BADLANDS, BiomePoolTier.COMMON ], - [ Biome.JUNGLE, BiomePoolTier.COMMON ] + [ Biome.JUNGLE, BiomePoolTier.COMMON ], + [ Biome.DESERT, BiomePoolTier.COMMON ] ] ], [ TrainerType.BAKER, [ - [ Biome.SLUM, BiomePoolTier.UNCOMMON ] + [ Biome.SLUM, BiomePoolTier.UNCOMMON ], + [ Biome.MEADOW, BiomePoolTier.UNCOMMON ] ] ], [ TrainerType.BEAUTY, [ + [ Biome.METROPOLIS, BiomePoolTier.COMMON ], + [ Biome.MEADOW, BiomePoolTier.COMMON ], [ Biome.FAIRY_CAVE, BiomePoolTier.COMMON ] ]], [ TrainerType.BIKER, [ @@ -7247,7 +7251,8 @@ export function initBiomes() { ] ], [ TrainerType.PARASOL_LADY, [ - [ Biome.BEACH, BiomePoolTier.COMMON ], + [ Biome.SWAMP, BiomePoolTier.COMMON ], + [ Biome.LAKE, BiomePoolTier.COMMON ], [ Biome.MEADOW, BiomePoolTier.COMMON ] ] ], @@ -7313,6 +7318,7 @@ export function initBiomes() { ] ], [ TrainerType.HEX_MANIAC, [ + [ Biome.RUINS, BiomePoolTier.UNCOMMON ], [ Biome.GRAVEYARD, BiomePoolTier.UNCOMMON ] ] ], diff --git a/src/data/balance/signature-species.ts b/src/data/balance/signature-species.ts new file mode 100644 index 00000000000..a1b73af40cd --- /dev/null +++ b/src/data/balance/signature-species.ts @@ -0,0 +1,162 @@ +import { Species } from "#enums/species"; + +export type SignatureSpecies = { + [key in string]: (Species | Species[])[]; +}; + +/* + * The signature species for each Gym Leader, Elite Four member, and Champion. + * The key is the trainer type, and the value is an array of Species or Species arrays. + * This is in a separate const so it can be accessed from other places and not just the trainerConfigs + */ +export const signatureSpecies: SignatureSpecies = { + // Gym Leaders- Kanto + BROCK: [Species.GEODUDE, Species.ONIX], + MISTY: [Species.STARYU, Species.PSYDUCK], + LT_SURGE: [Species.VOLTORB, Species.PIKACHU, Species.ELECTABUZZ], + ERIKA: [Species.ODDISH, Species.BELLSPROUT, Species.TANGELA, Species.HOPPIP], + JANINE: [Species.VENONAT, Species.SPINARAK, Species.ZUBAT], + SABRINA: [Species.ABRA, Species.MR_MIME, Species.ESPEON], + BLAINE: [Species.GROWLITHE, Species.PONYTA, Species.MAGMAR], + GIOVANNI: [Species.SANDILE, Species.MURKROW, Species.NIDORAN_M, Species.NIDORAN_F], + // Gym Leaders- Johto + FALKNER: [Species.PIDGEY, Species.HOOTHOOT, Species.DODUO], + BUGSY: [Species.SCYTHER, Species.HERACROSS, Species.SHUCKLE, Species.PINSIR], + WHITNEY: [Species.JIGGLYPUFF, Species.MILTANK, Species.AIPOM, Species.GIRAFARIG], + MORTY: [Species.GASTLY, Species.MISDREAVUS, Species.SABLEYE], + CHUCK: [Species.POLIWRATH, Species.MANKEY], + JASMINE: [Species.MAGNEMITE, Species.STEELIX], + PRYCE: [Species.SEEL, Species.SWINUB], + CLAIR: [Species.DRATINI, Species.HORSEA, Species.GYARADOS], + // Gym Leaders- Hoenn + ROXANNE: [Species.GEODUDE, Species.NOSEPASS], + BRAWLY: [Species.MACHOP, Species.MAKUHITA], + WATTSON: [Species.MAGNEMITE, Species.VOLTORB, Species.ELECTRIKE], + FLANNERY: [Species.SLUGMA, Species.TORKOAL, Species.NUMEL], + NORMAN: [Species.SLAKOTH, Species.SPINDA, Species.ZIGZAGOON, Species.KECLEON], + WINONA: [Species.SWABLU, Species.WINGULL, Species.TROPIUS, Species.SKARMORY], + TATE: [Species.SOLROCK, Species.NATU, Species.CHIMECHO, Species.GALLADE], + LIZA: [Species.LUNATONE, Species.SPOINK, Species.BALTOY, Species.GARDEVOIR], + JUAN: [Species.HORSEA, Species.BARBOACH, Species.SPHEAL, Species.RELICANTH], + // Gym Leaders- Sinnoh + ROARK: [Species.CRANIDOS, Species.LARVITAR, Species.GEODUDE], + GARDENIA: [Species.ROSELIA, Species.TANGELA, Species.TURTWIG], + MAYLENE: [Species.LUCARIO, Species.MEDITITE, Species.CHIMCHAR], + CRASHER_WAKE: [Species.BUIZEL, Species.WOOPER, Species.PIPLUP, Species.MAGIKARP], + FANTINA: [Species.MISDREAVUS, Species.DRIFLOON, Species.SPIRITOMB], + BYRON: [Species.SHIELDON, Species.BRONZOR, Species.AGGRON], + CANDICE: [Species.SNEASEL, Species.SNOVER, Species.SNORUNT], + VOLKNER: [Species.SHINX, Species.CHINCHOU, Species.ROTOM], + // Gym Leaders- Unova + CILAN: [Species.PANSAGE, Species.FOONGUS, Species.PETILIL], + CHILI: [Species.PANSEAR, Species.DARUMAKA, Species.NUMEL], + CRESS: [Species.PANPOUR, Species.TYMPOLE, Species.SLOWPOKE], + CHEREN: [Species.LILLIPUP, Species.MINCCINO, Species.PIDOVE], + LENORA: [Species.PATRAT, Species.DEERLING, Species.AUDINO], + ROXIE: [Species.VENIPEDE, Species.TRUBBISH, Species.SKORUPI], + BURGH: [Species.SEWADDLE, Species.SHELMET, Species.KARRABLAST], + ELESA: [Species.EMOLGA, Species.BLITZLE, Species.JOLTIK], + CLAY: [Species.DRILBUR, Species.SANDILE, Species.GOLETT], + SKYLA: [Species.DUCKLETT, Species.WOOBAT, Species.RUFFLET], + BRYCEN: [Species.CRYOGONAL, Species.VANILLITE, Species.CUBCHOO], + DRAYDEN: [Species.DRUDDIGON, Species.AXEW, Species.DEINO], + MARLON: [Species.WAILMER, Species.FRILLISH, Species.TIRTOUGA], + // Gym Leaders- Kalos + VIOLA: [Species.SURSKIT, Species.SCATTERBUG], + GRANT: [Species.AMAURA, Species.TYRUNT], + KORRINA: [Species.HAWLUCHA, Species.LUCARIO, Species.MIENFOO], + RAMOS: [Species.SKIDDO, Species.HOPPIP, Species.BELLSPROUT], + CLEMONT: [Species.HELIOPTILE, Species.MAGNEMITE, Species.EMOLGA], + VALERIE: [Species.SYLVEON, Species.MAWILE, Species.MR_MIME], + OLYMPIA: [Species.ESPURR, Species.SIGILYPH, Species.SLOWKING], + WULFRIC: [Species.BERGMITE, Species.SNOVER, Species.CRYOGONAL], + // Gym Leaders- Galar + MILO: [Species.GOSSIFLEUR, Species.APPLIN, Species.BOUNSWEET], + NESSA: [Species.CHEWTLE, Species.ARROKUDA, Species.WIMPOD], + KABU: [Species.SIZZLIPEDE, Species.VULPIX, Species.TORKOAL], + BEA: [Species.GALAR_FARFETCHD, Species.MACHOP, Species.CLOBBOPUS], + ALLISTER: [Species.GALAR_YAMASK, Species.GALAR_CORSOLA, Species.GASTLY], + OPAL: [Species.MILCERY, Species.TOGETIC, Species.GALAR_WEEZING], + BEDE: [Species.HATENNA, Species.GALAR_PONYTA, Species.GARDEVOIR], + GORDIE: [Species.ROLYCOLY, Species.STONJOURNER, Species.BINACLE], + MELONY: [Species.SNOM, Species.GALAR_DARUMAKA, Species.GALAR_MR_MIME], + PIERS: [Species.GALAR_ZIGZAGOON, Species.SCRAGGY, Species.INKAY], + MARNIE: [Species.IMPIDIMP, Species.PURRLOIN, Species.MORPEKO], + RAIHAN: [Species.DURALUDON, Species.TURTONATOR, Species.GOOMY], + // Gym Leaders- Paldea; First slot is Tera + KATY: [Species.TEDDIURSA, Species.NYMBLE, Species.TAROUNTULA], // Tera Bug Teddiursa + BRASSIUS: [Species.SUDOWOODO, Species.BRAMBLIN, Species.SMOLIV], // Tera Grass Sudowoodo + IONO: [Species.MISDREAVUS, Species.TADBULB, Species.WATTREL], // Tera Ghost Misdreavus + KOFU: [Species.CRABRAWLER, Species.VELUZA, Species.WIGLETT, Species.WINGULL], // Tera Water Crabrawler + LARRY: [Species.STARLY, Species.DUNSPARCE, Species.LECHONK, Species.KOMALA], // Tera Normal Starly + RYME: [Species.TOXEL, Species.GREAVARD, Species.SHUPPET, Species.MIMIKYU], // Tera Ghost Toxel + TULIP: [Species.FLABEBE, Species.FLITTLE, Species.RALTS, Species.GIRAFARIG], // Tera Psychic Flabebe + GRUSHA: [Species.SWABLU, Species.CETODDLE, Species.CUBCHOO, Species.ALOLA_VULPIX], // Tera Ice Swablu + + // Elite Four- Kanto + LORELEI: [ + Species.JYNX, + [Species.SLOWBRO, Species.GALAR_SLOWBRO], + Species.LAPRAS, + [Species.CLOYSTER, Species.ALOLA_SANDSLASH], + ], + BRUNO: [Species.MACHAMP, Species.HITMONCHAN, Species.HITMONLEE, [Species.GOLEM, Species.ALOLA_GOLEM]], + AGATHA: [Species.GENGAR, [Species.ARBOK, Species.WEEZING], Species.CROBAT, Species.ALOLA_MAROWAK], + LANCE: [Species.DRAGONITE, Species.GYARADOS, Species.AERODACTYL, Species.ALOLA_EXEGGUTOR], + // Elite Four- Johto (Bruno included) + WILL: [Species.XATU, Species.JYNX, [Species.SLOWBRO, Species.SLOWKING], Species.EXEGGUTOR], + KOGA: [[Species.MUK, Species.WEEZING], [Species.VENOMOTH, Species.ARIADOS], Species.CROBAT, Species.TENTACRUEL], + KAREN: [Species.UMBREON, Species.HONCHKROW, Species.HOUNDOOM, Species.WEAVILE], + // Elite Four- Hoenn + SIDNEY: [ + [Species.SHIFTRY, Species.CACTURNE], + [Species.SHARPEDO, Species.CRAWDAUNT], + Species.ABSOL, + Species.MIGHTYENA, + ], + PHOEBE: [Species.SABLEYE, Species.DUSKNOIR, Species.BANETTE, [Species.DRIFBLIM, Species.MISMAGIUS]], + GLACIA: [Species.GLALIE, Species.WALREIN, Species.FROSLASS, Species.ABOMASNOW], + DRAKE: [Species.ALTARIA, Species.SALAMENCE, Species.FLYGON, Species.KINGDRA], + // Elite Four- Sinnoh + AARON: [[Species.SCIZOR, Species.KLEAVOR], Species.HERACROSS, [Species.VESPIQUEN, Species.YANMEGA], Species.DRAPION], + BERTHA: [Species.WHISCASH, Species.HIPPOWDON, Species.GLISCOR, Species.RHYPERIOR], + FLINT: [ + [Species.RAPIDASH, Species.FLAREON], + Species.MAGMORTAR, + [Species.STEELIX, Species.LOPUNNY], + Species.INFERNAPE, + ], // Tera Fire Steelix or Lopunny + LUCIAN: [Species.MR_MIME, Species.GALLADE, Species.BRONZONG, [Species.ALAKAZAM, Species.ESPEON]], + // Elite Four- Unova + SHAUNTAL: [Species.COFAGRIGUS, Species.CHANDELURE, Species.GOLURK, Species.JELLICENT], + MARSHAL: [Species.CONKELDURR, Species.MIENSHAO, Species.THROH, Species.SAWK], + GRIMSLEY: [Species.LIEPARD, Species.KINGAMBIT, Species.SCRAFTY, Species.KROOKODILE], + CAITLIN: [Species.MUSHARNA, Species.GOTHITELLE, Species.SIGILYPH, Species.REUNICLUS], + // Elite Four- Kalos + MALVA: [Species.PYROAR, Species.TORKOAL, Species.CHANDELURE, Species.TALONFLAME], + SIEBOLD: [Species.CLAWITZER, Species.GYARADOS, Species.BARBARACLE, Species.STARMIE], + WIKSTROM: [Species.KLEFKI, Species.PROBOPASS, Species.SCIZOR, Species.AEGISLASH], + DRASNA: [Species.DRAGALGE, Species.DRUDDIGON, Species.ALTARIA, Species.NOIVERN], + // Elite Four- Alola + HALA: [Species.HARIYAMA, Species.BEWEAR, Species.CRABOMINABLE, [Species.POLIWRATH, Species.ANNIHILAPE]], + MOLAYNE: [Species.KLEFKI, Species.MAGNEZONE, Species.METAGROSS, Species.ALOLA_DUGTRIO], + OLIVIA: [Species.RELICANTH, Species.CARBINK, Species.ALOLA_GOLEM, Species.LYCANROC], + ACEROLA: [[Species.BANETTE, Species.DRIFBLIM], Species.MIMIKYU, Species.DHELMISE, Species.PALOSSAND], + KAHILI: [[Species.BRAVIARY, Species.MANDIBUZZ], Species.HAWLUCHA, Species.ORICORIO, Species.TOUCANNON], + // Elite Four- Galar + MARNIE_ELITE: [Species.MORPEKO, Species.LIEPARD, [Species.TOXICROAK, Species.SCRAFTY], Species.GRIMMSNARL], + NESSA_ELITE: [Species.GOLISOPOD, [Species.QUAGSIRE, Species.PELIPPER], Species.TOXAPEX, Species.DREDNAW], + BEA_ELITE: [Species.HAWLUCHA, [Species.GRAPPLOCT, Species.SIRFETCHD], Species.FALINKS, Species.MACHAMP], + ALLISTER_ELITE: [Species.DUSKNOIR, [Species.POLTEAGEIST, Species.RUNERIGUS], Species.CURSOLA, Species.GENGAR], + RAIHAN_ELITE: [Species.GOODRA, [Species.TORKOAL, Species.TURTONATOR], Species.FLYGON, Species.ARCHALUDON], + // Elite Four- Paldea + RIKA: [Species.CLODSIRE, [Species.DUGTRIO, Species.DONPHAN], Species.CAMERUPT, Species.WHISCASH], // Tera Ground Clodsire + POPPY: [Species.TINKATON, Species.BRONZONG, Species.CORVIKNIGHT, Species.COPPERAJAH], // Tera Steel Tinkaton + LARRY_ELITE: [Species.FLAMIGO, Species.STARAPTOR, [Species.ALTARIA, Species.TROPIUS], Species.ORICORIO], // Tera Flying Flamigo; random Oricorio + HASSEL: [Species.BAXCALIBUR, [Species.FLAPPLE, Species.APPLETUN], Species.DRAGALGE, Species.NOIVERN], // Tera Dragon Baxcalibur + // Elite Four- BBL + CRISPIN: [Species.BLAZIKEN, Species.MAGMORTAR, [Species.CAMERUPT, Species.TALONFLAME], Species.ROTOM], // Tera Fire Blaziken; Heat Rotom + AMARYS: [Species.METAGROSS, Species.SCIZOR, Species.EMPOLEON, Species.SKARMORY], // Tera Steel Metagross + LACEY: [Species.EXCADRILL, Species.PRIMARINA, [Species.WHIMSICOTT, Species.ALCREMIE], Species.GRANBULL], // Tera Fairy Excadrill + DRAYTON: [Species.ARCHALUDON, Species.DRAGONITE, Species.HAXORUS, Species.SCEPTILE], // Tera Dragon Archaludon +}; diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 43a7072fd12..7ff74d893b6 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1922,10 +1922,6 @@ export class SlowStartTag extends AbilityBattlerTag { i18next.t("battlerTags:slowStartOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }), - null, - false, - null, - true, ); } diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index f2157ab65b7..3d80b6b3f4c 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -5542,6 +5542,31 @@ export class LeechSeedAttr extends AddBattlerTagAttr { } } +/** + * Adds the appropriate battler tag for Smack Down and Thousand arrows + * @extends AddBattlerTagAttr + */ +export class FallDownAttr extends AddBattlerTagAttr { + constructor() { + super(BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true); + } + + /** + * Adds Grounded Tag to the target and checks if fallDown message should be displayed + * @param user the {@linkcode Pokemon} using the move + * @param target the {@linkcode Pokemon} targeted by the move + * @param move the {@linkcode Move} invoking this effect + * @param args n/a + * @returns `true` if the effect successfully applies; `false` otherwise + */ + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (!target.isGrounded()) { + globalScene.queueMessage(i18next.t("moveTriggers:fallDown", { targetPokemonName: getPokemonNameWithAffix(target) })); + } + return super.apply(user, target, move, args); + } +} + /** * Adds the appropriate battler tag for Gulp Missile when Surf or Dive is used. * @extends MoveEffectAttr @@ -9646,7 +9671,7 @@ export function initMoves() { .target(MoveTarget.BOTH_SIDES) .unimplemented(), new AttackMove(Moves.SMACK_DOWN, PokemonType.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, 100, 0, 5) - .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true) + .attr(FallDownAttr) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ]) .attr(HitsTagAttr, BattlerTagType.FLYING) @@ -10097,7 +10122,7 @@ export function initMoves() { .triageMove(), new AttackMove(Moves.THOUSAND_ARROWS, PokemonType.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6) .attr(NeutralDamageAgainstFlyingTypeMultiplierAttr) - .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true) + .attr(FallDownAttr) .attr(HitsTagAttr, BattlerTagType.FLYING) .attr(HitsTagAttr, BattlerTagType.FLOATING) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 1bf97209613..d172173e9df 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -2987,11 +2987,11 @@ export function initSpecies() { new PokemonSpecies(Species.VAROOM, 9, false, false, false, "Single-Cyl Pokémon", PokemonType.STEEL, PokemonType.POISON, 1, 35, Abilities.OVERCOAT, Abilities.NONE, Abilities.SLOW_START, 300, 45, 70, 63, 30, 45, 47, 190, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.REVAVROOM, 9, false, false, false, "Multi-Cyl Pokémon", PokemonType.STEEL, PokemonType.POISON, 1.8, 120, Abilities.OVERCOAT, Abilities.NONE, Abilities.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, GrowthRate.MEDIUM_FAST, 50, false, false, new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.POISON, 1.8, 120, Abilities.OVERCOAT, Abilities.NONE, Abilities.FILTER, 500, 80, 119, 90, 54, 67, 90, 75, 50, 175, false, null, true), - new PokemonForm("Segin Starmobile", "segin-starmobile", PokemonType.STEEL, PokemonType.DARK, 1.8, 240, Abilities.INTIMIDATE, Abilities.NONE, Abilities.INTIMIDATE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175), - new PokemonForm("Schedar Starmobile", "schedar-starmobile", PokemonType.STEEL, PokemonType.FIRE, 1.8, 240, Abilities.SPEED_BOOST, Abilities.NONE, Abilities.SPEED_BOOST, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175), - new PokemonForm("Navi Starmobile", "navi-starmobile", PokemonType.STEEL, PokemonType.POISON, 1.8, 240, Abilities.TOXIC_DEBRIS, Abilities.NONE, Abilities.TOXIC_DEBRIS, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175), - new PokemonForm("Ruchbah Starmobile", "ruchbah-starmobile", PokemonType.STEEL, PokemonType.FAIRY, 1.8, 240, Abilities.MISTY_SURGE, Abilities.NONE, Abilities.MISTY_SURGE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175), - new PokemonForm("Caph Starmobile", "caph-starmobile", PokemonType.STEEL, PokemonType.FIGHTING, 1.8, 240, Abilities.STAMINA, Abilities.NONE, Abilities.STAMINA, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175), + new PokemonForm("Segin Starmobile", "segin-starmobile", PokemonType.STEEL, PokemonType.DARK, 1.8, 240, Abilities.INTIMIDATE, Abilities.NONE, Abilities.INTIMIDATE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), + new PokemonForm("Schedar Starmobile", "schedar-starmobile", PokemonType.STEEL, PokemonType.FIRE, 1.8, 240, Abilities.SPEED_BOOST, Abilities.NONE, Abilities.SPEED_BOOST, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), + new PokemonForm("Navi Starmobile", "navi-starmobile", PokemonType.STEEL, PokemonType.POISON, 1.8, 240, Abilities.TOXIC_DEBRIS, Abilities.NONE, Abilities.TOXIC_DEBRIS, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), + new PokemonForm("Ruchbah Starmobile", "ruchbah-starmobile", PokemonType.STEEL, PokemonType.FAIRY, 1.8, 240, Abilities.MISTY_SURGE, Abilities.NONE, Abilities.MISTY_SURGE, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), + new PokemonForm("Caph Starmobile", "caph-starmobile", PokemonType.STEEL, PokemonType.FIGHTING, 1.8, 240, Abilities.STAMINA, Abilities.NONE, Abilities.STAMINA, 600, 110, 129, 100, 77, 79, 105, 75, 50, 175, false, null, false, true), ), new PokemonSpecies(Species.CYCLIZAR, 9, false, false, false, "Mount Pokémon", PokemonType.DRAGON, PokemonType.NORMAL, 1.6, 63, Abilities.SHED_SKIN, Abilities.NONE, Abilities.REGENERATOR, 501, 70, 95, 65, 85, 65, 121, 190, 50, 175, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(Species.ORTHWORM, 9, false, false, false, "Earthworm Pokémon", PokemonType.STEEL, null, 2.5, 310, Abilities.EARTH_EATER, Abilities.NONE, Abilities.SAND_VEIL, 480, 70, 85, 145, 60, 55, 65, 25, 50, 240, GrowthRate.SLOW, 50, false), diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index 693338c145e..ffe5cdfe04c 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -22,6 +22,8 @@ import { PartyMemberStrength } from "#enums/party-member-strength"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; import { Gender } from "#app/data/gender"; +import { signatureSpecies } from "./balance/signature-species"; +import { Abilities } from "#enums/abilities"; /** Minimum BST for Pokemon generated onto the Elite Four's teams */ const ELITE_FOUR_MINIMUM_BST = 460; @@ -1744,148 +1746,6 @@ function getSpeciesFilterRandomPartyMemberFunc( }; } -type SignatureSpecies = { - [key in string]: (Species | Species[])[]; -}; - -/* - * The signature species for each Gym Leader, Elite Four member, and Champion. - * The key is the trainer type, and the value is an array of Species or Species arrays. - * This is in a separate const so it can be accessed from other places and not just the trainerConfigs - */ -export const signatureSpecies: SignatureSpecies = { - BROCK: [Species.GEODUDE, Species.ONIX], - MISTY: [Species.STARYU, Species.PSYDUCK], - LT_SURGE: [Species.VOLTORB, Species.PIKACHU, Species.ELECTABUZZ], - ERIKA: [Species.ODDISH, Species.BELLSPROUT, Species.TANGELA, Species.HOPPIP], - JANINE: [Species.VENONAT, Species.SPINARAK, Species.ZUBAT], - SABRINA: [Species.ABRA, Species.MR_MIME, Species.ESPEON], - BLAINE: [Species.GROWLITHE, Species.PONYTA, Species.MAGMAR], - GIOVANNI: [Species.SANDILE, Species.MURKROW, Species.NIDORAN_M, Species.NIDORAN_F], - FALKNER: [Species.PIDGEY, Species.HOOTHOOT, Species.DODUO], - BUGSY: [Species.SCYTHER, Species.HERACROSS, Species.SHUCKLE, Species.PINSIR], - WHITNEY: [Species.JIGGLYPUFF, Species.MILTANK, Species.AIPOM, Species.GIRAFARIG], - MORTY: [Species.GASTLY, Species.MISDREAVUS, Species.SABLEYE], - CHUCK: [Species.POLIWRATH, Species.MANKEY], - JASMINE: [Species.MAGNEMITE, Species.STEELIX], - PRYCE: [Species.SEEL, Species.SWINUB], - CLAIR: [Species.DRATINI, Species.HORSEA, Species.GYARADOS], - ROXANNE: [Species.GEODUDE, Species.NOSEPASS], - BRAWLY: [Species.MACHOP, Species.MAKUHITA], - WATTSON: [Species.MAGNEMITE, Species.VOLTORB, Species.ELECTRIKE], - FLANNERY: [Species.SLUGMA, Species.TORKOAL, Species.NUMEL], - NORMAN: [Species.SLAKOTH, Species.SPINDA, Species.ZIGZAGOON, Species.KECLEON], - WINONA: [Species.SWABLU, Species.WINGULL, Species.TROPIUS, Species.SKARMORY], - TATE: [Species.SOLROCK, Species.NATU, Species.CHIMECHO, Species.GALLADE], - LIZA: [Species.LUNATONE, Species.SPOINK, Species.BALTOY, Species.GARDEVOIR], - JUAN: [Species.HORSEA, Species.BARBOACH, Species.SPHEAL, Species.RELICANTH], - ROARK: [Species.CRANIDOS, Species.LARVITAR, Species.GEODUDE], - GARDENIA: [Species.ROSELIA, Species.TANGELA, Species.TURTWIG], - MAYLENE: [Species.LUCARIO, Species.MEDITITE, Species.CHIMCHAR], - CRASHER_WAKE: [Species.BUIZEL, Species.WOOPER, Species.PIPLUP, Species.MAGIKARP], - FANTINA: [Species.MISDREAVUS, Species.DRIFLOON, Species.SPIRITOMB], - BYRON: [Species.SHIELDON, Species.BRONZOR, Species.AGGRON], - CANDICE: [Species.SNEASEL, Species.SNOVER, Species.SNORUNT], - VOLKNER: [Species.SHINX, Species.CHINCHOU, Species.ROTOM], - CILAN: [Species.PANSAGE, Species.FOONGUS, Species.PETILIL], - CHILI: [Species.PANSEAR, Species.DARUMAKA, Species.NUMEL], - CRESS: [Species.PANPOUR, Species.TYMPOLE, Species.SLOWPOKE], - CHEREN: [Species.LILLIPUP, Species.MINCCINO, Species.PIDOVE], - LENORA: [Species.PATRAT, Species.DEERLING, Species.AUDINO], - ROXIE: [Species.VENIPEDE, Species.TRUBBISH, Species.SKORUPI], - BURGH: [Species.SEWADDLE, Species.SHELMET, Species.KARRABLAST], - ELESA: [Species.EMOLGA, Species.BLITZLE, Species.JOLTIK], - CLAY: [Species.DRILBUR, Species.SANDILE, Species.GOLETT], - SKYLA: [Species.DUCKLETT, Species.WOOBAT, Species.RUFFLET], - BRYCEN: [Species.CRYOGONAL, Species.VANILLITE, Species.CUBCHOO], - DRAYDEN: [Species.DRUDDIGON, Species.AXEW, Species.DEINO], - MARLON: [Species.WAILMER, Species.FRILLISH, Species.TIRTOUGA], - VIOLA: [Species.SURSKIT, Species.SCATTERBUG], - GRANT: [Species.AMAURA, Species.TYRUNT], - KORRINA: [Species.HAWLUCHA, Species.LUCARIO, Species.MIENFOO], - RAMOS: [Species.SKIDDO, Species.HOPPIP, Species.BELLSPROUT], - CLEMONT: [Species.HELIOPTILE, Species.MAGNEMITE, Species.EMOLGA], - VALERIE: [Species.SYLVEON, Species.MAWILE, Species.MR_MIME], - OLYMPIA: [Species.ESPURR, Species.SIGILYPH, Species.SLOWKING], - WULFRIC: [Species.BERGMITE, Species.SNOVER, Species.CRYOGONAL], - MILO: [Species.GOSSIFLEUR, Species.APPLIN, Species.BOUNSWEET], - NESSA: [Species.CHEWTLE, Species.ARROKUDA, Species.WIMPOD], - KABU: [Species.SIZZLIPEDE, Species.VULPIX, Species.TORKOAL], - BEA: [Species.GALAR_FARFETCHD, Species.MACHOP, Species.CLOBBOPUS], - ALLISTER: [Species.GALAR_YAMASK, Species.GALAR_CORSOLA, Species.GASTLY], - OPAL: [Species.MILCERY, Species.TOGETIC, Species.GALAR_WEEZING], - BEDE: [Species.HATENNA, Species.GALAR_PONYTA, Species.GARDEVOIR], - GORDIE: [Species.ROLYCOLY, Species.STONJOURNER, Species.BINACLE], - MELONY: [Species.SNOM, Species.GALAR_DARUMAKA, Species.GALAR_MR_MIME], - PIERS: [Species.GALAR_ZIGZAGOON, Species.SCRAGGY, Species.INKAY], - MARNIE: [Species.IMPIDIMP, Species.PURRLOIN, Species.MORPEKO], - RAIHAN: [Species.DURALUDON, Species.TURTONATOR, Species.GOOMY], - KATY: [Species.TEDDIURSA, Species.NYMBLE, Species.TAROUNTULA], // Tera Bug Teddiursa - BRASSIUS: [Species.SUDOWOODO, Species.BRAMBLIN, Species.SMOLIV], // Tera Grass Sudowoodo - IONO: [Species.MISDREAVUS, Species.TADBULB, Species.WATTREL], // Tera Ghost Misdreavus - KOFU: [Species.CRABRAWLER, Species.VELUZA, Species.WIGLETT, Species.WINGULL], // Tera Water Crabrawler - LARRY: [Species.STARLY, Species.DUNSPARCE, Species.LECHONK, Species.KOMALA], // Tera Normal Starly - RYME: [Species.TOXEL, Species.GREAVARD, Species.SHUPPET, Species.MIMIKYU], // Tera Ghost Toxel - TULIP: [Species.FLABEBE, Species.FLITTLE, Species.RALTS, Species.GIRAFARIG], // Tera Psychic Flabebe - GRUSHA: [Species.SWABLU, Species.CETODDLE, Species.CUBCHOO, Species.ALOLA_VULPIX], // Tera Ice Swablu - LORELEI: [ - Species.JYNX, - [Species.SLOWBRO, Species.GALAR_SLOWBRO], - Species.LAPRAS, - [Species.CLOYSTER, Species.ALOLA_SANDSLASH], - ], - BRUNO: [Species.MACHAMP, Species.HITMONCHAN, Species.HITMONLEE, [Species.GOLEM, Species.ALOLA_GOLEM]], - AGATHA: [Species.GENGAR, [Species.ARBOK, Species.WEEZING], Species.CROBAT, Species.ALOLA_MAROWAK], - LANCE: [Species.DRAGONITE, Species.GYARADOS, Species.AERODACTYL, Species.ALOLA_EXEGGUTOR], - WILL: [Species.XATU, Species.JYNX, [Species.SLOWBRO, Species.SLOWKING], Species.EXEGGUTOR], - KOGA: [[Species.MUK, Species.WEEZING], [Species.VENOMOTH, Species.ARIADOS], Species.CROBAT, Species.TENTACRUEL], - KAREN: [Species.UMBREON, Species.HONCHKROW, Species.HOUNDOOM, Species.WEAVILE], - SIDNEY: [ - [Species.SHIFTRY, Species.CACTURNE], - [Species.SHARPEDO, Species.CRAWDAUNT], - Species.ABSOL, - Species.MIGHTYENA, - ], - PHOEBE: [Species.SABLEYE, Species.DUSKNOIR, Species.BANETTE, [Species.DRIFBLIM, Species.MISMAGIUS]], - GLACIA: [Species.GLALIE, Species.WALREIN, Species.FROSLASS, Species.ABOMASNOW], - DRAKE: [Species.ALTARIA, Species.SALAMENCE, Species.FLYGON, Species.KINGDRA], - AARON: [[Species.SCIZOR, Species.KLEAVOR], Species.HERACROSS, [Species.VESPIQUEN, Species.YANMEGA], Species.DRAPION], - BERTHA: [Species.WHISCASH, Species.HIPPOWDON, Species.GLISCOR, Species.RHYPERIOR], - FLINT: [ - [Species.RAPIDASH, Species.FLAREON], - Species.MAGMORTAR, - [Species.STEELIX, Species.LOPUNNY], - Species.INFERNAPE, - ], // Tera Fire Steelix or Lopunny - LUCIAN: [Species.MR_MIME, Species.GALLADE, Species.BRONZONG, [Species.ALAKAZAM, Species.ESPEON]], - SHAUNTAL: [Species.COFAGRIGUS, Species.CHANDELURE, Species.GOLURK, Species.JELLICENT], - MARSHAL: [Species.CONKELDURR, Species.MIENSHAO, Species.THROH, Species.SAWK], - GRIMSLEY: [Species.LIEPARD, Species.KINGAMBIT, Species.SCRAFTY, Species.KROOKODILE], - CAITLIN: [Species.MUSHARNA, Species.GOTHITELLE, Species.SIGILYPH, Species.REUNICLUS], - MALVA: [Species.PYROAR, Species.TORKOAL, Species.CHANDELURE, Species.TALONFLAME], - SIEBOLD: [Species.CLAWITZER, Species.GYARADOS, Species.BARBARACLE, Species.STARMIE], - WIKSTROM: [Species.KLEFKI, Species.PROBOPASS, Species.SCIZOR, Species.AEGISLASH], - DRASNA: [Species.DRAGALGE, Species.DRUDDIGON, Species.ALTARIA, Species.NOIVERN], - HALA: [Species.HARIYAMA, Species.BEWEAR, Species.CRABOMINABLE, [Species.POLIWRATH, Species.ANNIHILAPE]], - MOLAYNE: [Species.KLEFKI, Species.MAGNEZONE, Species.METAGROSS, Species.ALOLA_DUGTRIO], - OLIVIA: [Species.RELICANTH, Species.CARBINK, Species.ALOLA_GOLEM, Species.LYCANROC], - ACEROLA: [[Species.BANETTE, Species.DRIFBLIM], Species.MIMIKYU, Species.DHELMISE, Species.PALOSSAND], - KAHILI: [[Species.BRAVIARY, Species.MANDIBUZZ], Species.HAWLUCHA, Species.ORICORIO, Species.TOUCANNON], - MARNIE_ELITE: [Species.MORPEKO, Species.LIEPARD, [Species.TOXICROAK, Species.SCRAFTY], Species.GRIMMSNARL], - NESSA_ELITE: [Species.GOLISOPOD, [Species.QUAGSIRE, Species.PELIPPER], Species.TOXAPEX, Species.DREDNAW], - BEA_ELITE: [Species.HAWLUCHA, [Species.GRAPPLOCT, Species.SIRFETCHD], Species.FALINKS, Species.MACHAMP], - ALLISTER_ELITE: [Species.DUSKNOIR, [Species.POLTEAGEIST, Species.RUNERIGUS], Species.CURSOLA, Species.GENGAR], - RAIHAN_ELITE: [Species.GOODRA, [Species.TORKOAL, Species.TURTONATOR], Species.FLYGON, Species.ARCHALUDON], - RIKA: [Species.CLODSIRE, [Species.DUGTRIO, Species.DONPHAN], Species.CAMERUPT, Species.WHISCASH], // Tera Ground Clodsire - POPPY: [Species.TINKATON, Species.BRONZONG, Species.CORVIKNIGHT, Species.COPPERAJAH], // Tera Steel Tinkaton - LARRY_ELITE: [Species.FLAMIGO, Species.STARAPTOR, [Species.ALTARIA, Species.TROPIUS], Species.ORICORIO], // Tera Flying Flamigo; random Oricorio - HASSEL: [Species.BAXCALIBUR, [Species.FLAPPLE, Species.APPLETUN], Species.DRAGALGE, Species.NOIVERN], // Tera Dragon Baxcalibur - CRISPIN: [Species.BLAZIKEN, Species.MAGMORTAR, [Species.CAMERUPT, Species.TALONFLAME], Species.ROTOM], // Tera Fire Blaziken; Heat Rotom - AMARYS: [Species.METAGROSS, Species.SCIZOR, Species.EMPOLEON, Species.SKARMORY], // Tera Steel Metagross - LACEY: [Species.EXCADRILL, Species.PRIMARINA, [Species.WHIMSICOTT, Species.ALCREMIE], Species.GRANBULL], // Tera Fairy Excadrill - DRAYTON: [Species.ARCHALUDON, Species.DRAGONITE, Species.HAXORUS, Species.SCEPTILE], // Tera Dragon Archaludon -}; - export const trainerConfigs: TrainerConfigs = { [TrainerType.UNKNOWN]: new TrainerConfig(0).setHasGenders(), [TrainerType.ACE_TRAINER]: new TrainerConfig(++t) @@ -1952,12 +1812,92 @@ export const trainerConfigs: TrainerConfigs = { [TrainerType.BAKER]: new TrainerConfig(++t) .setEncounterBgm(TrainerType.CLERK) .setMoneyMultiplier(1.35) - .setSpeciesFilter(s => s.isOfType(PokemonType.GRASS) || s.isOfType(PokemonType.FIRE)), - [TrainerType.BEAUTY]: new TrainerConfig(++t).setMoneyMultiplier(1.55).setEncounterBgm(TrainerType.PARASOL_LADY), + .setSpeciesFilter( + s => + [s.ability1, s.ability2, s.abilityHidden].some( + a => + !!a && + [ + Abilities.WHITE_SMOKE, + Abilities.GLUTTONY, + Abilities.HONEY_GATHER, + Abilities.HARVEST, + Abilities.CHEEK_POUCH, + Abilities.SWEET_VEIL, + Abilities.RIPEN, + Abilities.PURIFYING_SALT, + Abilities.WELL_BAKED_BODY, + Abilities.SUPERSWEET_SYRUP, + Abilities.HOSPITALITY, + ].includes(a), + ) || + s + .getLevelMoves() + .some(plm => + [Moves.SOFT_BOILED, Moves.SPORE, Moves.MILK_DRINK, Moves.OVERHEAT, Moves.TEATIME].includes(plm[1]), + ), + ), // Mons with baking related abilities or who learn Overheat, Teatime, Milk Drink, Spore, or Soft-Boiled by level + [TrainerType.BEAUTY]: new TrainerConfig(++t) + .setMoneyMultiplier(1.55) + .setEncounterBgm(TrainerType.PARASOL_LADY) + .setPartyTemplates( + trainerPartyTemplates.TWO_AVG_SAME_ONE_AVG, + trainerPartyTemplates.TWO_AVG_SAME_ONE_STRONG, + trainerPartyTemplates.THREE_AVG_SAME, + trainerPartyTemplates.THREE_AVG, + trainerPartyTemplates.FOUR_WEAK, + trainerPartyTemplates.ONE_STRONG, + ) + .setSpeciesPools({ + [TrainerPoolTier.COMMON]: [ + Species.MEOWTH, + Species.GOLDEEN, + Species.MAREEP, + Species.MARILL, + Species.SKITTY, + Species.GLAMEOW, + Species.PURRLOIN, + ], + [TrainerPoolTier.UNCOMMON]: [ + Species.SMOOCHUM, + Species.ROSELIA, + Species.LUVDISC, + Species.BLITZLE, + Species.SEWADDLE, + Species.PETILIL, + Species.MINCCINO, + Species.GOTHITA, + Species.SPRITZEE, + Species.FLITTLE, + ], + [TrainerPoolTier.RARE]: [ + Species.FEEBAS, + Species.FURFROU, + Species.SALANDIT, + Species.BRUXISH, + Species.HATENNA, + Species.SNOM, + Species.ALOLA_VULPIX, + ], + [TrainerPoolTier.SUPER_RARE]: [Species.CLAMPERL, Species.AMAURA, Species.SYLVEON, Species.GOOMY, Species.POPPLIO], + }), [TrainerType.BIKER]: new TrainerConfig(++t) .setMoneyMultiplier(1.4) .setEncounterBgm(TrainerType.ROUGHNECK) - .setSpeciesFilter(s => s.isOfType(PokemonType.POISON)), + .setSpeciesPools({ + [TrainerPoolTier.COMMON]: [Species.EKANS, Species.KOFFING, Species.CROAGUNK, Species.VENIPEDE, Species.SCRAGGY], + [TrainerPoolTier.UNCOMMON]: [ + Species.GRIMER, + Species.VOLTORB, + Species.TEDDIURSA, + Species.MAGBY, + Species.SKORUPI, + Species.SANDILE, + Species.PAWNIARD, + Species.SHROODLE, + ], + [TrainerPoolTier.RARE]: [Species.VAROOM, Species.CYCLIZAR], + }), [TrainerType.BLACK_BELT]: new TrainerConfig(++t) .setHasGenders("Battle Girl", TrainerType.PSYCHIC) .setHasDouble("Crush Kin") @@ -2059,9 +1999,15 @@ export const trainerConfigs: TrainerConfigs = { .setEncounterBgm(TrainerType.CYCLIST) .setPartyTemplates(trainerPartyTemplates.TWO_WEAK, trainerPartyTemplates.ONE_AVG) .setSpeciesPools({ - [TrainerPoolTier.COMMON]: [Species.PICHU, Species.STARLY, Species.TAILLOW, Species.BOLTUND], - [TrainerPoolTier.UNCOMMON]: [Species.DODUO, Species.ELECTRIKE, Species.BLITZLE, Species.WATTREL], - [TrainerPoolTier.RARE]: [Species.YANMA, Species.NINJASK, Species.WHIRLIPEDE, Species.EMOLGA], + [TrainerPoolTier.COMMON]: [Species.DODUO, Species.PICHU, Species.TAILLOW, Species.STARLY, Species.PONYTA], + [TrainerPoolTier.UNCOMMON]: [ + Species.ELECTRIKE, + Species.SHINX, + Species.BLITZLE, + Species.DUCKLETT, + Species.WATTREL, + ], + [TrainerPoolTier.RARE]: [Species.YANMA, Species.NINJASK, Species.WHIRLIPEDE, Species.EMOLGA, Species.SKIDDO], [TrainerPoolTier.SUPER_RARE]: [Species.ACCELGOR, Species.DREEPY], }), [TrainerType.DANCER]: new TrainerConfig(++t) @@ -2077,7 +2023,7 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.COMMON]: [Species.RALTS, Species.SPOINK, Species.LOTAD, Species.BUDEW], [TrainerPoolTier.UNCOMMON]: [Species.SPINDA, Species.SWABLU, Species.MARACTUS], [TrainerPoolTier.RARE]: [Species.BELLOSSOM, Species.HITMONTOP, Species.MIME_JR, Species.ORICORIO], - [TrainerPoolTier.SUPER_RARE]: [Species.POPPLIO], + [TrainerPoolTier.SUPER_RARE]: [Species.QUAXLY, Species.JANGMO_O], }), [TrainerType.DEPOT_AGENT]: new TrainerConfig(++t).setMoneyMultiplier(1.45).setEncounterBgm(TrainerType.CLERK), [TrainerType.DOCTOR]: new TrainerConfig(++t) @@ -2215,7 +2161,7 @@ export const trainerConfigs: TrainerConfigs = { trainerPartyTemplates.THREE_AVG, trainerPartyTemplates.TWO_STRONG, ) - .setSpeciesFilter(s => s.isOfType(PokemonType.GHOST)), + .setSpeciesFilter(s => s.isOfType(PokemonType.GHOST) || s.isOfType(PokemonType.PSYCHIC)), [TrainerType.NURSERY_AIDE]: new TrainerConfig(++t).setMoneyMultiplier(1.3).setEncounterBgm("lass"), [TrainerType.OFFICER]: new TrainerConfig(++t) .setMoneyMultiplier(1.55) @@ -2245,7 +2191,28 @@ export const trainerConfigs: TrainerConfigs = { [TrainerType.PARASOL_LADY]: new TrainerConfig(++t) .setMoneyMultiplier(1.55) .setEncounterBgm(TrainerType.PARASOL_LADY) - .setSpeciesFilter(s => s.isOfType(PokemonType.WATER)), + .setPartyTemplates( + trainerPartyTemplates.TWO_AVG_SAME_ONE_AVG, + trainerPartyTemplates.TWO_AVG_SAME_ONE_STRONG, + trainerPartyTemplates.TWO_AVG, + trainerPartyTemplates.FOUR_WEAK, + trainerPartyTemplates.ONE_STRONG, + ) + .setSpeciesFilter( + s => + [s.ability1, s.ability2, s.abilityHidden].some( + a => + !!a && + [ + Abilities.DRIZZLE, + Abilities.SWIFT_SWIM, + Abilities.HYDRATION, + Abilities.RAIN_DISH, + Abilities.DRY_SKIN, + Abilities.WIND_POWER, + ].includes(a), + ) || s.getLevelMoves().some(plm => plm[1] === Moves.RAIN_DANCE), + ), // Mons with rain abilities or who learn Rain Dance by level [TrainerType.PILOT]: new TrainerConfig(++t) .setEncounterBgm(TrainerType.CLERK) .setSpeciesFilter(s => tmSpecies[Moves.FLY].indexOf(s.speciesId) > -1), diff --git a/src/field/arena.ts b/src/field/arena.ts index 2ed94e0e3e3..2c538de890f 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -303,6 +303,11 @@ export class Arena { return true; } + /** Returns weather or not the weather can be changed to {@linkcode weather} */ + canSetWeather(weather: WeatherType): boolean { + return !(this.weather?.weatherType === (weather || undefined)); + } + /** * Attempts to set a new weather to the battle * @param weather {@linkcode WeatherType} new {@linkcode WeatherType} to set @@ -314,7 +319,7 @@ export class Arena { return this.trySetWeatherOverride(Overrides.WEATHER_OVERRIDE); } - if (this.weather?.weatherType === (weather || undefined)) { + if (!this.canSetWeather(weather)) { return false; } @@ -388,8 +393,13 @@ export class Arena { }); } + /** Returns whether or not the terrain can be set to {@linkcode terrain} */ + canSetTerrain(terrain: TerrainType): boolean { + return !(this.terrain?.terrainType === (terrain || undefined)); + } + trySetTerrain(terrain: TerrainType, hasPokemonSource: boolean, ignoreAnim = false): boolean { - if (this.terrain?.terrainType === (terrain || undefined)) { + if (!this.canSetTerrain(terrain)) { return false; } @@ -663,7 +673,7 @@ export class Arena { ): boolean { const existingTag = this.getTagOnSide(tagType, side); if (existingTag) { - existingTag.onOverlap(this); + existingTag.onOverlap(this, globalScene.getPokemonById(sourceId)); if (existingTag instanceof ArenaTrapTag) { const { tagType, side, turnCount, layers, maxLayers } = existingTag as ArenaTrapTag; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 3cd25e4d10a..890c6bab0d6 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -6916,10 +6916,10 @@ export class EnemyPokemon extends Pokemon { if ( speciesId in Overrides.OPP_FORM_OVERRIDES && - Overrides.OPP_FORM_OVERRIDES[speciesId] && + !isNullOrUndefined(Overrides.OPP_FORM_OVERRIDES[speciesId]) && this.species.forms[Overrides.OPP_FORM_OVERRIDES[speciesId]] ) { - this.formIndex = Overrides.OPP_FORM_OVERRIDES[speciesId] ?? 0; + this.formIndex = Overrides.OPP_FORM_OVERRIDES[speciesId]; } if (!dataSource) { diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 4325b54a783..98919ebfa93 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -9,7 +9,6 @@ import { TrainerSlot, trainerConfigs, trainerPartyTemplates, - signatureSpecies, TeraAIMode, } from "#app/data/trainer-config"; import type { EnemyPokemon } from "#app/field/pokemon"; @@ -22,6 +21,7 @@ import i18next from "i18next"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; +import { signatureSpecies } from "#app/data/balance/signature-species"; export enum TrainerVariant { DEFAULT, diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 1e98c175487..295dc318db4 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -79,6 +79,7 @@ export class LoadingScene extends SceneBase { this.loadImage("icon_owned", "ui"); this.loadImage("icon_egg_move", "ui"); this.loadImage("ability_bar_left", "ui"); + this.loadImage("ability_bar_right", "ui"); this.loadImage("bgm_bar", "ui"); this.loadImage("party_exp_bar", "ui"); this.loadImage("achv_bar", "ui"); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 7ceba13440d..3d2e67b0dc3 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -3566,10 +3566,10 @@ function getNewModifierTypeOption( } else if (upgradeCount === undefined && player) { upgradeCount = 0; if (tier < ModifierTier.MASTER && allowLuckUpgrades) { - const partyShinyCount = party.filter(p => p.isShiny() && !p.isFainted()).length; - const upgradeOdds = Math.floor(32 / ((partyShinyCount + 2) / 2)); + const partyLuckValue = getPartyLuckValue(party); + const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4)); while (modifierPool.hasOwnProperty(tier + upgradeCount + 1) && modifierPool[tier + upgradeCount + 1].length) { - if (!randSeedInt(upgradeOdds)) { + if (randSeedInt(upgradeOdds) < 4) { upgradeCount++; } else { break; diff --git a/src/overrides.ts b/src/overrides.ts index dc04f72049a..882a634ff90 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable @typescript-eslint/consistent-type-imports */ import { type PokeballCounts } from "#app/battle-scene"; import { EvolutionItem } from "#app/data/balance/pokemon-evolutions"; import { Gender } from "#app/data/gender"; diff --git a/src/phase.ts b/src/phase.ts index 8da00d78b61..20cc7cc4063 100644 --- a/src/phase.ts +++ b/src/phase.ts @@ -1,11 +1,7 @@ import { globalScene } from "#app/global-scene"; export class Phase { - start() { - if (globalScene.abilityBar.shown) { - globalScene.abilityBar.resetAutoHideTimer(); - } - } + start() {} end() { globalScene.shiftPhase(); diff --git a/src/phases/hide-ability-phase.ts b/src/phases/hide-ability-phase.ts new file mode 100644 index 00000000000..0745b3f832a --- /dev/null +++ b/src/phases/hide-ability-phase.ts @@ -0,0 +1,27 @@ +import { globalScene } from "#app/global-scene"; +import type { BattlerIndex } from "#app/battle"; +import { PokemonPhase } from "./pokemon-phase"; + +export class HideAbilityPhase extends PokemonPhase { + private passive: boolean; + + constructor(battlerIndex: BattlerIndex, passive = false) { + super(battlerIndex); + + this.passive = passive; + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + + if (pokemon) { + globalScene.abilityBar.hide().then(() => { + this.end(); + }); + } else { + this.end(); + } + } +} diff --git a/src/phases/message-phase.ts b/src/phases/message-phase.ts index af4e63c6916..2a5bcf6b99c 100644 --- a/src/phases/message-phase.ts +++ b/src/phases/message-phase.ts @@ -28,17 +28,28 @@ export class MessagePhase extends Phase { super.start(); if (this.text.indexOf("$") > -1) { + const pokename: string[] = []; + const repname = [ "#POKEMON1", "#POKEMON2" ]; + for (let p = 0; p < globalScene.getPlayerField().length; p++) { + pokename.push(globalScene.getPlayerField()[p].getNameToRender()); + this.text = this.text.split(pokename[p]).join(repname[p]); + } const pageIndex = this.text.indexOf("$"); - globalScene.unshiftPhase( - new MessagePhase( - this.text.slice(pageIndex + 1), - this.callbackDelay, - this.prompt, - this.promptDelay, - this.speaker, - ), - ); - this.text = this.text.slice(0, pageIndex).trim(); + for (let p = 0; p < globalScene.getPlayerField().length; p++) { + this.text = this.text.split(repname[p]).join(pokename[p]); + } + if (pageIndex !== -1) { + globalScene.unshiftPhase( + new MessagePhase( + this.text.slice(pageIndex + 1), + this.callbackDelay, + this.prompt, + this.promptDelay, + this.speaker, + ), + ); + this.text = this.text.slice(0, pageIndex).trim(); + } } if (this.speaker) { @@ -61,12 +72,4 @@ export class MessagePhase extends Phase { ); } } - - end() { - if (globalScene.abilityBar.shown) { - globalScene.abilityBar.hide(); - } - - super.end(); - } } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 16802f8e0ff..f8edaa56981 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -504,7 +504,7 @@ export class MovePhase extends BattlePhase { globalScene .getField(true) .filter(p => p !== this.pokemon) - .forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget)); + .forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget, this.pokemon)); /** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */ let redirectedByAbility = currentTarget !== redirectTarget.value; diff --git a/src/phases/pokemon-transform-phase.ts b/src/phases/pokemon-transform-phase.ts index 70b390dc6de..b33689321b5 100644 --- a/src/phases/pokemon-transform-phase.ts +++ b/src/phases/pokemon-transform-phase.ts @@ -5,6 +5,8 @@ import { EFFECTIVE_STATS, BATTLE_STATS } from "#enums/stat"; import { PokemonMove } from "#app/field/pokemon"; import { globalScene } from "#app/global-scene"; import { PokemonPhase } from "./pokemon-phase"; +import { getPokemonNameWithAffix } from "#app/messages"; +import i18next from "i18next"; /** * Transforms a Pokemon into another Pokemon on the field. @@ -62,6 +64,13 @@ export class PokemonTransformPhase extends PokemonPhase { globalScene.playSound("battle_anims/PRSFX- Transform"); } + globalScene.queueMessage( + i18next.t("abilityTriggers:postSummonTransform", { + pokemonNameWithAffix: getPokemonNameWithAffix(user), + targetName: target.name, + }), + ); + promises.push( user.loadAssets(false).then(() => { user.playAnim(); diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index 103174a4c83..0f4360b0af6 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -53,6 +53,7 @@ export class SelectStarterPhase extends Phase { let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); if ( starter.species.speciesId in Overrides.STARTER_FORM_OVERRIDES && + !Utils.isNullOrUndefined(Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]) && starter.species.forms[Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!] ) { starterFormIndex = Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!; diff --git a/src/phases/show-ability-phase.ts b/src/phases/show-ability-phase.ts index 326b3a55ce3..1b3c6dde568 100644 --- a/src/phases/show-ability-phase.ts +++ b/src/phases/show-ability-phase.ts @@ -1,36 +1,60 @@ import { globalScene } from "#app/global-scene"; import type { BattlerIndex } from "#app/battle"; import { PokemonPhase } from "./pokemon-phase"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { HideAbilityPhase } from "#app/phases/hide-ability-phase"; export class ShowAbilityPhase extends PokemonPhase { private passive: boolean; + private pokemonName: string; + private abilityName: string; + private pokemonOnField: boolean; constructor(battlerIndex: BattlerIndex, passive = false) { super(battlerIndex); this.passive = passive; + + const pokemon = this.getPokemon(); + if (pokemon) { + // Set these now as the pokemon object may change before the queued phase is run + this.pokemonName = getPokemonNameWithAffix(pokemon); + this.abilityName = (passive ? this.getPokemon().getPassiveAbility() : this.getPokemon().getAbility()).name; + this.pokemonOnField = true; + } else { + this.pokemonOnField = false; + } } start() { super.start(); + if (!this.pokemonOnField || !this.getPokemon()) { + return this.end(); + } + + // If the bar is already out, hide it before showing the new one + if (globalScene.abilityBar.isVisible()) { + globalScene.unshiftPhase(new HideAbilityPhase(this.battlerIndex, this.passive)); + globalScene.unshiftPhase(new ShowAbilityPhase(this.battlerIndex, this.passive)); + return this.end(); + } + const pokemon = this.getPokemon(); - if (pokemon) { - if (!pokemon.isPlayer()) { - /** If its an enemy pokemon, list it as last enemy to use ability or move */ - globalScene.currentBattle.lastEnemyInvolved = pokemon.getBattlerIndex() % 2; - } else { - globalScene.currentBattle.lastPlayerInvolved = pokemon.getBattlerIndex() % 2; - } - - globalScene.abilityBar.showAbility(pokemon, this.passive); + if (!pokemon.isPlayer()) { + /** If its an enemy pokemon, list it as last enemy to use ability or move */ + globalScene.currentBattle.lastEnemyInvolved = pokemon.getBattlerIndex() % 2; + } else { + globalScene.currentBattle.lastPlayerInvolved = pokemon.getBattlerIndex() % 2; + } + globalScene.abilityBar.showAbility(this.pokemonName, this.abilityName, this.passive, this.player).then(() => { if (pokemon?.battleData) { pokemon.battleData.abilityRevealed = true; } - } - this.end(); + this.end(); + }); } } diff --git a/src/ui/ability-bar.ts b/src/ui/ability-bar.ts index 7fe9c0d8052..5481791de64 100644 --- a/src/ui/ability-bar.ts +++ b/src/ui/ability-bar.ts @@ -1,31 +1,33 @@ -import { getPokemonNameWithAffix } from "#app/messages"; import { globalScene } from "#app/global-scene"; -import type Pokemon from "../field/pokemon"; import { TextStyle, addTextObject } from "./text"; import i18next from "i18next"; -const hiddenX = -118; -const shownX = 0; +const barWidth = 118; +const screenLeft = 0; const baseY = -116; export default class AbilityBar extends Phaser.GameObjects.Container { - private bg: Phaser.GameObjects.Image; + private abilityBars: Phaser.GameObjects.Image[]; private abilityBarText: Phaser.GameObjects.Text; - - private tween: Phaser.Tweens.Tween | null; - private autoHideTimer: NodeJS.Timeout | null; - - public shown: boolean; + private player: boolean; + private screenRight: number; // hold screenRight in case size changes between show and hide + private shown: boolean; constructor() { - super(globalScene, hiddenX, baseY); + super(globalScene, barWidth, baseY); + this.abilityBars = []; + this.player = true; + this.shown = false; } setup(): void { - this.bg = globalScene.add.image(0, 0, "ability_bar_left"); - this.bg.setOrigin(0, 0); - - this.add(this.bg); + for (const key of ["ability_bar_right", "ability_bar_left"]) { + const bar = globalScene.add.image(0, 0, key); + bar.setOrigin(0, 0); + bar.setVisible(false); + this.add(bar); + this.abilityBars.push(bar); + } this.abilityBarText = addTextObject(15, 3, "", TextStyle.MESSAGE, { fontSize: "72px", @@ -33,72 +35,80 @@ export default class AbilityBar extends Phaser.GameObjects.Container { this.abilityBarText.setOrigin(0, 0); this.abilityBarText.setWordWrapWidth(600, true); this.add(this.abilityBarText); + this.bringToTop(this.abilityBarText); this.setVisible(false); - this.shown = false; + this.setX(-barWidth); // start hidden (right edge of bar at x=0) } - showAbility(pokemon: Pokemon, passive = false): void { - this.abilityBarText.setText( - `${i18next.t("fightUiHandler:abilityFlyInText", { pokemonName: getPokemonNameWithAffix(pokemon), passive: passive ? i18next.t("fightUiHandler:passive") : "", abilityName: !passive ? pokemon.getAbility().name : pokemon.getPassiveAbility().name })}`, - ); + public override setVisible(value: boolean): this { + this.abilityBars[+this.player].setVisible(value); + this.shown = value; + return this; + } - if (this.shown) { - return; + public async startTween(config: any, text?: string): Promise { + this.setVisible(true); + if (text) { + this.abilityBarText.setText(text); } + return new Promise(resolve => { + globalScene.tweens.add({ + ...config, + onComplete: () => { + if (config.onComplete) { + config.onComplete(); + } + resolve(); + }, + }); + }); + } + public async showAbility(pokemonName: string, abilityName: string, passive = false, player = true): Promise { + const text = `${i18next.t("fightUiHandler:abilityFlyInText", { pokemonName: pokemonName, passive: passive ? i18next.t("fightUiHandler:passive") : "", abilityName: abilityName })}`; + this.screenRight = globalScene.scaledCanvas.width; + if (player !== this.player) { + // Move the bar if it has changed from the player to enemy side (or vice versa) + this.setX(player ? -barWidth : this.screenRight); + this.player = player; + } globalScene.fieldUI.bringToTop(this); - this.y = baseY + (globalScene.currentBattle.double ? 14 : 0); - this.tween = globalScene.tweens.add({ - targets: this, - x: shownX, - duration: 500, - ease: "Sine.easeOut", - onComplete: () => { - this.tween = null; - this.resetAutoHideTimer(); - }, - }); + let y = baseY; + if (this.player) { + y += globalScene.currentBattle.double ? 14 : 0; + } else { + y -= globalScene.currentBattle.double ? 28 : 14; + } - this.setVisible(true); - this.shown = true; + this.setY(y); + + return this.startTween( + { + targets: this, + x: this.player ? screenLeft : this.screenRight - barWidth, + duration: 500, + ease: "Sine.easeOut", + hold: 1000, + }, + text, + ); } - hide(): void { - if (!this.shown) { - return; - } - - if (this.autoHideTimer) { - clearInterval(this.autoHideTimer); - } - - if (this.tween) { - this.tween.stop(); - } - - this.tween = globalScene.tweens.add({ + public async hide(): Promise { + return this.startTween({ targets: this, - x: -91, - duration: 500, + x: this.player ? -barWidth : this.screenRight, + duration: 200, ease: "Sine.easeIn", onComplete: () => { - this.tween = null; this.setVisible(false); }, }); - - this.shown = false; } - resetAutoHideTimer(): void { - if (this.autoHideTimer) { - clearInterval(this.autoHideTimer); - } - this.autoHideTimer = setTimeout(() => { - this.hide(); - this.autoHideTimer = null; - }, 2500); + public isVisible(): boolean { + return this.shown; } } diff --git a/src/ui/message-ui-handler.ts b/src/ui/message-ui-handler.ts index 50522152021..230b951de59 100644 --- a/src/ui/message-ui-handler.ts +++ b/src/ui/message-ui-handler.ts @@ -76,6 +76,12 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { const fadeMap = new Map(); const actionPattern = /@(c|d|s|f)\{(.*?)\}/; let actionMatch: RegExpExecArray | null; + const pokename: string[] = []; + const repname = [ "#POKEMON1", "#POKEMON2" ]; + for (let p = 0; p < globalScene.getPlayerField().length; p++) { + pokename.push(globalScene.getPlayerField()[p].getNameToRender()); + text = text.split(pokename[p]).join(repname[p]); + } while ((actionMatch = actionPattern.exec(text))) { switch (actionMatch[1]) { case "c": @@ -94,6 +100,9 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { text = text.slice(0, actionMatch.index) + text.slice(actionMatch.index + actionMatch[2].length + 4); } + for (let p = 0; p < globalScene.getPlayerField().length; p++) { + text = text.split(repname[p]).join(pokename[p]); + } if (text) { // Predetermine overflow line breaks to avoid words breaking while displaying const textWords = text.split(" "); diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index f4cb9f51c16..f3625f64476 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -1742,36 +1742,26 @@ export default class PokedexUiHandler extends MessageUiHandler { container.icon.setTint(0); } - if (data.eggMove1) { - container.eggMove1Icon.setVisible(true); - } else { - container.eggMove1Icon.setVisible(false); - } - if (data.eggMove2) { - container.eggMove2Icon.setVisible(true); - } else { - container.eggMove2Icon.setVisible(false); - } - if (data.tmMove1) { - container.tmMove1Icon.setVisible(true); - } else { - container.tmMove1Icon.setVisible(false); - } - if (data.tmMove2) { - container.tmMove2Icon.setVisible(true); - } else { - container.tmMove2Icon.setVisible(false); - } - if (data.passive1) { - container.passive1Icon.setVisible(true); - } else { - container.passive1Icon.setVisible(false); - } - if (data.passive2) { - container.passive2Icon.setVisible(true); - } else { - container.passive2Icon.setVisible(false); - } + const pairs: [boolean | undefined, Phaser.GameObjects.Image][] = [ + [data.eggMove1, container.eggMove1Icon], + [data.eggMove2, container.eggMove2Icon], + [data.tmMove1, container.tmMove1Icon], + [data.tmMove2, container.tmMove2Icon], + [data.passive1, container.passive1Icon], + [data.passive2, container.passive2Icon], + ]; + + pairs.forEach(([unlocked, icon]) => { + if (unlocked) { + icon.setVisible(true); + icon.clearTint(); + } else if (unlocked === false) { + icon.setVisible(true); + icon.setTint(0x808080); + } else { + icon.setVisible(false); + } + }); if (this.showDecorations) { if (this.pokerusSpecies.includes(data.species)) { diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 7c202e9210d..026e42ccf46 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -328,17 +328,28 @@ export default class UI extends Phaser.GameObjects.Container { prompt?: boolean | null, promptDelay?: number | null, ): void { + const pokename: string[] = []; + const repname = [ "#POKEMON1", "#POKEMON2" ]; + for (let p = 0; p < globalScene.getPlayerField().length; p++) { + pokename.push(globalScene.getPlayerField()[p].getNameToRender()); + text = text.split(pokename[p]).join(repname[p]); + } if (prompt && text.indexOf("$") > -1) { const messagePages = text.split(/\$/g).map(m => m.trim()); // biome-ignore lint/complexity/useOptionalChain: optional chain would change this to be null instead of undefined. let showMessageAndCallback = () => callback && callback(); for (let p = messagePages.length - 1; p >= 0; p--) { const originalFunc = showMessageAndCallback; + messagePages[p] = messagePages[p].split(repname[0]).join(pokename[0]); + messagePages[p] = messagePages[p].split(repname[1]).join(pokename[1]); showMessageAndCallback = () => this.showText(messagePages[p], null, originalFunc, null, true); } showMessageAndCallback(); } else { const handler = this.getHandler(); + for (let p = 0; p < globalScene.getPlayerField().length; p++) { + text = text.split(repname[p]).join(pokename[p]); + } if (handler instanceof MessageUiHandler) { (handler as MessageUiHandler).showText(text, delay, callback, callbackDelay, prompt, promptDelay); } else { diff --git a/test/abilities/lightningrod.test.ts b/test/abilities/lightningrod.test.ts new file mode 100644 index 00000000000..1ca6c6b1e89 --- /dev/null +++ b/test/abilities/lightningrod.test.ts @@ -0,0 +1,115 @@ +import { BattlerIndex } from "#app/battle"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Lightningrod", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH, Moves.SHOCK_WAVE ]) + .ability(Abilities.BALL_FETCH) + .battleType("double") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should redirect electric type moves", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + + const enemy1 = game.scene.getEnemyField()[0]; + const enemy2 = game.scene.getEnemyField()[1]; + + enemy2.summonData.ability = Abilities.LIGHTNING_ROD; + + game.move.select(Moves.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy1.isFullHp()).toBe(true); + }); + + it("should not redirect non-electric type moves", async () => { + game.override.moveset([ Moves.SPLASH, Moves.AERIAL_ACE ]); + await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + + const enemy1 = game.scene.getEnemyField()[0]; + const enemy2 = game.scene.getEnemyField()[1]; + + enemy2.summonData.ability = Abilities.LIGHTNING_ROD; + + game.move.select(Moves.AERIAL_ACE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy1.isFullHp()).toBe(false); + }); + + it("should boost the user's spatk without damaging", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + + const enemy2 = game.scene.getEnemyField()[1]; + + enemy2.summonData.ability = Abilities.LIGHTNING_ROD; + + game.move.select(Moves.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy2.isFullHp()).toBe(true); + expect(enemy2.getStatStage(Stat.SPATK)).toBe(1); + }); + + it("should not redirect moves changed from electric type via ability", async () => { + game.override.ability(Abilities.NORMALIZE); + await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + + const enemy1 = game.scene.getEnemyField()[0]; + const enemy2 = game.scene.getEnemyField()[1]; + + enemy2.summonData.ability = Abilities.LIGHTNING_ROD; + + game.move.select(Moves.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy1.isFullHp()).toBe(false); + }); + + it("should redirect moves changed to electric type via ability", async () => { + game.override.ability(Abilities.GALVANIZE) + .moveset(Moves.TACKLE); + await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + + const enemy1 = game.scene.getEnemyField()[0]; + const enemy2 = game.scene.getEnemyField()[1]; + + enemy2.summonData.ability = Abilities.LIGHTNING_ROD; + + game.move.select(Moves.TACKLE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy1.isFullHp()).toBe(true); + expect(enemy2.getStatStage(Stat.SPATK)).toBe(1); + }); +}); diff --git a/test/abilities/neutralizing_gas.test.ts b/test/abilities/neutralizing_gas.test.ts index 2bba5b83987..08ab884d806 100644 --- a/test/abilities/neutralizing_gas.test.ts +++ b/test/abilities/neutralizing_gas.test.ts @@ -1,4 +1,5 @@ import { BattlerIndex } from "#app/battle"; +import { PostSummonWeatherChangeAbAttr } from "#app/data/ability"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { Moves } from "#enums/moves"; @@ -7,7 +8,7 @@ import { Species } from "#enums/species"; import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Abilities - Neutralizing Gas", () => { let phaserGame: Phaser.Game; @@ -155,4 +156,25 @@ describe("Abilities - Neutralizing Gas", () => { expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined(); }); + + it("should not activate abilities of pokemon no longer on the field", async () => { + game.override + .battleType("single") + .ability(Abilities.NEUTRALIZING_GAS) + .enemyAbility(Abilities.DELTA_STREAM); + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const enemy = game.scene.getEnemyPokemon()!; + const weatherChangeAttr = enemy.getAbilityAttrs(PostSummonWeatherChangeAbAttr, false)[0]; + vi.spyOn(weatherChangeAttr, "applyPostSummon"); + + expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined(); + + game.move.select(Moves.SPLASH); + await game.killPokemon(enemy); + await game.killPokemon(game.scene.getPlayerPokemon()!); + + expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined(); + expect(weatherChangeAttr.applyPostSummon).not.toHaveBeenCalled(); + }); }); diff --git a/test/abilities/storm_drain.test.ts b/test/abilities/storm_drain.test.ts new file mode 100644 index 00000000000..e2a7b3e212e --- /dev/null +++ b/test/abilities/storm_drain.test.ts @@ -0,0 +1,115 @@ +import { BattlerIndex } from "#app/battle"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Storm Drain", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH, Moves.WATER_GUN ]) + .ability(Abilities.BALL_FETCH) + .battleType("double") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should redirect water type moves", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + + const enemy1 = game.scene.getEnemyField()[0]; + const enemy2 = game.scene.getEnemyField()[1]; + + enemy2.summonData.ability = Abilities.STORM_DRAIN; + + game.move.select(Moves.WATER_GUN, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy1.isFullHp()).toBe(true); + }); + + it("should not redirect non-water type moves", async () => { + game.override.moveset([ Moves.SPLASH, Moves.AERIAL_ACE ]); + await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + + const enemy1 = game.scene.getEnemyField()[0]; + const enemy2 = game.scene.getEnemyField()[1]; + + enemy2.summonData.ability = Abilities.STORM_DRAIN; + + game.move.select(Moves.AERIAL_ACE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy1.isFullHp()).toBe(false); + }); + + it("should boost the user's spatk without damaging", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + + const enemy2 = game.scene.getEnemyField()[1]; + + enemy2.summonData.ability = Abilities.STORM_DRAIN; + + game.move.select(Moves.WATER_GUN, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy2.isFullHp()).toBe(true); + expect(enemy2.getStatStage(Stat.SPATK)).toBe(1); + }); + + it("should not redirect moves changed from water type via ability", async () => { + game.override.ability(Abilities.NORMALIZE); + await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + + const enemy1 = game.scene.getEnemyField()[0]; + const enemy2 = game.scene.getEnemyField()[1]; + + enemy2.summonData.ability = Abilities.STORM_DRAIN; + + game.move.select(Moves.WATER_GUN, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy1.isFullHp()).toBe(false); + }); + + it("should redirect moves changed to water type via ability", async () => { + game.override.ability(Abilities.LIQUID_VOICE) + .moveset(Moves.PSYCHIC_NOISE); + await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + + const enemy1 = game.scene.getEnemyField()[0]; + const enemy2 = game.scene.getEnemyField()[1]; + + enemy2.summonData.ability = Abilities.STORM_DRAIN; + + game.move.select(Moves.PSYCHIC_NOISE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy1.isFullHp()).toBe(true); + expect(enemy2.getStatStage(Stat.SPATK)).toBe(1); + }); +}); diff --git a/test/moves/chilly_reception.test.ts b/test/moves/chilly_reception.test.ts index f90aaf6bb02..39342a921b6 100644 --- a/test/moves/chilly_reception.test.ts +++ b/test/moves/chilly_reception.test.ts @@ -27,8 +27,8 @@ describe("Moves - Chilly Reception", () => { .battleType("single") .moveset([Moves.CHILLY_RECEPTION, Moves.SNOWSCAPE]) .enemyMoveset(Array(4).fill(Moves.SPLASH)) - .enemyAbility(Abilities.NONE) - .ability(Abilities.NONE); + .enemyAbility(Abilities.BALL_FETCH) + .ability(Abilities.BALL_FETCH); }); it("should still change the weather if user can't switch out", async () => { @@ -72,7 +72,6 @@ describe("Moves - Chilly Reception", () => { game.override .battleType("single") .enemyMoveset([Moves.CHILLY_RECEPTION, Moves.TACKLE]) - .enemyAbility(Abilities.NONE) .moveset(Array(4).fill(Moves.SPLASH)); await game.classicMode.startBattle([Species.SLOWKING, Species.MEOWTH]); @@ -89,7 +88,6 @@ describe("Moves - Chilly Reception", () => { .battleType("single") .startingWave(8) .enemyMoveset(Array(4).fill(Moves.CHILLY_RECEPTION)) - .enemyAbility(Abilities.NONE) .enemySpecies(Species.MAGIKARP) .moveset([Moves.SPLASH, Moves.THUNDERBOLT]); diff --git a/test/moves/secret_power.test.ts b/test/moves/secret_power.test.ts index 28dc7efc434..37f1664251b 100644 --- a/test/moves/secret_power.test.ts +++ b/test/moves/secret_power.test.ts @@ -68,7 +68,7 @@ describe("Moves - Secret Power", () => { await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]); const sereneGraceAttr = allAbilities[Abilities.SERENE_GRACE].getAttrs(MoveEffectChanceMultiplierAbAttr)[0]; - vi.spyOn(sereneGraceAttr, "apply"); + vi.spyOn(sereneGraceAttr, "canApply"); game.move.select(Moves.WATER_PLEDGE, 0, BattlerIndex.ENEMY); game.move.select(Moves.FIRE_PLEDGE, 1, BattlerIndex.ENEMY_2); @@ -86,8 +86,8 @@ describe("Moves - Secret Power", () => { await game.phaseInterceptor.to("BerryPhase", false); - expect(sereneGraceAttr.apply).toHaveBeenCalledOnce(); - expect(sereneGraceAttr.apply).toHaveLastReturnedWith(true); + expect(sereneGraceAttr.canApply).toHaveBeenCalledOnce(); + expect(sereneGraceAttr.canApply).toHaveLastReturnedWith(true); expect(rainbowEffect.apply).toHaveBeenCalledTimes(0); }); diff --git a/test/moves/tailwind.test.ts b/test/moves/tailwind.test.ts index 24a7fa25061..591b94408ce 100644 --- a/test/moves/tailwind.test.ts +++ b/test/moves/tailwind.test.ts @@ -1,9 +1,9 @@ -import { Stat } from "#enums/stat"; import { ArenaTagSide } from "#app/data/arena-tag"; import { ArenaTagType } from "#app/enums/arena-tag-type"; -import { TurnEndPhase } from "#app/phases/turn-end-phase"; +import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -24,13 +24,16 @@ describe("Moves - Tailwind", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleType("double"); - game.override.moveset([Moves.TAILWIND, Moves.SPLASH, Moves.PETAL_BLIZZARD, Moves.SANDSTORM]); - game.override.enemyMoveset(Moves.SPLASH); + game.override + .battleType("double") + .moveset([Moves.TAILWIND, Moves.SPLASH]) + .enemyMoveset(Moves.SPLASH) + .enemyAbility(Abilities.BALL_FETCH) + .ability(Abilities.BALL_FETCH); }); it("doubles the Speed stat of the Pokemons on its side", async () => { - await game.startBattle([Species.MAGIKARP, Species.MEOWTH]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.MEOWTH]); const magikarp = game.scene.getPlayerField()[0]; const meowth = game.scene.getPlayerField()[1]; @@ -43,7 +46,7 @@ describe("Moves - Tailwind", () => { game.move.select(Moves.TAILWIND); game.move.select(Moves.SPLASH, 1); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); expect(magikarp.getEffectiveStat(Stat.SPD)).toBe(magikarpSpd * 2); expect(meowth.getEffectiveStat(Stat.SPD)).toBe(meowthSpd * 2); @@ -53,7 +56,7 @@ describe("Moves - Tailwind", () => { it("lasts for 4 turns", async () => { game.override.battleType("single"); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); game.move.select(Moves.TAILWIND); await game.toNextTurn(); @@ -76,7 +79,7 @@ describe("Moves - Tailwind", () => { it("does not affect the opposing side", async () => { game.override.battleType("single"); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const ally = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; @@ -91,7 +94,7 @@ describe("Moves - Tailwind", () => { game.move.select(Moves.TAILWIND); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); expect(ally.getEffectiveStat(Stat.SPD)).toBe(allySpd * 2); expect(enemy.getEffectiveStat(Stat.SPD)).equal(enemySpd);