[Move] Implement After You (#1789)
* Complete after you implementation (no localization) * reset override changes * Remove hardcoded English text, add tests * Fix test * Make sure phases occur in the correct order * fix after-you issues - fix i18n interpolation ot state "target name" and not "pokemon name" as the target takes the offer, not the user - fix some tsdocs - add override to apply - update scene.findPhase to be able to use generic types. Add tsdocs * add move-trigger.afterYou for DE * fix after_you.test.ts --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>
This commit is contained in:
parent
e959595471
commit
a919b9c0af
|
@ -2193,8 +2193,14 @@ export default class BattleScene extends SceneBase {
|
|||
return true;
|
||||
}
|
||||
|
||||
findPhase(phaseFilter: (phase: Phase) => boolean): Phase | undefined {
|
||||
return this.phaseQueue.find(phaseFilter);
|
||||
/**
|
||||
* Find a specific {@linkcode Phase} in the phase queue.
|
||||
*
|
||||
* @param phaseFilter filter function to use to find the wanted phase
|
||||
* @returns the found phase or undefined if none found
|
||||
*/
|
||||
findPhase<P extends Phase = Phase>(phaseFilter: (phase: P) => boolean): P | undefined {
|
||||
return this.phaseQueue.find(phaseFilter) as P;
|
||||
}
|
||||
|
||||
tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean {
|
||||
|
|
|
@ -6272,12 +6272,42 @@ export class VariableTargetAttr extends MoveAttr {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute for {@linkcode Moves.AFTER_YOU}
|
||||
*
|
||||
* [After You - Move | Bulbapedia](https://bulbapedia.bulbagarden.net/wiki/After_You_(move))
|
||||
*/
|
||||
export class AfterYouAttr extends MoveEffectAttr {
|
||||
/**
|
||||
* Allows the target of this move to act right after the user.
|
||||
*
|
||||
* @param user {@linkcode Pokemon} that is using the move.
|
||||
* @param target {@linkcode Pokemon} that will move right after this move is used.
|
||||
* @param move {@linkcode Move} {@linkcode Moves.AFTER_YOU}
|
||||
* @param _args N/A
|
||||
* @returns true
|
||||
*/
|
||||
override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
||||
user.scene.queueMessage(i18next.t("moveTriggers:afterYou", {targetName: getPokemonNameWithAffix(target)}));
|
||||
|
||||
//Will find next acting phase of the targeted pokémon, delete it and queue it next on successful delete.
|
||||
const nextAttackPhase = target.scene.findPhase<MovePhase>((phase) => phase.pokemon === target);
|
||||
if (nextAttackPhase && target.scene.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) {
|
||||
target.scene.prependToPhase(new MovePhase(target.scene, target, [...nextAttackPhase.targets], nextAttackPhase.move), MovePhase);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const failOnGravityCondition: MoveConditionFunc = (user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY);
|
||||
|
||||
const failOnBossCondition: MoveConditionFunc = (user, target, move) => !target.isBossImmune();
|
||||
|
||||
const failOnMaxCondition: MoveConditionFunc = (user, target, move) => !target.isMax();
|
||||
|
||||
const failIfSingleBattle: MoveConditionFunc = (user, target, move) => user.scene.currentBattle.double;
|
||||
|
||||
const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
user.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled));
|
||||
|
@ -7925,7 +7955,10 @@ export function initMoves() {
|
|||
.attr(AbilityGiveAttr),
|
||||
new StatusMove(Moves.AFTER_YOU, Type.NORMAL, -1, 15, -1, 0, 5)
|
||||
.ignoresProtect()
|
||||
.unimplemented(),
|
||||
.target(MoveTarget.NEAR_OTHER)
|
||||
.condition(failIfSingleBattle)
|
||||
.condition((user, target, move) => !target.turnData.acted)
|
||||
.attr(AfterYouAttr),
|
||||
new AttackMove(Moves.ROUND, Type.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5)
|
||||
.soundBased()
|
||||
.partial(),
|
||||
|
|
|
@ -66,5 +66,6 @@
|
|||
"revivalBlessing": "{{pokemonName}} ist wieder fit und kampfbereit!",
|
||||
"swapArenaTags": "{{pokemonName}} hat die Effekte, die auf den beiden Seiten des Kampffeldes wirken, miteinander getauscht!",
|
||||
"exposedMove": "{{pokemonName}} erkennt {{targetPokemonName}}!",
|
||||
"safeguard": "{{targetName}} wird durch Bodyguard geschützt!"
|
||||
"safeguard": "{{targetName}} wird durch Bodyguard geschützt!",
|
||||
"afterYou": "{{targetName}} lässt sich auf Galanterie ein!"
|
||||
}
|
||||
|
|
|
@ -67,5 +67,6 @@
|
|||
"revivalBlessing": "{{pokemonName}} was revived!",
|
||||
"swapArenaTags": "{{pokemonName}} swapped the battle effects affecting each side of the field!",
|
||||
"exposedMove": "{{pokemonName}} identified\n{{targetPokemonName}}!",
|
||||
"safeguard": "{{targetName}} is protected by Safeguard!"
|
||||
}
|
||||
"safeguard": "{{targetName}} is protected by Safeguard!",
|
||||
"afterYou": "{{pokemonName}} took the kind offer!"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import { BattlerIndex } from "#app/battle";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import { MovePhase } from "#app/phases/move-phase";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
describe("Moves - After You", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.battleType("double")
|
||||
.enemyLevel(5)
|
||||
.enemySpecies(Species.PIKACHU)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.moveset([Moves.AFTER_YOU, Moves.SPLASH]);
|
||||
});
|
||||
|
||||
it("makes the target move immediately after the user", async () => {
|
||||
await game.classicMode.startBattle([Species.REGIELEKI, Species.SHUCKLE]);
|
||||
|
||||
game.move.select(Moves.AFTER_YOU, 0, BattlerIndex.PLAYER_2);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
const phase = game.scene.getCurrentPhase() as MovePhase;
|
||||
expect(phase.pokemon).toBe(game.scene.getPlayerField()[1]);
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
}, TIMEOUT);
|
||||
|
||||
it("fails if target already moved", async () => {
|
||||
game.override.enemySpecies(Species.SHUCKLE);
|
||||
await game.classicMode.startBattle([Species.REGIELEKI, Species.PIKACHU]);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
game.move.select(Moves.AFTER_YOU, 1, BattlerIndex.PLAYER);
|
||||
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
await game.phaseInterceptor.to(MovePhase);
|
||||
|
||||
expect(game.scene.getPlayerField()[1].getLastXMoves(1)[0].result).toBe(MoveResult.FAIL);
|
||||
}, TIMEOUT);
|
||||
});
|
Loading…
Reference in New Issue