diff --git a/daggerheart.mjs b/daggerheart.mjs index 879c3b58..27979ae1 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -13,6 +13,7 @@ import DhpTokenRuler from './module/ui/tokenRuler.mjs'; import { dualityRollEnricher } from './module/enrichers/DualityRollEnricher.mjs'; import { getCommandTarget, rollCommandToJSON, setDiceSoNiceForDualityRoll } from './module/helpers/utils.mjs'; import { abilities } from './module/config/actorConfig.mjs'; +import Resources from './module/applications/resources.mjs'; globalThis.SYSTEM = SYSTEM; @@ -92,9 +93,11 @@ Hooks.once('init', () => { CONFIG.Combat.documentClass = documents.DhpCombat; CONFIG.ui.combat = DhpCombatTracker; CONFIG.ui.chat = DhpChatLog; - CONFIG.ui.players = DhpPlayers; + // CONFIG.ui.players = DhpPlayers; CONFIG.Token.rulerClass = DhpTokenRuler; + CONFIG.ui.resources = Resources; + game.socket.on(`system.${SYSTEM.id}`, handleSocketEvent); // Make Compendium Dialog resizable @@ -106,6 +109,11 @@ Hooks.once('init', () => { return preloadHandlebarsTemplates(); }); +Hooks.on('ready', () => { + ui.resources = new CONFIG.ui.resources(); + ui.resources.render({force: true}); +}) + Hooks.once('dicesoniceready', () => {}); Hooks.on(socketEvent.GMUpdate, async (action, uuid, update) => { diff --git a/lang/en.json b/lang/en.json index b8a350be..75a803e1 100755 --- a/lang/en.json +++ b/lang/en.json @@ -81,6 +81,14 @@ "Fear": { "Name": "Fear", "Hint": "The Fear pool of the GM." + }, + "MaxFear": { + "Name": "Maximum amount of Fear", + "Hint": "The maximum amount of Fear the GM can get." + }, + "DisplayFear": { + "Name": "Fear display style", + "Hint": "Change how the GM Fear count should be displayed." } }, "General": { diff --git a/module/applications/resources.mjs b/module/applications/resources.mjs new file mode 100644 index 00000000..535a1631 --- /dev/null +++ b/module/applications/resources.mjs @@ -0,0 +1,110 @@ + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; + +/** + * A UI element which displays the Users defined for this world. + * Currently active users are always displayed, while inactive users can be displayed on toggle. + * + * @extends ApplicationV2 + * @mixes HandlebarsApplication + */ + +export default class Resources extends HandlebarsApplicationMixin(ApplicationV2) { + constructor(options={}) { + super(options); + } + + /** @inheritDoc */ + static DEFAULT_OPTIONS = { + id: "resources", + classes: [], + tag: "div", + window: { + frame: true, + title: "Fear", + positioned: true, + resizable: true + }, + actions: { + setFear: Resources.setFear, + increaseFear: Resources.increaseFear + }, + position: { + width: 222, + height: 222, + // top: "200px", + // left: "120px" + } + }; + + /** @override */ + static PARTS = { + resources: { + root: true, + template: "systems/daggerheart/templates/views/resources.hbs" + // template: "templates/ui/players.hbs" + } + }; + + get currentFear() { + return game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear); + } + + get maxFear() { + return game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.MaxFear); + } + + /* -------------------------------------------- */ + /* Rendering */ + /* -------------------------------------------- */ + + /** @override */ + async _prepareContext(_options) { + const display = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.DisplayFear), + current = this.currentFear, + max = this.maxFear, + percent = (current / max) * 100, + isGM = game.user.isGM; + // Return the data for rendering + return {display, current, max, percent, isGM}; + } + + /** @override */ + async _preFirstRender(context, options) { + options.position = game.user.getFlag(SYSTEM.id, 'app.resources.position') ?? Resources.DEFAULT_OPTIONS.position; + } + + /** @override */ + async _preRender(context, options) { + if(this.currentFear > this.maxFear) await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear, this.maxFear); + } + + _onPosition(position) { + game.user.setFlag(SYSTEM.id, 'app.resources.position', position); + } + + async close(options={}) { + if(!options.allowed) return; + else super.close(options); + } + + static async setFear(event, target) { + if(!game.user.isGM) return; + const fearCount = Number(target.dataset.index ?? 0); + await this.updateFear(this.currentFear === fearCount + 1 ? fearCount : fearCount + 1); + } + + static async increaseFear(event, target) { + let value = target.dataset.increment ?? 0, + operator = value.split('')[0] ?? null; + value = Number(value); + await this.updateFear(operator ? this.currentFear + value : value); + } + + async updateFear(value) { + if(!game.user.isGM) return; + value = Math.max(0, Math.min(this.maxFear, value)); + await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear, value); + await this.render(true); + } +} \ No newline at end of file diff --git a/module/applications/settings.mjs b/module/applications/settings.mjs index 631ce1ce..c86af3a7 100644 --- a/module/applications/settings.mjs +++ b/module/applications/settings.mjs @@ -182,6 +182,38 @@ export const registerDHSettings = () => { default: 0 }); + game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.MaxFear, { + name: game.i18n.localize('DAGGERHEART.Settings.Resources.MaxFear.Name'), + hint: game.i18n.localize('DAGGERHEART.Settings.Resources.MaxFear.Hint'), + scope: 'world', + config: true, + type: Number, + default: 12, + onChange: () => { + if(ui.resources) ui.resources.render({force: true}); + } + }); + + game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.DisplayFear, { + name: game.i18n.localize('DAGGERHEART.Settings.Resources.DisplayFear.Name'), + hint: game.i18n.localize('DAGGERHEART.Settings.Resources.DisplayFear.Hint'), + scope: 'client', + config: true, + type: String, + choices: { + 'token': 'Tokens', + 'bar': 'Bar', + 'hide': 'Hide' + }, + default: 'token', + onChange: value => { + if(ui.resources) { + if(value === 'hide') ui.resources.close({allowed: true}); + else ui.resources.render({force: true}); + } + } + }); + game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope, { name: game.i18n.localize('DAGGERHEART.Settings.Automation.Hope.Name'), hint: game.i18n.localize('DAGGERHEART.Settings.Automation.Hope.Hint'), diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index be570260..26de2a48 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -19,7 +19,9 @@ export const gameSettings = { ActionPoints: 'AutomationActionPoints' }, Resources: { - Fear: 'ResourcesFear' + Fear: 'ResourcesFear', + MaxFear: 'ResourcesMaxFear', + DisplayFear: 'DisplayFear' }, General: { AbilityArray: 'AbilityArray', diff --git a/styles/daggerheart.css b/styles/daggerheart.css index e890794f..a5550802 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -2718,6 +2718,115 @@ div.daggerheart.views.multiclass { .item-button .item-icon.checked { opacity: 1; } +:root { + --primary-color-fear: rgba(9, 71, 179, 0.75); + --secondary-color-fear: rgba(9, 71, 179, 0.75); + --shadow-text-stroke: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; +} +#resources { + min-height: calc(var(--header-height) + 4rem); + min-width: 4rem; + color: #d3d3d3; +} +#resources .window-content { + padding: 0.5rem; +} +#resources .window-content #resource-fear { + display: flex; + flex-direction: row; + gap: 0.5rem 0.25rem; + flex-wrap: wrap; +} +#resources .window-content #resource-fear i { + font-size: var(--font-size-18); + border: 1px solid rgba(0, 0, 0, 0.5); + border-radius: 50%; + aspect-ratio: 1; + display: flex; + justify-content: center; + align-items: center; + width: 3rem; + background-color: var(--primary-color-fear); + -webkit-box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.75); + box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.75); + color: #d3d3d3; + flex-grow: 0; +} +#resources .window-content #resource-fear i.inactive { + filter: grayscale(1) !important; + opacity: 0.5; +} +#resources .window-content #resource-fear .controls, +#resources .window-content #resource-fear .resource-bar { + border: 2px solid #997a4f; + background-color: #18162e; +} +#resources .window-content #resource-fear .controls { + display: flex; + align-self: center; + border-radius: 50%; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + font-size: var(--font-size-20); + cursor: pointer; +} +#resources .window-content #resource-fear .controls:hover { + font-size: 1.5rem; +} +#resources .window-content #resource-fear .controls.disabled { + opacity: 0.5; +} +#resources .window-content #resource-fear .resource-bar { + display: flex; + justify-content: center; + border-radius: 6px; + font-size: var(--font-size-20); + overflow: hidden; + position: relative; + padding: 0.25rem 0.5rem; + flex: 1; + text-shadow: var(--shadow-text-stroke); +} +#resources .window-content #resource-fear .resource-bar:before { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: var(--fear-percent); + max-width: 100%; + background: linear-gradient(90deg, #020026 0%, #c701fc 100%); + z-index: 0; + border-radius: 4px; +} +#resources .window-content #resource-fear .resource-bar span { + position: inherit; + z-index: 1; +} +#resources .window-content #resource-fear.isGM i { + cursor: pointer; +} +#resources .window-content #resource-fear.isGM i:hover { + font-size: var(--font-size-20); +} +#resources button[data-action="close"] { + display: none; +} +#resources:not(:hover):not(.minimized) { + background: transparent; + box-shadow: unset; + border-color: transparent; +} +#resources:not(:hover):not(.minimized) header, +#resources:not(:hover):not(.minimized) .controls, +#resources:not(:hover):not(.minimized) .window-resize-handle { + visibility: hidden; +} +#resources:has(.fear-bar) { + min-width: 200px; +} .application.sheet.daggerheart.dh-style.feature .item-sheet-header { display: flex; } diff --git a/styles/daggerheart.less b/styles/daggerheart.less index 181cd8a3..2ae18980 100755 --- a/styles/daggerheart.less +++ b/styles/daggerheart.less @@ -9,6 +9,7 @@ @import './sheets/sheets.less'; @import './dialog.less'; @import '../node_modules/@yaireo/tagify/dist/tagify.css'; +@import './resources.less'; // new styles imports @import './less/items/feature.less'; diff --git a/styles/resources.less b/styles/resources.less new file mode 100644 index 00000000..71a00c59 --- /dev/null +++ b/styles/resources.less @@ -0,0 +1,113 @@ +:root { + --primary-color-fear: rgba(9, 71, 179, .75); + --secondary-color-fear: rgba(9, 71, 179, .75); + --shadow-text-stroke: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; +} + +#resources { + min-height: calc(var(--header-height) + 4rem); + min-width: 4rem; + color: #d3d3d3; + .window-content { + padding: .5rem; + #resource-fear { + display: flex; + flex-direction: row; + gap: .5rem 0.25rem; + flex-wrap: wrap; + i { + font-size: var(--font-size-18); + // flex: 1 1 calc(25% - 0.25rem); + border: 1px solid rgba(0,0,0,.5); + border-radius: 50%; + aspect-ratio: 1; + display: flex; + justify-content: center; + align-items: center; + width: 3rem; + background-color: var(--primary-color-fear); + -webkit-box-shadow: 0px 0px 5px 1px rgba(0,0,0,.75); + box-shadow: 0px 0px 5px 1px rgba(0,0,0,.75); + color: #d3d3d3; + flex-grow: 0; + &.inactive{ + filter: grayscale(1) !important; + opacity: .5; + } + } + .controls, .resource-bar { + border: 2px solid rgb(153 122 79); + background-color: rgb(24 22 46); + } + .controls { + display: flex; + align-self: center; + border-radius: 50%; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + font-size: var(--font-size-20); + cursor: pointer; + &:hover { + font-size: 1.5rem; + } + &.disabled { + opacity: .5; + } + } + .resource-bar { + display: flex; + justify-content: center; + border-radius: 6px; + font-size: var(--font-size-20); + overflow: hidden; + position: relative; + padding: .25rem .5rem; + flex: 1; + text-shadow: var(--shadow-text-stroke); + &:before { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: var(--fear-percent); + max-width: 100%; + background: linear-gradient(90deg,rgba(2, 0, 38, 1) 0%, rgba(199, 1, 252, 1) 100%); + z-index: 0; + border-radius: 4px; + } + span { + position: inherit; + z-index: 1; + } + &.fear { + + } + } + &.isGM { + i { + cursor: pointer; + &:hover { + font-size: var(--font-size-20); + } + } + } + } + } + button[data-action="close"] { + display: none; + } + &:not(:hover):not(.minimized) { + background: transparent; + box-shadow: unset; + border-color: transparent; + header, .controls, .window-resize-handle { + visibility: hidden; + } + } + &:has(.fear-bar) { + min-width: 200px; + } +} \ No newline at end of file diff --git a/templates/views/resources.hbs b/templates/views/resources.hbs new file mode 100644 index 00000000..6832ab90 --- /dev/null +++ b/templates/views/resources.hbs @@ -0,0 +1,16 @@ +
+
+ {{#if (eq display 'token')}} + {{#times max}} + + {{/times}} + {{/if}} + {{#if (eq display 'bar')}} + {{#if isGM}}
-
{{/if}} +
+ {{current}}/{{max}} +
+ {{#if isGM}}
+
{{/if}} + {{/if}} +
+
\ No newline at end of file