109 lines
4.4 KiB
Markdown
109 lines
4.4 KiB
Markdown
# 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`
|
|
|
|
```js
|
|
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
|
|
|
|
```js
|
|
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:
|
|
|
|
```js
|
|
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:
|
|
|
|
```js
|
|
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.
|