diff --git a/src/macros_new/gowlers-tracking-ledger/foundry.gowlershome.dyndns.org/modules/gowlers-tracking-ledger/scripts/gowlers-tracking-ledger.js b/src/macros_new/gowlers-tracking-ledger/foundry.gowlershome.dyndns.org/modules/gowlers-tracking-ledger/scripts/gowlers-tracking-ledger.js
index 83eb7c7b..a3534349 100644
--- a/src/macros_new/gowlers-tracking-ledger/foundry.gowlershome.dyndns.org/modules/gowlers-tracking-ledger/scripts/gowlers-tracking-ledger.js
+++ b/src/macros_new/gowlers-tracking-ledger/foundry.gowlershome.dyndns.org/modules/gowlers-tracking-ledger/scripts/gowlers-tracking-ledger.js
@@ -1,6 +1,6 @@
const MODULE_ID = "gowlers-tracking-ledger";
-const MODULE_VERSION = "1.1.1";
+const MODULE_VERSION = "1.2.1";
const TRACK_SETTING = "actorSettings";
const FLAG_SCOPE = "world";
const MAX_HISTORY_ROWS = 100;
@@ -9,8 +9,8 @@ const BUTTON_TITLE = "Open Log";
const BUTTON_ICON = '';
const BUTTON_STYLE =
"position:absolute;right:6px;top:4px;border:none;background:transparent;padding:0;width:18px;height:18px;display:flex;align-items:center;justify-content:center;color:inherit;cursor:pointer;";
-const DEFAULT_TRACKING = Object.freeze({ hp: true, xp: true, currency: true });
-const DEFAULT_CHAT = Object.freeze({ hp: false, xp: false, currency: false });
+const DEFAULT_TRACKING = Object.freeze({ hp: true, outgoing: true, xp: true, currency: true, encounters: true });
+const DEFAULT_CHAT = Object.freeze({ hp: false, outgoing: false, xp: false, currency: false, encounters: false, chatAll: false });
const SETTINGS_VERSION = 2;
const COIN_ORDER = ["pp", "gp", "sp", "cp"];
const ENCOUNTER_FLAG = "pf1EncounterHistory";
@@ -85,6 +85,7 @@ const ledgerState = {
historyTabState: new Map(), // actorId -> active tab id
damageOverlay: null,
damageMeterIncludeNPCs: true,
+ damageMeterMode: "encounter", // "encounter" or "all"
};
function getHistoryPageState(actorId, tabId) {
@@ -275,6 +276,10 @@ Hooks.once("ready", async () => {
ledgerState.damageMeterIncludeNPCs = !!checked;
refreshDamageMeterOverlay();
};
+ window.GowlersTrackingDamageMeterSetMode = (mode) => {
+ ledgerState.damageMeterMode = mode === "all" ? "all" : "encounter";
+ refreshDamageMeterOverlay();
+ };
});
async function initializeModule() {
@@ -587,6 +592,20 @@ function registerSceneControls() {
button: true,
onClick: () => openDamageMeterOverlay(),
});
+ tokenControls.tools.push({
+ name: "history-ledger",
+ title: "History Ledger",
+ icon: "fas fa-scroll",
+ button: true,
+ onClick: () => {
+ const actor = canvas?.tokens?.controlled?.[0]?.actor ?? null;
+ if (actor) {
+ openHistoryDialog(actor, "hp");
+ } else {
+ ui.notifications?.warn?.("Select a token to open its history ledger.");
+ }
+ },
+ });
});
}
@@ -611,7 +630,7 @@ function openDamageMeterOverlay() {
},
}, {
id: `${MODULE_ID}-damage-meter`,
- width: 300,
+ width: 420,
height: "auto",
resizable: true,
});
@@ -967,9 +986,9 @@ function buildHistoryContent(actor, tabArg) {
setActiveHistoryTab(actor.id, initialTab);
const canConfigure = game.user?.isGM;
const configs = [
- {
- id: "hp",
- label: "HP",
+ {
+ id: "hp",
+ label: "Incoming",
flag: STAT_CONFIGS.hp.flag,
columns: [
{ label: "Timestamp", render: (entry) => formatDate(entry.timestamp) },
@@ -985,11 +1004,12 @@ function buildHistoryContent(actor, tabArg) {
},
{
id: "damage",
- label: "Damage",
+ label: "Outgoing",
flag: DAMAGE_DEALT_FLAG,
columns: [
{ label: "Timestamp", render: (entry) => formatDate(entry.timestamp) },
- { label: "Dmg", render: (entry) => entry.amount != null ? `${entry.amount}` : "" },
+ { label: "Dmg", render: (entry) => entry.type === "healing" ? "" : (entry.amount != null ? `${entry.amount}` : "") },
+ { label: "Heal", render: (entry) => entry.type === "healing" ? `${entry.amount}` : "" },
{
label: "Details",
render: (entry) => {
@@ -1291,6 +1311,7 @@ async function recordHistoryEntry(actor, statId, previous, nextValue, userId, op
source: source,
breakdown: damageBreakdown,
encounterId: encounterId,
+ type: "damage",
damageDetails: matchedMessage.damageDetails ?? null,
};
recordDamageDealt(attacker, dealtEntry);
@@ -1300,6 +1321,33 @@ async function recordHistoryEntry(actor, statId, previous, nextValue, userId, op
}
}
}
+ // For HP healing, also try to match recent messages (same mechanism as damage)
+ else if (statId === "hp" && diffValue > 0) {
+ const healAmount = Math.abs(diffValue);
+ const now = Date.now();
+
+ for (let i = ledgerState.recentMessages.length - 1; i >= 0; i--) {
+ const msg = ledgerState.recentMessages[i];
+ const timeSinceMessage = now - msg.timestamp;
+ const valueMatch = Math.abs(Math.abs(msg.value) - healAmount) <= Math.max(1, healAmount * 0.1);
+
+ if (timeSinceMessage < 4000 && valueMatch) {
+ matchedMessage = msg;
+ console.log("[GowlersTracking] Found matching healing message:", msg.source, "value:", msg.value, "vs", healAmount);
+ ledgerState.recentMessages.splice(i, 1);
+ break;
+ }
+ }
+
+ if (matchedMessage) {
+ source = matchedMessage.source;
+ damageBreakdown =
+ formatDamageBreakdown(matchedMessage.damageDetails, healAmount) ||
+ matchedMessage.damageDetails?.breakdown ||
+ `${healAmount} healing`;
+ console.log(`[GowlersTracking] Using matched healing source:`, source);
+ }
+ }
// Fallback for unmatched HP changes
if (source === "Manual" && statId === "hp") {
@@ -1312,6 +1360,31 @@ async function recordHistoryEntry(actor, statId, previous, nextValue, userId, op
source = "Healing";
damageBreakdown = `${hpDiff} healing`;
}
+ } else if (statId === "hp" && diffValue > 0) {
+ // Record outgoing healing (simple)
+ const metadata = change?.metadata ?? options?.message?.flags?.pf1?.metadata ?? {};
+ const healer = resolveActorFromMetadataSafe(metadata) || resolveActorFromMetadataSafe(matchedMessage?.message?.flags?.pf1?.metadata) || actor; // fallback to self
+ if (matchedMessage) {
+ source = matchedMessage.source;
+ damageBreakdown =
+ formatDamageBreakdown(matchedMessage.damageDetails, diffValue) ||
+ matchedMessage.damageDetails?.breakdown ||
+ `${diffValue} healing`;
+ } else {
+ source = healer?.name ? `${healer.name} -> Healing` : "Healing";
+ damageBreakdown = `${diffValue} healing`;
+ }
+ const healEntry = {
+ timestamp: Date.now(),
+ amount: diffValue,
+ target: actor.name,
+ source: source,
+ breakdown: damageBreakdown,
+ encounterId: encounterId,
+ type: "healing",
+ damageDetails: matchedMessage?.damageDetails ?? null,
+ };
+ recordDamageDealt(healer, healEntry);
} else if (statId === "xp" && diffValue > 0) {
// XP gains - encounter or manual award
source = options?.source ?? (encounterId ? "Encounter XP Award" : "XP Award");
@@ -1525,6 +1598,7 @@ function formatDamagePartsWithIcons(parts) {
force: { icon: "ra ra-crystal-ball", color: "#845ef7" },
negative: { icon: "ra ra-skull", color: "#7950f2" },
positive: { icon: "ra ra-sun", color: "#fab005" },
+ healing: { icon: "ra ra-health", color: "#4caf50" },
precision: { icon: "ra ra-target-arrows", color: "#000" },
nonlethal: { icon: "ra ra-hand", color: "#000" },
untyped: { icon: "ra ra-uncertainty", color: "#666" },
@@ -1553,6 +1627,7 @@ function renderDamageBar(composition = [], total = 0) {
force: { icon: "ra ra-crystal-ball", color: "#845ef7" },
negative: { icon: "ra ra-skull", color: "#7950f2" },
positive: { icon: "ra ra-sun", color: "#fab005" },
+ healing: { icon: "ra ra-health", color: "#4caf50" },
precision: { icon: "ra ra-target-arrows", color: "#000" },
nonlethal: { icon: "ra ra-hand", color: "#000" },
untyped: { icon: "ra ra-uncertainty", color: "#666" },
@@ -1684,7 +1759,8 @@ function refreshDamageMeterOverlay() {
}
function computeDamageMeterData() {
- const currentEncounterId = game.combat?.id ?? ledgerState.lastCombatId ?? null;
+ const mode = ledgerState.damageMeterMode === "all" ? "all" : "encounter";
+ const currentEncounterId = mode === "encounter" ? (game.combat?.id ?? ledgerState.lastCombatId ?? null) : null;
const actorsMap = new Map();
// Directory actors
@@ -1724,10 +1800,13 @@ function computeDamageMeterData() {
if (!filtered.length) continue;
const typeTotals = new Map();
- let total = 0;
+ let totalDamage = 0;
+ let totalHealing = 0;
for (const e of filtered) {
const amount = Number(e.amount) || 0;
- total += amount;
+ if (e.type === "healing") totalHealing += amount;
+ else totalDamage += amount;
+
const parts = e.damageDetails?.parts;
if (Array.isArray(parts) && parts.length) {
for (const p of parts) {
@@ -1736,8 +1815,9 @@ function computeDamageMeterData() {
typeTotals.set(t, prev + (Number(p.total) || 0));
}
} else {
- const prev = typeTotals.get("untyped") ?? 0;
- typeTotals.set("untyped", prev + amount);
+ const key = e.type === "healing" ? "healing" : "untyped";
+ const prev = typeTotals.get(key) ?? 0;
+ typeTotals.set(key, prev + amount);
}
}
@@ -1749,7 +1829,9 @@ function computeDamageMeterData() {
totals.push({
actorId: actor.id,
name: actor.name,
- total,
+ total: totalDamage,
+ healing: totalHealing,
+ overall: totalDamage + totalHealing,
hits: filtered.length,
last: filtered[0],
img,
@@ -1757,29 +1839,44 @@ function computeDamageMeterData() {
});
}
- totals.sort((a, b) => b.total - a.total);
+ totals.sort((a, b) => (b.overall ?? b.total) - (a.overall ?? a.total));
const grandTotal = totals.reduce((s, t) => s + t.total, 0);
- return { totals, grandTotal, encounterId: currentEncounterId };
+ const grandHealing = totals.reduce((s, t) => s + (t.healing ?? 0), 0);
+ return { totals, grandTotal, grandHealing, encounterId: currentEncounterId };
}
function buildDamageMeterContent() {
- const { totals, grandTotal, encounterId } = computeDamageMeterData();
+ const { totals, grandTotal, grandHealing, encounterId } = computeDamageMeterData();
const encounterLabel = encounterId ? `Encounter: ${encounterId.slice(0, 8)}` : "All history";
const includeNPCs = ledgerState.damageMeterIncludeNPCs;
+ const npcToggle = ``;
+ const modeToggle = `
+
+
+
+
`;
+
const rows = totals.length
? totals
.map(
(t, idx) => {
- const max = totals[0]?.total || 1;
- const pct = Math.max(1, Math.round((t.total / max) * 100));
- const bar = renderDamageBar(t.composition, t.total) || ``;
+ const max = totals[0]?.overall || totals[0]?.total || 1;
+ const pct = Math.max(1, Math.round(((t.overall ?? t.total) / max) * 100));
+ const innerBar =
+ renderDamageBar(t.composition, t.overall ?? t.total) ||
+ ``;
+ const bar = `${innerBar}
`;
const avatar = t.img ? `
` : "";
return `
| ${idx + 1} |
${avatar}${t.name} |
${t.total} |
+ ${t.healing} |
${t.hits} |
${bar}
@@ -1789,26 +1886,23 @@ function buildDamageMeterContent() {
}
)
.join("")
- : ` |
| No damage recorded${encounterId ? " for this encounter" : ""}. |
`;
-
- const npcToggle = ``;
+ : `| No damage recorded${encounterId ? " for this encounter" : ""}. |
`;
return `
Damage Meter
-
-
${encounterLabel}
- ${npcToggle}
+
+
${modeToggle}
+
${npcToggle}
+
${encounterLabel}
| # |
Actor |
- Total |
+ Damage |
+ Healing |
Hits |
Last Breakdown |
@@ -1821,6 +1915,7 @@ function buildDamageMeterContent() {
|
Total |
${grandTotal} |
+ ${grandHealing} |
|
|
@@ -1853,6 +1948,7 @@ function createActorConfig(source = null) {
if (source.version === SETTINGS_VERSION) {
config.tracking = { ...config.tracking, ...(source.tracking ?? {}) };
config.chat = { ...config.chat, ...(source.chat ?? {}) };
+ config.chat.chatAll = source.chat?.chatAll ?? Object.values(source.chat ?? {}).some(Boolean);
return config;
}
@@ -1912,7 +2008,8 @@ function getActorTracking(actorId) {
function getActorChat(actorId) {
const entry = getActorConfig(actorId);
- return { ...entry.chat };
+ const hasAny = Object.values(entry.chat ?? {}).some(Boolean);
+ return { ...entry.chat, chatAll: entry.chat?.chatAll ?? hasAny };
}
async function ensureActorConfig(actor) {
@@ -1931,8 +2028,9 @@ async function setActorTracking(actorId, partial) {
const entry = getActorConfig(actorId);
entry.tracking = {
hp: partial.hp ?? entry.tracking.hp,
- xp: partial.xp ?? entry.tracking.xp,
+ outgoing: partial.outgoing ?? entry.tracking.outgoing,
currency: partial.currency ?? entry.tracking.currency,
+ encounters: partial.encounters ?? entry.tracking.encounters,
};
await saveActorSettings(settings);
const actor = game.actors.get(actorId);
@@ -1943,17 +2041,32 @@ async function setActorChat(actorId, partial) {
if (!actorId) return;
const settings = getSettingsCache();
const entry = getActorConfig(actorId);
+ const chatAll = partial.chatAll;
+ const mergedChat = { ...entry.chat, ...partial };
+ if (chatAll !== undefined && chatAll !== null) {
+ mergedChat.hp = !!chatAll;
+ mergedChat.outgoing = !!chatAll;
+ mergedChat.xp = !!chatAll;
+ mergedChat.currency = !!chatAll;
+ mergedChat.encounters = !!chatAll;
+ mergedChat.chatAll = !!chatAll;
+ } else {
+ mergedChat.chatAll = !!mergedChat.chatAll;
+ }
entry.chat = {
- hp: partial.hp ?? entry.chat.hp,
- xp: partial.xp ?? entry.chat.xp,
- currency: partial.currency ?? entry.chat.currency,
+ hp: mergedChat.hp,
+ outgoing: mergedChat.outgoing,
+ xp: mergedChat.xp,
+ currency: mergedChat.currency,
+ encounters: mergedChat.encounters,
+ chatAll: mergedChat.chatAll,
};
await saveActorSettings(settings);
}
function shouldSendChat(actorId, statId) {
const chat = getActorChat(actorId);
- return !!chat[statId];
+ return chat.chatAll ?? !!chat[statId];
}
class TrackingLedgerConfig extends FormApplication {
@@ -2009,11 +2122,13 @@ class TrackingLedgerConfig extends FormApplication {
const start = this._page * pageSize;
const pageItems = filtered.slice(start, start + pageSize).map((ref) => {
const entry = settings[ref.id] ?? createActorConfig();
+ const chatAll = entry.chat?.chatAll ?? false;
return {
id: ref.id,
name: ref.name,
tracking: { ...entry.tracking },
- chat: { ...entry.chat },
+ chat: { ...entry.chat, chatAll },
+ chatAll: chatAll,
};
});
@@ -2033,6 +2148,12 @@ class TrackingLedgerConfig extends FormApplication {
}));
const totalPagesDisplay = totalActors ? totalPages : 0;
+ const allEntries = Object.values(settings);
+ const allIncoming = allEntries.length ? allEntries.every((e) => e.tracking?.hp) : false;
+ const allOutgoing = allEntries.length ? allEntries.every((e) => e.tracking?.outgoing) : false;
+ const allCurrency = allEntries.length ? allEntries.every((e) => e.tracking?.currency) : false;
+ const allEncounters = allEntries.length ? allEntries.every((e) => e.tracking?.encounters) : false;
+ const allChatAll = allEntries.length ? allEntries.every((e) => e.chat?.chatAll ?? false) : false;
return {
actors: pageItems,
@@ -2048,6 +2169,12 @@ class TrackingLedgerConfig extends FormApplication {
hasPrev,
hasNext,
displayPage: totalActors ? this._page + 1 : 0,
+ moduleVersion: MODULE_VERSION,
+ allIncoming,
+ allOutgoing,
+ allCurrency,
+ allEncounters,
+ allChatAll,
};
}
@@ -2065,25 +2192,40 @@ class TrackingLedgerConfig extends FormApplication {
const entry = getActorConfig(actorId);
const nextTracking = {
hp: checkboxValue(cfg.tracking?.hp, false),
- xp: checkboxValue(cfg.tracking?.xp, false),
+ outgoing: checkboxValue(cfg.tracking?.outgoing, false),
+ xp: entry.tracking?.xp ?? true,
currency: checkboxValue(cfg.tracking?.currency, false),
+ encounters: checkboxValue(cfg.tracking?.encounters, false),
};
+ const chatAll = checkboxValue(cfg.chatAll, false);
const nextChat = {
- hp: checkboxValue(cfg.chat?.hp, false),
- xp: checkboxValue(cfg.chat?.xp, false),
- currency: checkboxValue(cfg.chat?.currency, false),
+ hp: chatAll,
+ outgoing: chatAll,
+ xp: chatAll,
+ currency: chatAll,
+ encounters: chatAll,
+ chatAll: chatAll,
};
if (
entry.tracking.hp !== nextTracking.hp ||
+ entry.tracking.outgoing !== nextTracking.outgoing ||
entry.tracking.xp !== nextTracking.xp ||
- entry.tracking.currency !== nextTracking.currency
+ entry.tracking.currency !== nextTracking.currency ||
+ entry.tracking.encounters !== nextTracking.encounters
) {
entry.tracking = nextTracking;
dirty = true;
}
- if (entry.chat.hp !== nextChat.hp || entry.chat.xp !== nextChat.xp || entry.chat.currency !== nextChat.currency) {
+ if (
+ entry.chat.hp !== nextChat.hp ||
+ entry.chat.outgoing !== nextChat.outgoing ||
+ entry.chat.xp !== nextChat.xp ||
+ entry.chat.currency !== nextChat.currency ||
+ entry.chat.encounters !== nextChat.encounters ||
+ entry.chat.chatAll !== nextChat.chatAll
+ ) {
entry.chat = nextChat;
dirty = true;
}
@@ -2142,6 +2284,109 @@ class TrackingLedgerConfig extends FormApplication {
TrackingLedgerConfig._lastPage = this._page;
this.render(false);
});
+
+ html.find("[data-action=\"toggle-all\"]").on("change", async (event) => {
+ const target = event.currentTarget.dataset.target;
+ const value = event.currentTarget.checked;
+ if (!target) return;
+ const settings = getSettingsCache();
+ let dirty = false;
+ for (const [actorId, entry] of Object.entries(settings)) {
+ if (!entry.tracking || !entry.chat) continue;
+ switch (target) {
+ case "tracking.hp":
+ if (entry.tracking.hp !== value) {
+ entry.tracking.hp = value;
+ dirty = true;
+ }
+ break;
+ case "tracking.outgoing":
+ if (entry.tracking.outgoing !== value) {
+ entry.tracking.outgoing = value;
+ dirty = true;
+ }
+ break;
+ case "tracking.currency":
+ if (entry.tracking.currency !== value) {
+ entry.tracking.currency = value;
+ dirty = true;
+ }
+ break;
+ case "tracking.encounters":
+ if (entry.tracking.encounters !== value) {
+ entry.tracking.encounters = value;
+ dirty = true;
+ }
+ break;
+ case "chat.chatAll":
+ if (
+ entry.chat.chatAll !== value ||
+ entry.chat.hp !== value ||
+ entry.chat.outgoing !== value ||
+ entry.chat.currency !== value ||
+ entry.chat.encounters !== value
+ ) {
+ entry.chat.chatAll = value;
+ entry.chat.hp = value;
+ entry.chat.outgoing = value;
+ entry.chat.currency = value;
+ entry.chat.encounters = value;
+ entry.chat.xp = value;
+ dirty = true;
+ }
+ break;
+ }
+ }
+ if (dirty) {
+ await saveActorSettings(settings);
+ this.render(false);
+ }
+ });
+
+ html.find("[data-action=\"clear-all-history\"]").on("click", async (event) => {
+ event.preventDefault();
+ const confirmed = await Dialog.confirm({
+ title: "Clear All Histories",
+ content: "Remove all stored ledger history for all actors?
",
+ yes: () => true,
+ no: () => false,
+ defaultYes: false,
+ });
+ if (!confirmed) return;
+ try {
+ const actors = collectAllActorDocuments();
+ const tokens = collectAllTokens();
+ for (const actor of actors) await clearDocumentHistory(actor);
+ for (const token of tokens) await clearDocumentHistory(token);
+ ui.notifications?.info?.("History cleared for all actors.");
+ } catch (err) {
+ console.error("[GowlersTracking] Failed to clear all histories", err);
+ ui.notifications?.error?.("Failed to clear all histories; see console.");
+ }
+ });
+
+ html.find("[data-action=\"clear-history\"]").on("click", async (event) => {
+ event.preventDefault();
+ const actorId = event.currentTarget.dataset.actorId;
+ if (!actorId) return;
+ const actor = game.actors.get(actorId);
+ if (!actor) return;
+ const confirmed = await Dialog.confirm({
+ title: "Clear History",
+ content: `Remove all stored ledger history (incoming, outgoing, XP, currency, encounters) for ${actor.name}?
`,
+ yes: () => true,
+ no: () => false,
+ defaultYes: false,
+ });
+ if (!confirmed) return;
+ try {
+ await clearDocumentHistory(actor);
+ ui.notifications?.info?.(`History cleared for ${actor.name}.`);
+ } catch (err) {
+ console.error("[GowlersTracking] Failed to clear history", err);
+ ui.notifications?.error?.("Failed to clear history for actor; see console.");
+ }
+ });
}
static openForActor(actorId) {
@@ -2156,6 +2401,44 @@ class TrackingLedgerConfig extends FormApplication {
}
}
+async function clearDocumentHistory(doc) {
+ if (!doc?.unsetFlag) return;
+ const flagKeys = [
+ ...Object.values(STAT_CONFIGS).map((cfg) => cfg.flag),
+ DAMAGE_DEALT_FLAG,
+ ENCOUNTER_FLAG,
+ ];
+ for (const key of flagKeys) {
+ try {
+ await doc.unsetFlag(FLAG_SCOPE, key);
+ } catch (err) {
+ console.warn(`[GowlersTracking] Failed to clear flag ${key} for ${doc.name ?? doc.id}`, err);
+ }
+ }
+}
+
+function collectAllActorDocuments() {
+ const actors = new Map();
+ for (const a of game.actors.contents ?? []) {
+ actors.set(a.id, a);
+ }
+ for (const scene of game.scenes ?? []) {
+ for (const token of scene.tokens ?? []) {
+ const a = token.actor;
+ if (a?.id && !actors.has(a.id)) actors.set(a.id, a);
+ }
+ }
+ return Array.from(actors.values());
+}
+
+function collectAllTokens() {
+ const tokens = [];
+ for (const scene of game.scenes ?? []) {
+ for (const token of scene.tokens ?? []) tokens.push(token);
+ }
+ return tokens;
+}
+
/**
* Update encounter summary when combat starts
*/
@@ -2291,10 +2574,22 @@ function sendChatNotification(statId, actor, previous, nextValue, entry) {
const title = titles[statId] ?? statId.toUpperCase();
const prevText = config.formatValue(previous);
const nextText = entry.value;
+ const encounter = entry.encounterId ? entry.encounterId.slice(0, 8) : "N/A";
+ const source = entry.source ?? "Manual";
+ const partsHtml = entry.damageDetails?.parts?.length ? formatDamagePartsWithIcons(entry.damageDetails.parts) : "";
+ const details = entry.damageBreakdown || entry.breakdown || "";
+ const detailHtml = partsHtml || details ? `Details: ${partsHtml || details}
` : "";
+
const content = `
- ${title} Log
- ${actor.name}: ${prevText} -> ${nextText} (${entry.diff})
- User: ${entry.user ?? "System"}
+
+
${title} Update
+
Actor: ${actor.name}
+
Value: ${prevText} → ${nextText} (${entry.diff})
+
Source: ${source}
+
Encounter: ${encounter}
+ ${detailHtml}
+
User: ${entry.user ?? "System"}
+
`;
ChatMessage.create({
diff --git a/src/macros_new/gowlers-tracking-ledger/templates/config.hbs b/src/macros_new/gowlers-tracking-ledger/templates/config.hbs
index 00d5a2cb..3f697ee6 100644
--- a/src/macros_new/gowlers-tracking-ledger/templates/config.hbs
+++ b/src/macros_new/gowlers-tracking-ledger/templates/config.hbs
@@ -34,21 +34,29 @@