From f9f07cbc7e65c2ff73cdc766f5c90aa50e58937f Mon Sep 17 00:00:00 2001 From: LeRatierBretonnier Date: Thu, 4 Jun 2026 16:48:57 +0200 Subject: [PATCH] fix: migrate combat system from d6 to d10 to match official rules - Change all dice rolls in fight.mjs from d6 to d10 - Implement success counting system (dice >= difficulty) as per Vermine2047 rules - Add d10 success class to dice that meet or exceed difficulty - Display success count in confrontation UI - Update chat message handler to count successes instead of summing dice - Add comprehensive JSDoc documentation to performTest method - Add missing French translations for fight tool terms This corrects a critical inconsistency where fight.mjs was using d6 while the official Vermine2047 rules and the rest of the system (roll.mjs) use d10 with success counting. Compatibility: FoundryVTT v11-v14 Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- lang/fr.json | 8 ++++ module/system/fight.mjs | 88 ++++++++++++++++++++++++++++++++--------- 2 files changed, 78 insertions(+), 18 deletions(-) diff --git a/lang/fr.json b/lang/fr.json index 77bc2fe..351c4d9 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -130,6 +130,14 @@ "error_no_rerolls_left": "Plus de relances possibles", "error_select_ability": "Veuillez sélectionner une caractéristique", "error_not_enough_self_control": "Pas assez de points de sang-froid", + "FightTool": "Outil de combat", + "Roll4Fight": "Lancer pour le combat", + "Selected": "Sélectionné", + "Achievement": "Accomplissement", + "Conservation": "Conservation", + "ConfrontationHint": "Résolvez la confrontation en sélectionnant des dés", + "PurposeTrait": "Trait de but", + "SpleenTrait": "Trait de rate", "group": "Groupe", "abilities": "Caractéristiques", "ability": "Caractéristique", diff --git a/module/system/fight.mjs b/module/system/fight.mjs index 395fd6a..0971969 100644 --- a/module/system/fight.mjs +++ b/module/system/fight.mjs @@ -1,11 +1,36 @@ import { VERMINE } from "./config.mjs"; import { getActorSkillScore, updateActorSkillScore } from "./functions.mjs"; +/** + * Handles combat-related dice rolls for Vermine2047. + * Uses d10-based system with success counting as per official rules. + */ export class VermineFight { + /** + * Performs a d10-based test according to Vermine2047 official rules. + * Each die result is compared to a difficulty threshold. + * Each result >= difficulty counts as 1 Success. + * + * @param {number} enemyAchievement - Opponent's achievement score + * @param {number} enemyConservation - Opponent's conservation score + * @param {string} skillKey - The skill key being tested + * @param {number} skill - The skill value + * @param {Object} params - Additional test parameters + * @param {number} [params.difficulty=7] - Difficulty threshold (3-10) + * @param {boolean} [params.spleen] - Whether to use spleen rule + * @param {boolean} [params.purpose] - Whether to use purpose rule + * @param {number} [params.usure] - Wear/usage modifier + * @param {number} [params.trait] - Trait bonus + * @param {boolean} [params.specialization] - Whether specialization applies + * @param {Actor} actor - The actor performing the test + * @returns {Promise} Roll data including successes count + */ async performTest(enemyAchievement, enemyConservation, skillKey, skill, params, actor) { + // Use d10 as per Vermine2047 official rules const dicePool = (params.spleen != undefined || params.purpose != undefined) ? '5' : '4'; - const r = new Roll(dicePool + `d6`); + const difficulty = params.difficulty || 7; // Default difficulty + const r = new Roll(dicePool + `d10`); let diceString = ''; let dicePoolHint = ''; let discardedRoll = false; @@ -47,9 +72,14 @@ export class VermineFight { } const discardedRollText = (discardedRoll.result != undefined) ? '
' + discardedRoll.result + '
' : ""; + // Count successes (dice >= difficulty) as per Vermine2047 rules + let successes = 0; for (let i = 0; i < r.terms[0].results.length; i++) { let result = r.terms[0].results[i].result; - diceString += '
  • ' + result + '
  • '; + if (result >= difficulty) { + successes++; + } + diceString += '
  • ' + result + '
  • '; } let hintText = game.i18n.format('VERMINE.ConfrontationHint'); @@ -60,7 +90,7 @@ export class VermineFight {
    - ` + dicePool + `d6 ` + dicePoolHint + ` + ` + dicePool + `d10 ` + dicePoolHint + `
    @@ -79,7 +109,10 @@ export class VermineFight { ` + game.i18n.format('VERMINE.Conservation') + ` -
    +
    +
    + ` + game.i18n.format('VERMINE.success_count') + `: ` + successes + ` +
    @@ -95,7 +128,14 @@ export class VermineFight { this.sendToChat(html, r, actor); }; - // on fait les comptes + // Return roll data for further processing with d10 success counting + return { + roll: r, + successes: successes, + difficulty: difficulty, + dicePool: parseInt(dicePool, 10), + total: r.total + }; } @@ -141,42 +181,54 @@ export class VermineFight { // console.log("accès au fin du fin", message._id); // sélection du dé actif - html.on("click", '.confrontation .die.d6', event => { + html.on("click", '.confrontation .die.d10', event => { const diceResult = parseInt($(event.target).html(), 10); - html.find('.confrontation .die.d6').removeClass('active'); + html.find('.confrontation .die.d10').removeClass('active'); $(event.target).addClass('active'); }); - // sélection des dés d'accomplissement + // sélection des dés d'accomplissement (achievement = successes) html.on("click", '.confrontation .add-to-achievement', event => { - const diceResult = parseInt(html.find('.confrontation .die.d6.active').html(), 10); - html.find('.confrontation .die.d6.active').removeClass('min').addClass('max'); + const diceResult = parseInt(html.find('.confrontation .die.d10.active').html(), 10); + html.find('.confrontation .die.d10.active').removeClass('min').addClass('max'); }); // sélection des dés de conservation html.on("click", '.confrontation .add-to-conservation', event => { - const diceResult = parseInt(html.find('.confrontation .die.d6.active').html(), 10); - html.find('.confrontation .die.d6.active').removeClass('max').addClass('min'); + const diceResult = parseInt(html.find('.confrontation .die.d10.active').html(), 10); + html.find('.confrontation .die.d10.active').removeClass('max').addClass('min'); }); // reset de la sélection des pools html.on("click", '.confrontation .reset', event => { - html.find('.confrontation .die.d6') + html.find('.confrontation .die.d10') .removeClass('max') .removeClass('min'); }); // résolution de la confrontation + // Note: With d10 system, we count successes (dice >= difficulty) instead of summing html.on("click", '.confrontation .resolve', async event => { let achievementDice = 0; let conservationDice = 0; let achievementBasis = 0; let conservationBasis = 0; - html.find('.confrontation .die.d6.max').each(function (index) { - achievementDice += parseInt($(this).html(), 10); + const difficulty = 7; // Default difficulty for legacy compatibility + + // Count successes (dice >= difficulty) for achievement + html.find('.confrontation .die.d10.max').each(function (index) { + const value = parseInt($(this).html(), 10); + if (value >= difficulty) { + achievementDice++; + } }); - html.find('.confrontation .die.d6.min').each(function (index) { - conservationDice += parseInt($(this).html(), 10); + + // Count successes (dice >= difficulty) for conservation + html.find('.confrontation .die.d10.min').each(function (index) { + const value = parseInt($(this).html(), 10); + if (value >= difficulty) { + conservationDice++; + } }); // saisie des résultats @@ -188,7 +240,7 @@ export class VermineFight { html.find('td.conservation-result').data('conservation-value', conservationDice); html.find('td.conservation-result').html(conservationBasis + conservationDice); - // calcul des marges + // calcul des marges (now based on successes, not sum) const achievementMargin = achievementBasis + achievementDice - parseInt(html.find('td.adv-achievement-result').html(), 10); const conservationMargin = conservationBasis + conservationDice - parseInt(html.find('td.adv-conservation-result').html(), 10); html.find('td.achievement-margin').html(achievementMargin);