From ea2070598f1e4fc28dfcbd4e1a65af29124a462e Mon Sep 17 00:00:00 2001 From: "centron\\schwoerer" Date: Fri, 21 Nov 2025 09:16:21 +0100 Subject: [PATCH] test --- .../memories/pf1_damage_application_system.md | 265 ++++++++++++++++++ Manual_dmgtracking.md | 108 +++++++ 2 files changed, 373 insertions(+) create mode 100644 .serena/memories/pf1_damage_application_system.md create mode 100644 Manual_dmgtracking.md diff --git a/.serena/memories/pf1_damage_application_system.md b/.serena/memories/pf1_damage_application_system.md new file mode 100644 index 00000000..7b4f9be4 --- /dev/null +++ b/.serena/memories/pf1_damage_application_system.md @@ -0,0 +1,265 @@ +# PF1 System Damage Application Analysis + +## 1. WHERE `applyDamage` IS DEFINED + +### Primary Definition +- **File**: `c:\DEV\Foundry2\Foundry_VTT\src\foundryvtt-pathfinder1-v10.8\module\documents\actor\actor-pf.mjs` +- **Line**: 3780 (instance method) and 3809 (static method) + +### Instance Method +```javascript +// Line 3780 +async applyDamage(value, options = {}) { + return this.constructor.applyDamage( + value, + foundry.utils.mergeObject(options, { + targets: [this], + }) + ); +} +``` + +### Static Method +```javascript +// Line 3809 +static async applyDamage( + value = 0, + { + forceDialog = false, + reductionDefault = "", + asNonlethal = false, + targets = null, + critMult = 0, + dualHeal = false, + asWounds = false, + instances = [], + event, + element, + message = null, + } = {} +) +``` + +## 2. METHOD PARAMETERS & OPTIONS STRUCTURE + +### Method Signature +```javascript +static async applyDamage(value = 0, options = {}) +``` + +### Parameters +- **value** (number): The damage amount to apply + - Positive = damage + - Negative = healing + - Returns warning if 0 or not finite + +### Options Object Structure +```javascript +{ + forceDialog: false, // Forces dialog even if Shift not pressed + reductionDefault: "", // Default damage reduction value + asNonlethal: false, // Marks damage as nonlethal + targets: null, // Array of Token|Actor to apply damage to + critMult: 0, // Critical multiplier (for Wounds & Vigor variant) + dualHeal: false, // If healing, also heals nonlethal damage + asWounds: false, // Apply to wounds directly (Wounds & Vigor variant) + instances: [], // Individual damage instances (not currently processed) + event, // Triggering event object + element, // Triggering HTML element + message: null, // ChatMessage reference (for modules) +} +``` + +### Return Value +```javascript +Promise // False if cancelled, array of updated actors if successful +``` + +## 3. HOW DAMAGE IS APPLIED FROM CHAT CARDS + +### Chat Card Button Flow + +**Template Files**: +- `simple-damage.hbs` (line 19-20): Simple damage/healing cards +- `attack-roll.hbs`: Attack roll cards with damage buttons + +**Button HTML**: +```handlebars + + +``` + +Or inline actions: +```handlebars +Apply +``` + +### Click Handler Chain + +1. **Item Chat Handler** (`item-pf.mjs`, line 1637): + ```javascript + html.on("click", ".card-buttons button, .inline-action", this._onChatCardButton.bind(this)); + ``` + +2. **_onChatCardButton** (`item-pf.mjs`, line 1643): + - Extracts button data and message + - Checks permissions + - Routes to `_onChatCardAction` if action is "applyDamage" + +3. **_onChatCardAction** (`item-pf.mjs`, line 1688): + - Extracts damage value from `button.dataset.value` + - Gets attack data from `message.systemRolls.attacks` + - Builds damage instances array + - **Calls**: `pf1.documents.actor.ActorPF.applyDamage(value, options)` + +### Extracted Data at Application Point + +```javascript +// From item-pf.mjs line 1723-1731 +pf1.documents.actor.ActorPF.applyDamage(value, { + asNonlethal, // from button.dataset.tags + event, // click event + element: button, // button element + message, // ChatMessage object + isCritical, // attack type === "critical" + critMult: isCritical ? metadata.config.critMult ?? 0 : 0, + instances, // damage roll instances +}); +``` + +### Alternative Entry Point: Text Enrichers + +**File**: `text-enrichers.mjs`, line 480 +```javascript +actor.applyDamage(value, { ...options, event, element: target }); +``` +- Used for inline damage/heal links in chat +- Creates damage instances from formula evaluation + +## 4. CHAT MESSAGE FLAGS STRUCTURE + +### Message Flags +```javascript +message.flags.pf1.metadata = { + action: itemActionId, // Line 36: ItemAction UUID + item: itemId, // Line 46: ItemPF ID + template: templateId, // Line 67: MeasuredTemplate ID + targets: [uuid, uuid, ...], // Line 78: Target actor/token UUIDs + rolls: { // Line 97: Rolls data + attacks: [ + { + damage: [ { total: 10, damageType: "slashing" } ], + critDamage: [ { total: 20, damageType: "slashing" } ], + } + ] + }, + config: { + critMult: 2, // Critical multiplier + } +} + +message.flags.pf1.subject = { + health: "damage" | "healing" // Line 152: Type of application +} + +message.flags.pf1.ammoRecovery = { + [attackIndex]: { + [ammoId]: { recovered: count } + } +} +``` + +### Relevant Message Properties +```javascript +message.systemRolls // Chat message rolls (init'd from flags.pf1.metadata.rolls) +message.targets // Property getter that extracts targets from flags (line 77) +``` + +## 5. ALTERNATIVE DAMAGE APPLICATION PATHS + +### Instance Method Wrapper +**File**: `actor-pf.mjs`, line 3780 +```javascript +async applyDamage(value, options = {}) { + return this.constructor.applyDamage(value, { targets: [this], ...options }); +} +``` +- Used to apply damage to specific actor instance +- Automatically sets targets to self +- All options passed through + +### Direct Actor Update Alternative +Not a separate method, but damage internally calls: +```javascript +a.update({ + "system.attributes.hp.value": newHP, + "system.attributes.hp.nonlethal": newNL, + "system.attributes.hp.temp": newTemp, + // Or for Wounds & Vigor: + "system.attributes.vigor.value": newHP, + "system.attributes.wounds.value": newWounds, +}) +``` + +### No Hook Integration +- **Hooks searched**: None found in applyDamage +- **Pre-hooks**: None (could wrap to intercept) +- **Post-hooks**: None (could call custom hook after update) + +## 6. KEY IMPLEMENTATION DETAILS + +### Health System Support +- **Normal HP Mode**: Uses `system.attributes.hp` +- **Wounds & Vigor Mode**: Uses `system.attributes.vigor` + `system.attributes.wounds` +- Determined by: `healthConfig.variants[actorType].useWoundsAndVigor` + +### Damage Flow (Normal Mode) +1. Nonlethal damage applied first +2. Temp HP reduced before actual HP +3. Remaining damage reduces actual HP +4. Cannot go below 0 + +### Damage Flow (Wounds & Vigor Mode) +1. Vigor (temp health) reduced first +2. Wounds (permanent damage) calculated +3. Critical multiplier applied if applicable +4. Wounds clamped to [0, max] + +### Dialog Display +- Only shown if `forceDialog` is true OR Shift key pressed +- Allows user to: + - Select target tokens + - Enter custom damage reduction + - Choose damage multiplier (normal/half) + - Apply to specific targets + +### Permissions Check +```javascript +if (!a.isOwner) { + ui.notifications.warn(`No permission for ${actor.name}`); + continue; // Skip this actor +} +``` + +## 7. WRAPPING POINTS FOR INTERCEPTION + +### Recommended Hooks to Add +1. **Pre-damage hook**: Before `actor.update()` calls + ```javascript + Hooks.call("pf1.preDamageApplied", actor, updateData, options) + ``` + +2. **Post-damage hook**: After all updates complete + ```javascript + Hooks.call("pf1.damageApplied", updatedActors, options) + ``` + +### Intercept Location +- Line 3950 in actor-pf.mjs: `promises.push(a.update(updateData))` +- Could wrap this to modify updateData or add custom logging + +### For Gowler's Tracking Ledger +- **Best approach**: Add post-damage hook +- Call `actor.getFlag("pf1", "gowlers-tracking")` to get ledger +- Log damage with timestamp and source +- Store in actor flags or external system diff --git a/Manual_dmgtracking.md b/Manual_dmgtracking.md new file mode 100644 index 00000000..cb5d7f70 --- /dev/null +++ b/Manual_dmgtracking.md @@ -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.