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,12 +173,17 @@ class SpikesTag extends ArenaTrapTag {
activateTrap(pokemon: Pokemon): boolean { activateTrap(pokemon: Pokemon): boolean {
if (pokemon.isGrounded()) { if (pokemon.isGrounded()) {
const damageHpRatio = 1 / (10 - 2 * this.layers); const cancelled = new Utils.BooleanHolder(false);
const damage = Math.ceil(pokemon.getMaxHp() * damageHpRatio); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is hurt\nby the spikes!')); if (!cancelled.value) {
pokemon.damageAndUpdate(damage, HitResult.OTHER); const damageHpRatio = 1 / (10 - 2 * this.layers);
return true; 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; 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,10 +280,15 @@ 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) {
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)); if (!cancelled.value) {
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 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.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
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; return ret;
@ -506,7 +515,11 @@ 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));
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; return ret;
@ -924,10 +937,15 @@ 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 pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER); const cancelled = new Utils.BooleanHolder(false);
pokemon.damageAndUpdate(Math.max(Math.floor(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8), 1)); 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; 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,24 +2621,32 @@ 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();
this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectActivationText(pokemon.status.effect))); const cancelled = new Utils.BooleanHolder(false);
let damage: integer = 0; applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
switch (pokemon.status.effect) {
case StatusEffect.POISON: console.log(cancelled.value)
damage = Math.max(pokemon.getMaxHp() >> 3, 1);
break; if (!cancelled.value) {
case StatusEffect.TOXIC: this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectActivationText(pokemon.status.effect)));
damage = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); let damage: integer = 0;
break; switch (pokemon.status.effect) {
case StatusEffect.BURN: case StatusEffect.POISON:
damage = Math.max(pokemon.getMaxHp() >> 4, 1); damage = Math.max(pokemon.getMaxHp() >> 3, 1);
break; break;
} case StatusEffect.TOXIC:
if (damage) { damage = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1);
this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage)); break;
pokemon.updateInfo(); case StatusEffect.BURN:
} damage = Math.max(pokemon.getMaxHp() >> 4, 1);
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => this.end()); 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 } else
this.end(); this.end();
} }