deletion works

This commit is contained in:
centron\schwoerer
2025-11-25 09:09:43 +01:00
parent ec3e65c0f0
commit a48f058583

View File

@@ -15,6 +15,33 @@ const SETTINGS_VERSION = 2;
const COIN_ORDER = ["pp", "gp", "sp", "cp"];
const ENCOUNTER_FLAG = "pf1EncounterHistory";
const DAMAGE_DEALT_FLAG = "pf1DamageDealtHistory";
const DEBUG_LOGS = false;
const HISTORY_PAGE_OPTIONS = [10, 20, 50, "all"];
const DAMAGE_ICON_MAP = {
slashing: { icon: "ra ra-sword", color: "#e3c000" },
piercing: { icon: "ra ra-spear-head", color: "#2c7be5" },
bludgeoning: { icon: "ra ra-large-hammer", color: "#e03131" },
fire: { icon: "ra ra-fire", color: "#f76707" },
cold: { icon: "ra ra-snowflake", color: "#3bc9db" },
electricity: { icon: "ra ra-lightning-bolt", color: "#f0c419" },
acid: { icon: "ra ra-round-bottom-flask", color: "#2f9e44" },
sonic: { icon: "ra ra-megaphone", color: "#22b8cf" },
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" },
};
const log = {
debug: (...args) => DEBUG_LOGS && console.debug("[GowlersTracking]", ...args),
info: (...args) => console.info("[GowlersTracking]", ...args),
warn: (...args) => console.warn("[GowlersTracking]", ...args),
error: (...args) => console.error("[GowlersTracking]", ...args),
};
const STAT_CONFIGS = {
hp: {
@@ -116,12 +143,12 @@ function setActiveHistoryTab(actorId, tabId) {
// Global tab switching function for history dialog
window.switchHistoryTab = function(tabId, el = null) {
console.log("[GowlersTracking] Tab switched to:", tabId);
log.debug("Tab switched to:", tabId);
// Find the root element using the data-history-root attribute
const root = el?.closest?.('[data-history-root]') ?? document.querySelector('[data-history-root]');
if (!root) {
console.warn("[GowlersTracking] History root element not found");
log.warn("History root element not found");
return;
}
@@ -139,16 +166,16 @@ window.switchHistoryTab = function(tabId, el = null) {
if (activeButton) {
activeButton.classList.add('active');
console.log("[GowlersTracking] Tab button activated:", tabId);
log.debug("Tab button activated:", tabId);
} else {
console.warn("[GowlersTracking] Tab button not found for:", tabId);
log.warn("Tab button not found for:", tabId);
}
if (activePanel) {
activePanel.style.display = 'block';
console.log("[GowlersTracking] Tab panel displayed:", tabId);
log.debug("Tab panel displayed:", tabId);
} else {
console.warn("[GowlersTracking] Tab panel not found for:", tabId);
log.warn("Tab panel not found for:", tabId);
}
const actorId = root?.getAttribute('data-history-root');
@@ -500,16 +527,15 @@ async function initializeModule() {
}
}
// Log detailed inspection for debugging
console.log("[GowlersTracking] Message inspection for damage details:");
console.log("[GowlersTracking] - pf1Flags keys:", Object.keys(pf1Flags));
console.log("[GowlersTracking] - Full metadata:", JSON.stringify(metadata, null, 2));
console.log("[GowlersTracking] - message.rolls:", message.rolls?.length > 0 ? "Present" : "None");
console.log("[GowlersTracking] - message.content length:", message.content?.length ?? 0);
// Log HTML content snippet for analysis
if (DEBUG_LOGS) {
log.debug("Message inspection for damage details:");
log.debug(" - pf1Flags keys:", Object.keys(pf1Flags));
log.debug(" - Full metadata:", JSON.stringify(metadata, null, 2));
log.debug(" - message.rolls:", message.rolls?.length > 0 ? "Present" : "None");
log.debug(" - message.content length:", message.content?.length ?? 0);
if (message.content) {
console.log("[GowlersTracking] - HTML preview (first 1000 chars):", message.content.substring(0, 1000));
log.debug(" - HTML preview (first 1000 chars):", message.content.substring(0, 1000));
}
}
return {
@@ -982,7 +1008,7 @@ function buildXpBreakdownTooltip(actor, xpEntry) {
function buildHistoryContent(actor, tabArg) {
const initialTab = tabArg ?? getActiveHistoryTab(actor.id, "hp"); // Explicitly capture the tab parameter
console.log("[GowlersTracking] buildHistoryContent called with initialTab:", initialTab);
log.debug("buildHistoryContent called with initialTab:", initialTab);
setActiveHistoryTab(actor.id, initialTab);
const canConfigure = game.user?.isGM;
const configs = [
@@ -994,7 +1020,7 @@ function buildHistoryContent(actor, tabArg) {
{ label: "Timestamp", render: (entry) => formatDate(entry.timestamp) },
{ label: "HP", render: (entry) => entry.value },
{
label: "Δ",
label: "Delta",
render: (entry) => entry.diff,
getTitle: (entry) => entry.damageBreakdown ? `${entry.damageBreakdown}` : ""
},
@@ -1100,15 +1126,15 @@ function buildHistoryContent(actor, tabArg) {
currentPage = totalPages;
setHistoryPageState(actor.id, cfg.id, currentPage, rowsPerPage);
}
const selectOptions = [10, 20, 50, "all"]
const selectOptions = HISTORY_PAGE_OPTIONS
.map((value) => `<option value="${value}" ${String(value) === String(rowsPerPage) ? "selected" : ""}>${value === "all" ? "All" : `${value} rows`}</option>`)
.join("");
return `<div class="history-panel tab ${isActive}" data-history-panel="${cfg.id}" data-page="${currentPage}" data-page-size="${rowsPerPage}" style="display:${display}">
${renderHistoryTable(entries, cfg.columns, cfg.id, rowsPerPage, currentPage)}
<div class="history-panel-pagination" style="display: flex; gap: 8px; align-items: center; justify-content: center; margin-top: 12px; padding-top: 8px; border-top: 1px solid #e0e0e0;">
<button type="button" data-action="history-page" data-direction="prev" onclick="window.historyPageNav(this, 'prev')" style="padding: 4px 8px; cursor: pointer;" ${totalPages <= 1 ? 'disabled' : ''}>« Prev</button>
<button type="button" data-action="history-page" data-direction="prev" onclick="window.historyPageNav(this, 'prev')" style="padding: 4px 8px; cursor: pointer;" ${totalPages <= 1 ? 'disabled' : ''}>< Prev</button>
<span data-page-info style="font-size: 0.85em; min-width: 100px; text-align: center;">Page ${currentPage} / ${Math.max(1, totalPages)}</span>
<button type="button" data-action="history-page" data-direction="next" onclick="window.historyPageNav(this, 'next')" style="padding: 4px 8px; cursor: pointer;" ${totalPages <= 1 ? 'disabled' : ''}>Next »</button>
<button type="button" data-action="history-page" data-direction="next" onclick="window.historyPageNav(this, 'next')" style="padding: 4px 8px; cursor: pointer;" ${totalPages <= 1 ? 'disabled' : ''}>Next ></button>
<select data-history-page-size onchange="window.historyChangePageSize(this)" style="padding: 2px 4px;">
${selectOptions}
</select>
@@ -1185,23 +1211,23 @@ async function recordHistoryEntry(actor, statId, previous, nextValue, userId, op
const diffValue = config.diff(previous, nextValue);
// COMPREHENSIVE DEBUG LOGGING FOR DAMAGE REPORTING
console.log("[GowlersTracking] ===== recordHistoryEntry DEBUG =====");
console.log("[GowlersTracking] statId:", statId);
console.log("[GowlersTracking] Previous:", previous, "Next:", nextValue, "Diff:", diffValue);
console.log("[GowlersTracking] Actor:", actor.name, "(" + actor.id + ")");
console.log("[GowlersTracking] userId:", userId);
console.log("[GowlersTracking] Full options object:", options);
console.log("[GowlersTracking] Full change object:", change);
if (DEBUG_LOGS) {
log.debug("===== recordHistoryEntry DEBUG =====");
log.debug("statId:", statId);
log.debug("Previous:", previous, "Next:", nextValue, "Diff:", diffValue);
log.debug("Actor:", actor.name, "(" + actor.id + ")");
log.debug("userId:", userId);
log.debug("Full options object:", options);
log.debug("Full change object:", change);
// Log all keys in options for inspection
if (Object.keys(options).length > 0) {
console.log("[GowlersTracking] Options keys:", Object.keys(options));
log.debug("Options keys:", Object.keys(options));
for (const key of Object.keys(options)) {
console.log(`[GowlersTracking] options.${key}:`, options[key]);
log.debug(`options.${key}:`, options[key]);
}
}
console.log("[GowlersTracking] ===== end debug =====");
log.debug("===== end debug =====");
}
// Determine encounter ID: use active combat, or if none, check if combat just ended
let encounterId = game.combat?.id ?? null;
@@ -1239,28 +1265,29 @@ async function recordHistoryEntry(actor, statId, previous, nextValue, userId, op
else if (options?.encounter?.id) encounterId = options.encounter.id;
else if (options?.encounter?._id) encounterId = options.encounter._id;
// XP debug logging for data inspection
if (DEBUG_LOGS) {
try {
console.log("[GowlersTracking][XP DEBUG] -----");
console.log("[GowlersTracking][XP DEBUG] encounterId:", encounterId, "manualXp:", manualXp, "hasExplicitEncounter:", hasExplicitEncounter);
console.log("[GowlersTracking][XP DEBUG] options keys:", Object.keys(options || {}));
console.log("[GowlersTracking][XP DEBUG] change keys:", Object.keys(change || {}));
if (options) console.log("[GowlersTracking][XP DEBUG] options JSON:", JSON.stringify(options, null, 2));
if (change) console.log("[GowlersTracking][XP DEBUG] change JSON keys:", Object.keys(change));
if (change?.system?.details?.xp) console.log("[GowlersTracking][XP DEBUG] change.system.details.xp:", change.system.details.xp);
if (change?.system?.attributes?.hp) console.log("[GowlersTracking][XP DEBUG] change.system.attributes.hp (truncated):", JSON.stringify(change.system.attributes.hp, null, 2).substring(0, 500));
log.debug("[XP DEBUG] -----");
log.debug("[XP DEBUG] encounterId:", encounterId, "manualXp:", manualXp, "hasExplicitEncounter:", hasExplicitEncounter);
log.debug("[XP DEBUG] options keys:", Object.keys(options || {}));
log.debug("[XP DEBUG] change keys:", Object.keys(change || {}));
if (options) log.debug("[XP DEBUG] options JSON:", JSON.stringify(options, null, 2));
if (change) log.debug("[XP DEBUG] change JSON keys:", Object.keys(change));
if (change?.system?.details?.xp) log.debug("[XP DEBUG] change.system.details.xp:", change.system.details.xp);
if (change?.system?.attributes?.hp) log.debug("[XP DEBUG] change.system.attributes.hp (truncated):", JSON.stringify(change.system.attributes.hp, null, 2).substring(0, 500));
const pf1Flags = actor.getFlag("pf1") ?? {};
if (pf1Flags && Object.keys(pf1Flags).length) console.log("[GowlersTracking][XP DEBUG] actor pf1 flags keys:", Object.keys(pf1Flags));
if (game.combat) console.log("[GowlersTracking][XP DEBUG] active combat flags:", game.combat.flags ?? "(none)");
if (pf1Flags && Object.keys(pf1Flags).length) log.debug("[XP DEBUG] actor pf1 flags keys:", Object.keys(pf1Flags));
if (game.combat) log.debug("[XP DEBUG] active combat flags:", game.combat.flags ?? "(none)");
const encounterFlag = actor.getFlag(FLAG_SCOPE, ENCOUNTER_FLAG) ?? [];
console.log("[GowlersTracking][XP DEBUG] actor encounter flag count:", encounterFlag.length);
log.debug("[XP DEBUG] actor encounter flag count:", encounterFlag.length);
if (encounterFlag.length && encounterId) {
const encounterEntry = encounterFlag.find((e) => e.encounterID === encounterId);
console.log("[GowlersTracking][XP DEBUG] matched encounter entry:", encounterEntry ?? "(not found)");
log.debug("[XP DEBUG] matched encounter entry:", encounterEntry ?? "(not found)");
}
console.log("[GowlersTracking][XP DEBUG] -----");
log.debug("[XP DEBUG] -----");
} catch (e) {
console.warn("[GowlersTracking][XP DEBUG] logging failed:", e);
log.warn("[XP DEBUG] logging failed:", e);
}
}
}
@@ -1584,30 +1611,20 @@ function isNonlethalType(type) {
return String(type ?? "").toLowerCase() === "nonlethal";
}
function getPrimaryDamageType(part) {
return (part?.types && part.types[0]) || part?.customTypes?.[0] || part?.materials?.[0] || "untyped";
}
function getDamageIconMeta(type) {
return DAMAGE_ICON_MAP[type?.toLowerCase?.()] ?? DAMAGE_ICON_MAP.untyped;
}
function formatDamagePartsWithIcons(parts) {
if (!Array.isArray(parts) || !parts.length) return "";
const iconMap = {
slashing: { icon: "ra ra-sword", color: "#e3c000" },
piercing: { icon: "ra ra-spear-head", color: "#2c7be5" },
bludgeoning: { icon: "ra ra-large-hammer", color: "#e03131" },
fire: { icon: "ra ra-fire", color: "#f76707" },
cold: { icon: "ra ra-snowflake", color: "#3bc9db" },
electricity: { icon: "ra ra-lightning-bolt", color: "#f0c419" },
acid: { icon: "ra ra-round-bottom-flask", color: "#2f9e44" },
sonic: { icon: "ra ra-megaphone", color: "#22b8cf" },
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" },
};
return parts
.map((p) => {
const baseType = (p.types && p.types[0]) || p.customTypes?.[0] || p.materials?.[0] || "untyped";
const mapEntry = iconMap[baseType?.toLowerCase?.()] ?? iconMap.untyped;
const icon = `<i class="${mapEntry.icon}" style="color:${mapEntry.color};"></i>`;
const meta = getDamageIconMeta(getPrimaryDamageType(p));
const icon = `<i class="${meta.icon}" style="color:${meta.color};"></i>`;
const amt = Number.isFinite(p.total) ? p.total : p.formula ?? "?";
return `<span style="display:inline-flex; align-items:center; gap:4px; margin-right:6px;">${icon}<span>${amt}</span></span>`;
})
@@ -1615,27 +1632,10 @@ function formatDamagePartsWithIcons(parts) {
}
function renderDamageBar(composition = [], total = 0) {
const iconMap = {
slashing: { icon: "ra ra-sword", color: "#e3c000" },
piercing: { icon: "ra ra-spear-head", color: "#2c7be5" },
bludgeoning: { icon: "ra ra-large-hammer", color: "#e03131" },
fire: { icon: "ra ra-fire", color: "#f76707" },
cold: { icon: "ra ra-snowflake", color: "#3bc9db" },
electricity: { icon: "ra ra-lightning-bolt", color: "#f0c419" },
acid: { icon: "ra ra-round-bottom-flask", color: "#2f9e44" },
sonic: { icon: "ra ra-megaphone", color: "#22b8cf" },
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" },
};
if (!total || !composition.length) return "";
const segments = composition.map((c) => {
const pct = Math.max(2, Math.round((c.value / total) * 100));
const entry = iconMap[c.type?.toLowerCase?.()] ?? iconMap.untyped;
const entry = getDamageIconMeta(c.type);
return `<div style="width:${pct}%; background:${entry.color}; height:10px; display:flex; align-items:center; justify-content:center;">
<i class="${entry.icon}" style="color:#fff; font-size:10px;"></i>
</div>`;
@@ -2353,16 +2353,7 @@ class TrackingLedgerConfig extends FormApplication {
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.");
}
await clearAllHistoriesWithProgress();
});
html.find("[data-action=\"clear-history\"]").on("click", async (event) => {
@@ -2417,6 +2408,71 @@ async function clearDocumentHistory(doc) {
}
}
async function clearAllHistoriesWithProgress() {
try {
const targets = new Map();
// Core actors in the directory
for (const actor of collectAllActorDocuments()) {
const key = `actor:${actor.uuid ?? actor.id}`;
if (!targets.has(key)) targets.set(key, { doc: actor, label: actor.name ?? actor.id });
}
// Tokens on scenes (covers unlinked NPCs) and their embedded actor documents
for (const token of collectAllTokens()) {
const tokenKey = `token:${token.uuid ?? token.id}:${token.parent?.uuid ?? "scene"}`;
if (!targets.has(tokenKey)) targets.set(tokenKey, { doc: token, label: token.name ?? token.id });
const tokenActor = token.actor;
if (tokenActor) {
const actorKey = `actor:${tokenActor.uuid ?? tokenActor.id}`;
if (!targets.has(actorKey)) targets.set(actorKey, { doc: tokenActor, label: tokenActor.name ?? tokenActor.id });
}
}
const entries = Array.from(targets.values());
if (!entries.length) {
ui.notifications?.info?.("No actor or token histories found to clear.");
return;
}
// Simple progress dialog
let progressEl = null;
let statusEl = null;
const dialog = new Dialog({
title: "Clearing Histories",
content: `
<div style="display:flex; flex-direction:column; gap:8px; padding:4px;">
<progress value="0" max="${entries.length}" style="width:100%;"></progress>
<div data-status style="font-size:12px;">Starting...</div>
</div>
`,
buttons: {},
close: () => {},
render: (html) => {
progressEl = html[0].querySelector("progress");
statusEl = html[0].querySelector("[data-status]");
},
});
dialog.render(true);
let completed = 0;
for (const { doc, label } of entries) {
if (statusEl) statusEl.textContent = `Clearing: ${label}`;
await clearDocumentHistory(doc);
completed += 1;
if (progressEl) progressEl.value = completed;
}
if (statusEl) statusEl.textContent = "Done.";
setTimeout(() => dialog.close(), 500);
ui.notifications?.info?.("History cleared for all actors and tokens.");
} catch (err) {
console.error("[GowlersTracking] Failed to clear all histories", err);
ui.notifications?.error?.("Failed to clear all histories; see console.");
}
}
function collectAllActorDocuments() {
const actors = new Map();
for (const a of game.actors.contents ?? []) {
@@ -2584,7 +2640,7 @@ function sendChatNotification(statId, actor, previous, nextValue, entry) {
<div style="font-family: var(--font-primary); font-size: 12px; line-height: 1.4;">
<div style="font-weight: bold; margin-bottom: 4px;">${title} Update</div>
<div><strong>Actor:</strong> ${actor.name}</div>
<div><strong>Value:</strong> ${prevText} ${nextText} (${entry.diff})</div>
<div><strong>Value:</strong> ${prevText} -> ${nextText} (${entry.diff})</div>
<div><strong>Source:</strong> ${source}</div>
<div><strong>Encounter:</strong> ${encounter}</div>
${detailHtml}