diff --git a/daggerheart.mjs b/daggerheart.mjs
index 0aadecca..cd4dbc19 100644
--- a/daggerheart.mjs
+++ b/daggerheart.mjs
@@ -66,6 +66,7 @@ CONFIG.Token.rulerClass = placeables.DhTokenRuler;
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
CONFIG.ui.combat = applications.ui.DhCombatTracker;
+CONFIG.ui.nav = applications.ui.DhSceneNavigation;
CONFIG.ui.chat = applications.ui.DhChatLog;
CONFIG.ui.effectsDisplay = applications.ui.DhEffectsDisplay;
CONFIG.ui.hotbar = applications.ui.DhHotbar;
@@ -89,6 +90,8 @@ Hooks.once('init', () => {
fields
};
+ game.system.registeredTriggers = new RegisteredTriggers();
+
const { DocumentSheetConfig } = foundry.applications.apps;
DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, {
@@ -321,7 +324,7 @@ const updateActorsRangeDependentEffects = async token => {
CONFIG.DH.SETTINGS.gameSettings.variantRules
).rangeMeasurement;
- for (let effect of token.actor.allApplicableEffects()) {
+ for (let effect of token.actor?.allApplicableEffects() ?? []) {
if (!effect.system.rangeDependence?.enabled) continue;
const { target, range, type } = effect.system.rangeDependence;
@@ -384,3 +387,50 @@ Hooks.on('refreshToken', (_, options) => {
Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
Hooks.on('renderDocumentDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
+
+class RegisteredTriggers extends Map {
+ constructor() {
+ super();
+ }
+
+ async registerTriggers(trigger, actor, triggeringActorType, uuid, commands) {
+ const existingTrigger = this.get(trigger);
+ if (!existingTrigger) this.set(trigger, new Map());
+
+ this.get(trigger).set(uuid, { actor, triggeringActorType, commands });
+ }
+
+ async runTrigger(trigger, currentActor, ...args) {
+ const updates = [];
+ const triggerSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).triggers;
+ if (!triggerSettings.enabled) return updates;
+
+ const dualityTrigger = this.get(trigger);
+ if (dualityTrigger) {
+ for (let { actor, triggeringActorType, commands } of dualityTrigger.values()) {
+ const triggerData = CONFIG.DH.TRIGGER.triggers[trigger];
+ if (triggerData.usesActor && triggeringActorType !== 'any') {
+ if (triggeringActorType === 'self' && currentActor?.uuid !== actor) continue;
+ else if (triggeringActorType === 'other' && currentActor?.uuid === actor) continue;
+ }
+
+ for (let command of commands) {
+ try {
+ const result = await command(...args);
+ if (result?.updates?.length) updates.push(...result.updates);
+ } catch (_) {
+ const triggerName = game.i18n.localize(triggerData.label);
+ ui.notifications.error(
+ game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerError', {
+ trigger: triggerName,
+ actor: currentActor?.name
+ })
+ );
+ }
+ }
+ }
+ }
+
+ return updates;
+ }
+}
diff --git a/lang/en.json b/lang/en.json
index d41f84ef..b8aded88 100755
--- a/lang/en.json
+++ b/lang/en.json
@@ -94,7 +94,9 @@
"customFormula": "Custom Formula",
"formula": "Formula"
},
- "displayInChat": "Display in chat"
+ "displayInChat": "Display in chat",
+ "deleteTriggerTitle": "Delete Trigger",
+ "deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?"
},
"RollField": {
"diceRolling": {
@@ -1151,7 +1153,8 @@
"any": "Any",
"friendly": "Friendly",
"hostile": "Hostile",
- "self": "Self"
+ "self": "Self",
+ "other": "Other"
},
"TemplateTypes": {
"circle": "Circle",
@@ -1225,6 +1228,29 @@
}
}
},
+ "Triggers": {
+ "postDamageReduction": {
+ "label": "After Damage Reduction"
+ },
+ "preDamageReduction": {
+ "label": "Before Damage Reduction"
+ },
+ "dualityRoll": {
+ "label": "Duality Roll"
+ },
+ "fearRoll": {
+ "label": "Fear Roll"
+ },
+ "triggerTexts": {
+ "strangePatternsContentTitle": "Matched {nr} times.",
+ "strangePatternsContentSubTitle": "Increase hope and stress to a total of {nr}.",
+ "ferocityContent": "Spend 2 Hope to gain {bonus} bonus Evasion until after the next attack against you?",
+ "ferocityEffectDescription": "Your evasion is increased by {bonus}. This bonus lasts until after the next attack made against you."
+ },
+ "triggerType": "Trigger Type",
+ "triggeringActorType": "Triggering Actor Type",
+ "triggerError": "{trigger} trigger failed for {actor}. It's probably configured wrong."
+ },
"WeaponFeature": {
"barrier": {
"name": "Barrier",
@@ -2062,10 +2088,10 @@
"partyMembers": "Party Members",
"projects": "Projects",
"types": "Types",
- "itemFeatures": "Item Features",
"questions": "Questions",
"configuration": "Configuration",
- "base": "Base"
+ "base": "Base",
+ "triggers": "Triggers"
},
"Tiers": {
"singular": "Tier",
@@ -2443,6 +2469,12 @@
"hint": "Automatically apply effects. Targets must be selected before the action is made and Reaction Roll Automation must be different than Never. Bypass users permissions."
}
},
+ "triggers": {
+ "enabled": {
+ "label": "Enabled",
+ "hint": "Advanced automation such as triggering a popup for a wizard's Strange Patterns"
+ }
+ },
"summaryMessages": {
"label": "Summary Messages"
}
@@ -2452,6 +2484,9 @@
},
"roll": {
"title": "Actions"
+ },
+ "trigger": {
+ "title": "Triggers"
}
},
"Homebrew": {
@@ -2579,7 +2614,9 @@
}
},
"disabledText": "Daggerheart Measurements are disabled in System Settings - Variant Rules",
- "rangeMeasurement": "Range Measurement"
+ "rangeMeasurement": "Range Measurement",
+ "sceneEnvironments": "Scene Environments",
+ "dragEnvironmentHere": "Drag environments here"
}
},
"UI": {
@@ -2792,7 +2829,9 @@
"gmRequired": "This action requires an online GM",
"gmOnly": "This can only be accessed by the GM",
"noActorOwnership": "You do not have permissions for this character",
- "documentIsMissing": "The {documentType} is missing from the world."
+ "documentIsMissing": "The {documentType} is missing from the world.",
+ "tokenActorMissing": "{name} is missing an Actor",
+ "tokenActorsMissing": "[{names}] missing Actors"
},
"Sidebar": {
"actorDirectory": {
diff --git a/module/applications/hud/tokenHUD.mjs b/module/applications/hud/tokenHUD.mjs
index b1136995..87c3e88e 100644
--- a/module/applications/hud/tokenHUD.mjs
+++ b/module/applications/hud/tokenHUD.mjs
@@ -21,6 +21,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
async _prepareContext(options) {
const context = await super._prepareContext(options);
+ if (!this.actor) return context;
+
context.partyOnCanvas =
this.actor.type === 'party' &&
this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0);
@@ -58,14 +60,33 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
}
static async #onToggleCombat() {
+ const tokensWithoutActors = canvas.tokens.controlled.filter(t => !t.actor);
+ const warning =
+ tokensWithoutActors.length === 1
+ ? game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorMissing', {
+ name: tokensWithoutActors[0].name
+ })
+ : game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorsMissing', {
+ names: tokensWithoutActors.map(x => x.name).join(', ')
+ });
+
const tokens = canvas.tokens.controlled
- .filter(t => !t.actor || !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
+ .filter(t => t.actor && !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
.map(t => t.document);
- if (!this.object.controlled) tokens.push(this.document);
+ if (!this.object.controlled && this.document.actor) tokens.push(this.document);
try {
- if (this.document.inCombat) await TokenDocument.implementation.deleteCombatants(tokens);
- else await TokenDocument.implementation.createCombatants(tokens);
+ if (this.document.inCombat) {
+ const tokensInCombat = tokens.filter(t => t.inCombat);
+ await TokenDocument.implementation.deleteCombatants([...tokensInCombat, ...tokensWithoutActors]);
+ } else {
+ if (tokensWithoutActors.length) {
+ ui.notifications.warn(warning);
+ }
+
+ const tokensOutOfCombat = tokens.filter(t => !t.inCombat);
+ await TokenDocument.implementation.createCombatants(tokensOutOfCombat);
+ }
} catch (err) {
ui.notifications.warn(err.message);
}
diff --git a/module/applications/scene/sceneConfigSettings.mjs b/module/applications/scene/sceneConfigSettings.mjs
index be8f7b71..1b93aa8c 100644
--- a/module/applications/scene/sceneConfigSettings.mjs
+++ b/module/applications/scene/sceneConfigSettings.mjs
@@ -1,16 +1,28 @@
+import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
+
export default class DhSceneConfigSettings extends foundry.applications.sheets.SceneConfig {
- // static DEFAULT_OPTIONS = {
- // ...super.DEFAULT_OPTIONS,
- // form: {
- // handler: this.updateData,
- // closeOnSubmit: true
- // }
- // };
+ constructor(options) {
+ super(options);
+
+ Hooks.on(socketEvent.Refresh, ({ refreshType }) => {
+ if (refreshType === RefreshType.Scene) {
+ this.daggerheartFlag = new game.system.api.data.scenes.DHScene(this.document.flags.daggerheart);
+ this.render();
+ }
+ });
+ }
+
+ static DEFAULT_OPTIONS = {
+ ...super.DEFAULT_OPTIONS,
+ actions: {
+ ...super.DEFAULT_OPTIONS.actions,
+ removeSceneEnvironment: DhSceneConfigSettings.#removeSceneEnvironment
+ }
+ };
static buildParts() {
const { footer, tabs, ...parts } = super.PARTS;
const tmpParts = {
- // tabs,
tabs: { template: 'systems/daggerheart/templates/scene/tabs.hbs' },
...parts,
dh: { template: 'systems/daggerheart/templates/scene/dh-config.hbs' },
@@ -28,27 +40,45 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
static TABS = DhSceneConfigSettings.buildTabs();
+ async _preRender(context, options) {
+ await super._preFirstRender(context, options);
+ this.daggerheartFlag = new game.system.api.data.scenes.DHScene(this.document.flags.daggerheart);
+ }
+
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
+
switch (partId) {
case 'dh':
htmlElement.querySelector('#rangeMeasurementSetting')?.addEventListener('change', async event => {
- const flagData = foundry.utils.mergeObject(this.document.flags.daggerheart, {
- rangeMeasurement: { setting: event.target.value }
- });
- this.document.flags.daggerheart = flagData;
+ this.daggerheartFlag.updateSource({ rangeMeasurement: { setting: event.target.value } });
this.render();
});
+
+ const dragArea = htmlElement.querySelector('.scene-environments');
+ if (dragArea) dragArea.ondrop = this._onDrop.bind(this);
+
break;
}
}
+ async _onDrop(event) {
+ const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
+ const item = await foundry.utils.fromUuid(data.uuid);
+ if (item instanceof game.system.api.documents.DhpActor && item.type === 'environment') {
+ await this.daggerheartFlag.updateSource({
+ sceneEnvironments: [...this.daggerheartFlag.sceneEnvironments, data.uuid]
+ });
+ this.render();
+ }
+ }
+
/** @inheritDoc */
async _preparePartContext(partId, context, options) {
context = await super._preparePartContext(partId, context, options);
switch (partId) {
case 'dh':
- context.data = new game.system.api.data.scenes.DHScene(canvas.scene.flags.daggerheart);
+ context.data = this.daggerheartFlag;
context.variantRules = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules);
break;
}
@@ -56,8 +86,24 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
return context;
}
- // static async updateData(event, _, formData) {
- // const data = foundry.utils.expandObject(formData.object);
- // this.close(data);
- // }
+ static async #removeSceneEnvironment(_event, button) {
+ await this.daggerheartFlag.updateSource({
+ sceneEnvironments: this.daggerheartFlag.sceneEnvironments.filter(
+ (_, index) => index !== Number.parseInt(button.dataset.index)
+ )
+ });
+ this.render();
+ }
+
+ /** @override */
+ async _processSubmitData(event, form, submitData, options) {
+ submitData.flags.daggerheart = this.daggerheartFlag.toObject();
+ for (const key of Object.keys(this.document._source.flags.daggerheart?.sceneEnvironments ?? {})) {
+ if (!submitData.flags.daggerheart.sceneEnvironments[key]) {
+ submitData.flags.daggerheart.sceneEnvironments[`-=${key}`] = null;
+ }
+ }
+
+ super._processSubmitData(event, form, submitData, options);
+ }
}
diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs
index 7224bd72..7051ad2b 100644
--- a/module/applications/sheets-configs/action-base-config.mjs
+++ b/module/applications/sheets-configs/action-base-config.mjs
@@ -7,6 +7,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
this.action = action;
this.openSection = null;
+ this.openTrigger = this.action.triggers.length > 0 ? 0 : null;
}
get title() {
@@ -15,7 +16,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
static DEFAULT_OPTIONS = {
tag: 'form',
- classes: ['daggerheart', 'dh-style', 'dialog', 'action-config', 'max-800'],
+ classes: ['daggerheart', 'dh-style', 'action-config', 'dialog', 'max-800'],
window: {
icon: 'fa-solid fa-wrench',
resizable: false
@@ -30,7 +31,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
editEffect: this.editEffect,
addDamage: this.addDamage,
removeDamage: this.removeDamage,
- editDoc: this.editDoc
+ editDoc: this.editDoc,
+ addTrigger: this.addTrigger,
+ removeTrigger: this.removeTrigger,
+ expandTrigger: this.expandTrigger
},
form: {
handler: this.updateForm,
@@ -57,6 +61,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
effect: {
id: 'effect',
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
+ },
+ trigger: {
+ id: 'trigger',
+ template: 'systems/daggerheart/templates/sheets-settings/action-settings/trigger.hbs'
}
};
@@ -84,6 +92,14 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
id: 'effect',
icon: null,
label: 'DAGGERHEART.GENERAL.Tabs.effects'
+ },
+ trigger: {
+ active: false,
+ cssClass: '',
+ group: 'primary',
+ id: 'trigger',
+ icon: null,
+ label: 'DAGGERHEART.GENERAL.Tabs.triggers'
}
};
@@ -128,6 +144,16 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
context.baseSaveDifficulty = this.action.actor?.baseSaveDifficulty;
context.baseAttackBonus = this.action.actor?.system.attack?.roll.bonus;
context.hasRoll = this.action.hasRoll;
+ context.triggers = context.source.triggers.map((trigger, index) => {
+ const { hint, returns, usesActor } = CONFIG.DH.TRIGGER.triggers[trigger.trigger];
+ return {
+ ...trigger,
+ hint,
+ returns,
+ usesActor,
+ revealed: this.openTrigger === index
+ };
+ });
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
context.tierOptions = [
@@ -256,6 +282,60 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
+ static addTrigger() {
+ const data = this.action.toObject();
+ data.triggers.push({
+ trigger: CONFIG.DH.TRIGGER.triggers.dualityRoll.id,
+ triggeringActor: CONFIG.DH.TRIGGER.triggerActorTargetType.any.id
+ });
+ this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
+ }
+
+ static async removeTrigger(_event, button) {
+ const trigger = CONFIG.DH.TRIGGER.triggers[this.action.triggers[button.dataset.index].trigger];
+ const confirmed = await foundry.applications.api.DialogV2.confirm({
+ window: {
+ title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.deleteTriggerTitle')
+ },
+ content: game.i18n.format('DAGGERHEART.ACTIONS.Config.deleteTriggerContent', {
+ trigger: game.i18n.localize(trigger.label)
+ })
+ });
+
+ if (!confirmed) return;
+
+ const data = this.action.toObject();
+ data.triggers = data.triggers.filter((_, index) => index !== Number.parseInt(button.dataset.index));
+ this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
+ }
+
+ static async expandTrigger(_event, button) {
+ const index = Number.parseInt(button.dataset.index);
+ const toggle = (element, codeMirror) => {
+ codeMirror.classList.toggle('revealed');
+ const button = element.querySelector('a > i');
+ button.classList.toggle('fa-angle-up');
+ button.classList.toggle('fa-angle-down');
+ };
+
+ const fieldset = button.closest('fieldset');
+ const codeMirror = fieldset.querySelector('.code-mirror-wrapper');
+ toggle(fieldset, codeMirror);
+
+ if (this.openTrigger !== null && this.openTrigger !== index) {
+ const previouslyExpanded = fieldset
+ .closest(`section`)
+ .querySelector(`fieldset[data-index="${this.openTrigger}"]`);
+ const codeMirror = previouslyExpanded.querySelector('.code-mirror-wrapper');
+ toggle(previouslyExpanded, codeMirror);
+ this.openTrigger = index;
+ } else if (this.openTrigger === index) {
+ this.openTrigger = null;
+ } else {
+ this.openTrigger = index;
+ }
+ }
+
updateSummonCount(event) {
event.stopPropagation();
const wrapper = event.target.closest('.summon-count-wrapper');
diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs
index d25a1a4e..903caa2a 100644
--- a/module/applications/sheets/api/application-mixin.mjs
+++ b/module/applications/sheets/api/application-mixin.mjs
@@ -211,7 +211,7 @@ export default function DHApplicationMixin(Base) {
const step = event.key === 'ArrowUp' ? 1 : event.key === 'ArrowDown' ? -1 : 0;
if (step !== 0) {
handleUpdate(step);
- deltaInput.dispatchEvent(new Event("change", { bubbles: true }));
+ deltaInput.dispatchEvent(new Event('change', { bubbles: true }));
}
});
@@ -222,7 +222,7 @@ export default function DHApplicationMixin(Base) {
if (deltaInput === document.activeElement) {
event.preventDefault();
handleUpdate(Math.sign(-1 * event.deltaY));
- deltaInput.dispatchEvent(new Event("change", { bubbles: true }));
+ deltaInput.dispatchEvent(new Event('change', { bubbles: true }));
}
},
{ passive: false }
@@ -236,7 +236,7 @@ export default function DHApplicationMixin(Base) {
// Handle contenteditable
for (const input of htmlElement.querySelectorAll('[contenteditable][data-property]')) {
const property = input.dataset.property;
- input.addEventListener("blur", () => {
+ input.addEventListener('blur', () => {
const selection = document.getSelection();
if (input.contains(selection.anchorNode)) {
selection.empty();
@@ -244,12 +244,12 @@ export default function DHApplicationMixin(Base) {
this.document.update({ [property]: input.textContent });
});
- input.addEventListener("keydown", event => {
- if (event.key === "Enter") input.blur();
+ input.addEventListener('keydown', event => {
+ if (event.key === 'Enter') input.blur();
});
// Chrome sometimes add
, which aren't a problem for the value but are for the placeholder
- input.addEventListener("input", () => input.querySelectorAll("br").forEach((i) => i.remove()));
+ input.addEventListener('input', () => input.querySelectorAll('br').forEach(i => i.remove()));
}
}
@@ -585,7 +585,9 @@ export default function DHApplicationMixin(Base) {
if (!doc || !descriptionElement) continue;
// localize the description (idk if it's still necessary)
- const description = game.i18n.localize(doc.system?.description ?? doc.description);
+ const description = doc.system?.getEnrichedDescription
+ ? await doc.system.getEnrichedDescription()
+ : game.i18n.localize(doc.system?.description ?? doc.description);
// Enrich the description and attach it;
const isAction = doc.documentName === 'Action';
@@ -736,7 +738,7 @@ export default function DHApplicationMixin(Base) {
};
if (inVault) data['system.inVault'] = true;
if (disabled) data.disabled = true;
- if (type === "domainCard" && parent?.system.domains?.length) {
+ if (type === 'domainCard' && parent?.system.domains?.length) {
data.system.domain = parent.system.domains[0];
}
diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs
index 42ed9426..e3568b23 100644
--- a/module/applications/sheets/api/base-item.mjs
+++ b/module/applications/sheets/api/base-item.mjs
@@ -76,16 +76,10 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
/**@inheritdoc */
async _preparePartContext(partId, context, options) {
await super._preparePartContext(partId, context, options);
- const { TextEditor } = foundry.applications.ux;
switch (partId) {
case 'description':
- const value = foundry.utils.getProperty(this.document, 'system.description') ?? '';
- context.enrichedDescription = await TextEditor.enrichHTML(value, {
- relativeTo: this.item,
- rollData: this.item.getRollData(),
- secrets: this.item.isOwner
- });
+ context.enrichedDescription = await this.document.system.getEnrichedDescription();
break;
case 'effects':
await this._prepareEffectsContext(context, options);
diff --git a/module/applications/ui/_module.mjs b/module/applications/ui/_module.mjs
index d5f31906..8c5c020e 100644
--- a/module/applications/ui/_module.mjs
+++ b/module/applications/ui/_module.mjs
@@ -5,4 +5,5 @@ export { default as DhCombatTracker } from './combatTracker.mjs';
export { default as DhEffectsDisplay } from './effectsDisplay.mjs';
export { default as DhFearTracker } from './fearTracker.mjs';
export { default as DhHotbar } from './hotbar.mjs';
+export { default as DhSceneNavigation } from './sceneNavigation.mjs';
export { ItemBrowser } from './itemBrowser.mjs';
diff --git a/module/applications/ui/combatTracker.mjs b/module/applications/ui/combatTracker.mjs
index babc4a65..288ba8ad 100644
--- a/module/applications/ui/combatTracker.mjs
+++ b/module/applications/ui/combatTracker.mjs
@@ -127,7 +127,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
resource,
active: index === combat.turn,
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
- type: combatant.actor.system.type,
+ type: combatant.actor?.system?.type,
img: await this._getCombatantThumbnail(combatant)
};
@@ -165,7 +165,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
if (this.viewed.turn !== toggleTurn) {
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
- if (combatant.actor.type === 'character') {
+ if (combatant.actor?.type === 'character') {
await updateCountdowns(
CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id,
CONFIG.DH.GENERAL.countdownProgressionTypes.characterSpotlight.id
diff --git a/module/applications/ui/sceneNavigation.mjs b/module/applications/ui/sceneNavigation.mjs
new file mode 100644
index 00000000..ac16ac99
--- /dev/null
+++ b/module/applications/ui/sceneNavigation.mjs
@@ -0,0 +1,89 @@
+import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
+
+export default class DhSceneNavigation extends foundry.applications.ui.SceneNavigation {
+ /** @inheritdoc */
+ static DEFAULT_OPTIONS = {
+ ...super.DEFAULT_OPTIONS,
+ classes: ['faded-ui', 'flexcol', 'scene-navigation'],
+ actions: {
+ openSceneEnvironment: DhSceneNavigation.#openSceneEnvironment
+ }
+ };
+
+ /** @inheritdoc */
+ static PARTS = {
+ scenes: {
+ root: true,
+ template: 'systems/daggerheart/templates/ui/sceneNavigation/scene-navigation.hbs'
+ }
+ };
+
+ /** @inheritdoc */
+ async _prepareContext(options) {
+ const context = await super._prepareContext(options);
+
+ const extendScenes = scenes =>
+ scenes.map(x => {
+ const scene = game.scenes.get(x.id);
+ if (!scene.flags.daggerheart) return x;
+
+ const daggerheartInfo = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart);
+ const environments = daggerheartInfo.sceneEnvironments.filter(
+ x => x && x.testUserPermission(game.user, 'LIMITED')
+ );
+ const hasEnvironments = environments.length > 0;
+ return {
+ ...x,
+ hasEnvironments,
+ environmentImage: hasEnvironments ? environments[0].img : null,
+ environments: environments
+ };
+ });
+ context.scenes.active = extendScenes(context.scenes.active);
+ context.scenes.inactive = extendScenes(context.scenes.inactive);
+
+ return context;
+ }
+
+ static async #openSceneEnvironment(event, button) {
+ const scene = game.scenes.get(button.dataset.sceneId);
+ const sceneEnvironments = new game.system.api.data.scenes.DHScene(
+ scene.flags.daggerheart
+ ).sceneEnvironments.filter(x => x.testUserPermission(game.user, 'LIMITED'));
+
+ if (sceneEnvironments.length === 1 || event.shiftKey) {
+ sceneEnvironments[0].sheet.render(true);
+ } else {
+ new foundry.applications.ux.ContextMenu.implementation(
+ button,
+ '.scene-environment',
+ sceneEnvironments.map(environment => ({
+ name: environment.name,
+ callback: () => {
+ if (scene.flags.daggerheart.sceneEnvironments[0] !== environment.uuid) {
+ const newEnvironments = scene.flags.daggerheart.sceneEnvironments;
+ const newFirst = newEnvironments.splice(
+ newEnvironments.findIndex(x => x === environment.uuid)
+ )[0];
+ newEnvironments.unshift(newFirst);
+ emitAsGM(
+ GMUpdateEvent.UpdateDocument,
+ scene.update.bind(scene),
+ { 'flags.daggerheart.sceneEnvironments': newEnvironments },
+ scene.uuid
+ );
+ }
+
+ environment.sheet.render({ force: true });
+ }
+ })),
+ {
+ jQuery: false,
+ fixed: true
+ }
+ );
+
+ CONFIG.ux.ContextMenu.triggerContextMenu(event, '.scene-environment');
+ }
+ }
+}
diff --git a/module/applications/ux/contextMenu.mjs b/module/applications/ux/contextMenu.mjs
index 09454848..081e6ba0 100644
--- a/module/applications/ux/contextMenu.mjs
+++ b/module/applications/ux/contextMenu.mjs
@@ -96,11 +96,11 @@ export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
* Trigger a context menu event in response to a normal click on a additional options button.
* @param {PointerEvent} event
*/
- static triggerContextMenu(event) {
+ static triggerContextMenu(event, altSelector) {
event.preventDefault();
event.stopPropagation();
const { clientX, clientY } = event;
- const selector = '[data-item-uuid]';
+ const selector = altSelector ?? '[data-item-uuid]';
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
target?.dispatchEvent(
new PointerEvent('contextmenu', {
diff --git a/module/config/_module.mjs b/module/config/_module.mjs
index ef26b958..560f3fec 100644
--- a/module/config/_module.mjs
+++ b/module/config/_module.mjs
@@ -10,3 +10,4 @@ export * as itemConfig from './itemConfig.mjs';
export * as settingsConfig from './settingsConfig.mjs';
export * as systemConfig from './system.mjs';
export * as itemBrowserConfig from './itemBrowserConfig.mjs';
+export * as triggerConfig from './triggerConfig.mjs';
diff --git a/module/config/encounterConfig.mjs b/module/config/encounterConfig.mjs
index 7565652f..4e0f8a6e 100644
--- a/module/config/encounterConfig.mjs
+++ b/module/config/encounterConfig.mjs
@@ -9,7 +9,7 @@ export const AdversaryBPPerEncounter = (adversaries, characters) => {
);
if (existingEntry) {
existingEntry.nr += 1;
- } else {
+ } else if (adversary.type) {
acc.push({ adversary, nr: 1 });
}
return acc;
diff --git a/module/config/hooksConfig.mjs b/module/config/hooksConfig.mjs
index d316c4d4..9140ea0a 100644
--- a/module/config/hooksConfig.mjs
+++ b/module/config/hooksConfig.mjs
@@ -1,5 +1,3 @@
-const hooksConfig = {
+export const hooksConfig = {
effectDisplayToggle: 'DHEffectDisplayToggle'
};
-
-export default hooksConfig;
diff --git a/module/config/system.mjs b/module/config/system.mjs
index ac15b1d9..47a41e8d 100644
--- a/module/config/system.mjs
+++ b/module/config/system.mjs
@@ -7,7 +7,8 @@ import * as SETTINGS from './settingsConfig.mjs';
import * as EFFECTS from './effectConfig.mjs';
import * as ACTIONS from './actionConfig.mjs';
import * as FLAGS from './flagsConfig.mjs';
-import HOOKS from './hooksConfig.mjs';
+import * as HOOKS from './hooksConfig.mjs';
+import * as TRIGGER from './triggerConfig.mjs';
import * as ITEMBROWSER from './itemBrowserConfig.mjs';
export const SYSTEM_ID = 'daggerheart';
@@ -24,5 +25,6 @@ export const SYSTEM = {
ACTIONS,
FLAGS,
HOOKS,
+ TRIGGER,
ITEMBROWSER
};
diff --git a/module/config/triggerConfig.mjs b/module/config/triggerConfig.mjs
new file mode 100644
index 00000000..35609bd1
--- /dev/null
+++ b/module/config/triggerConfig.mjs
@@ -0,0 +1,42 @@
+/* hints and returns are intentionally not translated. They are programatical terms and best understood in english */
+export const triggers = {
+ dualityRoll: {
+ id: 'dualityRoll',
+ usesActor: true,
+ args: ['roll', 'actor'],
+ label: 'DAGGERHEART.CONFIG.Triggers.dualityRoll.label',
+ hint: 'this: Action, roll: DhRoll, actor: DhActor',
+ returns: '{ updates: [{ key, value, total }] }'
+ },
+ fearRoll: {
+ id: 'fearRoll',
+ usesActor: true,
+ args: ['roll', 'actor'],
+ label: 'DAGGERHEART.CONFIG.Triggers.fearRoll.label',
+ hint: 'this: Action, roll: DhRoll, actor: DhActor',
+ returns: '{ updates: [{ key, value, total }] }'
+ },
+ postDamageReduction: {
+ id: 'postDamageReduction',
+ usesActor: true,
+ args: ['damageUpdates', 'actor'],
+ label: 'DAGGERHEART.CONFIG.Triggers.postDamageReduction.label',
+ hint: 'damageUpdates: ResourceUpdates, actor: DhActor',
+ returns: '{ updates: [{ originActor: this.actor, updates: [{ key, value, total }] }] }'
+ }
+};
+
+export const triggerActorTargetType = {
+ any: {
+ id: 'any',
+ label: 'DAGGERHEART.CONFIG.TargetTypes.any'
+ },
+ self: {
+ id: 'self',
+ label: 'DAGGERHEART.CONFIG.TargetTypes.self'
+ },
+ other: {
+ id: 'other',
+ label: 'DAGGERHEART.CONFIG.TargetTypes.other'
+ }
+};
diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs
index fce2f50b..66482ce3 100644
--- a/module/data/action/baseAction.mjs
+++ b/module/data/action/baseAction.mjs
@@ -2,6 +2,7 @@ import DhpActor from '../../documents/actor.mjs';
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
import { ActionMixin } from '../fields/actionField.mjs';
import { originItemField } from '../chat-message/actorRoll.mjs';
+import TriggerField from '../fields/triggerField.mjs';
const fields = foundry.data.fields;
@@ -34,7 +35,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
nullable: false,
required: true
}),
- targetUuid: new fields.StringField({ initial: undefined })
+ targetUuid: new fields.StringField({ initial: undefined }),
+ triggers: new fields.ArrayField(new TriggerField())
};
this.extraSchemas.forEach(s => {
@@ -342,6 +344,10 @@ export class ResourceUpdateMap extends Map {
}
addResources(resources) {
+ if (!resources?.length) return;
+ const invalidResources = resources.some(resource => !resource.key);
+ if (invalidResources) return;
+
for (const resource of resources) {
if (!resource.key) continue;
diff --git a/module/data/actor/environment.mjs b/module/data/actor/environment.mjs
index 4ed3819e..0aaf8eb0 100644
--- a/module/data/actor/environment.mjs
+++ b/module/data/actor/environment.mjs
@@ -1,8 +1,11 @@
import BaseDataActor from './base.mjs';
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
import DHEnvironmentSettings from '../../applications/sheets-configs/environment-settings.mjs';
+import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
export default class DhEnvironment extends BaseDataActor {
+ scenes = new Set();
+
/**@override */
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Environment'];
@@ -53,6 +56,31 @@ export default class DhEnvironment extends BaseDataActor {
}
isItemValid(source) {
- return source.type === "feature";
+ return source.type === 'feature';
+ }
+
+ _onUpdate(changes, options, userId) {
+ super._onUpdate(changes, options, userId);
+ for (const scene of this.scenes) {
+ scene.render();
+ }
+ }
+
+ _onDelete(options, userId) {
+ super._onDelete(options, userId);
+ for (const scene of this.scenes) {
+ if (game.user.isActiveGM) {
+ const newSceneEnvironments = scene.flags.daggerheart.sceneEnvironments.filter(
+ x => x !== this.parent.uuid
+ );
+ scene.update({ 'flags.daggerheart.sceneEnvironments': newSceneEnvironments }).then(() => {
+ Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Scene });
+ game.socket.emit(`system.${CONFIG.DH.id}`, {
+ action: socketEvent.Refresh,
+ data: { refreshType: RefreshType.TagTeamRoll }
+ });
+ });
+ }
+ }
}
}
diff --git a/module/data/combat.mjs b/module/data/combat.mjs
index 565afffc..3671a2cd 100644
--- a/module/data/combat.mjs
+++ b/module/data/combat.mjs
@@ -15,8 +15,9 @@ export default class DhCombat extends foundry.abstract.TypeDataModel {
get extendedBattleToggles() {
const modifiers = CONFIG.DH.ENCOUNTER.BPModifiers;
const adversaries =
- this.parent.turns?.filter(x => x.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? [];
- const characters = this.parent.turns?.filter(x => !x.isNPC) ?? [];
+ this.parent.turns?.filter(x => x.actor && x.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ??
+ [];
+ const characters = this.parent.turns?.filter(x => x.actor && !x.isNPC) ?? [];
const activeAutomatic = Object.keys(modifiers).reduce((acc, categoryKey) => {
const category = modifiers[categoryKey];
diff --git a/module/data/fields/_module.mjs b/module/data/fields/_module.mjs
index 8d36b76d..2a8ba454 100644
--- a/module/data/fields/_module.mjs
+++ b/module/data/fields/_module.mjs
@@ -2,5 +2,6 @@ export { ActionCollection } from './actionField.mjs';
export { default as FormulaField } from './formulaField.mjs';
export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs';
export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs';
+export { default as TriggerField } from './triggerField.mjs';
export { default as MappingField } from './mappingField.mjs';
export * as ActionFields from './action/_module.mjs';
diff --git a/module/data/fields/triggerField.mjs b/module/data/fields/triggerField.mjs
new file mode 100644
index 00000000..8378ea19
--- /dev/null
+++ b/module/data/fields/triggerField.mjs
@@ -0,0 +1,24 @@
+export default class TriggerField extends foundry.data.fields.SchemaField {
+ constructor(context) {
+ super(
+ {
+ trigger: new foundry.data.fields.StringField({
+ nullable: false,
+ blank: false,
+ initial: CONFIG.DH.TRIGGER.triggers.dualityRoll.id,
+ choices: CONFIG.DH.TRIGGER.triggers,
+ label: 'DAGGERHEART.CONFIG.Triggers.triggerType'
+ }),
+ triggeringActorType: new foundry.data.fields.StringField({
+ nullable: false,
+ blank: false,
+ initial: CONFIG.DH.TRIGGER.triggerActorTargetType.any.id,
+ choices: CONFIG.DH.TRIGGER.triggerActorTargetType,
+ label: 'DAGGERHEART.CONFIG.Triggers.triggeringActorType'
+ }),
+ command: new foundry.data.fields.JavaScriptField({ async: true })
+ },
+ context
+ );
+ }
+}
diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs
index e35fae46..3d4a62fa 100644
--- a/module/data/item/armor.mjs
+++ b/module/data/item/armor.mjs
@@ -54,6 +54,21 @@ export default class DHArmor extends AttachableItem {
);
}
+ /**@inheritdoc */
+ async getDescriptionData() {
+ const baseDescription = this.description;
+ const allFeatures = CONFIG.DH.ITEM.allArmorFeatures();
+ const features = this.armorFeatures.map(x => allFeatures[x.value]);
+ if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
+
+ const prefix = await foundry.applications.handlebars.renderTemplate(
+ 'systems/daggerheart/templates/sheets/items/armor/description.hbs',
+ { features }
+ );
+
+ return { prefix, value: baseDescription, suffix: null };
+ }
+
/**@inheritdoc */
async _preUpdate(changes, options, user) {
const allowed = await super._preUpdate(changes, options, user);
diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs
index 11be0a52..415fc8d4 100644
--- a/module/data/item/base.mjs
+++ b/module/data/item/base.mjs
@@ -8,7 +8,7 @@
* @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item
*/
-import { addLinkedItemsDiff, createScrollText, getScrollTextData, updateLinkedItemApps } from '../../helpers/utils.mjs';
+import { addLinkedItemsDiff, getScrollTextData, updateLinkedItemApps } from '../../helpers/utils.mjs';
import { ActionsField } from '../fields/actionField.mjs';
import FormulaField from '../fields/formulaField.mjs';
@@ -124,6 +124,33 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
return [source, page ? `pg ${page}.` : null].filter(x => x).join('. ');
}
+ /**
+ * Augments the description for the item with type specific info to display. Implemented in applicable item subtypes.
+ * @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
+ * @returns {string}
+ */
+ async getDescriptionData(_options) {
+ return { prefix: null, value: this.description, suffix: null };
+ }
+
+ /**
+ * Gets the enriched and augmented description for the item.
+ * @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
+ * @returns {string}
+ */
+ async getEnrichedDescription() {
+ if (!this.metadata.hasDescription) return '';
+
+ const { prefix, value, suffix } = await this.getDescriptionData();
+ const fullDescription = [prefix, value, suffix].filter(p => !!p).join('\n
When you cause an adversary to mark 1 or more Hit Points, you can spend 2 Hope to increase your Evasion by the number of Hit Points they marked. This bonus lasts until after the next attack made against you.
", "chatDisplay": true, "actionType": "action", - "cost": [], + "cost": [ + { + "scalable": false, + "key": "hope", + "value": 2, + "itemId": null, + "step": null, + "consumeOnSuccess": false + } + ], "uses": { "value": null, "max": "", @@ -30,8 +39,15 @@ "amount": null }, "name": "Spend Hope", - "img": "icons/skills/melee/maneuver-daggers-paired-orange.webp", - "range": "" + "img": "icons/skills/melee/maneuver-sword-katana-yellow.webp", + "range": "", + "triggers": [ + { + "trigger": "postDamageReduction", + "triggeringActorType": "other", + "command": "/* Check if sufficient hope */\nif (this.actor.system.resources.hope.value < 2) return;\n\n/* Check if hit point damage was dealt */\nconst hpDamage = damageUpdates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);\nif (hpDamage.value < 0) return;\n\n/* Dialog to give player choice */\nconst confirmed = await foundry.applications.api.DialogV2.confirm({\n window: { title: this.item?.name ?? '' },\n content: game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.ferocityContent', { bonus: hpDamage.value }),\n});\n\nif (!confirmed) return;\n\n/* Create the effect */\nthis.actor.createEmbeddedDocuments('ActiveEffect', [{\n name: this.item.name,\n img: 'icons/skills/melee/maneuver-sword-katana-yellow.webp',\n description: game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.ferocityEffectDescription', { bonus: hpDamage.value }),\n changes: [{ key: 'system.evasion', mode: 2, value: hpDamage.value }]\n}]);\n\n/* Update hope */\nreturn { updates: [{ \n originActor: this.actor, \n updates: [{\n key: CONFIG.DH.GENERAL.healingTypes.hope.id,\n value: -2,\n total: 2\n }] \n}]}" + } + ] } }, "attribution": { diff --git a/styles/less/sheets/actions/actions.less b/styles/less/sheets/actions/actions.less index 813e4416..07c99491 100644 --- a/styles/less/sheets/actions/actions.less +++ b/styles/less/sheets/actions/actions.less @@ -47,4 +47,58 @@ color: light-dark(@dark-blue-50, @beige-50); } } + + .trigger-data { + width: 100%; + display: flex; + justify-content: space-between; + gap: 8px; + + .trigger-data-inner { + flex: 1; + display: flex; + flex-direction: column; + + select { + flex: 1; + } + + .select-section { + flex: 1; + display: flex; + gap: 8px; + } + + .programmer-section { + flex: 3; + display: flex; + flex-direction: column; + + .hint-section { + display: flex; + gap: 4px; + + .hint { + flex: 1; + flex-wrap: wrap; + } + } + } + } + + .expand-trigger { + font-size: 18px; + } + } + + .code-mirror-wrapper { + width: 100%; + height: 0; + min-height: 0; + transition: height 0.1s ease-in-out; + + &.revealed { + height: 300px; + } + } } diff --git a/styles/less/sheets/index.less b/styles/less/sheets/index.less index c339e7be..216cda33 100644 --- a/styles/less/sheets/index.less +++ b/styles/less/sheets/index.less @@ -1,3 +1,5 @@ +@import './actions/actions.less'; + @import './actors/actor-sheet-shared.less'; @import './actors/adversary/actions.less'; diff --git a/styles/less/ui/index.less b/styles/less/ui/index.less index 7f9ada25..25f51d0f 100644 --- a/styles/less/ui/index.less +++ b/styles/less/ui/index.less @@ -33,3 +33,5 @@ @import './scene-config/scene-config.less'; @import './effects-display/sheet.less'; + +@import './scene-navigation/scene-navigation.less'; diff --git a/styles/less/ui/scene-config/scene-config.less b/styles/less/ui/scene-config/scene-config.less index fb36dd33..664e7526 100644 --- a/styles/less/ui/scene-config/scene-config.less +++ b/styles/less/ui/scene-config/scene-config.less @@ -37,4 +37,63 @@ .helper-text { font-style: italic; } + + .scene-environments { + display: flex; + flex-direction: column; + gap: 8px; + + .scene-environment { + display: flex; + align-items: center; + gap: 8px; + + .scene-environment-inner { + display: flex; + align-items: center; + gap: 16px; + flex: 1; + + img { + height: 36px; + } + + h5 { + margin: 0; + } + + .tags { + display: flex; + gap: 4px; + padding-bottom: 0; + + .tag { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: var(--font-size-12); + font: @font-body; + + background: light-dark(@dark-15, @beige-15); + border: 1px solid light-dark(@dark, @beige); + border-radius: 3px; + } + + .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: var(--font-size-12); + } + } + } + + .remove-icon { + font-size: 16px; + } + } + } } diff --git a/styles/less/ui/scene-navigation/scene-navigation.less b/styles/less/ui/scene-navigation/scene-navigation.less new file mode 100644 index 00000000..6b97ddec --- /dev/null +++ b/styles/less/ui/scene-navigation/scene-navigation.less @@ -0,0 +1,36 @@ +#ui-left #ui-left-column-2 { + flex: 0 0 230px; + + .scene-navigation { + .scene-wrapper { + display: flex; + gap: 2px; + height: var(--control-size); + width: 100%; + + .scene-environment { + padding: 0; + + img { + border-radius: 4px; + } + } + } + + .scene { + justify-content: center; + align-content: center; + background: var(--control-bg-color); + border: 1px solid var(--control-border-color); + border-radius: 4px; + color: var(--control-icon-color); + pointer-events: all; + transition: + border 0.25s, + color 0.25s; + text-shadow: none; + width: 200px; + max-width: 200px; + } + } +} diff --git a/system.json b/system.json index c5fd61f2..5570bdbf 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "1.4.5", + "version": "1.4.6", "compatibility": { "minimum": "13.346", "verified": "13.351", diff --git a/templates/scene/dh-config.hbs b/templates/scene/dh-config.hbs index 1f7dcd81..017613ee 100644 --- a/templates/scene/dh-config.hbs +++ b/templates/scene/dh-config.hbs @@ -21,4 +21,39 @@ {{localize "DAGGERHEART.SETTINGS.Scene.disabledText"}} {{/if}} + + \ No newline at end of file diff --git a/templates/settings/automation-settings/roll.hbs b/templates/settings/automation-settings/roll.hbs index 5769bf61..dc65f8ae 100644 --- a/templates/settings/automation-settings/roll.hbs +++ b/templates/settings/automation-settings/roll.hbs @@ -19,4 +19,15 @@ {{/each}} + + \ No newline at end of file diff --git a/templates/sheets-settings/action-settings/trigger.hbs b/templates/sheets-settings/action-settings/trigger.hbs new file mode 100644 index 00000000..b048461e --- /dev/null +++ b/templates/sheets-settings/action-settings/trigger.hbs @@ -0,0 +1,37 @@ +