Release gowlers-tracking-ledger v1.0.0

This commit is contained in:
centron\schwoerer
2025-11-21 20:12:25 +01:00
parent 351eb37a2c
commit f801bf5a6d

View File

@@ -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;
@@ -865,36 +865,18 @@ function buildXpBreakdownTooltip(actor, xpEntry) {
// 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 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 players = resolved.filter((p) => p.isPlayer);
const monsters = resolved.filter((p) => !p.isPlayer);
const playerNames = (players.length ? players : resolved).map((p) => p.name).filter(Boolean);
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 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);
const bonusXp = Math.max(0, xpEntry.diff - perActorMonsterXp);
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 (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()}`);
// If raw XP award data exists (from distributor/combat flags), surface it for debugging
if (encounter.xpAwardsRaw) {
@@ -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];