[Bug] Fix Make It Rain and Clanging Scales stat drop trigger logic (#3355)

* Fix Make It Rain + Clanging Scales stat drop logic

* Use "includes" instead of "<" for firstTarget logic instead
This commit is contained in:
innerthunder 2024-08-04 22:19:49 -07:00 committed by GitHub
parent b6da93a092
commit 32d4102594
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 37 additions and 21 deletions

View File

@ -7905,7 +7905,7 @@ export function initMoves() {
.makesContact(false)
.partial(),
new AttackMove(Moves.CLANGING_SCALES, Type.DRAGON, MoveCategory.SPECIAL, 110, 100, 5, -1, 0, 7)
.attr(StatChangeAttr, BattleStat.DEF, -1, true)
.attr(StatChangeAttr, BattleStat.DEF, -1, true, null, true, false, MoveEffectTrigger.HIT, true)
.soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.DRAGON_HAMMER, Type.DRAGON, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 7),

View File

@ -2966,6 +2966,8 @@ export class MoveEffectPhase extends PokemonPhase {
// Move animation only needs one target
new MoveAnim(move.id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => {
/** Has the move successfully hit a target (for damage) yet? */
let hasHit: boolean = false;
for (const target of targets) {
if (!targetHitChecks[target.getBattlerIndex()]) {
this.stopMultiHit(target);
@ -2981,7 +2983,6 @@ export class MoveEffectPhase extends PokemonPhase {
const isProtected = !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType));
const firstHit = (user.turnData.hitsLeft === user.turnData.hitCount);
const firstTarget = (moveHistoryEntry.result === MoveResult.PENDING);
if (firstHit) {
user.pushMoveHistory(moveHistoryEntry);
@ -2991,6 +2992,18 @@ export class MoveEffectPhase extends PokemonPhase {
const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT;
const dealsDamage = [
HitResult.EFFECTIVE,
HitResult.SUPER_EFFECTIVE,
HitResult.NOT_VERY_EFFECTIVE,
HitResult.ONE_HIT_KO
].includes(hitResult);
const firstTarget = dealsDamage && !hasHit;
if (firstTarget) {
hasHit = true;
}
const lastHit = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive());
if (lastHit) {
@ -3008,7 +3021,7 @@ export class MoveEffectPhase extends PokemonPhase {
if (hitResult !== HitResult.NO_EFFECT) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, this.move.getMove()).then(() => {
if (hitResult < HitResult.NO_EFFECT && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr)) {
if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr)) {
const flinched = new Utils.BooleanHolder(false);
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
if (flinched.value) {

View File

@ -1,6 +1,6 @@
import { BattleStat } from "#app/data/battle-stat.js";
import {
CommandPhase,
MoveEffectPhase,
MoveEndPhase,
StatChangePhase,
} from "#app/phases";
@ -10,7 +10,7 @@ import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
const TIMEOUT = 20 * 1000;
@ -44,16 +44,8 @@ describe("Moves - Make It Rain", () => {
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
const playerPokemon = game.scene.getPlayerField();
expect(playerPokemon.length).toBe(2);
playerPokemon.forEach(p => expect(p).toBeDefined());
const enemyPokemon = game.scene.getEnemyField();
expect(enemyPokemon.length).toBe(2);
enemyPokemon.forEach(p => expect(p).toBeDefined());
game.doAttack(getMovePosition(game.scene, 0, Moves.MAKE_IT_RAIN));
await game.phaseInterceptor.to(CommandPhase);
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.phaseInterceptor.to(MoveEndPhase);
@ -68,10 +60,7 @@ describe("Moves - Make It Rain", () => {
await game.startBattle([Species.CHARIZARD]);
const playerPokemon = game.scene.getPlayerPokemon();
expect(playerPokemon).toBeDefined();
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).toBeDefined();
game.doAttack(getMovePosition(game.scene, 0, Moves.MAKE_IT_RAIN));
@ -87,14 +76,9 @@ describe("Moves - Make It Rain", () => {
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
const playerPokemon = game.scene.getPlayerField();
playerPokemon.forEach(p => expect(p).toBeDefined());
const enemyPokemon = game.scene.getEnemyField();
enemyPokemon.forEach(p => expect(p).toBeDefined());
game.doAttack(getMovePosition(game.scene, 0, Moves.MAKE_IT_RAIN));
await game.phaseInterceptor.to(CommandPhase);
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.phaseInterceptor.to(StatChangePhase);
@ -102,4 +86,23 @@ describe("Moves - Make It Rain", () => {
enemyPokemon.forEach(p => expect(p.isFainted()).toBe(true));
expect(playerPokemon[0].summonData.battleStats[BattleStat.SPATK]).toBe(-1);
}, TIMEOUT);
it("should reduce Sp. Atk if it only hits the second target", async () => {
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
const playerPokemon = game.scene.getPlayerField();
game.doAttack(getMovePosition(game.scene, 0, Moves.MAKE_IT_RAIN));
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.phaseInterceptor.to(MoveEffectPhase, false);
// Make Make It Rain miss the first target
const moveEffectPhase = game.scene.getCurrentPhase() as MoveEffectPhase;
vi.spyOn(moveEffectPhase, "hitCheck").mockReturnValueOnce(false);
await game.phaseInterceptor.to(MoveEndPhase);
expect(playerPokemon[0].summonData.battleStats[BattleStat.SPATK]).toBe(-1);
}, TIMEOUT);
});