7.8 KiB
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 cardsattack-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
-
Item Chat Handler (
item-pf.mjs, line 1637):html.on("click", ".card-buttons button, .inline-action", this._onChatCardButton.bind(this)); -
_onChatCardButton (
item-pf.mjs, line 1643):- Extracts button data and message
- Checks permissions
- Routes to
_onChatCardActionif action is "applyDamage"
-
_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)
- Extracts damage value from
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)
- Nonlethal damage applied first
- Temp HP reduced before actual HP
- Remaining damage reduces actual HP
- Cannot go below 0
Damage Flow (Wounds & Vigor Mode)
- Vigor (temp health) reduced first
- Wounds (permanent damage) calculated
- Critical multiplier applied if applicable
- Wounds clamped to [0, max]
Dialog Display
- Only shown if
forceDialogis 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
Recommended Hooks to Add
-
Pre-damage hook: Before
actor.update()callsHooks.call("pf1.preDamageApplied", actor, updateData, options) -
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