From 89413c17a492094659bfa8206854d94e0c5346aa Mon Sep 17 00:00:00 2001 From: LeRatierBretonnier Date: Sun, 9 Aug 2026 09:32:59 +0200 Subject: [PATCH] =?UTF-8?q?feat(effort):=20d=C3=A9pense=20des=20points=20d?= =?UTF-8?q?'Effort=20sur=20un=20test=20(r=C3=A8gles=20p.35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le bouton d'Effort du message de jet déduit désormais les points de la Réserve d'Effort de l'acteur (1 point = 1 relance), s'ajoute aux relances de Compétence au lieu de les remplacer, et affiche le pool restant. Le curseur est plafonné à min(caractéristique, effort disponible) et la section est masquée pour les acteurs sans Réserve d'Effort (PNJ, créatures, effort vide). --- less/base.less | 9 ++++ module/system/dialogs/combatDialog.mjs | 6 ++- module/system/dialogs/rollDialog.mjs | 4 +- module/system/roll.mjs | 75 ++++++++++++++++++-------- templates/roll-message.hbs | 5 +- 5 files changed, 74 insertions(+), 25 deletions(-) diff --git a/less/base.less b/less/base.less index d7768d5..f2b0606 100644 --- a/less/base.less +++ b/less/base.less @@ -627,6 +627,15 @@ ol.chat-log { &.visible { max-height: 15rem; } } + .reroll-from-effort { + h4.flexcol { gap: 2px; } + .effort-pool { + font-size: @font-size-12; + opacity: 0.8; + text-align: center; + } + } + // ── Dice results ──────────────────────────────────────────────── ul.roll-results { list-style: none; diff --git a/module/system/dialogs/combatDialog.mjs b/module/system/dialogs/combatDialog.mjs index 57fce3a..2284253 100644 --- a/module/system/dialogs/combatDialog.mjs +++ b/module/system/dialogs/combatDialog.mjs @@ -268,8 +268,10 @@ export default class CombatDialog extends HandlebarsApplicationMixin(foundry.app } getMaxEffort() { - if (this.actor.type === "character") return this.actor.system?.abilities?.[this.abilityKey]?.value || 0; - return 0; + if (this.actor.type !== "character") return 0; + const ability = this.actor.system?.abilities?.[this.abilityKey]?.value || 0; + const effort = this.actor.system?.attributes?.effort?.value || 0; + return Math.min(ability, effort); } getPoolBreakdown() { diff --git a/module/system/dialogs/rollDialog.mjs b/module/system/dialogs/rollDialog.mjs index ff3c824..aa9e973 100644 --- a/module/system/dialogs/rollDialog.mjs +++ b/module/system/dialogs/rollDialog.mjs @@ -288,7 +288,9 @@ export default class RollDialog extends HandlebarsApplicationMixin(foundry.appli getMaxEffort() { const sel = this.#getAbility(); - return parseInt(sel?.value, 10) || 0; + const ability = parseInt(sel?.value, 10) || 0; + const effort = this.#actor?.system?.attributes?.effort?.value || 0; + return Math.min(ability, effort); } getTotems() { diff --git a/module/system/roll.mjs b/module/system/roll.mjs index 57a6e39..f39d94d 100644 --- a/module/system/roll.mjs +++ b/module/system/roll.mjs @@ -415,45 +415,78 @@ export class VermineUtils { }); } - // Add click event for granting rerolls + // Add click event for spending Effort points as rerolls (règles p.35) : + // 1 point d'Effort = 1 relance, ajouté aux relances de Compétence restantes. html.querySelectorAll("button.grant-reroll").forEach(el => { el.addEventListener('click', async (ev) => { const btn = ev.currentTarget; + const rollMessage = btn.closest(".vermine-roll-message"); const grantedRerollElement = html.querySelector('#granted-reroll'); const allowedRerollElement = html.querySelector("#allowed_reroll"); + const requested = parseInt(grantedRerollElement?.innerText, 10) || 0; - if (grantedRerollElement && allowedRerollElement) { - allowedRerollElement.innerText = grantedRerollElement.innerText; + // Déduit les points d'Effort de l'acteur (max : ce qu'il lui reste). + const actor = await VermineUtils.#resolveMessageActor(rollMessage); + const effort = actor?.system?.attributes?.effort; + let spent = 0; + if (effort && requested > 0) { + spent = Math.min(requested, parseInt(effort.value, 10) || 0); + if (spent > 0) { + const remaining = (parseInt(effort.value, 10) || 0) - spent; + await actor.update({ 'system.attributes.effort.value': remaining }); + const poolEl = rollMessage?.querySelector('.effort-pool'); + if (poolEl) poolEl.innerText = `${remaining}/${effort.max || 0}`; + } } + // Les relances d'Effort s'ajoutent aux relances de Compétence restantes. + if (allowedRerollElement) { + const current = parseInt(allowedRerollElement.innerText, 10) || 0; + allowedRerollElement.innerText = current + spent; + } + const effortSection = btn.closest('.reroll-from-effort'); + if (effortSection) effortSection.style.display = "none"; + const mesEl = btn.closest('[data-message-id]'); const messageId = mesEl?.dataset?.messageId; if (messageId) { - btn.closest('.reroll-from-effort').style.display = "none"; - - const rollMessage = btn.closest(".vermine-roll-message"); - if (rollMessage) { - const content = rollMessage.outerHTML; - // GM applies the update directly ; players relay through the - // system socket (Foundry v14 ChatMessage updates are GM-only). - if (game.user?.isGM) { - const message = await game.messages.get(messageId); - await message.update({ content: content }); - } else { - game.socket?.emit(`system.${game.system.id}`, { - type: "reroll", - messageId: messageId, - requestId: foundry.utils.randomID(8), - content: content - }); - } + const content = rollMessage.outerHTML; + // GM applies the update directly ; players relay through the + // system socket (Foundry v14 ChatMessage updates are GM-only). + if (game.user?.isGM) { + const message = await game.messages.get(messageId); + await message.update({ content: content }); + } else { + game.socket?.emit(`system.${game.system.id}`, { + type: "reroll", + messageId: messageId, + requestId: foundry.utils.randomID(8), + content: content + }); } } }); }); } + /** + * Résout l'acteur à l'origine d'un message de jet depuis les attributs + * data-actor-id / data-actor-uuid posés sur le message. Le uuid est + * privilégié : il distingue les tokens non liés (PNJ mineurs) issus de la + * même fiche, qui partagent le même id d'acteur monde. + * @param {HTMLElement} rollMessage + * @returns {Promise} + */ + static async #resolveMessageActor(rollMessage) { + if (!rollMessage) return null; + const id = rollMessage.dataset.actorId ?? null; + const uuid = rollMessage.dataset.actorUuid ?? null; + if (!id && !uuid) return null; + const { VermineExchange } = await import('./exchange.mjs'); + return VermineExchange.resolveDefender({ id, uuid }); + } + /** * Displays dice rolls in 3D if available. * @param {Roll} roll - The roll to display diff --git a/templates/roll-message.hbs b/templates/roll-message.hbs index 6c0ae86..4f79129 100644 --- a/templates/roll-message.hbs +++ b/templates/roll-message.hbs @@ -1,4 +1,4 @@ -
+
{{!-- Verdict --}}
{{#if verdict.success}} @@ -226,9 +226,11 @@

{{localize "VERMINE.rerolls_possible"}} : {{param.Reroll}}

+ {{#ifgt param.actor.system.attributes.effort.value 0}}

{{localize "VERMINE.effort"}} + {{param.actor.system.attributes.effort.value}}/{{param.actor.system.attributes.effort.max}}

0
+ {{/ifgt}}