mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-01-18 06:51:08 +00:00
[Bug] Fix Make It Rain applying stat change twice after KO'ing both opponents (#2696)
* Revert Make It Rain stat change behavior * ESLint * Add double KO unit test
This commit is contained in:
parent
b82c85899e
commit
a48429de09
@ -1,5 +1,5 @@
|
||||
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
|
||||
import { BattleEndPhase, MoveEffectPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../phases";
|
||||
import { BattleEndPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../phases";
|
||||
import { BattleStat, getBattleStatName } from "./battle-stat";
|
||||
import { EncoreTag, SemiInvulnerableTag } from "./battler-tags";
|
||||
import { getPokemonMessage, getPokemonNameWithAffix } from "../messages";
|
||||
@ -813,12 +813,15 @@ export class MoveEffectAttr extends MoveAttr {
|
||||
public firstHitOnly: boolean;
|
||||
/** Should this effect only apply on the last hit? */
|
||||
public lastHitOnly: boolean;
|
||||
/** Should this effect only apply on the first target hit? */
|
||||
public firstTargetOnly: boolean;
|
||||
|
||||
constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false, lastHitOnly: boolean = false) {
|
||||
constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false, lastHitOnly: boolean = false, firstTargetOnly: boolean = false) {
|
||||
super(selfTarget);
|
||||
this.trigger = trigger !== undefined ? trigger : MoveEffectTrigger.POST_APPLY;
|
||||
this.firstHitOnly = firstHitOnly;
|
||||
this.lastHitOnly = lastHitOnly;
|
||||
this.firstTargetOnly = firstTargetOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2330,8 +2333,8 @@ export class StatChangeAttr extends MoveEffectAttr {
|
||||
private condition: MoveConditionFunc;
|
||||
private showMessage: boolean;
|
||||
|
||||
constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT) {
|
||||
super(selfTarget, moveEffectTrigger, firstHitOnly);
|
||||
constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false) {
|
||||
super(selfTarget, moveEffectTrigger, firstHitOnly, false, firstTargetOnly);
|
||||
this.stats = typeof(stats) === "number"
|
||||
? [ stats as BattleStat ]
|
||||
: stats as BattleStat[];
|
||||
@ -5513,25 +5516,6 @@ const userSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target:
|
||||
|
||||
const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(Abilities.COMATOSE);
|
||||
|
||||
/**
|
||||
* Condition to apply effects only upon applying the move to its last target.
|
||||
* Currently only used for Make It Rain.
|
||||
* @param {Pokemon} user The user of the move.
|
||||
* @param {Pokemon} target The current target of the move.
|
||||
* @param {Move} move The move to which this condition applies.
|
||||
* @returns true if the target is the last target to which the move applies.
|
||||
*/
|
||||
const lastTargetOnlyCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => {
|
||||
const effectPhase = user.scene.getCurrentPhase();
|
||||
const targetIndex = target.getFieldIndex() + (target.isPlayer() ? 0 : BattlerIndex.ENEMY);
|
||||
|
||||
if (effectPhase instanceof MoveEffectPhase) {
|
||||
const activeTargets = effectPhase.getTargets();
|
||||
return (activeTargets.length === 0 || targetIndex >= activeTargets.at(-1).getBattlerIndex());
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
|
||||
|
||||
function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<void> {
|
||||
@ -8279,7 +8263,7 @@ export function initMoves() {
|
||||
.attr(RemoveScreensAttr),
|
||||
new AttackMove(Moves.MAKE_IT_RAIN, Type.STEEL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
|
||||
.attr(MoneyAttr)
|
||||
.attr(StatChangeAttr, BattleStat.SPATK, -1, true, lastTargetOnlyCondition, true, false)
|
||||
.attr(StatChangeAttr, BattleStat.SPATK, -1, true, null, true, false, MoveEffectTrigger.HIT, true)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new AttackMove(Moves.PSYBLADE, Type.PSYCHIC, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 9)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.ELECTRIC && user.isGrounded() ? 1.5 : 1)
|
||||
|
@ -2923,6 +2923,7 @@ 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);
|
||||
@ -2956,8 +2957,8 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id);
|
||||
}
|
||||
}
|
||||
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit),
|
||||
user, target, this.move.getMove()).then(() => {
|
||||
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT
|
||||
&& (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && (!attr.firstTargetOnly || firstTarget), user, target, this.move.getMove()).then(() => {
|
||||
return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => {
|
||||
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
|
||||
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
|
||||
|
@ -13,6 +13,8 @@ import { getMovePosition } from "#app/test/utils/gameManagerUtils";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { BattleStat } from "#app/data/battle-stat.js";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
describe("Moves - Make It Rain", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
@ -57,7 +59,7 @@ describe("Moves - Make It Rain", () => {
|
||||
await game.phaseInterceptor.to(MoveEndPhase);
|
||||
|
||||
expect(playerPokemon[0].summonData.battleStats[BattleStat.SPATK]).toBe(-1);
|
||||
});
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should apply effects even if the target faints", async () => {
|
||||
vi.spyOn(overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(1); // ensures the enemy will faint
|
||||
@ -78,5 +80,27 @@ describe("Moves - Make It Rain", () => {
|
||||
|
||||
expect(enemyPokemon.isFainted()).toBe(true);
|
||||
expect(playerPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(-1);
|
||||
});
|
||||
}, TIMEOUT);
|
||||
|
||||
it("should reduce Sp. Atk. once after KOing two enemies", async () => {
|
||||
vi.spyOn(overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(1); // ensures the enemy will faint
|
||||
|
||||
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);
|
||||
|
||||
enemyPokemon.forEach(p => expect(p.isFainted()).toBe(true));
|
||||
expect(playerPokemon[0].summonData.battleStats[BattleStat.SPATK]).toBe(-1);
|
||||
}, TIMEOUT);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user