From 453c5644aa3b89e5e49b67790581a1a95c6ef768 Mon Sep 17 00:00:00 2001 From: Xavion3 Date: Thu, 18 Apr 2024 15:44:03 +1000 Subject: [PATCH] Fix mold breaker + implement neutralising gas --- src/data/ability.ts | 32 ++++++++++++++++++++++++++++---- src/data/move.ts | 26 +++++++++++++++++++++----- src/field/pokemon.ts | 18 ++++++++++++++++-- src/phases.ts | 6 ++---- 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index b30104e4b..c31847aa6 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2034,13 +2034,35 @@ export class SyncEncounterNatureAbAttr extends AbAttr { } export class MoveAbilityBypassAbAttr extends AbAttr { + private moveIgnoreFunc: (pokemon: Pokemon, move: Move) => boolean; + + constructor(moveIgnoreFunc?: (pokemon: Pokemon, move: Move) => boolean) { + super(false); + + this.moveIgnoreFunc = moveIgnoreFunc || ((pokemon, move) => true); + } + + apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (this.moveIgnoreFunc(pokemon, (args[0] as Move))) { + cancelled.value = true; + return true; + } + return false; + } +} + +export class SuppressFieldAbilitiesAbAttr extends AbAttr { constructor() { super(false); } apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - cancelled.value = true; - return true; + const ability = (args[0] as Ability); + if (!ability.hasAttr(UnsuppressableAbilityAbAttr) && !ability.hasAttr(SuppressFieldAbilitiesAbAttr)) { + cancelled.value = true; + return true; + } + return false; } } @@ -2912,7 +2934,8 @@ export function initAbilities() { .attr(PostDefendAbilitySwapAbAttr) .bypassFaint(), new Ability(Abilities.GORILLA_TACTICS, "Gorilla Tactics (N)", "Boosts the Pokémon's Attack stat but only allows the use of the first selected move.", 8), - new Ability(Abilities.NEUTRALIZING_GAS, "Neutralizing Gas (N)", "If the Pokémon with Neutralizing Gas is in the battle, the effects of all Pokémon's Abilities will be nullified or will not be triggered.", 8) + new Ability(Abilities.NEUTRALIZING_GAS, "Neutralizing Gas (P)", "If the Pokémon with Neutralizing Gas is in the battle, the effects of all Pokémon's Abilities will be nullified or will not be triggered.", 8) + .attr(SuppressFieldAbilitiesAbAttr) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(NoTransformAbilityAbAttr), @@ -3028,7 +3051,8 @@ export function initAbilities() { new Ability(Abilities.EARTH_EATER, "Earth Eater", "If hit by a Ground-type move, the Pokémon has its HP restored instead of taking damage.", 9) .attr(TypeImmunityHealAbAttr, Type.GROUND) .ignorable(), - new Ability(Abilities.MYCELIUM_MIGHT, "Mycelium Might (N)", "The Pokémon will always act more slowly when using status moves, but these moves will be unimpeded by the Ability of the target.", 9), + new Ability(Abilities.MYCELIUM_MIGHT, "Mycelium Might (P)", "The Pokémon will always act more slowly when using status moves, but these moves will be unimpeded by the Ability of the target.", 9) + .attr(MoveAbilityBypassAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS), new Ability(Abilities.MINDS_EYE, "Mind's Eye (N)", "The Pokémon ignores changes to opponents' evasiveness, its accuracy can't be lowered, and it can hit Ghost types with Normal- and Fighting-type moves.", 9) .ignorable(), new Ability(Abilities.SUPERSWEET_SYRUP, "Supersweet Syrup (N)", "A sickly sweet scent spreads across the field the first time the Pokémon enters a battle, lowering the evasiveness of opposing Pokémon.", 9), diff --git a/src/data/move.ts b/src/data/move.ts index bffd7a826..29cd2fdc5 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 { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, NoTransformAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr } from "./ability"; +import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, NoTransformAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr } from "./ability"; import { Abilities } from "./enums/abilities"; import { allAbilities } from './ability'; import { PokemonHeldItemModifier } from "../modifier/modifier"; @@ -68,7 +68,8 @@ export enum MoveFlags { POWDER_MOVE = 2048, DANCE_MOVE = 4096, WIND_MOVE = 8192, - TRIAGE_MOVE = 16384 + TRIAGE_MOVE = 16384, + IGNORE_ABILITIES = 32768 } type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; @@ -286,12 +287,24 @@ export default class Move { return this; } + ignoresAbilities(ignoresAbilities?: boolean): this { + this.setFlag(MoveFlags.IGNORE_ABILITIES, ignoresAbilities); + return this; + } + checkFlag(flag: MoveFlags, user: Pokemon, target: Pokemon): boolean { switch (flag) { case MoveFlags.MAKES_CONTACT: if (user.hasAbilityWithAttr(IgnoreContactAbAttr)) return false; break; + case MoveFlags.IGNORE_ABILITIES: + if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { + const abilityEffectsIgnored = new Utils.BooleanHolder(false); + applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, this); + if (abilityEffectsIgnored.value) + return true; + } } return !!(this.flags & flag); @@ -5405,8 +5418,10 @@ export function initMoves() { new AttackMove(Moves.SPECTRAL_THIEF, Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 7) .partial(), new AttackMove(Moves.SUNSTEEL_STRIKE, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 7) + .ignoresAbilities() .partial(), new AttackMove(Moves.MOONGEIST_BEAM, Type.GHOST, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7) + .ignoresAbilities() .partial(), new StatusMove(Moves.TEARFUL_LOOK, Type.NORMAL, -1, 20, 100, 0, 7) .attr(StatChangeAttr, BattleStat.ATK, -1) @@ -5429,15 +5444,16 @@ export function initMoves() { .partial(), new AttackMove(Moves.PHOTON_GEYSER, Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7) .attr(PhotonGeyserCategoryAttr) + .ignoresAbilities() .partial(), /* Unused */ new AttackMove(Moves.LIGHT_THAT_BURNS_THE_SKY, Type.PSYCHIC, MoveCategory.SPECIAL, 200, -1, 1, -1, 0, 7) .attr(PhotonGeyserCategoryAttr) - .partial(), + .ignoresAbilities(), new AttackMove(Moves.SEARING_SUNRAZE_SMASH, Type.STEEL, MoveCategory.PHYSICAL, 200, -1, 1, -1, 0, 7) - .partial(), + .ignoresAbilities(), new AttackMove(Moves.MENACING_MOONRAZE_MAELSTROM, Type.GHOST, MoveCategory.SPECIAL, 200, -1, 1, -1, 0, 7) - .partial(), + .ignoresAbilities(), new AttackMove(Moves.LETS_SNUGGLE_FOREVER, Type.FAIRY, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7) .partial(), new AttackMove(Moves.SPLINTERED_STORMSHARDS, Type.ROCK, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 19774d7df..1bffdf603 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -25,7 +25,7 @@ import { TempBattleStat } from '../data/temp-battle-stat'; import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from '../data/arena-tag'; import { ArenaTagType } from "../data/enums/arena-tag-type"; import { Biome } from "../data/enums/biome"; -import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from '../data/ability'; +import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr } from '../data/ability'; import { Abilities } from "#app/data/enums/abilities"; import PokemonData from '../system/pokemon-data'; import Battle, { BattlerIndex } from '../battle'; @@ -740,8 +740,21 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (passive && !this.hasPassive()) return false; const ability = (!passive ? this.getAbility() : this.getPassiveAbility()); - if (ability.isIgnorable && this.scene.arena.ignoreAbilities) + if (this.scene?.arena.ignoreAbilities && ability.isIgnorable) + return false; + if (this.summonData?.abilitySuppressed && !ability.hasAttr(UnsuppressableAbilityAbAttr)) + return false; + if (this.isOnField() && !ability.hasAttr(SuppressFieldAbilitiesAbAttr)) { + const suppressed = new Utils.BooleanHolder(false); + this.scene.getField(true).map(p => { + if (p.getAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility()) + p.getAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, false, suppressed, [ability])); + if (p.getPassiveAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility(true)) + p.getPassiveAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, true, suppressed, [ability])); + }); + if (suppressed.value) return false; + } return (this.hp || ability.isBypassFaint) && !ability.conditions.find(condition => !condition(this)); } @@ -2829,6 +2842,7 @@ export class PokemonSummonData { public disabledMove: Moves = Moves.NONE; public disabledTurns: integer = 0; public tags: BattlerTag[] = []; + public abilitySuppressed: boolean = false; public speciesForm: PokemonSpeciesForm; public fusionSpeciesForm: PokemonSpeciesForm; diff --git a/src/phases.ts b/src/phases.ts index 020cdf658..32eb3098c 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2141,10 +2141,8 @@ export class MovePhase extends BattlePhase { } if (!this.followUp) { - const abilityEffectsIgnored = new Utils.BooleanHolder(false); - this.scene.getField(true).map(p => applyAbAttrs(MoveAbilityBypassAbAttr, p, abilityEffectsIgnored)); - if (abilityEffectsIgnored.value) - this.scene.arena.setIgnoreAbilities(true); + if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) + this.scene.arena.setIgnoreAbilities(); } else { this.pokemon.turnData.hitsLeft = undefined; this.pokemon.turnData.hitCount = undefined;