Merge branch 'main' into feature/damage-iterrable-rework

This commit is contained in:
WBHarry 2026-03-06 11:16:17 +01:00
commit f004f575a5
27 changed files with 225 additions and 167 deletions

View file

@ -420,10 +420,7 @@ const updateActorsRangeDependentEffects = async token => {
// Get required distance and special case 5 feet to test adjacency // Get required distance and special case 5 feet to test adjacency
const required = rangeMeasurement[range]; const required = rangeMeasurement[range];
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id; const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
const inRange = const inRange = userTarget.distanceTo(token.object) <= required;
required === 5
? userTarget.isAdjacentWith(token.object)
: userTarget.distanceTo(token.object) <= required;
if (reverse ? inRange : !inRange) { if (reverse ? inRange : !inRange) {
enabledEffect = false; enabledEffect = false;
break; break;

View file

@ -1166,12 +1166,12 @@
}, },
"far": { "far": {
"name": "Far", "name": "Far",
"description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility check to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far.", "description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility roll to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far.",
"short": "Far" "short": "Far"
}, },
"veryFar": { "veryFar": {
"name": "Very Far", "name": "Very Far",
"description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility check to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far.", "description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility roll to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far.",
"short": "V. Far" "short": "V. Far"
} }
}, },
@ -1294,6 +1294,7 @@
"triggerTexts": { "triggerTexts": {
"strangePatternsContentTitle": "Matched {nr} times.", "strangePatternsContentTitle": "Matched {nr} times.",
"strangePatternsContentSubTitle": "Increase hope and stress to a total of {nr}.", "strangePatternsContentSubTitle": "Increase hope and stress to a total of {nr}.",
"strangePatternsActionExplanation": "Left click to increase, right click to decrease",
"ferocityContent": "Spend 2 Hope to gain {bonus} bonus Evasion until after the next attack against you?", "ferocityContent": "Spend 2 Hope to gain {bonus} bonus Evasion until after the next attack against you?",
"ferocityEffectDescription": "Your evasion is increased by {bonus}. This bonus lasts until after the next attack made against you." "ferocityEffectDescription": "Your evasion is increased by {bonus}. This bonus lasts until after the next attack made against you."
}, },
@ -2805,7 +2806,7 @@
"title": "Domain Card" "title": "Domain Card"
}, },
"dualityRoll": { "dualityRoll": {
"abilityCheckTitle": "{ability} Check" "abilityCheckTitle": "{ability} Roll"
}, },
"effectSummary": { "effectSummary": {
"title": "Effects Applied", "title": "Effects Applied",
@ -2820,7 +2821,7 @@
"selectLeader": "Select a Leader", "selectLeader": "Select a Leader",
"selectMember": "Select a Member", "selectMember": "Select a Member",
"rerollTitle": "Reroll Group Roll", "rerollTitle": "Reroll Group Roll",
"rerollContent": "Are you sure you want to reroll your {trait} check?", "rerollContent": "Are you sure you want to reroll your {trait} roll?",
"rerollTooltip": "Reroll", "rerollTooltip": "Reroll",
"wholePartySelected": "The whole party is selected" "wholePartySelected": "The whole party is selected"
}, },
@ -2986,7 +2987,8 @@
"tokenActorMissing": "{name} is missing an Actor", "tokenActorMissing": "{name} is missing an Actor",
"tokenActorsMissing": "[{names}] missing Actors", "tokenActorsMissing": "[{names}] missing Actors",
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used", "domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
"knowTheTide": "Know The Tide gained a token" "knowTheTide": "Know The Tide gained a token",
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}"
}, },
"Sidebar": { "Sidebar": {
"actorDirectory": { "actorDirectory": {

View file

@ -187,6 +187,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
}); });
} }
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
this.render(); this.render();
} }
@ -227,6 +228,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
} }
}); });
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
this.render(); this.render();
} }
@ -246,6 +248,8 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
await this.settings.updateSource({ await this.settings.updateSource({
[`${path}.-=${id}`]: null [`${path}.-=${id}`]: null
}); });
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
this.render(); this.render();
} }

View file

