Title Rework

This commit is contained in:
Matthew Olker 2024-06-27 23:19:53 -04:00
parent fbf60877ef
commit e83d3c8929
17 changed files with 230 additions and 38 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

View File

@ -40,6 +40,8 @@ export class LoadingScene extends SceneBase {
this.loadImage("loading_bg", "arenas");
this.loadImage("logo", "");
this.loadImage("pride-update", "events");
this.loadImage("ivy-sprite", "");
this.loadImage("finn-sprite", "");
// Load menu images
this.loadAtlas("bg", "ui");
@ -53,6 +55,7 @@ export class LoadingScene extends SceneBase {
this.loadImage(`window_${w}${getWindowVariantSuffix(wv)}`, "ui/windows");
}
}
this.loadImage("window_speech", "");
this.loadImage("discord", "ui");
this.loadImage("reddit", "ui");
this.loadImage("github", "ui");

View File

@ -32,8 +32,8 @@ export const settings: SimpleTranslationEntries = {
"language": "Language",
"change": "Change",
"uiTheme": "UI Theme",
"default": "Default",
"legacy": "Legacy",
"default": "Gen V",
"legacy": "Gen III",
"windowType": "Window Type",
"moneyFormat": "Money Format",
"damageNumbers": "Damage Numbers",

View File

@ -199,15 +199,6 @@ export class TitlePhase extends Phase {
showOptions(): OptionSelectConfig {
const options: OptionSelectItem[] = [];
// if (loggedInUser.lastSessionSlot > -1) {
// options.push({
// label: i18next.t("menu:continue"),
// handler: () => {
// this.loadSaveSlot(this.lastSessionData ? -1 : loggedInUser.lastSessionSlot);
// return true;
// }
// });
// }
options.push({
label: i18next.t("menu:loadGame"),
handler: () => this.loadGameHandler(),
@ -232,9 +223,9 @@ export class TitlePhase extends Phase {
return {
options: options,
noCancel: true,
xOffset: 220,
yOffset: 13,
noBg: true,
xOffset: 216,
yOffset: 1,
noBg: true
};
}
@ -389,7 +380,7 @@ export class TitlePhase extends Phase {
} else {
this.scene.playBgm();
}
this.scene.ui.getMessageHandler().bg.setVisible(true);
this.scene.pushPhase(new EncounterPhase(this.scene, this.loaded));
if (this.loaded) {

View File

@ -77,13 +77,14 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
}
setup() {
this.banner = new Phaser.GameObjects.Image(this.scene, 0, 0, this.event.bannerFilename);
this.banner = new Phaser.GameObjects.Image(this.scene, 3, -1, this.event.bannerFilename);
this.banner.setName("img-event-banner");
this.banner.setOrigin(0, 0);
this.banner.setScale(0.05);
this.banner.setScale(0.10);
this.bannerShadow = new Phaser.GameObjects.Rectangle(
this.scene,
this.banner.x - 2,
this.banner.x + 2,
this.banner.y + 2,
this.banner.width * this.banner.scaleX,
this.banner.height * this.banner.scaleY,
@ -92,15 +93,16 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
this.bannerShadow.setName("rect-event-banner-shadow");
this.bannerShadow.setAlpha(0.5);
this.bannerShadow.setOrigin(0,0);
this.eventTimerText = addTextObject(
this.scene,
this.banner.x + 3,
this.banner.y - 10,
this.banner.x,
this.banner.y + 49,
this.timeToGo(this.event.endDate),
TextStyle.BATTLE_INFO
TextStyle.SUMMARY_ALT,
{ fontSize: 72 }
);
this.eventTimerText.setName("text-event-timer");
this.eventTimerText.setScale(0.15);
this.eventTimerText.setOrigin(0,0);
this.add([this.eventTimerText, this.bannerShadow, this.banner]);

View File

@ -16,6 +16,7 @@ export interface OptionSelectConfig {
supportHover?: boolean;
noBg?: boolean;
textStyle?: TextStyle;
extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle;
}
export interface OptionSelectItem {
@ -81,12 +82,18 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
Phaser.Actions.Call(this.optionSelectText, (text) => text.destroy(), this);
}
const optionText = this.config?.options.length > this.config?.maxOptions ? this.getOptionsWithScroll() : options;
const optionTextStyle = {
maxLines: options.length, lineSpacing: 12
};
if (this.config.extraStyleOptions) {
Object.assign(optionTextStyle, this.config.extraStyleOptions);
}
this.optionSelectText = [];
this.optionSelectText = optionText.map(o => {
const ret = addTextObject(this.scene, 0, 0,
o.item ? ` ${o.label}` : o.label,
( this.config?.textStyle ?? TextStyle.WINDOW),
{ maxLines: options.length, lineSpacing: 12 }
optionTextStyle
);
ret.setName(`text-${o.label}`);
return ret;

View File

@ -0,0 +1,52 @@
export class SpeechBubble extends Phaser.GameObjects.Container {
private bubble: Phaser.GameObjects.NineSlice;
private trail: Phaser.GameObjects.Triangle;
private trailStrokes: Phaser.GameObjects.Line[];
private text: Phaser.GameObjects.Text;
constructor(scene, x, y, text?: Phaser.GameObjects.Text) {
super(scene, x, y);
this.text = text;
this.text.setPadding(0, 0);
this.text.setPosition(-(this.text.width / 12), -(this.text.height / 12));
this.bubble = new Phaser.GameObjects.NineSlice(
scene,
0, 0,
"window_speech", null,
(this.text.width / 6) + 18, 100);
this.bubble.setName("speech-bubble");
this.trail = new Phaser.GameObjects.Triangle(
scene,
0, 0,
-51, 21,
-51, 11,
-39, 13,
0xffffff,
1
);
this.trail.setName("speech-bubble-trail");
this.trailStrokes = [];
this.trailStrokes.push(
new Phaser.GameObjects.Line(
scene,
0, 0,
-57, 19,
-57, 13,
0xa6a6a6, 1
),
new Phaser.GameObjects.Line(
scene,
0, 0,
-52, 19,
-41, 13,
0xa6a6a6, 1
),
);
this.add([this.bubble, this.trail, ...this.trailStrokes, this.text]);
}
}

View File

@ -27,10 +27,12 @@ export default class AbstractSettingsUiHandler extends UiHandler {
private settingLabels: Phaser.GameObjects.Text[];
private optionValueLabels: Phaser.GameObjects.Text[][];
private reloadRequiredText: Phaser.GameObjects.Text;
protected navigationIcons: InputsIcons;
private cursorObj: Phaser.GameObjects.NineSlice;
private actionGroup: Phaser.GameObjects.Group;
private reloadSettings: Array<Setting>;
private reloadRequired: boolean;
@ -39,6 +41,7 @@ export default class AbstractSettingsUiHandler extends UiHandler {
protected title: string;
protected settings: Array<Setting>;
protected localStorageKey: string;
protected actionButtons: boolean = true;
constructor(scene: BattleScene, mode?: Mode) {
super(scene, mode);
@ -53,6 +56,8 @@ export default class AbstractSettingsUiHandler extends UiHandler {
setup() {
const ui = this.getUi();
this.actionGroup = new Phaser.GameObjects.Group(this.scene);
this.settingsContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1);
this.settingsContainer.setName(`settings-${this.title}`);
this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6 - 20), Phaser.Geom.Rectangle.Contains);
@ -61,7 +66,12 @@ export default class AbstractSettingsUiHandler extends UiHandler {
this.navigationContainer = new NavigationMenu(this.scene, 0, 0);
this.optionsBg = addWindow(this.scene, 0, this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 16 - this.navigationContainer.height - 2);
this.optionsBg = addWindow(
this.scene, 0,
this.navigationContainer.height - 1,
(this.scene.game.canvas.width / 6) - 2,
(this.scene.game.canvas.height / 6) - 16 - this.navigationContainer.height - 2
);
this.optionsBg.setName("window-options-bg");
this.optionsBg.setOrigin(0, 0);
@ -69,23 +79,35 @@ export default class AbstractSettingsUiHandler extends UiHandler {
actionsBg.setOrigin(0, 0);
const iconAction = this.scene.add.sprite(0, 0, "keyboard");
iconAction.setName("icon-action");
iconAction.setOrigin(0, -0.1);
iconAction.setPositionRelative(actionsBg, this.navigationContainer.width - 32, 4);
this.navigationIcons["BUTTON_ACTION"] = iconAction;
const actionText = addTextObject(this.scene, 0, 0, i18next.t("settings:action"), TextStyle.SETTINGS_LABEL);
actionText.setName("text-action");
actionText.setOrigin(0, 0.15);
actionText.setPositionRelative(iconAction, -actionText.width/6-2, 0);
const iconCancel = this.scene.add.sprite(0, 0, "keyboard");
iconCancel.setName("icon-cancel");
iconCancel.setOrigin(0, -0.1);
iconCancel.setPositionRelative(actionsBg, this.navigationContainer.width - 100, 4);
this.navigationIcons["BUTTON_CANCEL"] = iconCancel;
const cancelText = addTextObject(this.scene, 0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL);
cancelText.setName("text-cancel");
cancelText.setOrigin(0, 0.15);
cancelText.setPositionRelative(iconCancel, -cancelText.width/6-2, 0);
this.reloadRequiredText = addTextObject(
this.scene,
8, 159,
`* ${i18next.t("settings:requireReload")}`,
TextStyle.SETTINGS_LABEL
);
this.reloadRequiredText.setName("reload-required");
this.optionsContainer = this.scene.add.container(0, 0);
this.settingLabels = [];
@ -97,7 +119,7 @@ export default class AbstractSettingsUiHandler extends UiHandler {
.forEach((setting, s) => {
let settingName = setting.label;
if (setting?.requireReload) {
settingName += ` (${i18next.t("settings:requireReload")})`;
settingName += " *";
}
this.settingLabels[s] = addTextObject(this.scene, 8, 28 + s * 16, settingName, TextStyle.SETTINGS_LABEL);
@ -138,6 +160,11 @@ export default class AbstractSettingsUiHandler extends UiHandler {
this.settingsContainer.add(iconCancel);
this.settingsContainer.add(actionText);
this.settingsContainer.add(cancelText);
this.settingsContainer.add(this.reloadRequiredText);
this.actionGroup.addMultiple([
iconAction, iconCancel, actionText, cancelText
]);
ui.add(this.settingsContainer);
@ -185,6 +212,8 @@ export default class AbstractSettingsUiHandler extends UiHandler {
this.settings.forEach((setting, s) => this.setOptionCursor(s, settings.hasOwnProperty(setting.key) ? settings[setting.key] : this.settings[s].default));
this.settingsContainer.setVisible(true);
Phaser.Actions.SetVisible(this.actionGroup.getChildren(), this.actionButtons);
this.reloadRequiredText.setVisible(!this.actionButtons);
this.setCursor(0);
this.getUi().moveTo(this.settingsContainer, this.getUi().length - 1);

View File

@ -3,6 +3,7 @@ import { Mode } from "../ui";
"#app/inputs-controller.js";
import AbstractSettingsUiHandler from "./abstract-settings-ui-handler";
import { Setting, SettingKeys, SettingType } from "#app/system/settings/settings";
import TitleUiHandler from "#app/ui/title-ui-handler.js";
export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler {
/**
@ -15,6 +16,7 @@ export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler
super(scene, mode);
this.title = "Display";
this.settings = Setting.filter(s => s.type === SettingType.DISPLAY);
this.actionButtons = false;
/**
* Update to current language from default value.
@ -90,4 +92,9 @@ export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler
this.localStorageKey = "settings";
}
clear() {
super.clear();
(this.getUi().handlers[Mode.TITLE] as TitleUiHandler).update();
}
}

View File

@ -15,5 +15,6 @@ export default class SettingsUiHandler extends AbstractSettingsUiHandler {
this.title = "General";
this.settings = Setting.filter(s => s.type === SettingType.GENERAL);
this.localStorageKey = "settings";
this.actionButtons = false;
}
}

View File

@ -122,8 +122,8 @@ function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraStyleOptio
case TextStyle.MONEY:
case TextStyle.TOOLTIP_TITLE:
styleOptions.fontSize = defaultFontSize - 24;
shadowXpos = 3.5;
shadowYpos = 3.5;
shadowXpos = 3;
shadowYpos = 3;
break;
case TextStyle.PARTY:
case TextStyle.PARTY_RED:
@ -146,8 +146,11 @@ function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraStyleOptio
if (extraStyleOptions) {
if (extraStyleOptions.fontSize) {
const sizeRatio = parseInt(extraStyleOptions.fontSize.toString().slice(0, -2)) / parseInt(styleOptions.fontSize.toString().slice(0, -2));
const currentSize = typeof styleOptions.fontSize === "string" ? Number(styleOptions.fontSize.slice(0, -2)) : styleOptions.fontSize;
const newSize = typeof extraStyleOptions.fontSize === "string" ? Number(extraStyleOptions.fontSize.slice(0, -2)) : extraStyleOptions.fontSize;
const sizeRatio = newSize / currentSize;
shadowXpos *= sizeRatio;
shadowYpos *= sizeRatio;
}
styleOptions = Object.assign(styleOptions, extraStyleOptions);
}

View File

@ -6,15 +6,24 @@ import { TextStyle, addTextObject } from "./text";
import { getSplashMessages } from "../data/splash-messages";
import i18next from "i18next";
import { TimedEventDisplay } from "#app/timed-event-manager.js";
import { Color } from "#app/enums/color.js";
import { PlayerGender } from "#app/enums/player-gender.js";
import { SpeechBubble } from "#app/ui/components/speech-bubble.js";
export default class TitleUiHandler extends OptionSelectUiHandler {
private titleContainer: Phaser.GameObjects.Container;
private logo: Phaser.GameObjects.Image;
private playerCountLabel: Phaser.GameObjects.Text;
private playerCountWidth: number;
private splashMessage: string;
private splashMessageText: Phaser.GameObjects.Text;
private eventDisplay: TimedEventDisplay;
private iconContainer: TitleIcons;
private menuOverlay: Phaser.GameObjects.Rectangle;
private rivalSprite: Phaser.GameObjects.Sprite;
private spriteShadow: Phaser.GameObjects.Ellipse;
private bubble: SpeechBubble;
private rivalText: Phaser.GameObjects.Text;
private titleStatsTimer: NodeJS.Timeout;
@ -27,30 +36,93 @@ export default class TitleUiHandler extends OptionSelectUiHandler {
const ui = this.getUi();
const overlayColor = this.scene.uiTheme ? Color.OFF_WHITE : Color.DARK_GREY;
this.titleContainer = this.scene.add.container(0, -(this.scene.game.canvas.height / 6));
this.titleContainer.setName("title");
this.titleContainer.setAlpha(0);
ui.add(this.titleContainer);
this.logo = this.scene.add.image((this.scene.scaledCanvas.width / 4) + 3, 8, "logo");
this.logo = this.scene.add.image(
(this.scene.scaledCanvas.width / 4) + 3,
8,
"logo"
);
this.logo.setName("logo");
this.logo.setOrigin(0.5, 0);
this.titleContainer.add(this.logo);
this.iconContainer = new TitleIcons(this.scene, 15, this.scene.scaledCanvas.height - 15);
this.menuOverlay = this.scene.add.rectangle(
8, 59,
92, 71,
Number(`0x${overlayColor.slice(1)}`),
0.5
);
this.menuOverlay.setName("title-options-bg");
this.menuOverlay.setOrigin(0);
this.menuOverlay.setBlendMode(Phaser.BlendModes.OVERLAY);
this.titleContainer.add(this.menuOverlay);
this.iconContainer = new TitleIcons(
this.scene,
15, this.scene.scaledCanvas.height - 15
);
this.iconContainer.setup();
this.titleContainer.add(this.iconContainer);
this.rivalSprite = new Phaser.GameObjects.Sprite(
this.scene,
176, 127,
`${this.scene.gameData.gender === PlayerGender.MALE ? "ivy" : "finn" }-sprite`
);
this.rivalSprite.setName("rival");
this.spriteShadow = new Phaser.GameObjects.Ellipse(
this.scene,
this.rivalSprite.x, (this.rivalSprite.y + this.rivalSprite.height / 2) - 1,
this.rivalSprite.width / 2, this.rivalSprite.height / 10,
Number(`0x${Color.DARK_GREY}`), 0.5
);
this.spriteShadow.setName("sprite-shadow");
this.spriteShadow.setBlendMode(Phaser.BlendModes.OVERLAY);
this.titleContainer.add([this.rivalSprite, this.spriteShadow]);
if (this.scene.eventManager.isEventActive()) {
this.eventDisplay = new TimedEventDisplay(this.scene, 170, 66, this.scene.eventManager.activeEvent());
this.eventDisplay = new TimedEventDisplay(this.scene, 189, 49, this.scene.eventManager.activeEvent());
this.eventDisplay.setup();
this.titleContainer.add(this.eventDisplay);
} else {
this.rivalText = addTextObject(
this.scene,
190,
98,
"Check the Discord for the latest changes!",
TextStyle.WINDOW_ALT,
{ fontSize: 49 }
);
this.rivalText.setOrigin(0);
this.rivalText.setName("text-rival-changelog");
}
this.playerCountLabel = addTextObject(this.scene, 8, this.scene.scaledCanvas.height - 132, i18next.t("menu:playersOnline", { count: 0 }), TextStyle.MESSAGE, { fontSize: "54px" });
console.log(this.playerCountLabel);
this.bubble = new SpeechBubble(this.scene, 244, 102, (this.eventDisplay?.getByName("text-event-timer") ?? this.rivalText) as Phaser.GameObjects.Text);
this.titleContainer.add(this.bubble);
this.playerCountLabel = addTextObject(
this.scene,
this.scene.scaledCanvas.width - 60,
this.scene.scaledCanvas.height - 20,
i18next.t("menu:playersOnline", { count: 0 }),
TextStyle.MESSAGE,
{
fontSize: "60px",
align: "right",
}
);
this.playerCountLabel.setName("player-count");
this.playerCountLabel.setOrigin(0);
this.playerCountWidth = this.playerCountLabel.width;
this.titleContainer.add(this.playerCountLabel);
this.splashMessageText = addTextObject(this.scene, this.logo.x + 64, this.logo.y + this.logo.displayHeight - 8, "", TextStyle.MONEY, { fontSize: "54px" });
@ -75,6 +147,8 @@ export default class TitleUiHandler extends OptionSelectUiHandler {
.then(request => request.json())
.then((stats: { playerCount: number, battleCount: number }) => {
this.playerCountLabel.setText(i18next.t("menu:playersOnline", { count: stats.playerCount }));
this.playerCountLabel.setX(this.playerCountLabel.x - ((this.playerCountLabel.width - this.playerCountWidth) / 6));
this.playerCountWidth = this.playerCountLabel.width;
})
.catch(err => {
console.error("Failed to fetch title stats:\n", err);
@ -86,7 +160,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler {
if (ret) {
this.splashMessage = Utils.randItem(getSplashMessages());
this.splashMessageText.setText(this.splashMessage.replace("{COUNT}", "?"));
this.splashMessageText.setText(this.splashMessage);
const ui = this.getUi();
@ -94,7 +168,9 @@ export default class TitleUiHandler extends OptionSelectUiHandler {
this.eventDisplay.show();
}
this.bubble.setVisible(true);
this.iconContainer.setVisible(true);
this.update();
this.updateTitleStats();
@ -113,6 +189,12 @@ export default class TitleUiHandler extends OptionSelectUiHandler {
return ret;
}
update() {
const playerMale = this.scene.gameData.gender === PlayerGender.MALE;
this.rivalSprite.setTexture(`${playerMale ? "ivy" : "finn" }-sprite`);
this.spriteShadow.setY((this.rivalSprite.y + this.rivalSprite.height / 2) - (playerMale ? 2 : 3));
}
clear(): void {
super.clear();
@ -120,6 +202,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler {
this.eventDisplay?.setVisible(false);
this.iconContainer?.setVisible(false);
this.bubble.setVisible(false);
clearInterval(this.titleStatsTimer);
this.titleStatsTimer = null;
@ -192,9 +275,11 @@ class Icon extends Phaser.GameObjects.Sprite {
this.setAlpha(this.DEFAULT_ALPHA);
this.on(Phaser.Input.Events.GAMEOBJECT_POINTER_OVER, () => {
this.setAlpha(1);
scene.ui.showTooltip("", texture, true);
});
this.on(Phaser.Input.Events.GAMEOBJECT_POINTER_OUT, () => {
this.setAlpha(this.DEFAULT_ALPHA);
scene.ui.hideTooltip();
});
this.on(Phaser.Input.Events.GAMEOBJECT_POINTER_DOWN, () => {
window.open(link, "_blank").focus();

View File

@ -220,6 +220,7 @@ export default class UI extends Phaser.GameObjects.Container {
this.tooltipContent = addTextObject(this.scene, 6, 16, "", TextStyle.TOOLTIP_CONTENT);
this.tooltipContent.setName("text-tooltip-content");
this.tooltipContent.setAlign("center");
this.tooltipContent.setWordWrapWidth(696);
this.tooltipContainer.add(this.tooltipBg);
@ -365,8 +366,13 @@ export default class UI extends Phaser.GameObjects.Container {
update(): void {
if (this.tooltipContainer.visible) {
const reverse = this.scene.game.input.mousePointer.x >= this.scene.game.canvas.width - this.tooltipBg.width * 6 - 12;
this.tooltipContainer.setPosition(!reverse ? this.scene.game.input.mousePointer.x / 6 + 2 : this.scene.game.input.mousePointer.x / 6 - this.tooltipBg.width - 2, this.scene.game.input.mousePointer.y / 6 + 2);
const reverseX = this.scene.game.input.mousePointer.x >= this.scene.game.canvas.width - this.tooltipBg.width * 6 - 12;
const reverseY = this.scene.game.input.mousePointer.y >= this.scene.game.canvas.height - this.tooltipBg.height * 6 - 12;
this.tooltipContainer.setPosition(
!reverseX ? this.scene.game.input.mousePointer.x / 6 + 2 : this.scene.game.input.mousePointer.x / 6 - this.tooltipBg.width - 2,
!reverseY ? this.scene.game.input.mousePointer.y / 6 + 2 : this.scene.game.input.mousePointer.y / 6 - this.tooltipBg.height - 2
);
this.tooltipTitle.setX(this.tooltipBg.width / 2);
}
}

View File

@ -433,13 +433,19 @@ export function deltaRgb(rgb1: integer[], rgb2: integer[]): integer {
return Math.ceil(Math.sqrt(2 * drp2 + 4 * dgp2 + 3 * dbp2 + t * (drp2 - dbp2) / 256));
}
export function rgbHexToRgba(hex: string) {
/**
* Converts a hex code for a color to rgba
* @param {string} hex hex code
* @param {number} alpha transparency between 0 and 1
* @returns rgba object
*/
export function rgbHexToRgba(hex: string, alpha?: number): { r: number, g: number, b: number, a: number } {
const color = hex.match(/^([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i);
return {
r: parseInt(color[1], 16),
g: parseInt(color[2], 16),
b: parseInt(color[3], 16),
a: 255
a: 255 * (alpha ?? 1)
};
}