From 6140b4baaf8cd4fadf20276cd3337e510ba418f2 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 31 Dec 2025 17:16:28 +0100 Subject: [PATCH 1/7] Initial --- daggerheart.mjs | 1 + lang/en.json | 12 ++- .../scene/sceneConfigSettings.mjs | 78 ++++++++++++++++--- module/applications/ui/_module.mjs | 1 + module/applications/ui/sceneNavigation.mjs | 54 +++++++++++++ module/config/generalConfig.mjs | 33 ++++++++ module/data/scene/scene.mjs | 13 +++- styles/less/ui/index.less | 2 + styles/less/ui/scene-config/scene-config.less | 59 ++++++++++++++ .../ui/scene-navigation/scene-navigation.less | 32 ++++++++ templates/scene/dh-config.hbs | 37 +++++++++ .../ui/sceneNavigation/scene-navigation.hbs | 36 +++++++++ 12 files changed, 345 insertions(+), 13 deletions(-) create mode 100644 module/applications/ui/sceneNavigation.mjs create mode 100644 styles/less/ui/scene-navigation/scene-navigation.less create mode 100644 templates/ui/sceneNavigation/scene-navigation.hbs diff --git a/daggerheart.mjs b/daggerheart.mjs index f1d8c67a..2f3e861b 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -62,6 +62,7 @@ CONFIG.Token.rulerClass = placeables.DhTokenRuler; CONFIG.Token.hudClass = applications.hud.DHTokenHUD; CONFIG.ui.combat = applications.ui.DhCombatTracker; +CONFIG.ui.nav = applications.ui.DhSceneNavigation; CONFIG.ui.chat = applications.ui.DhChatLog; CONFIG.ui.effectsDisplay = applications.ui.DhEffectsDisplay; CONFIG.ui.hotbar = applications.ui.DhHotbar; diff --git a/lang/en.json b/lang/en.json index 3f8c4321..b2f4413e 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1016,6 +1016,14 @@ "spell": "Spell", "grimoire": "Grimoire" }, + "EnvironmentIcons": { + "city": "City", + "dungeon": "Dungeon", + "mountain": "Mountain", + "social": "Social", + "tree": "Tree", + "water": "Water" + }, "EnvironmentType": { "exploration": { "label": "Exploration", @@ -2567,7 +2575,9 @@ } }, "disabledText": "Daggerheart Measurements are disabled in System Settings - Variant Rules", - "rangeMeasurement": "Range Measurement" + "rangeMeasurement": "Range Measurement", + "sceneEnvironments": "Scene Environments", + "noEnvironmentLinked": "Drag in an environment" } }, "UI": { diff --git a/module/applications/scene/sceneConfigSettings.mjs b/module/applications/scene/sceneConfigSettings.mjs index be8f7b71..5db6a502 100644 --- a/module/applications/scene/sceneConfigSettings.mjs +++ b/module/applications/scene/sceneConfigSettings.mjs @@ -1,11 +1,12 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.SceneConfig { - // static DEFAULT_OPTIONS = { - // ...super.DEFAULT_OPTIONS, - // form: { - // handler: this.updateData, - // closeOnSubmit: true - // } - // }; + static DEFAULT_OPTIONS = { + ...super.DEFAULT_OPTIONS, + actions: { + ...super.DEFAULT_OPTIONS.actions, + addSceneEnvironment: DhSceneConfigSettings.#addSceneEnvironment, + removeSceneEnvironment: DhSceneConfigSettings.#removeSceneEnvironment + } + }; static buildParts() { const { footer, tabs, ...parts } = super.PARTS; @@ -30,6 +31,7 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); + switch (partId) { case 'dh': htmlElement.querySelector('#rangeMeasurementSetting')?.addEventListener('change', async event => { @@ -39,10 +41,37 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S this.document.flags.daggerheart = flagData; this.render(); }); + + htmlElement.querySelectorAll('.scene-environment').forEach(element => { + element.querySelector('select')?.addEventListener('change', async event => { + const id = event.target.dataset.key; + const flagData = foundry.utils.mergeObject(this.document.flags.daggerheart, { + sceneEnvironments: { [id]: { icon: event.target.value } } + }); + this.document.flags.daggerheart = flagData; + this.render(); + }); + + element.ondrop = this._onDrop.bind(this); + }); + break; } } + async _onDrop(event) { + const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); + const item = await foundry.utils.fromUuid(data.uuid); + if (item instanceof game.system.api.documents.DhpActor && item.type === 'environment') { + const element = event.target.closest('.scene-environment'); + const flagData = foundry.utils.mergeObject(this.document.flags.daggerheart, { + sceneEnvironments: { [element.dataset.key]: { environment: data.uuid } } + }); + this.document.flags.daggerheart = flagData; + this.render(); + } + } + /** @inheritDoc */ async _preparePartContext(partId, context, options) { context = await super._preparePartContext(partId, context, options); @@ -50,14 +79,41 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S case 'dh': context.data = new game.system.api.data.scenes.DHScene(canvas.scene.flags.daggerheart); context.variantRules = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules); + context.environmentIcons = CONFIG.DH.GENERAL.environmentIcons; break; } return context; } - // static async updateData(event, _, formData) { - // const data = foundry.utils.expandObject(formData.object); - // this.close(data); - // } + static async #addSceneEnvironment() { + const flagData = foundry.utils.mergeObject(this.document.flags.daggerheart, { + sceneEnvironments: { [foundry.utils.randomID()]: { icon: CONFIG.DH.GENERAL.environmentIcons.tree.icon } } + }); + this.document.flags.daggerheart = flagData; + + this.render(); + } + + static async #removeSceneEnvironment(_event, button) { + this.document.flags.daggerheart.sceneEnvironments = Object.keys( + this.document.flags.daggerheart.sceneEnvironments + ).reduce((acc, key) => { + if (key !== button.dataset.key) acc[key] = this.document.flags.daggerheart.sceneEnvironments[key]; + return acc; + }, {}); + this.render(); + } + + /** @override */ + async _processSubmitData(event, form, submitData, options) { + submitData.flags.daggerheart.sceneEnvironments = this.document.flags.daggerheart.sceneEnvironments; + for (const key of Object.keys(this.document._source.flags.daggerheart.sceneEnvironments)) { + if (!submitData.flags.daggerheart.sceneEnvironments[key]) { + submitData.flags.daggerheart.sceneEnvironments[`-=${key}`] = null; + } + } + + super._processSubmitData(event, form, submitData, options); + } } diff --git a/module/applications/ui/_module.mjs b/module/applications/ui/_module.mjs index d5f31906..8c5c020e 100644 --- a/module/applications/ui/_module.mjs +++ b/module/applications/ui/_module.mjs @@ -5,4 +5,5 @@ export { default as DhCombatTracker } from './combatTracker.mjs'; export { default as DhEffectsDisplay } from './effectsDisplay.mjs'; export { default as DhFearTracker } from './fearTracker.mjs'; export { default as DhHotbar } from './hotbar.mjs'; +export { default as DhSceneNavigation } from './sceneNavigation.mjs'; export { ItemBrowser } from './itemBrowser.mjs'; diff --git a/module/applications/ui/sceneNavigation.mjs b/module/applications/ui/sceneNavigation.mjs new file mode 100644 index 00000000..fbad056b --- /dev/null +++ b/module/applications/ui/sceneNavigation.mjs @@ -0,0 +1,54 @@ +export default class DhSceneNavigation extends foundry.applications.ui.SceneNavigation { + /** @inheritdoc */ + static DEFAULT_OPTIONS = { + ...super.DEFAULT_OPTIONS, + classes: ['faded-ui', 'flexcol', 'scene-navigation'], + actions: { + openSceneEnvironment: DhSceneNavigation.#openSceneEnvironment + } + }; + + /** @inheritdoc */ + static PARTS = { + scenes: { + root: true, + template: 'systems/daggerheart/templates/ui/sceneNavigation/scene-navigation.hbs' + } + }; + + /** @inheritdoc */ + async _prepareContext(options) { + const context = await super._prepareContext(options); + + const extendScenes = scenes => + scenes.map(x => { + const scene = game.scenes.get(x.id); + if (!scene.flags.daggerheart) return x; + + const daggerheartInfo = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart); + const environmentKeys = Object.keys(daggerheartInfo.sceneEnvironments); + const hasEnvironments = environmentKeys.length; + return { + ...x, + hasEnvironments, + environmentImage: hasEnvironments + ? daggerheartInfo.sceneEnvironments[environmentKeys[0]].environment.img + : null, + environments: daggerheartInfo.sceneEnvironments + }; + }); + context.scenes.active = extendScenes(context.scenes.active); + context.scenes.inactive = extendScenes(context.scenes.inactive); + + return context; + } + + static async #openSceneEnvironment(_event, button) { + const scene = game.scenes.get(button.dataset.sceneId); + const sceneEnvironments = Object.keys(scene.flags.daggerheart.sceneEnvironments); + const environment = await foundry.utils.fromUuid( + scene.flags.daggerheart.sceneEnvironments[sceneEnvironments[0]].environment + ); + environment.sheet.render(true); + } +} diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 3f49f7aa..33a6370e 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -731,3 +731,36 @@ export const sceneRangeMeasurementSetting = { label: 'Custom' } }; + +export const environmentIcons = { + tree: { + name: 'DAGGERHEART.CONFIG.EnvironmentIcons.tree', + icon: 'fa-solid fa-tree', + unicode: '' + }, + mountain: { + name: 'DAGGERHEART.CONFIG.EnvironmentIcons.mountain', + icon: 'fa-solid fa-mountain', + unicode: '' + }, + city: { + name: 'DAGGERHEART.CONFIG.EnvironmentIcons.city', + icon: 'fa-solid fa-house', + unicode: '' + }, + dungeon: { + name: 'DAGGERHEART.CONFIG.EnvironmentIcons.dungeon', + icon: 'fa-solid fa-dungeon', + unicode: '' + }, + water: { + name: 'DAGGERHEART.CONFIG.EnvironmentIcons.water', + icon: 'fa-solid fa-water', + unicode: '' + }, + social: { + name: 'DAGGERHEART.CONFIG.EnvironmentIcons.social', + icon: 'fa-solid fa-masks-theater', + unicode: '' + } +}; diff --git a/module/data/scene/scene.mjs b/module/data/scene/scene.mjs index 7cf74ade..e3796936 100644 --- a/module/data/scene/scene.mjs +++ b/module/data/scene/scene.mjs @@ -1,3 +1,5 @@ +import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; + export default class DHScene extends foundry.abstract.DataModel { static defineSchema() { const fields = foundry.data.fields; @@ -13,7 +15,16 @@ export default class DHScene extends foundry.abstract.DataModel { veryClose: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.veryClose.name' }), close: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.close.name' }), far: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.far.name' }) - }) + }), + sceneEnvironments: new fields.TypedObjectField( + new fields.SchemaField({ + environment: new ForeignDocumentUUIDField({ type: 'Actor' }), + icon: new fields.StringField({ + required: true, + initial: CONFIG.DH.GENERAL.environmentIcons.tree.icon + }) + }) + ) }; } } diff --git a/styles/less/ui/index.less b/styles/less/ui/index.less index 7f9ada25..25f51d0f 100644 --- a/styles/less/ui/index.less +++ b/styles/less/ui/index.less @@ -33,3 +33,5 @@ @import './scene-config/scene-config.less'; @import './effects-display/sheet.less'; + +@import './scene-navigation/scene-navigation.less'; diff --git a/styles/less/ui/scene-config/scene-config.less b/styles/less/ui/scene-config/scene-config.less index fb36dd33..664e7526 100644 --- a/styles/less/ui/scene-config/scene-config.less +++ b/styles/less/ui/scene-config/scene-config.less @@ -37,4 +37,63 @@ .helper-text { font-style: italic; } + + .scene-environments { + display: flex; + flex-direction: column; + gap: 8px; + + .scene-environment { + display: flex; + align-items: center; + gap: 8px; + + .scene-environment-inner { + display: flex; + align-items: center; + gap: 16px; + flex: 1; + + img { + height: 36px; + } + + h5 { + margin: 0; + } + + .tags { + display: flex; + gap: 4px; + padding-bottom: 0; + + .tag { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: var(--font-size-12); + font: @font-body; + + background: light-dark(@dark-15, @beige-15); + border: 1px solid light-dark(@dark, @beige); + border-radius: 3px; + } + + .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: var(--font-size-12); + } + } + } + + .remove-icon { + font-size: 16px; + } + } + } } diff --git a/styles/less/ui/scene-navigation/scene-navigation.less b/styles/less/ui/scene-navigation/scene-navigation.less new file mode 100644 index 00000000..63c302c6 --- /dev/null +++ b/styles/less/ui/scene-navigation/scene-navigation.less @@ -0,0 +1,32 @@ +#ui-left #ui-left-column-2 { + flex: 0 0 230px; + + .scene-navigation { + .scene-wrapper { + display: flex; + gap: 2px; + height: var(--control-size); + width: 100%; + + .scene-environment { + padding: 0; + } + } + + .scene { + justify-content: center; + align-content: center; + background: var(--control-bg-color); + border: 1px solid var(--control-border-color); + border-radius: 4px; + color: var(--control-icon-color); + pointer-events: all; + transition: + border 0.25s, + color 0.25s; + text-shadow: none; + width: 200px; + max-width: 200px; + } + } +} diff --git a/templates/scene/dh-config.hbs b/templates/scene/dh-config.hbs index 1f7dcd81..c6446eb8 100644 --- a/templates/scene/dh-config.hbs +++ b/templates/scene/dh-config.hbs @@ -21,4 +21,41 @@ {{localize "DAGGERHEART.SETTINGS.Scene.disabledText"}} {{/if}} + +
+ + {{localize "DAGGERHEART.SETTINGS.Scene.sceneEnvironments"}} + + + +
+ {{#each data.sceneEnvironments as |data key|}} +
+ {{#if data.environment}} +
+ +
{{data.environment.name}}
+
+
+ + {{localize (concat 'DAGGERHEART.GENERAL.Tiers.' data.environment.system.tier)}} + +
+ {{#if data.environment.system.type}} +
+ + {{localize (concat 'DAGGERHEART.CONFIG.EnvironmentType.' data.environment.system.type '.label')}} + +
+ {{/if}} +
+
+ {{else}} + {{localize "DAGGERHEART.SETTINGS.Scene.noEnvironmentLinked"}} + {{/if}} + +
+ {{/each}} +
+
\ No newline at end of file diff --git a/templates/ui/sceneNavigation/scene-navigation.hbs b/templates/ui/sceneNavigation/scene-navigation.hbs new file mode 100644 index 00000000..0666eabf --- /dev/null +++ b/templates/ui/sceneNavigation/scene-navigation.hbs @@ -0,0 +1,36 @@ + From e6419b89d0130ff947c40941cda6532119ed9cc3 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 31 Dec 2025 19:45:10 +0100 Subject: [PATCH 2/7] Remade to array structure --- lang/en.json | 2 +- .../scene/sceneConfigSettings.mjs | 58 ++++++------------- module/applications/ui/sceneNavigation.mjs | 4 +- module/config/generalConfig.mjs | 33 ----------- module/data/scene/scene.mjs | 12 +--- templates/scene/dh-config.hbs | 22 ++++--- 6 files changed, 33 insertions(+), 98 deletions(-) diff --git a/lang/en.json b/lang/en.json index b2f4413e..2ba20e88 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2577,7 +2577,7 @@ "disabledText": "Daggerheart Measurements are disabled in System Settings - Variant Rules", "rangeMeasurement": "Range Measurement", "sceneEnvironments": "Scene Environments", - "noEnvironmentLinked": "Drag in an environment" + "dragEnvironmentHere": "Drag environments here" } }, "UI": { diff --git a/module/applications/scene/sceneConfigSettings.mjs b/module/applications/scene/sceneConfigSettings.mjs index 5db6a502..5c707f8e 100644 --- a/module/applications/scene/sceneConfigSettings.mjs +++ b/module/applications/scene/sceneConfigSettings.mjs @@ -1,9 +1,14 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.SceneConfig { + constructor(options = {}) { + super(options); + + this.daggerheartFlag = new game.system.api.data.scenes.DHScene(this.document.flags.daggerheart); + } + static DEFAULT_OPTIONS = { ...super.DEFAULT_OPTIONS, actions: { ...super.DEFAULT_OPTIONS.actions, - addSceneEnvironment: DhSceneConfigSettings.#addSceneEnvironment, removeSceneEnvironment: DhSceneConfigSettings.#removeSceneEnvironment } }; @@ -11,7 +16,6 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S static buildParts() { const { footer, tabs, ...parts } = super.PARTS; const tmpParts = { - // tabs, tabs: { template: 'systems/daggerheart/templates/scene/tabs.hbs' }, ...parts, dh: { template: 'systems/daggerheart/templates/scene/dh-config.hbs' }, @@ -35,25 +39,12 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S switch (partId) { case 'dh': htmlElement.querySelector('#rangeMeasurementSetting')?.addEventListener('change', async event => { - const flagData = foundry.utils.mergeObject(this.document.flags.daggerheart, { - rangeMeasurement: { setting: event.target.value } - }); - this.document.flags.daggerheart = flagData; + this.daggerheartFlag.updateSource({ rangeMeasurement: { setting: event.target.value } }); this.render(); }); - htmlElement.querySelectorAll('.scene-environment').forEach(element => { - element.querySelector('select')?.addEventListener('change', async event => { - const id = event.target.dataset.key; - const flagData = foundry.utils.mergeObject(this.document.flags.daggerheart, { - sceneEnvironments: { [id]: { icon: event.target.value } } - }); - this.document.flags.daggerheart = flagData; - this.render(); - }); - - element.ondrop = this._onDrop.bind(this); - }); + const dragArea = htmlElement.querySelector('.scene-environments'); + if (dragArea) dragArea.ondrop = this._onDrop.bind(this); break; } @@ -63,11 +54,9 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const item = await foundry.utils.fromUuid(data.uuid); if (item instanceof game.system.api.documents.DhpActor && item.type === 'environment') { - const element = event.target.closest('.scene-environment'); - const flagData = foundry.utils.mergeObject(this.document.flags.daggerheart, { - sceneEnvironments: { [element.dataset.key]: { environment: data.uuid } } + await this.daggerheartFlag.updateSource({ + sceneEnvironments: [...this.daggerheartFlag.sceneEnvironments, data.uuid] }); - this.document.flags.daggerheart = flagData; this.render(); } } @@ -77,37 +66,26 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S context = await super._preparePartContext(partId, context, options); switch (partId) { case 'dh': - context.data = new game.system.api.data.scenes.DHScene(canvas.scene.flags.daggerheart); + context.data = this.daggerheartFlag; context.variantRules = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules); - context.environmentIcons = CONFIG.DH.GENERAL.environmentIcons; break; } return context; } - static async #addSceneEnvironment() { - const flagData = foundry.utils.mergeObject(this.document.flags.daggerheart, { - sceneEnvironments: { [foundry.utils.randomID()]: { icon: CONFIG.DH.GENERAL.environmentIcons.tree.icon } } - }); - this.document.flags.daggerheart = flagData; - - this.render(); - } - static async #removeSceneEnvironment(_event, button) { - this.document.flags.daggerheart.sceneEnvironments = Object.keys( - this.document.flags.daggerheart.sceneEnvironments - ).reduce((acc, key) => { - if (key !== button.dataset.key) acc[key] = this.document.flags.daggerheart.sceneEnvironments[key]; - return acc; - }, {}); + await this.daggerheartFlag.updateSource({ + sceneEnvironments: this.daggerheartFlag.sceneEnvironments.filter( + (_, index) => index !== Number.parseInt(button.dataset.index) + ) + }); this.render(); } /** @override */ async _processSubmitData(event, form, submitData, options) { - submitData.flags.daggerheart.sceneEnvironments = this.document.flags.daggerheart.sceneEnvironments; + submitData.flags.daggerheart = this.daggerheartFlag.toObject(); for (const key of Object.keys(this.document._source.flags.daggerheart.sceneEnvironments)) { if (!submitData.flags.daggerheart.sceneEnvironments[key]) { submitData.flags.daggerheart.sceneEnvironments[`-=${key}`] = null; diff --git a/module/applications/ui/sceneNavigation.mjs b/module/applications/ui/sceneNavigation.mjs index fbad056b..13519d04 100644 --- a/module/applications/ui/sceneNavigation.mjs +++ b/module/applications/ui/sceneNavigation.mjs @@ -32,7 +32,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi ...x, hasEnvironments, environmentImage: hasEnvironments - ? daggerheartInfo.sceneEnvironments[environmentKeys[0]].environment.img + ? daggerheartInfo.sceneEnvironments[environmentKeys[0]].img : null, environments: daggerheartInfo.sceneEnvironments }; @@ -47,7 +47,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi const scene = game.scenes.get(button.dataset.sceneId); const sceneEnvironments = Object.keys(scene.flags.daggerheart.sceneEnvironments); const environment = await foundry.utils.fromUuid( - scene.flags.daggerheart.sceneEnvironments[sceneEnvironments[0]].environment + scene.flags.daggerheart.sceneEnvironments[sceneEnvironments[0]] ); environment.sheet.render(true); } diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 33a6370e..3f49f7aa 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -731,36 +731,3 @@ export const sceneRangeMeasurementSetting = { label: 'Custom' } }; - -export const environmentIcons = { - tree: { - name: 'DAGGERHEART.CONFIG.EnvironmentIcons.tree', - icon: 'fa-solid fa-tree', - unicode: '' - }, - mountain: { - name: 'DAGGERHEART.CONFIG.EnvironmentIcons.mountain', - icon: 'fa-solid fa-mountain', - unicode: '' - }, - city: { - name: 'DAGGERHEART.CONFIG.EnvironmentIcons.city', - icon: 'fa-solid fa-house', - unicode: '' - }, - dungeon: { - name: 'DAGGERHEART.CONFIG.EnvironmentIcons.dungeon', - icon: 'fa-solid fa-dungeon', - unicode: '' - }, - water: { - name: 'DAGGERHEART.CONFIG.EnvironmentIcons.water', - icon: 'fa-solid fa-water', - unicode: '' - }, - social: { - name: 'DAGGERHEART.CONFIG.EnvironmentIcons.social', - icon: 'fa-solid fa-masks-theater', - unicode: '' - } -}; diff --git a/module/data/scene/scene.mjs b/module/data/scene/scene.mjs index e3796936..720a6c50 100644 --- a/module/data/scene/scene.mjs +++ b/module/data/scene/scene.mjs @@ -1,4 +1,4 @@ -import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; +import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; export default class DHScene extends foundry.abstract.DataModel { static defineSchema() { @@ -16,15 +16,7 @@ export default class DHScene extends foundry.abstract.DataModel { close: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.close.name' }), far: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.far.name' }) }), - sceneEnvironments: new fields.TypedObjectField( - new fields.SchemaField({ - environment: new ForeignDocumentUUIDField({ type: 'Actor' }), - icon: new fields.StringField({ - required: true, - initial: CONFIG.DH.GENERAL.environmentIcons.tree.icon - }) - }) - ) + sceneEnvironments: new ForeignDocumentUUIDArrayField({ type: 'Actor' }) }; } } diff --git a/templates/scene/dh-config.hbs b/templates/scene/dh-config.hbs index c6446eb8..017613ee 100644 --- a/templates/scene/dh-config.hbs +++ b/templates/scene/dh-config.hbs @@ -25,37 +25,35 @@
{{localize "DAGGERHEART.SETTINGS.Scene.sceneEnvironments"}} -
- {{#each data.sceneEnvironments as |data key|}} -
- {{#if data.environment}} + {{#each data.sceneEnvironments as |environment index|}} +
+ {{#if environment}}
- -
{{data.environment.name}}
+ +
{{environment.name}}
- {{localize (concat 'DAGGERHEART.GENERAL.Tiers.' data.environment.system.tier)}} + {{localize (concat 'DAGGERHEART.GENERAL.Tiers.' environment.system.tier)}}
- {{#if data.environment.system.type}} + {{#if environment.system.type}}
- {{localize (concat 'DAGGERHEART.CONFIG.EnvironmentType.' data.environment.system.type '.label')}} + {{localize (concat 'DAGGERHEART.CONFIG.EnvironmentType.' environment.system.type '.label')}}
{{/if}}
- {{else}} - {{localize "DAGGERHEART.SETTINGS.Scene.noEnvironmentLinked"}} {{/if}} - +
{{/each}} + {{localize "DAGGERHEART.SETTINGS.Scene.dragEnvironmentHere"}}
\ No newline at end of file From eb954b7ef63399270af0a88969fb7245ecc06d4f Mon Sep 17 00:00:00 2001 From: WBHarry Date: Wed, 31 Dec 2025 21:02:16 +0100 Subject: [PATCH 3/7] Fixed context menu for multiples --- .../scene/sceneConfigSettings.mjs | 8 +++-- module/applications/ui/sceneNavigation.mjs | 29 +++++++++++++++---- module/applications/ux/contextMenu.mjs | 4 +-- .../ui/scene-navigation/scene-navigation.less | 4 +++ .../ui/sceneNavigation/scene-navigation.hbs | 14 ++++----- 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/module/applications/scene/sceneConfigSettings.mjs b/module/applications/scene/sceneConfigSettings.mjs index 5c707f8e..15cdfeee 100644 --- a/module/applications/scene/sceneConfigSettings.mjs +++ b/module/applications/scene/sceneConfigSettings.mjs @@ -86,9 +86,11 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S /** @override */ async _processSubmitData(event, form, submitData, options) { submitData.flags.daggerheart = this.daggerheartFlag.toObject(); - for (const key of Object.keys(this.document._source.flags.daggerheart.sceneEnvironments)) { - if (!submitData.flags.daggerheart.sceneEnvironments[key]) { - submitData.flags.daggerheart.sceneEnvironments[`-=${key}`] = null; + if (this.document._source.flags.daggerheart) { + for (const key of Object.keys(this.document._source.flags.daggerheart.sceneEnvironments)) { + if (!submitData.flags.daggerheart.sceneEnvironments[key]) { + submitData.flags.daggerheart.sceneEnvironments[`-=${key}`] = null; + } } } diff --git a/module/applications/ui/sceneNavigation.mjs b/module/applications/ui/sceneNavigation.mjs index 13519d04..dc9ef1e5 100644 --- a/module/applications/ui/sceneNavigation.mjs +++ b/module/applications/ui/sceneNavigation.mjs @@ -43,12 +43,29 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi return context; } - static async #openSceneEnvironment(_event, button) { + static async #openSceneEnvironment(event, button) { const scene = game.scenes.get(button.dataset.sceneId); - const sceneEnvironments = Object.keys(scene.flags.daggerheart.sceneEnvironments); - const environment = await foundry.utils.fromUuid( - scene.flags.daggerheart.sceneEnvironments[sceneEnvironments[0]] - ); - environment.sheet.render(true); + const sceneEnvironments = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart).sceneEnvironments; + + if (sceneEnvironments.length === 1) { + sceneEnvironments[0].sheet.render(true); + } else { + new ContextMenu( + button, + '.scene-environment', + sceneEnvironments.map(environment => ({ + name: environment.name, + callback: () => { + environment.sheet.render({ force: true }); + } + })), + { + jQuery: false, + fixed: true + } + ); + + CONFIG.ux.ContextMenu.triggerContextMenu(event, '.scene-environment'); + } } } diff --git a/module/applications/ux/contextMenu.mjs b/module/applications/ux/contextMenu.mjs index 09454848..a87a51fc 100644 --- a/module/applications/ux/contextMenu.mjs +++ b/module/applications/ux/contextMenu.mjs @@ -96,11 +96,11 @@ export default class DHContextMenu extends foundry.applications.ux.ContextMenu { * Trigger a context menu event in response to a normal click on a additional options button. * @param {PointerEvent} event */ - static triggerContextMenu(event) { + static triggerContextMenu(event, elementSelector) { event.preventDefault(); event.stopPropagation(); const { clientX, clientY } = event; - const selector = '[data-item-uuid]'; + const selector = elementSelector ?? '[data-item-uuid]'; const target = event.target.closest(selector) ?? event.currentTarget.closest(selector); target?.dispatchEvent( new PointerEvent('contextmenu', { diff --git a/styles/less/ui/scene-navigation/scene-navigation.less b/styles/less/ui/scene-navigation/scene-navigation.less index 63c302c6..6b97ddec 100644 --- a/styles/less/ui/scene-navigation/scene-navigation.less +++ b/styles/less/ui/scene-navigation/scene-navigation.less @@ -10,6 +10,10 @@ .scene-environment { padding: 0; + + img { + border-radius: 4px; + } } } diff --git a/templates/ui/sceneNavigation/scene-navigation.hbs b/templates/ui/sceneNavigation/scene-navigation.hbs index 0666eabf..41e9e3e8 100644 --- a/templates/ui/sceneNavigation/scene-navigation.hbs +++ b/templates/ui/sceneNavigation/scene-navigation.hbs @@ -7,8 +7,7 @@ {{#each scenes.active as |scene|}}
  • -
    +
    {{scene.name}} {{#if scene.users}}
      @@ -20,17 +19,18 @@ {{/if}}
    {{#if scene.hasEnvironments}} - + {{/if}}
  • {{/each}}
    {{#each scenes.inactive as |scene|}} -
  • - {{scene.name}} -
  • +
  • +
    + {{scene.name}} +
    +
  • {{/each}}
    From db8c2e29c1f13f0efebbfc6d9996b375f2a6c7f0 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 2 Jan 2026 04:42:40 +0100 Subject: [PATCH 4/7] . --- .../scene/sceneConfigSettings.mjs | 16 ++++++++-- module/applications/ui/sceneNavigation.mjs | 21 ++++++++----- module/data/actor/environment.mjs | 30 ++++++++++++++++++- module/data/scene/scene.mjs | 5 +++- module/documents/scene.mjs | 26 ++++++++++++++++ module/systemRegistration/socket.mjs | 7 ++++- 6 files changed, 93 insertions(+), 12 deletions(-) diff --git a/module/applications/scene/sceneConfigSettings.mjs b/module/applications/scene/sceneConfigSettings.mjs index 15cdfeee..6a093699 100644 --- a/module/applications/scene/sceneConfigSettings.mjs +++ b/module/applications/scene/sceneConfigSettings.mjs @@ -1,8 +1,15 @@ +import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; + export default class DhSceneConfigSettings extends foundry.applications.sheets.SceneConfig { - constructor(options = {}) { + constructor(options) { super(options); - this.daggerheartFlag = new game.system.api.data.scenes.DHScene(this.document.flags.daggerheart); + Hooks.on(socketEvent.Refresh, ({ refreshType }) => { + if (refreshType === RefreshType.Scene) { + this.daggerheartFlag = new game.system.api.data.scenes.DHScene(this.document.flags.daggerheart); + this.render(); + } + }); } static DEFAULT_OPTIONS = { @@ -33,6 +40,11 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S static TABS = DhSceneConfigSettings.buildTabs(); + async _preFirstRender(context, options) { + await super._preFirstRender(context, options); + this.daggerheartFlag = new game.system.api.data.scenes.DHScene(this.document.flags.daggerheart); + } + _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); diff --git a/module/applications/ui/sceneNavigation.mjs b/module/applications/ui/sceneNavigation.mjs index dc9ef1e5..735685d1 100644 --- a/module/applications/ui/sceneNavigation.mjs +++ b/module/applications/ui/sceneNavigation.mjs @@ -26,15 +26,13 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi if (!scene.flags.daggerheart) return x; const daggerheartInfo = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart); - const environmentKeys = Object.keys(daggerheartInfo.sceneEnvironments); - const hasEnvironments = environmentKeys.length; + const environments = daggerheartInfo.sceneEnvironments.filter(x => x); + const hasEnvironments = environments.length > 0; return { ...x, hasEnvironments, - environmentImage: hasEnvironments - ? daggerheartInfo.sceneEnvironments[environmentKeys[0]].img - : null, - environments: daggerheartInfo.sceneEnvironments + environmentImage: hasEnvironments ? environments[0].img : null, + environments: environments }; }); context.scenes.active = extendScenes(context.scenes.active); @@ -47,7 +45,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi const scene = game.scenes.get(button.dataset.sceneId); const sceneEnvironments = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart).sceneEnvironments; - if (sceneEnvironments.length === 1) { + if (sceneEnvironments.length === 1 || event.shiftKey) { sceneEnvironments[0].sheet.render(true); } else { new ContextMenu( @@ -56,6 +54,15 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi sceneEnvironments.map(environment => ({ name: environment.name, callback: () => { + if (scene.flags.daggerheart.sceneEnvironments[0] !== environment.uuid) { + const newEnvironments = scene.flags.daggerheart.sceneEnvironments; + const newFirst = newEnvironments.splice( + newEnvironments.findIndex(x => x === environment.uuid) + )[0]; + newEnvironments.unshift(newFirst); + scene.update({ 'flags.daggerheart.sceneEnvironments': newEnvironments }); + } + environment.sheet.render({ force: true }); } })), diff --git a/module/data/actor/environment.mjs b/module/data/actor/environment.mjs index 4ed3819e..0aaf8eb0 100644 --- a/module/data/actor/environment.mjs +++ b/module/data/actor/environment.mjs @@ -1,8 +1,11 @@ import BaseDataActor from './base.mjs'; import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; import DHEnvironmentSettings from '../../applications/sheets-configs/environment-settings.mjs'; +import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; export default class DhEnvironment extends BaseDataActor { + scenes = new Set(); + /**@override */ static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Environment']; @@ -53,6 +56,31 @@ export default class DhEnvironment extends BaseDataActor { } isItemValid(source) { - return source.type === "feature"; + return source.type === 'feature'; + } + + _onUpdate(changes, options, userId) { + super._onUpdate(changes, options, userId); + for (const scene of this.scenes) { + scene.render(); + } + } + + _onDelete(options, userId) { + super._onDelete(options, userId); + for (const scene of this.scenes) { + if (game.user.isActiveGM) { + const newSceneEnvironments = scene.flags.daggerheart.sceneEnvironments.filter( + x => x !== this.parent.uuid + ); + scene.update({ 'flags.daggerheart.sceneEnvironments': newSceneEnvironments }).then(() => { + Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Scene }); + game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.Refresh, + data: { refreshType: RefreshType.TagTeamRoll } + }); + }); + } + } } } diff --git a/module/data/scene/scene.mjs b/module/data/scene/scene.mjs index 720a6c50..3ff74f9f 100644 --- a/module/data/scene/scene.mjs +++ b/module/data/scene/scene.mjs @@ -1,5 +1,8 @@ import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; +/* Foundry does not add any system data for subtyped Scenes. The data model is therefore mainly for reference until this changes. + Needed dataprep and lifetime hooks are handled in documents/scene. +*/ export default class DHScene extends foundry.abstract.DataModel { static defineSchema() { const fields = foundry.data.fields; @@ -16,7 +19,7 @@ export default class DHScene extends foundry.abstract.DataModel { close: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.close.name' }), far: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.far.name' }) }), - sceneEnvironments: new ForeignDocumentUUIDArrayField({ type: 'Actor' }) + sceneEnvironments: new ForeignDocumentUUIDArrayField({ type: 'Actor', prune: true }) }; } } diff --git a/module/documents/scene.mjs b/module/documents/scene.mjs index c6cdd2c2..fe9b2dfd 100644 --- a/module/documents/scene.mjs +++ b/module/documents/scene.mjs @@ -37,4 +37,30 @@ export default class DhScene extends Scene { this.#sizeSyncBatch.clear(); this.updateEmbeddedDocuments('Token', entries, { animation: { movementSpeed: 1.5 } }); }, 0); + + prepareBaseData() { + super.prepareBaseData(); + + if (this instanceof game.system.api.documents.DhScene) { + const system = new game.system.api.data.scenes.DHScene(this.flags.daggerheart); + + // Register this scene to all environements + for (const environment of system.sceneEnvironments) { + environment.system.scenes?.add(this); + } + } + } + + _onDelete(options, userId) { + super._onDelete(options, userId); + + if (this instanceof game.system.api.documents.DhScene) { + const system = new game.system.api.data.scenes.DHScene(this.flags.daggerheart); + + // Clear this scene from all environments that aren't deleted + for (const environement of system.sceneEnvironments) { + environement?.system?.scenes?.delete(this); + } + } + } } diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs index 046f1b68..82ca2e1c 100644 --- a/module/systemRegistration/socket.mjs +++ b/module/systemRegistration/socket.mjs @@ -37,7 +37,8 @@ export const GMUpdateEvent = { export const RefreshType = { Countdown: 'DhCoundownRefresh', TagTeamRoll: 'DhTagTeamRollRefresh', - EffectsDisplay: 'DhEffectsDisplayRefresh' + EffectsDisplay: 'DhEffectsDisplayRefresh', + Scene: 'DhSceneRefresh' }; export const registerSocketHooks = () => { @@ -92,6 +93,10 @@ export const registerSocketHooks = () => { } } }); + Hooks.on(socketEvent.RefreshDocument, async data => { + const document = await foundry.utils.fromUuid(data.uuid); + document.sheet.render(); + }); }; export const registerUserQueries = () => { From 4596efd9210dbe980bf664f346381ea39ca08f49 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 2 Jan 2026 04:52:53 +0100 Subject: [PATCH 5/7] Cleanup --- lang/en.json | 8 -------- module/applications/ux/contextMenu.mjs | 4 ++-- module/data/scene/scene.mjs | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lang/en.json b/lang/en.json index 2ba20e88..85e83dbe 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1016,14 +1016,6 @@ "spell": "Spell", "grimoire": "Grimoire" }, - "EnvironmentIcons": { - "city": "City", - "dungeon": "Dungeon", - "mountain": "Mountain", - "social": "Social", - "tree": "Tree", - "water": "Water" - }, "EnvironmentType": { "exploration": { "label": "Exploration", diff --git a/module/applications/ux/contextMenu.mjs b/module/applications/ux/contextMenu.mjs index a87a51fc..09454848 100644 --- a/module/applications/ux/contextMenu.mjs +++ b/module/applications/ux/contextMenu.mjs @@ -96,11 +96,11 @@ export default class DHContextMenu extends foundry.applications.ux.ContextMenu { * Trigger a context menu event in response to a normal click on a additional options button. * @param {PointerEvent} event */ - static triggerContextMenu(event, elementSelector) { + static triggerContextMenu(event) { event.preventDefault(); event.stopPropagation(); const { clientX, clientY } = event; - const selector = elementSelector ?? '[data-item-uuid]'; + const selector = '[data-item-uuid]'; const target = event.target.closest(selector) ?? event.currentTarget.closest(selector); target?.dispatchEvent( new PointerEvent('contextmenu', { diff --git a/module/data/scene/scene.mjs b/module/data/scene/scene.mjs index 3ff74f9f..f2a24308 100644 --- a/module/data/scene/scene.mjs +++ b/module/data/scene/scene.mjs @@ -1,6 +1,6 @@ import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; -/* Foundry does not add any system data for subtyped Scenes. The data model is therefore mainly for reference until this changes. +/* Foundry does not add any system data for subtyped Scenes. The data model is therefore used by instantiating a new instance of it for sceneConfigSettings.mjs. Needed dataprep and lifetime hooks are handled in documents/scene. */ export default class DHScene extends foundry.abstract.DataModel { From ec0b0df02ed72ba37e63f23286bfc81d501896fa Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 3 Jan 2026 12:13:43 +0100 Subject: [PATCH 6/7] . --- module/applications/scene/sceneConfigSettings.mjs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/module/applications/scene/sceneConfigSettings.mjs b/module/applications/scene/sceneConfigSettings.mjs index 6a093699..6d0d787b 100644 --- a/module/applications/scene/sceneConfigSettings.mjs +++ b/module/applications/scene/sceneConfigSettings.mjs @@ -98,11 +98,9 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S /** @override */ async _processSubmitData(event, form, submitData, options) { submitData.flags.daggerheart = this.daggerheartFlag.toObject(); - if (this.document._source.flags.daggerheart) { - for (const key of Object.keys(this.document._source.flags.daggerheart.sceneEnvironments)) { - if (!submitData.flags.daggerheart.sceneEnvironments[key]) { - submitData.flags.daggerheart.sceneEnvironments[`-=${key}`] = null; - } + for (const key of Object.keys(this.document._source.flags.daggerheart?.sceneEnvironments ?? {})) { + if (!submitData.flags.daggerheart.sceneEnvironments[key]) { + submitData.flags.daggerheart.sceneEnvironments[`-=${key}`] = null; } } From 503aa712e6f1929c275aa962c59660ff39dabb17 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 10 Jan 2026 17:32:29 +0100 Subject: [PATCH 7/7] Fixed so that users can access sceneEnvironments if they have permissions --- module/applications/ui/sceneNavigation.mjs | 19 +++++++++++++++---- module/applications/ux/contextMenu.mjs | 4 ++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/module/applications/ui/sceneNavigation.mjs b/module/applications/ui/sceneNavigation.mjs index 735685d1..ac16ac99 100644 --- a/module/applications/ui/sceneNavigation.mjs +++ b/module/applications/ui/sceneNavigation.mjs @@ -1,3 +1,5 @@ +import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs'; + export default class DhSceneNavigation extends foundry.applications.ui.SceneNavigation { /** @inheritdoc */ static DEFAULT_OPTIONS = { @@ -26,7 +28,9 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi if (!scene.flags.daggerheart) return x; const daggerheartInfo = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart); - const environments = daggerheartInfo.sceneEnvironments.filter(x => x); + const environments = daggerheartInfo.sceneEnvironments.filter( + x => x && x.testUserPermission(game.user, 'LIMITED') + ); const hasEnvironments = environments.length > 0; return { ...x, @@ -43,12 +47,14 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi static async #openSceneEnvironment(event, button) { const scene = game.scenes.get(button.dataset.sceneId); - const sceneEnvironments = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart).sceneEnvironments; + const sceneEnvironments = new game.system.api.data.scenes.DHScene( + scene.flags.daggerheart + ).sceneEnvironments.filter(x => x.testUserPermission(game.user, 'LIMITED')); if (sceneEnvironments.length === 1 || event.shiftKey) { sceneEnvironments[0].sheet.render(true); } else { - new ContextMenu( + new foundry.applications.ux.ContextMenu.implementation( button, '.scene-environment', sceneEnvironments.map(environment => ({ @@ -60,7 +66,12 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi newEnvironments.findIndex(x => x === environment.uuid) )[0]; newEnvironments.unshift(newFirst); - scene.update({ 'flags.daggerheart.sceneEnvironments': newEnvironments }); + emitAsGM( + GMUpdateEvent.UpdateDocument, + scene.update.bind(scene), + { 'flags.daggerheart.sceneEnvironments': newEnvironments }, + scene.uuid + ); } environment.sheet.render({ force: true }); diff --git a/module/applications/ux/contextMenu.mjs b/module/applications/ux/contextMenu.mjs index 09454848..081e6ba0 100644 --- a/module/applications/ux/contextMenu.mjs +++ b/module/applications/ux/contextMenu.mjs @@ -96,11 +96,11 @@ export default class DHContextMenu extends foundry.applications.ux.ContextMenu { * Trigger a context menu event in response to a normal click on a additional options button. * @param {PointerEvent} event */ - static triggerContextMenu(event) { + static triggerContextMenu(event, altSelector) { event.preventDefault(); event.stopPropagation(); const { clientX, clientY } = event; - const selector = '[data-item-uuid]'; + const selector = altSelector ?? '[data-item-uuid]'; const target = event.target.closest(selector) ?? event.currentTarget.closest(selector); target?.dispatchEvent( new PointerEvent('contextmenu', {