@ -4,6 +4,42 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
constructor(options) { constructor(options) {
super(options); super(options);
this.changeChoices = DhActiveEffectConfig.getChangeChoices();
}
static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'sheet', 'dh-style']
};
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' },
tabs: { template: 'templates/generic/tab-navigation.hbs' },
details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] },
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
changes: {
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
scrollable: ['ol[data-changes]']
},
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
};
static TABS = {
sheet: {
tabs: [
{ id: 'details', icon: 'fa-solid fa-book' },
{ id: 'settings', icon: 'fa-solid fa-bars', label: 'DAGGERHEART.GENERAL.Tabs.settings' },
{ id: 'changes', icon: 'fa-solid fa-gears' }
],
initial: 'details',
labelPrefix: 'EFFECT.TABS'
}
};
/**
* Get ChangeChoices for the changes autocomplete. Static for use in this class aswell as in settings-active-effect-config.mjs
* @returns {ChangeChoice { value: string, label: string, hint: string, group: string }[]}
*/
static getChangeChoices() {
const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty']; const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty'];
const getAllLeaves = (root, group, parentPath = '') => { const getAllLeaves = (root, group, parentPath = '') => {
@ -23,7 +59,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
return leaves; return leaves;
}; };
this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => { return Object.keys(game.system.api.models.actors).reduce((acc, key) => {
if (ignoredActorKeys.includes(key)) return acc; if (ignoredActorKeys.includes(key)) return acc;
const model = game.system.api.models.actors[key]; const model = game.system.api.models.actors[key];
@ -62,34 +98,6 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
}, []); }, []);
} }
static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'sheet', 'dh-style']
};
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' },
tabs: { template: 'templates/generic/tab-navigation.hbs' },
details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] },
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
changes: {
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
scrollable: ['ol[data-changes]']
},
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
};
static TABS = {
sheet: {
tabs: [
{ id: 'details', icon: 'fa-solid fa-book' },
{ id: 'settings', icon: 'fa-solid fa-bars', label: 'DAGGERHEART.GENERAL.Tabs.settings' },
{ id: 'changes', icon: 'fa-solid fa-gears' }
],
initial: 'details',
labelPrefix: 'EFFECT.TABS'
}
};
_attachPartListeners(partId, htmlElement, options) { _attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options); super._attachPartListeners(partId, htmlElement, options);
const changeChoices = this.changeChoices; const changeChoices = this.changeChoices;

View file

