From fb238321d6543debeb77650b6171c19568c8cc0b Mon Sep 17 00:00:00 2001 From: PrabbyDD Date: Wed, 23 Oct 2024 21:19:21 -0700 Subject: [PATCH] implementing friend guard ability --- src/data/ability.ts | 18 +++++++- src/field/pokemon.ts | 8 +++- src/test/abilities/friend_guard.test.ts | 58 +++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 src/test/abilities/friend_guard.test.ts diff --git a/src/data/ability.ts b/src/data/ability.ts index 33f6e0522f7..cf892777ead 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -330,6 +330,20 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr { } } +export class FriendGuardAbAttr extends PreDefendAbAttr { + private damageMultiplier: number; + + constructor(damageMultiplier: number) { + super(); + this.damageMultiplier = damageMultiplier; + } + + applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { + (args[0] as Utils.NumberHolder).value = Utils.toDmgValue((args[0] as Utils.NumberHolder).value * this.damageMultiplier); + return true; + } +} + export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultiplierAbAttr { constructor(moveType: Type, damageMultiplier: number) { super((target, user, move) => user.getMoveType(move) === moveType, damageMultiplier); @@ -5260,8 +5274,8 @@ export function initAbilities() { new Ability(Abilities.HEALER, 5) .conditionalAttr(pokemon => pokemon.getAlly() && Utils.randSeedInt(10) < 3, PostTurnResetStatusAbAttr, true), new Ability(Abilities.FRIEND_GUARD, 5) - .ignorable() - .unimplemented(), + .attr(FriendGuardAbAttr, .25) + .ignorable(), new Ability(Abilities.WEAK_ARMOR, 5) .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, Stat.DEF, -1) .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, Stat.SPD, 2), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0ee879ebf97..81f43846dc4 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -22,7 +22,7 @@ import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/ import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags"; import { WeatherType } from "#app/data/weather"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; -import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "#app/data/ability"; +import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, FriendGuardAbAttr } from "#app/data/ability"; import PokemonData from "#app/system/pokemon-data"; import { BattlerIndex } from "#app/battle"; import { Mode } from "#app/ui/ui"; @@ -2659,9 +2659,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.applyModifiers(EnemyDamageReducerModifier, false, damage); } + /** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */ if (!ignoreAbility) { applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, simulated, damage); + + /** Additionally apply friend guard damage reduction if ally has it. */ + if (this.scene.currentBattle.double && this.getAlly().isActive()) { + applyPreDefendAbAttrs(FriendGuardAbAttr, this.getAlly(), source, move, cancelled, simulated, damage); + } } // This attribute may modify damage arbitrarily, so be careful about changing its order of application. diff --git a/src/test/abilities/friend_guard.test.ts b/src/test/abilities/friend_guard.test.ts new file mode 100644 index 00000000000..3c03b67420d --- /dev/null +++ b/src/test/abilities/friend_guard.test.ts @@ -0,0 +1,58 @@ +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { Abilities } from "#enums/abilities"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { BattlerIndex } from "#app/battle"; +import { PlayerPokemon } from "#app/field/pokemon"; + +describe("Moves - Friend Guard", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + let hp1: number; + let hp2: number; + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("double") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([ Moves.TACKLE, Moves.SPLASH ]) + .enemySpecies(Species.SHUCKLE) + .moveset([ Moves.SPLASH ]); + }); + + it("first part of test, getting hp without friend guard", async () => { + await game.classicMode.startBattle([ Species.BULBASAUR, Species.BULBASAUR ]); + const party = game.scene.getParty()! as PlayerPokemon[]; + game.move.select(Moves.SPLASH); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + hp1 = party[0].hp; + }); + + it("second part of test, getting hp with friend guard and comparing", async () => { + game.override.ability(Abilities.FRIEND_GUARD); + await game.classicMode.startBattle([ Species.BULBASAUR, Species.BULBASAUR ]); + const party = game.scene.getParty()! as PlayerPokemon[]; + game.move.select(Moves.SPLASH); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + hp2 = party[0].hp; + expect(hp2).toBeGreaterThan(hp1); + }); +});