test
This commit is contained in:
108
Manual_dmgtracking.md
Normal file
108
Manual_dmgtracking.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user