diff --git a/public/images/ui/dawn_icon.png b/public/images/ui/dawn_icon.png new file mode 100644 index 00000000000..e04e6024aa0 Binary files /dev/null and b/public/images/ui/dawn_icon.png differ diff --git a/public/images/ui/day_icon.png b/public/images/ui/day_icon.png new file mode 100644 index 00000000000..fe41acffd9c Binary files /dev/null and b/public/images/ui/day_icon.png differ diff --git a/public/images/ui/dusk_icon.png b/public/images/ui/dusk_icon.png new file mode 100644 index 00000000000..e848fa31345 Binary files /dev/null and b/public/images/ui/dusk_icon.png differ diff --git a/public/images/ui/legacy/dawn_icon.png b/public/images/ui/legacy/dawn_icon.png new file mode 100644 index 00000000000..eb24a799ab9 Binary files /dev/null and b/public/images/ui/legacy/dawn_icon.png differ diff --git a/public/images/ui/legacy/day_icon.png b/public/images/ui/legacy/day_icon.png new file mode 100644 index 00000000000..310ba50dcf3 Binary files /dev/null and b/public/images/ui/legacy/day_icon.png differ diff --git a/public/images/ui/legacy/dusk_icon.png b/public/images/ui/legacy/dusk_icon.png new file mode 100644 index 00000000000..f383ebf5246 Binary files /dev/null and b/public/images/ui/legacy/dusk_icon.png differ diff --git a/public/images/ui/legacy/night_icon.png b/public/images/ui/legacy/night_icon.png new file mode 100644 index 00000000000..1796081c05b Binary files /dev/null and b/public/images/ui/legacy/night_icon.png differ diff --git a/public/images/ui/night_icon.png b/public/images/ui/night_icon.png new file mode 100644 index 00000000000..1782303f73d Binary files /dev/null and b/public/images/ui/night_icon.png differ diff --git a/src/battle-scene.ts b/src/battle-scene.ts index e28dedccbd5..12e0bcb699b 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -58,6 +58,7 @@ import {InputsController} from "./inputs-controller"; import {UiInputs} from "./ui-inputs"; import { MoneyFormat } from "./enums/money-format"; import { NewArenaEvent } from "./battle-scene-events"; +import ArenaFlyout from "./ui/arena-flyout"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -92,6 +93,7 @@ export default class BattleScene extends SceneBase { public damageNumbersMode: integer = 0; public reroll: boolean = false; public showMovesetFlyout: boolean = true; + public showArenaFlyout: boolean = true; public showLevelUpStats: boolean = true; public enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1"; public enableRetries: boolean = false; @@ -178,6 +180,8 @@ export default class BattleScene extends SceneBase { private luckText: Phaser.GameObjects.Text; private modifierBar: ModifierBar; private enemyModifierBar: ModifierBar; + public arenaFlyout: ArenaFlyout; + private fieldOverlay: Phaser.GameObjects.Rectangle; private modifiers: PersistentModifier[]; private enemyModifiers: PersistentModifier[]; @@ -411,6 +415,10 @@ export default class BattleScene extends SceneBase { this.luckLabelText.setVisible(false); this.fieldUI.add(this.luckLabelText); + this.arenaFlyout = new ArenaFlyout(this); + this.fieldUI.add(this.arenaFlyout); + this.fieldUI.moveBelow(this.arenaFlyout, this.fieldOverlay); + this.updateUIPositions(); this.damageNumberHandler = new DamageNumberHandler(); @@ -1272,6 +1280,13 @@ export default class BattleScene extends SceneBase { return sprite; } + moveBelowOverlay(gameObject: T) { + this.fieldUI.moveBelow(gameObject, this.fieldOverlay); + } + processInfoButton(pressed: boolean): void { + this.arenaFlyout.toggleFlyout(pressed); + } + showFieldOverlay(duration: integer): Promise { return new Promise(resolve => { this.tweens.add({ diff --git a/src/field/arena-events.ts b/src/field/arena-events.ts index 1cc632030a5..b3d03fdfcae 100644 --- a/src/field/arena-events.ts +++ b/src/field/arena-events.ts @@ -10,8 +10,10 @@ export enum ArenaEventType { /** Triggers when a {@linkcode TerrainType} is added, overlapped, or removed */ TERRAIN_CHANGED = "onTerrainChanged", - /** Triggers when a {@linkcode ArenaTagType} is added or removed */ - TAG_CHANGED = "onTagChanged", + /** Triggers when a {@linkcode ArenaTagType} is added */ + TAG_ADDED = "onTagAdded", + /** Triggers when a {@linkcode ArenaTagType} is removed */ + TAG_REMOVED = "onTagRemoved", } /** @@ -59,17 +61,34 @@ export class TerrainChangedEvent extends ArenaEvent { this.newTerrainType = newTerrainType; } } + /** - * Container class for {@linkcode ArenaEventType.TAG_CHANGED} events + * Container class for {@linkcode ArenaEventType.TAG_ADDED} events * @extends ArenaEvent */ -export class TagChangedEvent extends ArenaEvent { - /** The {@linkcode ArenaTagType} being set */ +export class TagAddedEvent extends ArenaEvent { + /** The {@linkcode ArenaTagType} being added */ public arenaTagType: ArenaTagType; /** The {@linkcode ArenaTagSide} the tag is being placed on */ public arenaTagSide: ArenaTagSide; constructor(arenaTagType: ArenaTagType, arenaTagSide: ArenaTagSide, duration: number) { - super(ArenaEventType.TAG_CHANGED, duration); + super(ArenaEventType.TAG_ADDED, duration); + + this.arenaTagType = arenaTagType; + this.arenaTagSide = arenaTagSide; + } +} +/** + * Container class for {@linkcode ArenaEventType.TAG_REMOVED} events + * @extends ArenaEvent +*/ +export class TagRemovedEvent extends ArenaEvent { + /** The {@linkcode ArenaTagType} being removed */ + public arenaTagType: ArenaTagType; + /** The {@linkcode ArenaTagSide} the tag was being placed on */ + public arenaTagSide: ArenaTagSide; + constructor(arenaTagType: ArenaTagType, arenaTagSide: ArenaTagSide, duration: number) { + super(ArenaEventType.TAG_REMOVED, duration); this.arenaTagType = arenaTagType; this.arenaTagSide = arenaTagSide; diff --git a/src/field/arena.ts b/src/field/arena.ts index eac2eafb265..f6390e40db5 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -19,7 +19,7 @@ import { Terrain, TerrainType } from "../data/terrain"; import { PostTerrainChangeAbAttr, PostWeatherChangeAbAttr, applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs } from "../data/ability"; import Pokemon from "./pokemon"; import * as Overrides from "../overrides"; -import { WeatherChangedEvent, TerrainChangedEvent, TagChangedEvent } from "./arena-events"; +import { WeatherChangedEvent, TerrainChangedEvent, TagAddedEvent, TagRemovedEvent } from "./arena-events"; export class Arena { public scene: BattleScene; @@ -550,7 +550,7 @@ export class Arena { this.tags.push(newTag); newTag.onAdd(this); - this.eventTarget.dispatchEvent(new TagChangedEvent(newTag.tagType, newTag.side, newTag.turnCount)); + this.eventTarget.dispatchEvent(new TagAddedEvent(newTag.tagType, newTag.side, newTag.turnCount)); return true; } @@ -577,6 +577,8 @@ export class Arena { this.tags.filter(t => !(t.lapse(this))).forEach(t => { t.onRemove(this); this.tags.splice(this.tags.indexOf(t), 1); + + this.eventTarget.dispatchEvent(new TagRemovedEvent(t.tagType, t.side, t.turnCount)); }); } @@ -586,6 +588,8 @@ export class Arena { if (tag) { tag.onRemove(this); tags.splice(tags.indexOf(tag), 1); + + this.eventTarget.dispatchEvent(new TagRemovedEvent(tag.tagType, tag.side, tag.turnCount)); } return !!tag; } @@ -595,6 +599,8 @@ export class Arena { if (tag) { tag.onRemove(this); this.tags.splice(this.tags.indexOf(tag), 1); + + this.eventTarget.dispatchEvent(new TagRemovedEvent(tag.tagType, tag.side, tag.turnCount)); } return !!tag; } @@ -603,6 +609,8 @@ export class Arena { removeAllTags(): void { while (this.tags.length) { this.tags[0].onRemove(this); + this.eventTarget.dispatchEvent(new TagRemovedEvent(this.tags[0].tagType, this.tags[0].side, this.tags[0].turnCount)); + this.tags.splice(0, 1); } } diff --git a/src/loading-scene.ts b/src/loading-scene.ts index fe63b39f805..0b15358c4bc 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -95,6 +95,11 @@ export class LoadingScene extends SceneBase { this.loadImage("type_tera", "ui"); this.loadAtlas("type_bgs", "ui"); + this.loadImage("dawn_icon", "ui"); + this.loadImage("day_icon", "ui"); + this.loadImage("dusk_icon", "ui"); + this.loadImage("night_icon", "ui"); + this.loadImage("pb_tray_overlay_player", "ui"); this.loadImage("pb_tray_overlay_enemy", "ui"); this.loadAtlas("pb_tray_ball", "ui"); diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index e68e5ea8704..cf657d0e828 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -52,6 +52,7 @@ export const SettingKeys = { Sprite_Set: "SPRITE_SET", Move_Animations: "MOVE_ANIMATIONS", Show_Moveset_Flyout: "SHOW_MOVESET_FLYOUT", + Show_Arena_Flyout: "SHOW_ARENA_FLYOUT", Show_Stats_on_Level_Up: "SHOW_LEVEL_UP_STATS", EXP_Gains_Speed: "EXP_GAINS_SPEED", EXP_Party_Display: "EXP_PARTY_DISPLAY", @@ -189,6 +190,13 @@ export const Setting: Array = [ default: 1, type: SettingType.ACCESSIBILITY }, + { + key: SettingKeys.Show_Arena_Flyout, + label: "Show Battle Effects Flyout", + options: OFF_ON, + default: 1, + type: SettingType.ACCESSIBILITY + }, { key: SettingKeys.Show_Stats_on_Level_Up, label: "Show Stats on Level Up", @@ -343,6 +351,9 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): case SettingKeys.Show_Moveset_Flyout: scene.showMovesetFlyout = Setting[index].options[value] === "On"; break; + case SettingKeys.Show_Arena_Flyout: + scene.showArenaFlyout = Setting[index].options[value] === "On"; + break; case SettingKeys.Show_Stats_on_Level_Up: scene.showLevelUpStats = Setting[index].options[value] === "On"; break; diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index c9bdc5feaf5..d4815ad5a7c 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -69,7 +69,7 @@ export class UiInputs { [Button.CYCLE_GENDER]: () => this.buttonCycleOption(Button.CYCLE_GENDER), [Button.CYCLE_ABILITY]: () => this.buttonCycleOption(Button.CYCLE_ABILITY), [Button.CYCLE_NATURE]: () => this.buttonCycleOption(Button.CYCLE_NATURE), - [Button.V]: () => this.buttonCycleOption(Button.V), + [Button.V]: () => this.buttonCycleOption(Button.V), [Button.SPEED_UP]: () => this.buttonSpeedChange(), [Button.SLOW_DOWN]: () => this.buttonSpeedChange(false), }; @@ -119,12 +119,14 @@ export class UiInputs { } } buttonInfo(pressed: boolean = true): void { - if (!this.scene.showMovesetFlyout) { - return; + if (this.scene.showMovesetFlyout ) { + for (const p of this.scene.getField().filter(p => p?.isActive(true))) { + p.toggleFlyout(pressed); + } } - for (const p of this.scene.getField().filter(p => p?.isActive(true))) { - p.toggleFlyout(pressed); + if (this.scene.showArenaFlyout) { + this.scene.ui.processInfoButton(pressed); } } diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts new file mode 100644 index 00000000000..73660ca4457 --- /dev/null +++ b/src/ui/arena-flyout.ts @@ -0,0 +1,384 @@ +import * as Utils from "../utils"; +import { addTextObject, TextStyle } from "./text"; +import BattleScene from "#app/battle-scene.js"; +import { ArenaTagSide } from "#app/data/arena-tag.js"; +import { WeatherType } from "#app/data/weather.js"; +import { TerrainType } from "#app/data/terrain.js"; +import { addWindow, WindowVariant } from "./ui-theme"; +import { ArenaEvent, ArenaEventType, TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/field/arena-events.js"; +import { BattleSceneEventType, TurnEndEvent } from "#app/battle-scene-events.js"; +import { ArenaTagType } from "#app/data/enums/arena-tag-type.js"; +import { TimeOfDay } from "#app/data/enums/time-of-day.js"; + +/** Enum used to differentiate {@linkcode Arena} effects */ +enum ArenaEffectType { + PLAYER, + WEATHER, + TERRAIN, + FIELD, + ENEMY, +} +/** Container for info about an {@linkcode Arena}'s effects */ +interface ArenaEffectInfo { + /** The enum string representation of the effect */ + name: string; + /** {@linkcode ArenaEffectType} type of effect */ + type: ArenaEffectType, + + /** The maximum duration set by the effect */ + maxDuration: number; + /** The current duration left on the effect */ + duration: number; +} + +export default class ArenaFlyout extends Phaser.GameObjects.Container { + /** An alias for the scene typecast to a {@linkcode BattleScene} */ + private battleScene: BattleScene; + + /** The restricted width of the flyout which should be drawn to */ + private flyoutWidth = 170; + /** The restricted height of the flyout which should be drawn to */ + private flyoutHeight = 51; + + /** The amount of translation animation on the x-axis */ + private translationX: number; + /** The x-axis point where the flyout should sit when activated */ + private anchorX: number; + /** The y-axis point where the flyout should sit when activated */ + private anchorY: number; + + /** The initial container which defines where the flyout should be attached */ + private flyoutParent: Phaser.GameObjects.Container; + /** The container which defines the drawable dimensions of the flyout */ + private flyoutContainer: Phaser.GameObjects.Container; + + /** The background {@linkcode Phaser.GameObjects.NineSlice} window for the flyout */ + private flyoutWindow: Phaser.GameObjects.NineSlice; + + /** The header {@linkcode Phaser.GameObjects.NineSlice} window for the flyout */ + private flyoutWindowHeader: Phaser.GameObjects.NineSlice; + /** The {@linkcode Phaser.GameObjects.Text} that goes inside of the header */ + private flyoutTextHeader: Phaser.GameObjects.Text; + + /** The {@linkcode Phaser.GameObjects.Sprite} that represents the current time of day */ + private timeOfDayIcon: Phaser.GameObjects.Sprite; + + /** The {@linkcode Phaser.GameObjects.Text} header used to indicate the player's effects */ + private flyoutTextHeaderPlayer: Phaser.GameObjects.Text; + /** The {@linkcode Phaser.GameObjects.Text} header used to indicate the enemy's effects */ + private flyoutTextHeaderEnemy: Phaser.GameObjects.Text; + /** The {@linkcode Phaser.GameObjects.Text} header used to indicate neutral effects */ + private flyoutTextHeaderField: Phaser.GameObjects.Text; + + /** The {@linkcode Phaser.GameObjects.Text} used to indicate the player's effects */ + private flyoutTextPlayer: Phaser.GameObjects.Text; + /** The {@linkcode Phaser.GameObjects.Text} used to indicate the enemy's effects */ + private flyoutTextEnemy: Phaser.GameObjects.Text; + /** The {@linkcode Phaser.GameObjects.Text} used to indicate neutral effects */ + private flyoutTextField: Phaser.GameObjects.Text; + + /** Container for all field effects observed by this object */ + private readonly fieldEffectInfo: ArenaEffectInfo[] = []; + + // Stores callbacks in a variable so they can be unsubscribed from when destroyed + private onNewArenaEvent = (event: Event) => this.onNewArena(event); + private onTurnInitEvent = (event: Event) => this.onTurnInit(event); + private onTurnEndEvent = (event: Event) => this.onTurnEnd(event); + + private onFieldEffectChangedEvent = (event: Event) => this.onFieldEffectChanged(event); + + constructor(scene: Phaser.Scene) { + super(scene, 0, 0); + this.battleScene = this.scene as BattleScene; + + this.translationX = this.flyoutWidth; + this.anchorX = 0; + this.anchorY = -98; + + this.flyoutParent = this.scene.add.container(this.anchorX - this.translationX, this.anchorY); + this.flyoutParent.setAlpha(0); + this.add(this.flyoutParent); + + this.flyoutContainer = this.scene.add.container(0, 0); + this.flyoutParent.add(this.flyoutContainer); + + this.flyoutWindow = addWindow(this.scene as BattleScene, 0, 0, this.flyoutWidth, this.flyoutHeight, false, false, 0, 0, WindowVariant.THIN); + this.flyoutContainer.add(this.flyoutWindow); + + this.flyoutWindowHeader = addWindow(this.scene as BattleScene, this.flyoutWidth / 2, 0, this.flyoutWidth / 2, 14, false, false, 0, 0, WindowVariant.XTHIN); + this.flyoutWindowHeader.setOrigin(); + + this.flyoutContainer.add(this.flyoutWindowHeader); + + this.flyoutTextHeader = addTextObject(this.scene, this.flyoutWidth / 2, 0, "Active Battle Effects", TextStyle.BATTLE_INFO); + this.flyoutTextHeader.setFontSize(54); + this.flyoutTextHeader.setAlign("center"); + this.flyoutTextHeader.setOrigin(); + + this.flyoutContainer.add(this.flyoutTextHeader); + + this.timeOfDayIcon = this.scene.add.sprite((this.flyoutWidth / 2) + (this.flyoutWindowHeader.displayWidth / 2), 0, "dawn_icon").setOrigin(); + this.timeOfDayIcon.setVisible(false); + + this.flyoutContainer.add(this.timeOfDayIcon); + + this.flyoutTextHeaderPlayer = addTextObject(this.scene, 6, 5, "Player", TextStyle.SUMMARY_BLUE); + this.flyoutTextHeaderPlayer.setFontSize(54); + this.flyoutTextHeaderPlayer.setAlign("left"); + this.flyoutTextHeaderPlayer.setOrigin(0, 0); + + this.flyoutContainer.add(this.flyoutTextHeaderPlayer); + + this.flyoutTextHeaderField = addTextObject(this.scene, this.flyoutWidth / 2, 5, "Neutral", TextStyle.SUMMARY_GREEN); + this.flyoutTextHeaderField.setFontSize(54); + this.flyoutTextHeaderField.setAlign("center"); + this.flyoutTextHeaderField.setOrigin(0.5, 0); + + this.flyoutContainer.add(this.flyoutTextHeaderField); + + this.flyoutTextHeaderEnemy = addTextObject(this.scene, this.flyoutWidth - 6, 5, "Enemy", TextStyle.SUMMARY_RED); + this.flyoutTextHeaderEnemy.setFontSize(54); + this.flyoutTextHeaderEnemy.setAlign("right"); + this.flyoutTextHeaderEnemy.setOrigin(1, 0); + + this.flyoutContainer.add(this.flyoutTextHeaderEnemy); + + this.flyoutTextPlayer = addTextObject(this.scene, 6, 13, "", TextStyle.BATTLE_INFO); + this.flyoutTextPlayer.setLineSpacing(-1); + this.flyoutTextPlayer.setFontSize(48); + this.flyoutTextPlayer.setAlign("left"); + this.flyoutTextPlayer.setOrigin(0, 0); + + this.flyoutContainer.add(this.flyoutTextPlayer); + + this.flyoutTextField = addTextObject(this.scene, this.flyoutWidth / 2, 13, "", TextStyle.BATTLE_INFO); + this.flyoutTextField.setLineSpacing(-1); + this.flyoutTextField.setFontSize(48); + this.flyoutTextField.setAlign("center"); + this.flyoutTextField.setOrigin(0.5, 0); + + this.flyoutContainer.add(this.flyoutTextField); + + this.flyoutTextEnemy = addTextObject(this.scene, this.flyoutWidth - 6, 13, "", TextStyle.BATTLE_INFO); + this.flyoutTextEnemy.setLineSpacing(-1); + this.flyoutTextEnemy.setFontSize(48); + this.flyoutTextEnemy.setAlign("right"); + this.flyoutTextEnemy.setOrigin(1, 0); + + this.flyoutContainer.add(this.flyoutTextEnemy); + + this.name = "Fight Flyout"; + this.flyoutParent.name = "Fight Flyout Parent"; + + // Subscribes to required events available on game start + this.battleScene.eventTarget.addEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent); + this.battleScene.eventTarget.addEventListener(BattleSceneEventType.TURN_INIT, this.onTurnInitEvent); + this.battleScene.eventTarget.addEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent); + } + + private setTimeOfDayIcon() { + this.timeOfDayIcon.setTexture(TimeOfDay[this.battleScene.arena.getTimeOfDay()].toLowerCase() + "_icon"); + } + + private onTurnInit(event: Event) { + this.setTimeOfDayIcon(); + } + + private onNewArena(event: Event) { + this.fieldEffectInfo.length = 0; + + // Subscribes to required events available on battle start + this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.WEATHER_CHANGED, this.onFieldEffectChangedEvent); + this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.TERRAIN_CHANGED, this.onFieldEffectChangedEvent); + this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.TAG_ADDED, this.onFieldEffectChangedEvent); + this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.TAG_REMOVED, this.onFieldEffectChangedEvent); + + this.setTimeOfDayIcon(); + } + + /** + * Formats a string to title case + * @param unformattedText Text to be formatted + * @returns the formatted string + */ + private formatText(unformattedText: string): string { + const text = unformattedText.split("_"); + for (let i = 0; i < text.length; i++) { + text[i] = text[i].charAt(0).toUpperCase() + text[i].substring(1).toLowerCase(); + } + + return text.join(" "); + } + + /** Clears out the current string stored in all arena effect texts */ + private clearText() { + this.flyoutTextPlayer.text = ""; + this.flyoutTextField.text = ""; + this.flyoutTextEnemy.text = ""; + } + + /** Parses through all set Arena Effects and puts them into the proper {@linkcode Phaser.GameObjects.Text} object */ + private updateFieldText() { + this.clearText(); + + this.fieldEffectInfo.sort((infoA, infoB) => infoA.duration - infoB.duration); + + for (let i = 0; i < this.fieldEffectInfo.length; i++) { + const fieldEffectInfo = this.fieldEffectInfo[i]; + + // Creates a proxy object to decide which text object needs to be updated + let textObject: Phaser.GameObjects.Text; + switch (fieldEffectInfo.type) { + case ArenaEffectType.PLAYER: + textObject = this.flyoutTextPlayer; + break; + + case ArenaEffectType.WEATHER: + case ArenaEffectType.TERRAIN: + case ArenaEffectType.FIELD: + textObject = this.flyoutTextField; + + break; + + case ArenaEffectType.ENEMY: + textObject = this.flyoutTextEnemy; + break; + } + + textObject.text += this.formatText(fieldEffectInfo.name); + if (fieldEffectInfo.type === ArenaEffectType.TERRAIN) { + textObject.text += " Terrain"; // Adds 'Terrain' since the enum does not contain it + } + + if (fieldEffectInfo.maxDuration !== 0) { + textObject.text += " " + fieldEffectInfo.duration + "/" + fieldEffectInfo.maxDuration; + } + + textObject.text += "\n"; + } + } + + /** + * Parses the {@linkcode Event} being passed and updates the state of the fieldEffectInfo array + * @param event {@linkcode Event} being sent + */ + private onFieldEffectChanged(event: Event) { + const arenaEffectChangedEvent = event as ArenaEvent; + if (!arenaEffectChangedEvent) { + return; + } + + let foundIndex: number; + switch (arenaEffectChangedEvent.constructor) { + case TagAddedEvent: + const tagAddedEvent = arenaEffectChangedEvent as TagAddedEvent; + this.fieldEffectInfo.push({ + name: ArenaTagType[tagAddedEvent.arenaTagType], + type: tagAddedEvent.arenaTagSide === ArenaTagSide.BOTH + ? ArenaEffectType.FIELD + : tagAddedEvent.arenaTagSide === ArenaTagSide.PLAYER + ? ArenaEffectType.PLAYER + : ArenaEffectType.ENEMY, + maxDuration: tagAddedEvent.duration, + duration: tagAddedEvent.duration}); + break; + case TagRemovedEvent: + const tagRemovedEvent = arenaEffectChangedEvent as TagRemovedEvent; + foundIndex = this.fieldEffectInfo.findIndex(info => info.name === ArenaTagType[tagRemovedEvent.arenaTagType]); + if (foundIndex !== -1) { // If the tag was being tracked, remove it + this.fieldEffectInfo.splice(foundIndex, 1); + } + break; + + case WeatherChangedEvent: + case TerrainChangedEvent: + const fieldEffectChangedEvent = arenaEffectChangedEvent as WeatherChangedEvent | TerrainChangedEvent; + + // Stores the old Weather/Terrain name in case it's in the array already + const oldName = + fieldEffectChangedEvent instanceof WeatherChangedEvent + ? WeatherType[fieldEffectChangedEvent.oldWeatherType] + : TerrainType[fieldEffectChangedEvent.oldTerrainType]; + // Stores the new Weather/Terrain info + const newInfo = { + name: + fieldEffectChangedEvent instanceof WeatherChangedEvent + ? WeatherType[fieldEffectChangedEvent.newWeatherType] + : TerrainType[fieldEffectChangedEvent.newTerrainType], + type: fieldEffectChangedEvent instanceof WeatherChangedEvent + ? ArenaEffectType.WEATHER + : ArenaEffectType.TERRAIN, + maxDuration: fieldEffectChangedEvent.duration, + duration: fieldEffectChangedEvent.duration}; + + foundIndex = this.fieldEffectInfo.findIndex(info => [newInfo.name, oldName].includes(info.name)); + if (foundIndex === -1) { + if (newInfo.name !== undefined) { + this.fieldEffectInfo.push(newInfo); // Adds the info to the array if it doesn't already exist and is defined + } + } else if (!newInfo.name) { + this.fieldEffectInfo.splice(foundIndex, 1); // Removes the old info if the new one is undefined + } else { + this.fieldEffectInfo[foundIndex] = newInfo; // Otherwise, replace the old info + } + break; + } + + this.updateFieldText(); + } + + /** + * Iterates through the fieldEffectInfo array and decrements the duration of each item + * @param event {@linkcode Event} being sent + */ + private onTurnEnd(event: Event) { + const turnEndEvent = event as TurnEndEvent; + if (!turnEndEvent) { + return; + } + + const fieldEffectInfo: ArenaEffectInfo[] = []; + this.fieldEffectInfo.forEach(i => fieldEffectInfo.push(i)); + + for (let i = 0; i < fieldEffectInfo.length; i++) { + const info = fieldEffectInfo[i]; + + if (info.maxDuration === 0) { + continue; + } + + --info.duration; + if (info.duration <= 0) { // Removes the item if the duration has expired + this.fieldEffectInfo.splice(this.fieldEffectInfo.indexOf(info), 1); + } + } + + this.updateFieldText(); + } + + /** + * Animates the flyout to either show or hide it by applying a fade and translation + * @param visible Should the flyout be shown? + */ + toggleFlyout(visible: boolean): void { + this.scene.tweens.add({ + targets: this.flyoutParent, + x: visible ? this.anchorX : this.anchorX - this.translationX, + duration: Utils.fixedInt(125), + ease: "Sine.easeInOut", + alpha: visible ? 1 : 0, + }); + } + + destroy(fromScene?: boolean): void { + this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent); + this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent); + + this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.WEATHER_CHANGED, this.onFieldEffectChangedEvent); + this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.TERRAIN_CHANGED, this.onFieldEffectChangedEvent); + this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.TAG_ADDED, this.onFieldEffectChangedEvent); + this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.TAG_REMOVED, this.onFieldEffectChangedEvent); + + super.destroy(); + } +} diff --git a/src/ui/ui.ts b/src/ui/ui.ts index b2df4d22259..0f33a9cb6c7 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -222,6 +222,21 @@ export default class UI extends Phaser.GameObjects.Container { return this.handlers[Mode.MESSAGE] as BattleMessageUiHandler; } + processInfoButton(pressed: boolean) { + if (this.overlayActive) { + return false; + } + + const battleScene = this.scene as BattleScene; + if ([Mode.CONFIRM, Mode.COMMAND, Mode.FIGHT, Mode.MESSAGE].includes(this.mode)) { + battleScene?.processInfoButton(pressed); + return true; + } + + battleScene?.processInfoButton(false); + return true; + } + processInput(button: Button): boolean { if (this.overlayActive) { return false;