[Bug] Fix Thousand Arrows not hitting targets under the effects of Magnet Rise (#3100)

* Fix Thousand Arrows not hitting through Magnet Rise

* Add integration test for Thousand Arrows vs. Magnet Rise

* ESLint

* Remove unnecessary checks in integration tests
This commit is contained in:
innerthunder 2024-07-23 07:19:48 -07:00 committed by GitHub
parent 630deb92e5
commit 1d39f8d638
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 51 additions and 28 deletions

View File

@ -7531,6 +7531,7 @@ export function initMoves() {
.attr(NeutralDamageAgainstFlyingTypeMultiplierAttr) .attr(NeutralDamageAgainstFlyingTypeMultiplierAttr)
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
.attr(HitsTagAttr, BattlerTagType.FLYING, false) .attr(HitsTagAttr, BattlerTagType.FLYING, false)
.attr(HitsTagAttr, BattlerTagType.MAGNET_RISEN, false)
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN]) .attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
.makesContact(false) .makesContact(false)

View File

@ -1170,7 +1170,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove, ignoreAbility: boolean = false): TypeDamageMultiplier { getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove, ignoreAbility: boolean = false): TypeDamageMultiplier {
const move = pokemonMove.getMove(); const move = pokemonMove.getMove();
const typeless = move.hasAttr(TypelessAttr); const typeless = move.hasAttr(TypelessAttr);
const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.type, source)); const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move, source));
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
if (!typeless && !ignoreAbility) { if (!typeless && !ignoreAbility) {
@ -1185,13 +1185,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** /**
* Calculates the type effectiveness multiplier for an attack type * Calculates the type effectiveness multiplier for an attack type
* @param moveType Type of the move * @param moveOrType The move being used, or a type if the move is unknown
* @param source the Pokemon using the move * @param source the Pokemon using the move
* @param ignoreStrongWinds whether or not this ignores strong winds (anticipation, forewarn, stealth rocks) * @param ignoreStrongWinds whether or not this ignores strong winds (anticipation, forewarn, stealth rocks)
* @param simulated tag to only apply the strong winds effect message when the move is used * @param simulated tag to only apply the strong winds effect message when the move is used
* @returns a multiplier for the type effectiveness * @returns a multiplier for the type effectiveness
*/ */
getAttackTypeEffectiveness(moveType: Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true): TypeDamageMultiplier { getAttackTypeEffectiveness(moveOrType: Move | Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true): TypeDamageMultiplier {
const move = (moveOrType instanceof Move)
? moveOrType
: undefined;
const moveType = (moveOrType instanceof Move)
? move.type
: moveOrType;
if (moveType === Type.STELLAR) { if (moveType === Type.STELLAR) {
return this.isTerastallized() ? 2 : 1; return this.isTerastallized() ? 2 : 1;
} }
@ -1219,9 +1226,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
if (!!this.summonData?.tags.find((tag) => tag instanceof TypeImmuneTag && tag.immuneType === moveType)) { const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType);
multiplier = 0; immuneTags.forEach(tag => {
} if (move !== undefined) {
const hitsTagAttrs = move.getAttrs(HitsTagAttr).filter(attr => attr.tagType === tag.tagType);
if (hitsTagAttrs.length === 0) {
multiplier = 0;
}
}
});
return multiplier; return multiplier;
} }
@ -1806,7 +1819,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const cancelled = new Utils.BooleanHolder(false); const cancelled = new Utils.BooleanHolder(false);
const typeless = move.hasAttr(TypelessAttr); const typeless = move.hasAttr(TypelessAttr);
const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType))) const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType)))
? this.getAttackTypeEffectiveness(move.type, source, false, false) ? this.getAttackTypeEffectiveness(move, source, false, false)
: 1); : 1);
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
if (typeless) { if (typeless) {

View File

@ -1,10 +1,10 @@
import {afterEach, beforeAll, beforeEach, describe, expect, test, vi} from "vitest"; import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser"; import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager"; import GameManager from "#app/test/utils/gameManager";
import overrides from "#app/overrides"; import overrides from "#app/overrides";
import { import {
MoveEffectPhase, BerryPhase,
TurnEndPhase MoveEffectPhase
} from "#app/phases"; } from "#app/phases";
import {getMovePosition} from "#app/test/utils/gameManagerUtils"; import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
@ -38,18 +38,12 @@ describe("Moves - Thousand Arrows", () => {
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH,Moves.SPLASH,Moves.SPLASH,Moves.SPLASH]); vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH,Moves.SPLASH,Moves.SPLASH,Moves.SPLASH]);
}); });
test( it(
"move should hit and ground Flying-type targets", "move should hit and ground Flying-type targets",
async () => { async () => {
await game.startBattle([ Species.ILLUMISE ]); await game.startBattle([ Species.ILLUMISE ]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).toBeDefined();
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).toBeDefined();
const enemyStartingHp = enemyPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.THOUSAND_ARROWS)); game.doAttack(getMovePosition(game.scene, 0, Moves.THOUSAND_ARROWS));
@ -57,14 +51,14 @@ describe("Moves - Thousand Arrows", () => {
// Enemy should not be grounded before move effect is applied // Enemy should not be grounded before move effect is applied
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined(); expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(BerryPhase, false);
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined(); expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
}, TIMEOUT }, TIMEOUT
); );
test( it(
"move should hit and ground targets with Levitate", "move should hit and ground targets with Levitate",
async () => { async () => {
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SNORLAX); vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SNORLAX);
@ -72,13 +66,7 @@ describe("Moves - Thousand Arrows", () => {
await game.startBattle([ Species.ILLUMISE ]); await game.startBattle([ Species.ILLUMISE ]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).toBeDefined();
const enemyPokemon = game.scene.getEnemyPokemon(); const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).toBeDefined();
const enemyStartingHp = enemyPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.THOUSAND_ARROWS)); game.doAttack(getMovePosition(game.scene, 0, Moves.THOUSAND_ARROWS));
@ -86,10 +74,31 @@ describe("Moves - Thousand Arrows", () => {
// Enemy should not be grounded before move effect is applied // Enemy should not be grounded before move effect is applied
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined(); expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
await game.phaseInterceptor.to(TurnEndPhase, false); await game.phaseInterceptor.to(BerryPhase, false);
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined(); expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
}, TIMEOUT }, TIMEOUT
); );
it(
"move should hit and ground targets under the effects of Magnet Rise",
async () => {
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SNORLAX);
await game.startBattle([ Species.ILLUMISE ]);
const enemyPokemon = game.scene.getEnemyPokemon();
enemyPokemon.addTag(BattlerTagType.MAGNET_RISEN, null, Moves.MAGNET_RISE);
game.doAttack(getMovePosition(game.scene, 0, Moves.THOUSAND_ARROWS));
await game.phaseInterceptor.to(BerryPhase, false);
expect(enemyPokemon.getTag(BattlerTagType.MAGNET_RISEN)).toBeUndefined();
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
}
);
}); });