Merged with development

This commit is contained in:
WBHarry 2025-09-07 01:04:11 +02:00
commit aed7942fc4
69 changed files with 1092 additions and 229 deletions

View file

@ -6,5 +6,6 @@ export * as scene from './scene/_module.mjs';
export * as settings from './settings/_module.mjs';
export * as sheets from './sheets/_module.mjs';
export * as sheetConfigs from './sheets-configs/_module.mjs';
export * as sidebar from './sidebar/_module.mjs';
export * as ui from './ui/_module.mjs';
export * as ux from './ux/_module.mjs';

View file

@ -1 +1 @@
export { default as DhSceneConfigSettings } from './sceneConfigSettings.mjs';
export { default as DhSceneConfigSettings } from './sceneConfigSettings.mjs';

View file

@ -1,25 +1,24 @@
export default class DhSceneConfigSettings extends foundry.applications.sheets.SceneConfig {
constructor(options, ...args) {
super(options, ...args);
}
static buildParts() {
const { footer, ...parts } = super.PARTS;
const tmpParts = {
...parts,
dh: { template: "systems/daggerheart/templates/scene/dh-config.hbs" },
footer
constructor(options, ...args) {
super(options, ...args);
}
return tmpParts;
}
static PARTS = DhSceneConfigSettings.buildParts();
static buildParts() {
const { footer, ...parts } = super.PARTS;
const tmpParts = {
...parts,
dh: { template: 'systems/daggerheart/templates/scene/dh-config.hbs' },
footer
};
return tmpParts;
}
static buildTabs() {
super.TABS.sheet.tabs.push({ id: "dh", icon: "fa-solid" });
return super.TABS;
}
static PARTS = DhSceneConfigSettings.buildParts();
static TABS = DhSceneConfigSettings.buildTabs();
static buildTabs() {
super.TABS.sheet.tabs.push({ id: 'dh', icon: 'fa-solid' });
return super.TABS;
}
}
static TABS = DhSceneConfigSettings.buildTabs();
}

View file

