diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5ca80fa --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +packs/** filter=lfs diff=lfs merge=lfs -text diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f58132f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "cSpell.words": [ + "biodata", + "LETHALFANTASY" + ] +} \ No newline at end of file diff --git a/lethal-fantasy.mjs b/lethal-fantasy.mjs index 0733b02..13e6004 100644 --- a/lethal-fantasy.mjs +++ b/lethal-fantasy.mjs @@ -117,6 +117,9 @@ Hooks.once("ready", function () { registerWorldCount("lethalFantasy") } + // Saignement piloté par le combat tracker + _registerBleedingHooks() + _showUserGuide() /** @@ -129,6 +132,93 @@ Hooks.once("ready", function () { } }) +/** + * Saignement piloté par le combat tracker. + * Chaque round = 1 seconde → les acteurs qui saignent perdent 1 HP/blessure. + * Hors combat, une notification prévient le MJ que des blessures saignent encore. + */ +function _registerBleedingHooks() { + if (!game.user.isGM) return + + Hooks.on("combatRound", async (combat, previous, current) => { + if (previous === current) return + const processed = new Set() + for (const combatant of combat.combatants) { + const actor = combatant.actor + if (!actor || processed.has(actor.id)) continue + processed.add(actor.id) + await _applyBleedingTick(actor) + } + }) + + Hooks.on("combatEnd", async (combat) => { + const bleeding = _findBleedingActors() + if (bleeding.length) { + ui.notifications.warn( + `Saignement actif hors combat : ${bleeding.map(a => a.name).join(", ")}` + ) + } + }) + + Hooks.on("combatStart", async (combat) => { + const bleeding = _findBleedingActors() + if (bleeding.length) { + ui.notifications.warn( + `Saignement toujours actif sur : ${bleeding.map(a => a.name).join(", ")}` + ) + } + }) +} + +/** + * Appliquer 1 HP de dégât par blessure active, décrémenter la durée. + * @param {import("foundry/common/documents.mjs").Actor} actor + */ +async function _applyBleedingTick(actor) { + if (!actor?.system?.hp?.wounds) return + const wounds = foundry.utils.duplicate(actor.system.hp.wounds) + let hpLoss = 0 + let changed = false + for (const wound of wounds) { + if (wound.duration > 0 && wound.value > 0) { + hpLoss += 1 + wound.duration -= 1 + if (wound.duration <= 0) { + wound.value = 0 + wound.description = "" + } + changed = true + } + } + if (!changed) return + const currentHp = actor.system.hp.value ?? 0 + await actor.update({ + "system.hp.value": currentHp - hpLoss, + "system.hp.wounds": wounds, + }) +} + +/** + * Retourne les acteurs (monde + tokens) qui ont des blessures actives. + * @returns {import("foundry/common/documents.mjs").Actor[]} + */ +function _findBleedingActors() { + const actors = [] + for (const actor of game.actors.values()) { + if (actor?.system?.hp?.wounds?.some(w => w.duration > 0 && w.value > 0)) { + actors.push(actor) + } + } + for (const token of canvas.tokens?.placeables ?? []) { + if (token.actor && !actors.includes(token.actor)) { + if (token.actor?.system?.hp?.wounds?.some(w => w.duration > 0 && w.value > 0)) { + actors.push(token.actor) + } + } + } + return actors +} + Hooks.on("renderChatMessageHTML", (message, html, data) => { const typeMessage = data.message.flags.lethalFantasy?.typeMessage // Message de demande de jet de dés