mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-09 08:59:29 +02:00
Did some fixes and some cleanups
This commit is contained in:
parent
7abad17030
commit
7ceb601979
@ -70,6 +70,10 @@ export class Battle {
|
|||||||
public battleScore = 0;
|
public battleScore = 0;
|
||||||
public postBattleLoot: PokemonHeldItemModifier[] = [];
|
public postBattleLoot: PokemonHeldItemModifier[] = [];
|
||||||
public escapeAttempts = 0;
|
public escapeAttempts = 0;
|
||||||
|
/**
|
||||||
|
* A tracker of the last {@linkcode MoveId} successfully used this battle.
|
||||||
|
*
|
||||||
|
*/
|
||||||
public lastMove: MoveId = MoveId.NONE;
|
public lastMove: MoveId = MoveId.NONE;
|
||||||
public battleSeed: string = randomString(16, true);
|
public battleSeed: string = randomString(16, true);
|
||||||
private battleSeedState: string | null = null;
|
private battleSeedState: string | null = null;
|
||||||
|
@ -1129,6 +1129,7 @@ export class EncoreTag extends MoveRestrictionBattlerTag {
|
|||||||
constructor(sourceId: number) {
|
constructor(sourceId: number) {
|
||||||
super(
|
super(
|
||||||
BattlerTagType.ENCORE,
|
BattlerTagType.ENCORE,
|
||||||
|
// TODO: This should trigger on turn end
|
||||||
[BattlerTagLapseType.CUSTOM, BattlerTagLapseType.AFTER_MOVE],
|
[BattlerTagLapseType.CUSTOM, BattlerTagLapseType.AFTER_MOVE],
|
||||||
3,
|
3,
|
||||||
MoveId.ENCORE,
|
MoveId.ENCORE,
|
||||||
|
@ -6795,7 +6795,7 @@ export abstract class CallMoveAttr extends OverrideMoveEffectAttr {
|
|||||||
/**
|
/**
|
||||||
* Whether this move should target the user; default `true`.
|
* Whether this move should target the user; default `true`.
|
||||||
* If `true`, will unleash non-spread moves against a random eligible target,
|
* If `true`, will unleash non-spread moves against a random eligible target,
|
||||||
* or else the move's selected target.
|
* as opposed to the move's selected target.
|
||||||
*/
|
*/
|
||||||
override selfTarget = true,
|
override selfTarget = true,
|
||||||
) {
|
) {
|
||||||
@ -6806,7 +6806,7 @@ export abstract class CallMoveAttr extends OverrideMoveEffectAttr {
|
|||||||
* Abstract function yielding the move to be used.
|
* Abstract function yielding the move to be used.
|
||||||
* @param user - The {@linkcode Pokemon} using the move
|
* @param user - The {@linkcode Pokemon} using the move
|
||||||
* @param target - The {@linkcode Pokemon} being targeted by the move
|
* @param target - The {@linkcode Pokemon} being targeted by the move
|
||||||
* @returns The MoveId that will be called and used.
|
* @returns The {@linkcode MoveId} that will be called and used.
|
||||||
*/
|
*/
|
||||||
protected abstract getMove(user: Pokemon, target: Pokemon): MoveId;
|
protected abstract getMove(user: Pokemon, target: Pokemon): MoveId;
|
||||||
|
|
||||||
@ -6830,92 +6830,10 @@ export abstract class CallMoveAttr extends OverrideMoveEffectAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Attribute to call a random move among moves not in a banlist.
|
|
||||||
* Used for {@linkcode MoveId.METRONOME}.
|
|
||||||
*/
|
|
||||||
export class RandomMoveAttr extends CallMoveAttr {
|
|
||||||
constructor(
|
|
||||||
/**
|
|
||||||
* A {@linkcode ReadonlySet} containing all moves that this {@linkcode MoveAttr} cannot copy,
|
|
||||||
* in addition to unimplemented moves and {@linkcode MoveId.NONE}.
|
|
||||||
* The move will fail if the chosen move is inside this banlist (if it exists).
|
|
||||||
*/
|
|
||||||
protected readonly invalidMoves: ReadonlySet<MoveId>,
|
|
||||||
) {
|
|
||||||
super(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pick a random move to execute, barring unimplemented moves and ones
|
|
||||||
* in this move's {@linkcode invalidMetronomeMoves | exclusion list}.
|
|
||||||
* Overridden as public to allow tests to override move choice using mocks.
|
|
||||||
*
|
|
||||||
* @param user - The {@linkcode Pokemon} using the move
|
|
||||||
* @returns The {@linkcode MoveId} that will be called.
|
|
||||||
*/
|
|
||||||
public override getMove(user: Pokemon): MoveId {
|
|
||||||
const moveIds = getEnumValues(MoveId).filter(m => m !== MoveId.NONE && !this.invalidMoves.has(m) && !allMoves[m].name.endsWith(" (N)"));
|
|
||||||
return moveIds[user.randBattleSeedInt(moveIds.length)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attribute used to call a random move in the user or party's moveset.
|
|
||||||
* Used for {@linkcode MoveId.ASSIST} and {@linkcode MoveId.SLEEP_TALK}
|
|
||||||
*
|
|
||||||
* Fails if the user has no callable moves.
|
|
||||||
*/
|
|
||||||
export class RandomMovesetMoveAttr extends RandomMoveAttr {
|
|
||||||
/**
|
|
||||||
* The previously-selected {@linkcode MoveId} for this attribute.
|
|
||||||
* Reset to {@linkcode MoveId.NONE} after successful use.
|
|
||||||
*/
|
|
||||||
private selectedMove: MoveId = MoveId.NONE
|
|
||||||
constructor(invalidMoves: ReadonlySet<MoveId>,
|
|
||||||
/**
|
|
||||||
* Whether to consider all moves from the user's party (`true`) or the user's own moveset (`false`);
|
|
||||||
* default `false`.
|
|
||||||
*/
|
|
||||||
private includeParty = false,
|
|
||||||
) {
|
|
||||||
super(invalidMoves);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select a random move from either the user's or its party members' movesets,
|
|
||||||
* or return an already-selected one if one exists.
|
|
||||||
*
|
|
||||||
* @param user - The {@linkcode Pokemon} using the move.
|
|
||||||
* @returns The {@linkcode MoveId} that will be called.
|
|
||||||
*/
|
|
||||||
override getMove(user: Pokemon): MoveId {
|
|
||||||
if (this.selectedMove) {
|
|
||||||
const m = this.selectedMove;
|
|
||||||
this.selectedMove = MoveId.NONE;
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
// includeParty will be true for Assist, false for Sleep Talk
|
|
||||||
const allies: Pokemon[] = this.includeParty
|
|
||||||
? (user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(p => p !== user)
|
|
||||||
: [ user ];
|
|
||||||
|
|
||||||
// Assist & Sleep Talk consider duplicate moves for their selection (hence why we use an array instead of a set)
|
|
||||||
const moveset = allies.flatMap(p => p.moveset);
|
|
||||||
const eligibleMoves = moveset.filter(m => m.moveId !== MoveId.NONE && !this.invalidMoves.has(m.moveId) && !m.getMove().name.endsWith(" (N)"));
|
|
||||||
this.selectedMove = eligibleMoves[user.randBattleSeedInt(eligibleMoves.length)]?.moveId ?? MoveId.NONE; // will fail if 0 length array
|
|
||||||
return this.selectedMove;
|
|
||||||
}
|
|
||||||
|
|
||||||
override getCondition(): MoveConditionFunc {
|
|
||||||
return (user) => this.getMove(user) !== MoveId.NONE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribute to call a different move based on the current terrain and biome.
|
* Attribute to call a different move based on the current terrain and biome.
|
||||||
* Used by {@linkcode MoveId.NATURE_POWER}
|
* Used by {@linkcode MoveId.NATURE_POWER}.
|
||||||
*/
|
*/
|
||||||
export class NaturePowerAttr extends CallMoveAttr {
|
export class NaturePowerAttr extends CallMoveAttr {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -6924,7 +6842,6 @@ export class NaturePowerAttr extends CallMoveAttr {
|
|||||||
|
|
||||||
override getMove(user: Pokemon): MoveId {
|
override getMove(user: Pokemon): MoveId {
|
||||||
const moveId = this.getMoveIdForTerrain(globalScene.arena.getTerrainType(), globalScene.arena.biomeType)
|
const moveId = this.getMoveIdForTerrain(globalScene.arena.getTerrainType(), globalScene.arena.biomeType)
|
||||||
// Unshift a phase to load the move's animation (in case it isn't already), then use the move.
|
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:naturePowerUse", {
|
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:naturePowerUse", {
|
||||||
pokemonName: getPokemonNameWithAffix(user),
|
pokemonName: getPokemonNameWithAffix(user),
|
||||||
moveName: allMoves[moveId].name,
|
moveName: allMoves[moveId].name,
|
||||||
@ -7024,34 +6941,40 @@ export class NaturePowerAttr extends CallMoveAttr {
|
|||||||
default:
|
default:
|
||||||
// Fallback for no match
|
// Fallback for no match
|
||||||
biome satisfies never;
|
biome satisfies never;
|
||||||
console.warn(`NaturePowerAttr lacks defined move to use for current biome ${toReadableString(BiomeId[biome])}; consider adding an appropriate move to the attribute's selection table.`)
|
console.warn(`NaturePowerAttr lacks defined move to use for current biome ${toTitleCase(BiomeId[biome])}; consider adding an appropriate move to the attribute's selection table.`)
|
||||||
return MoveId.TRI_ATTACK;
|
return MoveId.TRI_ATTACK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribute used to copy the last move executed.
|
* Abstract class to encompass move-copying-moves with a banlist of invalid moves.
|
||||||
* Used for {@linkcode MoveId.COPYCAT} and {@linkcode MoveId.MIRROR_MOVE}.
|
|
||||||
*/
|
*/
|
||||||
export class CopyMoveAttr extends CallMoveAttr {
|
abstract class CallMoveAttrWithBanlist extends CallMoveAttr {
|
||||||
|
/**
|
||||||
|
* A {@linkcode ReadonlySet} containing all moves that this {@linkcode MoveAttr} cannot copy,
|
||||||
|
* in addition to unimplemented moves and {@linkcode MoveId.NONE}.
|
||||||
|
* The move should fail if the chosen move is inside this banlist.
|
||||||
|
*/
|
||||||
|
protected readonly invalidMoves: ReadonlySet<MoveId>
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
/**
|
invalidMoves: ReadonlySet<MoveId>,
|
||||||
* A {@linkcode ReadonlySet} containing all moves that this {@linkcode MoveAttr} cannot copy,
|
|
||||||
* in addition to unimplemented moves and {@linkcode MoveId.NONE}.
|
|
||||||
* The move will fail if the chosen move is inside this banlist (if it exists).
|
|
||||||
*/
|
|
||||||
protected readonly invalidMoves: ReadonlySet<MoveId>,
|
|
||||||
selfTarget = true,
|
selfTarget = true,
|
||||||
) {
|
) {
|
||||||
super(selfTarget);
|
super(selfTarget);
|
||||||
|
this.invalidMoves = invalidMoves;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If `selfTarget` is `true`, grab the last successful move used by anyone.
|
* Attribute used to copy the last move executed, either globally or by the specific target.
|
||||||
* Otherwise, select the last move used by the target.
|
* Used for {@linkcode MoveId.COPYCAT} and {@linkcode MoveId.MIRROR_MOVE}.
|
||||||
*/
|
*/
|
||||||
|
export class CopyMoveAttr extends CallMoveAttrWithBanlist {
|
||||||
override getMove(_user: Pokemon, target: Pokemon): MoveId {
|
override getMove(_user: Pokemon, target: Pokemon): MoveId {
|
||||||
|
// If `selfTarget` is `true`, return the last successful move used by anyone on-field.
|
||||||
|
// Otherwise, select the last move used by the target specifically.
|
||||||
return this.selfTarget
|
return this.selfTarget
|
||||||
? globalScene.currentBattle.lastMove
|
? globalScene.currentBattle.lastMove
|
||||||
: target.getLastXMoves()[0]?.move ?? MoveId.NONE
|
: target.getLastXMoves()[0]?.move ?? MoveId.NONE
|
||||||
@ -7065,7 +6988,85 @@ export class CopyMoveAttr extends CallMoveAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute to call a random move among moves not in a banlist.
|
||||||
|
* Used for {@linkcode MoveId.METRONOME}.
|
||||||
|
*/
|
||||||
|
export class RandomMoveAttr extends CallMoveAttrWithBanlist {
|
||||||
|
constructor(invalidMoves: ReadonlySet<MoveId>) {
|
||||||
|
super(invalidMoves, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick a random move to execute, barring unimplemented moves and ones
|
||||||
|
* in this move's {@linkcode invalidMetronomeMoves | exclusion list}.
|
||||||
|
* Overridden as public to allow tests to override move choice using mocks.
|
||||||
|
*
|
||||||
|
* @param user - The {@linkcode Pokemon} using the move
|
||||||
|
* @returns The {@linkcode MoveId} that will be called.
|
||||||
|
*/
|
||||||
|
public override getMove(user: Pokemon): MoveId {
|
||||||
|
const moveIds = getEnumValues(MoveId).filter(m => m !== MoveId.NONE && !this.invalidMoves.has(m) && !allMoves[m].name.endsWith(" (N)"));
|
||||||
|
return moveIds[user.randBattleSeedInt(moveIds.length)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute used to call a random move in the user or its allies' moveset.
|
||||||
|
* Used for {@linkcode MoveId.ASSIST} and {@linkcode MoveId.SLEEP_TALK}.
|
||||||
|
*
|
||||||
|
* Fails if the user has no callable moves.
|
||||||
|
*/
|
||||||
|
export class RandomMovesetMoveAttr extends RandomMoveAttr {
|
||||||
|
/**
|
||||||
|
* The previously-selected {@linkcode MoveId} for this attribute, or `MoveId.NONE` if none could be found.
|
||||||
|
* Reset to {@linkcode MoveId.NONE} after a successful use.
|
||||||
|
* @defaultValue `MoveId.NONE`
|
||||||
|
*/
|
||||||
|
private selectedMove: MoveId = MoveId.NONE
|
||||||
|
/**
|
||||||
|
* Whether to consider moves from the user's other party members (`true`)
|
||||||
|
* or the user's own moveset (`false`).
|
||||||
|
* @defaultValue `false`.
|
||||||
|
*/
|
||||||
|
private includeParty = false;
|
||||||
|
constructor(invalidMoves: ReadonlySet<MoveId>, includeParty = false) {
|
||||||
|
super(invalidMoves);
|
||||||
|
this.includeParty = includeParty
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a random move from either the user's or its party members' movesets,
|
||||||
|
* or return an already-selected one if one exists.
|
||||||
|
*
|
||||||
|
* @param user - The {@linkcode Pokemon} using the move
|
||||||
|
* @returns The {@linkcode MoveId} that will be called.
|
||||||
|
*/
|
||||||
|
override getMove(user: Pokemon): MoveId {
|
||||||
|
// If we already have a selected move from the condition function,
|
||||||
|
// re-use and reset it rather than generating another random move
|
||||||
|
if (this.selectedMove) {
|
||||||
|
const m = this.selectedMove;
|
||||||
|
this.selectedMove = MoveId.NONE;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `includeParty` will be true for Assist, false for Sleep Talk
|
||||||
|
const allies: Pokemon[] = this.includeParty
|
||||||
|
? (user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(p => p !== user)
|
||||||
|
: [ user ];
|
||||||
|
|
||||||
|
// Assist & Sleep Talk consider duplicate moves for their selection (hence why we use an array instead of a set)
|
||||||
|
const moveset = allies.flatMap(p => p.moveset);
|
||||||
|
const eligibleMoves = moveset.filter(m => m.moveId !== MoveId.NONE && !this.invalidMoves.has(m.moveId) && !m.getMove().name.endsWith(" (N)"));
|
||||||
|
this.selectedMove = eligibleMoves[user.randBattleSeedInt(eligibleMoves.length)]?.moveId ?? MoveId.NONE; // will fail if 0 length array
|
||||||
|
return this.selectedMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
override getCondition(): MoveConditionFunc {
|
||||||
|
return (user) => this.getMove(user) !== MoveId.NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribute used for moves that cause the target to repeat their last used move.
|
* Attribute used for moves that cause the target to repeat their last used move.
|
||||||
|
@ -7,15 +7,26 @@ import { PokemonPhase } from "#phases/pokemon-phase";
|
|||||||
|
|
||||||
export class MoveEndPhase extends PokemonPhase {
|
export class MoveEndPhase extends PokemonPhase {
|
||||||
public readonly phaseName = "MoveEndPhase";
|
public readonly phaseName = "MoveEndPhase";
|
||||||
|
/**
|
||||||
|
* Whether the current move was a follow-up attack or not.
|
||||||
|
* Used to prevent ticking down Encore and similar effects when copying moves.
|
||||||
|
*/
|
||||||
private wasFollowUp: boolean;
|
private wasFollowUp: boolean;
|
||||||
|
/**
|
||||||
|
* Whether the current move successfully executed and showed usage text.
|
||||||
|
* Used to update the "last move used" tracker after successful move usage.
|
||||||
|
*/
|
||||||
|
private passedPreUsageChecks: boolean;
|
||||||
|
|
||||||
/** Targets from the preceding MovePhase */
|
/** Targets from the preceding MovePhase */
|
||||||
private targets: Pokemon[];
|
private targets: Pokemon[];
|
||||||
constructor(battlerIndex: BattlerIndex, targets: Pokemon[], wasFollowUp = false) {
|
|
||||||
|
constructor(battlerIndex: BattlerIndex, targets: Pokemon[], wasFollowUp: boolean, passedPreUsageChecks: boolean) {
|
||||||
super(battlerIndex);
|
super(battlerIndex);
|
||||||
|
|
||||||
this.targets = targets;
|
this.targets = targets;
|
||||||
this.wasFollowUp = wasFollowUp;
|
this.wasFollowUp = wasFollowUp;
|
||||||
|
this.passedPreUsageChecks = passedPreUsageChecks;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
@ -26,6 +37,12 @@ export class MoveEndPhase extends PokemonPhase {
|
|||||||
pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE);
|
pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the "last move used" counter for Copycat and co.
|
||||||
|
if (this.passedPreUsageChecks) {
|
||||||
|
// TODO: Make this check a move in flight instead of a hackjob
|
||||||
|
globalScene.currentBattle.lastMove = pokemon.getLastXMoves()[0].move;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove effects which were set on a Pokemon which removes them on summon (i.e. via Mold Breaker)
|
// Remove effects which were set on a Pokemon which removes them on summon (i.e. via Mold Breaker)
|
||||||
globalScene.arena.setIgnoreAbilities(false);
|
globalScene.arena.setIgnoreAbilities(false);
|
||||||
for (const target of this.targets) {
|
for (const target of this.targets) {
|
||||||
|
@ -42,6 +42,12 @@ export class MovePhase extends BattlePhase {
|
|||||||
protected failed = false;
|
protected failed = false;
|
||||||
/** Whether the current move should fail and retain PP. */
|
/** Whether the current move should fail and retain PP. */
|
||||||
protected cancelled = false;
|
protected cancelled = false;
|
||||||
|
/**
|
||||||
|
* Whether the move was interrupted prior to showing usage text.
|
||||||
|
* Used to set the "last move" pointer after move end.
|
||||||
|
* @defaultValue `true`
|
||||||
|
*/
|
||||||
|
private wasPreInterrupted = true;
|
||||||
|
|
||||||
public get pokemon(): Pokemon {
|
public get pokemon(): Pokemon {
|
||||||
return this._pokemon;
|
return this._pokemon;
|
||||||
@ -294,6 +300,10 @@ export class MovePhase extends BattlePhase {
|
|||||||
const moveQueue = this.pokemon.getMoveQueue();
|
const moveQueue = this.pokemon.getMoveQueue();
|
||||||
const move = this.move.getMove();
|
const move = this.move.getMove();
|
||||||
|
|
||||||
|
// Set flag to update Copycat's "last move" counter
|
||||||
|
// TODO: Verify interaction with a failed Focus Punch
|
||||||
|
this.wasPreInterrupted = false;
|
||||||
|
|
||||||
// form changes happen even before we know that the move wll execute.
|
// form changes happen even before we know that the move wll execute.
|
||||||
globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger);
|
globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger);
|
||||||
|
|
||||||
@ -367,11 +377,6 @@ export class MovePhase extends BattlePhase {
|
|||||||
const move = this.move.getMove();
|
const move = this.move.getMove();
|
||||||
const targets = this.getActiveTargetPokemon();
|
const targets = this.getActiveTargetPokemon();
|
||||||
|
|
||||||
// Update the battle's "last move" pointer unless we're currently mimicking a move or triggering Dancer.
|
|
||||||
if (!move.hasAttr("CopyMoveAttr") && !isReflected(this.useMode)) {
|
|
||||||
globalScene.currentBattle.lastMove = move.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger ability-based user type changes, display move text and then execute move effects.
|
// Trigger ability-based user type changes, display move text and then execute move effects.
|
||||||
// TODO: Investigate whether PokemonTypeChangeAbAttr can drop the "opponent" parameter
|
// TODO: Investigate whether PokemonTypeChangeAbAttr can drop the "opponent" parameter
|
||||||
applyAbAttrs("PokemonTypeChangeAbAttr", { pokemon: this.pokemon, move, opponent: targets[0] });
|
applyAbAttrs("PokemonTypeChangeAbAttr", { pokemon: this.pokemon, move, opponent: targets[0] });
|
||||||
@ -459,6 +464,9 @@ export class MovePhase extends BattlePhase {
|
|||||||
const move = this.move.getMove();
|
const move = this.move.getMove();
|
||||||
const targets = this.getActiveTargetPokemon();
|
const targets = this.getActiveTargetPokemon();
|
||||||
|
|
||||||
|
// Set flag to update Copycat's "last move" counter
|
||||||
|
this.wasPreInterrupted = false;
|
||||||
|
|
||||||
if (!move.applyConditions(this.pokemon, targets[0], move)) {
|
if (!move.applyConditions(this.pokemon, targets[0], move)) {
|
||||||
this.failMove(true);
|
this.failMove(true);
|
||||||
return;
|
return;
|
||||||
@ -490,6 +498,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
this.pokemon.getBattlerIndex(),
|
this.pokemon.getBattlerIndex(),
|
||||||
this.getActiveTargetPokemon(),
|
this.getActiveTargetPokemon(),
|
||||||
isVirtual(this.useMode),
|
isVirtual(this.useMode),
|
||||||
|
this.wasPreInterrupted,
|
||||||
);
|
);
|
||||||
|
|
||||||
super.end();
|
super.end();
|
||||||
|
@ -2,9 +2,9 @@ import { AbilityId } from "#enums/ability-id";
|
|||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { MoveResult } from "#enums/move-result";
|
import { MoveResult } from "#enums/move-result";
|
||||||
import { MoveUseMode } from "#enums/move-use-mode";
|
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import { GameManager } from "#test/test-utils/game-manager";
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
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";
|
||||||
@ -26,7 +26,6 @@ describe("Moves - Copycat", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override
|
game.override
|
||||||
.moveset([MoveId.COPYCAT, MoveId.SPIKY_SHIELD, MoveId.SWORDS_DANCE, MoveId.SPLASH])
|
|
||||||
.ability(AbilityId.BALL_FETCH)
|
.ability(AbilityId.BALL_FETCH)
|
||||||
.battleStyle("single")
|
.battleStyle("single")
|
||||||
.criticalHits(false)
|
.criticalHits(false)
|
||||||
@ -35,24 +34,40 @@ describe("Moves - Copycat", () => {
|
|||||||
.enemyMoveset(MoveId.SPLASH);
|
.enemyMoveset(MoveId.SPLASH);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should copy the last move successfully executed", async () => {
|
it("should copy the last move successfully executed by any Pokemon", async () => {
|
||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||||
|
|
||||||
game.move.select(MoveId.SWORDS_DANCE);
|
game.move.use(MoveId.COPYCAT);
|
||||||
await game.move.forceEnemyMove(MoveId.SPLASH);
|
await game.move.forceEnemyMove(MoveId.SWORDS_DANCE);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
await game.toNextTurn();
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
game.move.select(MoveId.COPYCAT); // Last successful move should be Swords Dance
|
|
||||||
await game.move.forceEnemyMove(MoveId.SUCKER_PUNCH);
|
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
||||||
await game.toNextTurn();
|
|
||||||
|
|
||||||
const player = game.field.getPlayerPokemon();
|
const player = game.field.getPlayerPokemon();
|
||||||
expect(player.getStatStage(Stat.ATK)).toBe(4);
|
expect(player.getStatStage(Stat.ATK)).toBe(2);
|
||||||
expect(player.getLastXMoves()[0].move).toBe(MoveId.SWORDS_DANCE);
|
expect(player.getLastXMoves()[0].move).toBe(MoveId.SWORDS_DANCE);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update "last move" tracker for moves failing conditions, but not pre-move interrupts', async () => {
|
||||||
|
game.override.enemyStatusEffect(StatusEffect.SLEEP);
|
||||||
|
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||||
|
|
||||||
|
game.move.use(MoveId.SUCKER_PUNCH);
|
||||||
|
await game.move.forceEnemyMove(MoveId.SPLASH);
|
||||||
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
|
||||||
|
// Enemy is asleep and should not have updated tracker
|
||||||
|
expect(game.scene.currentBattle.lastMove).toBe(MoveId.NONE);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
|
||||||
|
// Player sucker punch failed conditions, but still updated tracker
|
||||||
|
expect(game.scene.currentBattle.lastMove).toBe(MoveId.SUCKER_PUNCH);
|
||||||
|
|
||||||
|
const player = game.field.getPlayerPokemon();
|
||||||
|
expect(player.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||||
|
});
|
||||||
|
|
||||||
it("should fail if no prior moves have been made", async () => {
|
it("should fail if no prior moves have been made", async () => {
|
||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import { MoveResult } from "#enums/move-result";
|
|||||||
import { MoveUseMode } from "#enums/move-use-mode";
|
import { MoveUseMode } from "#enums/move-use-mode";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import type { RandomMoveAttr } from "#moves/move";
|
|
||||||
import { GameManager } from "#test/test-utils/game-manager";
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
@ -132,7 +131,7 @@ describe("Moves - Metronome", () => {
|
|||||||
expect(solarBeamMove).toBeDefined();
|
expect(solarBeamMove).toBeDefined();
|
||||||
|
|
||||||
game.move.use(MoveId.METRONOME);
|
game.move.use(MoveId.METRONOME);
|
||||||
await game.move.forceEnemyMove(MoveId.SPITE)
|
await game.move.forceEnemyMove(MoveId.SPITE);
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||||
await game.toEndOfTurn();
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
@ -181,7 +180,7 @@ describe("Moves - Metronome", () => {
|
|||||||
const enemyPokemon = game.field.getEnemyPokemon();
|
const enemyPokemon = game.field.getEnemyPokemon();
|
||||||
|
|
||||||
game.move.use(MoveId.METRONOME);
|
game.move.use(MoveId.METRONOME);
|
||||||
await game.toEndOfTurn()
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
const isVisible = enemyPokemon.visible;
|
const isVisible = enemyPokemon.visible;
|
||||||
const hasFled = enemyPokemon.switchOutStatus;
|
const hasFled = enemyPokemon.switchOutStatus;
|
||||||
|
@ -39,13 +39,14 @@ describe("Moves - Mirror Move", () => {
|
|||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
||||||
|
|
||||||
game.move.use(MoveId.MIRROR_MOVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
game.move.use(MoveId.MIRROR_MOVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||||
game.move.use(MoveId.SWORDS_DANCE, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2);
|
game.move.use(MoveId.MIRROR_MOVE, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2);
|
||||||
await game.move.forceEnemyMove(MoveId.TACKLE, BattlerIndex.PLAYER_2);
|
await game.move.forceEnemyMove(MoveId.TACKLE, BattlerIndex.PLAYER_2);
|
||||||
await game.move.forceEnemyMove(MoveId.SWORDS_DANCE);
|
await game.move.forceEnemyMove(MoveId.SWORDS_DANCE);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER]);
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER]);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
// Feebas copied enemy tackle against it
|
// Feebas copied enemy tackle against it;
|
||||||
|
// player 2 copied enemy swords dance and used it on itself
|
||||||
const [feebas, magikarp] = game.scene.getPlayerField();
|
const [feebas, magikarp] = game.scene.getPlayerField();
|
||||||
expect(feebas.getLastXMoves()[0]).toMatchObject({
|
expect(feebas.getLastXMoves()[0]).toMatchObject({
|
||||||
move: MoveId.TACKLE,
|
move: MoveId.TACKLE,
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { allMoves } from "#app/data/data-lists";
|
import { allMoves } from "#app/data/data-lists";
|
||||||
import { TerrainType } from "#app/data/terrain";
|
import { TerrainType } from "#app/data/terrain";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { getEnumValues, toReadableString } from "#app/utils/common";
|
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { BiomeId } from "#enums/biome-id";
|
import { BiomeId } from "#enums/biome-id";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import GameManager from "#test/testUtils/gameManager";
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
|
import { getEnumValues } from "#utils/enums";
|
||||||
|
import { toTitleCase } from "#utils/strings";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
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";
|
||||||
@ -43,7 +44,7 @@ describe("Move - Nature Power", () => {
|
|||||||
it.each(
|
it.each(
|
||||||
getEnumValues(BiomeId).map(biome => ({
|
getEnumValues(BiomeId).map(biome => ({
|
||||||
move: getNaturePowerType(TerrainType.NONE, biome),
|
move: getNaturePowerType(TerrainType.NONE, biome),
|
||||||
moveName: toReadableString(MoveId[getNaturePowerType(TerrainType.NONE, biome)]),
|
moveName: toTitleCase(MoveId[getNaturePowerType(TerrainType.NONE, biome)]),
|
||||||
biome,
|
biome,
|
||||||
biomeName: BiomeId[biome],
|
biomeName: BiomeId[biome],
|
||||||
})),
|
})),
|
||||||
@ -68,11 +69,11 @@ describe("Move - Nature Power", () => {
|
|||||||
it.todo.each(
|
it.todo.each(
|
||||||
getEnumValues(TerrainType).map(terrain => ({
|
getEnumValues(TerrainType).map(terrain => ({
|
||||||
move: getNaturePowerType(terrain, BiomeId.TOWN),
|
move: getNaturePowerType(terrain, BiomeId.TOWN),
|
||||||
moveName: toReadableString(MoveId[getNaturePowerType(terrain, BiomeId.TOWN)]),
|
moveName: toTitleCase(MoveId[getNaturePowerType(terrain, BiomeId.TOWN)]),
|
||||||
terrain: terrain,
|
terrain: terrain,
|
||||||
terrainName: TerrainType[terrain],
|
terrainName: TerrainType[terrain],
|
||||||
})),
|
})),
|
||||||
)("should select $moveName if the current terrain is $terrainName", async ({ move /* terrain */ }) => {
|
)("should select $moveName if the current terrain is $terrainName", async ({ move /*, terrain */ }) => {
|
||||||
// game.override.terrain(terrainType);
|
// game.override.terrain(terrainType);
|
||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||||
|
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import { allMoves } from "#data/data-lists";
|
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { MoveResult } from "#enums/move-result";
|
import { MoveResult } from "#enums/move-result";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import { RandomMoveAttr } from "#moves/move";
|
|
||||||
import { PokemonMove } from "#moves/pokemon-move";
|
import { PokemonMove } from "#moves/pokemon-move";
|
||||||
import { GameManager } from "#test/test-utils/game-manager";
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
describe("Moves - Sketch", () => {
|
describe("Moves - Sketch", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -81,20 +79,18 @@ describe("Moves - Sketch", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should sketch moves that call other moves", async () => {
|
it("should sketch moves that call other moves", async () => {
|
||||||
const randomMoveAttr = allMoves[MoveId.METRONOME].findAttr(
|
game.move.forceMetronomeMove(MoveId.FALSE_SWIPE);
|
||||||
attr => attr instanceof RandomMoveAttr,
|
|
||||||
) as RandomMoveAttr;
|
|
||||||
vi.spyOn(randomMoveAttr, "getMove").mockReturnValue(MoveId.FALSE_SWIPE);
|
|
||||||
|
|
||||||
game.override.enemyMoveset([MoveId.METRONOME]);
|
|
||||||
await game.classicMode.startBattle([SpeciesId.REGIELEKI]);
|
await game.classicMode.startBattle([SpeciesId.REGIELEKI]);
|
||||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
|
||||||
playerPokemon.moveset = [new PokemonMove(MoveId.SKETCH)];
|
const playerPokemon = game.field.getPlayerPokemon()!;
|
||||||
|
game.move.changeMoveset(playerPokemon, MoveId.SKETCH);
|
||||||
|
|
||||||
// Opponent uses Metronome -> False Swipe, then player uses Sketch, which should sketch Metronome
|
// Opponent uses Metronome -> False Swipe, then player uses Sketch, which should sketch Metronome
|
||||||
game.move.select(MoveId.SKETCH);
|
game.move.select(MoveId.SKETCH);
|
||||||
|
await game.move.forceEnemyMove(MoveId.METRONOME);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||||
expect(playerPokemon.moveset[0]?.moveId).toBe(MoveId.METRONOME);
|
expect(playerPokemon.moveset[0]?.moveId).toBe(MoveId.METRONOME);
|
||||||
expect(playerPokemon.hp).toBeLessThan(playerPokemon.getMaxHp()); // Make sure opponent actually used False Swipe
|
expect(playerPokemon.hp).toBeLessThan(playerPokemon.getMaxHp()); // Make sure opponent actually used False Swipe
|
||||||
|
@ -331,7 +331,7 @@ export class MoveHelper extends GameManagerHelper {
|
|||||||
* @returns The spy that for Metronome that was mocked (Usually unneeded).
|
* @returns The spy that for Metronome that was mocked (Usually unneeded).
|
||||||
*/
|
*/
|
||||||
public forceMetronomeMove(move: MoveId, once = false): MockInstance {
|
public forceMetronomeMove(move: MoveId, once = false): MockInstance {
|
||||||
const spy = vi.spyOn(allMoves[MoveId.METRONOME].getAttrs("RandomMoveAttr")[0], "getMoveOverride");
|
const spy = vi.spyOn(allMoves[MoveId.METRONOME].getAttrs("RandomMoveAttr")[0], "getMove");
|
||||||
if (once) {
|
if (once) {
|
||||||
spy.mockReturnValueOnce(move);
|
spy.mockReturnValueOnce(move);
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user