[QoL] Add Time of Day Widget to Arena Flyout (#1846)

* Time of Day Sample

* Add Proper Time of Day Tracking

* Add Settings
This commit is contained in:
Benjamin Odom 2024-06-05 22:57:55 -05:00 committed by GitHub
parent daa9e1ef0f
commit 5e52be676f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 251 additions and 27 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

View File

@ -20,6 +20,11 @@ export enum BattleSceneEventType {
*/
BERRY_USED = "onBerryUsed",
/**
* Triggers at the start of each new encounter
* @see {@linkcode EncounterPhaseEvent}
*/
ENCOUNTER_PHASE = "onEncounterPhase",
/**
* Triggers on the first turn of a new battle
* @see {@linkcode TurnInitEvent}
@ -85,6 +90,15 @@ export class BerryUsedEvent extends Event {
}
}
/**
* Container class for {@linkcode BattleSceneEventType.ENCOUNTER_PHASE} events
* @extends Event
*/
export class EncounterPhaseEvent extends Event {
constructor() {
super(BattleSceneEventType.ENCOUNTER_PHASE);
}
}
/**
* Container class for {@linkcode BattleSceneEventType.TURN_INIT} events
* @extends Event

View File

@ -60,6 +60,7 @@ import {UiInputs} from "./ui-inputs";
import { MoneyFormat } from "./enums/money-format";
import { NewArenaEvent } from "./battle-scene-events";
import ArenaFlyout from "./ui/arena-flyout";
import { EaseType } from "./ui/enums/ease-type";
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@ -100,6 +101,8 @@ export default class BattleScene extends SceneBase {
public reroll: boolean = false;
public showMovesetFlyout: boolean = true;
public showArenaFlyout: boolean = true;
public showTimeOfDayWidget: boolean = true;
public timeOfDayAnimation: EaseType = EaseType.NONE;
public showLevelUpStats: boolean = true;
public enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1";
public enableMoveInfo: boolean = true;

View File

@ -96,10 +96,18 @@ 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("dawn_icon_fg", "ui");
this.loadImage("dawn_icon_mg", "ui");
this.loadImage("dawn_icon_bg", "ui");
this.loadImage("day_icon_fg", "ui");
this.loadImage("day_icon_mg", "ui");
this.loadImage("day_icon_bg", "ui");
this.loadImage("dusk_icon_fg", "ui");
this.loadImage("dusk_icon_mg", "ui");
this.loadImage("dusk_icon_bg", "ui");
this.loadImage("night_icon_fg", "ui");
this.loadImage("night_icon_mg", "ui");
this.loadImage("night_icon_bg", "ui");
this.loadImage("pb_tray_overlay_player", "ui");
this.loadImage("pb_tray_overlay_enemy", "ui");

View File

@ -62,7 +62,7 @@ import { Abilities } from "./data/enums/abilities";
import * as Overrides from "./overrides";
import { TextStyle, addTextObject } from "./ui/text";
import { Type } from "./data/type";
import { BerryUsedEvent, MoveUsedEvent, TurnEndEvent, TurnInitEvent } from "./battle-scene-events";
import { BerryUsedEvent, EncounterPhaseEvent, MoveUsedEvent, TurnEndEvent, TurnInitEvent } from "./battle-scene-events";
export class LoginPhase extends Phase {
@ -741,6 +741,8 @@ export class EncounterPhase extends BattlePhase {
this.scene.initSession();
this.scene.eventTarget.dispatchEvent(new EncounterPhaseEvent());
// Failsafe if players somehow skip floor 200 in classic mode
if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) {
this.scene.unshiftPhase(new GameOverPhase(this.scene));

View File

@ -7,6 +7,7 @@ import { PlayerGender } from "#app/data/enums/player-gender";
import { CandyUpgradeNotificationChangedEvent } from "#app/battle-scene-events.js";
import { MoneyFormat } from "../../enums/money-format";
import SettingsUiHandler from "#app/ui/settings/settings-ui-handler";
import { EaseType } from "#app/ui/enums/ease-type.js";
const MUTE = "Mute";
const VOLUME_OPTIONS = new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : MUTE);
@ -54,6 +55,8 @@ export const SettingKeys = {
Move_Animations: "MOVE_ANIMATIONS",
Show_Moveset_Flyout: "SHOW_MOVESET_FLYOUT",
Show_Arena_Flyout: "SHOW_ARENA_FLYOUT",
Show_Time_Of_Day_Widget: "SHOW_TIME_OF_DAY_WIDGET",
Time_Of_Day_Animation: "TIME_OF_DAY_ANIMATION",
Show_Stats_on_Level_Up: "SHOW_LEVEL_UP_STATS",
EXP_Gains_Speed: "EXP_GAINS_SPEED",
EXP_Party_Display: "EXP_PARTY_DISPLAY",
@ -205,6 +208,21 @@ export const Setting: Array<Setting> = [
default: 1,
type: SettingType.ACCESSIBILITY
},
{
key: SettingKeys.Show_Time_Of_Day_Widget,
label: "Show Time of Day Widget",
options: OFF_ON,
default: 1,
type: SettingType.ACCESSIBILITY,
requireReload: true,
},
{
key: SettingKeys.Time_Of_Day_Animation,
label: "Time of Day Animation",
options: ["Bounce", "Back"],
default: 0,
type: SettingType.ACCESSIBILITY
},
{
key: SettingKeys.Show_Stats_on_Level_Up,
label: "Show Stats on Level Up",
@ -365,6 +383,12 @@ export function setSetting(scene: BattleScene, setting: string, value: integer):
case SettingKeys.Show_Arena_Flyout:
scene.showArenaFlyout = Setting[index].options[value] === "On";
break;
case SettingKeys.Show_Time_Of_Day_Widget:
scene.showTimeOfDayWidget = Setting[index].options[value] === "On";
break;
case SettingKeys.Time_Of_Day_Animation:
scene.timeOfDayAnimation = Setting[index].options[value] === "Bounce" ? EaseType.BOUNCE : EaseType.BACK;
break;
case SettingKeys.Show_Stats_on_Level_Up:
scene.showLevelUpStats = Setting[index].options[value] === "On";
break;

View File

@ -1,4 +1,3 @@
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";
@ -8,7 +7,8 @@ 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";
import TimeOfDayWidget from "./time-of-day-widget";
import * as Utils from "../utils";
/** Enum used to differentiate {@linkcode Arena} effects */
enum ArenaEffectType {
@ -60,8 +60,7 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container {
/** 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;
private timeOfDayWidget: TimeOfDayWidget;
/** The {@linkcode Phaser.GameObjects.Text} header used to indicate the player's effects */
private flyoutTextHeaderPlayer: Phaser.GameObjects.Text;
@ -82,7 +81,6 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container {
// Stores callbacks in a variable so they can be unsubscribed from when destroyed
private readonly onNewArenaEvent = (event: Event) => this.onNewArena(event);
private readonly onTurnInitEvent = (event: Event) => this.onTurnInit(event);
private readonly onTurnEndEvent = (event: Event) => this.onTurnEnd(event);
private readonly onFieldEffectChangedEvent = (event: Event) => this.onFieldEffectChanged(event);
@ -117,10 +115,8 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container {
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.timeOfDayWidget = new TimeOfDayWidget(this.scene, (this.flyoutWidth / 2) + (this.flyoutWindowHeader.displayWidth / 2));
this.flyoutContainer.add(this.timeOfDayWidget);
this.flyoutTextHeaderPlayer = addTextObject(this.scene, 6, 5, "Player", TextStyle.SUMMARY_BLUE);
this.flyoutTextHeaderPlayer.setFontSize(54);
@ -172,18 +168,9 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container {
// 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;
@ -192,8 +179,6 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container {
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();
}
/**
@ -360,17 +345,18 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container {
* 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 {
public 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,
onComplete: () => this.timeOfDayWidget.parentVisible = visible,
});
}
destroy(fromScene?: boolean): void {
public destroy(fromScene?: boolean): void {
this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent);
this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent);

15
src/ui/enums/ease-type.ts Normal file
View File

@ -0,0 +1,15 @@
export enum EaseType {
NONE,
LINEAR = "Linear",
QUADRATIC = "Quad",
CUBIC = "Cubic",
QUARTIC = "Quart",
QUINTIC = "Quint",
SINUSOIDAL = "Sine",
EXPONENTIAL = "Expo",
CIRCULAR = "Circ",
ELASTIC = "Elastic",
BACK = "Back",
BOUNCE = "Bounce",
STEPPED = "Stepped",
}

View File

@ -0,0 +1,172 @@
import * as Utils from "../utils";
import BattleScene from "#app/battle-scene.js";
import { TimeOfDay } from "#app/data/enums/time-of-day.js";
import { BattleSceneEventType } from "#app/battle-scene-events.js";
import { EaseType } from "./enums/ease-type";
/** A small self contained UI element that displays the time of day as an icon */
export default class TimeOfDayWidget extends Phaser.GameObjects.Container {
/** An alias for the scene typecast to a {@linkcode BattleScene} */
private battleScene: BattleScene;
/** The {@linkcode Phaser.GameObjects.Sprite} that represents the foreground of the current time of day */
private readonly timeOfDayIconFgs: Phaser.GameObjects.Sprite[] = new Array(2);
/** The {@linkcode Phaser.GameObjects.Sprite} that represents the middle-ground of the current time of day */
private readonly timeOfDayIconMgs: Phaser.GameObjects.Sprite[] = new Array(2);
/** The {@linkcode Phaser.GameObjects.Sprite} that represents the background of the current time of day */
private readonly timeOfDayIconBgs: Phaser.GameObjects.Sprite[] = new Array(2);
/** An array containing all timeOfDayIcon objects for easier iteration */
private timeOfDayIcons: Phaser.GameObjects.Sprite[];
/** A map containing all timeOfDayIcon arrays with a matching string key for easier iteration */
private timeOfDayIconPairs: Map<string, Phaser.GameObjects.Sprite[]> = new Map([
["bg", this.timeOfDayIconBgs],
["mg", this.timeOfDayIconMgs],
["fg", this.timeOfDayIconFgs],]);
/** The current time of day */
private currentTime: TimeOfDay = TimeOfDay.ALL;
/** The previous time of day */
private previousTime: TimeOfDay = TimeOfDay.ALL;
// Subscribes to required events available on game start
private readonly onEncounterPhaseEvent = (event: Event) => this.onEncounterPhase(event);
private _parentVisible: boolean;
/** Is the parent object visible? */
public get parentVisible(): boolean {
return this._parentVisible;
}
/** On set, resumes any paused tweens if true */
public set parentVisible(visible: boolean) {
if (visible && !this._parentVisible) { // Only resume the tweens if parent is newly visible
this.timeOfDayIcons?.forEach(
icon => this.scene.tweens.getTweensOf(icon).forEach(
tween => tween.resume()));
}
this._parentVisible = visible;
}
constructor(scene: Phaser.Scene, x: number = 0, y: number = 0) {
super(scene, x, y);
this.battleScene = this.scene as BattleScene;
this.setVisible(this.battleScene.showTimeOfDayWidget);
if (!this.battleScene.showTimeOfDayWidget) {
return;
}
// Initialize all sprites
this.timeOfDayIconPairs.forEach(
(icons, key) => {
for (let i = 0; i < icons.length; i++) {
icons[i] = this.scene.add.sprite(0, 0, "dawn_icon_" + key).setOrigin();
}
});
// Store a flat array of all icons for later
this.timeOfDayIcons = [this.timeOfDayIconBgs, this.timeOfDayIconMgs, this.timeOfDayIconFgs].flat();
this.add(this.timeOfDayIcons);
this.battleScene.eventTarget.addEventListener(BattleSceneEventType.ENCOUNTER_PHASE, this.onEncounterPhaseEvent);
}
/**
* Creates a tween animation based on the 'Back' ease algorithm
* @returns an array of all tweens in the animation
*/
private getBackTween(): Phaser.Types.Tweens.TweenBuilderConfig[] {
const rotate = {
targets: [this.timeOfDayIconMgs[0], this.timeOfDayIconMgs[1]],
angle: "+=90",
duration: Utils.fixedInt(1500),
ease: "Back.easeOut",
paused: !this.parentVisible,
};
const fade = {
targets: [this.timeOfDayIconBgs[1], this.timeOfDayIconMgs[1], this.timeOfDayIconFgs[1]],
alpha: 0,
duration: Utils.fixedInt(500),
ease: "Linear",
paused: !this.parentVisible,
};
return [rotate, fade];
}
/**
* Creates a tween animation based on the 'Bounce' ease algorithm
* @returns an array of all tweens in the animation
*/
private getBounceTween(): Phaser.Types.Tweens.TweenBuilderConfig[] {
const bounce = {
targets: [this.timeOfDayIconMgs[0], this.timeOfDayIconMgs[1]],
angle: "+=90",
duration: Utils.fixedInt(2000),
ease: "Bounce.easeOut",
paused: !this.parentVisible,
};
const fade = {
targets: [this.timeOfDayIconBgs[1], this.timeOfDayIconMgs[1], this.timeOfDayIconFgs[1]],
alpha: 0,
duration: Utils.fixedInt(800),
ease: "Linear",
paused: !this.parentVisible,
};
return [bounce, fade];
}
/** Resets all icons to the proper depth, texture, and alpha so they are ready to tween */
private resetIcons() {
this.moveBelow(this.timeOfDayIconBgs[0], this.timeOfDayIconBgs[1]);
this.moveBelow(this.timeOfDayIconMgs[0], this.timeOfDayIconBgs[1]);
this.moveBelow(this.timeOfDayIconFgs[0], this.timeOfDayIconFgs[1]);
this.timeOfDayIconPairs.forEach(
(icons, key) => {
icons[0].setTexture(TimeOfDay[this.currentTime].toLowerCase() + "_icon_" + key);
icons[1].setTexture(TimeOfDay[this.previousTime].toLowerCase() + "_icon_" + key);
});
this.timeOfDayIconMgs[0].setRotation(-90 * (3.14/180));
this.timeOfDayIcons.forEach(icon => icon.setAlpha(1));
}
/** Adds the proper tween for all icons */
private tweenTimeOfDayIcon() {
this.scene.tweens.killTweensOf(this.timeOfDayIcons);
this.resetIcons();
// Tween based on the player setting
(this.battleScene.timeOfDayAnimation === EaseType.BACK ? this.getBackTween() : this.getBounceTween())
.forEach(tween => this.scene.tweens.add(tween));
// Swaps all elements of the icon arrays by shifting the first element onto the end of the array
// This ensures index[0] is always the new time of day icon and index[1] is always the current one
this.timeOfDayIconPairs.forEach(
icons => icons.push(icons.shift()));
}
/**
* Grabs the current time of day from the arena and calls {@linkcode tweenTimeOfDayIcon}
* @param event {@linkcode Event} being sent
*/
private onEncounterPhase(event: Event) {
const newTime = this.battleScene.arena.getTimeOfDay();
if (this.currentTime === newTime) {
return;
}
this.currentTime = newTime;
this.previousTime = this.currentTime - 1;
if (this.previousTime < TimeOfDay.DAWN) {
this.previousTime = TimeOfDay.NIGHT;
}
this.tweenTimeOfDayIcon();
}
}