From 06c3611d06afd9b37a2d4341c3732ea183eb8c46 Mon Sep 17 00:00:00 2001 From: Brandon Rodgers Date: Tue, 14 May 2024 14:00:37 -0400 Subject: [PATCH] Ability Corrosion (#744) * Ability Corrosion Implemented Corrosion Partially. Tested against: - Poison Powder - Toxic - Dire Claw - Sludge Bomb - Psycho Shift They all work as expected Missing ability Magic Bounce to test against. * Added TSDoc Documentation Added documentation to the new IgnoreTypeStatusEffectImunnityAbAttr and added comments to the checks for this ability attribute. * Added More Documentation Add comment into Phases for what sourcePokemon is for. Renamed source to sourcePokemon onto trySetStatus and canSetStatus. Added TSDoc head for what sourcePokemon is and anything else I am aware of what they are used for. * Removed unfinished TSDoc Removed TSDoc headers due to not having enough understanding to fill out all of the parameters * Fix Formatting and Reorder Parameters * Update arena-tag.ts * Update phases.ts * Update ability.ts Added access modifiers to my class and the class I compared to. --------- Co-authored-by: Benjamin Odom --- src/data/ability.ts | 37 +++++++++++++++++++++++++++++++------ src/data/arena-tag.ts | 2 +- src/data/battler-tags.ts | 2 +- src/data/move.ts | 12 ++++++------ src/field/pokemon.ts | 35 ++++++++++++++++++++++++++++------- src/phases.ts | 6 ++++-- 6 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 22867ee131f..0ff03ed6776 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -715,7 +715,7 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { if (move.getMove().checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; - return attacker.trySetStatus(effect, true); + return attacker.trySetStatus(effect, true, pokemon); } return false; @@ -1177,7 +1177,7 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean { if (pokemon != attacker && (!this.contactRequired || move.getMove().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); + return attacker.trySetStatus(effect, true, pokemon); } return false; @@ -2648,8 +2648,8 @@ export class NoFusionAbilityAbAttr extends AbAttr { } export class IgnoreTypeImmunityAbAttr extends AbAttr { - defenderType: Type; - allowedMoveTypes: Type[]; + private defenderType: Type; + private allowedMoveTypes: Type[]; constructor(defenderType: Type, allowedMoveTypes: Type[]) { super(true); @@ -2666,6 +2666,30 @@ export class IgnoreTypeImmunityAbAttr extends AbAttr { } } +/** + * Ignores the type immunity to Status Effects of the defender if the defender is of a certain type + */ +export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr { + private statusEffect: StatusEffect[]; + private defenderType: Type[]; + + constructor(statusEffect: StatusEffect[], defenderType: Type[]) { + super(true); + + this.statusEffect = statusEffect; + this.defenderType = defenderType; + } + + apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (this.statusEffect.includes(args[0] as StatusEffect) && this.defenderType.includes(args[1] as Type)) { + cancelled.value = true; + return true; + } + + return false; + } +} + function applyAbAttrsInternal(attrType: { new(...args: any[]): TAttr }, pokemon: Pokemon, applyFunc: AbAttrApplyFunc, args: any[], isAsync: boolean = false, showAbilityInstant: boolean = false, quiet: boolean = false, passive: boolean = false): Promise { return new Promise(resolve => { @@ -3489,8 +3513,9 @@ export function initAbilities() { .attr(UnsuppressableAbilityAbAttr) .attr(NoFusionAbilityAbAttr) .partial(), - new Ability(Abilities.CORROSION, 7) - .unimplemented(), + new Ability(Abilities.CORROSION, 7) // TODO: Test Corrosion against Magic Bounce once it is implemented + .attr(IgnoreTypeStatusEffectImmunityAbAttr, [StatusEffect.POISON, StatusEffect.TOXIC], [Type.STEEL, Type.POISON]) + .partial(), new Ability(Abilities.COMATOSE, 7) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 8942cfe4b3e..63ed157b597 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -316,7 +316,7 @@ class ToxicSpikesTag extends ArenaTrapTag { } } else if (!pokemon.status) { const toxic = this.layers > 1; - if (pokemon.trySetStatus(!toxic ? StatusEffect.POISON : StatusEffect.TOXIC, true, null, `the ${this.getMoveName()}`)) + if (pokemon.trySetStatus(!toxic ? StatusEffect.POISON : StatusEffect.TOXIC, true, null, 0, `the ${this.getMoveName()}`)) return true; } } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index ba00b09067c..257f56d46ed 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -819,7 +819,7 @@ export class ContactPoisonProtectedTag extends ProtectedTag { const effectPhase = pokemon.scene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { const attacker = effectPhase.getPokemon(); - attacker.trySetStatus(StatusEffect.POISON, true); + attacker.trySetStatus(StatusEffect.POISON, true, pokemon); } } diff --git a/src/data/move.ts b/src/data/move.ts index 6a6bee468a9..8b21330900c 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1156,13 +1156,13 @@ export class StatusEffectAttr extends MoveEffectAttr { return false; } if (!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0)) - return pokemon.trySetStatus(this.effect, true, this.cureTurn); + return pokemon.trySetStatus(this.effect, true, user, this.cureTurn); } return false; } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true) ? Math.floor(move.chance * -0.1) : 0; + return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(move.chance * -0.1) : 0; } } @@ -1181,7 +1181,7 @@ export class MultiStatusEffectAttr extends StatusEffectAttr { } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true) ? Math.floor(move.chance * -0.1) : 0; + return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(move.chance * -0.1) : 0; } } @@ -1197,7 +1197,7 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr { return false; } if (!target.status || (target.status.effect === statusToApply && move.chance < 0)) { - var statusAfflictResult = target.trySetStatus(statusToApply, true); + var statusAfflictResult = target.trySetStatus(statusToApply, true, user); if (statusAfflictResult) { user.scene.queueMessage(getPokemonMessage(user, getStatusEffectHealText(user.status.effect))); user.resetStatus(); @@ -1210,7 +1210,7 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr { } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(user.status?.effect, true) ? Math.floor(move.chance * -0.1) : 0; + return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(user.status?.effect, true, false, user) ? Math.floor(move.chance * -0.1) : 0; } } @@ -5226,7 +5226,7 @@ export function initMoves() { || user.status?.effect === StatusEffect.TOXIC || user.status?.effect === StatusEffect.PARALYSIS || user.status?.effect === StatusEffect.SLEEP) - && target.canSetStatus(user.status?.effect) + && target.canSetStatus(user.status?.effect, false, false, user) ), new AttackMove(Moves.TRUMP_CARD, Type.NORMAL, MoveCategory.SPECIAL, -1, -1, 5, -1, 0, 4) .makesContact() diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index fd1613df1c7..573fc907bc3 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -27,7 +27,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, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr } 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, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr } from '../data/ability'; import { Abilities } from "#app/data/enums/abilities"; import PokemonData from '../system/pokemon-data'; import Battle, { BattlerIndex } from '../battle'; @@ -2024,7 +2024,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.gender !== Gender.GENDERLESS && pokemon.gender === (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE); } - canSetStatus(effect: StatusEffect, quiet: boolean = false, overrideStatus: boolean = false): boolean { + canSetStatus(effect: StatusEffect, quiet: boolean = false, overrideStatus: boolean = false, sourcePokemon: Pokemon = null): boolean { if (effect !== StatusEffect.FAINT) { if (overrideStatus ? this.status?.effect === effect : this.status) return false; @@ -2032,11 +2032,32 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } + const types = this.getTypes(true, true); + switch (effect) { case StatusEffect.POISON: case StatusEffect.TOXIC: - if (this.isOfType(Type.POISON) || this.isOfType(Type.STEEL)) - return false; + // Check if the Pokemon is immune to Poison/Toxic or if the source pokemon is canceling the immunity + let poisonImmunity = types.map(defType => { + // Check if the Pokemon is not immune to Poison/Toxic + if (defType !== Type.POISON && defType !== Type.STEEL) + return false; + + // Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity + const cancelImmunity = new Utils.BooleanHolder(false); + if (sourcePokemon) { + applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, effect, defType); + if (cancelImmunity.value) + return false; + } + + return true; + }) + + if (this.isOfType(Type.POISON) || this.isOfType(Type.STEEL)) { + if (poisonImmunity.includes(true)) + return false; + } break; case StatusEffect.PARALYSIS: if (this.isOfType(Type.ELECTRIC)) @@ -2065,12 +2086,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return true; } - trySetStatus(effect: StatusEffect, asPhase: boolean = false, cureTurn: integer = 0, sourceText: string = null): boolean { - if (!this.canSetStatus(effect, asPhase)) + trySetStatus(effect: StatusEffect, asPhase: boolean = false, sourcePokemon: Pokemon = null, cureTurn: integer = 0, sourceText: string = null): boolean { + if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) return false; if (asPhase) { - this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText)); + this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText, sourcePokemon)); return true; } diff --git a/src/phases.ts b/src/phases.ts index 271e5350c82..8aa179d359a 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2931,19 +2931,21 @@ export class ObtainStatusEffectPhase extends PokemonPhase { private statusEffect: StatusEffect; private cureTurn: integer; private sourceText: string; + private sourcePokemon: Pokemon; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect: StatusEffect, cureTurn?: integer, sourceText?: string) { + constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect: StatusEffect, cureTurn?: integer, sourceText?: string, sourcePokemon?: Pokemon) { super(scene, battlerIndex); this.statusEffect = statusEffect; this.cureTurn = cureTurn; this.sourceText = sourceText; + this.sourcePokemon = sourcePokemon; // For tracking which Pokemon caused the status effect } start() { const pokemon = this.getPokemon(); if (!pokemon.status) { - if (pokemon.trySetStatus(this.statusEffect)) { + if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) { if (this.cureTurn) pokemon.status.cureTurn = this.cureTurn; pokemon.updateInfo(true);