feat(effort): dépense des points d'Effort sur un test (règles p.35)

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).
This commit is contained in:
2026-08-09 09:32:59 +02:00
parent 15f8e927f0
commit 89413c17a4
5 changed files with 74 additions and 25 deletions
+4 -2
View File
@@ -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() {
+3 -1
View File
@@ -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() {
+54 -21
View File
@@ -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<Actor|null>}
*/
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