Bump version and finalize history clearing
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
const MODULE_ID = "gowlers-tracking-ledger";
|
const MODULE_ID = "gowlers-tracking-ledger";
|
||||||
const MODULE_VERSION = "1.1.1";
|
const MODULE_VERSION = "1.2.1";
|
||||||
const TRACK_SETTING = "actorSettings";
|
const TRACK_SETTING = "actorSettings";
|
||||||
const FLAG_SCOPE = "world";
|
const FLAG_SCOPE = "world";
|
||||||
const MAX_HISTORY_ROWS = 100;
|
const MAX_HISTORY_ROWS = 100;
|
||||||
@@ -9,8 +9,8 @@ const BUTTON_TITLE = "Open Log";
|
|||||||
const BUTTON_ICON = '<i class="fas fa-clock-rotate-left"></i>';
|
const BUTTON_ICON = '<i class="fas fa-clock-rotate-left"></i>';
|
||||||
const BUTTON_STYLE =
|
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;";
|
"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_TRACKING = Object.freeze({ hp: true, outgoing: true, xp: true, currency: true, encounters: true });
|
||||||
const DEFAULT_CHAT = Object.freeze({ hp: false, xp: false, currency: false });
|
const DEFAULT_CHAT = Object.freeze({ hp: false, outgoing: false, xp: false, currency: false, encounters: false, chatAll: false });
|
||||||
const SETTINGS_VERSION = 2;
|
const SETTINGS_VERSION = 2;
|
||||||
const COIN_ORDER = ["pp", "gp", "sp", "cp"];
|
const COIN_ORDER = ["pp", "gp", "sp", "cp"];
|
||||||
const ENCOUNTER_FLAG = "pf1EncounterHistory";
|
const ENCOUNTER_FLAG = "pf1EncounterHistory";
|
||||||
@@ -85,6 +85,7 @@ const ledgerState = {
|
|||||||
historyTabState: new Map(), // actorId -> active tab id
|
historyTabState: new Map(), // actorId -> active tab id
|
||||||
damageOverlay: null,
|
damageOverlay: null,
|
||||||
damageMeterIncludeNPCs: true,
|
damageMeterIncludeNPCs: true,
|
||||||
|
damageMeterMode: "encounter", // "encounter" or "all"
|
||||||
};
|
};
|
||||||
|
|
||||||
function getHistoryPageState(actorId, tabId) {
|
function getHistoryPageState(actorId, tabId) {
|
||||||
@@ -275,6 +276,10 @@ Hooks.once("ready", async () => {
|
|||||||
ledgerState.damageMeterIncludeNPCs = !!checked;
|
ledgerState.damageMeterIncludeNPCs = !!checked;
|
||||||
refreshDamageMeterOverlay();
|
refreshDamageMeterOverlay();
|
||||||
};
|
};
|
||||||
|
window.GowlersTrackingDamageMeterSetMode = (mode) => {
|
||||||
|
ledgerState.damageMeterMode = mode === "all" ? "all" : "encounter";
|
||||||
|
refreshDamageMeterOverlay();
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
async function initializeModule() {
|
async function initializeModule() {
|
||||||
@@ -587,6 +592,20 @@ function registerSceneControls() {
|
|||||||
button: true,
|
button: true,
|
||||||
onClick: () => openDamageMeterOverlay(),
|
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`,
|
id: `${MODULE_ID}-damage-meter`,
|
||||||
width: 300,
|
width: 420,
|
||||||
height: "auto",
|
height: "auto",
|
||||||
resizable: true,
|
resizable: true,
|
||||||
});
|
});
|
||||||
@@ -969,7 +988,7 @@ function buildHistoryContent(actor, tabArg) {
|
|||||||
const configs = [
|
const configs = [
|
||||||
{
|
{
|
||||||
id: "hp",
|
id: "hp",
|
||||||
label: "HP",
|
label: "Incoming",
|
||||||
flag: STAT_CONFIGS.hp.flag,
|
flag: STAT_CONFIGS.hp.flag,
|
||||||
columns: [
|
columns: [
|
||||||
{ label: "Timestamp", render: (entry) => formatDate(entry.timestamp) },
|
{ label: "Timestamp", render: (entry) => formatDate(entry.timestamp) },
|
||||||
@@ -985,11 +1004,12 @@ function buildHistoryContent(actor, tabArg) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "damage",
|
id: "damage",
|
||||||
label: "Damage",
|
label: "Outgoing",
|
||||||
flag: DAMAGE_DEALT_FLAG,
|
flag: DAMAGE_DEALT_FLAG,
|
||||||
columns: [
|
columns: [
|
||||||
{ label: "Timestamp", render: (entry) => formatDate(entry.timestamp) },
|
{ 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",
|
label: "Details",
|
||||||
render: (entry) => {
|
render: (entry) => {
|
||||||
@@ -1291,6 +1311,7 @@ async function recordHistoryEntry(actor, statId, previous, nextValue, userId, op
|
|||||||
source: source,
|
source: source,
|
||||||
breakdown: damageBreakdown,
|
breakdown: damageBreakdown,
|
||||||
encounterId: encounterId,
|
encounterId: encounterId,
|
||||||
|
type: "damage",
|
||||||
damageDetails: matchedMessage.damageDetails ?? null,
|
damageDetails: matchedMessage.damageDetails ?? null,
|
||||||
};
|
};
|
||||||
recordDamageDealt(attacker, dealtEntry);
|
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
|
// Fallback for unmatched HP changes
|
||||||
if (source === "Manual" && statId === "hp") {
|
if (source === "Manual" && statId === "hp") {
|
||||||
@@ -1312,6 +1360,31 @@ async function recordHistoryEntry(actor, statId, previous, nextValue, userId, op
|
|||||||
source = "Healing";
|
source = "Healing";
|
||||||
damageBreakdown = `${hpDiff} 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) {
|
} else if (statId === "xp" && diffValue > 0) {
|
||||||
// XP gains - encounter or manual award
|
// XP gains - encounter or manual award
|
||||||
source = options?.source ?? (encounterId ? "Encounter XP Award" : "XP 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" },
|
force: { icon: "ra ra-crystal-ball", color: "#845ef7" },
|
||||||
negative: { icon: "ra ra-skull", color: "#7950f2" },
|
negative: { icon: "ra ra-skull", color: "#7950f2" },
|
||||||
positive: { icon: "ra ra-sun", color: "#fab005" },
|
positive: { icon: "ra ra-sun", color: "#fab005" },
|
||||||
|
healing: { icon: "ra ra-health", color: "#4caf50" },
|
||||||
precision: { icon: "ra ra-target-arrows", color: "#000" },
|
precision: { icon: "ra ra-target-arrows", color: "#000" },
|
||||||
nonlethal: { icon: "ra ra-hand", color: "#000" },
|
nonlethal: { icon: "ra ra-hand", color: "#000" },
|
||||||
untyped: { icon: "ra ra-uncertainty", color: "#666" },
|
untyped: { icon: "ra ra-uncertainty", color: "#666" },
|
||||||
@@ -1553,6 +1627,7 @@ function renderDamageBar(composition = [], total = 0) {
|
|||||||
force: { icon: "ra ra-crystal-ball", color: "#845ef7" },
|
force: { icon: "ra ra-crystal-ball", color: "#845ef7" },
|
||||||
negative: { icon: "ra ra-skull", color: "#7950f2" },
|
negative: { icon: "ra ra-skull", color: "#7950f2" },
|
||||||
positive: { icon: "ra ra-sun", color: "#fab005" },
|
positive: { icon: "ra ra-sun", color: "#fab005" },
|
||||||
|
healing: { icon: "ra ra-health", color: "#4caf50" },
|
||||||
precision: { icon: "ra ra-target-arrows", color: "#000" },
|
precision: { icon: "ra ra-target-arrows", color: "#000" },
|
||||||
nonlethal: { icon: "ra ra-hand", color: "#000" },
|
nonlethal: { icon: "ra ra-hand", color: "#000" },
|
||||||
untyped: { icon: "ra ra-uncertainty", color: "#666" },
|
untyped: { icon: "ra ra-uncertainty", color: "#666" },
|
||||||
@@ -1684,7 +1759,8 @@ function refreshDamageMeterOverlay() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function computeDamageMeterData() {
|
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();
|
const actorsMap = new Map();
|
||||||
|
|
||||||
// Directory actors
|
// Directory actors
|
||||||
@@ -1724,10 +1800,13 @@ function computeDamageMeterData() {
|
|||||||
if (!filtered.length) continue;
|
if (!filtered.length) continue;
|
||||||
|
|
||||||
const typeTotals = new Map();
|
const typeTotals = new Map();
|
||||||
let total = 0;
|
let totalDamage = 0;
|
||||||
|
let totalHealing = 0;
|
||||||
for (const e of filtered) {
|
for (const e of filtered) {
|
||||||
const amount = Number(e.amount) || 0;
|
const amount = Number(e.amount) || 0;
|
||||||
total += amount;
|
if (e.type === "healing") totalHealing += amount;
|
||||||
|
else totalDamage += amount;
|
||||||
|
|
||||||
const parts = e.damageDetails?.parts;
|
const parts = e.damageDetails?.parts;
|
||||||
if (Array.isArray(parts) && parts.length) {
|
if (Array.isArray(parts) && parts.length) {
|
||||||
for (const p of parts) {
|
for (const p of parts) {
|
||||||
@@ -1736,8 +1815,9 @@ function computeDamageMeterData() {
|
|||||||
typeTotals.set(t, prev + (Number(p.total) || 0));
|
typeTotals.set(t, prev + (Number(p.total) || 0));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const prev = typeTotals.get("untyped") ?? 0;
|
const key = e.type === "healing" ? "healing" : "untyped";
|
||||||
typeTotals.set("untyped", prev + amount);
|
const prev = typeTotals.get(key) ?? 0;
|
||||||
|
typeTotals.set(key, prev + amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1749,7 +1829,9 @@ function computeDamageMeterData() {
|
|||||||
totals.push({
|
totals.push({
|
||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
name: actor.name,
|
name: actor.name,
|
||||||
total,
|
total: totalDamage,
|
||||||
|
healing: totalHealing,
|
||||||
|
overall: totalDamage + totalHealing,
|
||||||
hits: filtered.length,
|
hits: filtered.length,
|
||||||
last: filtered[0],
|
last: filtered[0],
|
||||||
img,
|
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);
|
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() {
|
function buildDamageMeterContent() {
|
||||||
const { totals, grandTotal, encounterId } = computeDamageMeterData();
|
const { totals, grandTotal, grandHealing, encounterId } = computeDamageMeterData();
|
||||||
const encounterLabel = encounterId ? `Encounter: ${encounterId.slice(0, 8)}` : "All history";
|
const encounterLabel = encounterId ? `Encounter: ${encounterId.slice(0, 8)}` : "All history";
|
||||||
const includeNPCs = ledgerState.damageMeterIncludeNPCs;
|
const includeNPCs = ledgerState.damageMeterIncludeNPCs;
|
||||||
|
|
||||||
|
const npcToggle = `<label style="font-size:12px; display:inline-flex; align-items:center; gap:4px; margin:0;">
|
||||||
|
<input type="checkbox" ${includeNPCs ? "checked" : ""} onchange="window.GowlersTrackingDamageMeterToggleNPCs?.(this.checked)">
|
||||||
|
Include NPCs
|
||||||
|
</label>`;
|
||||||
|
const modeToggle = `
|
||||||
|
<div style="display:inline-flex; border:1px solid #b5b3a4; border-radius:12px; overflow:hidden; height:24px;">
|
||||||
|
<button type="button" onclick="window.GowlersTrackingDamageMeterSetMode('encounter')" style="border:none; background:${ledgerState.damageMeterMode === "encounter" ? "#d6d3c8" : "#f5f4ef"}; padding:2px 8px; cursor:pointer; font-size:11px;">Encounter</button>
|
||||||
|
<button type="button" onclick="window.GowlersTrackingDamageMeterSetMode('all')" style="border:none; border-left:1px solid #b5b3a4; background:${ledgerState.damageMeterMode === "all" ? "#d6d3c8" : "#f5f4ef"}; padding:2px 8px; cursor:pointer; font-size:11px;">All-Time</button>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
const rows = totals.length
|
const rows = totals.length
|
||||||
? totals
|
? totals
|
||||||
.map(
|
.map(
|
||||||
(t, idx) => {
|
(t, idx) => {
|
||||||
const max = totals[0]?.total || 1;
|
const max = totals[0]?.overall || totals[0]?.total || 1;
|
||||||
const pct = Math.max(1, Math.round((t.total / max) * 100));
|
const pct = Math.max(1, Math.round(((t.overall ?? t.total) / max) * 100));
|
||||||
const bar = renderDamageBar(t.composition, t.total) || `<div style="background:#e0e0e0; border-radius:4px; overflow:hidden; height:10px; margin-bottom:4px;"><div style="width:${pct}%; background:#ff9800; height:10px;"></div></div>`;
|
const innerBar =
|
||||||
|
renderDamageBar(t.composition, t.overall ?? t.total) ||
|
||||||
|
`<div style="background:#e0e0e0; border-radius:4px; overflow:hidden; height:10px;"><div style="width:${pct}%; background:#ff9800; height:10px;"></div></div>`;
|
||||||
|
const bar = `<div style="width:${pct}%; min-width:4px; max-width:100%;">${innerBar}</div>`;
|
||||||
const avatar = t.img ? `<img src="${t.img}" style="width:18px; height:18px; object-fit:cover; border-radius:3px; margin-right:6px;" />` : "";
|
const avatar = t.img ? `<img src="${t.img}" style="width:18px; height:18px; object-fit:cover; border-radius:3px; margin-right:6px;" />` : "";
|
||||||
return `
|
return `
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:4px 6px;">${idx + 1}</td>
|
<td style="padding:4px 6px;">${idx + 1}</td>
|
||||||
<td style="padding:4px 6px; display:flex; align-items:center;">${avatar}${t.name}</td>
|
<td style="padding:4px 6px; display:flex; align-items:center;">${avatar}${t.name}</td>
|
||||||
<td style="padding:4px 6px; text-align:right;">${t.total}</td>
|
<td style="padding:4px 6px; text-align:right;">${t.total}</td>
|
||||||
|
<td style="padding:4px 6px; text-align:right;">${t.healing}</td>
|
||||||
<td style="padding:4px 6px; text-align:right;">${t.hits}</td>
|
<td style="padding:4px 6px; text-align:right;">${t.hits}</td>
|
||||||
<td style="padding:4px 6px; max-width: 220px;">
|
<td style="padding:4px 6px; max-width: 220px;">
|
||||||
${bar}
|
${bar}
|
||||||
@@ -1789,26 +1886,23 @@ function buildDamageMeterContent() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.join("")
|
.join("")
|
||||||
: `<tr><td colspan="5" style="padding:6px; text-align:center; opacity:0.7;">No damage recorded${encounterId ? " for this encounter" : ""}.</td></tr>`;
|
: `<tr><td colspan="6" style="padding:6px; text-align:center; opacity:0.7;">No damage recorded${encounterId ? " for this encounter" : ""}.</td></tr>`;
|
||||||
|
|
||||||
const npcToggle = `<label style="font-size:12px; display:inline-flex; align-items:center; gap:4px; margin-left:6px;">
|
|
||||||
<input type="checkbox" ${includeNPCs ? "checked" : ""} onchange="window.GowlersTrackingDamageMeterToggleNPCs?.(this.checked)">
|
|
||||||
Include NPCs
|
|
||||||
</label>`;
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div style="padding:8px; font-size: 13px; line-height: 1.4;">
|
<div style="padding:8px; font-size: 13px; line-height: 1.4;">
|
||||||
<div style="font-weight:bold; margin-bottom:4px;">Damage Meter</div>
|
<div style="font-weight:bold; margin-bottom:4px;">Damage Meter</div>
|
||||||
<div style="margin-bottom:6px; display:flex; align-items:center; gap:12px;">
|
<div style="margin-bottom:6px; display:flex; align-items:center; gap:12px; flex-wrap:wrap;">
|
||||||
<span>${encounterLabel}</span>
|
<div style="display:flex; align-items:center; gap:6px;">${modeToggle}</div>
|
||||||
${npcToggle}
|
<div style="display:flex; align-items:center; gap:6px;">${npcToggle}</div>
|
||||||
|
<div style="display:flex; align-items:center; gap:6px;">${encounterLabel}</div>
|
||||||
</div>
|
</div>
|
||||||
<table style="width:100%; border-collapse:collapse; font-size:12px;">
|
<table style="width:100%; border-collapse:collapse; font-size:12px;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="text-align:left; padding:4px 6px;">#</th>
|
<th style="text-align:left; padding:4px 6px;">#</th>
|
||||||
<th style="text-align:left; padding:4px 6px;">Actor</th>
|
<th style="text-align:left; padding:4px 6px;">Actor</th>
|
||||||
<th style="text-align:right; padding:4px 6px;">Total</th>
|
<th style="text-align:right; padding:4px 6px;">Damage</th>
|
||||||
|
<th style="text-align:right; padding:4px 6px;">Healing</th>
|
||||||
<th style="text-align:right; padding:4px 6px;">Hits</th>
|
<th style="text-align:right; padding:4px 6px;">Hits</th>
|
||||||
<th style="text-align:left; padding:4px 6px;">Last Breakdown</th>
|
<th style="text-align:left; padding:4px 6px;">Last Breakdown</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1821,6 +1915,7 @@ function buildDamageMeterContent() {
|
|||||||
<td></td>
|
<td></td>
|
||||||
<td style="padding:4px 6px; font-weight:bold;">Total</td>
|
<td style="padding:4px 6px; font-weight:bold;">Total</td>
|
||||||
<td style="padding:4px 6px; text-align:right; font-weight:bold;">${grandTotal}</td>
|
<td style="padding:4px 6px; text-align:right; font-weight:bold;">${grandTotal}</td>
|
||||||
|
<td style="padding:4px 6px; text-align:right; font-weight:bold;">${grandHealing}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1853,6 +1948,7 @@ function createActorConfig(source = null) {
|
|||||||
if (source.version === SETTINGS_VERSION) {
|
if (source.version === SETTINGS_VERSION) {
|
||||||
config.tracking = { ...config.tracking, ...(source.tracking ?? {}) };
|
config.tracking = { ...config.tracking, ...(source.tracking ?? {}) };
|
||||||
config.chat = { ...config.chat, ...(source.chat ?? {}) };
|
config.chat = { ...config.chat, ...(source.chat ?? {}) };
|
||||||
|
config.chat.chatAll = source.chat?.chatAll ?? Object.values(source.chat ?? {}).some(Boolean);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1912,7 +2008,8 @@ function getActorTracking(actorId) {
|
|||||||
|
|
||||||
function getActorChat(actorId) {
|
function getActorChat(actorId) {
|
||||||
const entry = getActorConfig(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) {
|
async function ensureActorConfig(actor) {
|
||||||
@@ -1931,8 +2028,9 @@ async function setActorTracking(actorId, partial) {
|
|||||||
const entry = getActorConfig(actorId);
|
const entry = getActorConfig(actorId);
|
||||||
entry.tracking = {
|
entry.tracking = {
|
||||||
hp: partial.hp ?? entry.tracking.hp,
|
hp: partial.hp ?? entry.tracking.hp,
|
||||||
xp: partial.xp ?? entry.tracking.xp,
|
outgoing: partial.outgoing ?? entry.tracking.outgoing,
|
||||||
currency: partial.currency ?? entry.tracking.currency,
|
currency: partial.currency ?? entry.tracking.currency,
|
||||||
|
encounters: partial.encounters ?? entry.tracking.encounters,
|
||||||
};
|
};
|
||||||
await saveActorSettings(settings);
|
await saveActorSettings(settings);
|
||||||
const actor = game.actors.get(actorId);
|
const actor = game.actors.get(actorId);
|
||||||
@@ -1943,17 +2041,32 @@ async function setActorChat(actorId, partial) {
|
|||||||
if (!actorId) return;
|
if (!actorId) return;
|
||||||
const settings = getSettingsCache();
|
const settings = getSettingsCache();
|
||||||
const entry = getActorConfig(actorId);
|
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 = {
|
entry.chat = {
|
||||||
hp: partial.hp ?? entry.chat.hp,
|
hp: mergedChat.hp,
|
||||||
xp: partial.xp ?? entry.chat.xp,
|
outgoing: mergedChat.outgoing,
|
||||||
currency: partial.currency ?? entry.chat.currency,
|
xp: mergedChat.xp,
|
||||||
|
currency: mergedChat.currency,
|
||||||
|
encounters: mergedChat.encounters,
|
||||||
|
chatAll: mergedChat.chatAll,
|
||||||
};
|
};
|
||||||
await saveActorSettings(settings);
|
await saveActorSettings(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldSendChat(actorId, statId) {
|
function shouldSendChat(actorId, statId) {
|
||||||
const chat = getActorChat(actorId);
|
const chat = getActorChat(actorId);
|
||||||
return !!chat[statId];
|
return chat.chatAll ?? !!chat[statId];
|
||||||
}
|
}
|
||||||
|
|
||||||
class TrackingLedgerConfig extends FormApplication {
|
class TrackingLedgerConfig extends FormApplication {
|
||||||
@@ -2009,11 +2122,13 @@ class TrackingLedgerConfig extends FormApplication {
|
|||||||
const start = this._page * pageSize;
|
const start = this._page * pageSize;
|
||||||
const pageItems = filtered.slice(start, start + pageSize).map((ref) => {
|
const pageItems = filtered.slice(start, start + pageSize).map((ref) => {
|
||||||
const entry = settings[ref.id] ?? createActorConfig();
|
const entry = settings[ref.id] ?? createActorConfig();
|
||||||
|
const chatAll = entry.chat?.chatAll ?? false;
|
||||||
return {
|
return {
|
||||||
id: ref.id,
|
id: ref.id,
|
||||||
name: ref.name,
|
name: ref.name,
|
||||||
tracking: { ...entry.tracking },
|
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 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 {
|
return {
|
||||||
actors: pageItems,
|
actors: pageItems,
|
||||||
@@ -2048,6 +2169,12 @@ class TrackingLedgerConfig extends FormApplication {
|
|||||||
hasPrev,
|
hasPrev,
|
||||||
hasNext,
|
hasNext,
|
||||||
displayPage: totalActors ? this._page + 1 : 0,
|
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 entry = getActorConfig(actorId);
|
||||||
const nextTracking = {
|
const nextTracking = {
|
||||||
hp: checkboxValue(cfg.tracking?.hp, false),
|
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),
|
currency: checkboxValue(cfg.tracking?.currency, false),
|
||||||
|
encounters: checkboxValue(cfg.tracking?.encounters, false),
|
||||||
};
|
};
|
||||||
|
const chatAll = checkboxValue(cfg.chatAll, false);
|
||||||
const nextChat = {
|
const nextChat = {
|
||||||
hp: checkboxValue(cfg.chat?.hp, false),
|
hp: chatAll,
|
||||||
xp: checkboxValue(cfg.chat?.xp, false),
|
outgoing: chatAll,
|
||||||
currency: checkboxValue(cfg.chat?.currency, false),
|
xp: chatAll,
|
||||||
|
currency: chatAll,
|
||||||
|
encounters: chatAll,
|
||||||
|
chatAll: chatAll,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
entry.tracking.hp !== nextTracking.hp ||
|
entry.tracking.hp !== nextTracking.hp ||
|
||||||
|
entry.tracking.outgoing !== nextTracking.outgoing ||
|
||||||
entry.tracking.xp !== nextTracking.xp ||
|
entry.tracking.xp !== nextTracking.xp ||
|
||||||
entry.tracking.currency !== nextTracking.currency
|
entry.tracking.currency !== nextTracking.currency ||
|
||||||
|
entry.tracking.encounters !== nextTracking.encounters
|
||||||
) {
|
) {
|
||||||
entry.tracking = nextTracking;
|
entry.tracking = nextTracking;
|
||||||
dirty = true;
|
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;
|
entry.chat = nextChat;
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
@@ -2142,6 +2284,109 @@ class TrackingLedgerConfig extends FormApplication {
|
|||||||
TrackingLedgerConfig._lastPage = this._page;
|
TrackingLedgerConfig._lastPage = this._page;
|
||||||
this.render(false);
|
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: "<p>Remove all stored ledger history for all actors?</p>",
|
||||||
|
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: `<p>Remove all stored ledger history (incoming, outgoing, XP, currency, encounters) for <strong>${actor.name}</strong>?</p>`,
|
||||||
|
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) {
|
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
|
* Update encounter summary when combat starts
|
||||||
*/
|
*/
|
||||||
@@ -2291,10 +2574,22 @@ function sendChatNotification(statId, actor, previous, nextValue, entry) {
|
|||||||
const title = titles[statId] ?? statId.toUpperCase();
|
const title = titles[statId] ?? statId.toUpperCase();
|
||||||
const prevText = config.formatValue(previous);
|
const prevText = config.formatValue(previous);
|
||||||
const nextText = entry.value;
|
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 ? `<div><strong>Details:</strong> ${partsHtml || details}</div>` : "";
|
||||||
|
|
||||||
const content = `
|
const content = `
|
||||||
<strong>${title} Log</strong><br>
|
<div style="font-family: var(--font-primary); font-size: 12px; line-height: 1.4;">
|
||||||
${actor.name}: ${prevText} -> ${nextText} (${entry.diff})<br>
|
<div style="font-weight: bold; margin-bottom: 4px;">${title} Update</div>
|
||||||
<em>User:</em> ${entry.user ?? "System"}
|
<div><strong>Actor:</strong> ${actor.name}</div>
|
||||||
|
<div><strong>Value:</strong> ${prevText} → ${nextText} (${entry.diff})</div>
|
||||||
|
<div><strong>Source:</strong> ${source}</div>
|
||||||
|
<div><strong>Encounter:</strong> ${encounter}</div>
|
||||||
|
${detailHtml}
|
||||||
|
<div style="opacity: 0.8;"><strong>User:</strong> ${entry.user ?? "System"}</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
ChatMessage.create({
|
ChatMessage.create({
|
||||||
|
|||||||
@@ -34,21 +34,29 @@
|
|||||||
<table class="tracking-ledger-table">
|
<table class="tracking-ledger-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th rowspan="2">Actor</th>
|
<th>Actor</th>
|
||||||
<th colspan="2">HP</th>
|
<th>Incoming (Track)</th>
|
||||||
<th colspan="2">XP</th>
|
<th>Outgoing (Track)</th>
|
||||||
<th colspan="2">Currency</th>
|
<th>Currency (Track)</th>
|
||||||
</tr>
|
<th>Encounters (Track)</th>
|
||||||
<tr>
|
<th>Chat (All)</th>
|
||||||
<th>Track</th>
|
<th>History</th>
|
||||||
<th>Chat</th>
|
|
||||||
<th>Track</th>
|
|
||||||
<th>Chat</th>
|
|
||||||
<th>Track</th>
|
|
||||||
<th>Chat</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<tr class="bulk-row" style="background:#f0f0f0;">
|
||||||
|
<td><strong>All actors</strong></td>
|
||||||
|
<td style="text-align:center;"><input type="checkbox" data-action="toggle-all" data-target="tracking.hp" {{#if allIncoming}}checked{{/if}}></td>
|
||||||
|
<td style="text-align:center;"><input type="checkbox" data-action="toggle-all" data-target="tracking.outgoing" {{#if allOutgoing}}checked{{/if}}></td>
|
||||||
|
<td style="text-align:center;"><input type="checkbox" data-action="toggle-all" data-target="tracking.currency" {{#if allCurrency}}checked{{/if}}></td>
|
||||||
|
<td style="text-align:center;"><input type="checkbox" data-action="toggle-all" data-target="tracking.encounters" {{#if allEncounters}}checked{{/if}}></td>
|
||||||
|
<td style="text-align:center;"><input type="checkbox" data-action="toggle-all" data-target="chat.chatAll" {{#if allChatAll}}checked{{/if}}></td>
|
||||||
|
<td style="text-align:center;">
|
||||||
|
<button type="button" class="clear-history-btn" data-action="clear-all-history" style="padding:4px 8px; display:inline-flex; align-items:center; gap:6px;">
|
||||||
|
<i class="fas fa-trash"></i><span>Clear All</span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{{#if actors.length}}
|
{{#if actors.length}}
|
||||||
{{#each actors}}
|
{{#each actors}}
|
||||||
<tr>
|
<tr>
|
||||||
@@ -60,30 +68,33 @@
|
|||||||
<input type="checkbox" name="actors.{{id}}.tracking.hp" {{#if tracking.hp}}checked{{/if}}>
|
<input type="checkbox" name="actors.{{id}}.tracking.hp" {{#if tracking.hp}}checked{{/if}}>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" name="actors.{{id}}.chat.hp" {{#if chat.hp}}checked{{/if}}>
|
<input type="checkbox" name="actors.{{id}}.tracking.outgoing" {{#if tracking.outgoing}}checked{{/if}}>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="checkbox" name="actors.{{id}}.tracking.xp" {{#if tracking.xp}}checked{{/if}}>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="checkbox" name="actors.{{id}}.chat.xp" {{#if chat.xp}}checked{{/if}}>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" name="actors.{{id}}.tracking.currency" {{#if tracking.currency}}checked{{/if}}>
|
<input type="checkbox" name="actors.{{id}}.tracking.currency" {{#if tracking.currency}}checked{{/if}}>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" name="actors.{{id}}.chat.currency" {{#if chat.currency}}checked{{/if}}>
|
<input type="checkbox" name="actors.{{id}}.tracking.encounters" {{#if tracking.encounters}}checked{{/if}}>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="actors.{{id}}.chatAll" {{#if chatAll}}checked{{/if}}>
|
||||||
|
</td>
|
||||||
|
<td style="text-align:center;">
|
||||||
|
<button type="button" class="clear-history-btn" data-action="clear-history" data-actor-id="{{id}}" style="padding:4px 8px; display:inline-flex; align-items:center; gap:6px;">
|
||||||
|
<i class="fas fa-trash"></i><span>Clear</span>
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" class="empty">No actors match the current filter.</td>
|
<td colspan="11" class="empty">No actors match the current filter.</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<footer>
|
<footer>
|
||||||
<button type="submit"><i class="fas fa-save"></i> Save</button>
|
<button type="submit"><i class="fas fa-save"></i> Save</button>
|
||||||
|
<div style="flex:1; text-align:center; font-size:11px; opacity:0.7; margin-top:6px;">v{{moduleVersion}}</div>
|
||||||
</footer>
|
</footer>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user