diff --git a/src/data/ability.ts b/src/data/ability.ts index 4128f22e2..8b4428dd4 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1709,15 +1709,22 @@ export class PreStatChangeAbAttr extends AbAttr { export class ProtectStatAbAttr extends PreStatChangeAbAttr { private protectedStat: BattleStat; + private applyToAlly: boolean; - constructor(protectedStat?: BattleStat) { + constructor(protectedStat?: BattleStat, applyToAlly: boolean = false) { super(); this.protectedStat = protectedStat; + this.applyToAlly = applyToAlly; } applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.protectedStat === undefined || stat === this.protectedStat) { + const allyCheck = args[0]; + if ((this.protectedStat === undefined || stat === this.protectedStat) && !allyCheck) { + cancelled.value = true; + return true; + } else if ((this.protectedStat === undefined || stat === this.protectedStat) + && allyCheck && this.applyToAlly) { cancelled.value = true; return true; } @@ -1737,16 +1744,24 @@ export class PreSetStatusAbAttr extends AbAttr { } export class StatusEffectImmunityAbAttr extends PreSetStatusAbAttr { + private applyToAlly: boolean; private immuneEffects: StatusEffect[]; - constructor(...immuneEffects: StatusEffect[]) { + constructor(immuneEffects: StatusEffect[] = [], applyToAlly: boolean = false) { super(); this.immuneEffects = immuneEffects; + this.applyToAlly = applyToAlly; } applyPreSetStatus(pokemon: Pokemon, passive: boolean, effect: StatusEffect, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (!this.immuneEffects.length || this.immuneEffects.indexOf(effect) > -1) { + const allyCheck = args[1]; + if ((!this.immuneEffects.length || this.immuneEffects.indexOf(effect) > -1) && !allyCheck) { + cancelled.value = true; + return true; + } + else if((!this.immuneEffects.length || this.immuneEffects.indexOf(effect) > -1) + && allyCheck && this.applyToAlly) { cancelled.value = true; return true; } @@ -1767,15 +1782,22 @@ export class PreApplyBattlerTagAbAttr extends AbAttr { export class BattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { private immuneTagType: BattlerTagType; + private applyToAlly: boolean; - constructor(immuneTagType: BattlerTagType) { + constructor(immuneTagType: BattlerTagType, applyToAlly: boolean = false) { super(); this.immuneTagType = immuneTagType; + this.applyToAlly = applyToAlly; } applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (tag.tagType === this.immuneTagType) { + const allyCheck = args[0]; + if ((tag.tagType === this.immuneTagType) && !allyCheck) { + cancelled.value = true; + return true; + } else if ((tag.tagType === this.immuneTagType) + && allyCheck && this.applyToAlly) { cancelled.value = true; return true; } @@ -2824,7 +2846,7 @@ export function applyPostStatChangeAbAttrs(attrType: { new(...args: any[]): Post export function applyPreSetStatusAbAttrs(attrType: { new(...args: any[]): PreSetStatusAbAttr }, pokemon: Pokemon, effect: StatusEffect, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { - const simulated = args.length > 1 && args[1]; + const simulated = args.length > 2 && args[2]; return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreSetStatus(pokemon, passive, effect, cancelled, args), args, false, false, simulated); } @@ -2905,7 +2927,7 @@ export function initAbilities() { .attr(FieldPreventExplosiveMovesAbAttr) .ignorable(), new Ability(Abilities.LIMBER, 3) - .attr(StatusEffectImmunityAbAttr, StatusEffect.PARALYSIS) + .attr(StatusEffectImmunityAbAttr, [StatusEffect.PARALYSIS]) .ignorable(), new Ability(Abilities.SAND_VEIL, 3) .attr(BattleStatMultiplierAbAttr, BattleStat.EVA, 1.2) @@ -2930,13 +2952,13 @@ export function initAbilities() { new Ability(Abilities.COMPOUND_EYES, 3) .attr(BattleStatMultiplierAbAttr, BattleStat.ACC, 1.3), new Ability(Abilities.INSOMNIA, 3) - .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) + .attr(StatusEffectImmunityAbAttr, [StatusEffect.SLEEP]) .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) .ignorable(), new Ability(Abilities.COLOR_CHANGE, 3) .attr(PostDefendTypeChangeAbAttr), new Ability(Abilities.IMMUNITY, 3) - .attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) + .attr(StatusEffectImmunityAbAttr, [StatusEffect.POISON, StatusEffect.TOXIC]) .ignorable(), new Ability(Abilities.FLASH_FIRE, 3) .attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1, (pokemon: Pokemon) => !pokemon.status || pokemon.status.effect !== StatusEffect.FREEZE) @@ -3005,10 +3027,10 @@ export function initAbilities() { .attr(IntimidateImmunityAbAttr) .ignorable(), new Ability(Abilities.MAGMA_ARMOR, 3) - .attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE) + .attr(StatusEffectImmunityAbAttr, [StatusEffect.FREEZE]) .ignorable(), new Ability(Abilities.WATER_VEIL, 3) - .attr(StatusEffectImmunityAbAttr, StatusEffect.BURN) + .attr(StatusEffectImmunityAbAttr, [StatusEffect.BURN]) .ignorable(), new Ability(Abilities.MAGNET_PULL, 3) /*.attr(ArenaTrapAbAttr) @@ -3090,7 +3112,7 @@ export function initAbilities() { .attr(ArenaTrapAbAttr) .attr(DoubleBattleChanceAbAttr), new Ability(Abilities.VITAL_SPIRIT, 3) - .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) + .attr(StatusEffectImmunityAbAttr, [StatusEffect.SLEEP]) .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) .ignorable(), new Ability(Abilities.WHITE_SMOKE, 3) @@ -3365,9 +3387,13 @@ export function initAbilities() { new Ability(Abilities.AROMA_VEIL, 6) .ignorable() .unimplemented(), - new Ability(Abilities.FLOWER_VEIL, 6) + new Ability(Abilities.FLOWER_VEIL, 6) // TODO: Check against Spectral Thief once implemented, check against Toxic orb and flame orb once implemented. + // Also needs to not prevent the user from using Rest. + .conditionalAttr(p => (p.isOfType(Type.GRASS)), ProtectStatAbAttr, undefined, true) + .conditionalAttr(p => (p.isOfType(Type.GRASS)), StatusEffectImmunityAbAttr, [], true) + .conditionalAttr(p => (p.isOfType(Type.GRASS)), BattlerTagImmunityAbAttr, BattlerTagType.DROWSY, true) .ignorable() - .unimplemented(), + .partial(), new Ability(Abilities.CHEEK_POUCH, 6) .unimplemented(), new Ability(Abilities.PROTEAN, 6) @@ -3387,10 +3413,9 @@ export function initAbilities() { new Ability(Abilities.REFRIGERATE, 6) .attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.ICE, 1.2), new Ability(Abilities.SWEET_VEIL, 6) - .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) - .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) - .ignorable() - .partial(), + .attr(StatusEffectImmunityAbAttr, [StatusEffect.SLEEP], true) + .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY, true) + .ignorable(), new Ability(Abilities.STANCE_CHANGE, 6) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) @@ -3457,7 +3482,7 @@ export function initAbilities() { new Ability(Abilities.WATER_BUBBLE, 7) .attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(MoveTypePowerBoostAbAttr, Type.WATER, 1) - .attr(StatusEffectImmunityAbAttr, StatusEffect.BURN) + .attr(StatusEffectImmunityAbAttr, [StatusEffect.BURN]) .ignorable(), new Ability(Abilities.STEELWORKER, 7) .attr(MoveTypePowerBoostAbAttr, Type.STEEL), @@ -3658,7 +3683,7 @@ export function initAbilities() { .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => getPokemonMessage(pokemon, '\'s Neutralizing Gas filled the area!')) .partial(), new Ability(Abilities.PASTEL_VEIL, 8) - .attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) + .attr(StatusEffectImmunityAbAttr, [StatusEffect.POISON, StatusEffect.TOXIC], true) .ignorable(), new Ability(Abilities.HUNGER_SWITCH, 8) .attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 0 : 1) @@ -3701,7 +3726,7 @@ export function initAbilities() { .attr(PostDefendTerrainChangeAbAttr, TerrainType.GRASSY), new Ability(Abilities.THERMAL_EXCHANGE, 9) .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.FIRE && move.category !== MoveCategory.STATUS, BattleStat.ATK, 1) - .attr(StatusEffectImmunityAbAttr, StatusEffect.BURN) + .attr(StatusEffectImmunityAbAttr, [StatusEffect.BURN]) .ignorable(), new Ability(Abilities.ANGER_SHELL, 9) .attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], 1) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 63ed157b5..e08fb4657 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -435,7 +435,11 @@ class StickyWebTag extends ArenaTrapTag { activateTrap(pokemon: Pokemon): boolean { if (pokemon.isGrounded()) { const cancelled = new Utils.BooleanHolder(false); - applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); + const allyCheck = true; + applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled, !allyCheck); + if (!cancelled.value) // If Pokemon fails to protect themselves, check if ally has an ability to protect them + applyAbAttrs(ProtectStatAbAttr, pokemon.getAlly(), cancelled, allyCheck); + if (!cancelled.value) { pokemon.scene.queueMessage(`The opposing ${pokemon.name} was caught in a sticky web!`); const statLevels = new Utils.NumberHolder(-1); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8d6b11ae9..c7d9fee73 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1746,7 +1746,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const newTag = getBattlerTag(tagType, turnCount, sourceMove, sourceId); const cancelled = new Utils.BooleanHolder(false); - applyPreApplyBattlerTagAbAttrs(PreApplyBattlerTagAbAttr, this, newTag, cancelled); + const allyCheck = true; + applyPreApplyBattlerTagAbAttrs(PreApplyBattlerTagAbAttr, this, newTag, cancelled, !allyCheck); + if (!cancelled.value) // If the tag was not cancelled, check if Ally has an ability that provides immunity to allies + applyPreApplyBattlerTagAbAttrs(PreApplyBattlerTagAbAttr, this.getAlly(), newTag, cancelled, allyCheck); if (!cancelled.value && newTag.canAdd(this)) { this.summonData.tags.push(newTag); @@ -2106,7 +2109,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } const cancelled = new Utils.BooleanHolder(false); - applyPreSetStatusAbAttrs(StatusEffectImmunityAbAttr, this, effect, cancelled, quiet); + const allyCheck = true; + applyPreSetStatusAbAttrs(StatusEffectImmunityAbAttr, this, effect, cancelled, quiet, !allyCheck); + if (!cancelled.value) {// If Pokemon fails to protect themselves, check if ally has an ability to protect them + applyPreSetStatusAbAttrs(StatusEffectImmunityAbAttr, this.getAlly(), effect, cancelled, quiet, allyCheck); + } if (cancelled.value) return false; diff --git a/src/phases.ts b/src/phases.ts index a802582cb..51d620aa2 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2761,8 +2761,11 @@ export class StatChangePhase extends PokemonPhase { if (!this.selfTarget && this.levels < 0) this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled); + const allyCheck = true; if (!cancelled.value && !this.selfTarget && this.levels < 0) - applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled); + applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled, !allyCheck); + if (!cancelled.value && !this.selfTarget) // If Pokemon fails to protect themselves, check if ally has an ability to protect them + applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon().getAlly(), stat, cancelled, allyCheck); return !cancelled.value; });