From c7342b6402fcec6a822e1cb12d41ddf05873b5bc Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 27 Feb 2025 22:56:36 -0700 Subject: [PATCH 01/16] Begin work on the updated delve dice HUD that is better in every way than the other version --- module/Apps/DelveDiceHUD.mjs | 106 ++++++++++++++++++ module/hooks/init.mjs | 3 + module/hooks/ready.mjs | 2 + module/settings/metaSettings.mjs | 17 ++- templates/Apps/DelveDiceHUD/difficulty.hbs | 3 + templates/Apps/DelveDiceHUD/fateCompass.hbs | 3 + templates/Apps/DelveDiceHUD/style.css | 18 +++ templates/Apps/DelveDiceHUD/tour/current.hbs | 3 + templates/Apps/DelveDiceHUD/tour/next.hbs | 7 ++ templates/Apps/DelveDiceHUD/tour/previous.hbs | 7 ++ templates/Apps/apps.css | 1 + templates/css/elements/button.css | 2 +- templates/css/themes/dark.css | 4 + 13 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 module/Apps/DelveDiceHUD.mjs create mode 100644 templates/Apps/DelveDiceHUD/difficulty.hbs create mode 100644 templates/Apps/DelveDiceHUD/fateCompass.hbs create mode 100644 templates/Apps/DelveDiceHUD/style.css create mode 100644 templates/Apps/DelveDiceHUD/tour/current.hbs create mode 100644 templates/Apps/DelveDiceHUD/tour/next.hbs create mode 100644 templates/Apps/DelveDiceHUD/tour/previous.hbs diff --git a/module/Apps/DelveDiceHUD.mjs b/module/Apps/DelveDiceHUD.mjs new file mode 100644 index 0000000..0122aea --- /dev/null +++ b/module/Apps/DelveDiceHUD.mjs @@ -0,0 +1,106 @@ +import { filePath } from "../consts.mjs"; +import { Logger } from "../utils/Logger.mjs"; + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; + +const conditions = [ + { label: `RipCrypt.common.difficulties.easy`, value: 4 }, + { label: `RipCrypt.common.difficulties.normal`, value: 5 }, + { label: `RipCrypt.common.difficulties.tough`, value: 6 }, + { label: `RipCrypt.common.difficulties.hard`, value: 7 }, +]; + +export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { + // #region Options + static DEFAULT_OPTIONS = { + id: `ripcrypt-delve-dice`, + tag: `aside`, + classes: [ + `ripcrypt`, + `ripcrypt--DelveDiceHUD` + ], + window: { + frame: false, + positioned: false, + }, + actions: { + tourDelta: this.#tourDelta, + }, + }; + + static PARTS = { + previousTour: { + template: filePath(`templates/Apps/DelveDiceHUD/tour/previous.hbs`), + }, + difficulty: { + template: filePath(`templates/Apps/DelveDiceHUD/difficulty.hbs`), + }, + fateCompass: { + template: filePath(`templates/Apps/DelveDiceHUD/fateCompass.hbs`), + }, + currentTour: { + template: filePath(`templates/Apps/DelveDiceHUD/tour/current.hbs`), + }, + nextTour: { + template: filePath(`templates/Apps/DelveDiceHUD/tour/next.hbs`), + }, + }; + // #endregion + + // #region Lifecycle + /** + * Injects the element into the Foundry UI in the top middle + */ + _insertElement(element) { + const existing = document.getElementById(element.id); + if (existing) { + existing.replaceWith(element); + } else { + const parent = document.getElementById(`ui-top`); + parent.prepend(element); + }; + }; + + async _onRender(context, options) { + await super._onRender(context, options); + + // Shortcut because users can't edit + if (!game.user.isGM) { return }; + }; + + async _preparePartContext(partId, ctx, opts) { + ctx = await super._preparePartContext(partId, ctx, opts); + ctx.meta ??= {}; + + ctx.meta.editable = game.user.isGM; + + switch (partId) { + case `currentTour`: { + await this._prepareTourContext(ctx); + break; + }; + case `difficulty`: { + await this._prepareDifficultyContext(ctx); + break; + }; + }; + + Logger.log(`${partId} Context`, ctx); + return ctx; + }; + + async _prepareTourContext(ctx) { + ctx.tour = game.settings.get(`ripcrypt`, `sandsOfFate`); + }; + + async _prepareDifficultyContext(ctx) { + ctx.dc = game.settings.get(`ripcrypt`, `dc`); + } + // #endregion + + // #region Actions + static async #tourDelta() { + ui.notifications.info(`Button Clicked!`, { console: false }); + }; + // #endregion +}; diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 812c6e2..4005cc5 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -1,6 +1,7 @@ // Applications import { AllItemSheetV1 } from "../Apps/ItemSheets/AllItemSheetV1.mjs"; import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs"; +import { DelveDiceHUD } from "../Apps/DelveDiceHUD.mjs"; import { DelveTourApp } from "../Apps/DelveTourApp.mjs"; import { HeroSkillsCardV1 } from "../Apps/ActorSheets/HeroSkillsCardV1.mjs"; import { HeroSummaryCardV1 } from "../Apps/ActorSheets/HeroSummaryCardV1.mjs"; @@ -39,6 +40,8 @@ Hooks.once(`init`, () => { CONFIG.Combat.initiative.decimals = 2; CONFIG.ui.crypt = DelveTourApp; + CONFIG.ui.delveDice = DelveDiceHUD; + // globalThis.delveDice = new DelveDiceHUD(); // #region Settings registerMetaSettings(); diff --git a/module/hooks/ready.mjs b/module/hooks/ready.mjs index c71bc25..c6c0167 100644 --- a/module/hooks/ready.mjs +++ b/module/hooks/ready.mjs @@ -23,6 +23,8 @@ Hooks.once(`ready`, () => { ui.crypt.render({ force: true }); }; + ui.delveDice.render({ force: true }); + // MARK: 1-time updates if (!game.settings.get(`ripcrypt`, `firstLoadFinished`)) { // Update the turnMarker to be the RipCrypt defaults diff --git a/module/settings/metaSettings.mjs b/module/settings/metaSettings.mjs index 7694088..77bc3dc 100644 --- a/module/settings/metaSettings.mjs +++ b/module/settings/metaSettings.mjs @@ -5,20 +5,25 @@ export function registerMetaSettings() { config: false, requiresReload: false, onChange: () => { - ui.crypt.render({ parts: [ `delveConditions` ]}); + ui.delveDice.render({ parts: [`difficulty`] }); }, }); + game.settings.register(`ripcrypt`, `sandsOfFate`, { + scope: `world`, + type: Number, + initial: 8, + config: false, + requiresReload: false, + onChange: async () => {}, + }); + game.settings.register(`ripcrypt`, `currentFate`, { scope: `world`, type: String, config: false, requiresReload: false, - onChange: async () => { - await ui.crypt.render({ parts: [ `fate` ] }); - await game.combat.setupTurns(); - await ui.combat.render({ parts: [ `tracker` ] }); - }, + onChange: async () => {}, }); game.settings.register(`ripcrypt`, `whoFirst`, { diff --git a/templates/Apps/DelveDiceHUD/difficulty.hbs b/templates/Apps/DelveDiceHUD/difficulty.hbs new file mode 100644 index 0000000..29b591f --- /dev/null +++ b/templates/Apps/DelveDiceHUD/difficulty.hbs @@ -0,0 +1,3 @@ +
+ Difficulty: {{dc}} +
diff --git a/templates/Apps/DelveDiceHUD/fateCompass.hbs b/templates/Apps/DelveDiceHUD/fateCompass.hbs new file mode 100644 index 0000000..3ad015d --- /dev/null +++ b/templates/Apps/DelveDiceHUD/fateCompass.hbs @@ -0,0 +1,3 @@ +
+ North +
\ No newline at end of file diff --git a/templates/Apps/DelveDiceHUD/style.css b/templates/Apps/DelveDiceHUD/style.css new file mode 100644 index 0000000..a7ab971 --- /dev/null +++ b/templates/Apps/DelveDiceHUD/style.css @@ -0,0 +1,18 @@ +#ripcrypt-delve-dice { + display: grid; + grid-template-columns: max-content 1fr 1fr 1fr max-content; + gap: 8px; + padding: 4px 1.5rem; + background: var(--DelveDice-background); + align-items: center; + justify-items: center; + pointer-events: all; + + border-radius: 0 0 999px 999px; + + button { + &:hover { + cursor: pointer; + } + } +} diff --git a/templates/Apps/DelveDiceHUD/tour/current.hbs b/templates/Apps/DelveDiceHUD/tour/current.hbs new file mode 100644 index 0000000..12c795e --- /dev/null +++ b/templates/Apps/DelveDiceHUD/tour/current.hbs @@ -0,0 +1,3 @@ +
+ The Hourglass +
diff --git a/templates/Apps/DelveDiceHUD/tour/next.hbs b/templates/Apps/DelveDiceHUD/tour/next.hbs new file mode 100644 index 0000000..1e85007 --- /dev/null +++ b/templates/Apps/DelveDiceHUD/tour/next.hbs @@ -0,0 +1,7 @@ + diff --git a/templates/Apps/DelveDiceHUD/tour/previous.hbs b/templates/Apps/DelveDiceHUD/tour/previous.hbs new file mode 100644 index 0000000..d498a99 --- /dev/null +++ b/templates/Apps/DelveDiceHUD/tour/previous.hbs @@ -0,0 +1,7 @@ + diff --git a/templates/Apps/apps.css b/templates/Apps/apps.css index bcf722e..9f6a4f6 100644 --- a/templates/Apps/apps.css +++ b/templates/Apps/apps.css @@ -1,6 +1,7 @@ @import url("./AllItemSheetV1/style.css"); @import url("./CombinedHeroSheet/style.css"); @import url("./CryptApp/style.css"); +@import url("./DelveDiceHUD/style.css"); @import url("./DicePool/style.css"); @import url("./HeroSummaryCardV1/style.css"); @import url("./HeroSkillsCardV1/style.css"); diff --git a/templates/css/elements/button.css b/templates/css/elements/button.css index 2d0b6bb..c9e0bab 100644 --- a/templates/css/elements/button.css +++ b/templates/css/elements/button.css @@ -1,4 +1,4 @@ -.ripcrypt > .window-content button { +.ripcrypt button { all: revert; outline: none; border: none; diff --git a/templates/css/themes/dark.css b/templates/css/themes/dark.css index b33c069..aa4a8a4 100644 --- a/templates/css/themes/dark.css +++ b/templates/css/themes/dark.css @@ -41,4 +41,8 @@ --pill-input-background: var(--accent-2); --pill-input-disabled-text: white; --pill-input-disabled-background: black; + + /* Custom HUD Components */ + --DelveDice-background: var(--accent-1); + --DelveDice-text: white; } From 77979f55506071e1353ddc3092153b1476911c7f Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 28 Feb 2025 16:17:16 -0700 Subject: [PATCH 02/16] Add arrow icons for the HUD controls --- assets/_credit.txt | 5 +++++ assets/icons/arrow-compass.svg | 3 +++ assets/icons/arrow-left.svg | 3 +++ assets/icons/arrow-right.svg | 3 +++ 4 files changed, 14 insertions(+) create mode 100644 assets/icons/arrow-compass.svg create mode 100644 assets/icons/arrow-left.svg create mode 100644 assets/icons/arrow-right.svg diff --git a/assets/_credit.txt b/assets/_credit.txt index 39124c6..43cf3fb 100644 --- a/assets/_credit.txt +++ b/assets/_credit.txt @@ -11,6 +11,11 @@ ARISO: Abdulloh Fauzan: - icons/info-circle.svg (https://thenounproject.com/icon/information-4176576/) : Rights Purchased +QOLBIN SALIIM: + - icons/arrow-left.svg (https://thenounproject.com/icon/arrow-1933583/) : Rights Purchased + - icons/arrow-right.svg (https://thenounproject.com/icon/arrow-1933581/) : Rights Purchased + - icons/arrow-compass.svg (https://thenounproject.com/icon/arrow-2052607/) : Rights Purchased + Soetarman Atmodjo: - icons/roll.svg (https://thenounproject.com/icon/dice-5195278/) : Rights Purchased diff --git a/assets/icons/arrow-compass.svg b/assets/icons/arrow-compass.svg new file mode 100644 index 0000000..b1e8a40 --- /dev/null +++ b/assets/icons/arrow-compass.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/arrow-left.svg b/assets/icons/arrow-left.svg new file mode 100644 index 0000000..e1a347e --- /dev/null +++ b/assets/icons/arrow-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/arrow-right.svg b/assets/icons/arrow-right.svg new file mode 100644 index 0000000..a477835 --- /dev/null +++ b/assets/icons/arrow-right.svg @@ -0,0 +1,3 @@ + + + From 3d6710dd189e657251c254b8907b200aaf5d99e5 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 28 Feb 2025 16:18:31 -0700 Subject: [PATCH 03/16] Get the fate compass looking good --- templates/Apps/DelveDiceHUD/fateCompass.hbs | 17 ++++++++-- templates/Apps/DelveDiceHUD/style.css | 36 +++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/templates/Apps/DelveDiceHUD/fateCompass.hbs b/templates/Apps/DelveDiceHUD/fateCompass.hbs index 3ad015d..19ea83c 100644 --- a/templates/Apps/DelveDiceHUD/fateCompass.hbs +++ b/templates/Apps/DelveDiceHUD/fateCompass.hbs @@ -1,3 +1,16 @@
- North -
\ No newline at end of file +
+
+ N + W + + E + S +
+
+ diff --git a/templates/Apps/DelveDiceHUD/style.css b/templates/Apps/DelveDiceHUD/style.css index a7ab971..0e4a226 100644 --- a/templates/Apps/DelveDiceHUD/style.css +++ b/templates/Apps/DelveDiceHUD/style.css @@ -15,4 +15,40 @@ cursor: pointer; } } + + #fate-compass { + width: 100%; + height: 100%; + overflow: visible; + position: relative; + + .compass-container { + position: absolute; + background: var(--DelveDice-background); + border-radius: 0 0 999px 999px; + padding: 4px; + width: 100%; + } + + .compass { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-rows: repeat(3, minmax(0, 1fr)); + grid-template-areas: + ". N ." + "W A E" + ". S ."; + align-items: center; + justify-items: center; + background: var(--accent-2); + border-radius: 999px; + aspect-ratio: 1; + } + + .compass-pointer { + grid-area: A; + transition: 500ms transform; + transform: rotate(-90deg); /* North by default */ + } + } } From 6ae412c787a51cb0e72a2b44c354d1769d88bca3 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 28 Feb 2025 16:47:41 -0700 Subject: [PATCH 04/16] Get hourglass improved --- templates/Apps/DelveDiceHUD/style.css | 27 +++++++++++++++++++- templates/Apps/DelveDiceHUD/tour/current.hbs | 13 ++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/templates/Apps/DelveDiceHUD/style.css b/templates/Apps/DelveDiceHUD/style.css index 0e4a226..4b19a11 100644 --- a/templates/Apps/DelveDiceHUD/style.css +++ b/templates/Apps/DelveDiceHUD/style.css @@ -1,6 +1,6 @@ #ripcrypt-delve-dice { display: grid; - grid-template-columns: max-content 1fr 1fr 1fr max-content; + grid-template-columns: max-content 1fr 0.6fr 1fr max-content; gap: 8px; padding: 4px 1.5rem; background: var(--DelveDice-background); @@ -51,4 +51,29 @@ transform: rotate(-90deg); /* North by default */ } } + + #the-hourglass { + display: flex; + flex-direction: row; + position: relative; + + .hourglass-container { + position: absolute; + width: 34px; + display: flex; + flex-direction: column; + justify-content: center; + padding: 4px 0; + background: var(--accent-1); + border-radius: 8px; + + rc-svg { + inset: 4px; + } + } + + .hud-text { + margin-left: 38px; + } + } } diff --git a/templates/Apps/DelveDiceHUD/tour/current.hbs b/templates/Apps/DelveDiceHUD/tour/current.hbs index 12c795e..991b6f4 100644 --- a/templates/Apps/DelveDiceHUD/tour/current.hbs +++ b/templates/Apps/DelveDiceHUD/tour/current.hbs @@ -1,3 +1,12 @@ -
- The Hourglass +
+
+ +
+
+ The Hourglass +
From c9ed4142e6994089f582e2176549c349f3cb73ad Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 1 Mar 2025 00:34:25 -0700 Subject: [PATCH 05/16] Finalize the general layout of the HUD --- templates/Apps/DelveDiceHUD/difficulty.hbs | 15 ++++++++-- templates/Apps/DelveDiceHUD/style.css | 29 +++++++++++++------- templates/Apps/DelveDiceHUD/tour/current.hbs | 11 +++++--- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/templates/Apps/DelveDiceHUD/difficulty.hbs b/templates/Apps/DelveDiceHUD/difficulty.hbs index 29b591f..d65b06b 100644 --- a/templates/Apps/DelveDiceHUD/difficulty.hbs +++ b/templates/Apps/DelveDiceHUD/difficulty.hbs @@ -1,3 +1,14 @@ -
- Difficulty: {{dc}} +
+
+ 8 + +
diff --git a/templates/Apps/DelveDiceHUD/style.css b/templates/Apps/DelveDiceHUD/style.css index 4b19a11..55128c2 100644 --- a/templates/Apps/DelveDiceHUD/style.css +++ b/templates/Apps/DelveDiceHUD/style.css @@ -1,6 +1,6 @@ #ripcrypt-delve-dice { display: grid; - grid-template-columns: max-content 1fr 0.6fr 1fr max-content; + grid-template-columns: max-content 2rem 90px 2rem max-content; gap: 8px; padding: 4px 1.5rem; background: var(--DelveDice-background); @@ -52,28 +52,37 @@ } } - #the-hourglass { + #the-hourglass, + #delve-difficulty { + width: 100%; + height: 100%; display: flex; flex-direction: row; position: relative; - .hourglass-container { + .icon-container { position: absolute; width: 34px; - display: flex; - flex-direction: column; - justify-content: center; + display: grid; padding: 4px 0; background: var(--accent-1); border-radius: 8px; + > * { + grid-row: 1 / -1; + grid-column: 1 / -1; + } + + span { + font-size: 1.25rem; + z-index: 2; + align-self: center; + justify-self: center; + } + rc-svg { inset: 4px; } } - - .hud-text { - margin-left: 38px; - } } } diff --git a/templates/Apps/DelveDiceHUD/tour/current.hbs b/templates/Apps/DelveDiceHUD/tour/current.hbs index 991b6f4..9e3d423 100644 --- a/templates/Apps/DelveDiceHUD/tour/current.hbs +++ b/templates/Apps/DelveDiceHUD/tour/current.hbs @@ -1,12 +1,15 @@
-
+
+ + 8 +
-
- The Hourglass -
From 507913139f915af34b0421f5575676b3e2b7601c Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 1 Mar 2025 00:44:49 -0700 Subject: [PATCH 06/16] Begin working on HUD interactivity --- module/Apps/DelveDiceHUD.mjs | 14 +++++++++- templates/Apps/DelveDiceHUD/fateCompass.hbs | 8 +++--- templates/Apps/DelveDiceHUD/tour/next.hbs | 27 ++++++++++++++----- templates/Apps/DelveDiceHUD/tour/previous.hbs | 27 ++++++++++++++----- templates/css/elements/button.css | 8 ++++++ 5 files changed, 65 insertions(+), 19 deletions(-) diff --git a/module/Apps/DelveDiceHUD.mjs b/module/Apps/DelveDiceHUD.mjs index 0122aea..a487480 100644 --- a/module/Apps/DelveDiceHUD.mjs +++ b/module/Apps/DelveDiceHUD.mjs @@ -83,6 +83,10 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { await this._prepareDifficultyContext(ctx); break; }; + case `fateCompass`: { + await this._prepareFateCompassContext(ctx); + break; + }; }; Logger.log(`${partId} Context`, ctx); @@ -95,12 +99,20 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { async _prepareDifficultyContext(ctx) { ctx.dc = game.settings.get(`ripcrypt`, `dc`); + }; + + async _prepareFateCompassContext(ctx) { + ctx.direction = game.settings.get(`ripcrypt`, `currentFate`); } // #endregion // #region Actions static async #tourDelta() { - ui.notifications.info(`Button Clicked!`, { console: false }); + ui.notifications.info(`Delve Tour Changed`, { console: false }); + }; + + static async #setFate() { + ui.notifications.info(`Fate Set!`, { console: false }); }; // #endregion }; diff --git a/templates/Apps/DelveDiceHUD/fateCompass.hbs b/templates/Apps/DelveDiceHUD/fateCompass.hbs index 19ea83c..75c0ed5 100644 --- a/templates/Apps/DelveDiceHUD/fateCompass.hbs +++ b/templates/Apps/DelveDiceHUD/fateCompass.hbs @@ -1,16 +1,16 @@
- N - W + + - E - S + +
diff --git a/templates/Apps/DelveDiceHUD/tour/next.hbs b/templates/Apps/DelveDiceHUD/tour/next.hbs index 1e85007..28573c3 100644 --- a/templates/Apps/DelveDiceHUD/tour/next.hbs +++ b/templates/Apps/DelveDiceHUD/tour/next.hbs @@ -1,7 +1,20 @@ - +
+ {{!-- This is here to prevent height collapsing --}} + ​ + + {{#if meta.editable}} + + {{/if}} +
+ diff --git a/templates/Apps/DelveDiceHUD/tour/previous.hbs b/templates/Apps/DelveDiceHUD/tour/previous.hbs index d498a99..ac420ef 100644 --- a/templates/Apps/DelveDiceHUD/tour/previous.hbs +++ b/templates/Apps/DelveDiceHUD/tour/previous.hbs @@ -1,7 +1,20 @@ - +
+ {{!-- This is here to prevent height collapsing --}} + ​ + + {{#if meta.editable}} + + {{/if}} +
+ diff --git a/templates/css/elements/button.css b/templates/css/elements/button.css index c9e0bab..0493fe8 100644 --- a/templates/css/elements/button.css +++ b/templates/css/elements/button.css @@ -3,6 +3,8 @@ outline: none; border: none; padding: 2px 4px; + font-family: inherit; + font-size: inherit; background: var(--button-background); color: var(--button-text); @@ -23,4 +25,10 @@ width: 20px; height: 20px; } + + &.transparent { + background: inherit; + color: inherit; + padding: 0; + } } From 7c8d6a7208ef0890725d84ecdb07054cbfe612c4 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 1 Mar 2025 19:20:21 -0700 Subject: [PATCH 07/16] Make the distanceBetweenFates more situation-complete --- module/utils/distanceBetweenFates.mjs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/module/utils/distanceBetweenFates.mjs b/module/utils/distanceBetweenFates.mjs index 4eaaddd..8c57240 100644 --- a/module/utils/distanceBetweenFates.mjs +++ b/module/utils/distanceBetweenFates.mjs @@ -14,12 +14,18 @@ export function distanceBetweenFates(start, end) { return undefined; }; - if (isOppositeFates(start, end)) { + if (start === end) { + return 0; + }; + + if (isOppositeFates(start, end) || isOppositeFates(end, start)) { return 2; }; let isForward = start === FatePath.SOUTH && end === FatePath.WEST; isForward ||= start === FatePath.NORTH && end === FatePath.EAST; + isForward ||= start === FatePath.WEST && end === FatePath.NORTH; + isForward ||= start === FatePath.EAST && end === FatePath.SOUTH; if (isForward) { return 1; }; From 76399621300f4dd40325818b1da78f239034fecb Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 1 Mar 2025 19:22:02 -0700 Subject: [PATCH 08/16] Add animation and editing support for the fate compass --- module/Apps/DelveDiceHUD.mjs | 82 ++++++++++++++++----- templates/Apps/DelveDiceHUD/fateCompass.hbs | 56 +++++++++++++- 2 files changed, 115 insertions(+), 23 deletions(-) diff --git a/module/Apps/DelveDiceHUD.mjs b/module/Apps/DelveDiceHUD.mjs index a487480..4e0d358 100644 --- a/module/Apps/DelveDiceHUD.mjs +++ b/module/Apps/DelveDiceHUD.mjs @@ -1,7 +1,17 @@ +import { distanceBetweenFates } from "../utils/distanceBetweenFates.mjs"; import { filePath } from "../consts.mjs"; +import { gameTerms } from "../gameTerms.mjs"; import { Logger } from "../utils/Logger.mjs"; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; +const { FatePath } = gameTerms; + +const CompassRotations = { + [FatePath.NORTH]: -90, + [FatePath.EAST]: 0, + [FatePath.SOUTH]: 90, + [FatePath.WEST]: 180, +}; const conditions = [ { label: `RipCrypt.common.difficulties.easy`, value: 4 }, @@ -17,7 +27,7 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { tag: `aside`, classes: [ `ripcrypt`, - `ripcrypt--DelveDiceHUD` + `ripcrypt--DelveDiceHUD`, ], window: { frame: false, @@ -25,6 +35,7 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { }, actions: { tourDelta: this.#tourDelta, + setFate: this.#setFate, }, }; @@ -38,7 +49,7 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { fateCompass: { template: filePath(`templates/Apps/DelveDiceHUD/fateCompass.hbs`), }, - currentTour: { + sandsOfFate: { template: filePath(`templates/Apps/DelveDiceHUD/tour/current.hbs`), }, nextTour: { @@ -47,6 +58,24 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { }; // #endregion + // #region Instance Data + /** + * The current number of degrees the compass pointer should be rotated, this + * is not stored in the DB since we only care about the initial rotation on + * reload, which is derived from the current fate. + * @type {Number} + */ + _rotation; + + constructor(...args) { + super(...args); + this._sandsOfFate = game.settings.get(`ripcrypt`, `sandsOfFate`); + this._currentFate = game.settings.get(`ripcrypt`, `currentFate`); + this._rotation = CompassRotations[this._currentFate]; + this._difficulty = game.settings.get(`ripcrypt`, `dc`); + }; + // #endregion + // #region Lifecycle /** * Injects the element into the Foundry UI in the top middle @@ -75,16 +104,17 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { ctx.meta.editable = game.user.isGM; switch (partId) { - case `currentTour`: { - await this._prepareTourContext(ctx); + case `sandsOfFate`: { + ctx.sandsOfFate = this._sandsOfFate; break; }; case `difficulty`: { - await this._prepareDifficultyContext(ctx); + ctx.dc = this._difficulty; break; }; case `fateCompass`: { - await this._prepareFateCompassContext(ctx); + ctx.fate = this._currentFate; + ctx.rotation = `${this._rotation}deg`; break; }; }; @@ -93,26 +123,40 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { return ctx; }; - async _prepareTourContext(ctx) { - ctx.tour = game.settings.get(`ripcrypt`, `sandsOfFate`); + async animate({ parts = [] } = {}) { + if (parts.includes(`fateCompass`)) { + this.#animateCompassTo(); + }; + + if (parts.includes(`sandsOfFate`)) {}; }; - async _prepareDifficultyContext(ctx) { - ctx.dc = game.settings.get(`ripcrypt`, `dc`); - }; + #animateCompassTo(newFate) { + /** @type {HTMLElement|undefined} */ + const pointer = this.element.querySelector(`.compass-pointer`); + if (!pointer) { return }; - async _prepareFateCompassContext(ctx) { - ctx.direction = 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)`); + }; // #endregion // #region Actions - static async #tourDelta() { - ui.notifications.info(`Delve Tour Changed`, { console: false }); - }; + static async #tourDelta() {}; - static async #setFate() { - ui.notifications.info(`Fate Set!`, { console: false }); + /** @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 }; diff --git a/templates/Apps/DelveDiceHUD/fateCompass.hbs b/templates/Apps/DelveDiceHUD/fateCompass.hbs index 75c0ed5..e4b74c0 100644 --- a/templates/Apps/DelveDiceHUD/fateCompass.hbs +++ b/templates/Apps/DelveDiceHUD/fateCompass.hbs @@ -1,16 +1,64 @@
- - - - + {{#if meta.editable}} + + + + + {{else}} + N + W + E + S + {{/if}}
From 110823a26b876252302edf62d6aa9ef79aff6522 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 1 Mar 2025 23:40:39 -0700 Subject: [PATCH 09/16] 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}}