From 110823a26b876252302edf62d6aa9ef79aff6522 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 1 Mar 2025 23:40:39 -0700 Subject: [PATCH] Get the delve tour incrementer changes working and affecting fate as well --- langs/en-ca.json | 17 +++++ module/Apps/DelveDiceHUD.mjs | 76 +++++++++++++++++-- module/api.mjs | 4 + module/settings/metaSettings.mjs | 19 ++++- module/settings/worldSettings.mjs | 43 ++++++++++- .../{distanceBetweenFates.mjs => fates.mjs} | 18 +++++ templates/Apps/DelveDiceHUD/tour/current.hbs | 7 +- templates/Apps/DelveDiceHUD/tour/next.hbs | 2 + templates/Apps/DelveDiceHUD/tour/previous.hbs | 2 + 9 files changed, 174 insertions(+), 14 deletions(-) rename module/utils/{distanceBetweenFates.mjs => fates.mjs} (69%) diff --git a/langs/en-ca.json b/langs/en-ca.json index a91a488..184e559 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -118,6 +118,20 @@ "condensedRange": { "name": "Condense Weapon Range Input", "hint": "With this enabled, the weapon range will be displayed as \"X / Y\" when editing a weapon. While disabled it will be as displayed as two different rows, one for Short Range and one for Long Range" + }, + "sandsOfFateInitial": { + "name": "Sands of Fate Initial", + "hint": "What value should The Hourglass reset to when a Cryptic Event occurs" + }, + "onCrypticEvent": { + "name": "Cryptic Event Alert", + "hint": "What happens when a cryptic event occurs by clicking the \"Next Delve Tour\" button in the HUD", + "options": { + "notif": "Notification", + "pause": "Pause Game", + "both": "Notification and Pause Game", + "nothing": "Do Nothing" + } } }, "Apps": { @@ -150,6 +164,9 @@ }, "warn": { "cannot-go-negative": "\"{name}\" is unable to be a negative number." + }, + "info": { + "cryptic-event-alert": "A Cryptic Event Has Occured!" } }, "tooltips": { diff --git a/module/Apps/DelveDiceHUD.mjs b/module/Apps/DelveDiceHUD.mjs index 4e0d358..960665e 100644 --- a/module/Apps/DelveDiceHUD.mjs +++ b/module/Apps/DelveDiceHUD.mjs @@ -1,6 +1,7 @@ -import { distanceBetweenFates } from "../utils/distanceBetweenFates.mjs"; +import { distanceBetweenFates, nextFate, previousFate } from "../utils/fates.mjs"; import { filePath } from "../consts.mjs"; import { gameTerms } from "../gameTerms.mjs"; +import { localizer } from "../utils/Localizer.mjs"; import { Logger } from "../utils/Logger.mjs"; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -128,35 +129,96 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { this.#animateCompassTo(); }; - if (parts.includes(`sandsOfFate`)) {}; + if (parts.includes(`sandsOfFate`)) { + this.#animateSandsTo(); + }; }; #animateCompassTo(newFate) { + if (newFate === this._currentFate) { return }; + /** @type {HTMLElement|undefined} */ const pointer = this.element.querySelector(`.compass-pointer`); if (!pointer) { return }; + newFate ??= game.settings.get(`ripcrypt`, `currentFate`); + let distance = distanceBetweenFates(this._currentFate, newFate); - Logger.table({ newFate, fate: this._currentFate, distance, _rotation: this._rotation }); if (distance === 3) { distance = -1 }; this._rotation += distance * 90; pointer.style.setProperty(`transform`, `rotate(${this._rotation}deg)`); + this._currentFate = newFate; + }; + + #animateSandsTo(newSands) { + /** @type {HTMLElement|undefined} */ + const sands = this.element.querySelector(`.sands-value`); + if (!sands) { return }; + + newSands ??= game.settings.get(`ripcrypt`, `sandsOfFate`); + + sands.innerHTML = newSands; + this._sandsOfFate = newSands; }; // #endregion // #region Actions - static async #tourDelta() {}; + /** @this {DelveDiceHUD} */ + static async #tourDelta(_event, element) { + const delta = parseInt(element.dataset.delta); + const initial = game.settings.get(`ripcrypt`, `sandsOfFateInitial`); + let newSands = this._sandsOfFate + delta; + + if (newSands > initial) { + Logger.info(`Cannot go to a previous Delve Tour above the initial value`); + return; + }; + + if (newSands === 0) { + newSands = initial; + await this.alertCrypticEvent(); + }; + + switch (Math.sign(delta)) { + case -1: { + game.settings.set(`ripcrypt`, `currentFate`, nextFate(this._currentFate)); + break; + } + case 1: { + game.settings.set(`ripcrypt`, `currentFate`, previousFate(this._currentFate)); + break; + } + }; + + this.#animateSandsTo(newSands); + game.settings.set(`ripcrypt`, `sandsOfFate`, newSands); + }; /** @this {DelveDiceHUD} */ static async #setFate(_event, element) { const fate = element.dataset.toFate; this.#animateCompassTo(fate); - - // must be done after animate, otherwise it won't change the rotation - this._currentFate = fate; game.settings.set(`ripcrypt`, `currentFate`, fate); }; // #endregion + + // #region Public API + async alertCrypticEvent() { + const alertType = game.settings.get(`ripcrypt`, `onCrypticEvent`); + if (alertType === `nothing`) { return }; + + if ([`both`, `notif`].includes(alertType)) { + ui.notifications.info( + localizer(`RipCrypt.notifs.info.cryptic-event-alert`), + { console: false }, + ); + }; + + if ([`both`, `pause`].includes(alertType) && game.user.isGM) { + game.togglePause(true, { broadcast: true }); + }; + }; + // #endregion }; diff --git a/module/api.mjs b/module/api.mjs index 7b04ea8..d2e50ac 100644 --- a/module/api.mjs +++ b/module/api.mjs @@ -6,6 +6,7 @@ import { HeroSummaryCardV1 } from "./Apps/ActorSheets/HeroSummaryCardV1.mjs"; import { RichEditor } from "./Apps/RichEditor.mjs"; // Util imports +import { distanceBetweenFates, nextFate, previousFate } from "./utils/fates.mjs"; import { documentSorter } from "./consts.mjs"; const { deepFreeze } = foundry.utils; @@ -24,6 +25,9 @@ Object.defineProperty( }, utils: { documentSorter, + distanceBetweenFates, + nextFate, + previousFate, }, }), writable: false, diff --git a/module/settings/metaSettings.mjs b/module/settings/metaSettings.mjs index 77bc3dc..66fb669 100644 --- a/module/settings/metaSettings.mjs +++ b/module/settings/metaSettings.mjs @@ -1,3 +1,8 @@ +import { gameTerms } from "../gameTerms.mjs"; + +const { StringField } = foundry.data.fields; +const { FatePath } = gameTerms; + export function registerMetaSettings() { game.settings.register(`ripcrypt`, `dc`, { scope: `world`, @@ -15,15 +20,23 @@ export function registerMetaSettings() { initial: 8, config: false, requiresReload: false, - onChange: async () => {}, + onChange: async () => { + ui.delveDice.animate({ parts: [`sandsOfFate`] }); + }, }); game.settings.register(`ripcrypt`, `currentFate`, { scope: `world`, - type: String, + type: new StringField({ + blank: false, + nullable: false, + initial: FatePath.NORTH, + }), config: false, requiresReload: false, - onChange: async () => {}, + onChange: async () => { + ui.delveDice.animate({ parts: [`fateCompass`] }); + }, }); game.settings.register(`ripcrypt`, `whoFirst`, { diff --git a/module/settings/worldSettings.mjs b/module/settings/worldSettings.mjs index d7c426a..d193a32 100644 --- a/module/settings/worldSettings.mjs +++ b/module/settings/worldSettings.mjs @@ -1,10 +1,51 @@ +const { NumberField, StringField } = foundry.data.fields; + export function registerWorldSettings() { game.settings.register(`ripcrypt`, `showDelveTour`, { name: `Delve Tour Popup`, scope: `world`, type: Boolean, - config: true, + config: false, default: true, requiresReload: false, }); + + game.settings.register(`ripcrypt`, `sandsOfFateInitial`, { + name: `RipCrypt.setting.sandsOfFateInitial.name`, + hint: `RipCrypt.setting.sandsOfFateInitial.hint`, + scope: `world`, + config: true, + requiresReload: false, + type: new NumberField({ + required: true, + min: 1, + step: 1, + max: 10, + initial: 8, + }), + onChange: async (newInitialSands) => { + const currentSands = game.settings.get(`ripcrypt`, `sandsOfFate`); + if (newInitialSands <= currentSands) { + game.settings.set(`ripcrypt`, `sandsOfFate`, newInitialSands); + }; + }, + }); + + game.settings.register(`ripcrypt`, `onCrypticEvent`, { + name: `RipCrypt.setting.onCrypticEvent.name`, + hint: `RipCrypt.setting.onCrypticEvent.hint`, + scope: `world`, + config: true, + requiresReload: false, + type: new StringField({ + required: true, + initial: `notif`, + choices: { + "notif": `RipCrypt.setting.onCrypticEvent.options.notif`, + "pause": `RipCrypt.setting.onCrypticEvent.options.pause`, + "both": `RipCrypt.setting.onCrypticEvent.options.both`, + "nothing": `RipCrypt.setting.onCrypticEvent.options.nothing`, + }, + }), + }); }; diff --git a/module/utils/distanceBetweenFates.mjs b/module/utils/fates.mjs similarity index 69% rename from module/utils/distanceBetweenFates.mjs rename to module/utils/fates.mjs index 8c57240..d1fb6dd 100644 --- a/module/utils/distanceBetweenFates.mjs +++ b/module/utils/fates.mjs @@ -31,3 +31,21 @@ export function distanceBetweenFates(start, end) { }; return 3; }; + +const fateOrder = [ + FatePath.WEST, // to make the .find not integer overflow + FatePath.NORTH, + FatePath.EAST, + FatePath.SOUTH, + FatePath.WEST, +]; + +export function nextFate(fate) { + const fateIndex = fateOrder.findIndex(f => f === fate); + return fateOrder[fateIndex + 1]; +}; + +export function previousFate(fate) { + const fateIndex = fateOrder.lastIndexOf(fate); + return fateOrder[fateIndex - 1]; +}; diff --git a/templates/Apps/DelveDiceHUD/tour/current.hbs b/templates/Apps/DelveDiceHUD/tour/current.hbs index 9e3d423..18b98d9 100644 --- a/templates/Apps/DelveDiceHUD/tour/current.hbs +++ b/templates/Apps/DelveDiceHUD/tour/current.hbs @@ -1,12 +1,13 @@
- - 8 + + {{sandsOfFate}}