Merge remote-tracking branch 'upstream/beta' into move-use-type

This commit is contained in:
Bertie690 2025-06-08 13:27:02 -04:00
commit 3e05489e9e
4 changed files with 84 additions and 51 deletions

View File

@ -70,14 +70,14 @@ import { CommonAnim } from "../battle-anims";
import { getBerryEffectFunc } from "#app/data/berry"; import { getBerryEffectFunc } from "#app/data/berry";
import { BerryUsedEvent } from "#app/events/battle-scene"; import { BerryUsedEvent } from "#app/events/battle-scene";
import { noAbilityTypeOverrideMoves } from "#app/data/moves/invalid-moves"; import { noAbilityTypeOverrideMoves } from "#app/data/moves/invalid-moves";
import { MoveUseMode } from "#enums/move-use-mode"; import { isVirtual, MoveUseMode } from "#enums/move-use-mode";
// Type imports // Type imports
import type { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import type { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import type { BattleStat, EffectiveStat } from "#enums/stat"; import type { BattleStat, EffectiveStat } from "#enums/stat";
import type { BerryType } from "#enums/berry-type"; import type { BerryType } from "#enums/berry-type";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import type { EnemyPokemon, PokemonMove, TurnMove } from "#app/field/pokemon";
import type { Weather } from "#app/data/weather"; import type { Weather } from "#app/data/weather";
import type { BattlerTag } from "#app/data/battler-tags"; import type { BattlerTag } from "#app/data/battler-tags";
import type { import type {
@ -2536,48 +2536,32 @@ export class AllyStatMultiplierAbAttr extends AbAttr {
} }
/** /**
* Ability attribute for Gorilla Tactics * Takes effect whenever a move succesfully executes, such as gorilla tactics' move-locking.
* @extends PostAttackAbAttr * (More specifically, whenever a move is pushed to the move history)
*/ */
export class GorillaTacticsAbAttr extends PostAttackAbAttr { export class ExecutedMoveAbAttr extends AbAttr {
constructor() { canApplyExecutedMove(_pokemon: Pokemon, _simulated: boolean): boolean {
super((_user, _target, _move) => true, false); return true;
} }
override canApplyPostAttack( applyExecutedMove(_pokemon: Pokemon, _simulated: boolean): void {}
pokemon: Pokemon,
passive: boolean,
simulated: boolean,
defender: Pokemon,
move: Move,
hitResult: HitResult | null,
args: any[],
): boolean {
return (
(super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) && simulated) ||
!pokemon.getTag(BattlerTagType.GORILLA_TACTICS)
);
} }
/** /**
* * Ability attribute for {@linkcode AbilityId.GORILLA_TACTICS | Gorilla Tactics}
* @param {Pokemon} pokemon the {@linkcode Pokemon} with this ability * to lock the user into its first selected move.
* @param _passive n/a
* @param simulated whether the ability is being simulated
* @param _defender n/a
* @param _move n/a
* @param _hitResult n/a
* @param _args n/a
*/ */
override applyPostAttack( export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr {
pokemon: Pokemon, constructor(showAbility = false) {
_passive: boolean, super(showAbility);
simulated: boolean, }
_defender: Pokemon,
_move: Move, override canApplyExecutedMove(pokemon: Pokemon, _simulated: boolean): boolean {
_hitResult: HitResult | null, // Gorilla Tactics does not trigger on called/reflected moves, only the calling move.
_args: any[], return !pokemon.canAddTag(BattlerTagType.GORILLA_TACTICS);
): void { }
override applyExecutedMove(pokemon: Pokemon, simulated: boolean): void {
if (!simulated) { if (!simulated) {
pokemon.addTag(BattlerTagType.GORILLA_TACTICS); pokemon.addTag(BattlerTagType.GORILLA_TACTICS);
} }
@ -7800,6 +7784,22 @@ export function applyPreAttackAbAttrs(
); );
} }
export function applyExecutedMoveAbAttrs(
attrType: Constructor<ExecutedMoveAbAttr>,
pokemon: Pokemon,
simulated: boolean = false,
...args: any[]
): void {
applyAbAttrsInternal<ExecutedMoveAbAttr>(
attrType,
pokemon,
attr => attr.applyExecutedMove(pokemon, simulated),
attr => attr.canApplyExecutedMove(pokemon, simulated),
args,
simulated,
);
}
export function applyPostAttackAbAttrs( export function applyPostAttackAbAttrs(
attrType: Constructor<PostAttackAbAttr>, attrType: Constructor<PostAttackAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,

View File

@ -362,7 +362,6 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
/** /**
* Tag used by Gorilla Tactics to restrict the user to using only one move. * Tag used by Gorilla Tactics to restrict the user to using only one move.
* @extends MoveRestrictionBattlerTag
*/ */
export class GorillaTacticsTag extends MoveRestrictionBattlerTag { export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
private moveId = MoveId.NONE; private moveId = MoveId.NONE;
@ -371,7 +370,6 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
super(BattlerTagType.GORILLA_TACTICS, BattlerTagLapseType.CUSTOM, 0); super(BattlerTagType.GORILLA_TACTICS, BattlerTagLapseType.CUSTOM, 0);
} }
/** @override */
override isMoveRestricted(move: MoveId): boolean { override isMoveRestricted(move: MoveId): boolean {
return move !== this.moveId; return move !== this.moveId;
} }
@ -386,8 +384,7 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
const lastSelectedMove = pokemon.getLastNonVirtualMove(); const lastSelectedMove = pokemon.getLastNonVirtualMove();
return ( return (
!isNullOrUndefined(lastSelectedMove) && !isNullOrUndefined(lastSelectedMove) &&
lastSelectedMove.move !== MoveId.STRUGGLE && lastSelectedMove.move !== MoveId.STRUGGLE
!pokemon.getTag(GorillaTacticsTag)
); );
} }
@ -408,19 +405,17 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
* @override * @override
* @param source Gorilla Tactics' {@linkcode BattlerTag} information * @param source Gorilla Tactics' {@linkcode BattlerTag} information
*/ */
public override loadTag(source: BattlerTag | any): void { override loadTag(source: BattlerTag | any): void {
super.loadTag(source); super.loadTag(source);
this.moveId = source.moveId; this.moveId = source.moveId;
} }
/** /**
* * Return the text displayed when a move is restricted.
* @override * @param pokemon - The {@linkcode Pokemon} with this tag.
* @param {Pokemon} pokemon n/a * @returns A string containing the text to display when the move is denied
* @param {MoveId} _move {@linkcode MoveId} ID of the move being denied
* @returns {string} text to display when the move is denied
*/ */
override selectionDeniedText(pokemon: Pokemon, _move: MoveId): string { override selectionDeniedText(pokemon: Pokemon): string {
return i18next.t("battle:canOnlyUseMove", { return i18next.t("battle:canOnlyUseMove", {
moveName: allMoves[this.moveId].name, moveName: allMoves[this.moveId].name,
pokemonName: getPokemonNameWithAffix(pokemon), pokemonName: getPokemonNameWithAffix(pokemon),

View File

@ -3,10 +3,12 @@ import { globalScene } from "#app/global-scene";
import { import {
AddSecondStrikeAbAttr, AddSecondStrikeAbAttr,
AlwaysHitAbAttr, AlwaysHitAbAttr,
applyExecutedMoveAbAttrs,
applyPostAttackAbAttrs, applyPostAttackAbAttrs,
applyPostDamageAbAttrs, applyPostDamageAbAttrs,
applyPostDefendAbAttrs, applyPostDefendAbAttrs,
applyPreAttackAbAttrs, applyPreAttackAbAttrs,
ExecutedMoveAbAttr,
IgnoreMoveEffectsAbAttr, IgnoreMoveEffectsAbAttr,
MaxMultiHitAbAttr, MaxMultiHitAbAttr,
PostAttackAbAttr, PostAttackAbAttr,
@ -389,6 +391,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Add to the move history entry // Add to the move history entry
if (this.firstHit) { if (this.firstHit) {
user.pushMoveHistory(this.moveHistoryEntry); user.pushMoveHistory(this.moveHistoryEntry);
applyExecutedMoveAbAttrs(ExecutedMoveAbAttr, user);
} }
try { try {

View File

@ -7,6 +7,8 @@ import { Stat } from "#app/enums/stat";
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, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { MoveResult } from "#app/field/pokemon";
import { MoveUseMode } from "#enums/move-use-mode";
describe("Abilities - Gorilla Tactics", () => { describe("Abilities - Gorilla Tactics", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -40,7 +42,7 @@ describe("Abilities - Gorilla Tactics", () => {
game.move.select(MoveId.SPLASH); game.move.select(MoveId.SPLASH);
await game.move.forceEnemyMove(MoveId.SPLASH); await game.move.forceEnemyMove(MoveId.SPLASH);
await game.toEndOfTurn() await game.toEndOfTurn();
expect(darmanitan.getStat(Stat.ATK, false)).toBeCloseTo(initialAtkStat * 1.5); expect(darmanitan.getStat(Stat.ATK, false)).toBeCloseTo(initialAtkStat * 1.5);
// Other moves should be restricted // Other moves should be restricted
@ -69,6 +71,7 @@ describe("Abilities - Gorilla Tactics", () => {
// Third turn, Struggle is used // Third turn, Struggle is used
game.move.select(MoveId.TACKLE); game.move.select(MoveId.TACKLE);
await game.move.forceEnemyMove(MoveId.SPLASH); //prevent protect from being used by the enemy
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to("MoveEndPhase"); await game.phaseInterceptor.to("MoveEndPhase");
@ -90,5 +93,37 @@ describe("Abilities - Gorilla Tactics", () => {
// Gorilla Tactics should bypass dancer and instruct // Gorilla Tactics should bypass dancer and instruct
expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(true); expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(true);
expect(darmanitan.isMoveRestricted(MoveId.METRONOME)).toBe(false); expect(darmanitan.isMoveRestricted(MoveId.METRONOME)).toBe(false);
expect(darmanitan.getLastXMoves(-1)).toEqual([
expect.objectContaining({ move: MoveId.TACKLE, result: MoveResult.SUCCESS, useMode: MoveUseMode.FOLLOW_UP }),
expect.objectContaining({ move: MoveId.METRONOME, result: MoveResult.SUCCESS, useMode: MoveUseMode.NORMAL }),
]);
});
it("should activate when the opponenet protects", async () => {
await game.classicMode.startBattle([SpeciesId.GALAR_DARMANITAN]);
const darmanitan = game.field.getPlayerPokemon();
game.move.select(MoveId.TACKLE);
await game.move.selectEnemyMove(MoveId.PROTECT);
await game.toEndOfTurn();
expect(darmanitan.isMoveRestricted(MoveId.SPLASH)).toBe(true);
expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(false);
});
it("should activate when a move is succesfully executed but misses", async () => {
await game.classicMode.startBattle([SpeciesId.GALAR_DARMANITAN]);
const darmanitan = game.field.getPlayerPokemon();
game.move.select(MoveId.TACKLE);
await game.move.selectEnemyMove(MoveId.SPLASH);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.move.forceMiss();
await game.toEndOfTurn();
expect(darmanitan.isMoveRestricted(MoveId.SPLASH)).toBe(true);
expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(false);
}); });
}); });