XP breakdown working

This commit is contained in:
centron\schwoerer
2025-11-21 17:19:05 +01:00
parent f59f14f6db
commit 351eb37a2c

View File

@@ -1,6 +1,6 @@
const MODULE_ID = "gowlers-tracking-ledger"; const MODULE_ID = "gowlers-tracking-ledger";
const MODULE_VERSION = "0.1.25"; const MODULE_VERSION = "0.1.27";
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;
@@ -862,17 +862,48 @@ function buildXpBreakdownTooltip(actor, xpEntry) {
return `${xpEntry.diff} XP from encounter`; return `${xpEntry.diff} XP from encounter`;
} }
// Build breakdown from encounter participants // Build breakdown from encounter participants with per-NPC XP pulled from actor docs
let breakdown = []; let breakdown = [];
if (encounter.participants && encounter.participants.length > 0) {
// Resolve participant UUIDs/IDs to actor names
const participantNames = encounter.participants.slice(0, 2).map(resolveParticipantName);
const participantsStr = participantNames.join(", "); const participants = encounter.participantDetails && encounter.participantDetails.length
const more = encounter.participants.length > 2 ? ` +${encounter.participants.length - 2} more` : ""; ? encounter.participantDetails
breakdown.push(`${xpEntry.diff} from: ${participantsStr}${more}`); : (encounter.participants ?? []).map((id) => ({ id, name: resolveParticipantName(id), isPlayer: false }));
} else {
breakdown.push(`${xpEntry.diff} 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 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 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) {
const keys = Object.keys(encounter.xpAwardsRaw);
breakdown.push(`XP awards data keys: ${keys.join(", ")}`);
if (encounter.xpAwardsRaw.pf1) {
breakdown.push(`pf1 keys: ${Object.keys(encounter.xpAwardsRaw.pf1).join(", ")}`);
console.log("[GowlersTracking] XP Tooltip build - xpAwardsRaw.pf1 contents:", encounter.xpAwardsRaw.pf1);
}
} }
// Add encounter status // Add encounter status
@@ -1109,6 +1140,30 @@ async function recordHistoryEntry(actor, statId, previous, nextValue, userId, op
else if (change?.encounterId) encounterId = change.encounterId; else if (change?.encounterId) encounterId = change.encounterId;
else if (options?.encounter?.id) encounterId = options.encounter.id; else if (options?.encounter?.id) encounterId = options.encounter.id;
else if (options?.encounter?._id) encounterId = options.encounter._id; else if (options?.encounter?._id) encounterId = options.encounter._id;
// XP debug logging for data inspection
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));
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)");
const encounterFlag = actor.getFlag(FLAG_SCOPE, ENCOUNTER_FLAG) ?? [];
console.log("[GowlersTracking][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)");
}
console.log("[GowlersTracking][XP DEBUG] -----");
} catch (e) {
console.warn("[GowlersTracking][XP DEBUG] logging failed:", e);
}
} }
// Detect source of the change with actor and item details // Detect source of the change with actor and item details
@@ -1779,6 +1834,8 @@ async function updateEncounterSummary(actor, combat, status = "ongoing") {
dateCreated: Date.now(), dateCreated: Date.now(),
dateUpdated: Date.now(), dateUpdated: Date.now(),
participants: [], participants: [],
participantDetails: [],
xpAwardsRaw: null,
status: status, status: status,
xpGained: 0, xpGained: 0,
rounds: 0, rounds: 0,
@@ -1793,12 +1850,41 @@ async function updateEncounterSummary(actor, combat, status = "ongoing") {
// Update participant list with all combatants // Update participant list with all combatants
const participantIds = new Set(); const participantIds = new Set();
const participantDetails = [];
for (const combatant of combat.combatants) { for (const combatant of combat.combatants) {
if (combatant.actor) { const cActor = combatant.actor;
participantIds.add(combatant.actor.id); if (cActor) {
participantIds.add(cActor.id);
participantDetails.push({
id: cActor.id,
name: cActor.name,
isPlayer: cActor.hasPlayerOwner || cActor.type === "character",
type: cActor.type,
disposition: combatant.token?.disposition,
});
} }
} }
encEntry.participants = Array.from(participantIds); encEntry.participants = Array.from(participantIds);
encEntry.participantDetails = participantDetails;
encEntry.xpAwardsRaw = combat.flags ? foundry.utils.duplicate(combat.flags) : null;
if (encEntry.xpAwardsRaw) {
try {
console.log("[GowlersTracking] Captured xpAwardsRaw keys:", Object.keys(encEntry.xpAwardsRaw));
if (encEntry.xpAwardsRaw.pf1) {
console.log("[GowlersTracking] xpAwardsRaw.pf1 keys:", Object.keys(encEntry.xpAwardsRaw.pf1));
console.log("[GowlersTracking] xpAwardsRaw.pf1 payload:", encEntry.xpAwardsRaw.pf1);
}
} catch (e) {
console.warn("[GowlersTracking] Failed to log xpAwardsRaw keys:", e);
}
} else {
console.log("[GowlersTracking] No xpAwardsRaw captured on combat flags");
}
try {
console.log("[GowlersTracking] Combat flags snapshot:", combat.flags ?? "(none)");
} catch (e) {
console.warn("[GowlersTracking] Failed to log combat flags snapshot:", e);
}
// Keep recent encounters (max 50) // Keep recent encounters (max 50)
if (existing.length > 50) { if (existing.length > 50) {