diff --git a/src/data/move.ts b/src/data/move.ts index 668246bd1e0..b78d8f799a2 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -3662,7 +3662,7 @@ export class NeutralDamageAgainstFlyingTypeMultiplierAttr extends VariableMoveTy if (!target.getTag(BattlerTagType.IGNORE_FLYING)) { const multiplier = args[0] as Utils.NumberHolder; //When a flying type is hit, the first hit is always 1x multiplier. Levitating pokemon are instantly affected by typing - if (target.isOfType(Type.FLYING)) { + if (target.isOfType(Type.FLYING) || target.hasAbility(Abilities.LEVITATE)) { multiplier.value = 1; } return true; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 3808f00d679..45ea72ef500 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, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, VariableMoveTypeAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags } from "../data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, VariableMoveTypeAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags, NeutralDamageAgainstFlyingTypeMultiplierAttr } from "../data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; import { Constructor } from "#app/utils"; import * as Utils from "../utils"; @@ -1773,6 +1773,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!typeless) { applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelled, typeMultiplier); + applyMoveAttrs(NeutralDamageAgainstFlyingTypeMultiplierAttr, source, this, move, typeMultiplier); } if (!cancelled.value) { applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier); diff --git a/src/test/moves/thousand_arrows.test.ts b/src/test/moves/thousand_arrows.test.ts index ff71a275743..08b4edfd313 100644 --- a/src/test/moves/thousand_arrows.test.ts +++ b/src/test/moves/thousand_arrows.test.ts @@ -10,6 +10,7 @@ import {getMovePosition} from "#app/test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { Abilities } from "#app/enums/abilities.js"; const TIMEOUT = 20 * 1000; @@ -62,4 +63,33 @@ describe("Moves - Thousand Arrows", () => { expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); }, TIMEOUT ); + + test( + "move should hit and ground targets with Levitate", + async () => { + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SNORLAX); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.LEVITATE); + + await game.startBattle([ Species.ILLUMISE ]); + + const leadPokemon = game.scene.getPlayerPokemon(); + expect(leadPokemon).toBeDefined(); + + const enemyPokemon = game.scene.getEnemyPokemon(); + expect(enemyPokemon).toBeDefined(); + + const enemyStartingHp = enemyPokemon.hp; + + game.doAttack(getMovePosition(game.scene, 0, Moves.THOUSAND_ARROWS)); + + await game.phaseInterceptor.to(MoveEffectPhase, false); + // Enemy should not be grounded before move effect is applied + expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined(); + + await game.phaseInterceptor.to(TurnEndPhase, false); + + expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined(); + expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); + }, TIMEOUT + ); });