@ -53,6 +53,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
settings: { template: 'systems/daggerheart/templates/settings/homebrew-settings/settings.hbs' },
domains: { template: 'systems/daggerheart/templates/settings/homebrew-settings/domains.hbs' },
types: { template: 'systems/daggerheart/templates/settings/homebrew-settings/types.hbs' },
itemTypes: { template: 'systems/daggerheart/templates/settings/homebrew-settings/itemFeatures.hbs' },
downtime: { template: 'systems/daggerheart/templates/settings/homebrew-settings/downtime.hbs' },
footer: { template: 'systems/daggerheart/templates/settings/homebrew-settings/footer.hbs' }
};
@ -60,7 +61,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
/** @inheritdoc */
static TABS = {
main: {
tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'types' }, { id: 'downtime' }],
tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'types' }, { id: 'itemFeatures' }, { id: 'downtime' }],
initial: 'settings',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}
@ -115,33 +116,53 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
}
static async addItem(_, target) {
await this.settings.updateSource({
[`restMoves.${target.dataset.type}.moves.${foundry.utils.randomID()}`]: {
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'),
img: 'icons/magic/life/cross-worn-green.webp',
description: '',
actions: []
}
});
const { type } = target.dataset;
if (['shortRest', 'longRest'].includes(type)) {
await this.settings.updateSource({
[`restMoves.${type}.moves.${foundry.utils.randomID()}`]: {
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'),
img: 'icons/magic/life/cross-worn-green.webp',
description: '',
actions: []
}
});
} else if (['armorFeatures', 'weaponFeatures'].includes(type)) {
await this.settings.updateSource({
[`itemFeatures.${type}.${foundry.utils.randomID()}`]: {
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newFeature'),
img: 'icons/magic/life/cross-worn-green.webp',
description: '',
actions: [],
effects: []
}
});
}
this.render();
}
static async editItem(_, target) {
const move = this.settings.restMoves[target.dataset.type].moves[target.dataset.id];
const path = `restMoves.${target.dataset.type}.moves.${target.dataset.id}`;
const editedMove = await game.system.api.applications.sheetConfigs.DowntimeConfig.configure(
move,
path,
this.settings
);
if (!editedMove) return;
const { type, id } = target.dataset;
const isDowntime = ['shortRest', 'longRest'].includes(type);
const path = isDowntime ? `restMoves.${type}.moves.${id}` : `itemFeatures.${type}.${id}`;
const featureBase = isDowntime ? this.settings.restMoves[type].moves[id] : this.settings.itemFeatures[type][id];
await this.updateAction.bind(this)(editedMove, target.dataset.type, target.dataset.id);
const editedBase = await game.system.api.applications.sheetConfigs.SettingFeatureConfig.configure(
featureBase,
path,
this.settings,
{ hasIcon: isDowntime, hasEffects: !isDowntime }
);
if (!editedBase) return;
await this.updateAction.bind(this)(editedBase, target.dataset.type, target.dataset.id);
}
async updateAction(data, type, id) {
const isDowntime = ['shortRest', 'longRest'].includes(type);
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
await this.settings.updateSource({
[`restMoves.${type}.moves.${id}`]: {
[`${path}.${id}`]: {
actions: data.actions,
name: data.name,
icon: data.icon,
@ -149,12 +170,16 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
description: data.description
}
});
this.render();
}
static async removeItem(_, target) {
const { type, id } = target.dataset;
const isDowntime = ['shortRest', 'longRest'].includes(type);
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
await this.settings.updateSource({
[`restMoves.${target.dataset.type}.moves.-=${target.dataset.id}`]: null
[`${path}.-=${id}`]: null
});
this.render();
}

View file

@ -2,7 +2,8 @@ export { default as ActionConfig } from './action-config.mjs';
export { default as CharacterSettings } from './character-settings.mjs';
export { default as AdversarySettings } from './adversary-settings.mjs';
export { default as CompanionSettings } from './companion-settings.mjs';
export { default as DowntimeConfig } from './downtimeConfig.mjs';
export { default as SettingActiveEffectConfig } from './setting-active-effect-config.mjs';
export { default as SettingFeatureConfig } from './setting-feature-config.mjs';
export { default as EnvironmentSettings } from './environment-settings.mjs';
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';
export { default as DhTokenConfig } from './token-config.mjs';

View file

@ -66,7 +66,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
group: 'primary',
id: 'base',
icon: null,
label: 'Base'
label: 'DAGGERHEART.GENERAL.Tabs.base'
},
config: {
active: false,
@ -74,7 +74,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
group: 'primary',
id: 'config',
icon: null,
label: 'Configuration'
label: 'DAGGERHEART.GENERAL.Tabs.configuration'
},
effect: {
active: false,
@ -82,7 +82,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
group: 'primary',
id: 'effect',
icon: null,
label: 'Effect'
label: 'DAGGERHEART.GENERAL.Tabs.effects'
}
};
@ -138,7 +138,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
};
}
if (this.action.parent.metadata.isQuantifiable) {
if (this.action.parent.metadata?.isQuantifiable) {
options.quantity = {
label: 'DAGGERHEART.GENERAL.itemQuantity',
group: 'Global'

View file

@ -96,6 +96,13 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
});
}
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.systemFields = context.document.system.schema.fields;
return context;
}
async _preparePartContext(partId, context) {
const partContext = await super._preparePartContext(partId, context);
switch (partId) {

View file

@ -0,0 +1,227 @@
import autocomplete from 'autocompleter';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class SettingActiveEffectConfig extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(effect) {
super({});
this.effect = foundry.utils.deepClone(effect);
const ignoredActorKeys = ['config', 'DhEnvironment'];
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 = {
classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config'],
tag: 'form',
position: {
width: 560
},
form: {
submitOnChange: false,
closeOnSubmit: false,
handler: SettingActiveEffectConfig.#onSubmit
},
actions: {
editImage: SettingActiveEffectConfig.#editImage,
addChange: SettingActiveEffectConfig.#addChange,
deleteChange: SettingActiveEffectConfig.#deleteChange
}
};
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'
}
};
/**@inheritdoc */
async _onFirstRender(context, options) {
await super._onFirstRender(context, options);
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.source = this.effect;
context.fields = game.system.api.documents.DhActiveEffect.schema.fields;
context.systemFields = game.system.api.data.activeEffects.BaseEffect._schema.fields;
return context;
}
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
const changeChoices = this.changeChoices;
htmlElement.querySelectorAll('.effect-change-input').forEach(element => {
autocomplete({
input: element,
fetch: function (text, update) {
if (!text) {
update(changeChoices);
} else {
text = text.toLowerCase();
var suggestions = changeChoices.filter(n => n.label.toLowerCase().includes(text));
update(suggestions);
}
},
render: function (item, search) {
const label = game.i18n.localize(item.label);
const matchIndex = label.toLowerCase().indexOf(search);
const beforeText = label.slice(0, matchIndex);
const matchText = label.slice(matchIndex, matchIndex + search.length);
const after = label.slice(matchIndex + search.length, label.length);
const element = document.createElement('li');
element.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
if (item.hint) {
element.dataset.tooltip = game.i18n.localize(item.hint);
}
return element;
},
renderGroup: function (label) {
const itemElement = document.createElement('div');
itemElement.textContent = game.i18n.localize(label);
return itemElement;
},
onSelect: function (item) {
element.value = `system.${item.value}`;
},
click: e => e.fetch(),
customize: function (_input, _inputRect, container) {
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
},
minLength: 0
});
});
}
async _preparePartContext(partId, context) {
if (partId in context.tabs) context.tab = context.tabs[partId];
switch (partId) {
case 'details':
context.isActorEffect = false;
context.isItemEffect = true;
const useGeneric = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.appearance
).showGenericStatusEffects;
if (!useGeneric) {
context.statuses = Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
value: status.id,
label: game.i18n.localize(status.name)
}));
}
break;
case 'changes':
context.modes = Object.entries(CONST.ACTIVE_EFFECT_MODES).reduce((modes, [key, value]) => {
modes[value] = game.i18n.localize(`EFFECT.MODE_${key}`);
return modes;
}, {});
context.priorities = ActiveEffectConfig.DEFAULT_PRIORITIES;
break;
}
return context;
}
static async #onSubmit(event, form, formData) {
this.data = foundry.utils.expandObject(formData.object);
this.close();
}
/**
* Edit a Document image.
* @this {DocumentSheetV2}
* @type {ApplicationClickAction}
*/
static async #editImage(_event, target) {
if (target.nodeName !== 'IMG') {
throw new Error('The editImage action is available only for IMG elements.');
}
const attr = target.dataset.edit;
const current = foundry.utils.getProperty(this.effect, attr);
const fp = new FilePicker.implementation({
current,
type: 'image',
callback: path => (target.src = path),
position: {
top: this.position.top + 40,
left: this.position.left + 10
}
});
await fp.browse();
}
/**
* Add a new change to the effect's changes array.
* @this {ActiveEffectConfig}
* @type {ApplicationClickAction}
*/
static async #addChange() {
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
const changes = Object.values(submitData.changes ?? {});
changes.push({});
this.effect.changes = changes;
this.render();
}
/**
* Delete a change from the effect's changes array.
* @this {ActiveEffectConfig}
* @type {ApplicationClickAction}
*/
static async #deleteChange(event) {
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
const changes = Object.values(submitData.changes);
const row = event.target.closest('li');
const index = Number(row.dataset.index) || 0;
changes.splice(index, 1);
this.effect.changes = changes;
this.render();
}
static async configure(effect, options = {}) {
return new Promise(resolve => {
const app = new this(effect, options);
app.addEventListener('close', () => resolve(app.data), { once: true });
app.render({ force: true });
});
}
}

