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

7.8 KiB

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

// Line 3780
async applyDamage(value, options = {}) {
  return this.constructor.applyDamage(
    value,
    foundry.utils.mergeObject(options, {
      targets: [this],
    })
  );
}

Static Method

// 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

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

{
  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

Promise<false|Actor[]>  // 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:

<button type="button" data-action="applyDamage" data-value="{{value.total}}">Apply</button>
<button type="button" data-action="applyDamage" data-value="{{value.half}}">Apply Half</button>

Or inline actions:

<a class="inline-action" data-action="applyDamage" data-type="{{type}}" data-value="...">Apply</a>

Click Handler Chain

  1. Item Chat Handler (item-pf.mjs, line 1637):

    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

// 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

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

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

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

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:

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

if (!a.isOwner) {
  ui.notifications.warn(`No permission for ${actor.name}`);
  continue;  // Skip this actor
}

7. WRAPPING POINTS FOR INTERCEPTION

  1. Pre-damage hook: Before actor.update() calls

    Hooks.call("pf1.preDamageApplied", actor, updateData, options)
    
  2. Post-damage hook: After all updates complete

    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