Protection moves now fail when used last (#4045)
This commit is contained in:
parent
6d312a2909
commit
6e26db27b8
|
@ -6234,6 +6234,8 @@ const userSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target:
|
|||
|
||||
const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(Abilities.COMATOSE);
|
||||
|
||||
const failIfLastCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => user.scene.phaseQueue.find(phase => phase instanceof MovePhase) !== undefined;
|
||||
|
||||
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
|
||||
|
||||
function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise<void> {
|
||||
|
@ -6972,7 +6974,8 @@ export function initMoves() {
|
|||
.attr(StatusEffectAttr, StatusEffect.FREEZE)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new SelfStatusMove(Moves.PROTECT, Type.NORMAL, -1, 10, -1, 4, 2)
|
||||
.attr(ProtectAttr),
|
||||
.attr(ProtectAttr)
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.MACH_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 2)
|
||||
.punchingMove(),
|
||||
new StatusMove(Moves.SCARY_FACE, Type.NORMAL, 100, 10, -1, 0, 2)
|
||||
|
@ -7023,7 +7026,8 @@ export function initMoves() {
|
|||
.windMove()
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new SelfStatusMove(Moves.DETECT, Type.FIGHTING, -1, 5, -1, 4, 2)
|
||||
.attr(ProtectAttr),
|
||||
.attr(ProtectAttr)
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.BONE_RUSH, Type.GROUND, MoveCategory.PHYSICAL, 25, 90, 10, -1, 0, 2)
|
||||
.attr(MultiHitAttr)
|
||||
.makesContact(false),
|
||||
|
@ -7041,7 +7045,8 @@ export function initMoves() {
|
|||
.attr(HitHealAttr)
|
||||
.triageMove(),
|
||||
new SelfStatusMove(Moves.ENDURE, Type.NORMAL, -1, 10, -1, 4, 2)
|
||||
.attr(ProtectAttr, BattlerTagType.ENDURING),
|
||||
.attr(ProtectAttr, BattlerTagType.ENDURING)
|
||||
.condition(failIfLastCondition),
|
||||
new StatusMove(Moves.CHARM, Type.FAIRY, 100, 20, -1, 0, 2)
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK ], -2),
|
||||
new AttackMove(Moves.ROLLOUT, Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2)
|
||||
|
@ -7788,7 +7793,8 @@ export function initMoves() {
|
|||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.ACC ], 1, true),
|
||||
new StatusMove(Moves.WIDE_GUARD, Type.ROCK, -1, 10, -1, 3, 5)
|
||||
.target(MoveTarget.USER_SIDE)
|
||||
.attr(AddArenaTagAttr, ArenaTagType.WIDE_GUARD, 1, true, true),
|
||||
.attr(AddArenaTagAttr, ArenaTagType.WIDE_GUARD, 1, true, true)
|
||||
.condition(failIfLastCondition),
|
||||
new StatusMove(Moves.GUARD_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
|
||||
.attr(AverageStatsAttr, [ Stat.DEF, Stat.SPDEF ], "moveTriggers:sharedGuard"),
|
||||
new StatusMove(Moves.POWER_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
|
||||
|
@ -7876,7 +7882,8 @@ export function initMoves() {
|
|||
.attr(PositiveStatStagePowerAttr),
|
||||
new StatusMove(Moves.QUICK_GUARD, Type.FIGHTING, -1, 15, -1, 3, 5)
|
||||
.target(MoveTarget.USER_SIDE)
|
||||
.attr(AddArenaTagAttr, ArenaTagType.QUICK_GUARD, 1, true, true),
|
||||
.attr(AddArenaTagAttr, ArenaTagType.QUICK_GUARD, 1, true, true)
|
||||
.condition(failIfLastCondition),
|
||||
new SelfStatusMove(Moves.ALLY_SWITCH, Type.PSYCHIC, -1, 15, -1, 2, 5)
|
||||
.ignoresProtect()
|
||||
.unimplemented(),
|
||||
|
@ -8047,7 +8054,8 @@ export function initMoves() {
|
|||
new StatusMove(Moves.MAT_BLOCK, Type.FIGHTING, -1, 10, -1, 0, 6)
|
||||
.target(MoveTarget.USER_SIDE)
|
||||
.attr(AddArenaTagAttr, ArenaTagType.MAT_BLOCK, 1, true, true)
|
||||
.condition(new FirstMoveCondition()),
|
||||
.condition(new FirstMoveCondition())
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.BELCH, Type.POISON, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 6)
|
||||
.condition((user, target, move) => user.battleData.berriesEaten.length > 0),
|
||||
new StatusMove(Moves.ROTOTILLER, Type.GROUND, -1, 10, -1, 0, 6)
|
||||
|
@ -8105,7 +8113,8 @@ export function initMoves() {
|
|||
.triageMove(),
|
||||
new StatusMove(Moves.CRAFTY_SHIELD, Type.FAIRY, -1, 10, -1, 3, 6)
|
||||
.target(MoveTarget.USER_SIDE)
|
||||
.attr(AddArenaTagAttr, ArenaTagType.CRAFTY_SHIELD, 1, true, true),
|
||||
.attr(AddArenaTagAttr, ArenaTagType.CRAFTY_SHIELD, 1, true, true)
|
||||
.condition(failIfLastCondition),
|
||||
new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, -1, 0, 6)
|
||||
.target(MoveTarget.ALL)
|
||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 1, false, (user, target, move) => target.getTypes().includes(Type.GRASS) && !target.getTag(SemiInvulnerableTag)),
|
||||
|
@ -8130,7 +8139,8 @@ export function initMoves() {
|
|||
.target(MoveTarget.BOTH_SIDES)
|
||||
.unimplemented(),
|
||||
new SelfStatusMove(Moves.KINGS_SHIELD, Type.STEEL, -1, 10, -1, 4, 6)
|
||||
.attr(ProtectAttr, BattlerTagType.KINGS_SHIELD),
|
||||
.attr(ProtectAttr, BattlerTagType.KINGS_SHIELD)
|
||||
.condition(failIfLastCondition),
|
||||
new StatusMove(Moves.PLAY_NICE, Type.NORMAL, -1, 20, -1, 0, 6)
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK ], -1),
|
||||
new StatusMove(Moves.CONFIDE, Type.NORMAL, -1, 20, -1, 0, 6)
|
||||
|
@ -8153,7 +8163,8 @@ export function initMoves() {
|
|||
new AttackMove(Moves.MYSTICAL_FIRE, Type.FIRE, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 6)
|
||||
.attr(StatStageChangeAttr, [ Stat.SPATK ], -1),
|
||||
new SelfStatusMove(Moves.SPIKY_SHIELD, Type.GRASS, -1, 10, -1, 4, 6)
|
||||
.attr(ProtectAttr, BattlerTagType.SPIKY_SHIELD),
|
||||
.attr(ProtectAttr, BattlerTagType.SPIKY_SHIELD)
|
||||
.condition(failIfLastCondition),
|
||||
new StatusMove(Moves.AROMATIC_MIST, Type.FAIRY, -1, 20, -1, 0, 6)
|
||||
.attr(StatStageChangeAttr, [ Stat.SPDEF ], 1)
|
||||
.target(MoveTarget.NEAR_ALLY),
|
||||
|
@ -8349,7 +8360,8 @@ export function initMoves() {
|
|||
new AttackMove(Moves.FIRST_IMPRESSION, Type.BUG, MoveCategory.PHYSICAL, 90, 100, 10, -1, 2, 7)
|
||||
.condition(new FirstMoveCondition()),
|
||||
new SelfStatusMove(Moves.BANEFUL_BUNKER, Type.POISON, -1, 10, -1, 4, 7)
|
||||
.attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER),
|
||||
.attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER)
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.SPIRIT_SHACKLE, Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 7)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true)
|
||||
.makesContact(false),
|
||||
|
@ -8592,6 +8604,7 @@ export function initMoves() {
|
|||
/* Unused */
|
||||
new SelfStatusMove(Moves.MAX_GUARD, Type.NORMAL, -1, 10, -1, 4, 8)
|
||||
.attr(ProtectAttr)
|
||||
.condition(failIfLastCondition)
|
||||
.ignoresVirtual(),
|
||||
/* End Unused */
|
||||
new AttackMove(Moves.DYNAMAX_CANNON, Type.DRAGON, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8)
|
||||
|
@ -8770,7 +8783,8 @@ export function initMoves() {
|
|||
.target(MoveTarget.USER_AND_ALLIES)
|
||||
.ignoresProtect(),
|
||||
new SelfStatusMove(Moves.OBSTRUCT, Type.DARK, 100, 10, -1, 4, 8)
|
||||
.attr(ProtectAttr, BattlerTagType.OBSTRUCT),
|
||||
.attr(ProtectAttr, BattlerTagType.OBSTRUCT)
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.FALSE_SURRENDER, Type.DARK, MoveCategory.PHYSICAL, 80, -1, 10, -1, 0, 8),
|
||||
new AttackMove(Moves.METEOR_ASSAULT, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 5, -1, 0, 8)
|
||||
.attr(RechargeAttr)
|
||||
|
@ -9061,7 +9075,8 @@ export function initMoves() {
|
|||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR))
|
||||
.partial(),
|
||||
new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9)
|
||||
.attr(ProtectAttr, BattlerTagType.SILK_TRAP),
|
||||
.attr(ProtectAttr, BattlerTagType.SILK_TRAP)
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.AXE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 90, 10, 30, 0, 9)
|
||||
.attr(MissEffectAttr, crashDamageFunc)
|
||||
.attr(NoEffectAttr, crashDamageFunc)
|
||||
|
@ -9253,7 +9268,8 @@ export function initMoves() {
|
|||
.attr(PreMoveMessageAttr, doublePowerChanceMessageFunc)
|
||||
.attr(DoublePowerChanceAttr),
|
||||
new SelfStatusMove(Moves.BURNING_BULWARK, Type.FIRE, -1, 10, -1, 4, 9)
|
||||
.attr(ProtectAttr, BattlerTagType.BURNING_BULWARK),
|
||||
.attr(ProtectAttr, BattlerTagType.BURNING_BULWARK)
|
||||
.condition(failIfLastCondition),
|
||||
new AttackMove(Moves.THUNDERCLAP, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, -1, 1, 9)
|
||||
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS), // TODO: is this bang correct?
|
||||
new AttackMove(Moves.MIGHTY_CLEAVE, Type.ROCK, MoveCategory.PHYSICAL, 95, 100, 5, -1, 0, 9)
|
||||
|
|
|
@ -7,7 +7,8 @@ import { Moves } from "#enums/moves";
|
|||
import { Stat } from "#enums/stat";
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
|
||||
import { BerryPhase } from "#app/phases/berry-phase";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
|
@ -43,13 +44,13 @@ describe("Moves - Protect", () => {
|
|||
test(
|
||||
"should protect the user from attacks",
|
||||
async () => {
|
||||
await game.startBattle([Species.CHARIZARD]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||
|
||||
game.move.select(Moves.PROTECT);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
|
||||
}, TIMEOUT
|
||||
|
@ -61,13 +62,13 @@ describe("Moves - Protect", () => {
|
|||
game.override.enemyMoveset(Array(4).fill(Moves.CEASELESS_EDGE));
|
||||
vi.spyOn(allMoves[Moves.CEASELESS_EDGE], "accuracy", "get").mockReturnValue(100);
|
||||
|
||||
await game.startBattle([Species.CHARIZARD]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||
|
||||
game.move.select(Moves.PROTECT);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
|
||||
expect(game.scene.arena.getTagOnSide(ArenaTrapTag, ArenaTagSide.ENEMY)).toBeUndefined();
|
||||
|
@ -79,13 +80,13 @@ describe("Moves - Protect", () => {
|
|||
async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.CHARM));
|
||||
|
||||
await game.startBattle([Species.CHARIZARD]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||
|
||||
game.move.select(Moves.PROTECT);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0);
|
||||
}, TIMEOUT
|
||||
|
@ -96,18 +97,38 @@ describe("Moves - Protect", () => {
|
|||
async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.TACHYON_CUTTER));
|
||||
|
||||
await game.startBattle([Species.CHARIZARD]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.PROTECT);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
|
||||
expect(enemyPokemon.turnData.hitCount).toBe(1);
|
||||
}, TIMEOUT
|
||||
);
|
||||
|
||||
test(
|
||||
"should fail if the user is the last to move in the turn",
|
||||
async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.PROTECT));
|
||||
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.PROTECT);
|
||||
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||
expect(leadPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
}, TIMEOUT
|
||||
);
|
||||
});
|
||||
|
|
|
@ -5,8 +5,8 @@ import { Species } from "#enums/species";
|
|||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { BerryPhase } from "#app/phases/berry-phase";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
|
@ -42,19 +42,16 @@ describe("Moves - Quick Guard", () => {
|
|||
test(
|
||||
"should protect the user and allies from priority moves",
|
||||
async () => {
|
||||
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerField();
|
||||
const playerPokemon = game.scene.getPlayerField();
|
||||
|
||||
game.move.select(Moves.QUICK_GUARD);
|
||||
|
||||
await game.phaseInterceptor.to(CommandPhase);
|
||||
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
leadPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp()));
|
||||
playerPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp()));
|
||||
}, TIMEOUT
|
||||
);
|
||||
|
||||
|
@ -64,19 +61,16 @@ describe("Moves - Quick Guard", () => {
|
|||
game.override.enemyAbility(Abilities.PRANKSTER);
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.GROWL));
|
||||
|
||||
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerField();
|
||||
const playerPokemon = game.scene.getPlayerField();
|
||||
|
||||
game.move.select(Moves.QUICK_GUARD);
|
||||
|
||||
await game.phaseInterceptor.to(CommandPhase);
|
||||
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
leadPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(0));
|
||||
playerPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(0));
|
||||
}, TIMEOUT
|
||||
);
|
||||
|
||||
|
@ -85,21 +79,40 @@ describe("Moves - Quick Guard", () => {
|
|||
async () => {
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.WATER_SHURIKEN));
|
||||
|
||||
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
|
||||
await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerField();
|
||||
const playerPokemon = game.scene.getPlayerField();
|
||||
const enemyPokemon = game.scene.getEnemyField();
|
||||
|
||||
game.move.select(Moves.QUICK_GUARD);
|
||||
|
||||
await game.phaseInterceptor.to(CommandPhase);
|
||||
|
||||
game.move.select(Moves.FOLLOW_ME, 1);
|
||||
|
||||
await game.phaseInterceptor.to(BerryPhase, false);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
leadPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp()));
|
||||
playerPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp()));
|
||||
enemyPokemon.forEach(p => expect(p.turnData.hitCount).toBe(1));
|
||||
}
|
||||
);
|
||||
|
||||
test(
|
||||
"should fail if the user is the last to move in the turn",
|
||||
async () => {
|
||||
game.override.battleType("single");
|
||||
game.override.enemyMoveset(Array(4).fill(Moves.QUICK_GUARD));
|
||||
|
||||
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.QUICK_GUARD);
|
||||
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||
expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
}, TIMEOUT
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue