feat: implémente 'Puiser dans ses ressources'

Règle : le joueur peut ignorer tous les malus actifs contre une case de Spleen.

- roll.mjs : checkbox puiserRessources ; ignore woundMalus, modifier négatif
  et aspectMod négatif ; incrémente system.spleen.lvl +1 après le jet
- roll-dialog.hbs : bloc rouge foncé visible si woundMalus < 0 ; preview mis à jour
- chat-message.hbs : bandeau '💪 Ressources puisées' si utilisé
- roll.less : .form-puiser-row, .used-info.used-puiser
- lang/fr.json : Roll.puiser/puiserDesc/usedPuiser

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-03-29 20:38:55 +02:00
parent c1c85762c5
commit 63eb7f703a
5 changed files with 148 additions and 18 deletions

View File

@@ -148,7 +148,10 @@
"fortune": "Utiliser la Fortune", "fortune": "Utiliser la Fortune",
"fortuneBonus": "1d8 + 8 au lieu de 2d8", "fortuneBonus": "1d8 + 8 au lieu de 2d8",
"fortuneFixed": "Bonus fixe Fortune", "fortuneFixed": "Bonus fixe Fortune",
"usedFortune": "Fortune utilisée (1d8+8)" "usedFortune": "Fortune utilisée (1d8+8)",
"puiser": "Puiser dans ses ressources",
"puiserDesc": "Ignore tous les malus — coche 1 case de Spleen",
"usedPuiser": "Ressources puisées — malus ignorés, +1 Spleen"
}, },
"Moon": { "Moon": {
"none": "Aucune phase", "none": "Aucune phase",

View File

@@ -55,6 +55,7 @@ export class CelestopolRoll extends Roll {
skillValue, skillValue,
woundMalus, woundMalus,
woundLabel, woundLabel,
hasMalus: woundMalus < 0,
difficultyChoices: SYSTEM.DIFFICULTY_CHOICES, difficultyChoices: SYSTEM.DIFFICULTY_CHOICES,
defaultDifficulty: options.difficulty ?? "normal", defaultDifficulty: options.difficulty ?? "normal",
destGaugeFull, destGaugeFull,
@@ -95,17 +96,23 @@ export class CelestopolRoll extends Roll {
if (!rollContext) return null if (!rollContext) return null
const difficulty = rollContext.difficulty ?? "normal" const difficulty = rollContext.difficulty ?? "normal"
const diffConfig = SYSTEM.DIFFICULTY_CHOICES[difficulty] ?? SYSTEM.DIFFICULTY_CHOICES.normal const diffConfig = SYSTEM.DIFFICULTY_CHOICES[difficulty] ?? SYSTEM.DIFFICULTY_CHOICES.normal
const modifier = parseInt(rollContext.modifier ?? 0) || 0 const modifier = parseInt(rollContext.modifier ?? 0) || 0
const aspectMod = parseInt(rollContext.aspectModifier ?? 0) || 0 const aspectMod = parseInt(rollContext.aspectModifier ?? 0) || 0
const useDestin = destGaugeFull && (rollContext.useDestin === true || rollContext.useDestin === "true") const useDestin = destGaugeFull && (rollContext.useDestin === true || rollContext.useDestin === "true")
const useFortune = fortuneValue > 0 && (rollContext.useFortune === true || rollContext.useFortune === "true") const useFortune = fortuneValue > 0 && (rollContext.useFortune === true || rollContext.useFortune === "true")
const rollMoonDie = rollContext.rollMoonDie === true || rollContext.rollMoonDie === "true" const puiserRessources = rollContext.puiserRessources === true || rollContext.puiserRessources === "true"
const rollMoonDie = rollContext.rollMoonDie === true || rollContext.rollMoonDie === "true"
// 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
// Fortune : 1d8 + 8 ; Destin : 3d8 ; sinon : 2d8 // Fortune : 1d8 + 8 ; Destin : 3d8 ; sinon : 2d8
const nbDice = useDestin ? 3 : 2 const nbDice = useDestin ? 3 : 2
const totalModifier = skillValue + woundMalus + aspectMod + modifier const totalModifier = skillValue + effectiveWoundMalus + effectiveAspectMod + effectiveModifier
const formula = useFortune const formula = useFortune
? buildFormula(1, totalModifier + 8) ? buildFormula(1, totalModifier + 8)
: buildFormula(nbDice, totalModifier) : buildFormula(nbDice, totalModifier)
@@ -125,10 +132,12 @@ export class CelestopolRoll extends Roll {
...options, ...options,
difficulty, difficulty,
difficultyValue: diffConfig.value, difficultyValue: diffConfig.value,
modifier, modifier: effectiveModifier,
aspectMod, aspectMod: effectiveAspectMod,
woundMalus: effectiveWoundMalus,
useDestin, useDestin,
useFortune, useFortune,
puiserRessources,
nbDice: useFortune ? 1 : nbDice, nbDice: useFortune ? 1 : nbDice,
formula, formula,
rollMode: rollContext.visibility ?? "publicroll", rollMode: rollContext.visibility ?? "publicroll",
@@ -165,6 +174,19 @@ export class CelestopolRoll extends Roll {
await actor.update({ "system.attributs.fortune.value": Math.max(0, currentFortune - 1) }) await actor.update({ "system.attributs.fortune.value": Math.max(0, currentFortune - 1) })
} }
// Puiser dans ses ressources → coche une case de spleen
if (puiserRessources && actor) {
const currentSpleen = actor.system.spleen.lvl ?? 0
if (currentSpleen < 8) {
const newLvl = currentSpleen + 1
const key = `s${newLvl}`
await actor.update({
"system.spleen.lvl": newLvl,
[`system.spleen.${key}.checked`]: true,
})
}
}
// Mémoriser les préférences sur l'acteur // Mémoriser les préférences sur l'acteur
if (actor) { if (actor) {
await actor.update({ await actor.update({
@@ -260,6 +282,7 @@ export class CelestopolRoll extends Roll {
skillValue, skillValue,
useDestin: this.options.useDestin ?? false, useDestin: this.options.useDestin ?? false,
useFortune: this.options.useFortune ?? false, useFortune: this.options.useFortune ?? false,
puiserRessources: this.options.puiserRessources ?? false,
nbDice: this.options.nbDice ?? diceResults.length, nbDice: this.options.nbDice ?? diceResults.length,
woundMalus, woundMalus,
woundLabel, woundLabel,

View File

@@ -285,6 +285,78 @@
.form-visibility label { color: #888; } .form-visibility label { color: #888; }
// ── Ligne Puiser dans ses ressources ──
.form-puiser-row {
border: 1px solid rgba(139,62,72,0.4);
border-radius: 4px;
background: rgba(107,30,40,0.06);
padding: 7px 10px;
.puiser-toggle {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
width: 100%;
input[type="checkbox"] {
width: 16px;
height: 16px;
flex-shrink: 0;
appearance: none;
-webkit-appearance: none;
border: 2px solid rgba(139,62,72,0.6);
border-radius: 2px;
background: white;
cursor: pointer;
position: relative;
&:checked {
background: var(--cel-accent, #6b1e28);
border-color: var(--cel-accent, #6b1e28);
&::after {
content: "✓";
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75em;
color: white;
}
}
}
.puiser-icon { font-size: 1em; flex-shrink: 0; }
.puiser-text {
flex: 1;
.puiser-main {
font-family: var(--cel-font-title, "CopaseticNF", serif);
font-size: 0.9em;
color: var(--cel-accent, #6b1e28);
display: block;
}
.puiser-sub {
font-size: 0.7em;
color: #888;
font-style: italic;
}
}
.puiser-cost {
font-size: 0.8em;
font-weight: bold;
color: var(--cel-accent, #6b1e28);
background: rgba(107,30,40,0.1);
border: 1px solid rgba(139,62,72,0.35);
border-radius: 10px;
padding: 1px 8px;
white-space: nowrap;
}
}
}
// ── Ligne Fortune ── // ── Ligne Fortune ──
.form-fortune-row { .form-fortune-row {
border: 1px solid rgba(12,76,12,0.4); border: 1px solid rgba(12,76,12,0.4);
@@ -567,6 +639,13 @@
.used-fortune { font-weight: bold; color: var(--cel-green, #0c4c0c); } .used-fortune { font-weight: bold; color: var(--cel-green, #0c4c0c); }
} }
.used-info.used-puiser {
color: var(--cel-accent, #6b1e28);
background: rgba(107,30,40,0.08);
border-top-color: rgba(139,62,72,0.3);
font-style: italic;
}
// ── Fortune fixe badge dans zone dés ── // ── Fortune fixe badge dans zone dés ──
.fortune-fixed-badge { .fortune-fixed-badge {
display: inline-flex; display: inline-flex;

View File

@@ -84,6 +84,11 @@
<span class="used-fortune">⚜ {{localize "CELESTOPOL.Roll.usedFortune"}}</span> <span class="used-fortune">⚜ {{localize "CELESTOPOL.Roll.usedFortune"}}</span>
</div> </div>
{{/if}} {{/if}}
{{#if puiserRessources}}
<div class="used-info used-puiser">
<span>💪 {{localize "CELESTOPOL.Roll.usedPuiser"}}</span>
</div>
{{/if}}
{{!-- Résultat du Dé de la Lune (narratif) --}} {{!-- Résultat du Dé de la Lune (narratif) --}}
{{#if hasMoonDie}} {{#if hasMoonDie}}

View File

@@ -82,6 +82,21 @@
</label> </label>
</div> </div>
{{!-- Puiser dans ses ressources — si malus de blessures --}}
{{#if hasMalus}}
<div class="form-puiser-row">
<label class="puiser-toggle" for="puiserRessources">
<input type="checkbox" id="puiserRessources" name="puiserRessources">
<span class="puiser-icon">💪</span>
<span class="puiser-text">
<span class="puiser-main">{{localize "CELESTOPOL.Roll.puiser"}}</span>
<span class="puiser-sub">{{localize "CELESTOPOL.Roll.puiserDesc"}}</span>
</span>
<span class="puiser-cost">+1 <i class="fas fa-face-sad-tear"></i></span>
</label>
</div>
{{/if}}
{{!-- Fortune (1d8+8) — seulement si Fortune > 0 --}} {{!-- Fortune (1d8+8) — seulement si Fortune > 0 --}}
{{#if fortuneValue}} {{#if fortuneValue}}
<div class="form-fortune-row"> <div class="form-fortune-row">
@@ -126,12 +141,17 @@
const woundMalus = {{woundMalus}}; const woundMalus = {{woundMalus}};
function update() { function update() {
const modifier = parseInt(wrap.querySelector('#modifier')?.value ?? 0) || 0; const modifier = parseInt(wrap.querySelector('#modifier')?.value ?? 0) || 0;
const aspectMod = parseInt(wrap.querySelector('#aspectModifier')?.value ?? 0) || 0; const aspectMod = parseInt(wrap.querySelector('#aspectModifier')?.value ?? 0) || 0;
const useDestin = wrap.querySelector('#useDestin')?.checked; const useDestin = wrap.querySelector('#useDestin')?.checked;
const useFortune= wrap.querySelector('#useFortune')?.checked; const useFortune = wrap.querySelector('#useFortune')?.checked;
const ndice = useDestin ? 3 : 2; const puiser = wrap.querySelector('#puiserRessources')?.checked;
const totalMod = skillVal + woundMalus + modifier + aspectMod; const ndice = useDestin ? 3 : 2;
const effWound = puiser ? 0 : woundMalus;
const effMod = puiser ? Math.max(0, modifier) : modifier;
const effAspect = puiser ? Math.max(0, aspectMod) : aspectMod;
const totalMod = skillVal + effWound + effMod + effAspect;
let formula; let formula;
if (useFortune) { if (useFortune) {
@@ -147,7 +167,7 @@
if (previewEl) previewEl.textContent = formula; if (previewEl) previewEl.textContent = formula;
} }
wrap.querySelectorAll('#modifier, #aspectModifier, #useDestin, #useFortune').forEach(el => { wrap.querySelectorAll('#modifier, #aspectModifier, #useDestin, #useFortune, #puiserRessources').forEach(el => {
el.addEventListener('change', update); el.addEventListener('change', update);
el.addEventListener('input', update); el.addEventListener('input', update);
}); });