Release gowlers-tracking-ledger v1.0.0
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
const MODULE_ID = "gowlers-tracking-ledger";
|
const MODULE_ID = "gowlers-tracking-ledger";
|
||||||
const MODULE_VERSION = "0.1.27";
|
const MODULE_VERSION = "1.0.0";
|
||||||
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;
|
||||||
@@ -865,36 +865,18 @@ function buildXpBreakdownTooltip(actor, xpEntry) {
|
|||||||
// Build breakdown from encounter participants with per-NPC XP pulled from actor docs
|
// Build breakdown from encounter participants with per-NPC XP pulled from actor docs
|
||||||
let breakdown = [];
|
let breakdown = [];
|
||||||
|
|
||||||
const participants = encounter.participantDetails && encounter.participantDetails.length
|
const { monsters, players, playerCount, monsterTotalXp, perActorMonsterXp } = computeEncounterXp(encounter);
|
||||||
? encounter.participantDetails
|
const playerNames = (players.length ? players : monsters).map((p) => p.name ?? resolveParticipantName(p.id)).filter(Boolean);
|
||||||
: (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 monsterLines = monsters.map((m) => `${m.name ?? "Unknown"}${m.npcXp ? ` (${m.npcXp.toLocaleString()} XP)` : ""}`);
|
const monsterLines = monsters.map((m) => `${m.name ?? "Unknown"}${m.npcXp ? ` (${m.npcXp.toLocaleString()} XP)` : ""}`);
|
||||||
|
|
||||||
const playerCount = playerNames.length || 1;
|
const bonusXp = Math.max(0, xpEntry.diff - perActorMonsterXp);
|
||||||
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)`);
|
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 (playerNames.length) breakdown.push(`Players: ${playerNames.join(", ")}`);
|
||||||
if (monsterLines.length) breakdown.push(`Monsters: ${monsterLines.join("; ")}`);
|
if (monsterLines.length) breakdown.push(`Monsters: ${monsterLines.join("; ")}`);
|
||||||
if (monsterLines.length && monsterXpTotal) breakdown.push(`Monster XP total: ${monsterXpTotal.toLocaleString()}`);
|
if (monsterLines.length && monsterTotalXp) breakdown.push(`Monster XP total: ${monsterTotalXp.toLocaleString()}`);
|
||||||
if (bonusXp > 0) breakdown.push(`Bonus XP: ${bonusXp.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 raw XP award data exists (from distributor/combat flags), surface it for debugging
|
||||||
if (encounter.xpAwardsRaw) {
|
if (encounter.xpAwardsRaw) {
|
||||||
@@ -979,6 +961,15 @@ function buildHistoryContent(actor, tabArg) {
|
|||||||
{ label: "Status", render: (entry) => entry.status ?? "unknown" },
|
{ label: "Status", render: (entry) => entry.status ?? "unknown" },
|
||||||
{ label: "Rounds", render: (entry) => entry.rounds || 0 },
|
{ label: "Rounds", render: (entry) => entry.rounds || 0 },
|
||||||
{ label: "Participants", render: (entry) => entry.participants ? entry.participants.length : 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";
|
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) {
|
function checkboxValue(value, fallback = false) {
|
||||||
if (value === undefined || value === null) return fallback;
|
if (value === undefined || value === null) return fallback;
|
||||||
if (Array.isArray(value)) value = value[value.length - 1];
|
if (Array.isArray(value)) value = value[value.length - 1];
|
||||||
|
|||||||
Reference in New Issue
Block a user