From 8dd5d029945aa77b5dd1e01e95511102bf3bdf4b Mon Sep 17 00:00:00 2001 From: LeRatierBretonnier Date: Fri, 7 Aug 2026 14:40:42 +0200 Subject: [PATCH] =?UTF-8?q?feat(traits):=20phase=201=20=E2=80=94=20r=C3=A9?= =?UTF-8?q?parations=20(Bien=20con=C3=A7u,=20Pratique,=20Coque=20r=C3=A9pa?= =?UTF-8?q?rable)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Coque : épuisée stockée {value:0, max:n} au lieu de null → réparable ; nouveau mode « Réparer la coque » (difficulté = coque.max) dans le dialogue ; affichage (0/n) sur la fiche - Bien conçu : Réussite automatique en réparation classique (restauration complète) ; +1 Fiabilité à la 1ère réparation de fortune réussie (compteur traits.bien_conçu.value) - Pratique : Handicap +I sur toutes les réparations (required +1, note sur la carte) - i18n fr/en (9 clés) - TRAITS_PLAN.md : suivi du plan d'automatisation des Traits --- TRAITS_PLAN.md | 43 ++++++++ lang/en.json | 9 +- lang/fr.json | 9 +- module/system/dialogs/reliabilityDialog.mjs | 4 + module/system/reliability.mjs | 103 +++++++++++++++----- templates/dialogs/reliability-dialog.hbs | 6 ++ templates/item/partials/traits.hbs | 19 ++-- templates/reliability-repair.hbs | 7 ++ 8 files changed, 169 insertions(+), 31 deletions(-) create mode 100644 TRAITS_PLAN.md diff --git a/TRAITS_PLAN.md b/TRAITS_PLAN.md new file mode 100644 index 0000000..6eba3db --- /dev/null +++ b/TRAITS_PLAN.md @@ -0,0 +1,43 @@ +# Plan d'automatisation des Traits — Suivi + +Base : `regles/regles_traits.txt` (et KitDeSurvie p.70-71, 39). +Implémenté déjà : `cassant`, `coque` (absorption), `etanche` (immersion) dans `module/system/reliability.mjs`. +Décisions utilisateur : Fétiche +1D sur les **deux** Réserves · Malus/Durée = **saisie manuelle** · Handicap de Mobilité = **note de rappel** · Items génériques = **flag équipé OK**. + +## Légende +- [x] fait +- [ ] à faire +- [~] en cours + +## Tâche 0 — Prérequis communs +- [ ] `item.mjs` : champ `equipped` sur items génériques + toggle fiche `item-item-sheet.hbs` +- [ ] nouveau `module/system/traits.mjs` : helpers partagés (`equippedItems`, `lourdMalus`, `mobilityNote`, `feticheItem`…) + +## Phase 1 — Réparations (reliability.mjs + dialogue) +- [x] **Bien conçu** : succès auto en réparation classique (restauration complète) + 1ʳᵉ réparation de fortune rend +1 Fiabilité (compteur `traits.bien_conçu.value`) +- [x] **Pratique** : Handicap +I aux réparations (required +1, note sur la carte) +- [x] **Coque réparable** : épuisement → `{ value: 0, max: n }` ; mode « Réparer la coque » dans le dialogue (jet vs `coque.max`) ; affichage `(0/n)` sur la fiche +- [x] **Étanche** : rien de plus (immersion gérée ; gaz/maladie hors scope) + +## Phase 2 — Combat (combatDialog.mjs, exchange.mjs) +- [ ] **Lourd** : malus auto (`lourdMalus()`) dans dialogue combat +- [ ] **Rapide (n)** : handicap (n) défenseur pré-rempli + note carte d'échange +- [ ] **Rafale (n)** : case « Tir en rafale » → difficulté +2, +n réussites si réussi +- [ ] **Portée** : portée effective × Vigueur (archery/throwing) affichée +- [ ] **Maniable / Mobilité** : note de rappel dialogue combat + jets athlétiques +- [ ] **Incapacitant (n)** : bouton « Jet de Santé vs (n) » sur carte d'échange + +## Phase 3 — Jets d'action (rollDialog.mjs + roll-dialog.hbs) +- [ ] **Ponctuel (n)** : select outil → annule n Handicaps, décrémente, supprime à 0 +- [ ] **Pratique (jets)** : select d'outil → +1D / +2D si Pratique (remplace checkbox `usingTools`) +- [ ] **Intimidant** : case « Brandit » → malus −1D Psychologie ou note relance +- [ ] **Malus (n)** : champ « Malus temporaires » (saisie manuelle) déduit du pool +- [ ] **Durée (n)** : note « effet pendant n tours » sur carte de jet d'arme + +## Phase 4 — Acteur (character.mjs + fiche) +- [ ] **Fétiche** : +1D Effort ET Sang-Froid (corriger config « ou » → « et ») ; alerte 2+ fétiches ; action « Perdre le fétiche » (−2D, max −1D) +- [ ] **Zone (n)** : note carte d'échange + bouton « Appliquer aux cibles de la zone » + +## Transverse +- [x] i18n fr/en pour la Phase 1 +- [x] ESLint (0 erreur sur les fichiers modifiés) + tests navigateur Phase 1 (coque, réparation coque, Bien conçu classique/fortune, Pratique, dialogue, affichage fiche) diff --git a/lang/en.json b/lang/en.json index 3fddad7..4fd2edf 100644 --- a/lang/en.json +++ b/lang/en.json @@ -234,7 +234,14 @@ "reliability_need_actor": "Repair impossible: the object must belong to an actor", "reliability_already_destroyed": "Object already destroyed or unusable", "reliability_coque_absorbed": "Hull absorbs {absorbed} damage(s), {remaining} left", - "reliability_coque_lost": "Hull: trait exhausted and lost", + "reliability_coque_lost": "Hull: trait exhausted — repairable (Difficulty {max})", + "reliability_coque_restored": "Hull repaired: {max} points", + "reliability_repair_coque": "Repair the hull (Difficulty {max})", + "reliability_repair_coque_label": "Hull", + "reliability_bien_concu": "Well-made: automatic success — reliability fully restored", + "reliability_bien_concu_fortune": "Well-made: +1 reliability point (first field repair)", + "reliability_auto_success": "Automatic success (Well-made)", + "reliability_pratique": "Pratique: +I Handicap", "reliability_cassant": "Fragile: reliability losses increased by 1 point", "reliability_etanche": "Watertight: immersion damage ignored", "reliability_fortune_skill_bonus": "+1D — the character has the required skill ({skill})", diff --git a/lang/fr.json b/lang/fr.json index 1069213..d28195d 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -315,7 +315,14 @@ "reliability_need_actor": "Réparation impossible : l'objet doit appartenir à un acteur", "reliability_already_destroyed": "Objet déjà détruit ou inutilisable", "reliability_coque_absorbed": "Coque absorbe {absorbed} Dommage(s), {remaining} restant(s)", - "reliability_coque_lost": "Coque : Trait épuisé et perdu", + "reliability_coque_lost": "Coque : Trait épuisé — réparable (Difficulté {max})", + "reliability_coque_restored": "Coque restaurée : {max} points", + "reliability_repair_coque": "Réparer la coque (Difficulté {max})", + "reliability_repair_coque_label": "Coque", + "reliability_bien_concu": "Bien conçu : Réussite automatique — Fiabilité entièrement restaurée", + "reliability_bien_concu_fortune": "Bien conçu : +1 point de Fiabilité (1ʳᵉ réparation de fortune)", + "reliability_auto_success": "Réussite automatique (Bien conçu)", + "reliability_pratique": "Pratique : Handicap +I", "reliability_cassant": "Cassant : pertes de Fiabilité majorées d'1 point", "reliability_etanche": "Étanche : Dommages d'immersion ignorés", "reliability_fortune_skill_bonus": "+1D — le personnage possède la Compétence requise ({skill})", diff --git a/module/system/dialogs/reliabilityDialog.mjs b/module/system/dialogs/reliabilityDialog.mjs index 69b04f5..3362144 100644 --- a/module/system/dialogs/reliabilityDialog.mjs +++ b/module/system/dialogs/reliabilityDialog.mjs @@ -50,6 +50,8 @@ export default class ReliabilityDialog extends HandlebarsApplicationMixin(foundr async _prepareContext() { const hasEtanche = !!this.#item.system?.traits?.etanche; const hasActor = !!this.#item.actor; + const coque = this.#item.system?.traits?.coque ?? {}; + const hasBrokenCoque = this.#mode === 'repair' && (parseInt(coque?.value ?? 0, 10) || 0) === 0 && (parseInt(coque?.max ?? 0, 10) || 0) > 0; const storedSkill = (this.#item.system?.needSkill?.value && this.#item.system?.needSkill?.skill) || this.#item.system?.skill || ''; const severities = ['mineurs', 'graves', 'critiques'].map((key) => ({ @@ -68,6 +70,8 @@ export default class ReliabilityDialog extends HandlebarsApplicationMixin(foundr isRepair: this.#mode === 'repair', hasEtanche, hasActor, + hasBrokenCoque, + coqueMax: parseInt(coque?.max ?? 0, 10) || 0, severities, skills, }; diff --git a/module/system/reliability.mjs b/module/system/reliability.mjs index 3a7efa1..83945ce 100644 --- a/module/system/reliability.mjs +++ b/module/system/reliability.mjs @@ -81,8 +81,10 @@ export class VermineReliability { traitsNotes.push(game.i18n.localize('VERMINE.reliability_etanche')); } - // Trait Coque (n) : ignore les (n) premiers Dommages puis se perd. + // Trait Coque (n) : ignore les (n) premiers Dommages puis s'épuise. + // La coque épuisée est stockée { value: 0, max: n } : réparable (Phase 1). let coqueValue = parseInt(traits.coque?.value ?? 0, 10) || 0; + const coqueInit = parseInt(traits.coque?.max ?? traits.coque?.value ?? 0, 10) || 0; const updates = {}; if (coqueValue > 0 && dommages > 0) { const absorbed = Math.min(coqueValue, dommages); @@ -92,8 +94,8 @@ export class VermineReliability { updates['system.traits.coque.value'] = coqueValue; traitsNotes.push(game.i18n.format('VERMINE.reliability_coque_absorbed', { absorbed, remaining: coqueValue })); } else { - updates['system.traits.coque'] = null; - traitsNotes.push(game.i18n.localize('VERMINE.reliability_coque_lost')); + updates['system.traits.coque'] = { value: 0, max: coqueInit }; + traitsNotes.push(game.i18n.format('VERMINE.reliability_coque_lost', { max: coqueInit })); } } @@ -154,10 +156,10 @@ export class VermineReliability { } /** - * Réparation classique ou de fortune. + * Réparation classique, de fortune ou de coque. * @param {Item} item * @param {Object} opts - * @param {string} opts.mode "classique" | "fortune" + * @param {string} opts.mode "classique" | "fortune" | "coque" * @param {string} opts.skill Compétence de réparation classique (clé de skills) * @returns {Promise} */ @@ -168,13 +170,25 @@ export class VermineReliability { return null; } + const traits = item.system?.traits ?? {}; const current = parseInt(item.system?.reliability ?? 0, 10) || 0; const damagesValue = parseInt(item.system?.damages?.value ?? 0, 10) || 0; const handicap = this.panneHandicap(item); + // Trait Pratique : +1 Handicap sur toutes les réparations. + const pratique = !!traits.pratique; + const pratiqueHandicap = pratique ? 1 : 0; + const pratiqueNote = pratique ? game.i18n.localize('VERMINE.reliability_pratique') : ''; + + const isCoque = mode === 'coque'; + const coqueMax = isCoque ? parseInt(traits.coque?.max ?? 0, 10) || 0 : 0; + // Difficulté : Fiabilité d'origine (≈ actuelle + Dommages subis) pour la - // réparation classique ; Fiabilité actuelle pour la réparation de fortune. - const difficulty = mode === 'classique' ? Math.min(10, current + damagesValue) : current; + // réparation classique ; Fiabilité actuelle pour la réparation de fortune ; + // valeur maximale du Trait pour la réparation de coque. + let difficulty = current; + if (isCoque) difficulty = coqueMax; + else if (mode === 'classique') difficulty = Math.min(10, current + damagesValue); const precision = parseInt(actor.system?.abilities?.precision?.value ?? 0, 10) || 0; const skillValue = mode === 'fortune' @@ -198,27 +212,57 @@ export class VermineReliability { } } - const roll = new Roll(`${NoD + fortuneBonus}d10cs>=${difficulty}`); - await roll.evaluate(); - await VermineUtils.showDiceSoNice(roll); + // Trait Bien conçu : Réussite automatique sur les Réparations classiques. + const bienConcu = !!traits.bien_conçu; + const autoSuccess = bienConcu && mode === 'classique'; + let bienConcuNote = ''; - const results = (roll.dice ?? []).flatMap((d) => (d.results ?? []).map((r) => ({ - value: r.result ?? 0, - isSuccess: r.success, - }))); - const successes = roll._total ?? 0; - const required = 1 + handicap + fortuneHandicap; - const success = successes >= required; + // Trait Bien conçu (fortune) : la 1ʳᵉ réparation de fortune réussie rend + // 1 point de Fiabilité (compteur traits.bien_conçu.value). + const bienConcuUsed = bienConcu && mode === 'fortune' && (parseInt(traits.bien_conçu?.value ?? 0, 10) || 0) > 0; + const bienConcuBonus = bienConcu && mode === 'fortune' && !bienConcuUsed; + + // Réparation de coque : pas de panne à réparer, Difficulté = max du Trait. + const required = 1 + (isCoque ? 0 : handicap) + (isCoque ? 0 : fortuneHandicap) + pratiqueHandicap; + + const roll = autoSuccess ? null : new Roll(`${NoD + fortuneBonus}d10cs>=${difficulty}`); + if (roll) { + await roll.evaluate(); + await VermineUtils.showDiceSoNice(roll); + } + + const results = roll + ? (roll.dice ?? []).flatMap((d) => (d.results ?? []).map((r) => ({ + value: r.result ?? 0, + isSuccess: r.success, + }))) + : []; + const successes = roll?._total ?? required; + const success = autoSuccess || successes >= required; let gained = 0; let fortuneFixed = false; + let coqueRestored = false; + let newReliability = null; let resultLabel = ''; if (success) { - if (mode === 'classique') { - gained = successes; + if (isCoque) { + coqueRestored = true; + resultLabel = game.i18n.format('VERMINE.reliability_coque_restored', { max: coqueMax }); + } else if (mode === 'classique') { + gained = autoSuccess ? damagesValue : successes; + newReliability = Math.min(10, current + gained); resultLabel = game.i18n.format('VERMINE.reliability_gained', { gained }); + if (autoSuccess) { + bienConcuNote = game.i18n.localize('VERMINE.reliability_bien_concu'); + } } else { fortuneFixed = true; + if (bienConcuBonus) { + gained = 1; + newReliability = Math.min(10, current + 1); + bienConcuNote = game.i18n.localize('VERMINE.reliability_bien_concu_fortune'); + } resultLabel = game.i18n.localize('VERMINE.reliability_fixed'); } } else { @@ -226,31 +270,44 @@ export class VermineReliability { } const updates = {}; - if (mode === 'classique' && gained > 0) { - updates['system.reliability'] = Math.min(10, current + gained); + if (isCoque && coqueRestored) { + updates['system.traits.coque'] = { value: coqueMax, max: coqueMax }; + } else if (mode === 'classique' && gained > 0) { + updates['system.reliability'] = newReliability; updates['system.damages.value'] = Math.max(0, damagesValue - gained); } else if (mode === 'fortune' && fortuneFixed) { updates['system.damages.value'] = 0; + if (bienConcuBonus) { + updates['system.reliability'] = newReliability; + updates['system.traits.bien_conçu'] = { value: 1 }; + } } if (Object.keys(updates).length) await item.update(updates); const skillLabel = mode === 'fortune' ? game.i18n.localize('SKILLS.diy.name') : game.i18n.localize(`SKILLS.${skill}.name`); + const modeLabel = isCoque + ? game.i18n.localize('VERMINE.reliability_repair_coque_label') + : game.i18n.localize(mode === 'classique' ? 'VERMINE.reliability_repair_classic' : 'VERMINE.reliability_repair_fortune'); const content = await foundry.applications.handlebars.renderTemplate( 'systems/vermine2047/templates/reliability-repair.hbs', { item: { name: item.name, img: item.img }, - modeLabel: game.i18n.localize(mode === 'classique' ? 'VERMINE.reliability_repair_classic' : 'VERMINE.reliability_repair_fortune'), + modeLabel, skillLabel, difficulty, - handicap, + handicap: isCoque ? 0 : handicap, + pratiqueHandicap, results, successes, required, resultLabel, + autoSuccess, fortuneNote, + bienConcuNote, + pratiqueNote, }, ); diff --git a/templates/dialogs/reliability-dialog.hbs b/templates/dialogs/reliability-dialog.hbs index 6bdf44c..173bee4 100644 --- a/templates/dialogs/reliability-dialog.hbs +++ b/templates/dialogs/reliability-dialog.hbs @@ -31,6 +31,12 @@ {{ localize "VERMINE.reliability_repair_fortune" }} + {{#if hasBrokenCoque}} + + {{/if}}
diff --git a/templates/item/partials/traits.hbs b/templates/item/partials/traits.hbs index bd55b49..da92de3 100644 --- a/templates/item/partials/traits.hbs +++ b/templates/item/partials/traits.hbs @@ -6,12 +6,19 @@