@ -7,19 +7,7 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
super({}); super({});
this.effect = foundry.utils.deepClone(effect); this.effect = foundry.utils.deepClone(effect);
const ignoredActorKeys = ['config', 'DhEnvironment']; this.changeChoices = game.system.api.applications.sheetConfigs.ActiveEffectConfig.getChangeChoices();
this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => {
if (!ignoredActorKeys.includes(key)) {
const model = game.system.api.models.actors[key];
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
const group = game.i18n.localize(model.metadata.label);
const choices = CONFIG.Token.documentClass
.getTrackedAttributeChoices(attributes, model)
.map(x => ({ ...x, group: group }));
acc.push(...choices);
}
return acc;
}, []);
} }
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {

View file

@ -73,9 +73,11 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
return context; return context;
} }
static async updateData(event, element, formData) { static async updateData(_event, _element, formData) {
const data = foundry.utils.expandObject(formData.object); const data = foundry.utils.expandObject(formData.object);
foundry.utils.mergeObject(this.move, data); await this.updateMove({
[`${this.movePath}`]: data
});
this.render(); this.render();
} }
@ -135,9 +137,7 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
} }
); );
await this.settings.updateSource({ [`${this.actionsPath}.${action.id}`]: action }); await this.updateMove({ [`${this.actionsPath}.${action.id}`]: action });
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render(); this.render();
} }
@ -150,13 +150,12 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect); await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect);
if (!updatedEffect) return; if (!updatedEffect) return;
await this.settings.updateSource({ await this.updateMove({
[`${this.movePath}.effects`]: this.move.effects.reduce((acc, effect, index) => { [`${this.movePath}.effects`]: this.move.effects.reduce((acc, effect, index) => {
acc.push(index === effectIndex ? { ...updatedEffect, id: effect.id } : effect); acc.push(index === effectIndex ? { ...updatedEffect, id: effect.id } : effect);
return acc; return acc;
}, []) }, [])
}); });
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render(); this.render();
} else { } else {
const action = this.move.actions.get(id); const action = this.move.actions.get(id);
@ -171,13 +170,13 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
: existingEffectIndex === -1 : existingEffectIndex === -1
? [...currentEffects, effectData] ? [...currentEffects, effectData]
: currentEffects.with(existingEffectIndex, effectData); : currentEffects.with(existingEffectIndex, effectData);
await this.settings.updateSource({ await this.updateMove({
[`${this.movePath}.effects`]: updatedEffects [`${this.movePath}.effects`]: updatedEffects
}); });
} }
await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove }); await this.updateMove({ [`${this.actionsPath}.${id}`]: updatedMove });
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render(); this.render();
return updatedEffects; return updatedEffects;
}).render(true); }).render(true);
@ -199,33 +198,36 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
}); });
} }
} }
await this.settings.updateSource({ await this.updateMove({
[this.movePath]: { [this.movePath]: {
effects: move.effects.filter(x => x.id !== id), effects: move.effects.filter(x => x.id !== id),
actions: move.actions actions: move.actions
} }
}); });
} else { } else {
await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null }); await this.updateMove({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
} }
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render(); this.render();
} }
static async addEffect(_, target) { static async addEffect() {
const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`); const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`);
await this.settings.updateSource({
await this.updateMove({
[`${this.movePath}.effects`]: [ [`${this.movePath}.effects`]: [
...currentEffects, ...currentEffects,
game.system.api.data.activeEffects.BaseEffect.getDefaultObject() game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
] ]
}); });
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render(); this.render();
} }
async updateMove(update) {
await this.settings.updateSource(update);
this.move = foundry.utils.getProperty(this.settings, this.movePath);
}
static resetMoves() {} static resetMoves() {}
_filterTabs(tabs) { _filterTabs(tabs) {

View file

@ -6,7 +6,6 @@ import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
import { socketEvent } from '../../../systemRegistration/socket.mjs'; import { socketEvent } from '../../../systemRegistration/socket.mjs';
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs'; import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
import DhpActor from '../../../documents/actor.mjs'; import DhpActor from '../../../documents/actor.mjs';
import DHItem from '../../../documents/item.mjs';
export default class Party extends DHBaseActorSheet { export default class Party extends DHBaseActorSheet {
constructor(options) { constructor(options) {
@ -269,15 +268,6 @@ export default class Party extends DHBaseActorSheet {
).render({ force: true }); ).render({ force: true });
} }
/**
* Get the set of ContextMenu options for Consumable and Loot.
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
* @this {CharacterSheet}
* @protected
*/
static #getItemContextOptions() {
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Filter Tracking */ /* Filter Tracking */
/* -------------------------------------------- */ /* -------------------------------------------- */

View file

@ -431,18 +431,18 @@ export default function DHApplicationMixin(Base) {
{ {
name: 'disableEffect', name: 'disableEffect',
icon: 'fa-solid fa-lightbulb', icon: 'fa-solid fa-lightbulb',
condition: target => { condition: element => {
const doc = getDocFromElementSync(target); const target = element.closest('[data-item-uuid]');
return doc && !doc.disabled && doc.type !== 'beastform'; return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
}, },
callback: async target => (await getDocFromElement(target)).update({ disabled: true }) callback: async target => (await getDocFromElement(target)).update({ disabled: true })
}, },
{ {
name: 'enableEffect', name: 'enableEffect',
icon: 'fa-regular fa-lightbulb', icon: 'fa-regular fa-lightbulb',
condition: target => { condition: element => {
const doc = getDocFromElementSync(target); const target = element.closest('[data-item-uuid]');
return doc && doc.disabled && doc.type !== 'beastform'; return target.dataset.disabled && target.dataset.itemType !== 'beastform';
}, },
callback: async target => (await getDocFromElement(target)).update({ disabled: false }) callback: async target => (await getDocFromElement(target)).update({ disabled: false })
} }
@ -539,9 +539,9 @@ export default function DHApplicationMixin(Base) {
options.push({ options.push({
name: 'CONTROLS.CommonDelete', name: 'CONTROLS.CommonDelete',
icon: 'fa-solid fa-trash', icon: 'fa-solid fa-trash',
condition: target => { condition: element => {
const doc = getDocFromElementSync(target); const target = element.closest('[data-item-uuid]');
return doc && doc.type !== 'beastform'; return target.dataset.itemType !== 'beastform';
}, },
callback: async (target, event) => { callback: async (target, event) => {
const doc = await getDocFromElement(target); const doc = await getDocFromElement(target);

View file

@ -36,7 +36,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
], ],
dragDrop: [ dragDrop: [
{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null }, { dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null },
{ dragSelector: ".currency[data-currency] .drag-handle", dropSelector: null } { dragSelector: '.currency[data-currency] .drag-handle', dropSelector: null }
] ]
}; };
@ -92,7 +92,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
value: context.source.system.gold[key] value: context.source.system.gold[key]
}; };
} }
context.inventory.hasCurrency = Object.values(context.inventory.currencies).some((c) => c.enabled); context.inventory.hasCurrency = Object.values(context.inventory.currencies).some(c => c.enabled);
} }
return context; return context;
@ -270,7 +270,9 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
currency currency
}); });
if (quantity) { if (quantity) {
originActor.update({ [`system.gold.${currency}`]: Math.max(0, originActor.system.gold[currency] - quantity) }); originActor.update({
[`system.gold.${currency}`]: Math.max(0, originActor.system.gold[currency] - quantity)
});
this.document.update({ [`system.gold.${currency}`]: this.document.system.gold[currency] + quantity }); this.document.update({ [`system.gold.${currency}`]: this.document.system.gold[currency] + quantity });
} }
return; return;
@ -292,6 +294,15 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
/* Handling transfer of inventoryItems */ /* Handling transfer of inventoryItems */
if (item.system.metadata.isInventoryItem) { if (item.system.metadata.isInventoryItem) {
if (!this.document.testUserPermission(game.user, 'OWNER', { exact: true })) {
return ui.notifications.error(
game.i18n.format('DAGGERHEART.UI.Notifications.lackingItemTransferPermission', {
user: game.user.name,
target: this.document.name
})
);
}
if (item.system.metadata.isQuantifiable) { if (item.system.metadata.isQuantifiable) {
const actorItem = originActor.items.get(data.originId); const actorItem = originActor.items.get(data.originId);
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({ const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
@ -300,14 +311,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
}); });
if (quantityTransfered) { if (quantityTransfered) {
if (quantityTransfered === actorItem.system.quantity) {
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
} else {
await actorItem.update({
'system.quantity': actorItem.system.quantity - quantityTransfered
});
}
const existingItem = this.document.items.find(x => itemIsIdentical(x, item)); const existingItem = this.document.items.find(x => itemIsIdentical(x, item));
if (existingItem) { if (existingItem) {
await existingItem.update({ await existingItem.update({
@ -325,10 +328,18 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
} }
]); ]);
} }
if (quantityTransfered === actorItem.system.quantity) {
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
} else {
await actorItem.update({
'system.quantity': actorItem.system.quantity - quantityTransfered
});
}
} }
} else { } else {
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
await this.document.createEmbeddedDocuments('Item', [item.toObject()]); await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
} }
} }
} }
@ -339,7 +350,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
*/ */
async _onDragStart(event) { async _onDragStart(event) {
// Handle drag/dropping currencies // Handle drag/dropping currencies
const currencyEl = event.currentTarget.closest(".currency[data-currency]"); const currencyEl = event.currentTarget.closest('.currency[data-currency]');
if (currencyEl) { if (currencyEl) {
const currency = currencyEl.dataset.currency; const currency = currencyEl.dataset.currency;
const data = { type: 'Currency', currency, originActor: this.document.uuid }; const data = { type: 'Currency', currency, originActor: this.document.uuid };

View file

@ -54,30 +54,58 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
if (this === target) return 0; if (this === target) return 0;
const originPoint = this.center; const originPoint = this.center;
const destinationPoint = target.center; const targetPoint = target.center;
const thisBounds = this.bounds;
const targetBounds = target.bounds;
const adjacencyBuffer = canvas.grid.distance * 1.75; // handles diagonals with one square elevation difference
// Figure out the elevation difference.
// This intends to return "grid distance" for adjacent ones, so we add that number if not overlapping.
const sizePerUnit = canvas.grid.size / canvas.grid.distance;
const thisHeight = Math.max(thisBounds.width, thisBounds.height) / sizePerUnit;
const targetHeight = Math.max(targetBounds.width, targetBounds.height) / sizePerUnit;
const thisElevation = [this.document.elevation, this.document.elevation + thisHeight];
const targetElevation = [target.document.elevation, target.document.elevation + targetHeight];
const isSameAltitude =
thisElevation[0] < targetElevation[1] && // bottom of this must be at or below the top of target
thisElevation[1] > targetElevation[0]; // top of this must be at or above the bottom of target
const [lower, higher] = [targetElevation, thisElevation].sort((a, b) => a[1] - b[1]);
const elevation = isSameAltitude ? 0 : higher[0] - lower[1] + canvas.grid.distance;
// Compute for gridless. This version returns circular edge to edge + grid distance, // Compute for gridless. This version returns circular edge to edge + grid distance,
// so that tokens that are touching return 5. // so that tokens that are touching return 5.
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) { if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
const boundsCorrection = canvas.grid.distance / canvas.grid.size; const boundsCorrection = canvas.grid.distance / canvas.grid.size;
const originRadius = (this.bounds.width * boundsCorrection) / 2; const originRadius = (thisBounds.width * boundsCorrection) / 2;
const targetRadius = (target.bounds.width * boundsCorrection) / 2; const targetRadius = (targetBounds.width * boundsCorrection) / 2;
const distance = canvas.grid.measurePath([originPoint, destinationPoint]).distance; const measuredDistance = canvas.grid.measurePath([
return Math.floor(distance - originRadius - targetRadius + canvas.grid.distance); { ...originPoint, elevation: 0 },
{ ...targetPoint, elevation }
]).distance;
const distance = Math.floor(measuredDistance - originRadius - targetRadius + canvas.grid.distance);
return Math.min(distance, distance > adjacencyBuffer ? Infinity : canvas.grid.distance);
} }
// Compute what the closest grid space of each token is, then compute that distance // Compute what the closest grid space of each token is, then compute that distance
const originEdge = this.#getEdgeBoundary(this.bounds, originPoint, destinationPoint); const originEdge = this.#getEdgeBoundary(thisBounds, originPoint, targetPoint);
const targetEdge = this.#getEdgeBoundary(target.bounds, originPoint, destinationPoint); const targetEdge = this.#getEdgeBoundary(targetBounds, originPoint, targetPoint);
const adjustedOriginPoint = canvas.grid.getTopLeftPoint({ const adjustedOriginPoint = originEdge
x: originEdge.x + Math.sign(originPoint.x - originEdge.x), ? canvas.grid.getTopLeftPoint({
y: originEdge.y + Math.sign(originPoint.y - originEdge.y) x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
}); y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
const adjustDestinationPoint = canvas.grid.getTopLeftPoint({ })
x: targetEdge.x + Math.sign(destinationPoint.x - targetEdge.x), : originPoint;
y: targetEdge.y + Math.sign(destinationPoint.y - targetEdge.y) const adjustDestinationPoint = targetEdge
}); ? canvas.grid.getTopLeftPoint({
return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance; x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x),
y: targetEdge.y + Math.sign(targetPoint.y - targetEdge.y)
})
: targetPoint;
const distance = canvas.grid.measurePath([
{ ...adjustedOriginPoint, elevation: 0 },
{ ...adjustDestinationPoint, elevation }
]).distance;
return Math.min(distance, distance > adjacencyBuffer ? Infinity : canvas.grid.distance);
} }
_onHoverIn(event, options) { _onHoverIn(event, options) {
@ -103,8 +131,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
// Determine the actual range // Determine the actual range
const ranges = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement; const ranges = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
const distanceNum = originToken.distanceTo(this); const distanceResult = DhMeasuredTemplate.getRangeLabels(originToken.distanceTo(this), ranges);
const distanceResult = DhMeasuredTemplate.getRangeLabels(distanceNum, ranges);
const distanceLabel = `${distanceResult.distance} ${distanceResult.units}`.trim(); const distanceLabel = `${distanceResult.distance} ${distanceResult.units}`.trim();
// Create the element // Create the element
@ -156,11 +183,6 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
return null; return null;
} }
/** Tests if the token is at least adjacent with another, with some leeway for diagonals */
isAdjacentWith(token) {
return this.distanceTo(token) <= canvas.grid.distance * 1.5;
}
/** @inheritDoc */ /** @inheritDoc */
_drawBar(number, bar, data) { _drawBar(number, bar, data) {
const val = Number(data.value); const val = Number(data.value);

View file

@ -467,9 +467,7 @@ export const allArmorFeatures = () => {
}; };
export const orderedArmorFeatures = () => { export const orderedArmorFeatures = () => {
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures const allFeatures = allArmorFeatures();
.armorFeatures;
const allFeatures = { ...armorFeatures, ...homebrewFeatures };
const all = Object.keys(allFeatures).map(key => { const all = Object.keys(allFeatures).map(key => {
const feature = allFeatures[key]; const feature = allFeatures[key];
return { return {
@ -1404,9 +1402,7 @@ export const allWeaponFeatures = () => {
}; };
export const orderedWeaponFeatures = () => { export const orderedWeaponFeatures = () => {
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures const allFeatures = allWeaponFeatures();
.weaponFeatures;
const allFeatures = { ...weaponFeatures, ...homebrewFeatures };
const all = Object.keys(allFeatures).map(key => { const all = Object.keys(allFeatures).map(key => {
const feature = allFeatures[key]; const feature = allFeatures[key];
return { return {

View file

@ -190,6 +190,10 @@ export default class DhpAdversary extends DhCreature {
} }
} }
prepareDerivedData() {
this.attack.roll.isStandardAttack = true;
}
_getTags() { _getTags() {
const tags = [ const tags = [
game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${this.tier}`), game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${this.tier}`),

View file

@ -166,7 +166,8 @@ export default class DamageField extends fields.SchemaField {
if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt; if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt;
const isAdversary = this.actor.type === 'adversary'; const isAdversary = this.actor.type === 'adversary';
if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) { const isHorde = this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id;
if (isAdversary && isHorde && this.roll?.isStandardAttack) {
const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde'); const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde');
if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt; if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt;
} }

View file

@ -23,9 +23,7 @@ export default class DHArmor extends AttachableItem {
armorFeatures: new fields.ArrayField( armorFeatures: new fields.ArrayField(
new fields.SchemaField({ new fields.SchemaField({
value: new fields.StringField({ value: new fields.StringField({
required: true, required: true
choices: CONFIG.DH.ITEM.allArmorFeatures,
blank: true
}), }),
effectIds: new fields.ArrayField(new fields.StringField({ required: true })), effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
actionIds: new fields.ArrayField(new fields.StringField({ required: true })) actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
@ -58,7 +56,7 @@ export default class DHArmor extends AttachableItem {
async getDescriptionData() { async getDescriptionData() {
const baseDescription = this.description; const baseDescription = this.description;
const allFeatures = CONFIG.DH.ITEM.allArmorFeatures(); const allFeatures = CONFIG.DH.ITEM.allArmorFeatures();
const features = this.armorFeatures.map(x => allFeatures[x.value]); const features = this.armorFeatures.map(x => allFeatures[x.value]).filter(x => x);
const prefix = await foundry.applications.handlebars.renderTemplate( const prefix = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/sheets/items/armor/description.hbs', 'systems/daggerheart/templates/sheets/items/armor/description.hbs',

View file

@ -38,9 +38,7 @@ export default class DHWeapon extends AttachableItem {
weaponFeatures: new fields.ArrayField( weaponFeatures: new fields.ArrayField(
new fields.SchemaField({ new fields.SchemaField({
value: new fields.StringField({ value: new fields.StringField({
required: true, required: true
choices: CONFIG.DH.ITEM.allWeaponFeatures,
blank: true
}), }),
effectIds: new fields.ArrayField(new fields.StringField({ required: true })), effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
actionIds: new fields.ArrayField(new fields.StringField({ required: true })) actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
@ -121,7 +119,7 @@ export default class DHWeapon extends AttachableItem {
const burden = game.i18n.localize(CONFIG.DH.GENERAL.burden[this.burden].label); const burden = game.i18n.localize(CONFIG.DH.GENERAL.burden[this.burden].label);
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures(); const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures();
const features = this.weaponFeatures.map(x => allFeatures[x.value]); const features = this.weaponFeatures.map(x => allFeatures[x.value]).filter(x => x);
const prefix = await foundry.applications.handlebars.renderTemplate( const prefix = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/sheets/items/weapon/description.hbs', 'systems/daggerheart/templates/sheets/items/weapon/description.hbs',

View file

@ -75,7 +75,7 @@ export default class RegisteredTriggers extends Map {
unregisterSceneEnvironmentTriggers(flagSystemData) { unregisterSceneEnvironmentTriggers(flagSystemData) {
const sceneData = new game.system.api.data.scenes.DHScene(flagSystemData); const sceneData = new game.system.api.data.scenes.DHScene(flagSystemData);
for (const environment of sceneData.sceneEnvironments) { for (const environment of sceneData.sceneEnvironments) {
if (environment.pack) continue; if (!environment || environment.pack) continue;
this.unregisterItemTriggers(environment.system.features); this.unregisterItemTriggers(environment.system.features);
} }
} }

View file

@ -119,8 +119,8 @@ export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {}
}), }),
maxTags: typeof maxTags === 'function' ? maxTags() : maxTags, maxTags: typeof maxTags === 'function' ? maxTags() : maxTags,
dropdown: { dropdown: {
searchKeys: ['value', 'name'],
mapValueTo: 'name', mapValueTo: 'name',
searchKeys: ['value'],
enabled: 0, enabled: 0,
maxItems: 100, maxItems: 100,
closeOnSelect: true, closeOnSelect: true,

View file

@ -85,7 +85,7 @@
{ {
"trigger": "dualityRoll", "trigger": "dualityRoll",
"triggeringActorType": "self", "triggeringActorType": "self",
"command": "/* Ignore if it's a TagTeam roll */\nconst tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);\nif (tagTeam.members[actor.id]) return;\n\n/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n <div><div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}</div>\n <div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}</div>\n <div class=\"flexrow\" style=\"gap: 8px;\">\n <button type=\"button\" id=\"hopeButton\">\n <i class=\"fa-solid fa-hands-holding\"></i>\n <label>0</label>\n </button>\n <button type=\"button\" id=\"stressButton\">\n <i class=\"fa-solid fa-bolt-lightning\"></i>\n <label>0</label>\n </button>\n </div>\n</div>`;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;" "command": "/* Ignore if it's a TagTeam roll */\nconst tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);\nif (tagTeam.members[actor.id]) return;\n\n/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n <div><div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}</div>\n <div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}</div>\n<div>${game.i18n.localize('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsActionExplanation')}</div>\n <div class=\"flexrow\" style=\"gap: 8px;\">\n <button type=\"button\" id=\"hopeButton\">\n <i class=\"fa-solid fa-hands-holding\"></i>\n <label>0</label>\n </button>\n <button type=\"button\" id=\"stressButton\">\n <i class=\"fa-solid fa-bolt-lightning\"></i>\n <label>0</label>\n </button>\n </div>\n</div>`;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;"
} }
] ]
} }

View file

@ -53,7 +53,7 @@
"difficulty": null, "difficulty": null,
"damageMod": "none" "damageMod": "none"
}, },
"name": "Agility Check", "name": "Agility Roll",
"img": "icons/skills/melee/sword-engraved-glow-purple.webp", "img": "icons/skills/melee/sword-engraved-glow-purple.webp",
"range": "close" "range": "close"
} }

View file

@ -103,10 +103,9 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: end; justify-content: end;
gap: 8px;
a { a {
width: 15px; width: 20px;
text-align: center; text-align: center;
} }
@ -275,8 +274,10 @@
grid-area: controls; grid-area: controls;
align-self: start; align-self: start;
padding-top: 0.3125rem; padding-top: 0.3125rem;
gap: 4px;
margin-bottom: -1px; margin-bottom: -1px;
a {
width: 18px;
}
} }
> .item-labels { > .item-labels {
align-self: start; align-self: start;
@ -334,6 +335,27 @@
border-radius: 6px; border-radius: 6px;
} }
.recall-cost {
position: absolute;
right: 4px;
top: 4px;
width: 1.75em;
height: 1.75em;
align-items: center;
background: @dark-blue;
border-radius: 50%;
border: 1px solid @golden;
color: @golden;
display: flex;
justify-content: center;
padding-top: 0.1em; // compensate for font
i {
font-size: 0.68em;
}
}
.card-label { .card-label {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -183,6 +183,11 @@
} }
} }
.domain-details {
display: flex;
flex-direction: column;
}
.level-details { .level-details {
align-self: center; align-self: center;
} }

View file

@ -158,7 +158,7 @@
.daggerheart, .daggerheart,
#chat-notifications { #chat-notifications {
.chat-message { .chat-message {
--text-color: @golden; --text-color: light-dark(@dark-blue, @golden);
--bg-color: @golden-40; --bg-color: @golden-40;
[data-use-perm='false'] { [data-use-perm='false'] {
@ -233,7 +233,7 @@
font-family: @font-subtitle; font-family: @font-subtitle;
font-size: var(--font-size-18); font-size: var(--font-size-18);
font-weight: bold; font-weight: bold;
color: light-dark(@dark-blue, var(--text-color)); color: var(--text-color);
margin-bottom: -2px; margin-bottom: -2px;
} }

View file

@ -2,7 +2,7 @@
"id": "daggerheart", "id": "daggerheart",
"title": "Daggerheart", "title": "Daggerheart",
"description": "An unofficial implementation of the Daggerheart system", "description": "An unofficial implementation of the Daggerheart system",
"version": "1.7.2", "version": "1.7.3",
"compatibility": { "compatibility": {
"minimum": "13.346", "minimum": "13.346",
"verified": "13.351", "verified": "13.351",

View file

@ -1,5 +1,9 @@
<li class="card-item" data-item-uuid="{{item.uuid}}" data-type="domainCard"> <li class="card-item" data-item-uuid="{{item.uuid}}" data-type="domainCard">
<img src="{{item.img}}" data-action="useItem" class="card-img" /> <img src="{{item.img}}" data-action="useItem" class="card-img" />
<span class="item-icon recall-cost">
<span class="recall-value">{{item.system.recallCost}}</span>
<i class="fa-solid fa-bolt"></i>
</span>
<div class="card-label"> <div class="card-label">
<div <div
class="menu {{#if item.system.resource}}resource-menu{{/if}} {{#if (eq item.system.resource.type 'diceValue')}}dice-menu{{/if}}"> class="menu {{#if item.system.resource}}resource-menu{{/if}} {{#if (eq item.system.resource.type 'diceValue')}}dice-menu{{/if}}">

View file

@ -56,6 +56,7 @@ Parameters:
{{> 'daggerheart.inventory-item' {{> 'daggerheart.inventory-item'
item=item item=item
type=../type type=../type
disabledEffect=../disabledEffect
actorType=../actorType actorType=../actorType
hideControls=../hideControls hideControls=../hideControls
hideContextMenu=../hideContextMenu hideContextMenu=../hideContextMenu

View file

@ -17,8 +17,12 @@ Parameters:
- showActions {boolean} : If true show feature's actions. - showActions {boolean} : If true show feature's actions.
--}} --}}
<li class="inventory-item" data-item-id="{{item.id}}" {{#if (or (eq type 'action' ) (eq type 'attack' ))}} <li class="inventory-item" data-item-id="{{item.id}}"
data-action-id="{{item.id}}" {{/if}} data-item-uuid="{{item.uuid}}" data-type="{{type}}" data-no-compendium-edit="{{noCompendiumEdit}}"> {{#if (or (eq type 'action' ) (eq type 'attack' ))}}data-action-id="{{item.id}}" {{/if}}
{{#if disabledEffect}}data-disabled="true"{{/if}}
data-type="{{type}}" data-item-type="{{item.type}}"
data-item-uuid="{{item.uuid}}" data-no-compendium-edit="{{noCompendiumEdit}}"
>
<div class="inventory-item-header {{#if hideContextMenu}}padded{{/if}}" {{#unless noExtensible}}data-action="toggleExtended" {{/unless}}> <div class="inventory-item-header {{#if hideContextMenu}}padded{{/if}}" {{#unless noExtensible}}data-action="toggleExtended" {{/unless}}>
{{!-- Image --}} {{!-- Image --}}
<div class="img-portait" data-action='{{ifThen (or (hasProperty item "use") (eq type "attack")) "useItem" (ifThen <div class="img-portait" data-action='{{ifThen (or (hasProperty item "use") (eq type "attack")) "useItem" (ifThen
@ -105,7 +109,7 @@ Parameters:
{{else if (eq type 'armor')}} {{else if (eq type 'armor')}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" <a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.equipped 'unequip' 'equip' }}"> data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.equipped 'unequip' 'equip' }}">
<i class="fa-solid fa-shield"></i> <i class="fa-solid fa-fw fa-shield"></i>
</a> </a>
{{/if}} {{/if}}
{{#if (eq type 'domainCard')}} {{#if (eq type 'domainCard')}}
@ -121,7 +125,7 @@ Parameters:
{{/if}} {{/if}}
{{#if (hasProperty item "toChat")}} {{#if (hasProperty item "toChat")}}
<a data-action="toChat" data-tooltip="DAGGERHEART.UI.Tooltip.sendToChat"> <a data-action="toChat" data-tooltip="DAGGERHEART.UI.Tooltip.sendToChat">
<i class="fa-regular fa-message"></i> <i class="fa-regular fa-fw fa-message"></i>
</a> </a>
{{/if}} {{/if}}
{{else}} {{else}}
@ -134,7 +138,7 @@ Parameters:
{{/unless}} {{/unless}}
{{#unless hideContextMenu}} {{#unless hideContextMenu}}
<a data-action="triggerContextMenu" data-tooltip="DAGGERHEART.UI.Tooltip.moreOptions"> <a data-action="triggerContextMenu" data-tooltip="DAGGERHEART.UI.Tooltip.moreOptions">
<i class="fa-solid fa-ellipsis-vertical"></i> <i class="fa-solid fa-fw fa-ellipsis-vertical"></i>
</a> </a>
{{/unless}} {{/unless}}
{{/if}} {{/if}}

View file

@ -2,20 +2,21 @@
data-group='{{tabs.effects.group}}'> data-group='{{tabs.effects.group}}'>
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title='DAGGERHEART.GENERAL.activeEffects' title='DAGGERHEART.GENERAL.activeEffects'
type='effect' type='effect'
isGlassy=true isGlassy=true
collection=effects.actives collection=effects.actives
canCreate=true canCreate=true
hideResources=true hideResources=true
}} }}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title='DAGGERHEART.GENERAL.inactiveEffects' title='DAGGERHEART.GENERAL.inactiveEffects'
type='effect' type='effect'
isGlassy=true disabledEffect=true
collection=effects.inactives isGlassy=true
canCreate=true collection=effects.inactives
hideResources=true canCreate=true
hideResources=true
}} }}
</section> </section>