diff --git a/assets/icons/dice/duality/DualityBW.png b/assets/icons/dice/duality/DualityBW.png new file mode 100644 index 00000000..5f94e99f Binary files /dev/null and b/assets/icons/dice/duality/DualityBW.png differ diff --git a/assets/icons/dice/duality/DualityBW.svg b/assets/icons/dice/duality/DualityBW.svg new file mode 100644 index 00000000..9e716fd6 --- /dev/null +++ b/assets/icons/dice/duality/DualityBW.svg @@ -0,0 +1,59 @@ + + + + + + + diff --git a/lang/en.json b/lang/en.json index 26ba9bc2..a3b58680 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2030,6 +2030,7 @@ "range": "Range", "reactionRoll": "Reaction Roll", "recovery": "Recovery", + "refresh": "Refresh", "reroll": "Reroll", "rerollThing": "Reroll {thing}", "resource": "Resource", diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index 6c227152..c57dda12 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -16,7 +16,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.action = config.data.attack?._id == config.source.action ? config.data.attack - : this.item.system.actions.get(config.source.action); + : this.item.system.actionsList?.find(a => a.id === config.source.action); } } diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index bdd9fa68..dd845d34 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -99,6 +99,7 @@ export default function DHApplicationMixin(Base) { deleteDoc: DHSheetV2.#deleteDoc, toChat: DHSheetV2.#toChat, useItem: DHSheetV2.#useItem, + viewItem: DHSheetV2.#viewItem, toggleEffect: DHSheetV2.#toggleEffect, toggleExtended: DHSheetV2.#toggleExtended, addNewItem: DHSheetV2.#addNewItem, @@ -203,11 +204,19 @@ export default function DHApplicationMixin(Base) { this.relatedDocs.filter(doc => doc).map(doc => delete doc.apps[this.id]); } + /** @inheritdoc */ + async _renderHTML(context, options) { + const rendered = await super._renderHTML(context, options); + for (const result of Object.values(rendered)) { + await this.#prepareInventoryDescription(result); + } + return rendered; + } + /**@inheritdoc */ async _onRender(context, options) { await super._onRender(context, options); this._createTagifyElements(this.options.tagifyConfigs); - await this.#prepareInventoryDescription(context); } /* -------------------------------------------- */ @@ -215,8 +224,8 @@ export default function DHApplicationMixin(Base) { /* -------------------------------------------- */ /**@inheritdoc */ - _syncPartState(partId, newElement, priorElement, state) { - super._syncPartState(partId, newElement, priorElement, state); + _preSyncPartState(partId, newElement, priorElement, state) { + super._preSyncPartState(partId, newElement, priorElement, state); for (const el of priorElement.querySelectorAll('.extensible.extended')) { const { actionId, itemUuid } = el.parentElement.dataset; const selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`; @@ -496,11 +505,12 @@ export default function DHApplicationMixin(Base) { /** * Prepares and enriches an inventory item or action description for display. + * @param {HTMLElement} element the element to enrich the inventory items of * @returns {Promise} */ - async #prepareInventoryDescription(context) { + async #prepareInventoryDescription(element) { // Get all inventory item elements with a data-item-uuid attribute - const inventoryItems = this.element.querySelectorAll('.inventory-item[data-item-uuid]'); + const inventoryItems = element.querySelectorAll('.inventory-item[data-item-uuid]'); for (const el of inventoryItems) { // Get the doc uuid from the element const { itemUuid } = el?.dataset || {}; @@ -701,7 +711,7 @@ export default function DHApplicationMixin(Base) { * @type {ApplicationClickAction} */ static async #toChat(_event, target) { - let doc = await getDocFromElement(target); + const doc = await getDocFromElement(target); return doc.toChat(doc.uuid); } @@ -710,10 +720,19 @@ export default function DHApplicationMixin(Base) { * @type {ApplicationClickAction} */ static async #useItem(event, target) { - let doc = await getDocFromElement(target); + const doc = await getDocFromElement(target); await doc.use(event); } + /** + * View an item by opening its sheet + * @type {ApplicationClickAction} + */ + static async #viewItem(_, target) { + const doc = await getDocFromElement(target); + await doc.sheet.render({ force: true }); + } + /** * Toggle a ActiveEffect * @type {ApplicationClickAction} diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs index f719b6d1..21ccbbf1 100644 --- a/module/applications/sheets/api/base-item.mjs +++ b/module/applications/sheets/api/base-item.mjs @@ -66,7 +66,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { /**@inheritdoc */ async _prepareContext(options) { - const context = super._prepareContext(options); + const context = await super._prepareContext(options); context.showAttribution = !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance) .hideAttribution; diff --git a/module/applications/sidebar/sidebar.mjs b/module/applications/sidebar/sidebar.mjs index fad39ac5..ae28d56c 100644 --- a/module/applications/sidebar/sidebar.mjs +++ b/module/applications/sidebar/sidebar.mjs @@ -1,10 +1,49 @@ -export default class DhSidebar extends Sidebar { +export default class DhSidebar extends foundry.applications.sidebar.Sidebar { /** @override */ static TABS = { - ...super.TABS, + chat: { + documentName: 'ChatMessage' + }, + combat: { + documentName: 'Combat' + }, + scenes: { + documentName: 'Scene', + gmOnly: true + }, + actors: { + documentName: 'Actor' + }, + items: { + documentName: 'Item' + }, + journal: { + documentName: 'JournalEntry', + tooltip: 'SIDEBAR.TabJournal' + }, + tables: { + documentName: 'RollTable' + }, + cards: { + documentName: 'Cards' + }, + macros: { + documentName: 'Macro' + }, + playlists: { + documentName: 'Playlist' + }, + compendium: { + tooltip: 'SIDEBAR.TabCompendium', + icon: 'fa-solid fa-book-atlas' + }, daggerheartMenu: { tooltip: 'DAGGERHEART.UI.Sidebar.daggerheartMenu.title', img: 'systems/daggerheart/assets/logos/FoundryBorneLogoWhite.svg' + }, + settings: { + tooltip: 'SIDEBAR.TabSettings', + icon: 'fa-solid fa-gears' } }; diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index a00f8edc..4f3053bb 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -16,6 +16,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { this.selectedMenu = { path: [], data: null }; this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig; this.presets = {}; + this.compendiumBrowserTypeKey = 'compendiumBrowserDefault'; } /** @inheritDoc */ @@ -84,10 +85,25 @@ 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; + const noFolder = this.presets?.render?.noFolder; + if (noFolder === true) { + this.compendiumBrowserTypeKey = 'compendiumBrowserNoFolder'; + } + const lite = this.presets?.render?.lite; + if (lite === true) { + this.compendiumBrowserTypeKey = 'compendiumBrowserLite'; + } + const userPresetPosition = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.compendiumBrowserTypeKey}`].position) ; + + options.position = userPresetPosition ?? ItemBrowser.DEFAULT_OPTIONS.position; + + if (!userPresetPosition) { + const width = noFolder === true || lite === true ? 600 : 850; + if (this.rendered) + this.setPosition({ width }); + else + options.position.width = width; + } await super._preRender(context, options); } @@ -113,6 +129,10 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { }); } + _onPosition(position) { + game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.compendiumBrowserTypeKey}`].position, position); + } + _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); diff --git a/module/config/flagsConfig.mjs b/module/config/flagsConfig.mjs index c2a6dff2..32088bc1 100644 --- a/module/config/flagsConfig.mjs +++ b/module/config/flagsConfig.mjs @@ -8,6 +8,18 @@ export const encounterCountdown = { position: 'countdown-encounter-position' }; +export const compendiumBrowserDefault = { + position: 'compendium-browser-default-position' +}; + +export const compendiumBrowserNoFolder = { + position: 'compendium-browser-no-folder-position' +}; + +export const compendiumBrowserLite = { + position: 'compendium-browser-lite-position' +}; + export const itemAttachmentSource = 'attachmentSource'; export const userFlags = { diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index e870172f..2c3e1dfb 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -370,19 +370,6 @@ 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', diff --git a/module/data/fields/action/costField.mjs b/module/data/fields/action/costField.mjs index 2d2a38df..656edee3 100644 --- a/module/data/fields/action/costField.mjs +++ b/module/data/fields/action/costField.mjs @@ -103,7 +103,7 @@ export default class CostField extends fields.ArrayField { static calcCosts(costs) { const resources = CostField.getResources.call(this, costs); let filteredCosts = costs; - if (this.parent.metadata.isQuantifiable && this.parent.consumeOnUse === false) { + if (this.parent?.metadata.isQuantifiable && this.parent.consumeOnUse === false) { filteredCosts = filteredCosts.filter(c => c.key !== 'quantity'); } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 79e71549..3601e09d 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -501,6 +501,7 @@ export default class DhpActor extends Actor { /**@inheritdoc */ getRollData() { const rollData = super.getRollData(); + rollData.name = this.name; rollData.system = this.system.getRollData(); rollData.prof = this.system.proficiency ?? 1; rollData.cast = this.system.spellcastModifier ?? 1; diff --git a/module/enrichers/DamageEnricher.mjs b/module/enrichers/DamageEnricher.mjs index a52c4b31..e3f9c42a 100644 --- a/module/enrichers/DamageEnricher.mjs +++ b/module/enrichers/DamageEnricher.mjs @@ -1,29 +1,8 @@ +import { parseInlineParams } from './parser.mjs'; + export default function DhDamageEnricher(match, _options) { - const parts = match[1].split('|').map(x => x.trim()); - - let value = null, - type = null, - inline = false; - - parts.forEach(part => { - const split = part.split(':').map(x => x.toLowerCase().trim()); - if (split.length === 2) { - switch (split[0]) { - case 'value': - value = split[1]; - break; - case 'type': - type = split[1]; - break; - case 'inline': - inline = true; - break; - } - } - }); - - if (!value || !value) return match[0]; - + const { value, type, inline } = parseInlineParams(match[1]); + if (!value || !type) return match[0]; return getDamageMessage(value, type, inline, match[0]); } diff --git a/module/enrichers/LookupEnricher.mjs b/module/enrichers/LookupEnricher.mjs new file mode 100644 index 00000000..7836311e --- /dev/null +++ b/module/enrichers/LookupEnricher.mjs @@ -0,0 +1,8 @@ +import { parseInlineParams } from './parser.mjs'; + +export default function DhLookupEnricher(match, { rollData }) { + const results = parseInlineParams(match[1], { first: 'formula'}); + const element = document.createElement('span'); + element.textContent = Roll.replaceFormulaData(String(results.formula), rollData); + return element; +} diff --git a/module/enrichers/TemplateEnricher.mjs b/module/enrichers/TemplateEnricher.mjs index 15936b29..f1e9298d 100644 --- a/module/enrichers/TemplateEnricher.mjs +++ b/module/enrichers/TemplateEnricher.mjs @@ -1,49 +1,18 @@ +import { parseInlineParams } from './parser.mjs'; + export default function DhTemplateEnricher(match, _options) { - const parts = match[1].split('|').map(x => x.trim()); - - let type = null, - range = null, - angle = CONFIG.MeasuredTemplate.defaults.angle, - direction = 0, - inline = false; - - parts.forEach(part => { - const split = part.split(':').map(x => x.toLowerCase().trim()); - if (split.length === 2) { - switch (split[0]) { - case 'type': - const matchedType = Object.values(CONFIG.DH.GENERAL.templateTypes).find( - x => x.toLowerCase() === split[1] - ); - type = matchedType; - break; - case 'range': - if (Number.isNaN(Number(split[1]))) { - const matchedRange = Object.values(CONFIG.DH.GENERAL.templateRanges).find( - x => x.id.toLowerCase() === split[1] || x.short === split[1] - ); - range = matchedRange?.id; - } else { - range = split[1]; - } - break; - case 'inline': - inline = true; - break; - case 'angle': - angle = split[1]; - break; - case 'direction': - direction = split[1]; - break; - } - } - }); - - if (!type || !range) return match[0]; + const params = parseInlineParams(match[1]); + const { type, angle = CONFIG.MeasuredTemplate.defaults.angle, inline = false } = params; + const direction = Number(params.direction) || 0; + const range = + params.range && Number.isNaN(params.range) + ? Object.values(CONFIG.DH.GENERAL.templateRanges).find( + x => x.id.toLowerCase() === split[1] || x.short === split[1] + )?.id + : params.range; + if (!(type in CONFIG.MeasuredTemplate.types) || !range) return match[0]; const label = game.i18n.localize(`DAGGERHEART.CONFIG.TemplateTypes.${type}`); - const rangeDisplay = Number.isNaN(Number(range)) ? game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.name`) : range; let angleDisplay = ''; diff --git a/module/enrichers/_module.mjs b/module/enrichers/_module.mjs index deec4250..794756f4 100644 --- a/module/enrichers/_module.mjs +++ b/module/enrichers/_module.mjs @@ -2,6 +2,7 @@ import { default as DhDamageEnricher, renderDamageButton } from './DamageEnriche import { default as DhDualityRollEnricher, renderDualityButton } from './DualityRollEnricher.mjs'; import { default as DhEffectEnricher } from './EffectEnricher.mjs'; import { default as DhTemplateEnricher, renderMeasuredTemplate } from './TemplateEnricher.mjs'; +import { default as DhLookupEnricher } from './LookupEnricher.mjs'; export { DhDamageEnricher, DhDualityRollEnricher, DhEffectEnricher, DhTemplateEnricher }; @@ -21,6 +22,10 @@ export const enricherConfig = [ { pattern: /@Template\[(.*)\]({.*})?/g, enricher: DhTemplateEnricher + }, + { + pattern: /@Lookup\[(.*)\]({.*})?/g, + enricher: DhLookupEnricher } ]; diff --git a/module/enrichers/parser.mjs b/module/enrichers/parser.mjs new file mode 100644 index 00000000..9fcc4af1 --- /dev/null +++ b/module/enrichers/parser.mjs @@ -0,0 +1,20 @@ +/** + * @param {string} paramString The parameter inside the brackets of something like @Template[] to parse + * @param {Object} options + * @param {string} options.first If set, the first parameter is treated as a value with this as its key + * @returns {Record | null} + */ +export function parseInlineParams(paramString, { first } = {}) { + const parts = paramString.split('|').map(x => x.trim()); + const params = {}; + for (const [idx, param] of parts.entries()) { + if (first && idx === 0) { + params[first] = param; + } else { + const parts = param.split(':'); + params[parts[0]] = parts.length > 1 ? parts[1] : true; + } + } + + return params; +} diff --git a/styles/less/global/chat.less b/styles/less/global/chat.less index 95ea956f..3f83294a 100644 --- a/styles/less/global/chat.less +++ b/styles/less/global/chat.less @@ -61,6 +61,7 @@ width: 40px; height: 40px; object-fit: cover; + object-position: top center; } .message-sub-header-container { diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index e221f4e7..1f5840af 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -26,7 +26,7 @@ &:not(.single-img) { .inventory-item-header:hover { - .img-portait { + .img-portait:has(.roll-img) { .roll-img { opacity: 1; } @@ -96,7 +96,9 @@ } .roll-img { + object-fit: contain; opacity: 0; + padding: 2px; } } @@ -307,12 +309,14 @@ background-color: @dark-blue; bottom: 0; mask-image: linear-gradient(180deg, transparent 0%, black 20%); + border-radius: 0 0 6px 6px; .card-name { font-style: normal; font-weight: 400; font-size: var(--font-size-12); line-height: 15px; + text-align: center; color: @beige; } diff --git a/styles/less/global/tab-description.less b/styles/less/global/tab-description.less index 4d81f2f2..04a9d82a 100644 --- a/styles/less/global/tab-description.less +++ b/styles/less/global/tab-description.less @@ -3,11 +3,14 @@ .daggerheart.dh-style { .tab.active.description { - overflow-y: hidden !important; + display: flex; + flex-direction: column; height: -webkit-fill-available !important; + overflow-y: hidden !important; + padding-top: 10px; - fieldset { - height: -webkit-fill-available; + prose-mirror.active + .artist-attribution { + display: none; } } } diff --git a/templates/sheets/global/partials/domain-card-item.hbs b/templates/sheets/global/partials/domain-card-item.hbs index 1dd92128..ae95b7af 100644 --- a/templates/sheets/global/partials/domain-card-item.hbs +++ b/templates/sheets/global/partials/domain-card-item.hbs @@ -19,6 +19,6 @@ - {{item.name}} + {{item.name}} \ No newline at end of file diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index a4ecec3a..8758c77d 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -23,7 +23,13 @@ Parameters: (hasProperty item "toChat" ) "toChat" "editDoc" ) }}' {{#unless hideTooltip}} {{#if (eq type 'attack' )}} data-tooltip="#attack#{{item.actor.uuid}}" {{else}} data-tooltip="#item#{{item.uuid}}" {{/if}} {{/unless}}> - d20 + {{#if (or item.system.actionsList.size item.system.actionsList.length item.actionType)}} + {{#if @root.isNPC}} + d20 + {{else}} + 2d12 + {{/if}} + {{/if}} {{!-- Name & Tags --}} diff --git a/templates/sheets/global/tabs/tab-description.hbs b/templates/sheets/global/tabs/tab-description.hbs index 6d0669e0..71995a51 100755 --- a/templates/sheets/global/tabs/tab-description.hbs +++ b/templates/sheets/global/tabs/tab-description.hbs @@ -3,10 +3,7 @@ data-tab='{{tabs.description.id}}' data-group='{{tabs.description.group}}' > -
- {{localize "DAGGERHEART.GENERAL.description"}} - {{formInput systemFields.description value=document.system.description enriched=enrichedDescription toggled=true}} -
+ {{formInput systemFields.description value=document.system.description enriched=enrichedDescription toggled=true}} {{#if (and showAttribution document.system.attribution.artist)}} diff --git a/templates/sidebar/daggerheart-menu/main.hbs b/templates/sidebar/daggerheart-menu/main.hbs index 6f31f165..b00001eb 100644 --- a/templates/sidebar/daggerheart-menu/main.hbs +++ b/templates/sidebar/daggerheart-menu/main.hbs @@ -16,7 +16,7 @@ {{/each}} - + - \ No newline at end of file +