From f95f3ff30e7082e5a398f6ffa4264c996ecc483e Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Tue, 2 Apr 2024 15:14:07 -0400 Subject: [PATCH] Implement Magic Guard ability --- src/data/ability.ts | 12 ++++++++-- src/data/arena-tag.ts | 22 ++++++++++++++----- src/data/battler-tags.ts | 36 ++++++++++++++++++++++-------- src/data/move.ts | 11 +++++++++- src/phases.ts | 47 ++++++++++++++++++++++++---------------- 5 files changed, 92 insertions(+), 36 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index ffa7e1697..b076c074d 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -977,9 +977,16 @@ export class BlockCritAbAttr extends AbAttr { } } +export class BlockNonDirectDamageAbAttr extends AbAttr { + apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean { + cancelled.value = true; + return true; + } +} + export class BlockOneHitKOAbAttr extends AbAttr { apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean { - cancelled.value = false; + cancelled.value = true; return true; } } @@ -2200,7 +2207,8 @@ export function initAbilities() { new Ability(Abilities.QUICK_FEET, "Quick Feet (N)", "Boosts the Speed stat if the Pokémon has a status condition.", 4), new Ability(Abilities.NORMALIZE, "Normalize (N)", "All the Pokémon's moves become Normal type. The power of those moves is boosted a little.", 4), new Ability(Abilities.SNIPER, "Sniper (N)", "Powers up moves if they become critical hits when attacking.", 4), - new Ability(Abilities.MAGIC_GUARD, "Magic Guard (N)", "The Pokémon only takes damage from attacks.", 4), + new Ability(Abilities.MAGIC_GUARD, "Magic Guard", "The Pokémon only takes damage from attacks.", 4) + .attr(BlockNonDirectDamageAbAttr), new Ability(Abilities.NO_GUARD, "No Guard (N)", "The Pokémon employs no-guard tactics to ensure incoming and outgoing attacks always land.", 4), new Ability(Abilities.STALL, "Stall (N)", "The Pokémon moves after all other Pokémon do.", 4), new Ability(Abilities.TECHNICIAN, "Technician", "Powers up the Pokémon's weaker moves.", 4) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index ac2b54cad..b34205db3 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -9,6 +9,7 @@ import { StatusEffect } from "./status-effect"; import { BattlerIndex } from "../battle"; import { Moves } from "./enums/moves"; import { ArenaTagType } from "./enums/arena-tag-type"; +import { BlockNonDirectDamageAbAttr, applyAbAttrs } from "./ability"; export enum ArenaTagSide { BOTH, @@ -172,12 +173,17 @@ class SpikesTag extends ArenaTrapTag { activateTrap(pokemon: Pokemon): boolean { if (pokemon.isGrounded()) { - const damageHpRatio = 1 / (10 - 2 * this.layers); - const damage = Math.ceil(pokemon.getMaxHp() * damageHpRatio); + const cancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); - pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is hurt\nby the spikes!')); - pokemon.damageAndUpdate(damage, HitResult.OTHER); - return true; + if (!cancelled.value) { + const damageHpRatio = 1 / (10 - 2 * this.layers); + const damage = Math.ceil(pokemon.getMaxHp() * damageHpRatio); + + pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is hurt\nby the spikes!')); + pokemon.damageAndUpdate(damage, HitResult.OTHER); + return true; + } } return false; @@ -293,6 +299,12 @@ class StealthRockTag extends ArenaTrapTag { } activateTrap(pokemon: Pokemon): boolean { + const cancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + + if (cancelled.value) + return false; + const damageHpRatio = this.getDamageHpRatio(pokemon); if (damageHpRatio) { diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 6fff284d8..9a3ab9be3 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -8,7 +8,7 @@ import * as Utils from "../utils"; import { Moves } from "./enums/moves"; import { ChargeAttr, MoveFlags, allMoves } from "./move"; import { Type } from "./type"; -import { Abilities, FlinchEffectAbAttr, applyAbAttrs } from "./ability"; +import { Abilities, BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, applyAbAttrs } from "./ability"; import { BattlerTagType } from "./enums/battler-tag-type"; import { TerrainType } from "./terrain"; import { WeatherType } from "./weather"; @@ -280,10 +280,15 @@ export class SeedTag extends BattlerTag { if (ret) { const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex); if (source) { - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED)); + const cancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); - const damage = pokemon.damageAndUpdate(Math.max(Math.floor(pokemon.getMaxHp() / 8), 1)); - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by Leech Seed!'), false, true)); + if (!cancelled.value) { + pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED)); + + const damage = pokemon.damageAndUpdate(Math.max(Math.floor(pokemon.getMaxHp() / 8), 1)); + pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by Leech Seed!'), false, true)); + } } } @@ -319,7 +324,11 @@ export class NightmareTag extends BattlerTag { pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is locked\nin a Nightmare!')); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type - pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / 4)); + const cancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + + if (!cancelled.value) + pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / 4)); } return ret; @@ -506,7 +515,11 @@ export abstract class DamagingTrapTag extends TrappedTag { pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby ${this.getMoveName()}!`)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim)); - pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / 8)) + const cancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + + if (!cancelled.value) + pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / 8)) } return ret; @@ -924,10 +937,15 @@ export class SaltCuredTag extends BattlerTag { if (ret) { pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE)); - const pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER); - pokemon.damageAndUpdate(Math.max(Math.floor(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8), 1)); + const cancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); - pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt by ${this.getMoveName()}!`)); + if (!cancelled.value) { + const pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER); + pokemon.damageAndUpdate(Math.max(Math.floor(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8), 1)); + + pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt by ${this.getMoveName()}!`)); + } } return ret; diff --git a/src/data/move.ts b/src/data/move.ts index 19efec55a..85643b275 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -12,7 +12,7 @@ import * as Utils from "../utils"; import { WeatherType } from "./weather"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { ArenaTagType } from "./enums/arena-tag-type"; -import { Abilities, ProtectAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs } from "./ability"; +import { Abilities, ProtectAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr } from "./ability"; import { PokemonHeldItemModifier } from "../modifier/modifier"; import { BattlerIndex } from "../battle"; import { Stat } from "./pokemon-stat"; @@ -634,6 +634,10 @@ export class RecoilAttr extends MoveEffectAttr { if (!recoilDamage) return false; + applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); + if (cancelled.value) + return false; + user.damageAndUpdate(recoilDamage, HitResult.OTHER, false, true); user.scene.queueMessage(getPokemonMessage(user, ' is hit\nwith recoil!')); @@ -1825,6 +1829,11 @@ export class MissEffectAttr extends MoveAttr { } const halveHpMissEffectFunc = (user: Pokemon, move: Move) => { + const cancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); + if (cancelled.value) + return false; + const damage = user.damage(Math.floor(user.getMaxHp() / 2)); if (damage) user.scene.damageNumberHandler.add(user, damage, HitResult.OTHER); diff --git a/src/phases.ts b/src/phases.ts index 613bab676..82ec03c29 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -30,7 +30,7 @@ import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, get import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; import { ArenaTagType } from "./data/enums/arena-tag-type"; -import { Abilities, CheckTrappedAbAttr, MoveAbilityBypassAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "./data/ability"; +import { Abilities, CheckTrappedAbAttr, MoveAbilityBypassAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, applyPostBattleInitAbAttrs, PostBattleInitAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./field/arena"; import { BattleType, BattlerIndex, TurnCommand } from "./battle"; @@ -2551,6 +2551,7 @@ export class WeatherEffectPhase extends CommonAnimPhase { const cancelled = new Utils.BooleanHolder(false); applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (cancelled.value) return; @@ -2620,24 +2621,32 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { const pokemon = this.getPokemon(); if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) { pokemon.status.incrementTurn(); - this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectActivationText(pokemon.status.effect))); - let damage: integer = 0; - switch (pokemon.status.effect) { - case StatusEffect.POISON: - damage = Math.max(pokemon.getMaxHp() >> 3, 1); - break; - case StatusEffect.TOXIC: - damage = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); - break; - case StatusEffect.BURN: - damage = Math.max(pokemon.getMaxHp() >> 4, 1); - break; - } - if (damage) { - this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage)); - pokemon.updateInfo(); - } - new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => this.end()); + const cancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + + console.log(cancelled.value) + + if (!cancelled.value) { + this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectActivationText(pokemon.status.effect))); + let damage: integer = 0; + switch (pokemon.status.effect) { + case StatusEffect.POISON: + damage = Math.max(pokemon.getMaxHp() >> 3, 1); + break; + case StatusEffect.TOXIC: + damage = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); + break; + case StatusEffect.BURN: + damage = Math.max(pokemon.getMaxHp() >> 4, 1); + break; + } + if (damage) { + this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage)); + pokemon.updateInfo(); + } + new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => this.end()); + } else + this.end(); } else this.end(); }