Fixed issues with merging

This commit is contained in:
Bertie690 2025-07-23 12:16:52 -04:00
parent 768fb7036a
commit 5a4f7effb3
6 changed files with 55 additions and 61 deletions

View File

@ -10,9 +10,6 @@ export type ArenaTrapTagType =
| ArenaTagType.STEALTH_ROCK
| ArenaTagType.IMPRISON;
/** Subset of {@linkcode ArenaTagType}s that are considered delayed attacks */
export type ArenaDelayedAttackTagType = ArenaTagType.FUTURE_SIGHT | ArenaTagType.DOOM_DESIRE;
/** Subset of {@linkcode ArenaTagType}s that create {@link https://bulbapedia.bulbagarden.net/wiki/Category:Screen-creating_moves | screens}. */
export type ArenaScreenTagType = ArenaTagType.REFLECT | ArenaTagType.LIGHT_SCREEN | ArenaTagType.AURORA_VEIL;

View File

@ -20,7 +20,6 @@ import { StatusEffect } from "#enums/status-effect";
import type { Arena } from "#field/arena";
import type { Pokemon } from "#field/pokemon";
import type {
ArenaDelayedAttackTagType,
ArenaScreenTagType,
ArenaTagTypeData,
ArenaTrapTagType,
@ -33,7 +32,7 @@ import i18next from "i18next";
/*
ArenaTags are are meant for effects that are tied to the arena (as opposed to a specific pokemon).
Examples include (but are not limited to)
- Cross-turn effects that persist even if the user/target switches out, such as Wish, Future Sight, and Happy Hour
- Cross-turn effects that persist even if the user/target switches out, such as and Happy Hour
- Effects that are applied to a specific side of the field, such as Crafty Shield, Reflect, and Spikes
- Field-Effects, like Gravity and Trick Room
@ -1076,48 +1075,6 @@ class StickyWebTag extends ArenaTrapTag {
}
}
/**
* Arena Tag class for delayed attacks, such as {@linkcode MoveId.FUTURE_SIGHT} or {@linkcode MoveId.DOOM_DESIRE}.
* Delays the attack's effect by a set amount of turns, usually 3 (including the turn the move is used),
* and deals damage after the turn count is reached.
*/
export class DelayedAttackTag extends SerializableArenaTag {
public targetIndex: BattlerIndex;
public readonly tagType: ArenaDelayedAttackTagType;
constructor(
tagType: ArenaTagType.DOOM_DESIRE | ArenaTagType.FUTURE_SIGHT,
sourceMove: MoveId | undefined,
sourceId: number | undefined,
targetIndex: BattlerIndex,
side: ArenaTagSide = ArenaTagSide.BOTH,
) {
super(3, sourceMove, sourceId, side);
this.tagType = tagType;
this.targetIndex = targetIndex;
this.side = side;
}
lapse(arena: Arena): boolean {
const ret = super.lapse(arena);
if (!ret) {
// TODO: This should not add to move history (for Spite)
globalScene.phaseManager.unshiftNew(
"MoveEffectPhase",
this.sourceId!,
[this.targetIndex],
allMoves[this.sourceMove!],
MoveUseMode.FOLLOW_UP,
); // TODO: are those bangs correct?
}
return ret;
}
onRemove(_arena: Arena): void {}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Trick_Room_(move) Trick Room}.
* Reverses the Speed stats for all Pokémon on the field as long as this arena tag is up,
@ -1682,7 +1639,7 @@ export function getArenaTag(
*/
export function loadArenaTag(source: ArenaTag | ArenaTagTypeData): ArenaTag {
const tag =
getArenaTag(source.tagType, source.sourceId, source.sourceMove, source.turnCount, source.side) ?? new NoneTag();
getArenaTag(source.tagType, source.turnCount, source.sourceMove, source.sourceId, source.side) ?? new NoneTag();
tag.loadTag(source);
return tag;
}

View File

@ -3141,7 +3141,7 @@ abstract class AddPositionalTagAttr extends OverrideMoveEffectAttr {
public override getCondition(): MoveConditionFunc {
// Check the arena if another similar positional tag is active and affecting the same slot
return (_user, target, move) => globalScene.arena.positionalTagManager.canAddTag(this.tagType, target.getBattlerIndex(), move.id)
return (_user, target, move) => globalScene.arena.positionalTagManager.canAddTag(this.tagType, target.getBattlerIndex())
}
}
@ -3201,7 +3201,7 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
public override getCondition(): MoveConditionFunc {
// Check the arena if another similar attack is active and affecting the same slot
return (_user, target, move) => globalScene.arena.positionalTagManager.canAddTag(PositionalTagType.DELAYED_ATTACK, target.getBattlerIndex(), move.id)
return (_user, target, move) => globalScene.arena.positionalTagManager.canAddTag(PositionalTagType.DELAYED_ATTACK, target.getBattlerIndex())
}
}
@ -3219,8 +3219,8 @@ export class WishAttr extends MoveEffectAttr {
}
public override getCondition(): MoveConditionFunc {
// Check the arena if another similar attack is active and affecting the same slot
return (_user, target, move) => globalScene.arena.positionalTagManager.canAddTag(PositionalTagType.WISH, target.getBattlerIndex(), move.id)
// Check the arena if another wish is active and affecting the same slot
return (_user, target) => globalScene.arena.positionalTagManager.canAddTag(PositionalTagType.WISH, target.getBattlerIndex())
}
}

