From a821fc2f80c177fee3d1cd3d679286f88294ca90 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Thu, 14 Aug 2025 18:36:23 -0400 Subject: [PATCH] Fixed type errors; made tag type a const object --- .../positional-tags/load-positional-tag.ts | 2 +- src/enums/arena-event-type.ts | 38 ++++++++ src/enums/field-position.ts | 29 ------ src/events/arena.ts | 38 +++----- src/ui/arena-flyout.ts | 95 ++++++++++++------- 5 files changed, 115 insertions(+), 87 deletions(-) create mode 100644 src/enums/arena-event-type.ts diff --git a/src/data/positional-tags/load-positional-tag.ts b/src/data/positional-tags/load-positional-tag.ts index ebf079a916e..97ad74971c2 100644 --- a/src/data/positional-tags/load-positional-tag.ts +++ b/src/data/positional-tags/load-positional-tag.ts @@ -30,9 +30,9 @@ export function loadPositionalTag(tag: SerializedPositionalTag): PositionalTag { globalScene.arena.eventTarget.dispatchEvent(new PositionalTagAddedEvent(tag)); // Create the new tag - // TODO: review how many type assertions we need here const { tagType, ...rest } = tag; const tagClass = posTagConstructorMap[tagType]; + // @ts-expect-error - tagType always corresponds to the proper constructor for `rest` return new tagClass(rest); } diff --git a/src/enums/arena-event-type.ts b/src/enums/arena-event-type.ts new file mode 100644 index 00000000000..534bc7e756d --- /dev/null +++ b/src/enums/arena-event-type.ts @@ -0,0 +1,38 @@ +import type { ArenaTag } from "#data/arena-tag"; +import type { PositionalTag } from "#data/positional-tags/positional-tag"; +import type { TerrainType } from "#data/terrain"; +import type { WeatherType } from "#enums/weather-type"; +import type { ArenaEvent } from "#events/arena"; +import type { ObjectValues } from "#types/type-helpers"; + +/** + * Enum representing the types of all {@linkcode ArenaEvent}s that can be emitted. + * @eventProperty + * @enum + */ +export const ArenaEventType = { + /** Emitted when a {@linkcode WeatherType} is added, overlapped, or removed */ + WEATHER_CHANGED: "onWeatherChanged", + /** Emitted when a {@linkcode TerrainType} is added, overlapped, or removed */ + TERRAIN_CHANGED: "onTerrainChanged", + + /** Emitted when a new {@linkcode ArenaTag} is added */ + ARENA_TAG_ADDED: "onArenaTagAdded", + /** Emitted when an existing {@linkcode ArenaTag} is removed */ + ARENA_TAG_REMOVED: "onArenaTagRemoved", + + /** Emitted when a new {@linkcode PositionalTag} is added */ + POSITIONAL_TAG_ADDED: "onPositionalTagAdded", + /** Emitted when an existing {@linkcode PositionalTag} is removed */ + POSITIONAL_TAG_REMOVED: "onPositionalTagRemoved", +} as const; + +export type ArenaEventType = ObjectValues; + +/** + Doc comment removal prevention block + {@linkcode WeatherType} + {@linkcode TerrainType} + {@linkcode PositionalTag} + {@linkcode ArenaTag} +*/ \ No newline at end of file diff --git a/src/enums/field-position.ts b/src/enums/field-position.ts index d11e5040f41..5b7f9c6c570 100644 --- a/src/enums/field-position.ts +++ b/src/enums/field-position.ts @@ -1,34 +1,5 @@ -import { globalScene } from "#app/global-scene"; -import { BattlerIndex } from "#enums/battler-index"; - export enum FieldPosition { CENTER, LEFT, RIGHT } - -/** - * Convert a {@linkcode BattlerIndex} into a field position. - * @param index - The {@linkcode BattlerIndex} to convert - * @returns The resultant field position. - */ -export function battlerIndexToFieldPosition(index: BattlerIndex): FieldPosition { - let pos: FieldPosition; - switch (index) { - case BattlerIndex.ATTACKER: - throw new Error("Cannot convert BattlerIndex.ATTACKER to a field position!") - case BattlerIndex.PLAYER: - case BattlerIndex.ENEMY: - pos = FieldPosition.LEFT; - break; - case BattlerIndex.PLAYER_2: - case BattlerIndex.ENEMY_2: - pos = FieldPosition.RIGHT; - break; - } - // In single battles, left positions become center - if (!globalScene.currentBattle.double && pos === FieldPosition.LEFT) { - pos = FieldPosition.CENTER - } - return pos; -} \ No newline at end of file diff --git a/src/events/arena.ts b/src/events/arena.ts index 565d405e12e..6674a5278cc 100644 --- a/src/events/arena.ts +++ b/src/events/arena.ts @@ -2,32 +2,16 @@ import type { SerializedPositionalTag } from "#data/positional-tags/load-positio // biome-ignore lint/correctness/noUnusedImports: TSDoc import type { PositionalTag } from "#data/positional-tags/positional-tag"; import type { TerrainType } from "#data/terrain"; +import { ArenaEventType } from "#enums/arena-event-type"; import type { ArenaTagSide } from "#enums/arena-tag-side"; import type { ArenaTagType } from "#enums/arena-tag-type"; import type { BattlerIndex } from "#enums/battler-index"; import type { PositionalTagType } from "#enums/positional-tag-type"; import type { WeatherType } from "#enums/weather-type"; -/** Enum representing the types of all {@linkcode ArenaEvent}s that can be emitted. */ -export enum ArenaEventType { - /** Emitted when a {@linkcode WeatherType} is added, overlapped, or removed */ - WEATHER_CHANGED = "onWeatherChanged", - /** Emitted when a {@linkcode TerrainType} is added, overlapped, or removed */ - TERRAIN_CHANGED = "onTerrainChanged", - - /** Emitted when a new {@linkcode ArenaTag} is added */ - ARENA_TAG_ADDED = "onArenaTagAdded", - /** Emitted when an existing {@linkcode ArenaTag} is removed */ - ARENA_TAG_REMOVED = "onArenaTagRemoved", - - /** Emitted when a new {@linkcode PositionalTag} is added */ - POSITIONAL_TAG_ADDED = "onPositionalTagAdded", - /** Emitted when an existing {@linkcode PositionalTag} is removed */ - POSITIONAL_TAG_REMOVED = "onPositionalTagRemoved", -} - /** * Abstract container class for all {@linkcode ArenaEventType} events. + * @eventProperty */ abstract class ArenaEvent extends Event { /** The {@linkcode ArenaEventType} being emitted. */ @@ -43,9 +27,10 @@ export type { ArenaEvent }; /** * Container class for {@linkcode ArenaEventType.WEATHER_CHANGED} events. \ * Emitted whenever a weather effect starts, ends or is replaced. + * @eventProperty */ export class WeatherChangedEvent extends ArenaEvent { - declare type: ArenaEventType.WEATHER_CHANGED; + declare type: typeof ArenaEventType.WEATHER_CHANGED; /** The new {@linkcode WeatherType} being set. */ public weatherType: WeatherType; @@ -66,9 +51,10 @@ export class WeatherChangedEvent extends ArenaEvent { /** * Container class for {@linkcode ArenaEventType.TERRAIN_CHANGED} events. \ * Emitted whenever a terrain effect starts, ends or is replaced. + * @eventProperty */ export class TerrainChangedEvent extends ArenaEvent { - declare type: ArenaEventType.TERRAIN_CHANGED; + declare type: typeof ArenaEventType.TERRAIN_CHANGED; /** The new {@linkcode TerrainType} being set. */ public terrainType: TerrainType; @@ -90,9 +76,10 @@ export class TerrainChangedEvent extends ArenaEvent { * Container class for {@linkcode ArenaEventType.ARENA_TAG_ADDED} events. \ * Emitted whenever a new {@linkcode ArenaTag} is added to the arena, or whenever an existing * {@linkcode ArenaTrapTag} overlaps and adds new layers. + * @eventProperty */ export class ArenaTagAddedEvent extends ArenaEvent { - declare type: ArenaEventType.ARENA_TAG_ADDED; + declare type: typeof ArenaEventType.ARENA_TAG_ADDED; /** The {@linkcode ArenaTagType} of the tag being added */ public tagType: ArenaTagType; @@ -124,9 +111,10 @@ export class ArenaTagAddedEvent extends ArenaEvent { /** * Container class for {@linkcode ArenaEventType.ARENA_TAG_REMOVED} events. \ * Emitted whenever an {@linkcode ArenaTag} is removed from the field for any reason. + * @eventProperty */ export class ArenaTagRemovedEvent extends ArenaEvent { - declare type: ArenaEventType.ARENA_TAG_REMOVED; + declare type: typeof ArenaEventType.ARENA_TAG_REMOVED; /** The {@linkcode ArenaTagType} of the tag being removed. */ public tagType: ArenaTagType; @@ -144,9 +132,10 @@ export class ArenaTagRemovedEvent extends ArenaEvent { /** * Container class for {@linkcode ArenaEventType.POSITIONAL_TAG_ADDED} events. \ * Emitted whenever a new {@linkcode PositionalTag} is spawned and added to the arena. + * @eventProperty */ export class PositionalTagAddedEvent extends ArenaEvent { - declare type: ArenaEventType.POSITIONAL_TAG_ADDED; + declare type: typeof ArenaEventType.POSITIONAL_TAG_ADDED; /** The {@linkcode SerializedPositionalTag} being added to the arena. */ public tag: SerializedPositionalTag; @@ -169,9 +158,10 @@ export class PositionalTagAddedEvent extends ArenaEvent { * Container class for {@linkcode ArenaEventType.POSITIONAL_TAG_REMOVED} events. \ * Emitted whenever a currently-active {@linkcode PositionalTag} triggers (or disappears) * and is removed from the arena. + * @eventProperty */ export class PositionalTagRemovedEvent extends ArenaEvent { - declare type: ArenaEventType.POSITIONAL_TAG_REMOVED; + declare type: typeof ArenaEventType.POSITIONAL_TAG_REMOVED; /** The {@linkcode PositionalTagType} of the tag being deleted. */ public tagType: PositionalTagType; diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index 33cdf69bf48..2337ed48d14 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -5,23 +5,23 @@ import type { SerializedPositionalTag } from "#data/positional-tags/load-positio import type { PositionalTag } from "#data/positional-tags/positional-tag"; import { type Terrain, TerrainType } from "#data/terrain"; import type { Weather } from "#data/weather"; +import { ArenaEventType } from "#enums/arena-event-type"; // biome-ignore-end lint/correctness/noUnusedImports: TSDocs import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerIndex } from "#enums/battler-index"; -import { battlerIndexToFieldPosition, FieldPosition } from "#enums/field-position"; +import { FieldPosition } from "#enums/field-position"; import { MoveId } from "#enums/move-id"; import { PositionalTagType } from "#enums/positional-tag-type"; import { TextStyle } from "#enums/text-style"; import { WeatherType } from "#enums/weather-type"; -import { - ArenaEventType, - type ArenaTagAddedEvent, - type ArenaTagRemovedEvent, - type PositionalTagAddedEvent, - type PositionalTagRemovedEvent, - type TerrainChangedEvent, - type WeatherChangedEvent, +import type { + ArenaTagAddedEvent, + ArenaTagRemovedEvent, + PositionalTagAddedEvent, + PositionalTagRemovedEvent, + TerrainChangedEvent, + WeatherChangedEvent, } from "#events/arena"; import { BattleSceneEventType } from "#events/battle-scene"; import { addTextObject } from "#ui/text"; @@ -92,7 +92,7 @@ interface PositionalTagInfo { * @param text - The raw text of the effect; assumed to be in `UPPER_SNAKE_CASE` from a reverse mapping. * @returns The localized text for the effect. */ -function localizeEffectName(text: string): string { +export function localizeEffectName(text: string): string { const effectName = toCamelCase(text); const i18nKey = `arenaFlyout:${effectName}`; const resultName = i18next.t(i18nKey); @@ -103,11 +103,12 @@ function localizeEffectName(text: string): string { * Return the localized name of a given {@linkcode PositionalTag}. * @param tag - The raw serialized data for the given tag * @returns The localized text to be displayed on-screen. + * @package */ -function getPositionalTagDisplayName(tag: SerializedPositionalTag): string { +export function getPositionalTagDisplayName(tag: SerializedPositionalTag): string { let tagName: string; if ("sourceMove" in tag) { - // Delayed attacks will use the source move's name + // Delayed attacks will use the source move's name; other effects rely on type tagName = MoveId[tag.sourceMove]; } else { tagName = PositionalTagType[tag.tagType]; @@ -317,7 +318,7 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { private onTurnEnd() { // Remove all objects with positive max durations and whose durations have expired. this.arenaTags = this.arenaTags.filter(info => info.maxDuration === 0 || --info.duration >= 0); - this.positionalTags = this.positionalTags.filter(info => --info.duration >= 0); + this.positionalTags = this.positionalTags.filter(info => --info.duration > 0); this.updateFieldText(); } @@ -354,9 +355,9 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { /** * Update an existing trap tag with an updated layer count whenever one is overlapped. - * @param existingTag - The existing {@linkcode ArenaTagInfo} to update text for + * @param existingTag - The existing {@linkcode ArenaTagInfo} being updated * @param layers - The base number of layers of the new tag - * @param maxLayers - The maximum number of layers of the new tag; will not show layer count if <=0 + * @param maxLayers - The maximum number of layers of the new tag; will not show layer count if `<=0` * @param name - The name of the tag. */ private updateTrapLayers(existingTag: ArenaTagInfo, [layers, maxLayers]: [number, number], name: string): void { @@ -370,6 +371,7 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { */ private onArenaTagRemoved(event: ArenaTagRemovedEvent): void { const foundIndex = this.arenaTags.findIndex(info => info.tagType === event.tagType && info.side === event.side); + console.log(this.positionalTags, event); if (foundIndex > -1) { // If the tag was being tracked, remove it @@ -513,10 +515,10 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { // Weather and terrain go first if (this.weatherInfo) { - this.updateTagText(this.weatherInfo); + this.flyoutTextField.text += this.getTagText(this.weatherInfo); } if (this.terrainInfo) { - this.updateTagText(this.terrainInfo); + this.flyoutTextField.text += this.getTagText(this.terrainInfo); } // Sort and add all positional tags @@ -525,45 +527,48 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { (infoA, infoB) => infoA.name.localeCompare(infoB.name) || infoA.targetIndex - infoB.targetIndex, ); for (const tag of this.positionalTags) { - this.updatePosTagText(tag); + this.getPositionalTagTextObj(tag).text += this.getPosTagText(tag); } // Sort and update all arena tag text this.arenaTags.sort((infoA, infoB) => infoA.duration - infoB.duration); for (const tag of this.arenaTags) { - this.updateTagText(tag); + this.getArenaTagTargetObj(tag.side).text += this.getTagText(tag); } } /** - * Helper method to update the flyout box's text with a {@linkcode PositionalTag}'s info. + * Helper method to retrieve the flyout text for a given {@linkcode PositionalTag}. * @param info - The {@linkcode PositionalTagInfo} whose text is being updated + * @returns The text to be added to the container */ - private updatePosTagText(info: PositionalTagInfo): void { - const textObj = this.getPositionalTagTextObj(info); + private getPosTagText(info: PositionalTagInfo): string { + // Avoud showing slot target for single battles + if (!globalScene.currentBattle.double) { + return `${info.name} (${info.duration})\n`; + } const targetPos = battlerIndexToFieldPosition(info.targetIndex); const posText = localizeEffectName(FieldPosition[targetPos]); // Ex: "Future Sight (Center, 2)" - textObj.text += `${info.name} (${posText}, ${info.duration}\n`; + return `${info.name} (${posText}, ${info.duration})\n`; } /** - * Helper method to update the flyout box's text with an effect's info. + * Helper method to retrieve the flyout text for a given effect's info. * @param info - The {@linkcode ArenaTagInfo}, {@linkcode TerrainInfo} or {@linkcode WeatherInfo} being updated + * @returns The text to be added to the container */ - private updateTagText(info: ArenaTagInfo | WeatherInfo | TerrainInfo): void { - // Weathers and terrains use the "field" box by default - const textObject = "tagType" in info ? this.getArenaTagTargetObj(info.side) : this.flyoutTextField; - - textObject.text += info.name; + private getTagText(info: ArenaTagInfo | WeatherInfo | TerrainInfo): string { + let text = info.name; if (info.maxDuration > 0) { - textObject.text += ` ${info.duration}/ + ${info.maxDuration}`; + text += ` ${info.duration}/${info.maxDuration}`; } - textObject.text += "\n"; + text += "\n"; + return text; } /** @@ -600,7 +605,31 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { } } - private; - // # endregion Text display functions } + +/** + * Convert a {@linkcode BattlerIndex} into a field position. + * @param index - The {@linkcode BattlerIndex} to convert + * @returns The resultant field position. + */ +function battlerIndexToFieldPosition(index: BattlerIndex): FieldPosition { + let pos: FieldPosition; + switch (index) { + case BattlerIndex.ATTACKER: + throw new Error("Cannot convert BattlerIndex.ATTACKER to a field position!"); + case BattlerIndex.PLAYER: + case BattlerIndex.ENEMY: + pos = FieldPosition.LEFT; + break; + case BattlerIndex.PLAYER_2: + case BattlerIndex.ENEMY_2: + pos = FieldPosition.RIGHT; + break; + } + // In single battles, left positions become center + if (!globalScene.currentBattle.double && pos === FieldPosition.LEFT) { + pos = FieldPosition.CENTER; + } + return pos; +}