ChatGPT 95e8b7b994
All checks were successful
Sonarqube Scanner / Build and analyze (push) Successful in 50s
handle marker as feature
2026-06-05 00:37:37 +02:00

101 lines
3.4 KiB
JavaScript

import { TRIGGER_TYPES } from "../constants.js";
import { handleTrigger } from "../engine/reaction-engine.js";
import { getBestTokenDocumentForActor } from "../utils/token-utils.js";
export function registerDnd5eDamageTrigger() {
Hooks.on("dnd5e.applyDamage", async (actor, amount, options = {}) => {
await handleDamageReceivedHook(actor, amount, options);
});
Hooks.on("dnd5e.damageActor", async (actor, changes = {}, update = {}, userId = null) => {
const amount = normalizeDamageActorAmount(changes, update);
await handleDamageReceivedHook(actor, amount, { changes, update, userId });
});
}
async function handleDamageReceivedHook(actor, amount, options = {}) {
if (!game.user.isGM || !actor) return;
const hookOptions = normalizeDamageHookOptions(options);
const tokenDocument = resolveDamageTokenDocument(actor, hookOptions);
const targetActor = tokenDocument?.actor ?? actor;
const numericAmount = Number(amount) || 0;
await handleTrigger(TRIGGER_TYPES.DAMAGE_RECEIVED, {
actor: targetActor,
targetActor,
tokenDocument,
targetTokenDocument: tokenDocument,
targetToken: tokenDocument?.object ?? null,
amount: numericAmount,
damageAmount: numericAmount,
damageTypes: extractDamageTypes(hookOptions),
options: hookOptions
});
}
function normalizeDamageHookOptions(options) {
return options && typeof options === "object" ? options : {};
}
function normalizeDamageActorAmount(changes, update) {
const candidates = [changes?.total, changes?.amount, changes?.value, update?.total, update?.amount, update?.value];
const value = candidates.find(candidate => Number.isFinite(Number(candidate)));
return Number(value) || 0;
}
function resolveDamageTokenDocument(actor, options) {
return asTokenDocument(options.tokenDocument) ??
asTokenDocument(options.token) ??
asTokenDocument(options.targetTokenDocument) ??
asTokenDocument(options.targetToken) ??
asTokenDocument(actor.token) ??
getBestTokenDocumentForActor(actor);
}
function asTokenDocument(value) {
if (!value) return null;
if (value.documentName === "Token") return value;
if (value.document?.documentName === "Token") return value.document;
return null;
}
function extractDamageTypes(options) {
const knownTypes = getKnownDamageTypes();
const collected = new Set();
collectDamageTypes(options, knownTypes, collected);
return Array.from(collected);
}
function collectDamageTypes(value, knownTypes, collected, seen = new WeakSet(), depth = 0) {
if (value == null || depth > 5) return;
if (typeof value === "string") {
if (knownTypes.has(value)) collected.add(value);
return;
}
if (typeof value !== "object" || seen.has(value)) return;
seen.add(value);
if (Array.isArray(value) || value instanceof Set) {
for (const entry of value) collectDamageTypes(entry, knownTypes, collected, seen, depth + 1);
return;
}
if (value instanceof Map) {
for (const entry of value.values()) collectDamageTypes(entry, knownTypes, collected, seen, depth + 1);
return;
}
for (const key of ["type", "damageType", "damageTypeId", "types", "damageTypes", "damage", "damages", "damageDetail", "parts", "workflow"]) {
collectDamageTypes(value[key], knownTypes, collected, seen, depth + 1);
}
}
function getKnownDamageTypes() {
const damageTypes = CONFIG.DND5E?.damageTypes ?? {};
if (damageTypes instanceof Map) return new Set(damageTypes.keys());
return new Set(Object.keys(damageTypes));
}