feat(traits): phase 1 — réparations (Bien conçu, Pratique, Coque réparable)
- 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
This commit is contained in:
@@ -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)
|
||||
+8
-1
@@ -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})",
|
||||
|
||||
+8
-1
@@ -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})",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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<Object|null>}
|
||||
*/
|
||||
@@ -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}`);
|
||||
// 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 = '';
|
||||
|
||||
// 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.dice ?? []).flatMap((d) => (d.results ?? []).map((r) => ({
|
||||
const results = roll
|
||||
? (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;
|
||||
})))
|
||||
: [];
|
||||
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,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -31,6 +31,12 @@
|
||||
<input type="radio" name="repairMode" value="fortune">
|
||||
<span>{{ localize "VERMINE.reliability_repair_fortune" }}</span>
|
||||
</label>
|
||||
{{#if hasBrokenCoque}}
|
||||
<label class="reliability-option">
|
||||
<input type="radio" name="repairMode" value="coque">
|
||||
<span>{{ localize "VERMINE.reliability_repair_coque" max=coqueMax }}</span>
|
||||
</label>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
@@ -6,12 +6,19 @@
|
||||
</span>
|
||||
</h3>
|
||||
<h4>
|
||||
{{#each item.system.traits as | trait index|}}
|
||||
<span data-tooltip="{{trait.description}}">{{trait.name}}
|
||||
{{#each item.system.traits as |trait key|}}
|
||||
{{#if trait}}
|
||||
{{#with (lookup ../config.traits key)}}
|
||||
<span data-tooltip="{{this.description}}">{{this.name}}
|
||||
{{#if trait.max}}
|
||||
<em class="trait-count">({{trait.value}}/{{trait.max}})</em>
|
||||
{{/if}}
|
||||
{{#if trait.value}}
|
||||
<input type="number" class="hexa" value="{{trait.value}}" name="system.traits.{{index}}.value">
|
||||
<input type="number" class="hexa" value="{{trait.value}}" name="system.traits.{{key}}.value">
|
||||
{{/if}}
|
||||
</span>
|
||||
{{/with}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
@@ -8,15 +8,22 @@
|
||||
<span class="reliability-meta-item"><strong>{{ localize "VERMINE.reliability_skill" }}:</strong> {{skillLabel}}</span>
|
||||
<span class="reliability-meta-item"><strong>{{ localize "VERMINE.difficulty" }}:</strong> {{difficulty}}</span>
|
||||
{{#if handicap}}<span class="reliability-meta-item"><strong>{{ localize "VERMINE.handicap" }}:</strong> +{{handicap}}</span>{{/if}}
|
||||
{{#if pratiqueHandicap}}<span class="reliability-meta-item"><strong>{{ localize "VERMINE.handicap" }}:</strong> +{{pratiqueHandicap}}</span>{{/if}}
|
||||
</div>
|
||||
{{#if autoSuccess}}
|
||||
<p class="reliability-note">{{ localize "VERMINE.reliability_auto_success" }}</p>
|
||||
{{else}}
|
||||
<ul class="flexrow reliability-results">
|
||||
{{#each results}}
|
||||
<li class="reliability-die {{#if this.isSuccess}}success{{/if}}">{{this.value}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
<div class="reliability-summary flexrow flex-wrap">
|
||||
<span><strong>{{ localize "VERMINE.reliability_successes" }}:</strong> {{successes}} / {{required}}</span>
|
||||
<span><strong>{{ localize "VERMINE.reliability_result" }}:</strong> {{resultLabel}}</span>
|
||||
</div>
|
||||
{{#if fortuneNote}}<p class="reliability-note">{{fortuneNote}}</p>{{/if}}
|
||||
{{#if bienConcuNote}}<p class="reliability-note">{{bienConcuNote}}</p>{{/if}}
|
||||
{{#if pratiqueNote}}<p class="reliability-note">{{pratiqueNote}}</p>{{/if}}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user