Merge branch 'development' into feature/313-preset-measured-templates

This commit is contained in:
Chris Ryan 2025-10-15 23:14:24 +10:00
commit b75b4c8fcb
24 changed files with 239 additions and 109 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
width="42.0746mm" height="50.8071mm"
viewBox="0 0 159 192">
<path id="Fear"
fill="#808080" stroke="black" stroke-width="1"
d="M 107.14,62.84
C 107.14,62.84 52.70,137.55 52.70,137.55
52.70,137.55 110.33,137.55 110.33,137.55
110.33,137.55 127.99,79.63 127.99,79.63
127.99,79.63 107.14,62.84 107.14,62.84 Z
M 48.07,144.21
C 48.07,144.21 13.61,192.28 13.61,192.28
14.01,191.90 30.68,177.55 47.81,176.06
47.81,176.06 64.48,176.30 64.48,176.30
64.69,176.22 79.31,180.77 79.31,180.77
79.31,180.77 126.04,165.15 126.04,165.15
126.04,165.15 110.91,143.92 110.91,143.92
110.91,143.92 48.07,144.21 48.07,144.21 Z
M 134.24,81.37
C 134.24,81.37 115.74,140.33 115.74,140.33
115.74,140.33 131.41,161.00 131.41,161.00
131.41,161.00 158.98,122.82 158.98,122.82
158.98,122.82 158.91,72.83 158.91,72.83
158.91,72.83 134.24,81.37 134.24,81.37 Z
M 130.43,30.45
C 130.43,30.45 110.41,59.27 110.41,59.27
110.41,59.27 131.77,75.21 131.77,75.21
131.77,75.21 157.51,67.23 157.51,67.23
157.25,67.32 130.42,30.45 130.43,30.45 Z" />
<path id="Hope"
fill="white" stroke="black" stroke-width="1"
d="M 143.84,1.80
C 143.84,1.80 105.59,54.92 105.59,54.92
105.59,54.92 83.04,39.72 83.04,39.72
83.04,39.72 82.87,13.08 82.87,13.08
82.23,14.32 111.40,18.59 112.13,17.16
113.68,18.32 145.11,2.74 143.84,1.80 Z
M 76.01,13.40
C 76.01,13.40 76.01,40.05 76.01,40.05
76.01,40.05 25.99,75.35 25.99,75.35
25.99,75.35 1.96,68.16 1.96,68.16
1.96,68.16 30.08,27.62 30.08,27.62
30.08,27.62 76.01,13.40 76.01,13.40 Z
M 79.11,44.62
C 79.11,44.62 101.34,60.81 101.34,60.81
101.34,60.81 48.38,133.54 48.38,133.54
48.38,133.54 30.57,80.09 30.57,80.09
30.57,80.09 79.11,44.62 79.11,44.62 Z
M 24.36,81.07
C 24.36,81.07 43.64,139.59 43.64,139.59
43.64,139.59 28.11,162.31 28.11,162.31
28.11,162.31 0.33,123.90 0.33,123.90
0.33,123.90 0.33,74.05 0.33,74.05
0.33,74.05 24.36,81.07 24.36,81.07 Z" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -2030,6 +2030,7 @@
"range": "Range",
"reactionRoll": "Reaction Roll",
"recovery": "Recovery",
"refresh": "Refresh",
"reroll": "Reroll",
"rerollThing": "Reroll {thing}",
"resource": "Resource",

View file

@ -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);
}
}

View file

@ -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<void>}
*/
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}

View file

@ -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;

View file

@ -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'
}
};

View file

@ -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);

View file

@ -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 = {

View file

@ -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',

View file

@ -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');
}

View file

@ -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;

View file

@ -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]);
}

View file

@ -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;
}

View file

@ -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 = '';

View file

@ -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
}
];

View file

@ -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<string, string | undefined> | 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;
}

View file

@ -61,6 +61,7 @@
width: 40px;
height: 40px;
object-fit: cover;
object-position: top center;
}
.message-sub-header-container {

View file

@ -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;
}

View file

@ -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;
}
}
}

View file

@ -19,6 +19,6 @@
</a>
</div>
</div>
<span class="card-name">{{item.name}}</span>
<span class="card-name" data-action="viewItem">{{item.name}}</span>
</div>
</li>

View file

@ -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}}>
<img src="{{item.img}}" class="item-img {{#if isActor}}actor-img{{/if}}" />
<img class="roll-img" src="systems/daggerheart/assets/icons/dice/default/d20.svg" alt="d20">
{{#if (or item.system.actionsList.size item.system.actionsList.length item.actionType)}}
{{#if @root.isNPC}}
<img class="roll-img d20" src="systems/daggerheart/assets/icons/dice/default/d20.svg" alt="d20">
{{else}}
<img class="roll-img duality" src="systems/daggerheart/assets/icons/dice/duality/DualityBW.svg" alt="2d12">
{{/if}}
{{/if}}
</div>
{{!-- Name & Tags --}}

View file

@ -3,10 +3,7 @@
data-tab='{{tabs.description.id}}'
data-group='{{tabs.description.group}}'
>
<fieldset>
<legend>{{localize "DAGGERHEART.GENERAL.description"}}</legend>
{{formInput systemFields.description value=document.system.description enriched=enrichedDescription toggled=true}}
</fieldset>
{{formInput systemFields.description value=document.system.description enriched=enrichedDescription toggled=true}}
{{#if (and showAttribution document.system.attribution.artist)}}
<label class="artist-attribution">{{localize "DAGGERHEART.GENERAL.artistAttribution" artist=document.system.attribution.artist}}</label>

View file

@ -16,7 +16,7 @@
{{/each}}
</div>
<button data-action="refreshActors" {{disabled disableRefresh}}>{{localize "Refresh"}}</button>
<button data-action="refreshActors" {{disabled disableRefresh}}>{{localize "DAGGERHEART.GENERAL.refresh"}}</button>
</div>
</fieldset>
</div>
</div>