View file

@ -3,8 +3,8 @@ import DHActionConfig from './action-config.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DowntimeConfig extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(move, movePath, settings, options) {
export default class SettingFeatureConfig extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(move, movePath, settings, optionalParts, options) {
super(options);
this.move = move;
@ -12,6 +12,10 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
this.movePath = movePath;
this.actionsPath = `${movePath}.actions`;
this.settings = settings;
const { hasIcon, hasEffects } = optionalParts;
this.hasIcon = hasIcon;
this.hasEffects = hasEffects;
}
get title() {
@ -30,6 +34,7 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
addItem: this.addItem,
editItem: this.editItem,
removeItem: this.removeItem,
addEffect: this.addEffect,
resetMoves: this.resetMoves,
saveForm: this.saveForm
},
@ -41,13 +46,14 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
main: { template: 'systems/daggerheart/templates/settings/downtime-config/main.hbs' },
actions: { template: 'systems/daggerheart/templates/settings/downtime-config/actions.hbs' },
effects: { template: 'systems/daggerheart/templates/settings/downtime-config/effects.hbs' },
footer: { template: 'systems/daggerheart/templates/settings/downtime-config/footer.hbs' }
};
/** @inheritdoc */
static TABS = {
primary: {
tabs: [{ id: 'main' }, { id: 'actions' }],
tabs: [{ id: 'main' }, { id: 'actions' }, { id: 'effects' }],
initial: 'main',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}
@ -55,6 +61,9 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.tabs = this._filterTabs(context.tabs);
context.hasIcon = this.hasIcon;
context.hasEffects = this.hasEffects;
context.move = this.move;
context.move.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML(
context.move.description
@ -130,13 +139,30 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
}
static async editItem(_, target) {
const actionId = target.dataset.id;
const action = this.move.actions.get(actionId);
await new DHActionConfig(action, async updatedMove => {
await this.settings.updateSource({ [`${this.actionsPath}.${actionId}`]: updatedMove });
const { type, id } = target.dataset;
if (type === 'effect') {
const effectIndex = this.move.effects.findIndex(x => x.id === id);
const effect = this.move.effects[effectIndex];
const updatedEffect =
await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect);
if (!updatedEffect) return;
await this.settings.updateSource({
[`${this.movePath}.effects`]: this.move.effects.reduce((acc, effect, index) => {
acc.push(index === effectIndex ? { ...updatedEffect, id: effect.id } : effect);
return acc;
}, [])
});
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render();
}).render(true);
} else {
const action = this.move.actions.get(id);
await new DHActionConfig(action, async updatedMove => {
await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove });
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render();
}).render(true);
}
}
static async removeItem(_, target) {
@ -145,16 +171,38 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
this.render();
}
static async addEffect(_, target) {
const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`);
await this.settings.updateSource({
[`${this.movePath}.effects`]: [
...currentEffects,
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
]
});
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render();
}
static resetMoves() {}
_filterTabs(tabs) {
return this.hasEffects
? tabs
: Object.keys(tabs).reduce((acc, key) => {
if (key !== 'effects') acc[key] = tabs[key];
return acc;
}, {});
}
/** @override */
_onClose(options = {}) {
if (!options.submitted) this.move = null;
}
static async configure(move, movePath, settings, options = {}) {
static async configure(move, movePath, settings, optionalParts, options = {}) {
return new Promise(resolve => {
const app = new this(move, movePath, settings, options);
const app = new this(move, movePath, settings, optionalParts, options);
app.addEventListener('close', () => resolve(app.move), { once: true });
app.render({ force: true });
});

View file

@ -232,7 +232,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
* @protected
*/
async _prepareLoadoutContext(context, _options) {
context.cardView = !game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList);
context.cardView = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsCard);
}
/**
@ -722,8 +722,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
* @type {ApplicationClickAction}
*/
static async #toggleLoadoutView(_, button) {
const newAbilityView = button.dataset.value !== 'true';
await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList, newAbilityView);
const newAbilityView = button.dataset.value === 'true';
await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsCard, newAbilityView);
this.render();
}

View file

@ -455,7 +455,7 @@ export default function DHApplicationMixin(Base) {
options.push({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
icon: 'fa-solid fa-message',
callback: async target => (await getDocFromElement(target)).toChat(this.document.id)
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
});
if (deletable)

View file

@ -8,7 +8,7 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
tagifyConfigs: [
{
selector: '.features-input',
options: () => CONFIG.DH.ITEM.armorFeatures,
options: () => CONFIG.DH.ITEM.orderedArmorFeatures(),
callback: ArmorSheet.#onFeatureSelect
}
]

View file

@ -8,7 +8,7 @@ export default class WeaponSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
tagifyConfigs: [
{
selector: '.features-input',
options: () => CONFIG.DH.ITEM.weaponFeatures,
options: () => CONFIG.DH.ITEM.orderedWeaponFeatures(),
callback: WeaponSheet.#onFeatureSelect
}
]

View file

@ -0,0 +1,2 @@
export { default as DaggerheartMenu } from './tabs/daggerheartMenu.mjs';
export { default as DhSidebar } from './sidebar.mjs';

View file

@ -0,0 +1,33 @@
export default class DhSidebar extends Sidebar {
/** @override */
static TABS = {
...super.TABS,
daggerheartMenu: {
tooltip: 'DAGGERHEART.UI.Sidebar.daggerheartMenu.title',
img: 'systems/daggerheart/assets/logos/FoundryBorneLogoWhite.svg'
}
};
/** @override */
static PARTS = {
tabs: {
id: 'tabs',
template: 'systems/daggerheart/templates/sidebar/tabs.hbs'
}
};
/** @override */
async _prepareTabContext(context, options) {
context.tabs = Object.entries(this.constructor.TABS).reduce((obj, [k, v]) => {
let { documentName, gmOnly, tooltip, icon, img } = v;
if (gmOnly && !game.user.isGM) return obj;
if (documentName) {
tooltip ??= getDocumentClass(documentName).metadata.labelPlural;
icon ??= CONFIG[documentName]?.sidebarIcon;
}
obj[k] = { tooltip, icon, img };
obj[k].active = this.tabGroups.primary === k;
return obj;
}, {});
}
}

View file

@ -0,0 +1,160 @@
const { HandlebarsApplicationMixin } = foundry.applications.api;
const { AbstractSidebarTab } = foundry.applications.sidebar;
/**
* The daggerheart menu tab.
* @extends {AbstractSidebarTab}
* @mixes HandlebarsApplication
*/
export default class DaggerheartMenu extends HandlebarsApplicationMixin(AbstractSidebarTab) {
constructor(options) {
super(options);
this.refreshSelections = DaggerheartMenu.#defaultRefreshSelections();
}
static #defaultRefreshSelections() {
return {
session: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.session') },
scene: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.scene') },
longRest: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.longrest') },
shortRest: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.shortrest') }
};
}
/** @override */
static DEFAULT_OPTIONS = {
classes: ['dh-style'],
window: {
title: 'SIDEBAR.TabSettings'
},
actions: {
selectRefreshable: DaggerheartMenu.#selectRefreshable,
refreshActors: DaggerheartMenu.#refreshActors
}
};
/** @override */
static tabName = 'daggerheartMenu';
/** @override */
static PARTS = {
main: { template: 'systems/daggerheart/templates/sidebar/daggerheart-menu/main.hbs' }
};
/* -------------------------------------------- */
/** @inheritDoc */
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.refreshables = this.refreshSelections;
context.disableRefresh = Object.values(this.refreshSelections).every(x => !x.selected);
return context;
}
async getRefreshables(types) {
const refreshedActors = {};
for (let actor of game.actors) {
if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) {
const updates = {};
for (let item of actor.items) {
if (item.system.metadata.hasResource && types.includes(item.system.resource?.recovery)) {
if (!refreshedActors[actor.id])
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
refreshedActors[actor.id].refreshed.add(
game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[item.system.resource.recovery].label)
);
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
const increasing =
item.system.resource.progression === CONFIG.DH.ITEM.itemResourceProgression.increasing.id;
updates[item.id].system = {
...updates[item.id].system,
'resource.value': increasing
? 0
: Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
};
}
if (item.system.metadata.hasActions) {
const refreshTypes = new Set();
const actions = item.system.actions.filter(action => {
if (types.includes(action.uses.recovery)) {
refreshTypes.add(action.uses.recovery);
return true;
}
return false;
});
if (actions.length === 0) continue;
if (!refreshedActors[actor.id])
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
refreshedActors[actor.id].refreshed.add(
...refreshTypes.map(type => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[type].label))
);
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
updates[item.id].system = {
...updates[item.id].system,
...actions.reduce(
(acc, action) => {
acc.actions[action.id] = { 'uses.value': 0 };
return acc;
},
{ actions: updates[item.id].system.actions ?? {} }
)
};
}
}
for (let key in updates) {
const update = updates[key];
await actor.items.get(key).update(update);
}
}
}
return refreshedActors;
}
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
static async #selectRefreshable(_event, button) {
const { type } = button.dataset;
this.refreshSelections[type].selected = !this.refreshSelections[type].selected;
this.render();
}
static async #refreshActors() {
const refreshKeys = Object.keys(this.refreshSelections).filter(key => this.refreshSelections[key].selected);
await this.getRefreshables(refreshKeys);
const types = refreshKeys.map(x => this.refreshSelections[x].label).join(', ');
ui.notifications.info(
game.i18n.format('DAGGERHEART.UI.Notifications.gmMenuRefresh', {
types: `[${types}]`
})
);
this.refreshSelections = DaggerheartMenu.#defaultRefreshSelections();
const cls = getDocumentClass('ChatMessage');
const msg = {
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/refreshMessage.hbs',
{
types: types
}
),
title: game.i18n.localize('DAGGERHEART.UI.Chat.refreshMessage.title'),
speaker: cls.getSpeaker()
};
cls.create(msg);
this.render();
}
}

View file

@ -1,4 +1,4 @@
export const displayDomainCardsAsList = 'displayDomainCardsAsList';
export const displayDomainCardsAsCard = 'displayDomainCardsAsCard';
export const narrativeCountdown = {
simple: 'countdown-narrative-simple',
position: 'countdown-narrative-position'

View file

@ -90,22 +90,22 @@ export const rangeInclusion = {
export const otherTargetTypes = {
friendly: {
id: 'friendly',
label: 'Friendly'
label: 'DAGGERHEART.CONFIG.TargetTypes.friendly'
},
hostile: {
id: 'hostile',
label: 'Hostile'
label: 'DAGGERHEART.CONFIG.TargetTypes.hostile'
},
any: {
id: 'any',
label: 'Any'
label: 'DAGGERHEART.CONFIG.TargetTypes.any'
}
};
export const targetTypes = {
self: {
id: 'self',
label: 'Self'
label: 'DAGGERHEART.CONFIG.TargetTypes.self'
},
...otherTargetTypes
};
@ -587,17 +587,17 @@ export const abilityCosts = {
},
hope: {
id: 'hope',
label: 'Hope',
label: 'DAGGERHEART.CONFIG.HealingType.hope.name',
group: 'TYPES.Actor.character'
},
armor: {
id: 'armor',
label: 'Armor Slot',
label: 'DAGGERHEART.CONFIG.HealingType.armor.name',
group: 'TYPES.Actor.character'
},
fear: {
id: 'fear',
label: 'Fear',
label: 'DAGGERHEART.CONFIG.HealingType.fear.name',
group: 'TYPES.Actor.adversary'
},
resource: itemAbilityCosts.resource

View file

@ -370,6 +370,19 @@ 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

@ -452,6 +452,34 @@ export const armorFeatures = {
}
};
export const allArmorFeatures = () => {
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
.armorFeatures;
return {
...armorFeatures,
...Object.keys(homebrewFeatures).reduce((acc, key) => {
const feature = homebrewFeatures[key];
acc[key] = { ...feature, label: feature.name };
return acc;
}, {})
};
};
export const orderedArmorFeatures = () => {
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
.armorFeatures;
const allFeatures = { ...armorFeatures, ...homebrewFeatures };
const all = Object.keys(allFeatures).map(key => {
const feature = allFeatures[key];
return {
...feature,
id: key,
label: feature.label ?? feature.name
};
});
return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label)));
};
export const weaponFeatures = {
barrier: {
label: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.name',
@ -1383,6 +1411,34 @@ export const weaponFeatures = {
}
};
export const allWeaponFeatures = () => {
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
.weaponFeatures;
return {
...weaponFeatures,
...Object.keys(homebrewFeatures).reduce((acc, key) => {
const feature = homebrewFeatures[key];
acc[key] = { ...feature, label: feature.name };
return acc;
}, {})
};
};
export const orderedWeaponFeatures = () => {
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
.weaponFeatures;
const allFeatures = { ...weaponFeatures, ...homebrewFeatures };
const all = Object.keys(allFeatures).map(key => {
const feature = allFeatures[key];
return {
...feature,
id: key,
label: feature.label ?? feature.name
};
});
return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label)));
};
export const featureTypes = {
ancestry: {
id: 'ancestry',

View file

@ -28,7 +28,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
actionType: new fields.StringField({
choices: CONFIG.DH.ITEM.actionTypes,
initial: 'action',
nullable: true
nullable: false,
required: true
})
};

View file

@ -30,4 +30,24 @@ export default class BaseEffect extends foundry.abstract.TypeDataModel {
})
};
}
static getDefaultObject() {
return {
name: 'New Effect',
id: foundry.utils.randomID(),
disabled: false,
img: 'icons/magic/life/heart-cross-blue.webp',
description: '',
statuses: [],
changes: [],
system: {
rangeDependence: {
enabled: false,
type: CONFIG.DH.GENERAL.rangeInclusion.withinRange.id,
target: CONFIG.DH.GENERAL.otherTargetTypes.hostile.id,
range: CONFIG.DH.GENERAL.range.melee.id
}
}
};
}
}

View file

@ -218,7 +218,8 @@ export default class CostField extends fields.ArrayField {
* @returns
*/
static getRealCosts(costs) {
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
const cloneCosts = foundry.utils.deepClone(costs),
realCosts = cloneCosts?.length ? cloneCosts.filter(c => c.enabled) : [];
let mergedCosts = [];
realCosts.forEach(c => {
const getCost = Object.values(mergedCosts).find(gc => gc.key === c.key);

View file

@ -206,7 +206,9 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
multiplier: new fields.StringField({
choices: CONFIG.DH.GENERAL.multiplierTypes,
initial: 'prof',
label: 'DAGGERHEART.ACTIONS.Config.damage.multiplier'
label: 'DAGGERHEART.ACTIONS.Config.damage.multiplier',
nullable: false,
required: true
}),
flatMultiplier: new fields.NumberField({
nullable: true,
@ -216,7 +218,9 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
dice: new fields.StringField({
choices: CONFIG.DH.GENERAL.diceTypes,
initial: 'd6',
label: 'DAGGERHEART.GENERAL.Dice.single'
label: 'DAGGERHEART.GENERAL.Dice.single',
nullable: false,
required: true
}),
bonus: new fields.NumberField({ nullable: true, initial: null, label: 'DAGGERHEART.GENERAL.bonus' }),
custom: new fields.SchemaField({

View file

@ -50,7 +50,7 @@ export default class EffectsField extends fields.ArrayField {
let effects = this.effects;
const messageTargets = [];
targets.forEach(async baseToken => {
if (this.hasSave && baseToken.saved.success === true) effects = this.effects.filter(e => e.onSave === true);
if (this.hasSave && token.saved.success === true) effects = this.effects.filter(e => e.onSave === true);
if (!effects.length) return;
const token = canvas.tokens.get(baseToken.id);

View file

@ -15,13 +15,17 @@ export class DHActionRollData extends foundry.abstract.DataModel {
bonus: new fields.NumberField({ nullable: true, initial: null, integer: true }),
advState: new fields.StringField({
choices: CONFIG.DH.ACTIONS.advantageState,
initial: 'neutral'
initial: 'neutral',
nullable: false,
required: true
}),
diceRolling: new fields.SchemaField({
multiplier: new fields.StringField({
choices: CONFIG.DH.GENERAL.diceSetNumbers,
initial: 'prof',
label: 'DAGGERHEART.ACTIONS.RollField.diceRolling.multiplier'
label: 'DAGGERHEART.ACTIONS.RollField.diceRolling.multiplier',
nullable: false,
required: true
}),
flatMultiplier: new fields.NumberField({
nullable: true,
@ -31,7 +35,9 @@ export class DHActionRollData extends foundry.abstract.DataModel {
dice: new fields.StringField({
choices: CONFIG.DH.GENERAL.diceTypes,
initial: CONFIG.DH.GENERAL.diceTypes.d6,
label: 'DAGGERHEART.ACTIONS.RollField.diceRolling.dice'
label: 'DAGGERHEART.ACTIONS.RollField.diceRolling.dice',
nullable: false,
required: true
}),
compare: new fields.StringField({
choices: CONFIG.DH.ACTIONS.diceCompare,

View file

@ -19,7 +19,9 @@ export default class SaveField extends fields.SchemaField {
difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }),
damageMod: new fields.StringField({
initial: CONFIG.DH.ACTIONS.damageOnSave.none.id,
choices: CONFIG.DH.ACTIONS.damageOnSave
choices: CONFIG.DH.ACTIONS.damageOnSave,
nullable: false,
required: true
})
};
super(saveFields, options, context);

View file

@ -7,7 +7,8 @@ export default class TargetField extends fields.SchemaField {
type: new fields.StringField({
choices: CONFIG.DH.GENERAL.targetTypes,
initial: CONFIG.DH.GENERAL.targetTypes.any.id,
nullable: true
nullable: true,
blank: true
}),
amount: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 })
};

View file

@ -1,5 +1,4 @@
import AttachableItem from './attachableItem.mjs';
import { armorFeatures } from '../../config/itemConfig.mjs';
export default class DHArmor extends AttachableItem {
/** @inheritDoc */
@ -25,7 +24,7 @@ export default class DHArmor extends AttachableItem {
new fields.SchemaField({
value: new fields.StringField({
required: true,
choices: CONFIG.DH.ITEM.armorFeatures,
choices: CONFIG.DH.ITEM.allArmorFeatures,
blank: true
}),
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
@ -60,13 +59,14 @@ export default class DHArmor extends AttachableItem {
const allowed = await super._preUpdate(changes, options, user);
if (allowed === false) return false;
const changedArmorFeatures = changes.system?.armorFeatures ?? [];
const removedFeatures = this.armorFeatures.filter(x => changedArmorFeatures.every(y => y.value !== x.value));
if (changes.system?.armorFeatures) {
const removed = this.armorFeatures.filter(x => !changes.system.armorFeatures.includes(x));
const added = changes.system.armorFeatures.filter(x => !this.armorFeatures.includes(x));
const added = changedArmorFeatures.filter(x => this.armorFeatures.every(y => y.value !== x.value));
const effectIds = [];
const actionIds = [];
for (var feature of removed) {
for (var feature of removedFeatures) {
effectIds.push(...feature.effectIds);
actionIds.push(...feature.actionIds);
}
@ -76,8 +76,9 @@ export default class DHArmor extends AttachableItem {
return acc;
}, {});
const allFeatures = CONFIG.DH.ITEM.allArmorFeatures();
for (const feature of added) {
const featureData = armorFeatures[feature.value];
const featureData = allFeatures[feature.value];
if (featureData.effects?.length > 0) {
const embeddedItems = await this.parent.createEmbeddedDocuments(
'ActiveEffect',
@ -91,7 +92,7 @@ export default class DHArmor extends AttachableItem {
}
const newActions = {};
if (featureData.actions?.length > 0) {
if (featureData.actions?.length > 0 || featureData.actions?.size > 0) {
for (let action of featureData.actions) {
const embeddedEffects = await this.parent.createEmbeddedDocuments(
'ActiveEffect',
@ -110,10 +111,12 @@ export default class DHArmor extends AttachableItem {
{
...cls.getSourceConfig(this),
...action,
type: action.type,
_id: actionId,
name: game.i18n.localize(action.name),
description: game.i18n.localize(action.description),
effects: embeddedEffects.map(x => ({ _id: x.id }))
effects: embeddedEffects.map(x => ({ _id: x.id })),
systemPath: 'actions'
},
{ parent: this }
);
@ -126,6 +129,10 @@ export default class DHArmor extends AttachableItem {
}
}
_onUpdate(a, b, c) {
super._onUpdate(a, b, c);
}
/**
* Generates a list of localized tags based on this item's type-specific properties.
* @returns {string[]} An array of localized tag strings.
@ -145,7 +152,8 @@ export default class DHArmor extends AttachableItem {
*/
_getLabels() {
const labels = [];
if(this.baseScore) labels.push(`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`)
if (this.baseScore)
labels.push(`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`);
return labels;
}

View file

@ -39,7 +39,7 @@ export default class DHWeapon extends AttachableItem {
new fields.SchemaField({
value: new fields.StringField({
required: true,
choices: CONFIG.DH.ITEM.weaponFeatures,
choices: CONFIG.DH.ITEM.allWeaponFeatures,
blank: true
}),
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
@ -116,13 +116,14 @@ export default class DHWeapon extends AttachableItem {
const allowed = await super._preUpdate(changes, options, user);
if (allowed === false) return false;
const changedWeaponFeatures = changes.system?.weaponFeatures ?? [];
const removedFeatures = this.weaponFeatures.filter(x => changedWeaponFeatures.every(y => y.value !== x.value));
if (changes.system?.weaponFeatures) {
const removed = this.weaponFeatures.filter(x => !changes.system.weaponFeatures.includes(x));
const added = changes.system.weaponFeatures.filter(x => !this.weaponFeatures.includes(x));
const added = changedWeaponFeatures.filter(x => this.weaponFeatures.every(y => y.value !== x.value));
const removedEffectsUpdate = [];
const removedActionsUpdate = [];
for (let weaponFeature of removed) {
for (let weaponFeature of removedFeatures) {
removedEffectsUpdate.push(...weaponFeature.effectIds);
removedActionsUpdate.push(...weaponFeature.actionIds);
}
@ -133,8 +134,9 @@ export default class DHWeapon extends AttachableItem {
return acc;
}, {});
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures();
for (let weaponFeature of added) {
const featureData = CONFIG.DH.ITEM.weaponFeatures[weaponFeature.value];
const featureData = allFeatures[weaponFeature.value];
if (featureData.effects?.length > 0) {
const embeddedItems = await this.parent.createEmbeddedDocuments(
'ActiveEffect',
@ -148,7 +150,7 @@ export default class DHWeapon extends AttachableItem {
}
const newActions = {};
if (featureData.actions?.length > 0) {
if (featureData.actions?.length > 0 || featureData.actions?.size > 0) {
for (let action of featureData.actions) {
const embeddedEffects = await this.parent.createEmbeddedDocuments(
'ActiveEffect',
@ -170,10 +172,12 @@ export default class DHWeapon extends AttachableItem {
{
...cls.getSourceConfig(this),
...action,
type: action.type,
_id: actionId,
name: game.i18n.localize(action.name),
description: game.i18n.localize(action.description),
effects: embeddedEffects.map(x => ({ _id: x.id }))
effects: embeddedEffects.map(x => ({ _id: x.id })),
systemPath: 'actions'
},
{ parent: this }
);

View file

@ -169,7 +169,7 @@ export const defaultLevelTiers = {
tiers: {
2: {
tier: 2,
name: 'Tier 2',
name: 'DAGGERHEART.APPLICATIONS.Levelup.tier2.name',
levels: {
start: 2,
end: 4
@ -232,7 +232,7 @@ export const defaultLevelTiers = {
},
3: {
tier: 3,
name: 'Tier 3',
name: 'DAGGERHEART.APPLICATIONS.Levelup.tier3.name',
levels: {
start: 5,
end: 7
@ -313,7 +313,7 @@ export const defaultLevelTiers = {
},
4: {
tier: 4,
name: 'Tier 4',
name: 'DAGGERHEART.APPLICATIONS.Levelup.tier4.name',
levels: {
start: 8,
end: 10

View file

@ -234,7 +234,7 @@ export class DhLevelup extends foundry.abstract.DataModel {
const subclassInTier = subclasses.some(x => x.tier === Number(tierKey));
return {
name: tier.name,
name: game.i18n.localize(tier.name),
active: this.currentLevel >= Math.min(...tier.belongingLevels),
groups: Object.keys(tier.options).map(optionKey => {
const option = tier.options[optionKey];

View file

@ -115,7 +115,35 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
label: new fields.StringField({ required: true, label: 'DAGGERHEART.GENERAL.label' }),
description: new fields.StringField()
})
)
),
itemFeatures: new fields.SchemaField({
weaponFeatures: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({ required: true }),
img: new fields.FilePathField({
initial: 'icons/magic/life/cross-worn-green.webp',
categories: ['IMAGE'],
base64: false
}),
description: new fields.HTMLField(),
actions: new ActionsField(),
effects: new fields.ArrayField(new fields.ObjectField())
})
),
armorFeatures: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({ required: true }),
img: new fields.FilePathField({
initial: 'icons/magic/life/cross-worn-green.webp',
categories: ['IMAGE'],
base64: false
}),
description: new fields.HTMLField(),
actions: new ActionsField(),
effects: new fields.ArrayField(new fields.ObjectField())
})
)
})
};
}
}

View file

@ -32,7 +32,7 @@ export default class DHItem extends foundry.documents.Item {
/** @inheritDoc */
static migrateData(source) {
if(source.system?.attack && !source.system.attack.type) source.system.attack.type = "attack";
if (source.system?.attack && !source.system.attack.type) source.system.attack.type = 'attack';
return super.migrateData(source);
}

View file

@ -9,12 +9,12 @@ export default function DhDualityRollEnricher(match, _options) {
}
function getDualityMessage(roll, flavor) {
const trait = roll.trait && abilities[roll.trait] ? game.i18n.localize(abilities[roll.trait].label) : null;
const trait = roll?.trait && abilities[roll.trait] ? game.i18n.localize(abilities[roll.trait].label) : null;
const label =
flavor ??
(roll.trait
(roll?.trait
? game.i18n.format('DAGGERHEART.GENERAL.rollWith', { roll: trait })
: roll.reaction
: roll?.reaction
? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll')
: game.i18n.localize('DAGGERHEART.GENERAL.duality'));
@ -22,9 +22,9 @@ function getDualityMessage(roll, flavor) {
? game.i18n.localize(abilities[roll.trait].label)
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
const advantage = roll.advantage
const advantage = roll?.advantage
? CONFIG.DH.ACTIONS.advantageState.advantage.value
: roll.disadvantage
: roll?.disadvantage
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
: undefined;
const advantageLabel =
@ -36,21 +36,21 @@ function getDualityMessage(roll, flavor) {
const dualityElement = document.createElement('span');
dualityElement.innerHTML = `
<button type="button" class="duality-roll-button${roll.inline ? ' inline' : ''}"
<button type="button" class="duality-roll-button${roll?.inline ? ' inline' : ''}"
data-title="${label}"
data-label="${dataLabel}"
data-reaction="${roll.reaction ? 'true' : 'false'}"
data-hope="${roll.hope ?? 'd12'}"
data-fear="${roll.fear ?? 'd12'}"
data-reaction="${roll?.reaction ? 'true' : 'false'}"
data-hope="${roll?.hope ?? 'd12'}"
data-fear="${roll?.fear ?? 'd12'}"
${advantage ? `data-advantage="${advantage}"` : ''}
${roll.difficulty !== undefined ? `data-difficulty="${roll.difficulty}"` : ''}
${roll.trait && abilities[roll.trait] ? `data-trait="${roll.trait}"` : ''}
${roll.advantage ? 'data-advantage="true"' : ''}
${roll.disadvantage ? 'data-disadvantage="true"' : ''}
${roll?.difficulty !== undefined ? `data-difficulty="${roll.difficulty}"` : ''}
${roll?.trait && abilities[roll.trait] ? `data-trait="${roll.trait}"` : ''}
${roll?.advantage ? 'data-advantage="true"' : ''}
${roll?.disadvantage ? 'data-disadvantage="true"' : ''}
>
${roll.reaction ? '<i class="fa-solid fa-reply"></i>' : '<i class="fa-solid fa-circle-half-stroke"></i>'}
${roll?.reaction ? '<i class="fa-solid fa-reply"></i>' : '<i class="fa-solid fa-circle-half-stroke"></i>'}
${label}
${!flavor && (roll.difficulty || advantageLabel) ? `(${[roll.difficulty, advantageLabel ? game.i18n.localize(`DAGGERHEART.GENERAL.${advantageLabel}.short`) : null].filter(x => x).join(' ')})` : ''}
${!flavor && (roll?.difficulty || advantageLabel) ? `(${[roll.difficulty, advantageLabel ? game.i18n.localize(`DAGGERHEART.GENERAL.${advantageLabel}.short`) : null].filter(x => x).join(' ')})` : ''}
</button>
`;

View file

@ -29,14 +29,11 @@ export const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs',
'systems/daggerheart/templates/dialogs/downtime/activities.hbs',
'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs',
'systems/daggerheart/templates/ui/chat/parts/roll-part.hbs',
'systems/daggerheart/templates/ui/chat/parts/damage-part.hbs',
'systems/daggerheart/templates/ui/chat/parts/target-part.hbs',
'systems/daggerheart/templates/ui/chat/parts/button-part.hbs',
'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs',
'systems/daggerheart/templates/scene/dh-config.hbs'
]);
};