mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-30 13:33:01 +02:00
WIP
This commit is contained in:
parent
da6443562b
commit
4bac0ed3ec
@ -4454,10 +4454,10 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr {
|
||||
// TODO: fix in main dancer PR (currently keeping this purely semantic rather than actually fixing bug)
|
||||
if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) {
|
||||
const target = this.getTarget(dancer, source, targets);
|
||||
globalScene.unshiftPhase(new MovePhase(dancer, target, move, MoveUseType.FOLLOW_UP));
|
||||
globalScene.unshiftPhase(new MovePhase(dancer, target, move, MoveUseType.INDIRECT));
|
||||
} else if (move.getMove() instanceof SelfStatusMove) {
|
||||
// If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself
|
||||
globalScene.unshiftPhase(new MovePhase(dancer, [ dancer.getBattlerIndex() ], move, MoveUseType.FOLLOW_UP))
|
||||
globalScene.unshiftPhase(new MovePhase(dancer, [ dancer.getBattlerIndex() ], move, MoveUseType.INDIRECT))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -307,7 +307,8 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
|
||||
* and showing a message.
|
||||
*/
|
||||
override onAdd(pokemon: Pokemon): void {
|
||||
// Disable fails against struggle or an empty move history
|
||||
// Disable fails against struggle or an empty move history, but we still need to check for
|
||||
// Cursed Body
|
||||
const move = pokemon.getLastNonVirtualMove();
|
||||
if (isNullOrUndefined(move) || move.move === Moves.STRUGGLE) {
|
||||
return;
|
||||
|
@ -5419,9 +5419,13 @@ export class FrenzyAttr extends MoveEffectAttr {
|
||||
// TODO: Disable if used via dancer
|
||||
// TODO: Add support for moves that don't add the frenzy tag (Uproar, Rollout, etc.)
|
||||
|
||||
if (!user.getTag(BattlerTagType.FRENZY) && !user.getMoveQueue().length) {
|
||||
// If frenzy is not active, add a tag and push 1-2 extra turns of attacks to the user's move queue.
|
||||
// Otherwise, tick down the existing tag.
|
||||
if (!user.getTag(BattlerTagType.FRENZY) && user.getMoveQueue().length === 0) {
|
||||
const turnCount = user.randSeedIntRange(1, 2);
|
||||
new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }));
|
||||
for (let x = 0; x < turnCount; x++) {
|
||||
user.pushMoveQueue({move: move.id, targets: [target.getBattlerIndex()], useType: MoveUseType.IGNORE_PP})
|
||||
}
|
||||
user.addTag(BattlerTagType.FRENZY, turnCount, move.id, user.id);
|
||||
} else {
|
||||
applyMoveAttrs(AddBattlerTagAttr, user, target, move, args);
|
||||
@ -6746,7 +6750,9 @@ class CallMoveAttr extends OverrideMoveEffectAttr {
|
||||
// If not, target the Mirror Move recipient or else a random enemy in our target list
|
||||
const targets = moveTargets.multiple || moveTargets.targets.length === 1
|
||||
? moveTargets.targets
|
||||
: [ this.hasTarget ? target.getBattlerIndex() : moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; // account for Mirror Move having a target already
|
||||
: [this.hasTarget
|
||||
? target.getBattlerIndex()
|
||||
: moveTargets.targets[user.randSeedInt(moveTargets.targets.length)]];
|
||||
globalScene.unshiftPhase(new LoadMoveAnimPhase(move.id));
|
||||
globalScene.unshiftPhase(new MovePhase(user, targets, new PokemonMove(move.id), MoveUseType.FOLLOW_UP));
|
||||
return true;
|
||||
@ -7083,6 +7089,7 @@ export class RepeatMoveAttr extends MoveEffectAttr {
|
||||
Moves.PETAL_DANCE,
|
||||
Moves.THRASH,
|
||||
Moves.ICE_BALL,
|
||||
Moves.UPROAR,
|
||||
// Multi-turn Moves
|
||||
Moves.BIDE,
|
||||
Moves.SHELL_TRAP,
|
||||
@ -7120,8 +7127,17 @@ export class RepeatMoveAttr extends MoveEffectAttr {
|
||||
Moves.SOLAR_BEAM,
|
||||
Moves.SOLAR_BLADE,
|
||||
Moves.METEOR_BEAM,
|
||||
// Other moves
|
||||
// Copying/Move-Calling moves
|
||||
Moves.ASSIST,
|
||||
Moves.COPYCAT,
|
||||
Moves.ME_FIRST,
|
||||
Moves.METRONOME,
|
||||
Moves.MIRROR_MOVE,
|
||||
Moves.NATURE_POWER,
|
||||
Moves.SLEEP_TALK,
|
||||
Moves.SNATCH,
|
||||
Moves.INSTRUCT,
|
||||
// Misc moves
|
||||
Moves.KINGS_SHIELD,
|
||||
Moves.SKETCH,
|
||||
Moves.TRANSFORM,
|
||||
@ -7132,7 +7148,8 @@ export class RepeatMoveAttr extends MoveEffectAttr {
|
||||
|
||||
if (!lastMove?.move // no move to instruct
|
||||
|| !movesetMove // called move not in target's moveset (forgetting the move, etc.)
|
||||
|| !movesetMove.isUsable(target) // Move unusable due to PP shortage or similar
|
||||
|| movesetMove.ppUsed === movesetMove.getMovePp() // move out of pp
|
||||
|| allMoves[lastMove.move].isChargingMove() // called move is a charging/recharging move
|
||||
|| uninstructableMoves.includes(lastMove.move)) { // called move is in the banlist
|
||||
return false;
|
||||
}
|
||||
|
@ -15,22 +15,23 @@ export enum MoveUseType {
|
||||
NORMAL,
|
||||
|
||||
/**
|
||||
* Identical to {@linkcode MoveUseType.NORMAL}, except the move **does not consume PP** on use
|
||||
* and **will not fail** if none is left before its execution.
|
||||
* PP can still be reduced by other effects (such as Spite or Eerie Spell).
|
||||
*/
|
||||
* This move was called by an effect that ignores PP, such as a consecutively executed move.
|
||||
* Identical to {@linkcode MoveUseType.NORMAL}, except the move **does not consume PP** on use
|
||||
* and **will not fail** if none is left before its execution.
|
||||
* PP can still be reduced by other effects (such as Spite or Eerie Spell).
|
||||
*/
|
||||
IGNORE_PP,
|
||||
|
||||
/**
|
||||
* This move was called indirectly by another effect other than Instruct or the user's previous move.
|
||||
* Currently only used by {@linkcode PostDancingMoveAbAttr | Dancer}.
|
||||
* This move was called indirectly by an out-of-turn effect other than Instruct or the user's previous move.
|
||||
* Currently only used by {@linkcode PostDancingMoveAbAttr | Dancer}.
|
||||
|
||||
* Indirect moves ignore PP checks similar to {@linkcode MoveUseType.IGNORE_PP}, but additionally **cannot be copied**
|
||||
* by all move-copying effects (barring reflection).
|
||||
* They are also **"skipped over" by most moveset and move history-related effects** (PP reduction, Last Resort, etc).
|
||||
* Indirect moves ignore PP checks similar to {@linkcode MoveUseType.IGNORE_PP}, but additionally **cannot be copied**
|
||||
* by all move-copying effects (barring reflection).
|
||||
* They are also **"skipped over" by most moveset and move history-related effects** (PP reduction, Last Resort, etc).
|
||||
|
||||
* They still respect the user's volatile status conditions and confusion (though will uniquely _cure freeze and sleep before use_).
|
||||
*/
|
||||
* They still respect the user's volatile status conditions and confusion (though will uniquely _cure freeze and sleep before use_).
|
||||
*/
|
||||
INDIRECT,
|
||||
|
||||
/**
|
||||
@ -56,3 +57,9 @@ export enum MoveUseType {
|
||||
*/
|
||||
REFLECTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Comment block to prevent auto-import removal.
|
||||
* {@linkcode BattlerTagLapseType}
|
||||
* {@linkcode PostDancingMoveAbAttr}
|
||||
*/
|
@ -86,6 +86,8 @@ export class MoveChargePhase extends PokemonPhase {
|
||||
result: MoveResult.OTHER,
|
||||
useType: this.useType,
|
||||
});
|
||||
|
||||
super.end();
|
||||
}
|
||||
|
||||
public getUserPokemon(): Pokemon {
|
||||
|
@ -150,7 +150,8 @@ export class MovePhase extends BattlePhase {
|
||||
|
||||
console.log(Moves[this.move.moveId], MoveUseType[this.useType]);
|
||||
|
||||
// Check if move is unusable (e.g. because it's out of PP due to a mid-turn Spite).
|
||||
// Check if move is unusable (e.g. running out of PP due to a mid-turn Spite
|
||||
// or the user no longer being on field).
|
||||
if (!this.canMove(true)) {
|
||||
if (this.pokemon.isActive(true)) {
|
||||
this.fail();
|
||||
@ -241,6 +242,7 @@ export class MovePhase extends BattlePhase {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pokemon.status.incrementTurn();
|
||||
/** Whether to prevent us from using the move */
|
||||
let activated = false;
|
||||
/** Whether to cure the status */
|
||||
|
@ -41,6 +41,7 @@ describe("Abilities - Early Bird", () => {
|
||||
|
||||
game.move.select(Moves.BELLY_DRUM);
|
||||
await game.toNextTurn();
|
||||
|
||||
game.move.select(Moves.REST);
|
||||
await game.toNextTurn();
|
||||
|
||||
|
@ -75,7 +75,7 @@ describe("Moves - After You", () => {
|
||||
|
||||
game.move.select(Moves.SPLASH, BattlerIndex.PLAYER);
|
||||
game.move.select(Moves.OUTRAGE, BattlerIndex.PLAYER_2);
|
||||
await game.toNextTurn();
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
const outrageMove = rattata.getMoveset().find(m => m.moveId === Moves.OUTRAGE);
|
||||
expect(outrageMove?.ppUsed).toBe(1);
|
||||
|
@ -58,9 +58,11 @@ describe("Moves - Dig", () => {
|
||||
});
|
||||
|
||||
it("should deduct PP only on the 2nd turn of the move", async () => {
|
||||
game.override.moveset([]);
|
||||
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
game.move.changeMoveset(playerPokemon, Moves.DIG);
|
||||
|
||||
game.move.select(Moves.DIG);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
@ -48,7 +48,7 @@ describe("Moves - Disable", () => {
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(enemyMon.getLastXMoves(-1)).toHaveLength(1);
|
||||
expect(enemyMon.getLastXMoves(-1)).toHaveLength(2);
|
||||
expect(enemyMon.isMoveRestricted(Moves.SPLASH)).toBe(true);
|
||||
expect(enemyMon.isMoveRestricted(Moves.GROWL)).toBe(false);
|
||||
});
|
||||
@ -87,7 +87,7 @@ describe("Moves - Disable", () => {
|
||||
expect(enemyHistory.map(m => m.move)).toEqual([Moves.STRUGGLE, Moves.SPLASH]);
|
||||
});
|
||||
|
||||
it("cannot disable STRUGGLE", async () => {
|
||||
it("should fail if it would otherwise disable struggle", async () => {
|
||||
game.override.enemyMoveset([Moves.STRUGGLE]);
|
||||
await game.classicMode.startBattle([Species.PIKACHU]);
|
||||
|
||||
@ -120,28 +120,29 @@ describe("Moves - Disable", () => {
|
||||
|
||||
const enemyHistory = enemyMon.getLastXMoves(-1);
|
||||
expect(enemyHistory).toHaveLength(2);
|
||||
expect(enemyHistory[0]).toMatchObject({
|
||||
move: Moves.SPLASH,
|
||||
result: MoveResult.FAIL,
|
||||
});
|
||||
expect(enemyHistory[0].result).toBe(MoveResult.FAIL);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ name: "Nature Power", moveId: Moves.NATURE_POWER },
|
||||
{ name: "Mirror Move", moveId: Moves.MIRROR_MOVE },
|
||||
{ name: "Copycat", moveId: Moves.COPYCAT },
|
||||
{ name: "Copycat", moveId: Moves.COPYCAT },
|
||||
{ name: "Metronome", moveId: Moves.METRONOME },
|
||||
])("should ignore virtual moves called by $name", async ({ moveId }) => {
|
||||
game.override.enemyMoveset(moveId);
|
||||
await game.classicMode.startBattle([Species.PIKACHU]);
|
||||
|
||||
const enemyMon = game.scene.getEnemyPokemon()!;
|
||||
const playerMon = game.scene.getEnemyPokemon()!;
|
||||
playerMon.pushMoveHistory({ move: Moves.SPLASH, targets: [BattlerIndex.ENEMY], useType: MoveUseType.NORMAL });
|
||||
game.scene.currentBattle.lastMove = Moves.SPLASH;
|
||||
|
||||
game.move.select(Moves.DISABLE);
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
await game.toNextTurn();
|
||||
|
||||
const enemyMon = game.scene.getEnemyPokemon()!;
|
||||
expect.soft(enemyMon.isMoveRestricted(moveId), `calling move ${Moves[moveId]} was not disabled`).toBe(true);
|
||||
expect.soft(enemyMon.getLastXMoves(-1)).toHaveLength(2);
|
||||
const calledMove = enemyMon.getLastXMoves()[0].move;
|
||||
expect(
|
||||
enemyMon.isMoveRestricted(calledMove),
|
||||
@ -154,7 +155,6 @@ describe("Moves - Disable", () => {
|
||||
.enemyAbility(Abilities.DANCER)
|
||||
.moveset([Moves.DISABLE, Moves.SWORDS_DANCE])
|
||||
.enemyMoveset([Moves.SPLASH, Moves.SWORDS_DANCE]);
|
||||
|
||||
await game.classicMode.startBattle([Species.PIKACHU]);
|
||||
|
||||
game.move.select(Moves.SWORDS_DANCE);
|
||||
@ -173,6 +173,6 @@ describe("Moves - Disable", () => {
|
||||
expect.soft(shuckle.isMoveRestricted(Moves.SPLASH)).toBe(true);
|
||||
expect.soft(shuckle.isMoveRestricted(Moves.SWORDS_DANCE)).toBe(false);
|
||||
expect(shuckle.getLastXMoves()[0]).toMatchObject({ move: Moves.SWORDS_DANCE, result: MoveResult.SUCCESS });
|
||||
expect(shuckle.getStatStage(Stat.ATK)).toBe(2);
|
||||
expect(shuckle.getStatStage(Stat.ATK)).toBe(4);
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { RandomMoveAttr } from "#app/data/moves/move";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import type { MovePhase } from "#app/phases/move-phase";
|
||||
@ -8,7 +9,7 @@ import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Moves - Instruct", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -139,6 +140,22 @@ describe("Moves - Instruct", () => {
|
||||
expect(game.scene.getPlayerPokemon()!.turnData.attacksReceived.length).toBe(3);
|
||||
});
|
||||
|
||||
it("should fail on metronomed moves, even if also in moveset", async () => {
|
||||
game.override.moveset(Moves.INSTRUCT);
|
||||
vi.spyOn(RandomMoveAttr.prototype, "getMoveOverride").mockReturnValue(Moves.ABSORB);
|
||||
await game.classicMode.startBattle([Species.AMOONGUSS]);
|
||||
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
game.move.changeMoveset(enemy, [Moves.METRONOME, Moves.ABSORB]);
|
||||
|
||||
game.move.select(Moves.INSTRUCT);
|
||||
await game.forceEnemyMove(Moves.METRONOME);
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
});
|
||||
|
||||
it("should respect enemy's status condition", async () => {
|
||||
game.override.moveset([Moves.INSTRUCT, Moves.THUNDER_WAVE]).enemyMoveset(Moves.SONIC_BOOM);
|
||||
await game.classicMode.startBattle([Species.AMOONGUSS]);
|
||||
@ -249,15 +266,9 @@ describe("Moves - Instruct", () => {
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2]);
|
||||
await game.phaseInterceptor.to("TurnEndPhase", false);
|
||||
|
||||
expect(game.scene.getPlayerField()[0].getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||
const enemyMove = game.scene.getEnemyField()[0]!.getLastXMoves()[0];
|
||||
expect(enemyMove.result).toBe(MoveResult.FAIL);
|
||||
expect(
|
||||
game.scene
|
||||
.getEnemyField()[0]
|
||||
.getMoveset()
|
||||
.find(m => m?.moveId === Moves.SONIC_BOOM)?.ppUsed,
|
||||
).toBe(1);
|
||||
expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||
expect(enemy1.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||
expect(enemy1.getMoveset().find(m => m.moveId === Moves.SONIC_BOOM)?.ppUsed).toBe(1);
|
||||
});
|
||||
|
||||
it("should not repeat enemy's move through protect", async () => {
|
||||
|
@ -72,7 +72,7 @@ describe("Moves - Quash", () => {
|
||||
|
||||
game.move.select(Moves.SPLASH, BattlerIndex.PLAYER);
|
||||
game.move.select(Moves.OUTRAGE, BattlerIndex.PLAYER_2);
|
||||
await game.toNextTurn();
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
const outrageMove = rattata.getMoveset().find(m => m.moveId === Moves.OUTRAGE);
|
||||
expect(outrageMove?.ppUsed).toBe(1);
|
||||
|
@ -427,6 +427,7 @@ export default class GameManager {
|
||||
* If all active player Pokemon are using a rampaging, charging, recharging or other move that
|
||||
* disables user input, this **will not resolve** until at least 1 player pokemon becomes actionable.
|
||||
*/
|
||||
// TODO: Make this not need to be called twice in doubles tests
|
||||
async toNextTurn() {
|
||||
await this.phaseInterceptor.to(CommandPhase);
|
||||
console.log("==================[New Turn]==================");
|
||||
@ -519,9 +520,9 @@ export default class GameManager {
|
||||
* @returns A promise that resolves once the fainted pokemon's FaintPhase finishes running.
|
||||
*/
|
||||
async killPokemon(pokemon: PlayerPokemon | EnemyPokemon) {
|
||||
pokemon.hp = 0;
|
||||
this.scene.unshiftPhase(new FaintPhase(pokemon.getBattlerIndex(), true));
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
pokemon.hp = 0;
|
||||
this.scene.pushPhase(new FaintPhase(pokemon.getBattlerIndex(), true));
|
||||
await this.phaseInterceptor.to(FaintPhase).catch(e => reject(e));
|
||||
resolve();
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user