From a537113c8f186de12fd177b0d3fed7e92c8026b1 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:45:56 -0700 Subject: [PATCH 1/4] Re-add lost i18n strings (#4024) --- src/locales/en/arena-flyout.json | 5 +++-- src/locales/en/arena-tag.json | 8 +++++++- src/locales/en/move-trigger.json | 5 +++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/locales/en/arena-flyout.json b/src/locales/en/arena-flyout.json index 141ed4f743d..043d4127eb8 100644 --- a/src/locales/en/arena-flyout.json +++ b/src/locales/en/arena-flyout.json @@ -39,5 +39,6 @@ "matBlock": "Mat Block", "craftyShield": "Crafty Shield", "tailwind": "Tailwind", - "happyHour": "Happy Hour" -} + "happyHour": "Happy Hour", + "safeguard": "Safeguard" +} \ No newline at end of file diff --git a/src/locales/en/arena-tag.json b/src/locales/en/arena-tag.json index ef0b55b691b..d8fed386b24 100644 --- a/src/locales/en/arena-tag.json +++ b/src/locales/en/arena-tag.json @@ -47,5 +47,11 @@ "tailwindOnRemovePlayer": "Your team's Tailwind petered out!", "tailwindOnRemoveEnemy": "The opposing team's Tailwind petered out!", "happyHourOnAdd": "Everyone is caught up in the happy atmosphere!", - "happyHourOnRemove": "The atmosphere returned to normal." + "happyHourOnRemove": "The atmosphere returned to normal.", + "safeguardOnAdd": "The whole field is cloaked in a mystical veil!", + "safeguardOnAddPlayer": "Your team cloaked itself in a mystical veil!", + "safeguardOnAddEnemy": "The opposing team cloaked itself in a mystical veil!", + "safeguardOnRemove": "The field is no longer protected by Safeguard!", + "safeguardOnRemovePlayer": "Your team is no longer protected by Safeguard!", + "safeguardOnRemoveEnemy": "The opposing team is no longer protected by Safeguard!" } \ No newline at end of file diff --git a/src/locales/en/move-trigger.json b/src/locales/en/move-trigger.json index 110d3dc68c7..e70fb9dcfb7 100644 --- a/src/locales/en/move-trigger.json +++ b/src/locales/en/move-trigger.json @@ -65,5 +65,6 @@ "suppressAbilities": "{{pokemonName}}'s ability\nwas suppressed!", "revivalBlessing": "{{pokemonName}} was revived!", "swapArenaTags": "{{pokemonName}} swapped the battle effects affecting each side of the field!", - "exposedMove": "{{pokemonName}} identified\n{{targetPokemonName}}!" -} + "exposedMove": "{{pokemonName}} identified\n{{targetPokemonName}}!", + "safeguard": "{{targetName}} is protected by Safeguard!" +} \ No newline at end of file From 8835ae0299b53520406ef5b22faaa2ebd6f4fc3e Mon Sep 17 00:00:00 2001 From: chaosgrimmon <31082757+chaosgrimmon@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:46:33 -0400 Subject: [PATCH 2/4] [Sprite] Index egg skip UI (#4027) * [Sprite] Index egg skip UI * [Sprite] Index egg skip legacy UI --- public/images/ui/egg_summary_bg.png | Bin 2160 -> 1064 bytes public/images/ui/icon_egg_move.png | Bin 237 -> 179 bytes public/images/ui/legacy/egg_summary_bg.png | Bin 2160 -> 1064 bytes public/images/ui/legacy/icon_egg_move.png | Bin 237 -> 179 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/ui/egg_summary_bg.png b/public/images/ui/egg_summary_bg.png index 27e367212aaae20dc43f3f03c9fc122b9d99e8fe..658f5df0e96bd901d105690d73500b965788df5c 100644 GIT binary patch literal 1064 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 diff --git a/public/images/ui/icon_egg_move.png b/public/images/ui/icon_egg_move.png index 6af186e9b0c21a3952b80fc00ed2a7494cee18eb..a5b0bff4ace05ee65752673f6ecb0ff05ad415c8 100644 GIT binary patch delta 162 zcmaFMxS4T+WIZzj1H-O2_WeMLu{g-xiDBJ2nU_G0Xn;?ME0C5JleRXtPAcduuAjPe z&CdV-|A+Pm%>YWUmjw9*GXVKOu(W>JZ=jH+r;B3<$4uKEPd)|%4kp$s|Lu29(`z}b zq9dqm)OW(?kV>l8Y{gJXbzN!c+ikn=?*E-8&FIthyC#8A*OB4evfPeaKvNhzUHx3v IIVCg!08Ak}jQ{`u delta 220 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 diff --git a/public/images/ui/legacy/icon_egg_move.png b/public/images/ui/legacy/icon_egg_move.png index 6af186e9b0c21a3952b80fc00ed2a7494cee18eb..a5b0bff4ace05ee65752673f6ecb0ff05ad415c8 100644 GIT binary patch delta 162 zcmaFMxS4T+WIZzj1H-O2_WeMLu{g-xiDBJ2nU_G0Xn;?ME0C5JleRXtPAcduuAjPe z&CdV-|A+Pm%>YWUmjw9*GXVKOu(W>JZ=jH+r;B3<$4uKEPd)|%4kp$s|Lu29(`z}b zq9dqm)OW(?kV>l8Y{gJXbzN!c+ikn=?*E-8&FIthyC#8A*OB4evfPeaKvNhzUHx3v IIVCg!08Ak}jQ{`u delta 220 zcmV<203-ji0qp^h8Gi-<0050L&%FQu00DDSM?wIu&K&6g005^+L_t(2Q)6U61J=gY z|3N}W&mLt&mV$F&3M_o0!15=yEMs`^_yNPyS5LuezywHx^_&7|vb3165Xl%w93Qje!waVNXLA zLrKFlutH=uSRuL_=k}L@6($#U;ddim&%itcq9Zb^z$SsL0|AuaK@M0@2p}tf$pHZG W(p91VZ8D_*0000 Date: Wed, 4 Sep 2024 16:16:47 -0700 Subject: [PATCH 3/4] I have a brain made of cheese!!! (#4028) Co-authored-by: frutescens --- src/battle-scene.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index c8100e0d3b9..d4c33663c14 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2755,12 +2755,18 @@ export default class BattleScene extends SceneBase { keys.push("pkmn__" + p.species.getSpriteId(p.gender === Gender.FEMALE, p.species.formIndex, p.shiny, p.variant)); keys.push("pkmn__" + p.species.getSpriteId(p.gender === Gender.FEMALE, p.species.formIndex, p.shiny, p.variant, true)); keys.push("cry/" + p.species.getCryKey(p.species.formIndex)); + if (p.fusionSpecies && p.getSpeciesForm() !== p.getFusionSpeciesForm()) { + keys.push("cry/"+p.getFusionSpeciesForm().getCryKey(p.fusionSpecies.formIndex)); + } }); // enemyParty has to be operated on separately from playerParty because playerPokemon =/= enemyPokemon const enemyParty = this.getEnemyParty(); enemyParty.forEach(p => { keys.push(p.species.getSpriteKey(p.gender === Gender.FEMALE, p.species.formIndex, p.shiny, p.variant)); keys.push("cry/" + p.species.getCryKey(p.species.formIndex)); + if (p.fusionSpecies && p.getSpeciesForm() !== p.getFusionSpeciesForm()) { + keys.push("cry/"+p.getFusionSpeciesForm().getCryKey(p.fusionSpecies.formIndex)); + } }); return keys; } From f3ced7e81406b1464487b6d0b059809c0845e705 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:49:01 -0700 Subject: [PATCH 4/4] [Bug] Fix inconsistencies with move type resolution for some ability triggers (#3988) * Fix inconsistencies with ability triggers on variable-type moves * Fix aura effects not accounting for the move user * Fix Wonder Guard evaluating move type as if the defender used the move * Some additional test coverage for move-type-changing effects --- src/data/ability.ts | 43 ++++++++++++++---------- src/data/move.ts | 3 +- src/test/abilities/disguise.test.ts | 19 +++++++++++ src/test/abilities/steely_spirit.test.ts | 30 +++++++++++++---- 4 files changed, 68 insertions(+), 27 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 16ae7a2b2d2..925a7efb79b 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -310,7 +310,7 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr { export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultiplierAbAttr { constructor(moveType: Type, damageMultiplier: number) { - super((user, target, move) => move.type === moveType, damageMultiplier); + super((target, user, move) => user.getMoveType(move) === moveType, damageMultiplier); } } @@ -455,7 +455,7 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { } applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(pokemon.getMoveType(move), attacker) < 2) { + if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker) < 2) { cancelled.value = true; // Suppresses "No Effect" message (args[0] as Utils.NumberHolder).value = 0; return true; @@ -1462,7 +1462,7 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr { export class MoveTypePowerBoostAbAttr extends MovePowerBoostAbAttr { constructor(boostedType: Type, powerMultiplier?: number) { - super((pokemon, defender, move) => move.type === boostedType, powerMultiplier || 1.5); + super((pokemon, defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5); } } @@ -1546,7 +1546,7 @@ export class PreAttackFieldMoveTypePowerBoostAbAttr extends FieldMovePowerBoostA * @param powerMultiplier - The multiplier to apply to the move's power, defaults to 1.5 if not provided. */ constructor(boostedType: Type, powerMultiplier?: number) { - super((pokemon, defender, move) => move.type === boostedType, powerMultiplier || 1.5); + super((pokemon, defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5); } } @@ -5100,9 +5100,9 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.TINTED_LENS, 4) //@ts-ignore - .attr(DamageBoostAbAttr, 2, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) <= 0.5), // TODO: fix TS issues + .attr(DamageBoostAbAttr, 2, (user, target, move) => target?.getMoveEffectiveness(user, move) <= 0.5), // TODO: fix TS issues new Ability(Abilities.FILTER, 4) - .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75) + .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75) .ignorable(), new Ability(Abilities.SLOW_START, 4) .attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.SLOW_START, 5), @@ -5118,7 +5118,7 @@ export function initAbilities() { .attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL, WeatherType.SNOW) .partial(), // Healing not blocked by Heal Block new Ability(Abilities.SOLID_ROCK, 4) - .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75) + .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75) .ignorable(), new Ability(Abilities.SNOW_WARNING, 4) .attr(PostSummonWeatherChangeAbAttr, WeatherType.SNOW) @@ -5236,10 +5236,13 @@ export function initAbilities() { new Ability(Abilities.MOXIE, 5) .attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1), new Ability(Abilities.JUSTIFIED, 5) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.type === Type.DARK && move.category !== MoveCategory.STATUS, Stat.ATK, 1), + .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.DARK && move.category !== MoveCategory.STATUS, Stat.ATK, 1), new Ability(Abilities.RATTLED, 5) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS && (move.type === Type.DARK || move.type === Type.BUG || - move.type === Type.GHOST), Stat.SPD, 1) + .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => { + const moveType = user.getMoveType(move); + return move.category !== MoveCategory.STATUS + && (moveType === Type.DARK || moveType === Type.BUG || moveType === Type.GHOST); + }, Stat.SPD, 1) .attr(PostIntimidateStatStageChangeAbAttr, [Stat.SPD], 1), new Ability(Abilities.MAGIC_BOUNCE, 5) .ignorable() @@ -5313,7 +5316,7 @@ export function initAbilities() { .attr(UnsuppressableAbilityAbAttr) .attr(NoFusionAbilityAbAttr), new Ability(Abilities.GALE_WINGS, 6) - .attr(ChangeMovePriorityAbAttr, (pokemon, move) => pokemon.isFullHp() && move.type === Type.FLYING, 1), + .attr(ChangeMovePriorityAbAttr, (pokemon, move) => pokemon.isFullHp() && pokemon.getMoveType(move) === Type.FLYING, 1), new Ability(Abilities.MEGA_LAUNCHER, 6) .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.PULSE_MOVE), 1.5), new Ability(Abilities.GRASS_PELT, 6) @@ -5368,7 +5371,7 @@ export function initAbilities() { .condition(getSheerForceHitDisableAbCondition()) .unimplemented(), new Ability(Abilities.WATER_COMPACTION, 7) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.type === Type.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2), + .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2), new Ability(Abilities.MERCILESS, 7) .attr(ConditionalCritAbAttr, (user, target, move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON), new Ability(Abilities.SHIELDS_DOWN, 7) @@ -5424,7 +5427,7 @@ export function initAbilities() { .attr(NoFusionAbilityAbAttr) // Add BattlerTagType.DISGUISE if the pokemon is in its disguised form .conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false) - .attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getAttackTypeEffectiveness(move.type, user) > 0, 0, BattlerTagType.DISGUISE, + .attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE, (pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }), (pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8)) .attr(PostBattleInitFormChangeAbAttr, () => 0) @@ -5469,7 +5472,7 @@ export function initAbilities() { .attr(AllyMoveCategoryPowerBoostAbAttr, [MoveCategory.SPECIAL], 1.3), new Ability(Abilities.FLUFFY, 7) .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 0.5) - .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.type === Type.FIRE, 2) + .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => user.getMoveType(move) === Type.FIRE, 2) .ignorable(), new Ability(Abilities.DAZZLING, 7) .attr(FieldPriorityMoveImmunityAbAttr) @@ -5519,10 +5522,10 @@ export function initAbilities() { new Ability(Abilities.SHADOW_SHIELD, 7) .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.isFullHp(), 0.5), new Ability(Abilities.PRISM_ARMOR, 7) - .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75), + .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75), new Ability(Abilities.NEUROFORCE, 7) //@ts-ignore - .attr(MovePowerBoostAbAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 1.25), // TODO: fix TS issues + .attr(MovePowerBoostAbAttr, (user, target, move) => target?.getMoveEffectiveness(user, move) >= 2, 1.25), // TODO: fix TS issues new Ability(Abilities.INTREPID_SWORD, 8) .attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], 1, true) .condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)), @@ -5553,7 +5556,11 @@ export function initAbilities() { new Ability(Abilities.STALWART, 8) .attr(BlockRedirectAbAttr), new Ability(Abilities.STEAM_ENGINE, 8) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => (move.type === Type.FIRE || move.type === Type.WATER) && move.category !== MoveCategory.STATUS, Stat.SPD, 6), + .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => { + const moveType = user.getMoveType(move); + return move.category !== MoveCategory.STATUS + && (moveType === Type.FIRE || moveType === Type.WATER); + }, Stat.SPD, 6), new Ability(Abilities.PUNK_ROCK, 8) .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.SOUND_BASED), 1.3) .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.SOUND_BASED), 0.5) @@ -5653,7 +5660,7 @@ export function initAbilities() { new Ability(Abilities.SEED_SOWER, 9) .attr(PostDefendTerrainChangeAbAttr, TerrainType.GRASSY), new Ability(Abilities.THERMAL_EXCHANGE, 9) - .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.type === Type.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1) + .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1) .attr(StatusEffectImmunityAbAttr, StatusEffect.BURN) .ignorable(), new Ability(Abilities.ANGER_SHELL, 9) diff --git a/src/data/move.ts b/src/data/move.ts index 14d7addead0..ddf043c554d 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -761,8 +761,7 @@ export default class Move implements Localizable { .flat(), ); for (const aura of fieldAuras) { - // The only relevant values are `move` and the `power` holder - aura.applyPreAttack(null, null, simulated, null, this, [power]); + aura.applyPreAttack(source, null, simulated, target, this, [power]); } const alliedField: Pokemon[] = source instanceof PlayerPokemon ? source.scene.getPlayerField() : source.scene.getEnemyField(); diff --git a/src/test/abilities/disguise.test.ts b/src/test/abilities/disguise.test.ts index f7c45e91724..ef145262954 100644 --- a/src/test/abilities/disguise.test.ts +++ b/src/test/abilities/disguise.test.ts @@ -1,4 +1,5 @@ import { toDmgValue } from "#app/utils"; +import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { StatusEffect } from "#app/data/status-effect"; @@ -205,4 +206,22 @@ describe("Abilities - Disguise", () => { expect(game.scene.getCurrentPhase()?.constructor.name).toBe("CommandPhase"); expect(game.scene.currentBattle.waveIndex).toBe(2); }, TIMEOUT); + + it("activates when Aerilate circumvents immunity to the move's base type", async () => { + game.override.ability(Abilities.AERILATE); + game.override.moveset([Moves.TACKLE]); + + await game.classicMode.startBattle(); + + const mimikyu = game.scene.getEnemyPokemon()!; + const maxHp = mimikyu.getMaxHp(); + const disguiseDamage = toDmgValue(maxHp / 8); + + game.move.select(Moves.TACKLE); + + await game.phaseInterceptor.to("MoveEndPhase"); + + expect(mimikyu.formIndex).toBe(bustedForm); + expect(mimikyu.hp).toBe(maxHp - disguiseDamage); + }, TIMEOUT); }); diff --git a/src/test/abilities/steely_spirit.test.ts b/src/test/abilities/steely_spirit.test.ts index c632d0be777..7aaa0a42ae3 100644 --- a/src/test/abilities/steely_spirit.test.ts +++ b/src/test/abilities/steely_spirit.test.ts @@ -1,7 +1,6 @@ import { allAbilities } from "#app/data/ability"; import { allMoves } from "#app/data/move"; import { Abilities } from "#app/enums/abilities"; -import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; @@ -37,7 +36,7 @@ describe("Abilities - Steely Spirit", () => { }); it("increases Steel-type moves' power used by the user and its allies by 50%", async () => { - await game.startBattle([Species.PIKACHU, Species.SHUCKLE]); + await game.classicMode.startBattle([Species.PIKACHU, Species.SHUCKLE]); const boostSource = game.scene.getPlayerField()[1]; const enemyToCheck = game.scene.getEnemyPokemon()!; @@ -47,13 +46,13 @@ describe("Abilities - Steely Spirit", () => { game.move.select(moveToCheck, 0, enemyToCheck.getBattlerIndex()); game.move.select(Moves.SPLASH, 1); - await game.phaseInterceptor.to(MoveEffectPhase); + await game.phaseInterceptor.to("MoveEffectPhase"); expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower * steelySpiritMultiplier); }); it("stacks if multiple users with this ability are on the field.", async () => { - await game.startBattle([Species.PIKACHU, Species.PIKACHU]); + await game.classicMode.startBattle([Species.PIKACHU, Species.PIKACHU]); const enemyToCheck = game.scene.getEnemyPokemon()!; game.scene.getPlayerField().forEach(p => { @@ -64,13 +63,13 @@ describe("Abilities - Steely Spirit", () => { game.move.select(moveToCheck, 0, enemyToCheck.getBattlerIndex()); game.move.select(moveToCheck, 1, enemyToCheck.getBattlerIndex()); - await game.phaseInterceptor.to(MoveEffectPhase); + await game.phaseInterceptor.to("MoveEffectPhase"); expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower * Math.pow(steelySpiritMultiplier, 2)); }); it("does not take effect when suppressed", async () => { - await game.startBattle([Species.PIKACHU, Species.SHUCKLE]); + await game.classicMode.startBattle([Species.PIKACHU, Species.SHUCKLE]); const boostSource = game.scene.getPlayerField()[1]; const enemyToCheck = game.scene.getEnemyPokemon()!; @@ -84,8 +83,25 @@ describe("Abilities - Steely Spirit", () => { game.move.select(moveToCheck, 0, enemyToCheck.getBattlerIndex()); game.move.select(Moves.SPLASH, 1); - await game.phaseInterceptor.to(MoveEffectPhase); + await game.phaseInterceptor.to("MoveEffectPhase"); expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower); }); + + it("affects variable-type moves if their resolved type is Steel", async () => { + game.override + .ability(Abilities.STEELY_SPIRIT) + .moveset([Moves.REVELATION_DANCE]); + + const revelationDance = allMoves[Moves.REVELATION_DANCE]; + vi.spyOn(revelationDance, "calculateBattlePower"); + + await game.classicMode.startBattle([Species.KLINKLANG]); + + game.move.select(Moves.REVELATION_DANCE); + + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(revelationDance.calculateBattlePower).toHaveReturnedWith(revelationDance.power * 1.5); + }); });