diff --git a/src/data/ability.ts b/src/data/ability.ts index f2220c850dc..e051d8d43ab 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -712,7 +712,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, 0, null, pokemon); } return false; @@ -1138,7 +1138,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, 0, null, pokemon); } return false; @@ -2560,6 +2560,27 @@ export class IgnoreTypeImmunityAbAttr extends AbAttr { } } +export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr { + statusEffect: StatusEffect[]; + 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 => { @@ -3378,8 +3399,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/battler-tags.ts b/src/data/battler-tags.ts index ba00b09067c..07120ed1995 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, 0, null, pokemon); } } diff --git a/src/data/move.ts b/src/data/move.ts index 6e4e3f60fcb..a2467226cfb 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1120,13 +1120,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, this.cureTurn, null, user); } 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; } } @@ -1145,7 +1145,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; } } @@ -1161,7 +1161,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, 0, null, user); if (statusAfflictResult) { user.scene.queueMessage(getPokemonMessage(user, getStatusEffectHealText(user.status.effect))); user.resetStatus(); @@ -1174,7 +1174,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; } } @@ -5134,7 +5134,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 8afff1b2724..afd1ae8c1cb 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 } 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, IgnoreTypeStatusEffectImmunityAbAttr } from '../data/ability'; import { Abilities } from "#app/data/enums/abilities"; import PokemonData from '../system/pokemon-data'; import Battle, { BattlerIndex } from '../battle'; @@ -1889,7 +1889,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, source: Pokemon = null): boolean { if (effect !== StatusEffect.FAINT) { if (overrideStatus ? this.status?.effect === effect : this.status) return false; @@ -1897,11 +1897,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; + let poisonImmunity = types.map(defType => { + const cancelImmunity = new Utils.BooleanHolder(false); + if (defType !== Type.POISON && defType !== Type.STEEL) { + return false; + } + + if (source) { + applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, source, cancelImmunity, effect, defType); + if (cancelImmunity.value) + return false; + else + return true; + } + else { + 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)) @@ -1930,12 +1951,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, cureTurn: integer = 0, sourceText: string = null, source: Pokemon = null): boolean { + if (!this.canSetStatus(effect, asPhase, false, source)) 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, source)); return true; } diff --git a/src/phases.ts b/src/phases.ts index 6144fe47a86..f1a9e6b06ba 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2918,19 +2918,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; } start() { const pokemon = this.getPokemon(); if (!pokemon.status) { - if (pokemon.trySetStatus(this.statusEffect)) { + if (pokemon.trySetStatus(this.statusEffect, false, 0, null, this.sourcePokemon)) { if (this.cureTurn) pokemon.status.cureTurn = this.cureTurn; pokemon.updateInfo(true);