Files
FoundryVTT/Manual_dmgtracking.md
centron\schwoerer ea2070598f test
2025-11-21 09:16:21 +01:00

4.4 KiB

PF1 Tracking Ledger: Damage Source Logging

This document describes how the tracking-ledger module captures detailed HP change metadata (damage source, damage type, encounter ID, etc.) so the ledger can show where every hit point delta came from.

Overview

The module instruments every HP update by:

  1. Wrapping the PF1 system's ActorPF.applyDamage method to intercept the combat context (chat card, damage/healing roll, target list, action name).
  2. Buffering the captured metadata (source label, type, encounter) in a temporary cache keyed by actor ID.
  3. Listening to the updateActor hook for the specific actor, detecting HP deltas, and consuming the buffered metadata while persisting it into the world flag (flags.world.pf1HpHistory).

This preserves full provenance of each HP change without requiring upstream system modifications.

Hooking ActorPF.applyDamage

const ActorPF = pf1?.documents?.actor?.ActorPF;
const original = ActorPF.applyDamage;

ActorPF.applyDamage = async function wrappedApplyDamage(value, options = {}) {
  try {
    noteDamageSource(value, options);
  } catch (err) {
    console.warn("Tracking Ledger | Failed to record damage source", err);
  }
  return original.call(this, value, options);
};
  • Run once, guarded to avoid double wrapping.
  • value is the delta applied to each target (negative for damage, positive for healing); options carries metadata from PF1's roll flow.

Recording the source details

function noteDamageSource(value, options) {
  const actors = resolveActorTargets(options?.targets);
  if (!actors.length) return;

  const label = buildSourceLabel(value, options);
  if (!label) return;

  for (const actor of actors) {
    trackingState.sources.set(actor.id, { label, ts: Date.now() });
  }
}
  • resolveActorTargets converts the PF1 target collection (tokens or actors) into concrete Actor instances.
  • buildSourceLabel inspects options.message.flags.pf1 (identified action info, metadata, flavor text) to construct a human-readable source such as "Goblin -> Scimitar Slash" or "Manual healing".
  • The resulting label is cached in trackingState.sources until the corresponding HP delta is observed on that actor.

Source label construction

Order of precedence when building the label:

  1. options.message.flags.pf1.identifiedInfo (action name, item name).
  2. Chat card metadata (flags.pf1.metadata.actor / .item) to resolve the actor+item pair responsible.
  3. Chat card flavor text (message.flavor).
  4. Fallbacks: "Healing" or "Damage" depending on delta sign.

This ensures we always have some context, even for custom macros.

Consuming the buffered metadata

Inside the updateActor hook, every time system.attributes.hp.value changes, we:

const diff = newValue - previous;
const label = consumeDamageSource(actor.id) ?? inferManualSource(diff);
const entry = {
  timestamp: Date.now(),
  hp: newValue,
  diff: diff >= 0 ? `+${diff}` : `${diff}`,
  user: game.users.get(userId)?.name ?? "System",
  source: label ?? "",
};
  • consumeDamageSource pulls (and deletes) the cached metadata for that actor if present.
  • inferManualSource supplies "Manual healing" or "Manual damage" when the HP change didn't originate from a wrapped applyDamage call (e.g., direct sheet edits).
  • The entry is stored in flags.world.pf1HpHistory with a max history size of 50 rows.

Encounter tagging

Additionally, every recorded entrée is tagged with the active encounter:

entry.encounterId = game.combats?.active?.id ?? null;

updateEncounterSummary maintains flags.world.pf1EncounterHistory, aggregating:

  • Encounter ID
  • Start/end timestamps
  • Participants (actor names captured per HP/XP change)
  • XP gained per actor (sum of positive XP deltas)

The history dialog exposes this as a dedicated "Encounters" tab that updates in real time as HP/XP changes occur within the same combat.

Summary

  • Damage source detection: Done by wrapping ActorPF.applyDamage to capture chat metadata.
  • Storage: Cached per-actor, consumed when HP changes fire in updateActor, saved to flags.world.pf1HpHistory.
  • Encounter tracking: Each HP/XP change is tagged with game.combats.active.id and aggregated into flags.world.pf1EncounterHistory.

This instrumentation gives the ledger full visibility into who dealt which damage (and why), while remaining self-contained in the module.