[Misc] Fix missing types in battler tags and remove DragonCheerTag (#6188)

* Fix missing types in battler tags

* Fix issues with dragon cheer
This commit is contained in:
Sirz Benjie 2025-08-01 12:51:11 -06:00 committed by GitHub
parent 1c59b67d7e
commit e50ebaa815
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 63 additions and 32 deletions

View File

@ -88,6 +88,15 @@ export type AbilityBattlerTagType =
| BattlerTagType.SLOW_START | BattlerTagType.SLOW_START
| BattlerTagType.TRUANT; | BattlerTagType.TRUANT;
/** Subset of {@linkcode BattlerTagType}s that provide type boosts */
export type TypeBoostTagType = BattlerTagType.FIRE_BOOST | BattlerTagType.CHARGED;
/** Subset of {@linkcode BattlerTagType}s that boost the user's critical stage */
export type CritStageBoostTagType = BattlerTagType.CRIT_BOOST | BattlerTagType.DRAGON_CHEER;
/** Subset of {@linkcode BattlerTagType}s that remove one of the users' types */
export type RemovedTypeTagType = BattlerTagType.DOUBLE_SHOCKED | BattlerTagType.BURNED_UP;
/** /**
* Subset of {@linkcode BattlerTagType}s related to abilities that boost the highest stat. * Subset of {@linkcode BattlerTagType}s related to abilities that boost the highest stat.
*/ */

View File

@ -34,15 +34,19 @@ import type { StatStageChangeCallback } from "#phases/stat-stage-change-phase";
import i18next from "#plugins/i18n"; import i18next from "#plugins/i18n";
import type { import type {
AbilityBattlerTagType, AbilityBattlerTagType,
BattlerTagTypeData,
ContactSetStatusProtectedTagType, ContactSetStatusProtectedTagType,
ContactStatStageChangeProtectedTagType, ContactStatStageChangeProtectedTagType,
CritStageBoostTagType,
DamageProtectedTagType, DamageProtectedTagType,
EndureTagType, EndureTagType,
HighestStatBoostTagType, HighestStatBoostTagType,
MoveRestrictionBattlerTagType, MoveRestrictionBattlerTagType,
ProtectionBattlerTagType, ProtectionBattlerTagType,
RemovedTypeTagType,
SemiInvulnerableTagType, SemiInvulnerableTagType,
TrappingBattlerTagType, TrappingBattlerTagType,
TypeBoostTagType,
} from "#types/battler-tags"; } from "#types/battler-tags";
import type { Mutable } from "#types/type-helpers"; import type { Mutable } from "#types/type-helpers";
import { BooleanHolder, coerceArray, getFrameMs, isNullOrUndefined, NumberHolder, toDmgValue } from "#utils/common"; import { BooleanHolder, coerceArray, getFrameMs, isNullOrUndefined, NumberHolder, toDmgValue } from "#utils/common";
@ -203,6 +207,19 @@ export class SerializableBattlerTag extends BattlerTag {
private declare __SerializableBattlerTag: never; private declare __SerializableBattlerTag: never;
} }
/**
* Interface for a generic serializable battler tag, i.e. one that does not have a
* dedicated subclass.
*
* @remarks
* Used to ensure type safety when serializing battler tags,
* allowing typescript to properly infer the type of the tag.
* @see BattlerTagTypeMap
*/
interface GenericSerializableBattlerTag<T extends BattlerTagType> extends SerializableBattlerTag {
tagType: T;
}
/** /**
* Base class for tags that restrict the usage of moves. This effect is generally referred to as "disabling" a move * Base class for tags that restrict the usage of moves. This effect is generally referred to as "disabling" a move
* in-game (not to be confused with {@linkcode MoveId.DISABLE}). * in-game (not to be confused with {@linkcode MoveId.DISABLE}).
@ -560,6 +577,7 @@ export class BeakBlastChargingTag extends BattlerTag {
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Shell_Trap_(move) | Shell Trap} * @see {@link https://bulbapedia.bulbagarden.net/wiki/Shell_Trap_(move) | Shell Trap}
*/ */
export class ShellTrapTag extends BattlerTag { export class ShellTrapTag extends BattlerTag {
public override readonly tagType = BattlerTagType.SHELL_TRAP;
public activated = false; public activated = false;
constructor() { constructor() {
@ -1144,6 +1162,7 @@ export class PowderTag extends BattlerTag {
} }
export class NightmareTag extends SerializableBattlerTag { export class NightmareTag extends SerializableBattlerTag {
public override readonly tagType = BattlerTagType.NIGHTMARE;
constructor() { constructor() {
super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.TURN_END, 1, MoveId.NIGHTMARE); super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.TURN_END, 1, MoveId.NIGHTMARE);
} }
@ -1197,6 +1216,7 @@ export class NightmareTag extends SerializableBattlerTag {
} }
export class FrenzyTag extends SerializableBattlerTag { export class FrenzyTag extends SerializableBattlerTag {
public override readonly tagType = BattlerTagType.FRENZY;
constructor(turnCount: number, sourceMove: MoveId, sourceId: number) { constructor(turnCount: number, sourceMove: MoveId, sourceId: number) {
super(BattlerTagType.FRENZY, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); super(BattlerTagType.FRENZY, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
} }
@ -2199,7 +2219,7 @@ export class SemiInvulnerableTag extends SerializableBattlerTag {
} }
} }
export class TypeImmuneTag extends SerializableBattlerTag { export abstract class TypeImmuneTag extends SerializableBattlerTag {
#immuneType: PokemonType; #immuneType: PokemonType;
public get immuneType(): PokemonType { public get immuneType(): PokemonType {
return this.#immuneType; return this.#immuneType;
@ -2218,6 +2238,7 @@ export class TypeImmuneTag extends SerializableBattlerTag {
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move) | MoveId.TELEKINESIS} * @see {@link https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move) | MoveId.TELEKINESIS}
*/ */
export class FloatingTag extends TypeImmuneTag { export class FloatingTag extends TypeImmuneTag {
public override readonly tagType = BattlerTagType.FLOATING;
constructor(tagType: BattlerTagType, sourceMove: MoveId, turnCount: number) { constructor(tagType: BattlerTagType, sourceMove: MoveId, turnCount: number) {
super(tagType, sourceMove, PokemonType.GROUND, turnCount); super(tagType, sourceMove, PokemonType.GROUND, turnCount);
} }
@ -2247,6 +2268,7 @@ export class FloatingTag extends TypeImmuneTag {
} }
export class TypeBoostTag extends SerializableBattlerTag { export class TypeBoostTag extends SerializableBattlerTag {
public declare readonly tagType: TypeBoostTagType;
#boostedType: PokemonType; #boostedType: PokemonType;
#boostValue: number; #boostValue: number;
#oneUse: boolean; #oneUse: boolean;
@ -2296,13 +2318,24 @@ export class TypeBoostTag extends SerializableBattlerTag {
} }
export class CritBoostTag extends SerializableBattlerTag { export class CritBoostTag extends SerializableBattlerTag {
constructor(tagType: BattlerTagType, sourceMove: MoveId) { public declare readonly tagType: CritStageBoostTagType;
/** The number of stages boosted by this tag */
public readonly critStages: number;
constructor(tagType: CritStageBoostTagType, sourceMove: MoveId) {
super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove, undefined, true); super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove, undefined, true);
} }
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); super.onAdd(pokemon);
// Dragon cheer adds +2 crit stages if the pokemon is a Dragon type when the tag is added
if (this.tagType === BattlerTagType.DRAGON_CHEER && pokemon.getTypes(true).includes(PokemonType.DRAGON)) {
(this as Mutable<this>).critStages = 2;
} else {
(this as Mutable<this>).critStages = 1;
}
globalScene.phaseManager.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("battlerTags:critBoostOnAdd", { i18next.t("battlerTags:critBoostOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
@ -2323,23 +2356,12 @@ export class CritBoostTag extends SerializableBattlerTag {
}), }),
); );
} }
}
/** public override loadTag(source: BaseBattlerTag & Pick<CritBoostTag, "tagType" | "critStages">): void {
* Tag for the effects of Dragon Cheer, which boosts the critical hit ratio of the user's allies. super.loadTag(source);
*/ // TODO: Remove the nullish coalescing once Zod Schemas come in
export class DragonCheerTag extends CritBoostTag { // For now, this is kept for backwards compatibility with older save files
/** The types of the user's ally when the tag is added */ (this as Mutable<this>).critStages = source.critStages ?? 1;
public typesOnAdd: PokemonType[];
constructor() {
super(BattlerTagType.CRIT_BOOST, MoveId.DRAGON_CHEER);
}
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
this.typesOnAdd = pokemon.getTypes(true);
} }
} }
@ -2398,6 +2420,7 @@ export class SaltCuredTag extends SerializableBattlerTag {
} }
export class CursedTag extends SerializableBattlerTag { export class CursedTag extends SerializableBattlerTag {
public override readonly tagType = BattlerTagType.CURSED;
constructor(sourceId: number) { constructor(sourceId: number) {
super(BattlerTagType.CURSED, BattlerTagLapseType.TURN_END, 1, MoveId.CURSE, sourceId, true); super(BattlerTagType.CURSED, BattlerTagLapseType.TURN_END, 1, MoveId.CURSE, sourceId, true);
} }
@ -2444,7 +2467,8 @@ export class CursedTag extends SerializableBattlerTag {
* Battler tag for attacks that remove a type post use. * Battler tag for attacks that remove a type post use.
*/ */
export class RemovedTypeTag extends SerializableBattlerTag { export class RemovedTypeTag extends SerializableBattlerTag {
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, sourceMove: MoveId) { public declare readonly tagType: RemovedTypeTagType;
constructor(tagType: RemovedTypeTagType, lapseType: BattlerTagLapseType, sourceMove: MoveId) {
super(tagType, lapseType, 1, sourceMove); super(tagType, lapseType, 1, sourceMove);
} }
} }
@ -2454,7 +2478,8 @@ export class RemovedTypeTag extends SerializableBattlerTag {
* @description `IGNORE_FLYING`: Persistent grounding effects (i.e. from Smack Down and Thousand Waves) * @description `IGNORE_FLYING`: Persistent grounding effects (i.e. from Smack Down and Thousand Waves)
*/ */
export class GroundedTag extends SerializableBattlerTag { export class GroundedTag extends SerializableBattlerTag {
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, sourceMove: MoveId) { public override readonly tagType = BattlerTagType.IGNORE_FLYING;
constructor(tagType: BattlerTagType.IGNORE_FLYING, lapseType: BattlerTagLapseType, sourceMove: MoveId) {
super(tagType, lapseType, 1, sourceMove); super(tagType, lapseType, 1, sourceMove);
} }
} }
@ -3719,9 +3744,8 @@ export function getBattlerTag(
case BattlerTagType.FIRE_BOOST: case BattlerTagType.FIRE_BOOST:
return new TypeBoostTag(tagType, sourceMove, PokemonType.FIRE, 1.5, false); return new TypeBoostTag(tagType, sourceMove, PokemonType.FIRE, 1.5, false);
case BattlerTagType.CRIT_BOOST: case BattlerTagType.CRIT_BOOST:
return new CritBoostTag(tagType, sourceMove);
case BattlerTagType.DRAGON_CHEER: case BattlerTagType.DRAGON_CHEER:
return new DragonCheerTag(); return new CritBoostTag(tagType, sourceMove);
case BattlerTagType.ALWAYS_CRIT: case BattlerTagType.ALWAYS_CRIT:
case BattlerTagType.IGNORE_ACCURACY: case BattlerTagType.IGNORE_ACCURACY:
return new SerializableBattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove); return new SerializableBattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove);
@ -3813,7 +3837,7 @@ export function getBattlerTag(
* @param source - An object containing the data necessary to reconstruct the BattlerTag. * @param source - An object containing the data necessary to reconstruct the BattlerTag.
* @returns The valid battler tag * @returns The valid battler tag
*/ */
export function loadBattlerTag(source: SerializableBattlerTag): BattlerTag { export function loadBattlerTag(source: BattlerTag | BattlerTagTypeData): BattlerTag {
// TODO: Remove this bang by fixing the signature of `getBattlerTag` // TODO: Remove this bang by fixing the signature of `getBattlerTag`
// to allow undefined sourceIds and sourceMoves (with appropriate fallback for tags that require it) // to allow undefined sourceIds and sourceMoves (with appropriate fallback for tags that require it)
const tag = getBattlerTag(source.tagType, source.turnCount, source.sourceMove!, source.sourceId!); const tag = getBattlerTag(source.tagType, source.turnCount, source.sourceMove!, source.sourceId!);
@ -3854,7 +3878,7 @@ export type BattlerTagTypeMap = {
[BattlerTagType.POWDER]: PowderTag; [BattlerTagType.POWDER]: PowderTag;
[BattlerTagType.NIGHTMARE]: NightmareTag; [BattlerTagType.NIGHTMARE]: NightmareTag;
[BattlerTagType.FRENZY]: FrenzyTag; [BattlerTagType.FRENZY]: FrenzyTag;
[BattlerTagType.CHARGING]: SerializableBattlerTag; [BattlerTagType.CHARGING]: GenericSerializableBattlerTag<BattlerTagType.CHARGING>;
[BattlerTagType.ENCORE]: EncoreTag; [BattlerTagType.ENCORE]: EncoreTag;
[BattlerTagType.HELPING_HAND]: HelpingHandTag; [BattlerTagType.HELPING_HAND]: HelpingHandTag;
[BattlerTagType.INGRAIN]: IngrainTag; [BattlerTagType.INGRAIN]: IngrainTag;
@ -3894,11 +3918,11 @@ export type BattlerTagTypeMap = {
[BattlerTagType.HIDDEN]: SemiInvulnerableTag; [BattlerTagType.HIDDEN]: SemiInvulnerableTag;
[BattlerTagType.FIRE_BOOST]: TypeBoostTag; [BattlerTagType.FIRE_BOOST]: TypeBoostTag;
[BattlerTagType.CRIT_BOOST]: CritBoostTag; [BattlerTagType.CRIT_BOOST]: CritBoostTag;
[BattlerTagType.DRAGON_CHEER]: DragonCheerTag; [BattlerTagType.DRAGON_CHEER]: CritBoostTag;
[BattlerTagType.ALWAYS_CRIT]: SerializableBattlerTag; [BattlerTagType.ALWAYS_CRIT]: GenericSerializableBattlerTag<BattlerTagType.ALWAYS_CRIT>;
[BattlerTagType.IGNORE_ACCURACY]: SerializableBattlerTag; [BattlerTagType.IGNORE_ACCURACY]: GenericSerializableBattlerTag<BattlerTagType.IGNORE_ACCURACY>;
[BattlerTagType.ALWAYS_GET_HIT]: SerializableBattlerTag; [BattlerTagType.ALWAYS_GET_HIT]: GenericSerializableBattlerTag<BattlerTagType.ALWAYS_GET_HIT>;
[BattlerTagType.RECEIVE_DOUBLE_DAMAGE]: SerializableBattlerTag; [BattlerTagType.RECEIVE_DOUBLE_DAMAGE]: GenericSerializableBattlerTag<BattlerTagType.RECEIVE_DOUBLE_DAMAGE>;
[BattlerTagType.BYPASS_SLEEP]: BattlerTag; [BattlerTagType.BYPASS_SLEEP]: BattlerTag;
[BattlerTagType.IGNORE_FLYING]: GroundedTag; [BattlerTagType.IGNORE_FLYING]: GroundedTag;
[BattlerTagType.ROOSTED]: RoostedTag; [BattlerTagType.ROOSTED]: RoostedTag;

View File

@ -25,7 +25,6 @@ import {
AutotomizedTag, AutotomizedTag,
BattlerTag, BattlerTag,
CritBoostTag, CritBoostTag,
DragonCheerTag,
EncoreTag, EncoreTag,
ExposedTag, ExposedTag,
GroundedTag, GroundedTag,
@ -1390,8 +1389,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
const critBoostTag = source.getTag(CritBoostTag); const critBoostTag = source.getTag(CritBoostTag);
if (critBoostTag) { if (critBoostTag) {
// Dragon cheer only gives +1 crit stage to non-dragon types // Dragon cheer only gives +1 crit stage to non-dragon types
critStage.value += critStage.value += critBoostTag.critStages;
critBoostTag instanceof DragonCheerTag && !critBoostTag.typesOnAdd.includes(PokemonType.DRAGON) ? 1 : 2;
} }
console.log(`crit stage: +${critStage.value}`); console.log(`crit stage: +${critStage.value}`);