diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 80c01bce..b9099005 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,12 +44,14 @@ We encourage contributors to leave comments or open Discussions when proposing s ## 🧾 Issue & PR Best Practices **For Issues:** + - Use clear, descriptive titles - Provide a concise explanation of the problem or idea - Include reproduction steps or example scenarios if it's a bug - Add screenshots or logs if helpful **For Pull Requests:** + - Use a clear title summarizing the change - Provide a brief description of what your code does and why - Link to any related Issues @@ -71,6 +73,6 @@ Discussions are currently happening on GitHub β€” in Issues, PRs, and [GitHub Di ## πŸ€— Thank You! -Whether you're fixing a typo or designing entire mechanics β€” every contribution matters. Thank you for helping bring *Daggerheart* to life in FoundryVTT through **Foundryborne**! +Whether you're fixing a typo or designing entire mechanics β€” every contribution matters. Thank you for helping bring _Daggerheart_ to life in FoundryVTT through **Foundryborne**! πŸΈπŸ› οΈ diff --git a/daggerheart.mjs b/daggerheart.mjs index 1c4c2a85..f5f5e303 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -133,6 +133,8 @@ Hooks.once('init', () => { CONFIG.ui.combat = applications.ui.DhCombatTracker; CONFIG.ui.chat = applications.ui.DhChatLog; CONFIG.ui.hotbar = applications.ui.DhHotbar; + CONFIG.ui.sidebar = applications.sidebar.DhSidebar; + CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu; CONFIG.Token.rulerClass = placeables.DhTokenRuler; CONFIG.ui.resources = applications.ui.DhFearTracker; @@ -162,7 +164,7 @@ Hooks.on('ready', async () => { if (game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).displayFear !== 'hide') ui.resources.render({ force: true }); - if(!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser)) + if (!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser)) ui.compendiumBrowser = new applications.ui.ItemBrowser(); registerCountdownHooks(); @@ -309,5 +311,5 @@ Hooks.on('moveToken', async (movedToken, data) => { } }); -Hooks.on("renderCompendiumDirectory", (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); -Hooks.on("renderDocumentDirectory", (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); +Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); +Hooks.on('renderDocumentDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); diff --git a/lang/en.json b/lang/en.json index d5c4f037..8de962bd 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2420,6 +2420,10 @@ "heal": "Heal", "applyHealing": "Apply Healing" }, + "refreshMessage": { + "title": "Feature Refresh", + "header": "Refreshed" + }, "reroll": { "confirmTitle": "Reroll Dice", "confirmText": "Are you sure you want to reroll?" @@ -2534,8 +2538,16 @@ "multiclassAlreadyPresent": "You already have a class and multiclass", "subclassesAlreadyPresent": "You already have a class and multiclass subclass", "noDiceSystem": "Your selected dice {system} does not have a {faces} dice", + "gmMenuRefresh": "You refreshed all actions and resources {types}", "subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class." }, + "Sidebar": { + "daggerheartMenu": { + "title": "Daggerheart Menu", + "startSession": "Start Session", + "startScene": "Start Scene" + } + }, "Tooltip": { "disableEffect": "Disable Effect", "enableEffect": "Enable Effect", diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index 3dd0c78f..b2d3001a 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -6,5 +6,6 @@ export * as scene from './scene/_module.mjs'; export * as settings from './settings/_module.mjs'; export * as sheets from './sheets/_module.mjs'; export * as sheetConfigs from './sheets-configs/_module.mjs'; +export * as sidebar from './sidebar/_module.mjs'; export * as ui from './ui/_module.mjs'; export * as ux from './ux/_module.mjs'; diff --git a/module/applications/levelup/levelup.mjs b/module/applications/levelup/levelup.mjs index 99cc53f6..c3cc6e81 100644 --- a/module/applications/levelup/levelup.mjs +++ b/module/applications/levelup/levelup.mjs @@ -536,7 +536,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) static async viewCompendium(event, target) { const type = target.dataset.compendium ?? target.dataset.type; - const presets = { + const presets = { folder: type, render: { noFolder: true diff --git a/module/applications/scene/_module.mjs b/module/applications/scene/_module.mjs index 6dc59081..7cefd268 100644 --- a/module/applications/scene/_module.mjs +++ b/module/applications/scene/_module.mjs @@ -1 +1 @@ -export { default as DhSceneConfigSettings } from './sceneConfigSettings.mjs'; \ No newline at end of file +export { default as DhSceneConfigSettings } from './sceneConfigSettings.mjs'; diff --git a/module/applications/scene/sceneConfigSettings.mjs b/module/applications/scene/sceneConfigSettings.mjs index 2ad7421a..40f66ae2 100644 --- a/module/applications/scene/sceneConfigSettings.mjs +++ b/module/applications/scene/sceneConfigSettings.mjs @@ -1,25 +1,24 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.SceneConfig { - constructor(options, ...args) { - super(options, ...args); - } - - static buildParts() { - const { footer, ...parts } = super.PARTS; - const tmpParts = { - ...parts, - dh: { template: "systems/daggerheart/templates/scene/dh-config.hbs" }, - footer + constructor(options, ...args) { + super(options, ...args); } - return tmpParts; - } - static PARTS = DhSceneConfigSettings.buildParts(); + static buildParts() { + const { footer, ...parts } = super.PARTS; + const tmpParts = { + ...parts, + dh: { template: 'systems/daggerheart/templates/scene/dh-config.hbs' }, + footer + }; + return tmpParts; + } - static buildTabs() { - super.TABS.sheet.tabs.push({ id: "dh", icon: "fa-solid" }); - return super.TABS; - } + static PARTS = DhSceneConfigSettings.buildParts(); - static TABS = DhSceneConfigSettings.buildTabs(); + static buildTabs() { + super.TABS.sheet.tabs.push({ id: 'dh', icon: 'fa-solid' }); + return super.TABS; + } -} \ No newline at end of file + static TABS = DhSceneConfigSettings.buildTabs(); +} diff --git a/module/applications/settings/appearanceSettings.mjs b/module/applications/settings/appearanceSettings.mjs index bbc27a79..5950f961 100644 --- a/module/applications/settings/appearanceSettings.mjs +++ b/module/applications/settings/appearanceSettings.mjs @@ -33,7 +33,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, main: { template: 'systems/daggerheart/templates/settings/appearance-settings/main.hbs' }, diceSoNice: { template: 'systems/daggerheart/templates/settings/appearance-settings/diceSoNice.hbs' }, - footer: { template: "templates/generic/form-footer.hbs" } + footer: { template: 'templates/generic/form-footer.hbs' } }; /** @inheritdoc */ @@ -41,7 +41,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App general: { tabs: [ { id: 'main', label: 'DAGGERHEART.GENERAL.Tabs.general' }, - { id: 'diceSoNice', label: 'DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.title' }, + { id: 'diceSoNice', label: 'DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.title' } ], initial: 'main' }, @@ -73,7 +73,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App /** @inheritdoc */ _configureRenderParts(options) { const parts = super._configureRenderParts(options); - if (!game.modules.get('dice-so-nice')?.active){ + if (!game.modules.get('dice-so-nice')?.active) { delete parts.diceSoNice; delete parts.tabs; } @@ -83,7 +83,8 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App /**@inheritdoc */ async _prepareContext(options) { const context = await super._prepareContext(options); - if (options.isFirstRender) this.setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance); + if (options.isFirstRender) + this.setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance); context.setting = this.setting; context.fields = this.setting.schema.fields; @@ -99,18 +100,17 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App const partContext = await super._preparePartContext(partId, context, options); if (partId in context.tabs) partContext.tab = partContext.tabs[partId]; switch (partId) { - case "diceSoNice": + case 'diceSoNice': await this.prepareDiceSoNiceContext(partContext); break; - case "footer": + case 'footer': partContext.buttons = [ - { type: "button", action: "reset", icon: "fa-solid fa-arrow-rotate-left", label: "Reset" }, - { type: "submit", icon: "fa-solid fa-floppy-disk", label: "Save Changes" } + { type: 'button', action: 'reset', icon: 'fa-solid fa-arrow-rotate-left', label: 'Reset' }, + { type: 'submit', icon: 'fa-solid fa-floppy-disk', label: 'Save Changes' } ]; break; } return partContext; - } /** @@ -120,32 +120,44 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App * @protected */ async prepareDiceSoNiceContext(context) { - context.diceSoNiceTextures = Object.entries(game.dice3d.exports.TEXTURELIST).reduce((acc, [k, v]) => ({ - ...acc, - [k]: v.name - }), {}); - context.diceSoNiceColorsets = Object.values(game.dice3d.exports.COLORSETS).reduce((acc, v) => ({ - ...acc, - [v.id]: v.description - }), {}); - context.diceSoNiceMaterials = Object.keys(game.dice3d.DiceFactory.material_options).reduce((acc, key) => ({ - ...acc, - [key]: `DICESONICE.Material${key.capitalize()}` - }), {}); - context.diceSoNiceSystems = Object.fromEntries([...game.dice3d.DiceFactory.systems].map(([k, v]) => [k, v.name])); + context.diceSoNiceTextures = Object.entries(game.dice3d.exports.TEXTURELIST).reduce( + (acc, [k, v]) => ({ + ...acc, + [k]: v.name + }), + {} + ); + context.diceSoNiceColorsets = Object.values(game.dice3d.exports.COLORSETS).reduce( + (acc, v) => ({ + ...acc, + [v.id]: v.description + }), + {} + ); + context.diceSoNiceMaterials = Object.keys(game.dice3d.DiceFactory.material_options).reduce( + (acc, key) => ({ + ...acc, + [key]: `DICESONICE.Material${key.capitalize()}` + }), + {} + ); + context.diceSoNiceSystems = Object.fromEntries( + [...game.dice3d.DiceFactory.systems].map(([k, v]) => [k, v.name]) + ); - foundry.utils.mergeObject(context.dsnTabs, [ - "hope", - "fear", - "advantage", - "disadvantage", - ].reduce((acc, key) => ({ - ...acc, - [key]: { - values: this.setting.diceSoNice[key], - fields: this.setting.schema.getField(`diceSoNice.${key}`).fields, - } - }), {})); + foundry.utils.mergeObject( + context.dsnTabs, + ['hope', 'fear', 'advantage', 'disadvantage'].reduce( + (acc, key) => ({ + ...acc, + [key]: { + values: this.setting.diceSoNice[key], + fields: this.setting.schema.getField(`diceSoNice.${key}`).fields + } + }), + {} + ) + ); } /** @@ -169,7 +181,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App * @type {ApplicationClickAction} */ static async #onPreview(_, target) { - const formData = new foundry.applications.ux.FormDataExtended(target.closest("form")); + const formData = new foundry.applications.ux.FormDataExtended(target.closest('form')); const { diceSoNice } = foundry.utils.expandObject(formData.object); const { key } = target.dataset; const faces = ['advantage', 'disadvantage'].includes(key) ? 'd6' : 'd12'; @@ -181,10 +193,10 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App } /** - * Reset the form back to default values. - * @this {DHAppearanceSettings} - * @type {ApplicationClickAction} - */ + * Reset the form back to default values. + * @this {DHAppearanceSettings} + * @type {ApplicationClickAction} + */ static async #onReset() { this.setting = new this.setting.constructor(); this.render({ force: false }); diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 2158e48b..1a508131 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -416,11 +416,14 @@ export default function DHApplicationMixin(Base) { icon: 'fa-solid fa-ban', condition: target => { const doc = getDocFromElementSync(target); - return doc && doc.system?.actions?.some(a => a.type === "beastform"); + return doc && doc.system?.actions?.some(a => a.type === 'beastform'); }, - callback: async target => game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(await getDocFromElement(target)) + callback: async target => + game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call( + await getDocFromElement(target) + ) }); - + options.unshift({ name: 'DAGGERHEART.GENERAL.damage', icon: 'fa-solid fa-explosion', @@ -433,7 +436,7 @@ export default function DHApplicationMixin(Base) { action = doc?.system?.attack ?? doc; const config = action.prepareConfig(event); config.hasRoll = false; - return action && action.workflow.get("damage").execute(config, null, true); + return action && action.workflow.get('damage').execute(config, null, true); } }); diff --git a/module/applications/sidebar/_module.mjs b/module/applications/sidebar/_module.mjs new file mode 100644 index 00000000..f19f697c --- /dev/null +++ b/module/applications/sidebar/_module.mjs @@ -0,0 +1,2 @@ +export { default as DaggerheartMenu } from './tabs/daggerheartMenu.mjs'; +export { default as DhSidebar } from './sidebar.mjs'; diff --git a/module/applications/sidebar/sidebar.mjs b/module/applications/sidebar/sidebar.mjs new file mode 100644 index 00000000..fad39ac5 --- /dev/null +++ b/module/applications/sidebar/sidebar.mjs @@ -0,0 +1,33 @@ +export default class DhSidebar extends Sidebar { + /** @override */ + static TABS = { + ...super.TABS, + daggerheartMenu: { + tooltip: 'DAGGERHEART.UI.Sidebar.daggerheartMenu.title', + img: 'systems/daggerheart/assets/logos/FoundryBorneLogoWhite.svg' + } + }; + + /** @override */ + static PARTS = { + tabs: { + id: 'tabs', + template: 'systems/daggerheart/templates/sidebar/tabs.hbs' + } + }; + + /** @override */ + async _prepareTabContext(context, options) { + context.tabs = Object.entries(this.constructor.TABS).reduce((obj, [k, v]) => { + let { documentName, gmOnly, tooltip, icon, img } = v; + if (gmOnly && !game.user.isGM) return obj; + if (documentName) { + tooltip ??= getDocumentClass(documentName).metadata.labelPlural; + icon ??= CONFIG[documentName]?.sidebarIcon; + } + obj[k] = { tooltip, icon, img }; + obj[k].active = this.tabGroups.primary === k; + return obj; + }, {}); + } +} diff --git a/module/applications/sidebar/tabs/daggerheartMenu.mjs b/module/applications/sidebar/tabs/daggerheartMenu.mjs new file mode 100644 index 00000000..cf7aeae3 --- /dev/null +++ b/module/applications/sidebar/tabs/daggerheartMenu.mjs @@ -0,0 +1,160 @@ +const { HandlebarsApplicationMixin } = foundry.applications.api; +const { AbstractSidebarTab } = foundry.applications.sidebar; +/** + * The daggerheart menu tab. + * @extends {AbstractSidebarTab} + * @mixes HandlebarsApplication + */ +export default class DaggerheartMenu extends HandlebarsApplicationMixin(AbstractSidebarTab) { + constructor(options) { + super(options); + + this.refreshSelections = DaggerheartMenu.#defaultRefreshSelections(); + } + + static #defaultRefreshSelections() { + return { + session: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.session') }, + scene: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.scene') }, + longRest: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.longrest') }, + shortRest: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.shortrest') } + }; + } + + /** @override */ + static DEFAULT_OPTIONS = { + classes: ['dh-style'], + window: { + title: 'SIDEBAR.TabSettings' + }, + actions: { + selectRefreshable: DaggerheartMenu.#selectRefreshable, + refreshActors: DaggerheartMenu.#refreshActors + } + }; + + /** @override */ + static tabName = 'daggerheartMenu'; + + /** @override */ + static PARTS = { + main: { template: 'systems/daggerheart/templates/sidebar/daggerheart-menu/main.hbs' } + }; + + /* -------------------------------------------- */ + + /** @inheritDoc */ + async _prepareContext(options) { + const context = await super._prepareContext(options); + context.refreshables = this.refreshSelections; + context.disableRefresh = Object.values(this.refreshSelections).every(x => !x.selected); + + return context; + } + + async getRefreshables(types) { + const refreshedActors = {}; + for (let actor of game.actors) { + if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) { + const updates = {}; + for (let item of actor.items) { + if (item.system.metadata.hasResource && types.includes(item.system.resource?.recovery)) { + if (!refreshedActors[actor.id]) + refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() }; + refreshedActors[actor.id].refreshed.add( + game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[item.system.resource.recovery].label) + ); + + if (!updates[item.id]?.system) updates[item.id] = { system: {} }; + + const increasing = + item.system.resource.progression === CONFIG.DH.ITEM.itemResourceProgression.increasing.id; + updates[item.id].system = { + ...updates[item.id].system, + 'resource.value': increasing + ? 0 + : Roll.replaceFormulaData(item.system.resource.max, actor.getRollData()) + }; + } + if (item.system.metadata.hasActions) { + const refreshTypes = new Set(); + const actions = item.system.actions.filter(action => { + if (types.includes(action.uses.recovery)) { + refreshTypes.add(action.uses.recovery); + return true; + } + + return false; + }); + if (actions.length === 0) continue; + + if (!refreshedActors[actor.id]) + refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() }; + refreshedActors[actor.id].refreshed.add( + ...refreshTypes.map(type => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[type].label)) + ); + + if (!updates[item.id]?.system) updates[item.id] = { system: {} }; + + updates[item.id].system = { + ...updates[item.id].system, + ...actions.reduce( + (acc, action) => { + acc.actions[action.id] = { 'uses.value': 0 }; + return acc; + }, + { actions: updates[item.id].system.actions ?? {} } + ) + }; + } + } + + for (let key in updates) { + const update = updates[key]; + await actor.items.get(key).update(update); + } + } + } + + return refreshedActors; + } + + /* -------------------------------------------- */ + /* Application Clicks Actions */ + /* -------------------------------------------- */ + + static async #selectRefreshable(_event, button) { + const { type } = button.dataset; + this.refreshSelections[type].selected = !this.refreshSelections[type].selected; + this.render(); + } + + static async #refreshActors() { + const refreshKeys = Object.keys(this.refreshSelections).filter(key => this.refreshSelections[key].selected); + await this.getRefreshables(refreshKeys); + const types = refreshKeys.map(x => this.refreshSelections[x].label).join(', '); + ui.notifications.info( + game.i18n.format('DAGGERHEART.UI.Notifications.gmMenuRefresh', { + types: `[${types}]` + }) + ); + this.refreshSelections = DaggerheartMenu.#defaultRefreshSelections(); + + const cls = getDocumentClass('ChatMessage'); + const msg = { + user: game.user.id, + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/ui/chat/refreshMessage.hbs', + { + types: types + } + ), + title: game.i18n.localize('DAGGERHEART.UI.Chat.refreshMessage.title'), + speaker: cls.getSpeaker() + }; + + cls.create(msg); + + this.render(); + } +} diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 21762818..b95e50e1 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -112,7 +112,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo if (event.currentTarget.dataset.directDamage) { const config = action.prepareConfig(event); config.hasRoll = false; - action.workflow.get("damage").execute(config, null, true); + action.workflow.get('damage').execute(config, null, true); } else action.use(event); } diff --git a/module/applications/ui/fearTracker.mjs b/module/applications/ui/fearTracker.mjs index a346aa66..2b7c4dac 100644 --- a/module/applications/ui/fearTracker.mjs +++ b/module/applications/ui/fearTracker.mjs @@ -1,4 +1,4 @@ -import { emitAsGM, GMUpdateEvent, socketEvent } from "../../systemRegistration/socket.mjs"; +import { emitAsGM, GMUpdateEvent, socketEvent } from '../../systemRegistration/socket.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -106,6 +106,10 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV } async updateFear(value) { - return emitAsGM(GMUpdateEvent.UpdateFear, game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), value); + return emitAsGM( + GMUpdateEvent.UpdateFear, + game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), + value + ); } } diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index 40884817..a00f8edc 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -84,12 +84,10 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { /** @inheritDoc */ async _preRender(context, options) { this.presets = options.presets ?? {}; - + const width = this.presets?.render?.noFolder === true || this.presets?.render?.lite === true ? 600 : 850; - if(this.rendered) - this.setPosition({ width }); - else - options.position.width = width; + if (this.rendered) this.setPosition({ width }); + else options.position.width = width; await super._preRender(context, options); } @@ -100,15 +98,18 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { this.element .querySelectorAll('[data-action="selectFolder"]') - .forEach(element => element.classList.toggle('is-selected', element.dataset.folderId === this.selectedMenu.path.join('.'))); + .forEach(element => + element.classList.toggle('is-selected', element.dataset.folderId === this.selectedMenu.path.join('.')) + ); this._createSearchFilter(); - + this.element.classList.toggle('lite', this.presets?.render?.lite === true); this.element.classList.toggle('no-folder', this.presets?.render?.noFolder === true); this.element.classList.toggle('no-filter', this.presets?.render?.noFilter === true); this.element.querySelectorAll('.folder-list > [data-action="selectFolder"]').forEach(element => { - element.hidden = this.presets.render?.folders?.length && !this.presets.render.folders.includes(element.dataset.folderId); + element.hidden = + this.presets.render?.folders?.length && !this.presets.render.folders.includes(element.dataset.folderId); }); } @@ -157,7 +158,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { : []; folders.push(folder); }); - folders.sort((a, b) => a.label.localeCompare(b.label)) + folders.sort((a, b) => a.label.localeCompare(b.label)); return folders; } @@ -181,8 +182,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { await this.render({ force: true, presets: this.presets }); - if(this.selectedMenu?.data?.type?.length) - this.loadItems(); + if (this.selectedMenu?.data?.type?.length) this.loadItems(); } _replaceHTML(result, content, options) { @@ -194,53 +194,54 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { let loadTimeout = this.toggleLoader(true); const promises = []; - + game.packs.forEach(pack => { promises.push( new Promise(async resolve => { const items = await pack.getDocuments({ type__in: this.selectedMenu?.data?.type }); resolve(items); }) - ) + ); }); - + Promise.all(promises).then(async result => { - this.items = ItemBrowser.sortBy(result.flatMap(r => r), 'name'); + this.items = ItemBrowser.sortBy( + result.flatMap(r => r), + 'name' + ); this.fieldFilter = this._createFieldFilter(); if (this.presets?.filter) { - Object.entries(this.presets.filter).forEach( - ([k, v]) => { - const filter = this.fieldFilter.find(c => c.name === k) - if(filter) filter.value = v.value; - } - ); + Object.entries(this.presets.filter).forEach(([k, v]) => { + const filter = this.fieldFilter.find(c => c.name === k); + if (filter) filter.value = v.value; + }); // await this._onInputFilterBrowser(); } - - const filterList = await foundry.applications.handlebars.renderTemplate('systems/daggerheart/templates/ui/itemBrowser/filterContainer.hbs', + + const filterList = await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/ui/itemBrowser/filterContainer.hbs', { fieldFilter: this.fieldFilter, presets: this.presets, formatChoices: this.formatChoices } ); - + this.element.querySelector('.filter-content .wrapper').innerHTML = filterList; const filterContainer = this.element.querySelector('.filter-header > [data-action="expandContent"]'); - if(this.fieldFilter.length === 0) - filterContainer.setAttribute('disabled', ''); - else - filterContainer.removeAttribute('disabled'); - - const itemList = await foundry.applications.handlebars.renderTemplate('systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs', + if (this.fieldFilter.length === 0) filterContainer.setAttribute('disabled', ''); + else filterContainer.removeAttribute('disabled'); + + const itemList = await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs', { items: this.items, menu: this.selectedMenu, formatLabel: this.formatLabel } ); - + this.element.querySelector('.item-list').innerHTML = itemList; this._createFilterInputs(); @@ -255,7 +256,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { toggleLoader(state) { const container = this.element.querySelector('.item-list'); return setTimeout(() => { - container.classList.toggle("loader", state); + container.classList.toggle('loader', state); }, 100); } @@ -376,7 +377,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { for (const li of html.querySelectorAll('.item-container')) { const itemUUID = li.dataset.itemUuid, item = this.items.find(i => i.uuid === itemUUID); - if(!item) continue; + if (!item) continue; const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name); if (matchesSearch) this.#filteredItems.browser.search.add(item.id); const { input } = this.#filteredItems.browser; @@ -399,7 +400,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { item = this.items.find(i => i.uuid === itemUUID); if (!item) continue; - + const matchesMenu = this.fieldFilter.length === 0 || this.fieldFilter.every( @@ -503,19 +504,19 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { _canDragStart() { return true; } - + static injectSidebarButton(html) { - if(!game.user.isGM) return; + if (!game.user.isGM) return; const sectionId = html.dataset.tab, - menus = { + menus = { actors: { - folder: "adversaries", + folder: 'adversaries', render: { - folders: ["adversaries", "characters", "environments"] + folders: ['adversaries', 'characters', 'environments'] } }, items: { - folder: "equipments", + folder: 'equipments', render: { noFolder: true } @@ -523,20 +524,20 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { compendium: {} }; - if(Object.keys(menus).includes(sectionId)) { - const headerActions = html.querySelector(".header-actions"); + if (Object.keys(menus).includes(sectionId)) { + const headerActions = html.querySelector('.header-actions'); - const button = document.createElement("button"); - button.type = "button"; - button.classList.add("open-compendium-browser"); + const button = document.createElement('button'); + button.type = 'button'; + button.classList.add('open-compendium-browser'); button.innerHTML = ` - ${game.i18n.localize("DAGGERHEART.UI.Tooltip.compendiumBrowser")} + ${game.i18n.localize('DAGGERHEART.UI.Tooltip.compendiumBrowser')} `; - button.addEventListener("click", event => { + button.addEventListener('click', event => { ui.compendiumBrowser.open(menus[sectionId]); }); - + headerActions.append(button); } } diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index 046efd8b..e870172f 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -199,8 +199,7 @@ export const typeConfig = { key: 'system.itemFeatures', label: 'DAGGERHEART.GENERAL.features', choices: () => - Object.entries(CONFIG.DH.ITEM.weaponFeatures) - .map(([k, v]) => ({ value: k, label: v.label })), + Object.entries(CONFIG.DH.ITEM.weaponFeatures).map(([k, v]) => ({ value: k, label: v.label })), operator: 'contains3' } ] @@ -241,8 +240,7 @@ export const typeConfig = { key: 'system.itemFeatures', label: 'DAGGERHEART.GENERAL.features', choices: () => - Object.entries(CONFIG.DH.ITEM.armorFeatures) - .map(([k, v]) => ({ value: k, label: v.label })), + Object.entries(CONFIG.DH.ITEM.armorFeatures).map(([k, v]) => ({ value: k, label: v.label })), operator: 'contains3' } ] @@ -372,14 +370,30 @@ export const typeConfig = { label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait' } ], + filters: [] + }, + beastforms: { + columns: [ + { + key: 'system.tier', + label: 'DAGGERHEART.GENERAL.Tiers.singular' + }, + { + key: 'system.mainTrait', + label: 'DAGGERHEART.GENERAL.Trait.single' + } + ], filters: [ { key: 'system.linkedClass.uuid', label: 'Class', - choices: (items) => { - const list = items.map(item => ({ value: item.system.linkedClass.uuid, label: item.system.linkedClass.name })); - return list.reduce((a,c) => { - if(!(a.find(i => i.value === c.value))) a.push(c); + choices: items => { + const list = items.map(item => ({ + value: item.system.linkedClass.uuid, + label: item.system.linkedClass.name + })); + return list.reduce((a, c) => { + if (!a.find(i => i.value === c.value)) a.push(c); return a; }, []); } @@ -417,7 +431,7 @@ export const compendiumConfig = { id: 'characters', keys: ['characters'], label: 'DAGGERHEART.UI.ItemBrowser.folders.characters', - type: ['character'], + type: ['character'] // listType: 'characters' }, adversaries: { @@ -431,7 +445,7 @@ export const compendiumConfig = { id: 'ancestries', keys: ['ancestries'], label: 'DAGGERHEART.UI.ItemBrowser.folders.ancestries', - type: ['ancestry'], + type: ['ancestry'] /* folders: { features: { id: 'features', @@ -516,7 +530,7 @@ export const compendiumConfig = { id: 'communities', keys: ['communities'], label: 'DAGGERHEART.UI.ItemBrowser.folders.communities', - type: ['community'], + type: ['community'] /* folders: { features: { id: 'features', @@ -537,7 +551,7 @@ export const compendiumConfig = { keys: ['beastforms'], label: 'DAGGERHEART.UI.ItemBrowser.folders.beastforms', type: ['beastform'], - listType: 'beastforms', + listType: 'beastforms' /* folders: { features: { id: 'features', diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index 0f148aeb..5232cbd9 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -31,20 +31,20 @@ export const gameSettings = { }; export const actionAutomationChoices = { - never: { - id: "never", - label: "Never" + never: { + id: 'never', + label: 'Never' }, showDialog: { - id: "showDialog", - label: "Show Dialog only" + id: 'showDialog', + label: 'Show Dialog only' }, // npcOnly: { - // id: "npcOnly", - // label: "Always for non-characters" + // id: "npcOnly", + // label: "Always for non-characters" // }, always: { - id: "always", - label: "Always" + id: 'always', + label: 'Always' } -} +}; diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 6e522ceb..93d34700 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -34,8 +34,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel this.extraSchemas.forEach(s => { let clsField = this.getActionField(s); - if (clsField) - schemaFields[s] = new clsField(); + if (clsField) schemaFields[s] = new clsField(); }); return schemaFields; @@ -43,7 +42,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel /** * Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property. - * + * * Each step can be called individually as long as needed config is provided. * Ex: .workflow.get("damage").execute(config) * @returns {Map} @@ -53,8 +52,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel this.constructor.extraSchemas.forEach(s => { let clsField = this.constructor.getActionField(s); if (clsField?.execute) { - workflow.set(s, { order: clsField.order, execute: clsField.execute.bind(this) } ); - if( s === "damage" ) workflow.set("applyDamage", { order: 75, execute: clsField.applyDamage.bind(this) } ); + workflow.set(s, { order: clsField.order, execute: clsField.execute.bind(this) }); + if (s === 'damage') + workflow.set('applyDamage', { order: 75, execute: clsField.applyDamage.bind(this) }); } }); return new Map([...workflow.entries()].sort(([aKey, aValue], [bKey, bValue]) => aValue.order - bValue.order)); @@ -64,9 +64,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel * Getter returning the workflow property or creating it the first time the property is called */ get workflow() { - if ( this.hasOwnProperty("_workflow") ) return this._workflow; + if (this.hasOwnProperty('_workflow')) return this._workflow; const workflow = Object.freeze(this.defineWorkflow()); - Object.defineProperty(this, "_workflow", {value: workflow, writable: false}); + Object.defineProperty(this, '_workflow', { value: workflow, writable: false }); return workflow; } @@ -117,7 +117,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel /** * Prepare base data based on Action Type & Parent Type - * @param {object} parent + * @param {object} parent * @returns {object} */ static getSourceConfig(parent) { @@ -167,9 +167,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel * @param {object} config Config object usually created from prepareConfig method */ async executeWorkflow(config) { - for(const [key, part] of this.workflow) { + for (const [key, part] of this.workflow) { if (Hooks.call(`${CONFIG.DH.id}.pre${key.capitalize()}Action`, this, config) === false) return; - if(await part.execute(config) === false) return; + if ((await part.execute(config)) === false) return; if (Hooks.call(`${CONFIG.DH.id}.post${key.capitalize()}Action`, this, config) === false) return; } } @@ -185,7 +185,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel if (this.chatDisplay) await this.toChat(); let config = this.prepareConfig(event); - if(!config) return; + if (!config) return; if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return; @@ -194,7 +194,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel config = await D20RollDialog.configure(null, config); if (!config) return; } - + // Execute the Action Worflow in order based of schema fields await this.executeWorkflow(config); @@ -206,7 +206,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel /** * Create the basic config common to every action type * @param {Event} event Event from the button used to trigger the Action - * @returns {object} + * @returns {object} */ prepareBaseConfig(event) { const config = { @@ -236,13 +236,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel /** * Create the config for that action used for its workflow * @param {Event} event Event from the button used to trigger the Action - * @returns {object} + * @returns {object} */ prepareConfig(event) { const config = this.prepareBaseConfig(event); - for(const clsField of Object.values(this.schema.fields)) { - if (clsField?.prepareConfig) - if(clsField.prepareConfig.call(this, config) === false) return false; + for (const clsField of Object.values(this.schema.fields)) { + if (clsField?.prepareConfig) if (clsField.prepareConfig.call(this, config) === false) return false; } return config; } @@ -260,11 +259,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel * Consume Action configured resources & uses. * That method is only used when we want those resources to be consumed outside of the use method workflow. * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. - * @param {boolean} successCost + * @param {boolean} successCost */ async consume(config, successCost = false) { - await this.workflow.get("cost")?.execute(config, successCost); - await this.workflow.get("uses")?.execute(config, successCost); + await this.workflow.get('cost')?.execute(config, successCost); + await this.workflow.get('uses')?.execute(config, successCost); if (config.roll && !config.roll.success && successCost) { setTimeout(() => { @@ -291,13 +290,13 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel } get hasDamage() { - return this.damage?.parts?.length && this.type !== 'healing' + return this.damage?.parts?.length && this.type !== 'healing'; } get hasHealing() { - return this.damage?.parts?.length && this.type === 'healing' + return this.damage?.parts?.length && this.type === 'healing'; } - + get hasSave() { return !!this.save?.trait; } diff --git a/module/data/chat-message/_modules.mjs b/module/data/chat-message/_modules.mjs index 36c6ee9d..7e301906 100644 --- a/module/data/chat-message/_modules.mjs +++ b/module/data/chat-message/_modules.mjs @@ -1,9 +1,9 @@ -import DHAbilityUse from "./abilityUse.mjs"; -import DHActorRoll from "./actorRoll.mjs"; +import DHAbilityUse from './abilityUse.mjs'; +import DHActorRoll from './actorRoll.mjs'; export const config = { - abilityUse: DHAbilityUse, - adversaryRoll: DHActorRoll, - damageRoll: DHActorRoll, - dualityRoll: DHActorRoll + abilityUse: DHAbilityUse, + adversaryRoll: DHActorRoll, + damageRoll: DHActorRoll, + dualityRoll: DHActorRoll }; diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs index 91f44edc..a2cb03f9 100644 --- a/module/data/chat-message/actorRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -58,10 +58,8 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { const actionActor = this.actionActor, actionItem = this.actionItem; if (!this.source.action) return null; - if(actionItem) - return actionItem.system.actionsList?.find(a => a.id === this.source.action); - else if(actionActor?.system.attack?._id === this.source.action) - return actionActor.system.attack + if (actionItem) return actionItem.system.actionsList?.find(a => a.id === this.source.action); + else if (actionActor?.system.attack?._id === this.source.action) return actionActor.system.attack; return null; } diff --git a/module/data/fields/action/beastformField.mjs b/module/data/fields/action/beastformField.mjs index 62c735d0..0a1caea9 100644 --- a/module/data/fields/action/beastformField.mjs +++ b/module/data/fields/action/beastformField.mjs @@ -1,4 +1,4 @@ -import BeastformDialog from "../../../applications/dialogs/beastformDialog.mjs"; +import BeastformDialog from '../../../applications/dialogs/beastformDialog.mjs'; const fields = foundry.data.fields; @@ -49,14 +49,14 @@ export default class BeastformField extends fields.SchemaField { return await BeastformField.transform.call(this, selected, evolved, hybrid); } - + /** * Update Action Workflow config object. * Must be called within Action context. * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. */ prepareConfig(config) { - if(this.actor.effects.find(x => x.type === 'beastform')) { + if (this.actor.effects.find(x => x.type === 'beastform')) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.beastformAlreadyApplied')); return false; } @@ -73,10 +73,10 @@ export default class BeastformField extends fields.SchemaField { /** * TODO by Harry - * @param {*} selectedForm - * @param {*} evolvedData - * @param {*} hybridData - * @returns + * @param {*} selectedForm + * @param {*} evolvedData + * @param {*} hybridData + * @returns */ static async transform(selectedForm, evolvedData, hybridData) { const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm.toObject(); diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 816d9cbc..bab46d52 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -30,8 +30,13 @@ export default class DamageField extends fields.SchemaField { * @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example. */ static async execute(config, messageId = null, force = false) { - if(!this.hasDamage && !this.hasHealing) return; - if((this.hasRoll && DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id) && !force) return; + if (!this.hasDamage && !this.hasHealing) return; + if ( + this.hasRoll && + DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id && + !force + ) + return; let formulas = this.damage.parts.map(p => ({ formula: DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor), @@ -51,9 +56,10 @@ export default class DamageField extends fields.SchemaField { }; delete damageConfig.evaluate; - if(DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) damageConfig.dialog.configure = false; + if (DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) + damageConfig.dialog.configure = false; if (config.hasSave) config.onSave = damageConfig.onSave = this.save.damageMod; - + damageConfig.source.message = config.message?._id ?? messageId; damageConfig.directDamage = !!damageConfig.source?.message; @@ -61,7 +67,7 @@ export default class DamageField extends fields.SchemaField { // await game.dice3d.waitFor3DAnimationByMessageID(damageConfig.source.message); const damageResult = await CONFIG.Dice.daggerheart.DamageRoll.build(damageConfig); - if(!damageResult) return false; + if (!damageResult) return false; config.damage = damageResult.damage; config.message ??= damageConfig.message; } @@ -70,19 +76,15 @@ export default class DamageField extends fields.SchemaField { * Apply Damage/Healing Action Worflow part. * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. * @param {*[]} targets Arrays of targets to bypass pre-selected ones. - * @param {boolean} force If the method should be executed outside of Action workflow, for ChatMessage button for example. + * @param {boolean} force If the method should be executed outside of Action workflow, for ChatMessage button for example. */ static async applyDamage(config, targets = null, force = false) { targets ??= config.targets.filter(target => target.hit); - if(!config.damage || !targets?.length || (!DamageField.getApplyAutomation() && !force)) return; + if (!config.damage || !targets?.length || (!DamageField.getApplyAutomation() && !force)) return; for (let target of targets) { const actor = fromUuidSync(target.actorId); - if(!actor) continue; - if ( - !config.hasHealing && - config.onSave && - target.saved?.success === true - ) { + if (!actor) continue; + if (!config.hasHealing && config.onSave && target.saved?.success === true) { const mod = CONFIG.DH.ACTIONS.damageOnSave[config.onSave]?.mod ?? 1; Object.entries(config.damage).forEach(([k, v]) => { v.total = 0; @@ -97,17 +99,17 @@ export default class DamageField extends fields.SchemaField { else actor.takeDamage(config.damage, config.isDirect); } } - + /** * Return value or valueAlt from damage part * Must be called within Action context or similar. - * @param {object} part Damage Part + * @param {object} part Damage Part * @param {object} data Action getRollData * @returns Formula value object */ static getFormulaValue(part, data) { let formulaValue = part.value; - + if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt; const isAdversary = this.actor.type === 'adversary'; @@ -123,8 +125,8 @@ export default class DamageField extends fields.SchemaField { * Prepare formulas for Damage Roll * Must be called within Action context or similar. * @param {object[]} formulas Array of formatted formulas object - * @param {object} data Action getRollData - * @returns + * @param {object} data Action getRollData + * @returns */ static formatFormulas(formulas, data) { const formattedFormulas = []; @@ -145,16 +147,25 @@ export default class DamageField extends fields.SchemaField { * @returns {string} Id from settingsConfig.mjs actionAutomationChoices */ static getAutomation() { - return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.players) + return ( + (game.user.isGM && + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.gm) || + (!game.user.isGM && + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.players) + ); } - /** * Return the automation setting for applyDamage method for current user role * @returns {boolean} If applyDamage should be triggered automatically */ static getApplyAutomation() { - return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.players) + return ( + (game.user.isGM && + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.gm) || + (!game.user.isGM && + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.players) + ); } } diff --git a/module/data/fields/action/effectsField.mjs b/module/data/fields/action/effectsField.mjs index 3b8c5e43..0f205d72 100644 --- a/module/data/fields/action/effectsField.mjs +++ b/module/data/fields/action/effectsField.mjs @@ -1,4 +1,4 @@ -import { emitAsGM, GMUpdateEvent } from "../../../systemRegistration/socket.mjs"; +import { emitAsGM, GMUpdateEvent } from '../../../systemRegistration/socket.mjs'; const fields = foundry.data.fields; @@ -25,21 +25,16 @@ export default class EffectsField extends fields.ArrayField { * @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example. */ static async execute(config, targets = null, force = false) { - if(!config.hasEffect) return; + if (!config.hasEffect) return; let message = config.message ?? ui.chat.collection.get(config.parent?._id); - if(!message) { + if (!message) { const roll = new CONFIG.Dice.daggerheart.DHRoll(''); roll._evaluated = true; message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config); } - if(EffectsField.getAutomation() || force) { + if (EffectsField.getAutomation() || force) { targets ??= (message.system?.targets ?? config.targets).filter(t => !config.hasRoll || t.hit); - await emitAsGM( - GMUpdateEvent.UpdateEffect, - EffectsField.applyEffects.bind(this), - targets, - this.uuid - ); + await emitAsGM(GMUpdateEvent.UpdateEffect, EffectsField.applyEffects.bind(this), targets, this.uuid); // EffectsField.applyEffects.call(this, config.targets.filter(t => !config.hasRoll || t.hit)); } } @@ -53,8 +48,7 @@ export default class EffectsField extends fields.ArrayField { if (!this.effects?.length || !targets?.length) return; let effects = this.effects; targets.forEach(async token => { - if (this.hasSave && token.saved.success === true) - effects = this.effects.filter(e => e.onSave === true); + if (this.hasSave && token.saved.success === true) effects = this.effects.filter(e => e.onSave === true); if (!effects.length) return; effects.forEach(async e => { const actor = canvas.tokens.get(token.id)?.actor, @@ -96,6 +90,11 @@ export default class EffectsField extends fields.ArrayField { * @returns {boolean} If execute should be triggered automatically */ static getAutomation() { - return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.players) + return ( + (game.user.isGM && + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.gm) || + (!game.user.isGM && + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.players) + ); } } diff --git a/module/data/fields/action/macroField.mjs b/module/data/fields/action/macroField.mjs index 222feb2a..e37a2852 100644 --- a/module/data/fields/action/macroField.mjs +++ b/module/data/fields/action/macroField.mjs @@ -8,7 +8,7 @@ export default class MacroField extends fields.DocumentUUIDField { /** @inheritDoc */ constructor(context = {}) { - super({ type: "Macro" }, context); + super({ type: 'Macro' }, context); } /** diff --git a/module/data/fields/action/rangeField.mjs b/module/data/fields/action/rangeField.mjs index 1237e507..d0bceada 100644 --- a/module/data/fields/action/rangeField.mjs +++ b/module/data/fields/action/rangeField.mjs @@ -1,14 +1,13 @@ const fields = foundry.data.fields; export default class RangeField extends fields.StringField { - /** @inheritDoc */ constructor(context = {}) { const options = { choices: CONFIG.DH.GENERAL.range, required: false, blank: true, - label: "DAGGERHEART.GENERAL.range" + label: 'DAGGERHEART.GENERAL.range' }; super(options, context); } diff --git a/module/data/fields/action/rollField.mjs b/module/data/fields/action/rollField.mjs index cfcfb56b..98a1d5ed 100644 --- a/module/data/fields/action/rollField.mjs +++ b/module/data/fields/action/rollField.mjs @@ -5,7 +5,12 @@ export class DHActionRollData extends foundry.abstract.DataModel { static defineSchema() { return { type: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.GENERAL.rollTypes }), - trait: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.ACTOR.abilities, label: "DAGGERHEART.GENERAL.Trait.single" }), + trait: new fields.StringField({ + nullable: true, + initial: null, + choices: CONFIG.DH.ACTOR.abilities, + label: 'DAGGERHEART.GENERAL.Trait.single' + }), difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }), bonus: new fields.NumberField({ nullable: true, initial: null, integer: true }), advState: new fields.StringField({ @@ -86,14 +91,14 @@ export class DHActionRollData extends foundry.abstract.DataModel { } get rollTrait() { - if(this.parent?.actor?.type !== "character") return null; + if (this.parent?.actor?.type !== 'character') return null; switch (this.type) { case CONFIG.DH.GENERAL.rollTypes.spellcast.id: return this.parent.actor?.system?.spellcastModifierTrait?.key ?? 'agility'; case CONFIG.DH.GENERAL.rollTypes.attack.id: case CONFIG.DH.GENERAL.rollTypes.trait.id: return this.useDefault || !this.trait - ? this.parent.item.system.attack?.roll?.trait ?? 'agility' + ? (this.parent.item.system.attack?.roll?.trait ?? 'agility') : this.trait; default: return null; @@ -118,21 +123,21 @@ export default class RollField extends fields.EmbeddedDataField { * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. */ static async execute(config) { - if(!config.hasRoll) return; + if (!config.hasRoll) return; config = await this.actor.diceRoll(config); - if(!config) return false; + if (!config) return false; } - + /** * Update Action Workflow config object. * Must be called within Action context. * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. */ prepareConfig(config) { - if(!config.hasRoll) return; + if (!config.hasRoll) return; config.dialog.configure = RollField.getAutomation() ? !config.dialog.configure : config.dialog.configure; - + const roll = { baseModifiers: this.roll.getModifier(), label: 'Attack', @@ -152,6 +157,11 @@ export default class RollField extends fields.EmbeddedDataField { * @returns {boolean} If execute should be triggered automatically */ static getAutomation() { - return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.players) + return ( + (game.user.isGM && + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.gm) || + (!game.user.isGM && + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.players) + ); } } diff --git a/module/data/fields/action/saveField.mjs b/module/data/fields/action/saveField.mjs index 0003a4d5..b7021135 100644 --- a/module/data/fields/action/saveField.mjs +++ b/module/data/fields/action/saveField.mjs @@ -1,4 +1,4 @@ -import { abilities } from "../../../config/actorConfig.mjs"; +import { abilities } from '../../../config/actorConfig.mjs'; const fields = foundry.data.fields; @@ -33,15 +33,15 @@ export default class SaveField extends fields.SchemaField { * @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example. */ static async execute(config, targets = null, force = false) { - if(!config.hasSave) return; + if (!config.hasSave) return; let message = config.message ?? ui.chat.collection.get(config.parent?._id); - - if(!message) { + + if (!message) { const roll = new CONFIG.Dice.daggerheart.DHRoll(''); roll._evaluated = true; message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config); } - if(SaveField.getAutomation() !== CONFIG.DH.SETTINGS.actionAutomationChoices.never.id || force) { + if (SaveField.getAutomation() !== CONFIG.DH.SETTINGS.actionAutomationChoices.never.id || force) { targets ??= config.targets.filter(t => !config.hasRoll || t.hit); await SaveField.rollAllSave.call(this, targets, config.event, message); } else return false; @@ -52,35 +52,35 @@ export default class SaveField extends fields.SchemaField { * Must be called within Action context. * @param {object[]} targets Array of formatted targets. * @param {Event} event Triggering event - * @param {ChatMessage} message The ChatMessage the triggered button comes from. + * @param {ChatMessage} message The ChatMessage the triggered button comes from. */ static async rollAllSave(targets, event, message) { - if(!targets) return; + if (!targets) return; return new Promise(resolve => { const aPromise = []; targets.forEach(target => { aPromise.push( new Promise(async subResolve => { const actor = fromUuidSync(target.actorId); - if(actor) { - const rollSave = game.user === actor.owner ? - SaveField.rollSave.call(this, actor, event) - : actor.owner - .query('reactionRoll', { - actionId: this.uuid, - actorId: actor.uuid, - event, - message - }); + if (actor) { + const rollSave = + game.user === actor.owner + ? SaveField.rollSave.call(this, actor, event) + : actor.owner.query('reactionRoll', { + actionId: this.uuid, + actorId: actor.uuid, + event, + message + }); const result = await rollSave; await SaveField.updateSaveMessage.call(this, result, message, target.id); subResolve(); } else subResolve(); }) - ) + ); }); Promise.all(aPromise).then(result => resolve()); - }) + }); } /** @@ -93,10 +93,10 @@ export default class SaveField extends fields.SchemaField { static async rollSave(actor, event) { if (!actor) return; const title = actor.isNPC - ? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll') - : game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: game.i18n.localize(abilities[this.save.trait]?.label) - }), + ? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll') + : game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { + ability: game.i18n.localize(abilities[this.save.trait]?.label) + }), rollConfig = { event, title, @@ -109,7 +109,8 @@ export default class SaveField extends fields.SchemaField { hasRoll: true, data: actor.getRollData() }; - if(SaveField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) rollConfig.dialog = { configure: false }; + if (SaveField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) + rollConfig.dialog = { configure: false }; return actor.diceRoll(rollConfig); } @@ -117,31 +118,32 @@ export default class SaveField extends fields.SchemaField { * Update a Roll ChatMessage for a token according to his Reaction Roll result. * @param {object} result Result from the Reaction Roll * @param {object} message ChatMessage to update - * @param {string} targetId Token ID + * @param {string} targetId Token ID */ static async updateSaveMessage(result, message, targetId) { if (!result) return; - const updateMsg = async function(message, targetId, result) { + const updateMsg = async function (message, targetId, result) { // setTimeout(async () => { - const chatMessage = ui.chat.collection.get(message._id), - changes = { - flags: { - [game.system.id]: { - reactionRolls: { - [targetId]: - { - result: result.roll.total, - success: result.roll.success - } + const chatMessage = ui.chat.collection.get(message._id), + changes = { + flags: { + [game.system.id]: { + reactionRolls: { + [targetId]: { + result: result.roll.total, + success: result.roll.success } } } - }; - await chatMessage.update(changes); + } + }; + await chatMessage.update(changes); // }, 100); }; if (game.modules.get('dice-so-nice')?.active) - game.dice3d.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id).then(async () => await updateMsg(message, targetId, result)); + game.dice3d + .waitFor3DAnimationByMessageID(result.message.id ?? result.message._id) + .then(async () => await updateMsg(message, targetId, result)); else await updateMsg(message, targetId, result); } @@ -150,25 +152,29 @@ export default class SaveField extends fields.SchemaField { * @returns {string} Id from settingsConfig.mjs actionAutomationChoices */ static getAutomation() { - return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.players) + return ( + (game.user.isGM && + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.gm) || + (!game.user.isGM && + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.players) + ); } /** * Send a query to an Actor owner to roll a Reaction Roll then send back the result. - * @param {object} param0 - * @param {string} param0.actionId Action ID - * @param {string} param0.actorId Actor ID - * @param {Event} param0.event Triggering event - * @param {ChatMessage} param0.message Chat Message to update - * @returns + * @param {object} param0 + * @param {string} param0.actionId Action ID + * @param {string} param0.actorId Actor ID + * @param {Event} param0.event Triggering event + * @param {ChatMessage} param0.message Chat Message to update + * @returns */ static rollSaveQuery({ actionId, actorId, event, message }) { return new Promise(async (resolve, reject) => { const actor = await fromUuid(actorId), action = await fromUuid(actionId); if (!actor || !actor?.isOwner) reject(); - SaveField.rollSave.call(action, actor, event, message) - .then(result => resolve(result)); + SaveField.rollSave.call(action, actor, event, message).then(result => resolve(result)); }); } } diff --git a/module/data/fields/action/targetField.mjs b/module/data/fields/action/targetField.mjs index 4499dcc8..486a81f1 100644 --- a/module/data/fields/action/targetField.mjs +++ b/module/data/fields/action/targetField.mjs @@ -1,7 +1,6 @@ const fields = foundry.data.fields; export default class TargetField extends fields.SchemaField { - /** @inheritDoc */ constructor(options = {}, context = {}) { const targetFields = { @@ -21,7 +20,7 @@ export default class TargetField extends fields.SchemaField { * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. */ prepareConfig(config) { - if (!this.target?.type) return config.targets = []; + if (!this.target?.type) return (config.targets = []); config.hasTarget = true; let targets; // If the Action is configured as self-targeted, set targets as the owner. @@ -62,15 +61,11 @@ export default class TargetField extends fields.SchemaField { * @returns {boolean} If both actors respect the provided type. */ static isTargetFriendly(actor, target, type) { - const actorDisposition = actor.token - ? actor.token.disposition - : actor.prototypeToken.disposition, + const actorDisposition = actor.token ? actor.token.disposition : actor.prototypeToken.disposition, targetDisposition = target.document.disposition; return ( - (type === CONFIG.DH.GENERAL.targetTypes.friendly.id && - actorDisposition === targetDisposition) || - (type === CONFIG.DH.GENERAL.targetTypes.hostile.id && - actorDisposition + targetDisposition === 0) + (type === CONFIG.DH.GENERAL.targetTypes.friendly.id && actorDisposition === targetDisposition) || + (type === CONFIG.DH.GENERAL.targetTypes.hostile.id && actorDisposition + targetDisposition === 0) ); } diff --git a/module/data/fields/action/usesField.mjs b/module/data/fields/action/usesField.mjs index d180ddf8..d1f3ebff 100644 --- a/module/data/fields/action/usesField.mjs +++ b/module/data/fields/action/usesField.mjs @@ -7,7 +7,7 @@ export default class UsesField extends fields.SchemaField { * Action Workflow order */ static order = 160; - + /** @inheritDoc */ constructor(options = {}, context = {}) { const usesFields = { @@ -62,7 +62,7 @@ export default class UsesField extends fields.SchemaField { /** * Prepare Uses object for Action Workflow * Must be called within Action context. - * @param {object} uses + * @param {object} uses * @returns {object} */ static calcUses(uses) { @@ -77,7 +77,7 @@ export default class UsesField extends fields.SchemaField { /** * Check if the Action still get atleast one unspent uses. * Must be called within Action context. - * @param {*} uses + * @param {*} uses * @returns {boolean} */ static hasUses(uses) { diff --git a/module/data/settings/Appearance.mjs b/module/data/settings/Appearance.mjs index 4c6e01ea..dfdd17e2 100644 --- a/module/data/settings/Appearance.mjs +++ b/module/data/settings/Appearance.mjs @@ -1,26 +1,27 @@ export default class DhAppearance extends foundry.abstract.DataModel { - static LOCALIZATION_PREFIXES = ["DAGGERHEART.SETTINGS.Appearance"]; + static LOCALIZATION_PREFIXES = ['DAGGERHEART.SETTINGS.Appearance']; static defineSchema() { const { StringField, ColorField, BooleanField, SchemaField } = foundry.data.fields; // helper to create dice style schema - const diceStyle = ({ fg, bg, outline, edge }) => new SchemaField({ - foreground: new ColorField({ required: true, initial: fg }), - background: new ColorField({ required: true, initial: bg }), - outline: new ColorField({ required: true, initial: outline }), - edge: new ColorField({ required: true, initial: edge }), - texture: new StringField({ initial: 'astralsea', required: true, blank: false }), - colorset: new StringField({ initial: 'inspired', required: true, blank: false }), - material: new StringField({ initial: 'metal', required: true, blank: false }), - system: new StringField({ initial: 'standard', required: true, blank: false }) - }); + const diceStyle = ({ fg, bg, outline, edge }) => + new SchemaField({ + foreground: new ColorField({ required: true, initial: fg }), + background: new ColorField({ required: true, initial: bg }), + outline: new ColorField({ required: true, initial: outline }), + edge: new ColorField({ required: true, initial: edge }), + texture: new StringField({ initial: 'astralsea', required: true, blank: false }), + colorset: new StringField({ initial: 'inspired', required: true, blank: false }), + material: new StringField({ initial: 'metal', required: true, blank: false }), + system: new StringField({ initial: 'standard', required: true, blank: false }) + }); return { displayFear: new StringField({ required: true, choices: CONFIG.DH.GENERAL.fearDisplay, - initial: CONFIG.DH.GENERAL.fearDisplay.token.value, + initial: CONFIG.DH.GENERAL.fearDisplay.token.value }), diceSoNice: new SchemaField({ hope: diceStyle({ fg: '#ffffff', bg: '#ffe760', outline: '#000000', edge: '#ffffff' }), @@ -39,7 +40,7 @@ export default class DhAppearance extends foundry.abstract.DataModel { target: new BooleanField() }), hideAttribution: new BooleanField(), - showGenericStatusEffects: new BooleanField({ initial: true }), + showGenericStatusEffects: new BooleanField({ initial: true }) }; } } diff --git a/module/data/settings/Automation.mjs b/module/data/settings/Automation.mjs index da71e899..beefac0b 100644 --- a/module/data/settings/Automation.mjs +++ b/module/data/settings/Automation.mjs @@ -97,13 +97,13 @@ export default class DhAutomation extends foundry.abstract.DataModel { damage: new fields.SchemaField({ gm: new fields.StringField({ required: true, - initial: "never", + initial: 'never', choices: CONFIG.DH.SETTINGS.actionAutomationChoices, label: 'DAGGERHEART.GENERAL.gm' }), players: new fields.StringField({ required: true, - initial: "never", + initial: 'never', choices: CONFIG.DH.SETTINGS.actionAutomationChoices, label: 'DAGGERHEART.GENERAL.player.plurial' }) @@ -111,13 +111,13 @@ export default class DhAutomation extends foundry.abstract.DataModel { save: new fields.SchemaField({ gm: new fields.StringField({ required: true, - initial: "never", + initial: 'never', choices: CONFIG.DH.SETTINGS.actionAutomationChoices, label: 'DAGGERHEART.GENERAL.gm' }), players: new fields.StringField({ required: true, - initial: "never", + initial: 'never', choices: CONFIG.DH.SETTINGS.actionAutomationChoices, label: 'DAGGERHEART.GENERAL.player.plurial' }) diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 4d293d9d..534867f8 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -47,8 +47,7 @@ export default class DamageRoll extends DHRoll { ); } await super.buildPost(roll, config, message); - if (config.source?.message) - chatMessage.update({ 'system.damage': config.damage }); + if (config.source?.message) chatMessage.update({ 'system.damage': config.damage }); } static unifyDamageRoll(rolls) { diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 0dcdd316..3865710a 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -28,7 +28,7 @@ export default class DHRoll extends Roll { static async buildConfigure(config = {}, message = {}) { config.hooks = [...this.getHooks(), '']; config.dialog ??= {}; - + for (const hook of config.hooks) { if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null; } @@ -46,7 +46,10 @@ export default class DHRoll extends Roll { } for (const hook of config.hooks) { - if (Hooks.call(`${CONFIG.DH.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false) return []; + if ( + Hooks.call(`${CONFIG.DH.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false + ) + return []; } return roll; } @@ -91,7 +94,7 @@ export default class DHRoll extends Roll { system: config, rolls: [roll] }; - + config.selectedRollMode ??= game.settings.get('core', 'rollMode'); if (roll._evaluated) { diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 93ac231e..8fedc368 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -153,10 +153,13 @@ export default class DualityRoll extends D20Roll { applyBaseBonus() { const modifiers = super.applyBaseBonus(); - + if (this.options.roll.trait && this.data.traits?.[this.options.roll.trait]) modifiers.unshift({ - label: this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id ? "DAGGERHEART.CONFIG.RollTypes.spellcast.name" : `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`, + label: + this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id + ? 'DAGGERHEART.CONFIG.RollTypes.spellcast.name' + : `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`, value: this.data.traits[this.options.roll.trait].value }); diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 1a619a9c..d7476395 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -1,4 +1,4 @@ -import { emitAsGM, GMUpdateEvent } from "../systemRegistration/socket.mjs"; +import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs'; export default class DhpChatMessage extends foundry.documents.ChatMessage { targetHook = null; @@ -104,11 +104,11 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { }); if (itemDesc && autoExpandRoll.desc) itemDesc.setAttribute('open', ''); } - - if(!this.isAuthor && !this.speakerActor?.isOwner) { - const applyButtons = html.querySelector(".apply-buttons"); + + if (!this.isAuthor && !this.speakerActor?.isOwner) { + const applyButtons = html.querySelector('.apply-buttons'); applyButtons?.remove(); - const buttons = html.querySelectorAll(".ability-card-footer > .ability-use-button"); + const buttons = html.querySelectorAll('.ability-card-footer > .ability-use-button'); buttons.forEach(b => b.remove()); } } @@ -125,7 +125,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { html.querySelectorAll('.target-save').forEach(element => element.addEventListener('click', this.onRollSave.bind(this)) ); - + html.querySelectorAll('.roll-all-save-button').forEach(element => element.addEventListener('click', this.onRollAllSave.bind(this)) ); @@ -149,7 +149,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { event.stopPropagation(); const config = foundry.utils.deepClone(this.system); config.event = event; - this.system.action?.workflow.get("damage")?.execute(config, this._id, true); + this.system.action?.workflow.get('damage')?.execute(config, this._id, true); } async onApplyDamage(event) { @@ -171,9 +171,9 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { if (targets.length === 0) return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm')); - + this.consumeOnSuccess(); - this.system.action?.workflow.get("applyDamage")?.execute(config, targets, true); + this.system.action?.workflow.get('applyDamage')?.execute(config, targets, true); } async onRollSave(event) { @@ -187,7 +187,12 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { game.system.api.fields.ActionFields.SaveField.rollSave.call(action, token.actor, event).then(result => emitAsGM( GMUpdateEvent.UpdateSaveMessage, - game.system.api.fields.ActionFields.SaveField.updateSaveMessage.bind(action, result, this, token.id), + game.system.api.fields.ActionFields.SaveField.updateSaveMessage.bind( + action, + result, + this, + token.id + ), { action: action.uuid, message: this._id, @@ -205,7 +210,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { const targets = this.system.hitTargets, config = foundry.utils.deepClone(this.system); config.event = event; - this.system.action?.workflow.get("save")?.execute(config, targets, true); + this.system.action?.workflow.get('save')?.execute(config, targets, true); } async onApplyEffect(event) { @@ -216,16 +221,15 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { if (targets.length === 0) ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm')); this.consumeOnSuccess(); - this.system.action?.workflow.get("effects")?.execute(config, targets, true); + this.system.action?.workflow.get('effects')?.execute(config, targets, true); } filterPermTargets(targets) { - return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, "update")) + return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, 'update')); } consumeOnSuccess() { - if (!this.system.successConsumed && !this.targetSelection) - this.system.action?.consume(this.system, true); + if (!this.system.successConsumed && !this.targetSelection) this.system.action?.consume(this.system, true); } hoverTarget(event) { diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 8492f068..33daf52a 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -32,7 +32,7 @@ export default class DHItem extends foundry.documents.Item { /** @inheritDoc */ static migrateData(source) { - if(source.system?.attack && !source.system.attack.type) source.system.attack.type = "attack"; + if (source.system?.attack && !source.system.attack.type) source.system.attack.type = 'attack'; return super.migrateData(source); } diff --git a/module/helpers/handlebarsHelper.mjs b/module/helpers/handlebarsHelper.mjs index 83220307..e6c1a2f0 100644 --- a/module/helpers/handlebarsHelper.mjs +++ b/module/helpers/handlebarsHelper.mjs @@ -76,10 +76,10 @@ export default class RegisterHandlebarsHelpers { /** * Pluralize helper that returns the appropriate localized string based on count - * @param {number} count - The number to check for plurality + * @param {number} count - The number to check for plurality * @param {string} baseKey - The base localization key (e.g., "DAGGERHEART.GENERAL.Target") * @returns {string} The localized singular or plural string - * + * * Usage: {{pluralize currentTargets.length "DAGGERHEART.GENERAL.Target"}} * Returns: "Target" if count is exactly 1, "Targets" if count is 0, 2+, or invalid */ diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index fd569499..24047827 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -34,10 +34,7 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/ui/chat/parts/damage-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/target-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/button-part.hbs', - 'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs', - - 'systems/daggerheart/templates/scene/dh-config.hbs', - + 'systems/daggerheart/templates/scene/dh-config.hbs' ]); }; diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs index f3f9629b..f75c7b36 100644 --- a/module/systemRegistration/socket.mjs +++ b/module/systemRegistration/socket.mjs @@ -38,8 +38,7 @@ export const registerSocketHooks = () => { const document = data.uuid ? await fromUuid(data.uuid) : null; switch (data.action) { case GMUpdateEvent.UpdateDocument: - if (document && data.update) - await document.update(data.update); + if (document && data.update) await document.update(data.update); break; case GMUpdateEvent.UpdateEffect: if (document && data.update) diff --git a/pull_request_template.md b/pull_request_template.md index c1b8cbfa..263eb5b8 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,10 +1,11 @@ --- name: Pull Request about: Create a new pull request -title: "[PR] " +title: '[PR] ' labels: pr assignees: '' --- + Is this a community PR? Please go to preview tab and click [here](?expand=1&template=community_pull_request_template.md). If not, delete this line. ## Description diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index e4a2128c..65e825a9 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -603,7 +603,7 @@ display: flex; justify-content: space-between; align-items: center; - gap: .25rem .5rem; + gap: 0.25rem 0.5rem; flex-wrap: wrap; label { @@ -620,7 +620,7 @@ &.setting-two-values { display: grid; grid-template-columns: repeat(3, 1fr); - gap: .25rem .5rem; + gap: 0.25rem 0.5rem; .form-group { justify-content: end; diff --git a/styles/less/global/global.less b/styles/less/global/global.less index 4c06d42b..7e60dffc 100644 --- a/styles/less/global/global.less +++ b/styles/less/global/global.less @@ -29,11 +29,11 @@ overflow: hidden !important; div { - opacity: .5; + opacity: 0.5; } &:before { - font-family: "Font Awesome 6 Pro"; + font-family: 'Font Awesome 6 Pro'; content: '\f110'; position: absolute; height: 100%; @@ -41,11 +41,13 @@ display: flex; align-items: center; justify-content: center; - animation: spinner 1.5s linear infinite; + animation: spinner 1.5s linear infinite; } } @keyframes spinner { - to { transform: rotate(360deg); } + to { + transform: rotate(360deg); + } } -} \ No newline at end of file +} diff --git a/styles/less/global/sheet.less b/styles/less/global/sheet.less index 08e2668f..6f77a481 100755 --- a/styles/less/global/sheet.less +++ b/styles/less/global/sheet.less @@ -15,7 +15,7 @@ body.game:is(.performance-low, .noblur) { .themed.theme-dark.application.daggerheart.sheet.dh-style, &.theme-dark .application.daggerheart { background: @dark-blue; - }; + } } .application.sheet.dh-style { diff --git a/styles/less/sheets/actors/environment/potentialAdversaries.less b/styles/less/sheets/actors/environment/potentialAdversaries.less index 6fd7af65..274362a5 100644 --- a/styles/less/sheets/actors/environment/potentialAdversaries.less +++ b/styles/less/sheets/actors/environment/potentialAdversaries.less @@ -14,4 +14,4 @@ scrollbar-color: light-dark(@dark-blue, @golden) transparent; } } -} \ No newline at end of file +} diff --git a/styles/less/ui/chat/refresh-message.less b/styles/less/ui/chat/refresh-message.less new file mode 100644 index 00000000..2fce189b --- /dev/null +++ b/styles/less/ui/chat/refresh-message.less @@ -0,0 +1,13 @@ +.daggerheart.chat.refresh-message { + header { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + + .subtitle { + font-size: 18; + font-weight: bold; + } + } +} diff --git a/styles/less/ui/index.less b/styles/less/ui/index.less index 49d1e009..8b0c53f6 100644 --- a/styles/less/ui/index.less +++ b/styles/less/ui/index.less @@ -2,6 +2,7 @@ @import './chat/action.less'; @import './chat/chat.less'; @import './chat/downtime.less'; +@import './chat/refresh-message.less'; @import './chat/sheet.less'; @import './combat-sidebar/combat-sidebar.less'; @@ -19,6 +20,8 @@ @import './resources/resources.less'; @import './settings/settings.less'; - @import './settings/homebrew-settings/domains.less'; @import './settings/homebrew-settings/types.less'; + +@import './sidebar/tabs.less'; +@import './sidebar/daggerheartMenu.less'; diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less index 5df0482a..23844128 100644 --- a/styles/less/ui/item-browser/item-browser.less +++ b/styles/less/ui/item-browser/item-browser.less @@ -395,7 +395,7 @@ text-align: center; font-weight: bold; } - + .hint { flex: unset; } @@ -409,7 +409,8 @@ &.lite, &.no-folder { - .compendium-sidebar, .menu-path { + .compendium-sidebar, + .menu-path { display: none; } } diff --git a/styles/less/ui/settings/settings.less b/styles/less/ui/settings/settings.less index 67fe7718..788db394 100644 --- a/styles/less/ui/settings/settings.less +++ b/styles/less/ui/settings/settings.less @@ -4,7 +4,7 @@ fieldset { display: flex; flex-direction: column; - gap: .5rem; + gap: 0.5rem; &.two-columns { display: grid; @@ -127,4 +127,4 @@ text-align: center; } } -} \ No newline at end of file +} diff --git a/styles/less/ui/sidebar/daggerheartMenu.less b/styles/less/ui/sidebar/daggerheartMenu.less new file mode 100644 index 00000000..e975954c --- /dev/null +++ b/styles/less/ui/sidebar/daggerheartMenu.less @@ -0,0 +1,38 @@ +.tab.sidebar-tab.daggerheartMenu-sidebar { + padding: 0 4px; + + .menu-refresh-container { + display: flex; + flex-direction: column; + gap: 8px; + + .menu-refresh-inner-container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + + .experience-chip { + display: flex; + align-items: center; + border-radius: 5px; + width: fit-content; + gap: 5px; + cursor: pointer; + padding: 5px; + background: light-dark(@dark-blue-10, @golden-10); + color: light-dark(@dark-blue, @golden); + + .label { + font-style: normal; + font-weight: 400; + font-size: 14px; + line-height: 17px; + } + + &.selected { + background: light-dark(@dark-blue-40, @golden-40); + } + } + } + } +} diff --git a/styles/less/ui/sidebar/tabs.less b/styles/less/ui/sidebar/tabs.less new file mode 100644 index 00000000..073d3ef3 --- /dev/null +++ b/styles/less/ui/sidebar/tabs.less @@ -0,0 +1,8 @@ +#interface #ui-right #sidebar { + menu li button { + img { + width: 22px; + max-width: unset; + } + } +} diff --git a/templates/sidebar/daggerheart-menu/main.hbs b/templates/sidebar/daggerheart-menu/main.hbs new file mode 100644 index 00000000..6f31f165 --- /dev/null +++ b/templates/sidebar/daggerheart-menu/main.hbs @@ -0,0 +1,22 @@ +
+
+ {{localize "Refresh Features"}} + + +
+
\ No newline at end of file diff --git a/templates/sidebar/tabs.hbs b/templates/sidebar/tabs.hbs new file mode 100644 index 00000000..9063ac5d --- /dev/null +++ b/templates/sidebar/tabs.hbs @@ -0,0 +1,18 @@ + diff --git a/templates/ui/chat/refreshMessage.hbs b/templates/ui/chat/refreshMessage.hbs new file mode 100644 index 00000000..a29baa20 --- /dev/null +++ b/templates/ui/chat/refreshMessage.hbs @@ -0,0 +1,6 @@ +
+
+
{{localize "DAGGERHEART.UI.Chat.refreshMessage.header"}}
+
{{types}}
+
+
\ No newline at end of file