From 6030b780f2abed26b47ba3f1459a8bf8cdf0aa5f Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Tue, 17 Sep 2024 19:41:46 -0700 Subject: [PATCH] [Move][Mirror] Update HitTagAttr attributes v2 (#4297) * [Move] Updated HitAttr tags Affects Whirlwind/Fly, Steamroller/Minimize, and Malicious Moonsault/Minimize * [Move] Update for MinimizeAccuracyAttr Affects Steamroller and Malicious Moonsault * add: whirlwind test * add: steamroller test * rename: `AlwaysHitMinimizeAttr` (from `MinimizeAccuracyAttr`) * rename: `DealsDoubleDamageToTagAttr` (from `HitsTagAttr`) --------- Co-authored-by: chaosgrimmon <31082757+chaosgrimmon@users.noreply.github.com> --- src/data/move.ts | 61 ++++++++++++++++-------------- src/field/pokemon.ts | 8 ++-- src/phases/move-effect-phase.ts | 4 +- src/test/moves/steamroller.test.ts | 58 ++++++++++++++++++++++++++++ src/test/moves/whirlwind.test.ts | 53 ++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 35 deletions(-) create mode 100644 src/test/moves/steamroller.test.ts create mode 100644 src/test/moves/whirlwind.test.ts diff --git a/src/data/move.ts b/src/data/move.ts index 0c2f587d885..1bfe20abc48 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -3839,7 +3839,7 @@ export class StormAccuracyAttr extends VariableAccuracyAttr { * @extends VariableAccuracyAttr * @see {@linkcode apply} */ -export class MinimizeAccuracyAttr extends VariableAccuracyAttr { +export class AlwaysHitMinimizeAttr extends VariableAccuracyAttr { /** * @see {@linkcode apply} * @param user N/A @@ -4855,10 +4855,10 @@ export class RemoveAllSubstitutesAttr extends MoveEffectAttr { * Attribute used when a move hits a {@linkcode BattlerTagType} for double damage * @extends MoveAttr */ -export class HitsTagAttr extends MoveAttr { +export class DealsDoubleDamageToTagAttr extends MoveAttr { /** The {@linkcode BattlerTagType} this move hits */ public tagType: BattlerTagType; - /** Should this move deal double damage against {@linkcode HitsTagAttr.tagType}? */ + /** Should this move deal double damage against {@linkcode DealsDoubleDamageToTagAttr.tagType}? */ public doubleDamage: boolean; constructor(tagType: BattlerTagType, doubleDamage?: boolean) { @@ -6752,12 +6752,11 @@ export function initMoves() { new AttackMove(Moves.CUT, Type.NORMAL, MoveCategory.PHYSICAL, 50, 95, 30, -1, 0, 1) .slicingMove(), new AttackMove(Moves.GUST, Type.FLYING, MoveCategory.SPECIAL, 40, 100, 35, -1, 0, 1) - .attr(HitsTagAttr, BattlerTagType.FLYING, true) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, true) .windMove(), new AttackMove(Moves.WING_ATTACK, Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 35, -1, 0, 1), new StatusMove(Moves.WHIRLWIND, Type.NORMAL, -1, 20, -1, -6, 1) .attr(ForceSwitchOutAttr) - .attr(HitsTagAttr, BattlerTagType.FLYING, false) .ignoresSubstitute() .hidesTarget() .windMove(), @@ -6770,8 +6769,8 @@ export function initMoves() { new AttackMove(Moves.SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, 0, 1), new AttackMove(Moves.VINE_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 45, 100, 25, -1, 0, 1), new AttackMove(Moves.STOMP, Type.NORMAL, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 1) - .attr(MinimizeAccuracyAttr) - .attr(HitsTagAttr, BattlerTagType.MINIMIZED, true) + .attr(AlwaysHitMinimizeAttr) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true) .attr(FlinchAttr), new AttackMove(Moves.DOUBLE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 30, 100, 30, -1, 0, 1) .attr(MultiHitAttr, MultiHitType._2), @@ -6795,8 +6794,8 @@ export function initMoves() { .attr(OneHitKOAccuracyAttr), new AttackMove(Moves.TACKLE, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, 0, 1), new AttackMove(Moves.BODY_SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 85, 100, 15, 30, 0, 1) - .attr(MinimizeAccuracyAttr) - .attr(HitsTagAttr, BattlerTagType.MINIMIZED, true) + .attr(AlwaysHitMinimizeAttr) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true) .attr(StatusEffectAttr, StatusEffect.PARALYSIS), new AttackMove(Moves.WRAP, Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, 0, 1) .attr(TrapAttr, BattlerTagType.WRAP), @@ -6864,7 +6863,7 @@ export function initMoves() { new AttackMove(Moves.HYDRO_PUMP, Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, -1, 0, 1), new AttackMove(Moves.SURF, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 1) .target(MoveTarget.ALL_NEAR_OTHERS) - .attr(HitsTagAttr, BattlerTagType.UNDERWATER, true) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERWATER, true) .attr(GulpMissileTagAttr), new AttackMove(Moves.ICE_BEAM, Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1) .attr(StatusEffectAttr, StatusEffect.FREEZE), @@ -6947,18 +6946,18 @@ export function initMoves() { new AttackMove(Moves.THUNDER, Type.ELECTRIC, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 1) .attr(StatusEffectAttr, StatusEffect.PARALYSIS) .attr(ThunderAccuracyAttr) - .attr(HitsTagAttr, BattlerTagType.FLYING, false), + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false), new AttackMove(Moves.ROCK_THROW, Type.ROCK, MoveCategory.PHYSICAL, 50, 90, 15, -1, 0, 1) .makesContact(false), new AttackMove(Moves.EARTHQUAKE, Type.GROUND, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 1) - .attr(HitsTagAttr, BattlerTagType.UNDERGROUND, true) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERGROUND, true) .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), new AttackMove(Moves.FISSURE, Type.GROUND, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1) .attr(OneHitKOAttr) .attr(OneHitKOAccuracyAttr) - .attr(HitsTagAttr, BattlerTagType.UNDERGROUND, false) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERGROUND, false) .makesContact(false), new AttackMove(Moves.DIG, Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 1) .attr(ChargeAttr, ChargeAnim.DIG_CHARGING, i18next.t("moveTriggers:dugAHole", {pokemonName: "{USER}"}), BattlerTagType.UNDERGROUND) @@ -7347,7 +7346,7 @@ export function initMoves() { .attr(PreMoveMessageAttr, magnitudeMessageFunc) .attr(MagnitudePowerAttr) .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1) - .attr(HitsTagAttr, BattlerTagType.UNDERGROUND, true) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERGROUND, true) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), new AttackMove(Moves.DYNAMIC_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 50, 5, 100, 0, 2) @@ -7403,7 +7402,7 @@ export function initMoves() { new AttackMove(Moves.CROSS_CHOP, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 80, 5, -1, 0, 2) .attr(HighCritAttr), new AttackMove(Moves.TWISTER, Type.DRAGON, MoveCategory.SPECIAL, 40, 100, 20, 20, 0, 2) - .attr(HitsTagAttr, BattlerTagType.FLYING, true) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, true) .attr(FlinchAttr) .windMove() .target(MoveTarget.ALL_NEAR_ENEMIES), @@ -7435,7 +7434,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.DEF ], -1), new AttackMove(Moves.WHIRLPOOL, Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 2) .attr(TrapAttr, BattlerTagType.WHIRLPOOL) - .attr(HitsTagAttr, BattlerTagType.UNDERWATER, true), + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.UNDERWATER, true), new AttackMove(Moves.BEAT_UP, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 2) .attr(MultiHitAttr, MultiHitType.BEAT_UP) .attr(BeatUpAttr) @@ -7658,7 +7657,7 @@ export function initMoves() { new AttackMove(Moves.EXTRASENSORY, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 20, 10, 0, 3) .attr(FlinchAttr), new AttackMove(Moves.SKY_UPPERCUT, Type.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, 0, 3) - .attr(HitsTagAttr, BattlerTagType.FLYING) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING) .punchingMove(), new AttackMove(Moves.SAND_TOMB, Type.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 3) .attr(TrapAttr, BattlerTagType.SAND_TOMB) @@ -7889,8 +7888,8 @@ export function initMoves() { new AttackMove(Moves.DRAGON_PULSE, Type.DRAGON, MoveCategory.SPECIAL, 85, 100, 10, -1, 0, 4) .pulseMove(), new AttackMove(Moves.DRAGON_RUSH, Type.DRAGON, MoveCategory.PHYSICAL, 100, 75, 10, 20, 0, 4) - .attr(MinimizeAccuracyAttr) - .attr(HitsTagAttr, BattlerTagType.MINIMIZED, true) + .attr(AlwaysHitMinimizeAttr) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true) .attr(FlinchAttr), new AttackMove(Moves.POWER_GEM, Type.ROCK, MoveCategory.SPECIAL, 80, 100, 20, -1, 0, 4), new AttackMove(Moves.DRAIN_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 4) @@ -8087,7 +8086,7 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) .attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN]) - .attr(HitsTagAttr, BattlerTagType.FLYING, false) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false) .makesContact(false), new AttackMove(Moves.STORM_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5) .attr(CritOnlyAttr), @@ -8100,9 +8099,9 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true) .danceMove(), new AttackMove(Moves.HEAVY_SLAM, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5) - .attr(MinimizeAccuracyAttr) + .attr(AlwaysHitMinimizeAttr) .attr(CompareWeightPowerAttr) - .attr(HitsTagAttr, BattlerTagType.MINIMIZED, true), + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true), new AttackMove(Moves.SYNCHRONOISE, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 5) .target(MoveTarget.ALL_NEAR_OTHERS) .condition(unknownTypeCondition) @@ -8253,12 +8252,14 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.DEF ], -1) .slicingMove(), new AttackMove(Moves.HEAT_CRASH, Type.FIRE, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5) - .attr(MinimizeAccuracyAttr) + .attr(AlwaysHitMinimizeAttr) .attr(CompareWeightPowerAttr) - .attr(HitsTagAttr, BattlerTagType.MINIMIZED, true), + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true), new AttackMove(Moves.LEAF_TORNADO, Type.GRASS, MoveCategory.SPECIAL, 65, 90, 10, 50, 0, 5) .attr(StatStageChangeAttr, [ Stat.ACC ], -1), new AttackMove(Moves.STEAMROLLER, Type.BUG, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 5) + .attr(AlwaysHitMinimizeAttr) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true) .attr(FlinchAttr), new SelfStatusMove(Moves.COTTON_GUARD, Type.GRASS, -1, 10, -1, 0, 5) .attr(StatStageChangeAttr, [ Stat.DEF ], 3, true), @@ -8271,7 +8272,7 @@ export function initMoves() { new AttackMove(Moves.HURRICANE, Type.FLYING, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 5) .attr(ThunderAccuracyAttr) .attr(ConfuseAttr) - .attr(HitsTagAttr, BattlerTagType.FLYING, false) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false) .windMove(), new AttackMove(Moves.HEAD_CHARGE, Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 5) .attr(RecoilAttr) @@ -8325,9 +8326,9 @@ export function initMoves() { .attr(LastMoveDoublePowerAttr, Moves.FUSION_FLARE) .makesContact(false), new AttackMove(Moves.FLYING_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 95, 10, -1, 0, 6) - .attr(MinimizeAccuracyAttr) + .attr(AlwaysHitMinimizeAttr) .attr(FlyingTypeMultiplierAttr) - .attr(HitsTagAttr, BattlerTagType.MINIMIZED, true) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true) .condition(failOnGravityCondition), new StatusMove(Moves.MAT_BLOCK, Type.FIGHTING, -1, 10, -1, 0, 6) .target(MoveTarget.USER_SIDE) @@ -8498,8 +8499,8 @@ export function initMoves() { new AttackMove(Moves.THOUSAND_ARROWS, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6) .attr(NeutralDamageAgainstFlyingTypeMultiplierAttr) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true) - .attr(HitsTagAttr, BattlerTagType.FLYING, false) - .attr(HitsTagAttr, BattlerTagType.MAGNET_RISEN, false) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.FLYING, false) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MAGNET_RISEN, false) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) .attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN]) .makesContact(false) @@ -8756,6 +8757,8 @@ export function initMoves() { .partial() .ignoresVirtual(), new AttackMove(Moves.MALICIOUS_MOONSAULT, Type.DARK, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7) + .attr(AlwaysHitMinimizeAttr) + .attr(DealsDoubleDamageToTagAttr, BattlerTagType.MINIMIZED, true) .partial() .ignoresVirtual(), new AttackMove(Moves.OCEANIC_OPERETTA, Type.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 7593cb870b4..cdafc960382 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3,7 +3,7 @@ import BattleScene, { AnySound } from "../battle-scene"; import { Variant, VariantSet, variantColorCache } from "#app/data/variant"; import { variantData } from "#app/data/variant"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget } from "../data/move"; +import Move, { HighCritAttr, DealsDoubleDamageToTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget } from "../data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; import * as Utils from "../utils"; @@ -1513,7 +1513,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType); for (const tag of immuneTags) { - if (move && !move.getAttrs(HitsTagAttr).some(attr => attr.tagType === tag.tagType)) { + if (move && !move.getAttrs(DealsDoubleDamageToTagAttr).some(attr => attr.tagType === tag.tagType)) { typeMultiplier.value = 0; break; } @@ -2489,13 +2489,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, screenMultiplier); /** - * For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if: + * For each {@linkcode DealsDoubleDamageToTagAttr} the move has, doubles the damage of the move if: * The target has a {@linkcode BattlerTagType} that this move interacts with * AND * The move doubles damage when used against that tag */ const hitsTagMultiplier = new Utils.NumberHolder(1); - move.getAttrs(HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { + move.getAttrs(DealsDoubleDamageToTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { if (this.getTag(hta.tagType)) { hitsTagMultiplier.value *= 2; } diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index c3199166e84..e2fca951b2f 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -4,7 +4,7 @@ import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr, import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag"; import { MoveAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags"; -import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr } from "#app/data/move"; +import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, DealsDoubleDamageToTagAttr } from "#app/data/move"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Moves } from "#app/enums/moves"; @@ -394,7 +394,7 @@ export class MoveEffectPhase extends PokemonPhase { } const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); - if (semiInvulnerableTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) { + if (semiInvulnerableTag && !this.move.getMove().getAttrs(DealsDoubleDamageToTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) { return false; } diff --git a/src/test/moves/steamroller.test.ts b/src/test/moves/steamroller.test.ts new file mode 100644 index 00000000000..cbbb3a22593 --- /dev/null +++ b/src/test/moves/steamroller.test.ts @@ -0,0 +1,58 @@ +import { BattlerIndex } from "#app/battle"; +import { allMoves } from "#app/data/move"; +import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { DamageCalculationResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - Steamroller", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override.moveset([Moves.STEAMROLLER]).battleType("single").enemyAbility(Abilities.BALL_FETCH); + }); + + it("should always hit a minimzed target with double damage", async () => { + game.override.enemySpecies(Species.DITTO).enemyMoveset(Moves.MINIMIZE); + await game.classicMode.startBattle([Species.IRON_BOULDER]); + + const ditto = game.scene.getEnemyPokemon()!; + vi.spyOn(ditto, "getAttackDamage"); + ditto.hp = 5000; + const steamroller = allMoves[Moves.STEAMROLLER]; + vi.spyOn(steamroller, "calculateBattleAccuracy"); + const ironBoulder = game.scene.getPlayerPokemon()!; + vi.spyOn(ironBoulder, "getAccuracyMultiplier"); + // Turn 1 + game.move.select(Moves.STEAMROLLER); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.toNextTurn(); + // Turn 2 + game.move.select(Moves.STEAMROLLER); + await game.toNextTurn(); + + const [dmgCalcTurn1, dmgCalcTurn2]: DamageCalculationResult[] = vi + .mocked(ditto.getAttackDamage) + .mock.results.map((r) => r.value); + + expect(dmgCalcTurn2.damage).toBeGreaterThanOrEqual(dmgCalcTurn1.damage * 2); + expect(ditto.getTag(BattlerTagType.MINIMIZED)).toBeDefined(); + expect(steamroller.calculateBattleAccuracy).toHaveReturnedWith(-1); + }); +}); diff --git a/src/test/moves/whirlwind.test.ts b/src/test/moves/whirlwind.test.ts new file mode 100644 index 00000000000..a591a3cd6c5 --- /dev/null +++ b/src/test/moves/whirlwind.test.ts @@ -0,0 +1,53 @@ +import { BattlerIndex } from "#app/battle"; +import { allMoves } from "#app/data/move"; +import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; + +describe("Moves - Whirlwind", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.WHIRLWIND) + .enemySpecies(Species.PIDGEY); + }); + + it.each([ + { move: Moves.FLY, name: "Fly" }, + { move: Moves.BOUNCE, name: "Bounce" }, + { move: Moves.SKY_DROP, name: "Sky Drop" }, + ])("should not hit a flying target: $name (=$move)", async ({ move }) => { + game.override.moveset([move]); + await game.classicMode.startBattle([Species.STARAPTOR]); + + const staraptor = game.scene.getPlayerPokemon()!; + const whirlwind = allMoves[Moves.WHIRLWIND]; + vi.spyOn(whirlwind, "getFailedText"); + + game.move.select(move); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.toNextTurn(); + + expect(staraptor.findTag((t) => t.tagType === BattlerTagType.FLYING)).toBeDefined(); + expect(whirlwind.getFailedText).toHaveBeenCalledOnce(); + }); +});