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 1270046f..1ad6f155 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 = "0.1.27"; +const MODULE_VERSION = "1.0.0"; const TRACK_SETTING = "actorSettings"; const FLAG_SCOPE = "world"; const MAX_HISTORY_ROWS = 100; @@ -856,48 +856,30 @@ function buildXpBreakdownTooltip(actor, xpEntry) { try { const encounters = actor.getFlag(FLAG_SCOPE, ENCOUNTER_FLAG) ?? []; - const encounter = encounters.find(e => e.encounterID === xpEntry.encounterId); + const encounter = encounters.find(e => e.encounterID === xpEntry.encounterId); - if (!encounter) { - return `${xpEntry.diff} XP from encounter`; - } + if (!encounter) { + return `${xpEntry.diff} XP from encounter`; + } - // Build breakdown from encounter participants with per-NPC XP pulled from actor docs - let breakdown = []; + // Build breakdown from encounter participants with per-NPC XP pulled from actor docs + let breakdown = []; - const participants = encounter.participantDetails && encounter.participantDetails.length - ? encounter.participantDetails - : (encounter.participants ?? []).map((id) => ({ id, name: resolveParticipantName(id), isPlayer: false })); + const { monsters, players, playerCount, monsterTotalXp, perActorMonsterXp } = computeEncounterXp(encounter); + const playerNames = (players.length ? players : monsters).map((p) => p.name ?? resolveParticipantName(p.id)).filter(Boolean); + const monsterLines = monsters.map((m) => `${m.name ?? "Unknown"}${m.npcXp ? ` (${m.npcXp.toLocaleString()} XP)` : ""}`); - const resolved = participants.map((p) => { - const actorDoc = p.id ? game.actors.get(p.id) : null; - const name = p.name ?? actorDoc?.name ?? resolveParticipantName(p.id); - const isPlayer = p.isPlayer || actorDoc?.type === "character"; - const npcXp = !isPlayer ? Number(actorDoc?.system?.details?.xp?.value ?? 0) : 0; - return { ...p, name, isPlayer, npcXp }; - }); + const bonusXp = Math.max(0, xpEntry.diff - perActorMonsterXp); - const players = resolved.filter((p) => p.isPlayer); - const monsters = resolved.filter((p) => !p.isPlayer); + breakdown.push(`${xpEntry.diff} XP (per actor)`); + if (playerNames.length) breakdown.push(`Players: ${playerNames.join(", ")}`); + if (monsterLines.length) breakdown.push(`Monsters: ${monsterLines.join("; ")}`); + if (monsterLines.length && monsterTotalXp) breakdown.push(`Monster XP total: ${monsterTotalXp.toLocaleString()}`); + if (perActorMonsterXp) breakdown.push(`Monsters (per actor): ${Math.round(perActorMonsterXp).toLocaleString()}`); + if (bonusXp > 0) breakdown.push(`Bonus XP: ${Math.round(bonusXp).toLocaleString()}`); - const playerNames = (players.length ? players : resolved).map((p) => p.name).filter(Boolean); - const monsterLines = monsters.map((m) => `${m.name ?? "Unknown"}${m.npcXp ? ` (${m.npcXp.toLocaleString()} XP)` : ""}`); - - const playerCount = playerNames.length || 1; - const totalEncounterXp = Math.max(0, xpEntry.diff * playerCount); - - const monsterXpTotal = monsters.reduce((sum, m) => sum + (Number.isFinite(m.npcXp) ? m.npcXp : 0), 0); - const bonusXp = Math.max(0, totalEncounterXp - monsterXpTotal); - - breakdown.push(`${xpEntry.diff} XP (per actor)`); - if (totalEncounterXp) breakdown.push(`${totalEncounterXp.toLocaleString()} XP encounter total`); - if (playerNames.length) breakdown.push(`Players: ${playerNames.join(", ")}`); - if (monsterLines.length) breakdown.push(`Monsters: ${monsterLines.join("; ")}`); - if (monsterLines.length && monsterXpTotal) breakdown.push(`Monster XP total: ${monsterXpTotal.toLocaleString()}`); - if (bonusXp > 0) breakdown.push(`Bonus XP: ${bonusXp.toLocaleString()}`); - - // If raw XP award data exists (from distributor/combat flags), surface it for debugging - if (encounter.xpAwardsRaw) { + // If raw XP award data exists (from distributor/combat flags), surface it for debugging + if (encounter.xpAwardsRaw) { const keys = Object.keys(encounter.xpAwardsRaw); breakdown.push(`XP awards data keys: ${keys.join(", ")}`); if (encounter.xpAwardsRaw.pf1) { @@ -979,6 +961,15 @@ function buildHistoryContent(actor, tabArg) { { label: "Status", render: (entry) => entry.status ?? "unknown" }, { label: "Rounds", render: (entry) => entry.rounds || 0 }, { label: "Participants", render: (entry) => entry.participants ? entry.participants.length : 0 }, + { + label: "XP", + render: (entry) => { + const xpInfo = computeEncounterXp(entry); + const total = xpInfo.monsterTotalXp ? xpInfo.monsterTotalXp.toLocaleString() : "0"; + return total; + }, + getTitle: (entry) => buildEncounterXpTooltip(entry), + }, ], }, ]; @@ -1447,6 +1438,43 @@ function resolveParticipantName(participant) { return "Unknown"; } +function computeEncounterXp(encounter) { + const participants = encounter?.participantDetails ?? []; + const resolved = participants.map((p) => { + const actorDoc = p.id ? game.actors.get(p.id) : null; + const name = p.name ?? actorDoc?.name ?? resolveParticipantName(p.id); + const isPlayer = p.isPlayer || actorDoc?.type === "character"; + const npcXp = !isPlayer ? Number(actorDoc?.system?.details?.xp?.value ?? 0) : 0; + return { ...p, name, isPlayer, npcXp }; + }); + + const monsters = resolved.filter((p) => !p.isPlayer); + const players = resolved.filter((p) => p.isPlayer); + const playerCount = Math.max(players.length || resolved.length || 1, 1); + + const monsterTotalXp = monsters.reduce((sum, m) => sum + (Number.isFinite(m.npcXp) ? m.npcXp : 0), 0); + const perActorMonsterXp = monsterTotalXp / playerCount; + + return { monsters, monsterTotalXp, players, playerCount, perActorMonsterXp }; +} + +function buildEncounterXpTooltip(encounter) { + try { + const { monsters, monsterTotalXp, players, playerCount } = computeEncounterXp(encounter); + const playerNames = players.map((p) => p.name ?? resolveParticipantName(p.id)).filter(Boolean); + const monsterLines = monsters.map((m) => `${m.name ?? "Unknown"}${m.npcXp ? ` (${m.npcXp.toLocaleString()} XP)` : ""}`); + const lines = []; + if (monsterTotalXp) lines.push(`Total XP: ${monsterTotalXp.toLocaleString()}`); + if (monsterLines.length) lines.push(`Monsters: ${monsterLines.join("; ")}`); + if (playerNames.length) lines.push(`Players: ${playerNames.join(", ")}`); + lines.push(`Players counted: ${playerCount}`); + return lines.join("\n"); + } catch (e) { + console.warn("[GowlersTracking] Failed to build encounter XP tooltip:", e); + return ""; + } +} + function checkboxValue(value, fallback = false) { if (value === undefined || value === null) return fallback; if (Array.isArray(value)) value = value[value.length - 1];