View File

@ -7,16 +7,18 @@ import type { Constructor } from "#utils/common";
* Add a new {@linkcode PositionalTag} to the arena.
* @param tagType - The {@linkcode PositionalTagType} to create
* @param args - The arguments needed to instantize the given tag
* @returns The newly created tag.
* @remarks
* This function does not perform any checking if the added tag is valid.
*/
export function loadPositionalTag<T extends PositionalTagType>({
tagType,
...args
}: serializedPosTagParamMap[T]): posTagInstanceMap[T];
}: serializedPosTagMap[T]): posTagInstanceMap[T];
/**
* Add a new {@linkcode PositionalTag} to the arena.
* @param tag - The {@linkcode SerializedPositionalTag} to instantiate
* @returns The newly created tag.
* @remarks
* This function does not perform any checking if the added tag is valid.
*/
@ -24,12 +26,13 @@ export function loadPositionalTag(tag: SerializedPositionalTag): PositionalTag;
export function loadPositionalTag<T extends PositionalTagType>({
tagType,
...rest
}: serializedPosTagParamMap[T]): posTagInstanceMap[T] {
}: serializedPosTagMap[T]): posTagInstanceMap[T] {
// Note: We need 2 type assertions here:
// 1 because TS doesn't narrow the type of TagClass correctly based on `T`.
// It converts it into `new (DelayedAttackTag | WishTag) => DelayedAttackTag & WishTag`
const tagClass = posTagConstructorMap[tagType] as new (args: posTagParamMap[T]) => posTagInstanceMap[T];
// 2 because TS doesn't narrow `Omit<{tagType: T} & posTagParamMap[T], "tagType"> into `posTagParamMap[T]`
// 2 because TS doesn't narrow the type of `rest` correctly
// (from `Omit<serializedPosTagParamMap[T], "tagType"> into `posTagParamMap[T]`)
return new tagClass(rest as unknown as posTagParamMap[T]);
}
@ -38,10 +41,11 @@ const posTagConstructorMap = Object.freeze({
[PositionalTagType.DELAYED_ATTACK]: DelayedAttackTag,
[PositionalTagType.WISH]: WishTag,
}) satisfies {
// NB: This `satisfies` block ensures that all tag types have corresponding entries in the map.
[k in PositionalTagType]: Constructor<PositionalTag & { tagType: k }>;
};
/** Type mapping tag types to their constructors. */
/** Type mapping positional tag types to their constructors. */
type posTagMap = typeof posTagConstructorMap;
/** Type mapping all positional tag types to their instances. */
@ -54,10 +58,13 @@ type posTagParamMap = {
[k in PositionalTagType]: ConstructorParameters<posTagMap[k]>[0];
};
/** Type mapping all positional tag types to their constructors' parameters, alongside the `tagType` selector. */
type serializedPosTagParamMap = {
/**
* Type mapping all positional tag types to their constructors' parameters, alongside the `tagType` selector.
* Equivalent to their serialized representations.
*/
export type serializedPosTagMap = {
[k in PositionalTagType]: posTagParamMap[k] & { tagType: k };
};
/** Union type containing all serialized {@linkcode PositionalTag}s. */
export type SerializedPositionalTag = EnumValues<serializedPosTagParamMap>;
export type SerializedPositionalTag = EnumValues<serializedPosTagMap>;

View File

@ -13,6 +13,7 @@ export interface SerializedArenaData {
weather: NonFunctionProperties<Weather> | null;
terrain: NonFunctionProperties<Terrain> | null;
tags?: ArenaTagTypeData[];
positionalTags: SerializedPositionalTag[];
playerTerasUsed?: number;
}
@ -34,17 +35,20 @@ export class ArenaData {
?.filter((tag): tag is SerializableArenaTag => tag instanceof SerializableArenaTag) ?? [];
this.playerTerasUsed = source.playerTerasUsed ?? 0;
this.positionalTags = (sourceArena ? sourceArena.positionalTagManager.tags : source.positionalTags) ?? [];
if (source instanceof Arena) {
this.biome = source.biomeType;
this.weather = source.weather;
this.terrain = source.terrain;
// The assertion here is ok - we ensure that all tags are inside the `posTagConstructorMap` map,
// and that all `PositionalTags` will become their respective interfaces when serialized and de-serialized.
this.positionalTags = (source.positionalTagManager.tags as unknown as SerializedPositionalTag[]) ?? [];
return;
}
this.biome = source.biome;
this.weather = source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null;
this.terrain = source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null;
this.positionalTags = source.positionalTags ?? [];
}
}

