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}}>
-
+ {{#if (or item.system.actionsList.size item.system.actionsList.length item.actionType)}}
+ {{#if @root.isNPC}}
+
+ {{else}}
+
+ {{/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}}'
>
-
+ {{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
+