diff --git a/lang/fr.json b/lang/fr.json index 00da20c..65373fe 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -163,7 +163,10 @@ "puiser": "Puiser dans ses ressources", "puiserDesc": "Ignore tous les malus — coche 1 case de Spleen", "usedPuiser": "Ressources puisées — malus ignorés, +1 Spleen", - "autoSuccess": "Réussite automatique" + "autoSuccess": "Réussite automatique", + "resistanceTest": "Test de résistance", + "resistanceClickToRoll": "Lancer un test de résistance", + "woundTaken": "Blessure cochée suite à l'échec" }, "Modifier": { "evident": "Évident — Réussite automatique", diff --git a/module/applications/sheets/base-actor-sheet.mjs b/module/applications/sheets/base-actor-sheet.mjs index 57f807c..e1fdd39 100644 --- a/module/applications/sheets/base-actor-sheet.mjs +++ b/module/applications/sheets/base-actor-sheet.mjs @@ -76,7 +76,12 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou const el = event.currentTarget const statId = el.dataset.statId const skillId = el.dataset.skillId - if (!statId || !skillId) return + if (!statId) return + if (!skillId) { + // Test de résistance (clic sur la zone TR de la stat) + await this.document.system.rollResistance(statId) + return + } await this.document.system.roll(statId, skillId) } diff --git a/module/documents/roll.mjs b/module/documents/roll.mjs index 210b9c2..158b6ca 100644 --- a/module/documents/roll.mjs +++ b/module/documents/roll.mjs @@ -39,6 +39,7 @@ export class CelestopolRoll extends Roll { const woundLevelId = options.woundLevel ?? 0 const destGaugeFull = options.destGaugeFull ?? false const fortuneValue = options.fortuneValue ?? 0 + const isResistance = options.isResistance ?? false const woundLabel = woundLevelId > 0 ? game.i18n.localize(SYSTEM.WOUND_LEVELS[woundLevelId]?.label ?? "") : null @@ -64,6 +65,7 @@ export class CelestopolRoll extends Roll { defaultDifficulty: options.difficulty ?? "normal", destGaugeFull, defaultRollMoonDie: options.rollMoonDie ?? false, + isResistance, modifierChoices, aspectChoices, fortuneValue, @@ -100,10 +102,10 @@ export class CelestopolRoll extends Roll { const puiser = wrap.querySelector('#puiserRessources')?.checked const ndice = useDestin ? 3 : 2 - // Afficher/masquer le bloc "Puiser" selon les malus actifs + // En résistance : pas de "Puiser" possible const puiserRow = wrap.querySelector('#puiser-row') if (puiserRow) { - if (hasMalus(modifier, aspectMod)) { + if (!isResistance && hasMalus(modifier, aspectMod)) { puiserRow.style.display = '' } else { puiserRow.style.display = 'none' @@ -169,23 +171,27 @@ export class CelestopolRoll extends Roll { const puiserRessources = rollContext.puiserRessources === true || rollContext.puiserRessources === "true" const rollMoonDie = rollContext.rollMoonDie === true || rollContext.rollMoonDie === "true" + // En résistance : forcer puiser=false, lune=false, fortune=false, destin=false + const effectivePuiser = isResistance ? false : puiserRessources + const effectiveMoon = isResistance ? false : rollMoonDie + // Puiser dans ses ressources → ignorer tous les malus - const effectiveWoundMalus = puiserRessources ? 0 : woundMalus - const effectiveModifier = puiserRessources ? Math.max(0, modifier) : modifier - const effectiveAspectMod = puiserRessources ? Math.max(0, aspectMod) : aspectMod + const effectiveWoundMalus = effectivePuiser ? 0 : woundMalus + const effectiveModifier = effectivePuiser ? Math.max(0, modifier) : modifier + const effectiveAspectMod = effectivePuiser ? Math.max(0, aspectMod) : aspectMod // Fortune : 1d8 + 8 ; Destin : 3d8 ; sinon : 2d8 - const nbDice = useDestin ? 3 : 2 + const nbDice = (!isResistance && useDestin) ? 3 : 2 const totalModifier = skillValue + effectiveWoundMalus + effectiveAspectMod + effectiveModifier - const formula = useFortune + const formula = (!isResistance && useFortune) ? buildFormula(1, totalModifier + 8) : buildFormula(nbDice, totalModifier) - // Jet du dé de lune séparé (narratif) + // Jet du dé de lune séparé (narratif) — pas en résistance let moonDieResult = null let moonFace = null let moonResultType = null - if (rollMoonDie) { + if (effectiveMoon) { const moonRoll = await new Roll("1d8").evaluate() moonDieResult = moonRoll.total moonFace = SYSTEM.MOON_DIE_FACES[moonDieResult] ?? null @@ -200,13 +206,14 @@ export class CelestopolRoll extends Roll { aspectMod: effectiveAspectMod, woundMalus: effectiveWoundMalus, autoSuccess, - useDestin, - useFortune, - puiserRessources, - nbDice: useFortune ? 1 : nbDice, + isResistance, + useDestin: !isResistance && useDestin, + useFortune: !isResistance && useFortune, + puiserRessources: effectivePuiser, + nbDice: (!isResistance && useFortune) ? 1 : nbDice, formula, rollMode: rollContext.visibility ?? "publicroll", - rollMoonDie, + rollMoonDie: effectiveMoon, moonDieResult, moonFace, moonResultType, @@ -215,11 +222,22 @@ export class CelestopolRoll extends Roll { const roll = new this(formula, {}, rollData) await roll.evaluate() roll.computeResult() + + // Test de résistance échoué → cocher automatiquement la prochaine case de blessure + const actor = game.actors.get(options.actorId) + if (isResistance && actor && roll.options.resultType === "failure") { + const wounds = actor.system.blessures + const nextIdx = [1,2,3,4,5,6,7,8].find(i => !wounds[`b${i}`]?.checked) + if (nextIdx) { + await actor.update({ [`system.blessures.b${nextIdx}.checked`]: true }) + roll.options.woundTaken = nextIdx + } + } + await roll.toMessage({}, { rollMode: rollData.rollMode }) // Destin utilisé → vider la jauge (reset à 0) - const actor = game.actors.get(options.actorId) - if (useDestin && actor) { + if (rollData.useDestin && actor) { await actor.update({ "system.destin.lvl": 0, "system.destin.d1.checked": false, @@ -234,13 +252,13 @@ export class CelestopolRoll extends Roll { } // Fortune utilisée → décrémenter de 1 (min 0) - if (useFortune && actor) { + if (rollData.useFortune && actor) { const currentFortune = actor.system.attributs.fortune.value ?? 0 await actor.update({ "system.attributs.fortune.value": Math.max(0, currentFortune - 1) }) } // Puiser dans ses ressources → coche une case de spleen - if (puiserRessources && actor) { + if (rollData.puiserRessources && actor) { const currentSpleen = actor.system.spleen.lvl ?? 0 if (currentSpleen < 8) { const newLvl = currentSpleen + 1 @@ -255,7 +273,7 @@ export class CelestopolRoll extends Roll { // Mémoriser les préférences sur l'acteur if (actor) { await actor.update({ - "system.prefs.rollMoonDie": rollMoonDie, + "system.prefs.rollMoonDie": rollData.rollMoonDie, "system.prefs.difficulty": difficulty, }) } @@ -357,6 +375,8 @@ export class CelestopolRoll extends Roll { nbDice: this.options.nbDice ?? diceResults.length, woundMalus, woundLabel, + isResistance: this.options.isResistance ?? false, + woundTaken: this.options.woundTaken ?? null, // Dé de lune hasMoonDie: moonDieResult !== null, moonDieResult, diff --git a/module/models/character.mjs b/module/models/character.mjs index d0cc21a..290dcd9 100644 --- a/module/models/character.mjs +++ b/module/models/character.mjs @@ -226,4 +226,31 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel fortuneValue: this.attributs.fortune.value, }) } + + /** + * Lance un test de résistance pour une stat donnée. + * Formule : 2d8 + resBonus + woundMalus + * Pas de lune, Puiser, Fortune ou Destin. + * Échec → blessure automatique. + * @param {string} statId - Id de la stat (ame, corps, coeur, esprit) + */ + async rollResistance(statId) { + const { CelestopolRoll } = await import("../documents/roll.mjs") + const statData = this.stats[statId] + if (!statData) return null + + return CelestopolRoll.prompt({ + actorId: this.parent.id, + actorName: this.parent.name, + actorImage: this.parent.img, + statId, + statLabel: SYSTEM.STATS[statId]?.label, + skillLabel: "CELESTOPOL.Roll.resistanceTest", + skillValue: statData.res ?? 0, + woundMalus: this.getWoundMalus(), + woundLevel: this.blessures.lvl, + difficulty: this.prefs.difficulty, + isResistance: true, + }) + } } diff --git a/styles/roll.less b/styles/roll.less index 1f51f57..ad3faba 100644 --- a/styles/roll.less +++ b/styles/roll.less @@ -782,3 +782,20 @@ } } + +// Notification de blessure cochée lors d'un test de résistance raté +.celestopol.chat-roll { + .resistance-wound-notice { + display: flex; + align-items: center; + gap: 0.4em; + margin-top: 0.4em; + padding: 0.4em 0.8em; + background: #4a1520; + border-left: 3px solid #c0392b; + border-radius: 4px; + color: #f0c0c0; + font-size: 0.85em; + .wound-icon { font-size: 1em; } + } +} diff --git a/templates/character-competences.hbs b/templates/character-competences.hbs index cc12886..0027e26 100644 --- a/templates/character-competences.hbs +++ b/templates/character-competences.hbs @@ -5,7 +5,8 @@
{{localize stat.label}} -
+
{{lookup (lookup ../system.stats statId) 'res'}}
diff --git a/templates/chat-message.hbs b/templates/chat-message.hbs index 1d8c9ce..85b12b6 100644 --- a/templates/chat-message.hbs +++ b/templates/chat-message.hbs @@ -123,5 +123,12 @@ {{localize "CELESTOPOL.Roll.failure"}} {{/if}}
+ {{!-- Blessure auto-cochée (test de résistance raté) --}} + {{#if woundTaken}} +
+ 🩹 + {{localize "CELESTOPOL.Roll.woundTaken"}} +
+ {{/if}}
diff --git a/templates/roll-dialog.hbs b/templates/roll-dialog.hbs index f5541ce..86826a0 100644 --- a/templates/roll-dialog.hbs +++ b/templates/roll-dialog.hbs @@ -36,6 +36,9 @@
+ {{!-- Options non disponibles en test de résistance --}} + {{#unless isResistance}} + {{!-- Modificateur & Aspect côte à côte --}}
@@ -110,6 +113,8 @@
{{/if}} + {{/unless}}{{!-- /isResistance --}} + {{!-- Visibilité --}}