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

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.