mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-21 00:52:47 +02:00
* Fix #2735: Hazard moves incorrectly require targets Hazard moves should no longer require targets to successfully execute * Apply suggestions from code review made by NightKev Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
parent
1e6ceb5581
commit
0479b9dfcc
@ -1428,7 +1428,8 @@ export class MoveAnim extends BattleAnim {
|
|||||||
public move: Moves;
|
public move: Moves;
|
||||||
|
|
||||||
constructor(move: Moves, user: Pokemon, target: BattlerIndex, playOnEmptyField = false) {
|
constructor(move: Moves, user: Pokemon, target: BattlerIndex, playOnEmptyField = false) {
|
||||||
super(user, globalScene.getField()[target], playOnEmptyField);
|
// Set target to the user pokemon if no target is found to avoid crashes
|
||||||
|
super(user, globalScene.getField()[target] ?? user, playOnEmptyField);
|
||||||
|
|
||||||
this.move = move;
|
this.move = move;
|
||||||
}
|
}
|
||||||
|
@ -5928,7 +5928,7 @@ export class AddArenaTagAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) {
|
if ((move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) {
|
||||||
const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const side = ((this.selfSideTarget ? user : target).isPlayer() !== (move.hasAttr(AddArenaTrapTagAttr) && target === user)) ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
globalScene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, side);
|
globalScene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, side);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -5977,7 +5977,7 @@ export class RemoveArenaTagsAttr extends MoveEffectAttr {
|
|||||||
export class AddArenaTrapTagAttr extends AddArenaTagAttr {
|
export class AddArenaTrapTagAttr extends AddArenaTagAttr {
|
||||||
getCondition(): MoveConditionFunc {
|
getCondition(): MoveConditionFunc {
|
||||||
return (user, target, move) => {
|
return (user, target, move) => {
|
||||||
const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const side = (this.selfSideTarget !== user.isPlayer()) ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER;
|
||||||
const tag = globalScene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag;
|
const tag = globalScene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag;
|
||||||
if (!tag) {
|
if (!tag) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
} from "#app/data/battler-tags";
|
} from "#app/data/battler-tags";
|
||||||
import type { MoveAttr } from "#app/data/moves/move";
|
import type { MoveAttr } from "#app/data/moves/move";
|
||||||
import {
|
import {
|
||||||
|
AddArenaTrapTagAttr,
|
||||||
applyFilteredMoveAttrs,
|
applyFilteredMoveAttrs,
|
||||||
applyMoveAttrs,
|
applyMoveAttrs,
|
||||||
AttackMove,
|
AttackMove,
|
||||||
@ -209,12 +210,12 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
targets.some(t => t.hasAbilityWithAttr(ReflectStatusMoveAbAttr) || !!t.getTag(BattlerTagType.MAGIC_COAT));
|
targets.some(t => t.hasAbilityWithAttr(ReflectStatusMoveAbAttr) || !!t.getTag(BattlerTagType.MAGIC_COAT));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If no targets are left for the move to hit (FAIL), or the invoked move is non-reflectable, single-target
|
* If no targets are left for the move to hit and it is not a hazard move (FAIL), or the invoked move is non-reflectable, single-target
|
||||||
* (and not random target) and failed the hit check against its target (MISS), log the move
|
* (and not random target) and failed the hit check against its target (MISS), log the move
|
||||||
* as FAILed or MISSed (depending on the conditions above) and end this phase.
|
* as FAILed or MISSed (depending on the conditions above) and end this phase.
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
!hasActiveTargets ||
|
(!hasActiveTargets && !move.hasAttr(AddArenaTrapTagAttr)) ||
|
||||||
(!mayBounce &&
|
(!mayBounce &&
|
||||||
!move.hasAttr(VariableTargetAttr) &&
|
!move.hasAttr(VariableTargetAttr) &&
|
||||||
!move.isMultiTarget() &&
|
!move.isMultiTarget() &&
|
||||||
@ -239,18 +240,28 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
return this.end();
|
return this.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
const playOnEmptyField = globalScene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false;
|
const playOnEmptyField =
|
||||||
// Move animation only needs one target
|
(globalScene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false) ||
|
||||||
new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex(), playOnEmptyField).play(
|
(!hasActiveTargets && move.hasAttr(AddArenaTrapTagAttr));
|
||||||
move.hitsSubstitute(user, this.getFirstTarget()!),
|
// Move animation only needs one target. The attacker is used as a fallback.
|
||||||
() => {
|
new MoveAnim(
|
||||||
|
move.id as Moves,
|
||||||
|
user,
|
||||||
|
this.getFirstTarget()?.getBattlerIndex() ?? BattlerIndex.ATTACKER,
|
||||||
|
playOnEmptyField,
|
||||||
|
).play(move.hitsSubstitute(user, this.getFirstTarget()!), () => {
|
||||||
/** Has the move successfully hit a target (for damage) yet? */
|
/** Has the move successfully hit a target (for damage) yet? */
|
||||||
let hasHit = false;
|
let hasHit = false;
|
||||||
|
|
||||||
// Prevent ENEMY_SIDE targeted moves from occurring twice in double battles
|
// Prevent ENEMY_SIDE targeted moves from occurring twice in double battles
|
||||||
// and check which target will magic bounce.
|
// and check which target will magic bounce.
|
||||||
|
// In the event that the move is a hazard move, there may be no target and the move should still succeed.
|
||||||
|
// In this case, the user is used as the "target" to prevent a crash.
|
||||||
|
// This should not affect normal execution of the move otherwise.
|
||||||
const trueTargets: Pokemon[] =
|
const trueTargets: Pokemon[] =
|
||||||
move.moveTarget !== MoveTarget.ENEMY_SIDE
|
!hasActiveTargets && move.hasAttr(AddArenaTrapTagAttr)
|
||||||
|
? [user]
|
||||||
|
: move.moveTarget !== MoveTarget.ENEMY_SIDE
|
||||||
? targets
|
? targets
|
||||||
: (() => {
|
: (() => {
|
||||||
const magicCoatTargets = targets.filter(
|
const magicCoatTargets = targets.filter(
|
||||||
@ -338,9 +349,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
queuedPhases.push(new HideAbilityPhase());
|
queuedPhases.push(new HideAbilityPhase());
|
||||||
}
|
}
|
||||||
|
|
||||||
queuedPhases.push(
|
queuedPhases.push(new MovePhase(target, newTargets, new PokemonMove(move.id, 0, 0, true), true, true, true));
|
||||||
new MovePhase(target, newTargets, new PokemonMove(move.id, 0, 0, true), true, true, true),
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,7 +366,10 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
if (
|
if (
|
||||||
target.switchOutStatus ||
|
target.switchOutStatus ||
|
||||||
isCommanding ||
|
isCommanding ||
|
||||||
(!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()])
|
(!isImmune &&
|
||||||
|
!isProtected &&
|
||||||
|
!targetHitChecks[target.getBattlerIndex()] &&
|
||||||
|
!move.hasAttr(AddArenaTrapTagAttr))
|
||||||
) {
|
) {
|
||||||
this.stopMultiHit(target);
|
this.stopMultiHit(target);
|
||||||
if (!target.switchOutStatus) {
|
if (!target.switchOutStatus) {
|
||||||
@ -501,8 +513,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override end(): void {
|
public override end(): void {
|
||||||
|
@ -15,6 +15,7 @@ import type { DelayedAttackTag } from "#app/data/arena-tag";
|
|||||||
import { CommonAnim } from "#app/data/battle-anims";
|
import { CommonAnim } from "#app/data/battle-anims";
|
||||||
import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags";
|
import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags";
|
||||||
import {
|
import {
|
||||||
|
AddArenaTrapTagAttr,
|
||||||
allMoves,
|
allMoves,
|
||||||
applyMoveAttrs,
|
applyMoveAttrs,
|
||||||
BypassRedirectAttr,
|
BypassRedirectAttr,
|
||||||
@ -201,7 +202,10 @@ export class MovePhase extends BattlePhase {
|
|||||||
const targets = this.getActiveTargetPokemon();
|
const targets = this.getActiveTargetPokemon();
|
||||||
const moveQueue = this.pokemon.getMoveQueue();
|
const moveQueue = this.pokemon.getMoveQueue();
|
||||||
|
|
||||||
if (targets.length === 0 || (moveQueue.length && moveQueue[0].move === Moves.NONE)) {
|
if (
|
||||||
|
(targets.length === 0 && !this.move.getMove().hasAttr(AddArenaTrapTagAttr)) ||
|
||||||
|
(moveQueue.length && moveQueue[0].move === Moves.NONE)
|
||||||
|
) {
|
||||||
this.showMoveText();
|
this.showMoveText();
|
||||||
this.showFailedText();
|
this.showFailedText();
|
||||||
this.cancel();
|
this.cancel();
|
||||||
|
@ -4,6 +4,7 @@ import { Species } from "#enums/species";
|
|||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
|
||||||
|
|
||||||
describe("Moves - Spikes", () => {
|
describe("Moves - Spikes", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -77,4 +78,17 @@ describe("Moves - Spikes", () => {
|
|||||||
const enemy = game.scene.getEnemyParty()[0];
|
const enemy = game.scene.getEnemyParty()[0];
|
||||||
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
|
it("should work when all targets fainted", async () => {
|
||||||
|
game.override.enemySpecies(Species.DIGLETT);
|
||||||
|
game.override.battleType("double");
|
||||||
|
game.override.startingLevel(50);
|
||||||
|
await game.classicMode.startBattle([Species.RAYQUAZA, Species.ROWLET]);
|
||||||
|
|
||||||
|
game.move.select(Moves.EARTHQUAKE);
|
||||||
|
game.move.select(Moves.SPIKES, 1);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(game.scene.arena.getTagOnSide(ArenaTrapTag, ArenaTagSide.ENEMY)).toBeDefined();
|
||||||
|
}, 20000);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user