Use BattlerTag for move-disabling effects

This commit is contained in:
Zach Day 2024-06-10 13:57:36 -04:00
parent 7ce209dd02
commit 7847d1c445
7 changed files with 117 additions and 91 deletions

View File

@ -971,22 +971,16 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
}
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (!attacker.summonData.disabledMove) {
if (attacker.getTag(BattlerTagType.DISABLED) === null) {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !attacker.isMax()) {
this.attacker = attacker;
this.move = move;
attacker.summonData.disabledMove = move.id;
attacker.summonData.disabledTurns = 4;
this.attacker.addTag(BattlerTagType.DISABLED, 4, 0, pokemon.id);
return true;
}
}
return false;
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
return getPokemonMessage(this.attacker, `'s ${this.move.name}\nwas disabled!`);
}
}
export class PostStatChangeStatChangeAbAttr extends PostStatChangeAbAttr {

View File

@ -1,5 +1,5 @@
import { CommonAnim, CommonBattleAnim } from "./battle-anims";
import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases";
import { CommonAnimPhase, MessagePhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases";
import { getPokemonMessage, getPokemonNameWithAffix } from "../messages";
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
import { Stat, getStatName } from "./pokemon-stat";
@ -17,6 +17,7 @@ import { BattleStat } from "./battle-stat";
import { allAbilities } from "./ability";
import { SpeciesFormChangeManualTrigger } from "./pokemon-forms";
import { Species } from "./enums/species";
import i18next from "i18next";
export enum BattlerTagLapseType {
FAINT,
@ -91,6 +92,84 @@ export interface TerrainBattlerTag {
terrainTypes: TerrainType[];
}
/**
* Base class for tags that disable moves. Descendants can override {@linkcode moveIsDisabled} to disable moves that match a condition.
*/
export abstract class DisablingBattlerTag extends BattlerTag {
public abstract moveIsDisabled(move: Moves): boolean;
constructor(tagType: BattlerTagType, lapseType?: BattlerTagLapseType, turnCount?: integer, sourceMove?: Moves, sourceId?: integer) {
super(tagType, lapseType ?? BattlerTagLapseType.TURN_END, turnCount ?? 3, sourceMove, sourceId);
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (!super.lapse(pokemon, lapseType)) {
return false;
}
return true;
}
}
/**
* Tag representing the "disabling" effect performed by {@linkcode Moves.DISABLE} and {@linkcode Abilities.CURSED_BODY}.
* When the tag is added, the last used move of the tag holder is set as the disabled move.
*/
export class DisabledTag extends DisablingBattlerTag {
/** The move being disabled. Gets set when {@linkcode onAdd} is called for this tag. */
private moveId: integer = 0;
public override moveIsDisabled(move: Moves): boolean {
return move === this.moveId;
}
constructor(turnCount: integer, sourceId: integer) {
super(BattlerTagType.DISABLED, BattlerTagLapseType.TURN_END, turnCount, Moves.DISABLE, sourceId);
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (!super.lapse(pokemon, lapseType)) {
return false;
}
if (this.moveId === 0) {
console.warn(`attempt to disable move ID 0 on ${pokemon}`);
return false;
}
return true;
}
onAdd(pokemon: Pokemon): void {
const history = pokemon.getLastXMoves();
if (history.length === 0) {
return;
}
const move = history.find(m => m.move !== Moves.NONE);
if (move === undefined) {
return;
}
this.moveId = move.move;
pokemon.scene.queueMessage(this.generateAddMessage(pokemon));
}
onRemove(pokemon: Pokemon): void {
if (this.moveId === 0) {
return;
}
pokemon.scene.pushPhase(new MessagePhase(pokemon.scene, i18next.t("battle:notDisabled", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: allMoves[this.moveId].name })));
}
private generateAddMessage(pokemon: Pokemon): string {
return getPokemonMessage(pokemon, `'s ${allMoves[this.moveId].name}\nwas disabled!`);
}
}
export class RechargingTag extends BattlerTag {
constructor(sourceMove: Moves) {
super(BattlerTagType.RECHARGING, BattlerTagLapseType.PRE_MOVE, 1, sourceMove);
@ -1522,6 +1601,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc
return new DestinyBondTag(sourceMove, sourceId);
case BattlerTagType.ICE_FACE:
return new IceFaceTag(sourceMove);
case BattlerTagType.DISABLED:
return new DisabledTag(turnCount, sourceId);
case BattlerTagType.NONE:
default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -58,5 +58,6 @@ export enum BattlerTagType {
MAGNET_RISEN = "MAGNET_RISEN",
MINIMIZED = "MINIMIZED",
DESTINY_BOND = "DESTINY_BOND",
ICE_FACE = "ICE_FACE"
ICE_FACE = "ICE_FACE",
DISABLED = "DISABLED"
}

View File

@ -3690,70 +3690,6 @@ export class TypelessAttr extends MoveAttr { }
*/
export class BypassRedirectAttr extends MoveAttr { }
export class DisableMoveAttr extends MoveEffectAttr {
constructor() {
super(false);
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!super.apply(user, target, move, args)) {
return false;
}
const moveQueue = target.getLastXMoves();
let turnMove: TurnMove;
while (moveQueue.length) {
turnMove = moveQueue.shift();
if (turnMove.virtual) {
continue;
}
const moveIndex = target.getMoveset().findIndex(m => m.moveId === turnMove.move);
if (moveIndex === -1) {
return false;
}
const disabledMove = target.getMoveset()[moveIndex];
target.summonData.disabledMove = disabledMove.moveId;
target.summonData.disabledTurns = 4;
user.scene.queueMessage(getPokemonMessage(target, `'s ${disabledMove.getName()}\nwas disabled!`));
return true;
}
return false;
}
getCondition(): MoveConditionFunc {
return (user, target, move) => {
if (target.summonData.disabledMove || target.isMax()) {
return false;
}
const moveQueue = target.getLastXMoves();
let turnMove: TurnMove;
while (moveQueue.length) {
turnMove = moveQueue.shift();
if (turnMove.virtual) {
continue;
}
const move = target.getMoveset().find(m => m.moveId === turnMove.move);
if (!move) {
continue;
}
return true;
}
};
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return -5;
}
}
export class FrenzyAttr extends MoveEffectAttr {
constructor() {
super(true, MoveEffectTrigger.HIT);
@ -3842,6 +3778,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
case BattlerTagType.NIGHTMARE:
case BattlerTagType.DROWSY:
case BattlerTagType.NO_CRIT:
case BattlerTagType.DISABLED:
return -5;
case BattlerTagType.SEEDED:
case BattlerTagType.SALT_CURED:
@ -5306,6 +5243,9 @@ export class hitsSameTypeAttr extends VariableMoveTypeMultiplierAttr {
const unknownTypeCondition: MoveConditionFunc = (user, target, move) => !user.getTypes().includes(Type.UNKNOWN);
/** Ensures that the target has at least one non-virtual, non-NONE move in its history. */
const targetHasMoveHistoryCondition: MoveConditionFunc = (user, target, move) => target.getLastXMoves().filter(m => m.move !== Moves.NONE && !m.virtual).length >= 1;
export type MoveTargetSet = {
targets: BattlerIndex[];
multiple: boolean;
@ -5506,7 +5446,8 @@ export function initMoves() {
new AttackMove(Moves.SONIC_BOOM, Type.NORMAL, MoveCategory.SPECIAL, -1, 90, 20, -1, 0, 1)
.attr(FixedDamageAttr, 20),
new StatusMove(Moves.DISABLE, Type.NORMAL, 100, 20, -1, 0, 1)
.attr(DisableMoveAttr)
.attr(AddBattlerTagAttr, BattlerTagType.DISABLED, false, true, 4, 4)
.condition(targetHasMoveHistoryCondition)
.condition(failOnMaxCondition),
new AttackMove(Moves.ACID, Type.POISON, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
.attr(StatChangeAttr, BattleStat.SPDEF, -1)

View File

@ -19,7 +19,7 @@ import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEv
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
import { DamagePhase, FaintPhase, LearnMovePhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase } from "../phases";
import { BattleStat } from "../data/battle-stat";
import { BattlerTag, BattlerTagLapseType, EncoreTag, HelpingHandTag, HighestStatBoostTag, TypeBoostTag, getBattlerTag } from "../data/battler-tags";
import { BattlerTag, BattlerTagLapseType, DisablingBattlerTag, EncoreTag, HelpingHandTag, HighestStatBoostTag, TypeBoostTag, getBattlerTag } from "../data/battler-tags";
import { BattlerTagType } from "../data/enums/battler-tag-type";
import { Species } from "../data/enums/species";
import { WeatherType } from "../data/weather";
@ -2139,6 +2139,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.updateInfo();
}
/**
* Gets whether a move is currently disabled for this Pokemon.
* @see {@linkcode DisablingBattlerTag}
*/
isMoveDisabled(moveId: Moves): boolean {
for (const tag of this.findTags(t => t instanceof DisablingBattlerTag)) {
if ((tag as DisablingBattlerTag).moveIsDisabled(moveId)) {
return true;
}
}
}
getMoveHistory(): TurnMove[] {
return this.battleSummonData.moveHistory;
}
@ -3749,8 +3761,6 @@ export interface AttackMoveResult {
export class PokemonSummonData {
public battleStats: integer[] = [ 0, 0, 0, 0, 0, 0, 0 ];
public moveQueue: QueuedMove[] = [];
public disabledMove: Moves = Moves.NONE;
public disabledTurns: integer = 0;
public tags: BattlerTag[] = [];
public abilitySuppressed: boolean = false;
@ -3844,9 +3854,16 @@ export class PokemonMove {
}
isUsable(pokemon: Pokemon, ignorePp?: boolean): boolean {
if (this.moveId && pokemon.summonData?.disabledMove === this.moveId) {
if (!this.moveId) {
return false;
}
for (const tag of pokemon.findTags(t => t instanceof DisablingBattlerTag)) {
if ((tag as DisablingBattlerTag).moveIsDisabled(this.moveId)) {
return false;
}
}
return (ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1) && !this.getMove().name.endsWith(" (N)");
}

View File

@ -1916,7 +1916,7 @@ export class CommandPhase extends FieldPhase {
// Decides between a Disabled, Not Implemented, or No PP translation message
const errorMessage =
playerPokemon.summonData.disabledMove === move.moveId ? "battle:moveDisabled" :
playerPokemon.isMoveDisabled(move.moveId) ? "battle:moveDisabled" :
move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP";
const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator
@ -2355,11 +2355,6 @@ export class TurnEndPhase extends FieldPhase {
const handlePokemon = (pokemon: Pokemon) => {
pokemon.lapseTags(BattlerTagLapseType.TURN_END);
if (pokemon.summonData.disabledMove && !--pokemon.summonData.disabledTurns) {
this.scene.pushPhase(new MessagePhase(this.scene, i18next.t("battle:notDisabled", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: allMoves[pokemon.summonData.disabledMove].name })));
pokemon.summonData.disabledMove = Moves.NONE;
}
this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon);
if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
@ -2521,10 +2516,9 @@ export class MovePhase extends BattlePhase {
console.log(Moves[this.move.moveId]);
if (!this.canMove()) {
if (this.move.moveId && this.pokemon.summonData?.disabledMove === this.move.moveId) {
this.scene.queueMessage(`${this.move.getName()} is disabled!`);
}
return this.end();
this.fail();
this.showMoveText();
this.showFailedText();
}
if (!this.followUp) {

View File

@ -118,8 +118,6 @@ export default class PokemonData {
if (!forHistory && source.summonData) {
this.summonData.battleStats = source.summonData.battleStats;
this.summonData.moveQueue = source.summonData.moveQueue;
this.summonData.disabledMove = source.summonData.disabledMove;
this.summonData.disabledTurns = source.summonData.disabledTurns;
this.summonData.abilitySuppressed = source.summonData.abilitySuppressed;
this.summonData.ability = source.summonData.ability;