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 @@ + + + diff --git a/langs/en-ca.json b/langs/en-ca.json index 9d24b68..adda6f2 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -67,8 +67,10 @@ "easy": "Easy", "normal": "Normal", "tough": "Tough", - "hard": "Hard" + "hard": "Hard", + "random": "Random Condition" }, + "difficulty": "Difficulty", "drag": "Drag", "edit": "Edit", "edge": "Edge", @@ -80,11 +82,23 @@ "guts": "Guts", "location": "Location", "move": "Move", - "path": { - "North": "North", - "East": "East", - "South": "South", - "West": "West" + "ordinals": { + "North": { + "full": "North", + "abbv": "N" + }, + "East": { + "full": "East", + "abbv": "E" + }, + "South": { + "full": "South", + "abbv": "S" + }, + "West": { + "full": "West", + "abbv": "W" + } }, "protection": "Protection", "quantity": "Quantity", @@ -121,6 +135,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": { @@ -153,10 +181,17 @@ }, "warn": { "cannot-go-negative": "\"{name}\" is unable to be a negative number." + }, + "info": { + "cryptic-event-alert": "A Cryptic Event Has Occured!" } }, "tooltips": { - "shield-bonus": "Shield Bonus: {value}" + "shield-bonus": "Shield Bonus: {value}", + "set-fate-to": "Set Fate to {ordinal}", + "current-tour": "Current Delve Tour", + "next-tour": "Next Delve Tour", + "prev-tour": "Previous Delve Tour" }, "region": { "difficultyDelta": { diff --git a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs index 0dc19bd..3b7ad93 100644 --- a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs @@ -117,7 +117,7 @@ export class HeroSummaryCardV1 extends GenericAppMixin(HandlebarsApplicationMixi ctx.fate.options = [ { label: `RipCrypt.common.empty`, v: `` }, ...Object.values(gameTerms.FatePath) - .map(v => ({ label: `RipCrypt.common.path.${v}`, value: v })), + .map(v => ({ label: `RipCrypt.common.ordinals.${v}.full`, value: v })), ]; return ctx; }; diff --git a/module/Apps/DelveDiceHUD.mjs b/module/Apps/DelveDiceHUD.mjs new file mode 100644 index 0000000..a525c3a --- /dev/null +++ b/module/Apps/DelveDiceHUD.mjs @@ -0,0 +1,251 @@ +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; +const { ContextMenu } = foundry.applications.ui; +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 }, + { 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`, + `hud`, + ], + window: { + frame: false, + positioned: false, + }, + actions: { + tourDelta: this.#tourDelta, + setFate: this.#setFate, + }, + }; + + 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`), + }, + sandsOfFate: { + template: filePath(`templates/Apps/DelveDiceHUD/tour/current.hbs`), + }, + nextTour: { + template: filePath(`templates/Apps/DelveDiceHUD/tour/next.hbs`), + }, + }; + // #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 + */ + _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 }; + + new ContextMenu( + this.element, + `#delve-difficulty`, + [ + ...conditions.map(condition => ({ + name: localizer(condition.label), + callback: DelveDiceHUD.#setDifficulty.bind(this, condition.value), + })), + { + name: localizer(`RipCrypt.common.difficulties.random`), + callback: () => { + const condition = conditions[Math.floor(Math.random() * conditions.length)]; + DelveDiceHUD.#setDifficulty.bind(this)(condition.value); + }, + }, + ], + { jQuery: false, fixed: true }, + ); + }; + + async _preparePartContext(partId, ctx, opts) { + ctx = await super._preparePartContext(partId, ctx, opts); + ctx.meta ??= {}; + + ctx.meta.editable = game.user.isGM; + + switch (partId) { + case `sandsOfFate`: { + ctx.sandsOfFate = this._sandsOfFate; + break; + }; + case `difficulty`: { + ctx.dc = this._difficulty; + break; + }; + case `fateCompass`: { + ctx.fate = this._currentFate; + ctx.rotation = `${this._rotation}deg`; + break; + }; + }; + + Logger.log(`${partId} Context`, ctx); + return ctx; + }; + + async animate({ parts = [] } = {}) { + if (parts.includes(`fateCompass`)) { + this.#animateCompassTo(); + }; + + 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); + 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 + /** @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); + game.settings.set(`ripcrypt`, `currentFate`, fate); + }; + + /** @this {DelveDiceHUD} */ + static async #setDifficulty(value) { + this._difficulty = value; + game.settings.set(`ripcrypt`, `dc`, value); + }; + // #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/Apps/DelveTourApp.mjs b/module/Apps/DelveTourApp.mjs deleted file mode 100644 index 8c213ed..0000000 --- a/module/Apps/DelveTourApp.mjs +++ /dev/null @@ -1,104 +0,0 @@ -import { filePath } from "../consts.mjs"; -import { GenericAppMixin } from "./GenericApp.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 DelveTourApp extends GenericAppMixin(HandlebarsApplicationMixin(ApplicationV2)) { - // #region Options - static DEFAULT_OPTIONS = { - classes: [ - `ripcrypt--CryptApp`, - ], - window: { - title: `Delve Tour`, - frame: true, - positioned: true, - resizable: false, - minimizable: false, - }, - position: { - width: `auto`, - }, - actions: { - randomCondition: this.#randomCondition, - }, - }; - - static PARTS = { - turnCount: { - template: filePath(`templates/Apps/CryptApp/turnCount.hbs`), - }, - delveConditions: { - template: filePath(`templates/Apps/CryptApp/delveConditions.hbs`), - }, - fate: { - template: filePath(`templates/Apps/CryptApp/fate.hbs`), - }, - }; - // #endregion - - // #region Lifecycle - async _renderFrame(options) { - const frame = await super._renderFrame(options); - this.window.close.remove(); // Prevent closing - return frame; - }; - - async _onRender(context, options) { - await super._onRender(context, options); - - // Shortcut because users can't edit - if (!game.user.isGM) { return }; - - // Add event listener for the dropdown - if (options.parts.includes(`delveConditions`)) { - const select = this.element.querySelector(`#${this.id}-difficulty`); - select.addEventListener(`change`, async (ev) => { - const newDifficulty = parseInt(ev.target.value); - if (!Number.isNaN(newDifficulty)) { - await game.settings.set(`ripcrypt`, `dc`, newDifficulty); - }; - this.render({ parts: [`delveConditions`] }); - }); - }; - }; - - async _preparePartContext(partId, ctx, opts) { - ctx = await super._preparePartContext(partId, ctx, opts); - - ctx.meta.editable = game.user.isGM; - - switch (partId) { - case `delveConditions`: { - ctx = this._prepareDifficulty(ctx); - break; - }; - }; - - Logger.log(`${partId} Context`, ctx); - return ctx; - }; - - _prepareDifficulty(ctx) { - ctx.options = conditions; - ctx.difficulty = game.settings.get(`ripcrypt`, `dc`); - return ctx; - }; - // #endregion - - // #region Actions - static async #randomCondition() { - const dc = conditions[Math.floor(Math.random() * conditions.length)]; - await game.settings.set(`ripcrypt`, `dc`, dc.value); - await this.render({ parts: [`delveConditions`] }); - }; - // #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/documents/combatant.mjs b/module/documents/combatant.mjs index 6de765b..0594db5 100644 --- a/module/documents/combatant.mjs +++ b/module/documents/combatant.mjs @@ -1,4 +1,4 @@ -import { distanceBetweenFates } from "../utils/distanceBetweenFates.mjs"; +import { distanceBetweenFates } from "../utils/fates.mjs"; export class RipCryptCombatant extends Combatant { diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 70f81ff..b7dda31 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -1,7 +1,7 @@ // Applications import { AllItemSheetV1 } from "../Apps/ItemSheets/AllItemSheetV1.mjs"; import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs"; -import { DelveTourApp } from "../Apps/DelveTourApp.mjs"; +import { DelveDiceHUD } from "../Apps/DelveDiceHUD.mjs"; import { HeroSkillsCardV1 } from "../Apps/ActorSheets/HeroSkillsCardV1.mjs"; import { HeroSummaryCardV1 } from "../Apps/ActorSheets/HeroSummaryCardV1.mjs"; import { RipCryptCombatTracker } from "../Apps/sidebar/CombatTracker.mjs"; @@ -39,7 +39,7 @@ Hooks.once(`init`, () => { Logger.log(`Initializing`); CONFIG.Combat.initiative.decimals = 2; - CONFIG.ui.crypt = DelveTourApp; + CONFIG.ui.delveDice = DelveDiceHUD; // #region Settings registerMetaSettings(); diff --git a/module/hooks/ready.mjs b/module/hooks/ready.mjs index c71bc25..63223d7 100644 --- a/module/hooks/ready.mjs +++ b/module/hooks/ready.mjs @@ -19,9 +19,7 @@ Hooks.once(`ready`, () => { if (game.paused) { game.togglePause() }; }; - if (game.settings.get(`ripcrypt`, `showDelveTour`)) { - ui.crypt.render({ force: true }); - }; + ui.delveDice.render({ force: true }); // MARK: 1-time updates if (!game.settings.get(`ripcrypt`, `firstLoadFinished`)) { diff --git a/module/settings/metaSettings.mjs b/module/settings/metaSettings.mjs index 7694088..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`, @@ -5,19 +10,32 @@ 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 () => { + 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 () => { - await ui.crypt.render({ parts: [ `fate` ] }); - await game.combat.setupTurns(); - await ui.combat.render({ parts: [ `tracker` ] }); + ui.delveDice.animate({ parts: [`fateCompass`] }); }, }); diff --git a/module/settings/worldSettings.mjs b/module/settings/worldSettings.mjs index d7c426a..ca095ae 100644 --- a/module/settings/worldSettings.mjs +++ b/module/settings/worldSettings.mjs @@ -1,10 +1,42 @@ +const { NumberField, StringField } = foundry.data.fields; + export function registerWorldSettings() { - game.settings.register(`ripcrypt`, `showDelveTour`, { - name: `Delve Tour Popup`, + game.settings.register(`ripcrypt`, `sandsOfFateInitial`, { + name: `RipCrypt.setting.sandsOfFateInitial.name`, + hint: `RipCrypt.setting.sandsOfFateInitial.hint`, scope: `world`, - type: Boolean, config: true, - default: 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 50% rename from module/utils/distanceBetweenFates.mjs rename to module/utils/fates.mjs index 4eaaddd..d1fb6dd 100644 --- a/module/utils/distanceBetweenFates.mjs +++ b/module/utils/fates.mjs @@ -14,14 +14,38 @@ 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; }; 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/CryptApp/delveConditions.hbs b/templates/Apps/CryptApp/delveConditions.hbs deleted file mode 100644 index 4e0f00d..0000000 --- a/templates/Apps/CryptApp/delveConditions.hbs +++ /dev/null @@ -1,29 +0,0 @@ -
- -
- Difficulty -
-
- {{ difficulty }} - {{#if meta.editable}} -
- - -
- {{/if}} -
-
-
diff --git a/templates/Apps/CryptApp/fate.hbs b/templates/Apps/CryptApp/fate.hbs deleted file mode 100644 index c1a5ba6..0000000 --- a/templates/Apps/CryptApp/fate.hbs +++ /dev/null @@ -1,12 +0,0 @@ -
- -
- Fate -
-
- N -
-
-
diff --git a/templates/Apps/CryptApp/style.css b/templates/Apps/CryptApp/style.css deleted file mode 100644 index 9d6c772..0000000 --- a/templates/Apps/CryptApp/style.css +++ /dev/null @@ -1,18 +0,0 @@ -.ripcrypt.ripcrypt--CryptApp { - max-width: initial; - min-width: initial; - - > .window-header .window-title { - text-align: center; - } - - > .window-content { - background: var(--base-background); - padding: 4px; - }; - - .row { - display: flex; - flex-direction: row; - } -} diff --git a/templates/Apps/CryptApp/turnCount.hbs b/templates/Apps/CryptApp/turnCount.hbs deleted file mode 100644 index 0653413..0000000 --- a/templates/Apps/CryptApp/turnCount.hbs +++ /dev/null @@ -1,12 +0,0 @@ -
- -
- Turn -
-
- 1 -
-
-
diff --git a/templates/Apps/DelveDiceHUD/difficulty.hbs b/templates/Apps/DelveDiceHUD/difficulty.hbs new file mode 100644 index 0000000..bf5704f --- /dev/null +++ b/templates/Apps/DelveDiceHUD/difficulty.hbs @@ -0,0 +1,14 @@ +
+
+ {{dc}} + +
+
diff --git a/templates/Apps/DelveDiceHUD/fateCompass.hbs b/templates/Apps/DelveDiceHUD/fateCompass.hbs new file mode 100644 index 0000000..983e6bf --- /dev/null +++ b/templates/Apps/DelveDiceHUD/fateCompass.hbs @@ -0,0 +1,64 @@ +
+
+
+ + {{#if meta.editable}} + + + + + {{else}} + {{rc-i18n "RipCrypt.common.ordinals.North.abbv"}} + {{rc-i18n "RipCrypt.common.ordinals.West.abbv"}} + {{rc-i18n "RipCrypt.common.ordinals.East.abbv"}} + {{rc-i18n "RipCrypt.common.ordinals.South.abbv"}} + {{/if}} +
+
+
diff --git a/templates/Apps/DelveDiceHUD/style.css b/templates/Apps/DelveDiceHUD/style.css new file mode 100644 index 0000000..55128c2 --- /dev/null +++ b/templates/Apps/DelveDiceHUD/style.css @@ -0,0 +1,88 @@ +#ripcrypt-delve-dice { + display: grid; + grid-template-columns: max-content 2rem 90px 2rem 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; + } + } + + #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 */ + } + } + + #the-hourglass, + #delve-difficulty { + width: 100%; + height: 100%; + display: flex; + flex-direction: row; + position: relative; + + .icon-container { + position: absolute; + width: 34px; + 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; + } + } + } +} diff --git a/templates/Apps/DelveDiceHUD/tour/current.hbs b/templates/Apps/DelveDiceHUD/tour/current.hbs new file mode 100644 index 0000000..8c802ce --- /dev/null +++ b/templates/Apps/DelveDiceHUD/tour/current.hbs @@ -0,0 +1,16 @@ +
+
+ + {{sandsOfFate}} + + +
+
diff --git a/templates/Apps/DelveDiceHUD/tour/next.hbs b/templates/Apps/DelveDiceHUD/tour/next.hbs new file mode 100644 index 0000000..d81c879 --- /dev/null +++ b/templates/Apps/DelveDiceHUD/tour/next.hbs @@ -0,0 +1,22 @@ +
+ {{!-- 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 new file mode 100644 index 0000000..1120e8f --- /dev/null +++ b/templates/Apps/DelveDiceHUD/tour/previous.hbs @@ -0,0 +1,23 @@ +
+ {{!-- This is here to prevent height collapsing --}} + ​ + + {{#if meta.editable}} + + {{/if}} +
+ 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/common.css b/templates/css/common.css index 5b99ab4..f11d646 100644 --- a/templates/css/common.css +++ b/templates/css/common.css @@ -30,4 +30,4 @@ label, input, select { cursor: pointer; } -} \ No newline at end of file +} diff --git a/templates/css/elements/button.css b/templates/css/elements/button.css index 2d0b6bb..6eee9d8 100644 --- a/templates/css/elements/button.css +++ b/templates/css/elements/button.css @@ -1,8 +1,11 @@ +.ripcrypt.hud button, .ripcrypt > .window-content button { all: revert; outline: none; border: none; padding: 2px 4px; + font-family: inherit; + font-size: inherit; background: var(--button-background); color: var(--button-text); @@ -23,4 +26,10 @@ width: 20px; height: 20px; } + + &.transparent { + background: inherit; + color: inherit; + padding: 0; + } } diff --git a/templates/css/elements/input.css b/templates/css/elements/input.css index 34604d4..3121505 100644 --- a/templates/css/elements/input.css +++ b/templates/css/elements/input.css @@ -1,3 +1,4 @@ +.ripcrypt.hud, .ripcrypt > .window-content { input, .input { all: revert; diff --git a/templates/css/elements/lists.css b/templates/css/elements/lists.css index 2173bfd..2940b37 100644 --- a/templates/css/elements/lists.css +++ b/templates/css/elements/lists.css @@ -1,3 +1,4 @@ +.ripcrypt.hud, .ripcrypt > .window-content { ol { list-style-type: none; diff --git a/templates/css/elements/p.css b/templates/css/elements/p.css index 6a9151e..fcf243a 100644 --- a/templates/css/elements/p.css +++ b/templates/css/elements/p.css @@ -1,3 +1,4 @@ +.ripcrypt.hud p, .ripcrypt > .window-content p { &.warning { padding: 0.75rem; diff --git a/templates/css/elements/pill-bar.css b/templates/css/elements/pill-bar.css index e80537f..425499c 100644 --- a/templates/css/elements/pill-bar.css +++ b/templates/css/elements/pill-bar.css @@ -1,3 +1,4 @@ +.ripcrypt.hud .pill-bar, .ripcrypt > .window-content .pill-bar { display: flex; flex-direction: row; diff --git a/templates/css/elements/select.css b/templates/css/elements/select.css index b822816..0940786 100644 --- a/templates/css/elements/select.css +++ b/templates/css/elements/select.css @@ -1,3 +1,4 @@ +.ripcrypt.hud select, .ripcrypt > .window-content select { all: revert; appearance: auto; diff --git a/templates/css/elements/span.css b/templates/css/elements/span.css index 25c1ca9..b4a2cef 100644 --- a/templates/css/elements/span.css +++ b/templates/css/elements/span.css @@ -1,3 +1,4 @@ +.ripcrypt.hud span, .ripcrypt > .window-content span { &.small { font-size: var(--font-size-10) diff --git a/templates/css/elements/table.css b/templates/css/elements/table.css index d27d228..44abd0b 100644 --- a/templates/css/elements/table.css +++ b/templates/css/elements/table.css @@ -1,3 +1,4 @@ +.ripcrypt.hud table, .ripcrypt > .window-content table { all: revert; box-sizing: border-box; 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; } diff --git a/templates/css/themes/light.css b/templates/css/themes/light.css index ecb01c7..ebc1127 100644 --- a/templates/css/themes/light.css +++ b/templates/css/themes/light.css @@ -11,4 +11,4 @@ --base-text: black; --base-background: white; -} \ No newline at end of file +}