Implement Magic Guard ability

This commit is contained in:
Flashfyre 2024-04-02 15:14:07 -04:00
parent b9b7afb3ee
commit f95f3ff30e
5 changed files with 92 additions and 36 deletions

View File

@ -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 { export class BlockOneHitKOAbAttr extends AbAttr {
apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean { apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean {
cancelled.value = false; cancelled.value = true;
return 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.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.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.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.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.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) new Ability(Abilities.TECHNICIAN, "Technician", "Powers up the Pokémon's weaker moves.", 4)

View File

@ -9,6 +9,7 @@ import { StatusEffect } from "./status-effect";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
import { Moves } from "./enums/moves"; import { Moves } from "./enums/moves";
import { ArenaTagType } from "./enums/arena-tag-type"; import { ArenaTagType } from "./enums/arena-tag-type";
import { BlockNonDirectDamageAbAttr, applyAbAttrs } from "./ability";
export enum ArenaTagSide { export enum ArenaTagSide {
BOTH, BOTH,
@ -172,6 +173,10 @@ class SpikesTag extends ArenaTrapTag {
activateTrap(pokemon: Pokemon): boolean { activateTrap(pokemon: Pokemon): boolean {
if (pokemon.isGrounded()) { if (pokemon.isGrounded()) {
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) {
const damageHpRatio = 1 / (10 - 2 * this.layers); const damageHpRatio = 1 / (10 - 2 * this.layers);
const damage = Math.ceil(pokemon.getMaxHp() * damageHpRatio); const damage = Math.ceil(pokemon.getMaxHp() * damageHpRatio);
@ -179,6 +184,7 @@ class SpikesTag extends ArenaTrapTag {
pokemon.damageAndUpdate(damage, HitResult.OTHER); pokemon.damageAndUpdate(damage, HitResult.OTHER);
return true; return true;
} }
}
return false; return false;
} }
@ -293,6 +299,12 @@ class StealthRockTag extends ArenaTrapTag {
} }
activateTrap(pokemon: Pokemon): boolean { activateTrap(pokemon: Pokemon): boolean {
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (cancelled.value)
return false;
const damageHpRatio = this.getDamageHpRatio(pokemon); const damageHpRatio = this.getDamageHpRatio(pokemon);
if (damageHpRatio) { if (damageHpRatio) {

View File

@ -8,7 +8,7 @@ import * as Utils from "../utils";
import { Moves } from "./enums/moves"; import { Moves } from "./enums/moves";
import { ChargeAttr, MoveFlags, allMoves } from "./move"; import { ChargeAttr, MoveFlags, allMoves } from "./move";
import { Type } from "./type"; 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 { BattlerTagType } from "./enums/battler-tag-type";
import { TerrainType } from "./terrain"; import { TerrainType } from "./terrain";
import { WeatherType } from "./weather"; import { WeatherType } from "./weather";
@ -280,12 +280,17 @@ export class SeedTag extends BattlerTag {
if (ret) { if (ret) {
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex); const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
if (source) { if (source) {
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) {
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED)); 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)); 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)); pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by Leech Seed!'), false, true));
} }
} }
}
return ret; return ret;
} }
@ -319,6 +324,10 @@ export class NightmareTag extends BattlerTag {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is locked\nin a Nightmare!')); 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.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value)
pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / 4)); pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / 4));
} }
@ -506,6 +515,10 @@ export abstract class DamagingTrapTag extends TrappedTag {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby ${this.getMoveName()}!`)); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby ${this.getMoveName()}!`));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim));
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value)
pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / 8)) pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / 8))
} }
@ -924,11 +937,16 @@ export class SaltCuredTag extends BattlerTag {
if (ret) { if (ret) {
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE));
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (!cancelled.value) {
const pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER); 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.damageAndUpdate(Math.max(Math.floor(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8), 1));
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt by ${this.getMoveName()}!`)); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt by ${this.getMoveName()}!`));
} }
}
return ret; return ret;
} }

View File

@ -12,7 +12,7 @@ import * as Utils from "../utils";
import { WeatherType } from "./weather"; import { WeatherType } from "./weather";
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
import { ArenaTagType } from "./enums/arena-tag-type"; 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 { PokemonHeldItemModifier } from "../modifier/modifier";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
import { Stat } from "./pokemon-stat"; import { Stat } from "./pokemon-stat";
@ -634,6 +634,10 @@ export class RecoilAttr extends MoveEffectAttr {
if (!recoilDamage) if (!recoilDamage)
return false; return false;
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled);
if (cancelled.value)
return false;
user.damageAndUpdate(recoilDamage, HitResult.OTHER, false, true); user.damageAndUpdate(recoilDamage, HitResult.OTHER, false, true);
user.scene.queueMessage(getPokemonMessage(user, ' is hit\nwith recoil!')); 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 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)); const damage = user.damage(Math.floor(user.getMaxHp() / 2));
if (damage) if (damage)
user.scene.damageNumberHandler.add(user, damage, HitResult.OTHER); user.scene.damageNumberHandler.add(user, damage, HitResult.OTHER);

View File

@ -30,7 +30,7 @@ import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, get
import { TempBattleStat } from "./data/temp-battle-stat"; import { TempBattleStat } from "./data/temp-battle-stat";
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag";
import { ArenaTagType } from "./data/enums/arena-tag-type"; 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 { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./field/arena"; import { getBiomeKey } from "./field/arena";
import { BattleType, BattlerIndex, TurnCommand } from "./battle"; import { BattleType, BattlerIndex, TurnCommand } from "./battle";
@ -2551,6 +2551,7 @@ export class WeatherEffectPhase extends CommonAnimPhase {
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled); applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (cancelled.value) if (cancelled.value)
return; return;
@ -2620,6 +2621,12 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) { if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) {
pokemon.status.incrementTurn(); pokemon.status.incrementTurn();
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))); this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectActivationText(pokemon.status.effect)));
let damage: integer = 0; let damage: integer = 0;
switch (pokemon.status.effect) { switch (pokemon.status.effect) {
@ -2640,6 +2647,8 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => this.end()); new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => this.end());
} else } else
this.end(); this.end();
} else
this.end();
} }
} }