- {{#each item.system.traits as | trait index|}} - {{trait.name}} - {{#if trait.value}} - - {{/if}} - + {{#each item.system.traits as |trait key|}} + {{#if trait}} + {{#with (lookup ../config.traits key)}} + {{this.name}} + {{#if trait.max}} + ({{trait.value}}/{{trait.max}}) + {{/if}} + {{#if trait.value}} + + {{/if}} + + {{/with}} + {{/if}} {{/each}}

diff --git a/templates/reliability-repair.hbs b/templates/reliability-repair.hbs index 2e0a58c..f11935b 100644 --- a/templates/reliability-repair.hbs +++ b/templates/reliability-repair.hbs @@ -8,15 +8,22 @@ {{ localize "VERMINE.reliability_skill" }}: {{skillLabel}} {{ localize "VERMINE.difficulty" }}: {{difficulty}} {{#if handicap}}{{ localize "VERMINE.handicap" }}: +{{handicap}}{{/if}} + {{#if pratiqueHandicap}}{{ localize "VERMINE.handicap" }}: +{{pratiqueHandicap}}{{/if}} + {{#if autoSuccess}} +

{{ localize "VERMINE.reliability_auto_success" }}

+ {{else}} + {{/if}}
{{ localize "VERMINE.reliability_successes" }}: {{successes}} / {{required}} {{ localize "VERMINE.reliability_result" }}: {{resultLabel}}
{{#if fortuneNote}}

{{fortuneNote}}

{{/if}} + {{#if bienConcuNote}}

{{bienConcuNote}}

{{/if}} + {{#if pratiqueNote}}

{{pratiqueNote}}

{{/if}}