View File

@ -0,0 +1,29 @@
import type { SerializedPositionalTag, serializedPosTagMap } from "#data/positional-tags/load-positional-tag";
import type { DelayedAttackTag, WishTag } from "#data/positional-tags/positional-tag";
import type { PositionalTagType } from "#enums/positional-tag-type";
import type { Mutable, NonFunctionPropertiesRecursive } from "#types/type-helpers";
import { describe, expectTypeOf, it } from "vitest";
// Needed to get around properties being readonly in certain classes
type NonFunctionMutable<T> = Mutable<NonFunctionPropertiesRecursive<T>>;
describe("serializedPositionalTagMap", () => {
it("should contain representations of each tag's serialized form", () => {
expectTypeOf<serializedPosTagMap[PositionalTagType.DELAYED_ATTACK]>().branded.toEqualTypeOf<
NonFunctionMutable<DelayedAttackTag>
>();
expectTypeOf<serializedPosTagMap[PositionalTagType.WISH]>().branded.toEqualTypeOf<NonFunctionMutable<WishTag>>();
});
});
describe("SerializedPositionalTag", () => {
it("should accept a union of all serialized tag forms", () => {
expectTypeOf<SerializedPositionalTag>().branded.toEqualTypeOf<
NonFunctionMutable<DelayedAttackTag> | NonFunctionMutable<WishTag>
>();
});
it("should accept a union of all unserialized tag forms", () => {
expectTypeOf<WishTag>().toExtend<SerializedPositionalTag>();
expectTypeOf<DelayedAttackTag>().toExtend<SerializedPositionalTag>();
});
});