Merge branch 'development' into feature-fix/template-enricher-functionality

This commit is contained in:
Chris Ryan 2025-09-07 08:56:39 +10:00
commit 121f6839a9
71 changed files with 1290 additions and 427 deletions

View file

@ -44,12 +44,14 @@ We encourage contributors to leave comments or open Discussions when proposing s
## 🧾 Issue & PR Best Practices ## 🧾 Issue & PR Best Practices
**For Issues:** **For Issues:**
- Use clear, descriptive titles - Use clear, descriptive titles
- Provide a concise explanation of the problem or idea - Provide a concise explanation of the problem or idea
- Include reproduction steps or example scenarios if it's a bug - Include reproduction steps or example scenarios if it's a bug
- Add screenshots or logs if helpful - Add screenshots or logs if helpful
**For Pull Requests:** **For Pull Requests:**
- Use a clear title summarizing the change - Use a clear title summarizing the change
- Provide a brief description of what your code does and why - Provide a brief description of what your code does and why
- Link to any related Issues - Link to any related Issues
@ -71,6 +73,6 @@ Discussions are currently happening on GitHub — in Issues, PRs, and [GitHub Di
## 🤗 Thank You! ## 🤗 Thank You!
Whether you're fixing a typo or designing entire mechanics — every contribution matters. Thank you for helping bring *Daggerheart* to life in FoundryVTT through **Foundryborne**! Whether you're fixing a typo or designing entire mechanics — every contribution matters. Thank you for helping bring _Daggerheart_ to life in FoundryVTT through **Foundryborne**!
🐸🛠️ 🐸🛠️

View file

@ -1,5 +1,6 @@
import { SYSTEM } from './module/config/system.mjs'; import { SYSTEM } from './module/config/system.mjs';
import * as applications from './module/applications/_module.mjs'; import * as applications from './module/applications/_module.mjs';
import * as data from './module/data/_module.mjs';
import * as models from './module/data/_module.mjs'; import * as models from './module/data/_module.mjs';
import * as documents from './module/documents/_module.mjs'; import * as documents from './module/documents/_module.mjs';
import * as dice from './module/dice/_module.mjs'; import * as dice from './module/dice/_module.mjs';
@ -26,6 +27,7 @@ Hooks.once('init', () => {
CONFIG.DH = SYSTEM; CONFIG.DH = SYSTEM;
game.system.api = { game.system.api = {
applications, applications,
data,
models, models,
documents, documents,
dice, dice,
@ -133,6 +135,8 @@ Hooks.once('init', () => {
CONFIG.ui.combat = applications.ui.DhCombatTracker; CONFIG.ui.combat = applications.ui.DhCombatTracker;
CONFIG.ui.chat = applications.ui.DhChatLog; CONFIG.ui.chat = applications.ui.DhChatLog;
CONFIG.ui.hotbar = applications.ui.DhHotbar; CONFIG.ui.hotbar = applications.ui.DhHotbar;
CONFIG.ui.sidebar = applications.sidebar.DhSidebar;
CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu;
CONFIG.Token.rulerClass = placeables.DhTokenRuler; CONFIG.Token.rulerClass = placeables.DhTokenRuler;
CONFIG.ui.resources = applications.ui.DhFearTracker; CONFIG.ui.resources = applications.ui.DhFearTracker;
@ -309,5 +313,5 @@ Hooks.on('moveToken', async (movedToken, data) => {
} }
}); });
Hooks.on("renderCompendiumDirectory", (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
Hooks.on("renderDocumentDirectory", (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); Hooks.on('renderDocumentDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));

View file

@ -1917,6 +1917,7 @@
"roll": "Roll", "roll": "Roll",
"rules": "Rules", "rules": "Rules",
"types": "Types", "types": "Types",
"itemFeatures": "Item Features",
"questions": "Questions" "questions": "Questions"
}, },
"Tiers": { "Tiers": {
@ -1934,6 +1935,7 @@
"amount": "Amount", "amount": "Amount",
"any": "Any", "any": "Any",
"armor": "Armor", "armor": "Armor",
"armorFeatures": "Armor Features",
"armors": "Armors", "armors": "Armors",
"armorScore": "Armor Score", "armorScore": "Armor Score",
"activeEffects": "Active Effects", "activeEffects": "Active Effects",
@ -2046,6 +2048,7 @@
"used": "Used", "used": "Used",
"uses": "Uses", "uses": "Uses",
"value": "Value", "value": "Value",
"weaponFeatures": "Weapon Features",
"weapons": "Weapons", "weapons": "Weapons",
"withThing": "With {thing}" "withThing": "With {thing}"
}, },
@ -2276,7 +2279,9 @@
}, },
"Homebrew": { "Homebrew": {
"newDowntimeMove": "Downtime Move", "newDowntimeMove": "Downtime Move",
"newFeature": "New ItemFeature",
"downtimeMoves": "Downtime Moves", "downtimeMoves": "Downtime Moves",
"itemFeatures": "Item Features",
"nrChoices": "# Moves Per Rest", "nrChoices": "# Moves Per Rest",
"resetMovesTitle": "Reset {type} Downtime Moves", "resetMovesTitle": "Reset {type} Downtime Moves",
"resetMovesText": "Are you sure you want to reset?", "resetMovesText": "Are you sure you want to reset?",
@ -2420,6 +2425,10 @@
"heal": "Heal", "heal": "Heal",
"applyHealing": "Apply Healing" "applyHealing": "Apply Healing"
}, },
"refreshMessage": {
"title": "Feature Refresh",
"header": "Refreshed"
},
"reroll": { "reroll": {
"confirmTitle": "Reroll Dice", "confirmTitle": "Reroll Dice",
"confirmText": "Are you sure you want to reroll?" "confirmText": "Are you sure you want to reroll?"
@ -2534,8 +2543,16 @@
"multiclassAlreadyPresent": "You already have a class and multiclass", "multiclassAlreadyPresent": "You already have a class and multiclass",
"subclassesAlreadyPresent": "You already have a class and multiclass subclass", "subclassesAlreadyPresent": "You already have a class and multiclass subclass",
"noDiceSystem": "Your selected dice {system} does not have a {faces} dice", "noDiceSystem": "Your selected dice {system} does not have a {faces} dice",
"gmMenuRefresh": "You refreshed all actions and resources {types}",
"subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class." "subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class."
}, },
"Sidebar": {
"daggerheartMenu": {
"title": "Daggerheart Menu",
"startSession": "Start Session",
"startScene": "Start Scene"
}
},
"Tooltip": { "Tooltip": {
"disableEffect": "Disable Effect", "disableEffect": "Disable Effect",
"enableEffect": "Enable Effect", "enableEffect": "Enable Effect",

View file

@ -6,5 +6,6 @@ export * as scene from './scene/_module.mjs';
export * as settings from './settings/_module.mjs'; export * as settings from './settings/_module.mjs';
export * as sheets from './sheets/_module.mjs'; export * as sheets from './sheets/_module.mjs';
export * as sheetConfigs from './sheets-configs/_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 ui from './ui/_module.mjs';
export * as ux from './ux/_module.mjs'; export * as ux from './ux/_module.mjs';

View file

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

View file

@ -33,7 +33,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
main: { template: 'systems/daggerheart/templates/settings/appearance-settings/main.hbs' }, main: { template: 'systems/daggerheart/templates/settings/appearance-settings/main.hbs' },
diceSoNice: { template: 'systems/daggerheart/templates/settings/appearance-settings/diceSoNice.hbs' }, diceSoNice: { template: 'systems/daggerheart/templates/settings/appearance-settings/diceSoNice.hbs' },
footer: { template: "templates/generic/form-footer.hbs" } footer: { template: 'templates/generic/form-footer.hbs' }
}; };
/** @inheritdoc */ /** @inheritdoc */
@ -41,7 +41,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
general: { general: {
tabs: [ tabs: [
{ id: 'main', label: 'DAGGERHEART.GENERAL.Tabs.general' }, { id: 'main', label: 'DAGGERHEART.GENERAL.Tabs.general' },
{ id: 'diceSoNice', label: 'DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.title' }, { id: 'diceSoNice', label: 'DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.title' }
], ],
initial: 'main' initial: 'main'
}, },
@ -83,7 +83,8 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
/**@inheritdoc */ /**@inheritdoc */
async _prepareContext(options) { async _prepareContext(options) {
const context = await super._prepareContext(options); const context = await super._prepareContext(options);
if (options.isFirstRender) this.setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance); if (options.isFirstRender)
this.setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
context.setting = this.setting; context.setting = this.setting;
context.fields = this.setting.schema.fields; context.fields = this.setting.schema.fields;
@ -99,18 +100,17 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
const partContext = await super._preparePartContext(partId, context, options); const partContext = await super._preparePartContext(partId, context, options);
if (partId in context.tabs) partContext.tab = partContext.tabs[partId]; if (partId in context.tabs) partContext.tab = partContext.tabs[partId];
switch (partId) { switch (partId) {
case "diceSoNice": case 'diceSoNice':
await this.prepareDiceSoNiceContext(partContext); await this.prepareDiceSoNiceContext(partContext);
break; break;
case "footer": case 'footer':
partContext.buttons = [ partContext.buttons = [
{ type: "button", action: "reset", icon: "fa-solid fa-arrow-rotate-left", label: "Reset" }, { type: 'button', action: 'reset', icon: 'fa-solid fa-arrow-rotate-left', label: 'Reset' },
{ type: "submit", icon: "fa-solid fa-floppy-disk", label: "Save Changes" } { type: 'submit', icon: 'fa-solid fa-floppy-disk', label: 'Save Changes' }
]; ];
break; break;
} }
return partContext; return partContext;
} }
/** /**
@ -120,32 +120,44 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
* @protected * @protected
*/ */
async prepareDiceSoNiceContext(context) { async prepareDiceSoNiceContext(context) {
context.diceSoNiceTextures = Object.entries(game.dice3d.exports.TEXTURELIST).reduce((acc, [k, v]) => ({ context.diceSoNiceTextures = Object.entries(game.dice3d.exports.TEXTURELIST).reduce(
(acc, [k, v]) => ({
...acc, ...acc,
[k]: v.name [k]: v.name
}), {}); }),
context.diceSoNiceColorsets = Object.values(game.dice3d.exports.COLORSETS).reduce((acc, v) => ({ {}
);
context.diceSoNiceColorsets = Object.values(game.dice3d.exports.COLORSETS).reduce(
(acc, v) => ({
...acc, ...acc,
[v.id]: v.description [v.id]: v.description
}), {}); }),
context.diceSoNiceMaterials = Object.keys(game.dice3d.DiceFactory.material_options).reduce((acc, key) => ({ {}
);
context.diceSoNiceMaterials = Object.keys(game.dice3d.DiceFactory.material_options).reduce(
(acc, key) => ({
...acc, ...acc,
[key]: `DICESONICE.Material${key.capitalize()}` [key]: `DICESONICE.Material${key.capitalize()}`
}), {}); }),
context.diceSoNiceSystems = Object.fromEntries([...game.dice3d.DiceFactory.systems].map(([k, v]) => [k, v.name])); {}
);
context.diceSoNiceSystems = Object.fromEntries(
[...game.dice3d.DiceFactory.systems].map(([k, v]) => [k, v.name])
);
foundry.utils.mergeObject(context.dsnTabs, [ foundry.utils.mergeObject(
"hope", context.dsnTabs,
"fear", ['hope', 'fear', 'advantage', 'disadvantage'].reduce(
"advantage", (acc, key) => ({
"disadvantage",
].reduce((acc, key) => ({
...acc, ...acc,
[key]: { [key]: {
values: this.setting.diceSoNice[key], values: this.setting.diceSoNice[key],
fields: this.setting.schema.getField(`diceSoNice.${key}`).fields, fields: this.setting.schema.getField(`diceSoNice.${key}`).fields
} }
}), {})); }),
{}
)
);
} }
/** /**
@ -169,7 +181,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
* @type {ApplicationClickAction} * @type {ApplicationClickAction}
*/ */
static async #onPreview(_, target) { static async #onPreview(_, target) {
const formData = new foundry.applications.ux.FormDataExtended(target.closest("form")); const formData = new foundry.applications.ux.FormDataExtended(target.closest('form'));
const { diceSoNice } = foundry.utils.expandObject(formData.object); const { diceSoNice } = foundry.utils.expandObject(formData.object);
const { key } = target.dataset; const { key } = target.dataset;
const faces = ['advantage', 'disadvantage'].includes(key) ? 'd6' : 'd12'; const faces = ['advantage', 'disadvantage'].includes(key) ? 'd6' : 'd12';

View file

@ -53,6 +53,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
settings: { template: 'systems/daggerheart/templates/settings/homebrew-settings/settings.hbs' }, settings: { template: 'systems/daggerheart/templates/settings/homebrew-settings/settings.hbs' },
domains: { template: 'systems/daggerheart/templates/settings/homebrew-settings/domains.hbs' }, domains: { template: 'systems/daggerheart/templates/settings/homebrew-settings/domains.hbs' },
types: { template: 'systems/daggerheart/templates/settings/homebrew-settings/types.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' }, downtime: { template: 'systems/daggerheart/templates/settings/homebrew-settings/downtime.hbs' },
footer: { template: 'systems/daggerheart/templates/settings/homebrew-settings/footer.hbs' } footer: { template: 'systems/daggerheart/templates/settings/homebrew-settings/footer.hbs' }
}; };
@ -60,7 +61,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
/** @inheritdoc */ /** @inheritdoc */
static TABS = { static TABS = {
main: { main: {
tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'types' }, { id: 'downtime' }], tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'types' }, { id: 'itemFeatures' }, { id: 'downtime' }],
initial: 'settings', initial: 'settings',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs' labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
} }
@ -115,33 +116,53 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
} }
static async addItem(_, target) { static async addItem(_, target) {
const { type } = target.dataset;
if (['shortRest', 'longRest'].includes(type)) {
await this.settings.updateSource({ await this.settings.updateSource({
[`restMoves.${target.dataset.type}.moves.${foundry.utils.randomID()}`]: { [`restMoves.${type}.moves.${foundry.utils.randomID()}`]: {
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'), name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'),
img: 'icons/magic/life/cross-worn-green.webp', img: 'icons/magic/life/cross-worn-green.webp',
description: '', description: '',
actions: [] 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(); this.render();
} }
static async editItem(_, target) { static async editItem(_, target) {
const move = this.settings.restMoves[target.dataset.type].moves[target.dataset.id]; const { type, id } = target.dataset;
const path = `restMoves.${target.dataset.type}.moves.${target.dataset.id}`; const isDowntime = ['shortRest', 'longRest'].includes(type);
const editedMove = await game.system.api.applications.sheetConfigs.DowntimeConfig.configure( const path = isDowntime ? `restMoves.${type}.moves.${id}` : `itemFeatures.${type}.${id}`;
move, const featureBase = isDowntime ? this.settings.restMoves[type].moves[id] : this.settings.itemFeatures[type][id];
path,
this.settings
);
if (!editedMove) return;
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) { async updateAction(data, type, id) {
const isDowntime = ['shortRest', 'longRest'].includes(type);
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
await this.settings.updateSource({ await this.settings.updateSource({
[`restMoves.${type}.moves.${id}`]: { [`${path}.${id}`]: {
actions: data.actions, actions: data.actions,
name: data.name, name: data.name,
icon: data.icon, icon: data.icon,
@ -149,12 +170,16 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
description: data.description description: data.description
} }
}); });
this.render(); this.render();
} }
static async removeItem(_, target) { 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({ await this.settings.updateSource({
[`restMoves.${target.dataset.type}.moves.-=${target.dataset.id}`]: null [`${path}.-=${id}`]: null
}); });
this.render(); 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 CharacterSettings } from './character-settings.mjs';
export { default as AdversarySettings } from './adversary-settings.mjs'; export { default as AdversarySettings } from './adversary-settings.mjs';
export { default as CompanionSettings } from './companion-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 EnvironmentSettings } from './environment-settings.mjs';
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs'; export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';
export { default as DhTokenConfig } from './token-config.mjs'; export { default as DhTokenConfig } from './token-config.mjs';

View file

@ -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 = { options.quantity = {
label: 'DAGGERHEART.GENERAL.itemQuantity', label: 'DAGGERHEART.GENERAL.itemQuantity',
group: 'Global' 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) { async _preparePartContext(partId, context) {
const partContext = await super._preparePartContext(partId, context); const partContext = await super._preparePartContext(partId, context);
switch (partId) { 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; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DowntimeConfig extends HandlebarsApplicationMixin(ApplicationV2) { export default class SettingFeatureConfig extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(move, movePath, settings, options) { constructor(move, movePath, settings, optionalParts, options) {
super(options); super(options);
this.move = move; this.move = move;
@ -12,6 +12,10 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
this.movePath = movePath; this.movePath = movePath;
this.actionsPath = `${movePath}.actions`; this.actionsPath = `${movePath}.actions`;
this.settings = settings; this.settings = settings;
const { hasIcon, hasEffects } = optionalParts;
this.hasIcon = hasIcon;
this.hasEffects = hasEffects;
} }
get title() { get title() {
@ -30,6 +34,7 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
addItem: this.addItem, addItem: this.addItem,
editItem: this.editItem, editItem: this.editItem,
removeItem: this.removeItem, removeItem: this.removeItem,
addEffect: this.addEffect,
resetMoves: this.resetMoves, resetMoves: this.resetMoves,
saveForm: this.saveForm 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' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
main: { template: 'systems/daggerheart/templates/settings/downtime-config/main.hbs' }, main: { template: 'systems/daggerheart/templates/settings/downtime-config/main.hbs' },
actions: { template: 'systems/daggerheart/templates/settings/downtime-config/actions.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' } footer: { template: 'systems/daggerheart/templates/settings/downtime-config/footer.hbs' }
}; };
/** @inheritdoc */ /** @inheritdoc */
static TABS = { static TABS = {
primary: { primary: {
tabs: [{ id: 'main' }, { id: 'actions' }], tabs: [{ id: 'main' }, { id: 'actions' }, { id: 'effects' }],
initial: 'main', initial: 'main',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs' labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
} }
@ -55,6 +61,9 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
async _prepareContext(_options) { async _prepareContext(_options) {
const context = await super._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 = this.move;
context.move.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML( context.move.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML(
context.move.description context.move.description
@ -130,14 +139,31 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
} }
static async editItem(_, target) { static async editItem(_, target) {
const actionId = target.dataset.id; const { type, id } = target.dataset;
const action = this.move.actions.get(actionId); 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();
} else {
const action = this.move.actions.get(id);
await new DHActionConfig(action, async updatedMove => { await new DHActionConfig(action, async updatedMove => {
await this.settings.updateSource({ [`${this.actionsPath}.${actionId}`]: updatedMove }); await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove });
this.move = foundry.utils.getProperty(this.settings, this.movePath); this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render(); this.render();
}).render(true); }).render(true);
} }
}
static async removeItem(_, target) { static async removeItem(_, target) {
await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null }); await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
@ -145,16 +171,38 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
this.render(); 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() {} static resetMoves() {}
_filterTabs(tabs) {
return this.hasEffects
? tabs
: Object.keys(tabs).reduce((acc, key) => {
if (key !== 'effects') acc[key] = tabs[key];
return acc;
}, {});
}
/** @override */ /** @override */
_onClose(options = {}) { _onClose(options = {}) {
if (!options.submitted) this.move = null; 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 => { 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.addEventListener('close', () => resolve(app.move), { once: true });
app.render({ force: true }); app.render({ force: true });
}); });

View file

@ -416,9 +416,12 @@ export default function DHApplicationMixin(Base) {
icon: 'fa-solid fa-ban', icon: 'fa-solid fa-ban',
condition: target => { condition: target => {
const doc = getDocFromElementSync(target); const doc = getDocFromElementSync(target);
return doc && doc.system?.actions?.some(a => a.type === "beastform"); return doc && doc.system?.actions?.some(a => a.type === 'beastform');
}, },
callback: async target => game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(await getDocFromElement(target)) callback: async target =>
game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(
await getDocFromElement(target)
)
}); });
options.unshift({ options.unshift({
@ -433,7 +436,7 @@ export default function DHApplicationMixin(Base) {
action = doc?.system?.attack ?? doc; action = doc?.system?.attack ?? doc;
const config = action.prepareConfig(event); const config = action.prepareConfig(event);
config.hasRoll = false; config.hasRoll = false;
return action && action.workflow.get("damage").execute(config, null, true); return action && action.workflow.get('damage').execute(config, null, true);
} }
}); });

View file

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

View file

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

@ -112,7 +112,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
if (event.currentTarget.dataset.directDamage) { if (event.currentTarget.dataset.directDamage) {
const config = action.prepareConfig(event); const config = action.prepareConfig(event);
config.hasRoll = false; config.hasRoll = false;
action.workflow.get("damage").execute(config, null, true); action.workflow.get('damage').execute(config, null, true);
} else action.use(event); } else action.use(event);
} }

View file

@ -1,4 +1,4 @@
import { emitAsGM, GMUpdateEvent, socketEvent } from "../../systemRegistration/socket.mjs"; import { emitAsGM, GMUpdateEvent, socketEvent } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -106,6 +106,10 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
} }
async updateFear(value) { async updateFear(value) {
return emitAsGM(GMUpdateEvent.UpdateFear, game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), value); return emitAsGM(
GMUpdateEvent.UpdateFear,
game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
value
);
} }
} }

View file

@ -86,10 +86,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
this.presets = options.presets ?? {}; this.presets = options.presets ?? {};
const width = this.presets?.render?.noFolder === true || this.presets?.render?.lite === true ? 600 : 850; const width = this.presets?.render?.noFolder === true || this.presets?.render?.lite === true ? 600 : 850;
if(this.rendered) if (this.rendered) this.setPosition({ width });
this.setPosition({ width }); else options.position.width = width;
else
options.position.width = width;
await super._preRender(context, options); await super._preRender(context, options);
} }
@ -100,7 +98,9 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
this.element this.element
.querySelectorAll('[data-action="selectFolder"]') .querySelectorAll('[data-action="selectFolder"]')
.forEach(element => element.classList.toggle('is-selected', element.dataset.folderId === this.selectedMenu.path.join('.'))); .forEach(element =>
element.classList.toggle('is-selected', element.dataset.folderId === this.selectedMenu.path.join('.'))
);
this._createSearchFilter(); this._createSearchFilter();
@ -108,7 +108,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
this.element.classList.toggle('no-folder', this.presets?.render?.noFolder === true); this.element.classList.toggle('no-folder', this.presets?.render?.noFolder === true);
this.element.classList.toggle('no-filter', this.presets?.render?.noFilter === true); this.element.classList.toggle('no-filter', this.presets?.render?.noFilter === true);
this.element.querySelectorAll('.folder-list > [data-action="selectFolder"]').forEach(element => { this.element.querySelectorAll('.folder-list > [data-action="selectFolder"]').forEach(element => {
element.hidden = this.presets.render?.folders?.length && !this.presets.render.folders.includes(element.dataset.folderId); element.hidden =
this.presets.render?.folders?.length && !this.presets.render.folders.includes(element.dataset.folderId);
}); });
} }
@ -157,7 +158,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
: []; : [];
folders.push(folder); folders.push(folder);
}); });
folders.sort((a, b) => a.label.localeCompare(b.label)) folders.sort((a, b) => a.label.localeCompare(b.label));
return folders; return folders;
} }
@ -181,8 +182,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
await this.render({ force: true, presets: this.presets }); await this.render({ force: true, presets: this.presets });
if(this.selectedMenu?.data?.type?.length) if (this.selectedMenu?.data?.type?.length) this.loadItems();
this.loadItems();
} }
_replaceHTML(result, content, options) { _replaceHTML(result, content, options) {
@ -201,24 +201,26 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
const items = await pack.getDocuments({ type__in: this.selectedMenu?.data?.type }); const items = await pack.getDocuments({ type__in: this.selectedMenu?.data?.type });
resolve(items); resolve(items);
}) })
) );
}); });
Promise.all(promises).then(async result => { Promise.all(promises).then(async result => {
this.items = ItemBrowser.sortBy(result.flatMap(r => r), 'name'); this.items = ItemBrowser.sortBy(
result.flatMap(r => r),
'name'
);
this.fieldFilter = this._createFieldFilter(); this.fieldFilter = this._createFieldFilter();
if (this.presets?.filter) { if (this.presets?.filter) {
Object.entries(this.presets.filter).forEach( Object.entries(this.presets.filter).forEach(([k, v]) => {
([k, v]) => { const filter = this.fieldFilter.find(c => c.name === k);
const filter = this.fieldFilter.find(c => c.name === k)
if (filter) filter.value = v.value; if (filter) filter.value = v.value;
} });
);
// await this._onInputFilterBrowser(); // await this._onInputFilterBrowser();
} }
const filterList = await foundry.applications.handlebars.renderTemplate('systems/daggerheart/templates/ui/itemBrowser/filterContainer.hbs', const filterList = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/itemBrowser/filterContainer.hbs',
{ {
fieldFilter: this.fieldFilter, fieldFilter: this.fieldFilter,
presets: this.presets, presets: this.presets,
@ -228,12 +230,11 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
this.element.querySelector('.filter-content .wrapper').innerHTML = filterList; this.element.querySelector('.filter-content .wrapper').innerHTML = filterList;
const filterContainer = this.element.querySelector('.filter-header > [data-action="expandContent"]'); const filterContainer = this.element.querySelector('.filter-header > [data-action="expandContent"]');
if(this.fieldFilter.length === 0) if (this.fieldFilter.length === 0) filterContainer.setAttribute('disabled', '');
filterContainer.setAttribute('disabled', ''); else filterContainer.removeAttribute('disabled');
else
filterContainer.removeAttribute('disabled');
const itemList = await foundry.applications.handlebars.renderTemplate('systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs', const itemList = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs',
{ {
items: this.items, items: this.items,
menu: this.selectedMenu, menu: this.selectedMenu,
@ -255,7 +256,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
toggleLoader(state) { toggleLoader(state) {
const container = this.element.querySelector('.item-list'); const container = this.element.querySelector('.item-list');
return setTimeout(() => { return setTimeout(() => {
container.classList.toggle("loader", state); container.classList.toggle('loader', state);
}, 100); }, 100);
} }
@ -509,13 +510,13 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
const sectionId = html.dataset.tab, const sectionId = html.dataset.tab,
menus = { menus = {
actors: { actors: {
folder: "adversaries", folder: 'adversaries',
render: { render: {
folders: ["adversaries", "characters", "environments"] folders: ['adversaries', 'characters', 'environments']
} }
}, },
items: { items: {
folder: "equipments", folder: 'equipments',
render: { render: {
noFolder: true noFolder: true
} }
@ -524,16 +525,16 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
}; };
if (Object.keys(menus).includes(sectionId)) { if (Object.keys(menus).includes(sectionId)) {
const headerActions = html.querySelector(".header-actions"); const headerActions = html.querySelector('.header-actions');
const button = document.createElement("button"); const button = document.createElement('button');
button.type = "button"; button.type = 'button';
button.classList.add("open-compendium-browser"); button.classList.add('open-compendium-browser');
button.innerHTML = ` button.innerHTML = `
<i class="fa-solid fa-book-atlas"></i> <i class="fa-solid fa-book-atlas"></i>
${game.i18n.localize("DAGGERHEART.UI.Tooltip.compendiumBrowser")} ${game.i18n.localize('DAGGERHEART.UI.Tooltip.compendiumBrowser')}
`; `;
button.addEventListener("click", event => { button.addEventListener('click', event => {
ui.compendiumBrowser.open(menus[sectionId]); ui.compendiumBrowser.open(menus[sectionId]);
}); });

View file

@ -199,8 +199,7 @@ export const typeConfig = {
key: 'system.itemFeatures', key: 'system.itemFeatures',
label: 'DAGGERHEART.GENERAL.features', label: 'DAGGERHEART.GENERAL.features',
choices: () => choices: () =>
Object.entries(CONFIG.DH.ITEM.weaponFeatures) Object.entries(CONFIG.DH.ITEM.weaponFeatures).map(([k, v]) => ({ value: k, label: v.label })),
.map(([k, v]) => ({ value: k, label: v.label })),
operator: 'contains3' operator: 'contains3'
} }
] ]
@ -241,8 +240,7 @@ export const typeConfig = {
key: 'system.itemFeatures', key: 'system.itemFeatures',
label: 'DAGGERHEART.GENERAL.features', label: 'DAGGERHEART.GENERAL.features',
choices: () => choices: () =>
Object.entries(CONFIG.DH.ITEM.armorFeatures) Object.entries(CONFIG.DH.ITEM.armorFeatures).map(([k, v]) => ({ value: k, label: v.label })),
.map(([k, v]) => ({ value: k, label: v.label })),
operator: 'contains3' operator: 'contains3'
} }
] ]
@ -372,14 +370,30 @@ export const typeConfig = {
label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait' 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: [ filters: [
{ {
key: 'system.linkedClass.uuid', key: 'system.linkedClass.uuid',
label: 'Class', label: 'Class',
choices: (items) => { choices: items => {
const list = items.map(item => ({ value: item.system.linkedClass.uuid, label: item.system.linkedClass.name })); const list = items.map(item => ({
value: item.system.linkedClass.uuid,
label: item.system.linkedClass.name
}));
return list.reduce((a, c) => { return list.reduce((a, c) => {
if(!(a.find(i => i.value === c.value))) a.push(c); if (!a.find(i => i.value === c.value)) a.push(c);
return a; return a;
}, []); }, []);
} }
@ -417,7 +431,7 @@ export const compendiumConfig = {
id: 'characters', id: 'characters',
keys: ['characters'], keys: ['characters'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.characters', label: 'DAGGERHEART.UI.ItemBrowser.folders.characters',
type: ['character'], type: ['character']
// listType: 'characters' // listType: 'characters'
}, },
adversaries: { adversaries: {
@ -431,7 +445,7 @@ export const compendiumConfig = {
id: 'ancestries', id: 'ancestries',
keys: ['ancestries'], keys: ['ancestries'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.ancestries', label: 'DAGGERHEART.UI.ItemBrowser.folders.ancestries',
type: ['ancestry'], type: ['ancestry']
/* folders: { /* folders: {
features: { features: {
id: 'features', id: 'features',
@ -516,7 +530,7 @@ export const compendiumConfig = {
id: 'communities', id: 'communities',
keys: ['communities'], keys: ['communities'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.communities', label: 'DAGGERHEART.UI.ItemBrowser.folders.communities',
type: ['community'], type: ['community']
/* folders: { /* folders: {
features: { features: {
id: 'features', id: 'features',
@ -537,7 +551,7 @@ export const compendiumConfig = {
keys: ['beastforms'], keys: ['beastforms'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.beastforms', label: 'DAGGERHEART.UI.ItemBrowser.folders.beastforms',
type: ['beastform'], type: ['beastform'],
listType: 'beastforms', listType: 'beastforms'
/* folders: { /* folders: {
features: { features: {
id: 'features', id: 'features',

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 = { export const weaponFeatures = {
barrier: { barrier: {
label: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.name', 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 = { export const featureTypes = {
ancestry: { ancestry: {
id: 'ancestry', id: 'ancestry',

View file

@ -32,19 +32,19 @@ export const gameSettings = {
export const actionAutomationChoices = { export const actionAutomationChoices = {
never: { never: {
id: "never", id: 'never',
label: "Never" label: 'Never'
}, },
showDialog: { showDialog: {
id: "showDialog", id: 'showDialog',
label: "Show Dialog only" label: 'Show Dialog only'
}, },
// npcOnly: { // npcOnly: {
// id: "npcOnly", // id: "npcOnly",
// label: "Always for non-characters" // label: "Always for non-characters"
// }, // },
always: { always: {
id: "always", id: 'always',
label: "Always" label: 'Always'
}
} }
};

View file

@ -34,8 +34,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
this.extraSchemas.forEach(s => { this.extraSchemas.forEach(s => {
let clsField = this.getActionField(s); let clsField = this.getActionField(s);
if (clsField) if (clsField) schemaFields[s] = new clsField();
schemaFields[s] = new clsField();
}); });
return schemaFields; return schemaFields;
@ -54,7 +53,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
let clsField = this.constructor.getActionField(s); let clsField = this.constructor.getActionField(s);
if (clsField?.execute) { if (clsField?.execute) {
workflow.set(s, { order: clsField.order, execute: clsField.execute.bind(this) }); workflow.set(s, { order: clsField.order, execute: clsField.execute.bind(this) });
if( s === "damage" ) workflow.set("applyDamage", { order: 75, execute: clsField.applyDamage.bind(this) } ); if (s === 'damage')
workflow.set('applyDamage', { order: 75, execute: clsField.applyDamage.bind(this) });
} }
}); });
return new Map([...workflow.entries()].sort(([aKey, aValue], [bKey, bValue]) => aValue.order - bValue.order)); return new Map([...workflow.entries()].sort(([aKey, aValue], [bKey, bValue]) => aValue.order - bValue.order));
@ -64,9 +64,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
* Getter returning the workflow property or creating it the first time the property is called * Getter returning the workflow property or creating it the first time the property is called
*/ */
get workflow() { get workflow() {
if ( this.hasOwnProperty("_workflow") ) return this._workflow; if (this.hasOwnProperty('_workflow')) return this._workflow;
const workflow = Object.freeze(this.defineWorkflow()); const workflow = Object.freeze(this.defineWorkflow());
Object.defineProperty(this, "_workflow", {value: workflow, writable: false}); Object.defineProperty(this, '_workflow', { value: workflow, writable: false });
return workflow; return workflow;
} }
@ -169,7 +169,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
async executeWorkflow(config) { async executeWorkflow(config) {
for (const [key, part] of this.workflow) { for (const [key, part] of this.workflow) {
if (Hooks.call(`${CONFIG.DH.id}.pre${key.capitalize()}Action`, this, config) === false) return; if (Hooks.call(`${CONFIG.DH.id}.pre${key.capitalize()}Action`, this, config) === false) return;
if(await part.execute(config) === false) return; if ((await part.execute(config)) === false) return;
if (Hooks.call(`${CONFIG.DH.id}.post${key.capitalize()}Action`, this, config) === false) return; if (Hooks.call(`${CONFIG.DH.id}.post${key.capitalize()}Action`, this, config) === false) return;
} }
} }
@ -241,8 +241,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
prepareConfig(event) { prepareConfig(event) {
const config = this.prepareBaseConfig(event); const config = this.prepareBaseConfig(event);
for (const clsField of Object.values(this.schema.fields)) { for (const clsField of Object.values(this.schema.fields)) {
if (clsField?.prepareConfig) if (clsField?.prepareConfig) if (clsField.prepareConfig.call(this, config) === false) return false;
if(clsField.prepareConfig.call(this, config) === false) return false;
} }
return config; return config;
} }
@ -263,8 +262,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
* @param {boolean} successCost * @param {boolean} successCost
*/ */
async consume(config, successCost = false) { async consume(config, successCost = false) {
await this.workflow.get("cost")?.execute(config, successCost); await this.workflow.get('cost')?.execute(config, successCost);
await this.workflow.get("uses")?.execute(config, successCost); await this.workflow.get('uses')?.execute(config, successCost);
if (config.roll && !config.roll.success && successCost) { if (config.roll && !config.roll.success && successCost) {
setTimeout(() => { setTimeout(() => {
@ -291,11 +290,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
} }
get hasDamage() { get hasDamage() {
return this.damage?.parts?.length && this.type !== 'healing' return this.damage?.parts?.length && this.type !== 'healing';
} }
get hasHealing() { get hasHealing() {
return this.damage?.parts?.length && this.type === 'healing' return this.damage?.parts?.length && this.type === 'healing';
} }
get hasSave() { get hasSave() {

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

@ -1,5 +1,5 @@
import DHAbilityUse from "./abilityUse.mjs"; import DHAbilityUse from './abilityUse.mjs';
import DHActorRoll from "./actorRoll.mjs"; import DHActorRoll from './actorRoll.mjs';
export const config = { export const config = {
abilityUse: DHAbilityUse, abilityUse: DHAbilityUse,

View file

@ -58,10 +58,8 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
const actionActor = this.actionActor, const actionActor = this.actionActor,
actionItem = this.actionItem; actionItem = this.actionItem;
if (!this.source.action) return null; if (!this.source.action) return null;
if(actionItem) if (actionItem) return actionItem.system.actionsList?.find(a => a.id === this.source.action);
return actionItem.system.actionsList?.find(a => a.id === this.source.action); else if (actionActor?.system.attack?._id === this.source.action) return actionActor.system.attack;
else if(actionActor?.system.attack?._id === this.source.action)
return actionActor.system.attack
return null; return null;
} }

View file

@ -1,4 +1,4 @@
import BeastformDialog from "../../../applications/dialogs/beastformDialog.mjs"; import BeastformDialog from '../../../applications/dialogs/beastformDialog.mjs';
const fields = foundry.data.fields; const fields = foundry.data.fields;

View file

@ -31,7 +31,12 @@ export default class DamageField extends fields.SchemaField {
*/ */
static async execute(config, messageId = null, force = false) { static async execute(config, messageId = null, force = false) {
if (!this.hasDamage && !this.hasHealing) return; if (!this.hasDamage && !this.hasHealing) return;
if((this.hasRoll && DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id) && !force) return; if (
this.hasRoll &&
DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id &&
!force
)
return;
let formulas = this.damage.parts.map(p => ({ let formulas = this.damage.parts.map(p => ({
formula: DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor), formula: DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor),
@ -51,7 +56,8 @@ export default class DamageField extends fields.SchemaField {
}; };
delete damageConfig.evaluate; delete damageConfig.evaluate;
if(DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) damageConfig.dialog.configure = false; if (DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id)
damageConfig.dialog.configure = false;
if (config.hasSave) config.onSave = damageConfig.onSave = this.save.damageMod; if (config.hasSave) config.onSave = damageConfig.onSave = this.save.damageMod;
damageConfig.source.message = config.message?._id ?? messageId; damageConfig.source.message = config.message?._id ?? messageId;
@ -78,11 +84,7 @@ export default class DamageField extends fields.SchemaField {
for (let target of targets) { for (let target of targets) {
const actor = fromUuidSync(target.actorId); const actor = fromUuidSync(target.actorId);
if (!actor) continue; if (!actor) continue;
if ( if (!config.hasHealing && config.onSave && target.saved?.success === true) {
!config.hasHealing &&
config.onSave &&
target.saved?.success === true
) {
const mod = CONFIG.DH.ACTIONS.damageOnSave[config.onSave]?.mod ?? 1; const mod = CONFIG.DH.ACTIONS.damageOnSave[config.onSave]?.mod ?? 1;
Object.entries(config.damage).forEach(([k, v]) => { Object.entries(config.damage).forEach(([k, v]) => {
v.total = 0; v.total = 0;
@ -145,16 +147,25 @@ export default class DamageField extends fields.SchemaField {
* @returns {string} Id from settingsConfig.mjs actionAutomationChoices * @returns {string} Id from settingsConfig.mjs actionAutomationChoices
*/ */
static getAutomation() { static getAutomation() {
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.players) return (
(game.user.isGM &&
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.gm) ||
(!game.user.isGM &&
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.players)
);
} }
/** /**
* Return the automation setting for applyDamage method for current user role * Return the automation setting for applyDamage method for current user role
* @returns {boolean} If applyDamage should be triggered automatically * @returns {boolean} If applyDamage should be triggered automatically
*/ */
static getApplyAutomation() { static getApplyAutomation() {
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.players) return (
(game.user.isGM &&
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.gm) ||
(!game.user.isGM &&
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.players)
);
} }
} }

View file

@ -1,4 +1,4 @@
import { emitAsGM, GMUpdateEvent } from "../../../systemRegistration/socket.mjs"; import { emitAsGM, GMUpdateEvent } from '../../../systemRegistration/socket.mjs';
const fields = foundry.data.fields; const fields = foundry.data.fields;
@ -34,12 +34,7 @@ export default class EffectsField extends fields.ArrayField {
} }
if (EffectsField.getAutomation() || force) { if (EffectsField.getAutomation() || force) {
targets ??= (message.system?.targets ?? config.targets).filter(t => !config.hasRoll || t.hit); targets ??= (message.system?.targets ?? config.targets).filter(t => !config.hasRoll || t.hit);
await emitAsGM( await emitAsGM(GMUpdateEvent.UpdateEffect, EffectsField.applyEffects.bind(this), targets, this.uuid);
GMUpdateEvent.UpdateEffect,
EffectsField.applyEffects.bind(this),
targets,
this.uuid
);
// EffectsField.applyEffects.call(this, config.targets.filter(t => !config.hasRoll || t.hit)); // EffectsField.applyEffects.call(this, config.targets.filter(t => !config.hasRoll || t.hit));
} }
} }
@ -53,8 +48,7 @@ export default class EffectsField extends fields.ArrayField {
if (!this.effects?.length || !targets?.length) return; if (!this.effects?.length || !targets?.length) return;
let effects = this.effects; let effects = this.effects;
targets.forEach(async token => { targets.forEach(async token => {
if (this.hasSave && token.saved.success === true) if (this.hasSave && token.saved.success === true) effects = this.effects.filter(e => e.onSave === true);
effects = this.effects.filter(e => e.onSave === true);
if (!effects.length) return; if (!effects.length) return;
effects.forEach(async e => { effects.forEach(async e => {
const actor = canvas.tokens.get(token.id)?.actor, const actor = canvas.tokens.get(token.id)?.actor,
@ -96,6 +90,11 @@ export default class EffectsField extends fields.ArrayField {
* @returns {boolean} If execute should be triggered automatically * @returns {boolean} If execute should be triggered automatically
*/ */
static getAutomation() { static getAutomation() {
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.players) return (
(game.user.isGM &&
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.gm) ||
(!game.user.isGM &&
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.players)
);
} }
} }

View file

@ -8,7 +8,7 @@ export default class MacroField extends fields.DocumentUUIDField {
/** @inheritDoc */ /** @inheritDoc */
constructor(context = {}) { constructor(context = {}) {
super({ type: "Macro" }, context); super({ type: 'Macro' }, context);
} }
/** /**

View file

@ -1,14 +1,13 @@
const fields = foundry.data.fields; const fields = foundry.data.fields;
export default class RangeField extends fields.StringField { export default class RangeField extends fields.StringField {
/** @inheritDoc */ /** @inheritDoc */
constructor(context = {}) { constructor(context = {}) {
const options = { const options = {
choices: CONFIG.DH.GENERAL.range, choices: CONFIG.DH.GENERAL.range,
required: false, required: false,
blank: true, blank: true,
label: "DAGGERHEART.GENERAL.range" label: 'DAGGERHEART.GENERAL.range'
}; };
super(options, context); super(options, context);
} }

View file

@ -5,7 +5,12 @@ export class DHActionRollData extends foundry.abstract.DataModel {
static defineSchema() { static defineSchema() {
return { return {
type: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.GENERAL.rollTypes }), type: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.GENERAL.rollTypes }),
trait: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.ACTOR.abilities, label: "DAGGERHEART.GENERAL.Trait.single" }), trait: new fields.StringField({
nullable: true,
initial: null,
choices: CONFIG.DH.ACTOR.abilities,
label: 'DAGGERHEART.GENERAL.Trait.single'
}),
difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }), difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }),
bonus: new fields.NumberField({ nullable: true, initial: null, integer: true }), bonus: new fields.NumberField({ nullable: true, initial: null, integer: true }),
advState: new fields.StringField({ advState: new fields.StringField({
@ -86,14 +91,14 @@ export class DHActionRollData extends foundry.abstract.DataModel {
} }
get rollTrait() { get rollTrait() {
if(this.parent?.actor?.type !== "character") return null; if (this.parent?.actor?.type !== 'character') return null;
switch (this.type) { switch (this.type) {
case CONFIG.DH.GENERAL.rollTypes.spellcast.id: case CONFIG.DH.GENERAL.rollTypes.spellcast.id:
return this.parent.actor?.system?.spellcastModifierTrait?.key ?? 'agility'; return this.parent.actor?.system?.spellcastModifierTrait?.key ?? 'agility';
case CONFIG.DH.GENERAL.rollTypes.attack.id: case CONFIG.DH.GENERAL.rollTypes.attack.id:
case CONFIG.DH.GENERAL.rollTypes.trait.id: case CONFIG.DH.GENERAL.rollTypes.trait.id:
return this.useDefault || !this.trait return this.useDefault || !this.trait
? this.parent.item.system.attack?.roll?.trait ?? 'agility' ? (this.parent.item.system.attack?.roll?.trait ?? 'agility')
: this.trait; : this.trait;
default: default:
return null; return null;
@ -152,6 +157,11 @@ export default class RollField extends fields.EmbeddedDataField {
* @returns {boolean} If execute should be triggered automatically * @returns {boolean} If execute should be triggered automatically
*/ */
static getAutomation() { static getAutomation() {
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.players) return (
(game.user.isGM &&
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.gm) ||
(!game.user.isGM &&
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.players)
);
} }
} }

View file

@ -1,4 +1,4 @@
import { abilities } from "../../../config/actorConfig.mjs"; import { abilities } from '../../../config/actorConfig.mjs';
const fields = foundry.data.fields; const fields = foundry.data.fields;
@ -63,10 +63,10 @@ export default class SaveField extends fields.SchemaField {
new Promise(async subResolve => { new Promise(async subResolve => {
const actor = fromUuidSync(target.actorId); const actor = fromUuidSync(target.actorId);
if (actor) { if (actor) {
const rollSave = game.user === actor.owner ? const rollSave =
SaveField.rollSave.call(this, actor, event) game.user === actor.owner
: actor.owner ? SaveField.rollSave.call(this, actor, event)
.query('reactionRoll', { : actor.owner.query('reactionRoll', {
actionId: this.uuid, actionId: this.uuid,
actorId: actor.uuid, actorId: actor.uuid,
event, event,
@ -77,10 +77,10 @@ export default class SaveField extends fields.SchemaField {
subResolve(); subResolve();
} else subResolve(); } else subResolve();
}) })
) );
}); });
Promise.all(aPromise).then(result => resolve()); Promise.all(aPromise).then(result => resolve());
}) });
} }
/** /**
@ -109,7 +109,8 @@ export default class SaveField extends fields.SchemaField {
hasRoll: true, hasRoll: true,
data: actor.getRollData() data: actor.getRollData()
}; };
if(SaveField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) rollConfig.dialog = { configure: false }; if (SaveField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id)
rollConfig.dialog = { configure: false };
return actor.diceRoll(rollConfig); return actor.diceRoll(rollConfig);
} }
@ -128,8 +129,7 @@ export default class SaveField extends fields.SchemaField {
flags: { flags: {
[game.system.id]: { [game.system.id]: {
reactionRolls: { reactionRolls: {
[targetId]: [targetId]: {
{
result: result.roll.total, result: result.roll.total,
success: result.roll.success success: result.roll.success
} }
@ -141,7 +141,9 @@ export default class SaveField extends fields.SchemaField {
// }, 100); // }, 100);
}; };
if (game.modules.get('dice-so-nice')?.active) if (game.modules.get('dice-so-nice')?.active)
game.dice3d.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id).then(async () => await updateMsg(message, targetId, result)); game.dice3d
.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id)
.then(async () => await updateMsg(message, targetId, result));
else await updateMsg(message, targetId, result); else await updateMsg(message, targetId, result);
} }
@ -150,7 +152,12 @@ export default class SaveField extends fields.SchemaField {
* @returns {string} Id from settingsConfig.mjs actionAutomationChoices * @returns {string} Id from settingsConfig.mjs actionAutomationChoices
*/ */
static getAutomation() { static getAutomation() {
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.players) return (
(game.user.isGM &&
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.gm) ||
(!game.user.isGM &&
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.players)
);
} }
/** /**
@ -167,8 +174,7 @@ export default class SaveField extends fields.SchemaField {
const actor = await fromUuid(actorId), const actor = await fromUuid(actorId),
action = await fromUuid(actionId); action = await fromUuid(actionId);
if (!actor || !actor?.isOwner) reject(); if (!actor || !actor?.isOwner) reject();
SaveField.rollSave.call(action, actor, event, message) SaveField.rollSave.call(action, actor, event, message).then(result => resolve(result));
.then(result => resolve(result));
}); });
} }
} }

View file

@ -1,7 +1,6 @@
const fields = foundry.data.fields; const fields = foundry.data.fields;
export default class TargetField extends fields.SchemaField { export default class TargetField extends fields.SchemaField {
/** @inheritDoc */ /** @inheritDoc */
constructor(options = {}, context = {}) { constructor(options = {}, context = {}) {
const targetFields = { const targetFields = {
@ -21,7 +20,7 @@ export default class TargetField extends fields.SchemaField {
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/ */
prepareConfig(config) { prepareConfig(config) {
if (!this.target?.type) return config.targets = []; if (!this.target?.type) return (config.targets = []);
config.hasTarget = true; config.hasTarget = true;
let targets; let targets;
// If the Action is configured as self-targeted, set targets as the owner. // If the Action is configured as self-targeted, set targets as the owner.
@ -62,15 +61,11 @@ export default class TargetField extends fields.SchemaField {
* @returns {boolean} If both actors respect the provided type. * @returns {boolean} If both actors respect the provided type.
*/ */
static isTargetFriendly(actor, target, type) { static isTargetFriendly(actor, target, type) {
const actorDisposition = actor.token const actorDisposition = actor.token ? actor.token.disposition : actor.prototypeToken.disposition,
? actor.token.disposition
: actor.prototypeToken.disposition,
targetDisposition = target.document.disposition; targetDisposition = target.document.disposition;
return ( return (
(type === CONFIG.DH.GENERAL.targetTypes.friendly.id && (type === CONFIG.DH.GENERAL.targetTypes.friendly.id && actorDisposition === targetDisposition) ||
actorDisposition === targetDisposition) || (type === CONFIG.DH.GENERAL.targetTypes.hostile.id && actorDisposition + targetDisposition === 0)
(type === CONFIG.DH.GENERAL.targetTypes.hostile.id &&
actorDisposition + targetDisposition === 0)
); );
} }

View file

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

View file

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

View file

@ -1,11 +1,12 @@
export default class DhAppearance extends foundry.abstract.DataModel { export default class DhAppearance extends foundry.abstract.DataModel {
static LOCALIZATION_PREFIXES = ["DAGGERHEART.SETTINGS.Appearance"]; static LOCALIZATION_PREFIXES = ['DAGGERHEART.SETTINGS.Appearance'];
static defineSchema() { static defineSchema() {
const { StringField, ColorField, BooleanField, SchemaField } = foundry.data.fields; const { StringField, ColorField, BooleanField, SchemaField } = foundry.data.fields;
// helper to create dice style schema // helper to create dice style schema
const diceStyle = ({ fg, bg, outline, edge }) => new SchemaField({ const diceStyle = ({ fg, bg, outline, edge }) =>
new SchemaField({
foreground: new ColorField({ required: true, initial: fg }), foreground: new ColorField({ required: true, initial: fg }),
background: new ColorField({ required: true, initial: bg }), background: new ColorField({ required: true, initial: bg }),
outline: new ColorField({ required: true, initial: outline }), outline: new ColorField({ required: true, initial: outline }),
@ -20,7 +21,7 @@ export default class DhAppearance extends foundry.abstract.DataModel {
displayFear: new StringField({ displayFear: new StringField({
required: true, required: true,
choices: CONFIG.DH.GENERAL.fearDisplay, choices: CONFIG.DH.GENERAL.fearDisplay,
initial: CONFIG.DH.GENERAL.fearDisplay.token.value, initial: CONFIG.DH.GENERAL.fearDisplay.token.value
}), }),
diceSoNice: new SchemaField({ diceSoNice: new SchemaField({
hope: diceStyle({ fg: '#ffffff', bg: '#ffe760', outline: '#000000', edge: '#ffffff' }), hope: diceStyle({ fg: '#ffffff', bg: '#ffe760', outline: '#000000', edge: '#ffffff' }),
@ -39,7 +40,7 @@ export default class DhAppearance extends foundry.abstract.DataModel {
target: new BooleanField() target: new BooleanField()
}), }),
hideAttribution: new BooleanField(), hideAttribution: new BooleanField(),
showGenericStatusEffects: new BooleanField({ initial: true }), showGenericStatusEffects: new BooleanField({ initial: true })
}; };
} }
} }

View file

@ -97,13 +97,13 @@ export default class DhAutomation extends foundry.abstract.DataModel {
damage: new fields.SchemaField({ damage: new fields.SchemaField({
gm: new fields.StringField({ gm: new fields.StringField({
required: true, required: true,
initial: "never", initial: 'never',
choices: CONFIG.DH.SETTINGS.actionAutomationChoices, choices: CONFIG.DH.SETTINGS.actionAutomationChoices,
label: 'DAGGERHEART.GENERAL.gm' label: 'DAGGERHEART.GENERAL.gm'
}), }),
players: new fields.StringField({ players: new fields.StringField({
required: true, required: true,
initial: "never", initial: 'never',
choices: CONFIG.DH.SETTINGS.actionAutomationChoices, choices: CONFIG.DH.SETTINGS.actionAutomationChoices,
label: 'DAGGERHEART.GENERAL.player.plurial' label: 'DAGGERHEART.GENERAL.player.plurial'
}) })
@ -111,13 +111,13 @@ export default class DhAutomation extends foundry.abstract.DataModel {
save: new fields.SchemaField({ save: new fields.SchemaField({
gm: new fields.StringField({ gm: new fields.StringField({
required: true, required: true,
initial: "never", initial: 'never',
choices: CONFIG.DH.SETTINGS.actionAutomationChoices, choices: CONFIG.DH.SETTINGS.actionAutomationChoices,
label: 'DAGGERHEART.GENERAL.gm' label: 'DAGGERHEART.GENERAL.gm'
}), }),
players: new fields.StringField({ players: new fields.StringField({
required: true, required: true,
initial: "never", initial: 'never',
choices: CONFIG.DH.SETTINGS.actionAutomationChoices, choices: CONFIG.DH.SETTINGS.actionAutomationChoices,
label: 'DAGGERHEART.GENERAL.player.plurial' label: 'DAGGERHEART.GENERAL.player.plurial'
}) })

View file

@ -115,7 +115,35 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
label: new fields.StringField({ required: true, label: 'DAGGERHEART.GENERAL.label' }), label: new fields.StringField({ required: true, label: 'DAGGERHEART.GENERAL.label' }),
description: new fields.StringField() 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

@ -47,8 +47,7 @@ export default class DamageRoll extends DHRoll {
); );
} }
await super.buildPost(roll, config, message); await super.buildPost(roll, config, message);
if (config.source?.message) if (config.source?.message) chatMessage.update({ 'system.damage': config.damage });
chatMessage.update({ 'system.damage': config.damage });
} }
static unifyDamageRoll(rolls) { static unifyDamageRoll(rolls) {

View file

@ -46,7 +46,10 @@ export default class DHRoll extends Roll {
} }
for (const hook of config.hooks) { for (const hook of config.hooks) {
if (Hooks.call(`${CONFIG.DH.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false) return []; if (
Hooks.call(`${CONFIG.DH.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false
)
return [];
} }
return roll; return roll;
} }

View file

@ -156,7 +156,10 @@ export default class DualityRoll extends D20Roll {
if (this.options.roll.trait && this.data.traits?.[this.options.roll.trait]) if (this.options.roll.trait && this.data.traits?.[this.options.roll.trait])
modifiers.unshift({ modifiers.unshift({
label: this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id ? "DAGGERHEART.CONFIG.RollTypes.spellcast.name" : `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`, label:
this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id
? 'DAGGERHEART.CONFIG.RollTypes.spellcast.name'
: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`,
value: this.data.traits[this.options.roll.trait].value value: this.data.traits[this.options.roll.trait].value
}); });

View file

@ -1,4 +1,4 @@
import { emitAsGM, GMUpdateEvent } from "../systemRegistration/socket.mjs"; import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
export default class DhpChatMessage extends foundry.documents.ChatMessage { export default class DhpChatMessage extends foundry.documents.ChatMessage {
targetHook = null; targetHook = null;
@ -106,9 +106,9 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
} }
if (!this.isAuthor && !this.speakerActor?.isOwner) { if (!this.isAuthor && !this.speakerActor?.isOwner) {
const applyButtons = html.querySelector(".apply-buttons"); const applyButtons = html.querySelector('.apply-buttons');
applyButtons?.remove(); applyButtons?.remove();
const buttons = html.querySelectorAll(".ability-card-footer > .ability-use-button"); const buttons = html.querySelectorAll('.ability-card-footer > .ability-use-button');
buttons.forEach(b => b.remove()); buttons.forEach(b => b.remove());
} }
} }
@ -149,7 +149,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
event.stopPropagation(); event.stopPropagation();
const config = foundry.utils.deepClone(this.system); const config = foundry.utils.deepClone(this.system);
config.event = event; config.event = event;
this.system.action?.workflow.get("damage")?.execute(config, this._id, true); this.system.action?.workflow.get('damage')?.execute(config, this._id, true);
} }
async onApplyDamage(event) { async onApplyDamage(event) {
@ -173,7 +173,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm')); return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));
this.consumeOnSuccess(); this.consumeOnSuccess();
this.system.action?.workflow.get("applyDamage")?.execute(config, targets, true); this.system.action?.workflow.get('applyDamage')?.execute(config, targets, true);
} }
async onRollSave(event) { async onRollSave(event) {
@ -187,7 +187,12 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
game.system.api.fields.ActionFields.SaveField.rollSave.call(action, token.actor, event).then(result => game.system.api.fields.ActionFields.SaveField.rollSave.call(action, token.actor, event).then(result =>
emitAsGM( emitAsGM(
GMUpdateEvent.UpdateSaveMessage, GMUpdateEvent.UpdateSaveMessage,
game.system.api.fields.ActionFields.SaveField.updateSaveMessage.bind(action, result, this, token.id), game.system.api.fields.ActionFields.SaveField.updateSaveMessage.bind(
action,
result,
this,
token.id
),
{ {
action: action.uuid, action: action.uuid,
message: this._id, message: this._id,
@ -205,7 +210,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
const targets = this.system.hitTargets, const targets = this.system.hitTargets,
config = foundry.utils.deepClone(this.system); config = foundry.utils.deepClone(this.system);
config.event = event; config.event = event;
this.system.action?.workflow.get("save")?.execute(config, targets, true); this.system.action?.workflow.get('save')?.execute(config, targets, true);
} }
async onApplyEffect(event) { async onApplyEffect(event) {
@ -216,16 +221,15 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
if (targets.length === 0) if (targets.length === 0)
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm')); ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));
this.consumeOnSuccess(); this.consumeOnSuccess();
this.system.action?.workflow.get("effects")?.execute(config, targets, true); this.system.action?.workflow.get('effects')?.execute(config, targets, true);
} }
filterPermTargets(targets) { filterPermTargets(targets) {
return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, "update")) return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, 'update'));
} }
consumeOnSuccess() { consumeOnSuccess() {
if (!this.system.successConsumed && !this.targetSelection) if (!this.system.successConsumed && !this.targetSelection) this.system.action?.consume(this.system, true);
this.system.action?.consume(this.system, true);
} }
hoverTarget(event) { hoverTarget(event) {

View file

@ -32,7 +32,7 @@ export default class DHItem extends foundry.documents.Item {
/** @inheritDoc */ /** @inheritDoc */
static migrateData(source) { 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); return super.migrateData(source);
} }

View file

@ -34,10 +34,7 @@ export const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/ui/chat/parts/damage-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/target-part.hbs',
'systems/daggerheart/templates/ui/chat/parts/button-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/button-part.hbs',
'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs', 'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs',
'systems/daggerheart/templates/scene/dh-config.hbs'
'systems/daggerheart/templates/scene/dh-config.hbs',
]); ]);
}; };

View file

@ -38,8 +38,7 @@ export const registerSocketHooks = () => {
const document = data.uuid ? await fromUuid(data.uuid) : null; const document = data.uuid ? await fromUuid(data.uuid) : null;
switch (data.action) { switch (data.action) {
case GMUpdateEvent.UpdateDocument: case GMUpdateEvent.UpdateDocument:
if (document && data.update) if (document && data.update) await document.update(data.update);
await document.update(data.update);
break; break;
case GMUpdateEvent.UpdateEffect: case GMUpdateEvent.UpdateEffect:
if (document && data.update) if (document && data.update)

View file

@ -1,10 +1,11 @@
--- ---
name: Pull Request name: Pull Request
about: Create a new pull request about: Create a new pull request
title: "[PR] <Insert Title here>" title: '[PR] <Insert Title here>'
labels: pr labels: pr
assignees: '' assignees: ''
--- ---
Is this a community PR? Please go to preview tab and click [here](?expand=1&template=community_pull_request_template.md). If not, delete this line. Is this a community PR? Please go to preview tab and click [here](?expand=1&template=community_pull_request_template.md). If not, delete this line.
## Description ## Description

View file

@ -603,7 +603,7 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: .25rem .5rem; gap: 0.25rem 0.5rem;
flex-wrap: wrap; flex-wrap: wrap;
label { label {
@ -620,7 +620,7 @@
&.setting-two-values { &.setting-two-values {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: .25rem .5rem; gap: 0.25rem 0.5rem;
.form-group { .form-group {
justify-content: end; justify-content: end;

View file

@ -29,11 +29,11 @@
overflow: hidden !important; overflow: hidden !important;
div { div {
opacity: .5; opacity: 0.5;
} }
&:before { &:before {
font-family: "Font Awesome 6 Pro"; font-family: 'Font Awesome 6 Pro';
content: '\f110'; content: '\f110';
position: absolute; position: absolute;
height: 100%; height: 100%;
@ -46,6 +46,8 @@
} }
@keyframes spinner { @keyframes spinner {
to { transform: rotate(360deg); } to {
transform: rotate(360deg);
}
} }
} }

View file

@ -15,7 +15,7 @@ body.game:is(.performance-low, .noblur) {
.themed.theme-dark.application.daggerheart.sheet.dh-style, .themed.theme-dark.application.daggerheart.sheet.dh-style,
&.theme-dark .application.daggerheart { &.theme-dark .application.daggerheart {
background: @dark-blue; background: @dark-blue;
}; }
} }
.application.sheet.dh-style { .application.sheet.dh-style {

View file

@ -0,0 +1,13 @@
.daggerheart.chat.refresh-message {
header {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
.subtitle {
font-size: 18;
font-weight: bold;
}
}
}

View file

@ -2,6 +2,7 @@
@import './chat/action.less'; @import './chat/action.less';
@import './chat/chat.less'; @import './chat/chat.less';
@import './chat/downtime.less'; @import './chat/downtime.less';
@import './chat/refresh-message.less';
@import './chat/sheet.less'; @import './chat/sheet.less';
@import './combat-sidebar/combat-sidebar.less'; @import './combat-sidebar/combat-sidebar.less';
@ -19,6 +20,8 @@
@import './resources/resources.less'; @import './resources/resources.less';
@import './settings/settings.less'; @import './settings/settings.less';
@import './settings/homebrew-settings/domains.less'; @import './settings/homebrew-settings/domains.less';
@import './settings/homebrew-settings/types.less'; @import './settings/homebrew-settings/types.less';
@import './sidebar/tabs.less';
@import './sidebar/daggerheartMenu.less';

View file

@ -409,7 +409,8 @@
&.lite, &.lite,
&.no-folder { &.no-folder {
.compendium-sidebar, .menu-path { .compendium-sidebar,
.menu-path {
display: none; display: none;
} }
} }

View file

@ -4,7 +4,7 @@
fieldset { fieldset {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: .5rem; gap: 0.5rem;
&.two-columns { &.two-columns {
display: grid; display: grid;

View file

@ -0,0 +1,38 @@
.tab.sidebar-tab.daggerheartMenu-sidebar {
padding: 0 4px;
.menu-refresh-container {
display: flex;
flex-direction: column;
gap: 8px;
.menu-refresh-inner-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
.experience-chip {
display: flex;
align-items: center;
border-radius: 5px;
width: fit-content;
gap: 5px;
cursor: pointer;
padding: 5px;
background: light-dark(@dark-blue-10, @golden-10);
color: light-dark(@dark-blue, @golden);
.label {
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 17px;
}
&.selected {
background: light-dark(@dark-blue-40, @golden-40);
}
}
}
}
}

View file

@ -0,0 +1,8 @@
#interface #ui-right #sidebar {
menu li button {
img {
width: 22px;
max-width: unset;
}
}
}

View file

@ -0,0 +1,15 @@
<section
class='tab {{tabs.effects.cssClass}} {{tabs.effects.id}}'
data-tab='{{tabs.effects.id}}'
data-group='{{tabs.effects.group}}'
>
<fieldset class="one-column">
<legend>{{localize "DAGGERHEART.GENERAL.Effect.plural"}} <a><i class="fa-solid fa-plus icon-button" data-action="addEffect"></i></a></legend>
<div class="settings-items">
{{#each move.effects}}
{{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" id=this.id type="effect" }}
{{/each}}
</div>
</fieldset>
</section>

View file

@ -3,11 +3,13 @@
data-tab='{{tabs.main.id}}' data-tab='{{tabs.main.id}}'
data-group='{{tabs.main.group}}' data-group='{{tabs.main.group}}'
> >
{{#if hasIcon}}
<fieldset class="one-column"> <fieldset class="one-column">
<legend>{{localize "Icon"}}</legend> <legend>{{localize "Icon"}}</legend>
<input type="text" name="icon" value="{{move.icon}}" /> <input type="text" name="icon" value="{{move.icon}}" />
</fieldset> </fieldset>
{{/if}}
<fieldset class="one-column"> <fieldset class="one-column">
<legend>{{localize "Description"}}</legend> <legend>{{localize "Description"}}</legend>

View file

@ -0,0 +1,35 @@
<section
class="tab {{tabs.itemFeatures.cssClass}} {{tabs.itemFeatures.id}}"
data-tab="{{tabs.itemFeatures.id}}"
data-group="{{tabs.itemFeatures.group}}"
>
<div class="two-columns even">
<fieldset class="start-align">
<legend>
{{localize "DAGGERHEART.GENERAL.weaponFeatures"}}
<a data-action="addItem" data-type="weaponFeatures"><i class="fa-solid fa-plus"></i></a>
<a data-action="resetMoves" data-type="weaponFeatures"><i class="fa-solid fa-arrow-rotate-left"></i></a>
</legend>
<div class="settings-items">
{{#each settingFields._source.itemFeatures.weaponFeatures as |feature id|}}
{{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" this type="weaponFeatures" id=id }}
{{/each}}
</div>
</fieldset>
<fieldset class="start-align">
<legend>
{{localize "DAGGERHEART.GENERAL.armorFeatures"}}
<a data-action="addItem" data-type="armorFeatures"><i class="fa-solid fa-plus"></i></a>
<a data-action="resetMoves" data-type="armorFeatures"><i class="fa-solid fa-arrow-rotate-left"></i></a>
</legend>
<div class="settings-items">
{{#each settingFields._source.itemFeatures.armorFeatures as |feature id|}}
{{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" this type="armorFeatures" id=id }}
{{/each}}
</div>
</fieldset>
</div>
</section>

View file

@ -2,10 +2,10 @@
<fieldset class="one-column"> <fieldset class="one-column">
<legend>{{localize "DAGGERHEART.ACTIVEEFFECT.Config.rangeDependence.title"}}</legend> <legend>{{localize "DAGGERHEART.ACTIVEEFFECT.Config.rangeDependence.title"}}</legend>
{{formGroup document.system.schema.fields.rangeDependence.fields.enabled value=source.system.rangeDependence.enabled localize=true }} {{formGroup systemFields.rangeDependence.fields.enabled value=source.system.rangeDependence.enabled localize=true }}
{{formGroup document.system.schema.fields.rangeDependence.fields.type value=source.system.rangeDependence.type localize=true }} {{formGroup systemFields.rangeDependence.fields.type value=source.system.rangeDependence.type localize=true }}
{{formGroup document.system.schema.fields.rangeDependence.fields.target value=source.system.rangeDependence.target localize=true }} {{formGroup systemFields.rangeDependence.fields.target value=source.system.rangeDependence.target localize=true }}
{{formGroup document.system.schema.fields.rangeDependence.fields.range value=source.system.rangeDependence.range localize=true }} {{formGroup systemFields.rangeDependence.fields.range value=source.system.rangeDependence.range localize=true }}
</fieldset> </fieldset>
<fieldset class="one-column"> <fieldset class="one-column">

View file

@ -0,0 +1,22 @@
<div>
<fieldset>
<legend>{{localize "Refresh Features"}}</legend>
<div class="menu-refresh-container">
<div class="menu-refresh-inner-container">
{{#each refreshables as |type key|}}
<div class="experience-chip {{#if type.selected}}selected{{/if}}" data-action="selectRefreshable" data-type="{{key}}">
{{#if type.selected}}
<span><i class="fa-solid fa-circle"></i></span>
{{else}}
<span><i class="fa-regular fa-circle"></i></span>
{{/if}}
<span>{{type.label}}</span>
</div>
{{/each}}
</div>
<button data-action="refreshActors" {{disabled disableRefresh}}>{{localize "Refresh"}}</button>
</div>
</fieldset>
</div>

View file

@ -0,0 +1,18 @@
<nav class="tabs faded-ui" role="tablist" data-tooltip-direction="LEFT">
<menu class="flexcol">
{{#each tabs}}
<li>
<button type="button" class="ui-control plain icon {{#if icon}}{{icon}}{{/if}}" data-action="tab" data-tab="{{ @key }}"
role="tab" aria-pressed="{{ active }}" data-group="primary" aria-label="{{ localize tooltip }}"
aria-controls="{{ @key }}" data-tooltip>
{{#if img}}<img src="{{img}}" />{{/if}}
</button>
<div class="notification-pip"></div>
</li>
{{/each}}
<li>
<button type="button" class="collapse ui-control plain icon fas fa-caret-left" data-tooltip
aria-label="{{ localize "Expand" }}" data-action="toggleState"></button>
</li>
</menu>
</nav>

View file

@ -0,0 +1,6 @@
<div class="daggerheart chat refresh-message">
<header>
<div class="subtitle">{{localize "DAGGERHEART.UI.Chat.refreshMessage.header"}}</div>
<div class="types">{{types}}</div>
</header>
</div>