feat: tests de résistance (2d8+TR, auto-blessure sur échec)
- rollResistance(statId) dans character.mjs : formule 2d8 + bonus TR + malus blessures - Dialog sans Modificateur/Aspect/Lune/Destin/Fortune/Puiser en mode résistance - Auto-cochage de la prochaine case de blessure sur échec - Chat message : notification blessure cochée (woundTaken) - Stat-res cliquable (rollable) en mode jeu dans l'onglet compétences - base-actor-sheet : routing clic stat-res → rollResistance - CSS : .resistance-wound-notice - i18n : resistanceTest, resistanceClickToRoll, woundTaken Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -163,7 +163,10 @@
|
|||||||
"puiser": "Puiser dans ses ressources",
|
"puiser": "Puiser dans ses ressources",
|
||||||
"puiserDesc": "Ignore tous les malus — coche 1 case de Spleen",
|
"puiserDesc": "Ignore tous les malus — coche 1 case de Spleen",
|
||||||
"usedPuiser": "Ressources puisées — malus ignorés, +1 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": {
|
"Modifier": {
|
||||||
"evident": "Évident — Réussite automatique",
|
"evident": "Évident — Réussite automatique",
|
||||||
|
|||||||
@@ -76,7 +76,12 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
|
|||||||
const el = event.currentTarget
|
const el = event.currentTarget
|
||||||
const statId = el.dataset.statId
|
const statId = el.dataset.statId
|
||||||
const skillId = el.dataset.skillId
|
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)
|
await this.document.system.roll(statId, skillId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
const woundLevelId = options.woundLevel ?? 0
|
const woundLevelId = options.woundLevel ?? 0
|
||||||
const destGaugeFull = options.destGaugeFull ?? false
|
const destGaugeFull = options.destGaugeFull ?? false
|
||||||
const fortuneValue = options.fortuneValue ?? 0
|
const fortuneValue = options.fortuneValue ?? 0
|
||||||
|
const isResistance = options.isResistance ?? false
|
||||||
const woundLabel = woundLevelId > 0
|
const woundLabel = woundLevelId > 0
|
||||||
? game.i18n.localize(SYSTEM.WOUND_LEVELS[woundLevelId]?.label ?? "")
|
? game.i18n.localize(SYSTEM.WOUND_LEVELS[woundLevelId]?.label ?? "")
|
||||||
: null
|
: null
|
||||||
@@ -64,6 +65,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
defaultDifficulty: options.difficulty ?? "normal",
|
defaultDifficulty: options.difficulty ?? "normal",
|
||||||
destGaugeFull,
|
destGaugeFull,
|
||||||
defaultRollMoonDie: options.rollMoonDie ?? false,
|
defaultRollMoonDie: options.rollMoonDie ?? false,
|
||||||
|
isResistance,
|
||||||
modifierChoices,
|
modifierChoices,
|
||||||
aspectChoices,
|
aspectChoices,
|
||||||
fortuneValue,
|
fortuneValue,
|
||||||
@@ -100,10 +102,10 @@ export class CelestopolRoll extends Roll {
|
|||||||
const puiser = wrap.querySelector('#puiserRessources')?.checked
|
const puiser = wrap.querySelector('#puiserRessources')?.checked
|
||||||
const ndice = useDestin ? 3 : 2
|
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')
|
const puiserRow = wrap.querySelector('#puiser-row')
|
||||||
if (puiserRow) {
|
if (puiserRow) {
|
||||||
if (hasMalus(modifier, aspectMod)) {
|
if (!isResistance && hasMalus(modifier, aspectMod)) {
|
||||||
puiserRow.style.display = ''
|
puiserRow.style.display = ''
|
||||||
} else {
|
} else {
|
||||||
puiserRow.style.display = 'none'
|
puiserRow.style.display = 'none'
|
||||||
@@ -169,23 +171,27 @@ export class CelestopolRoll extends Roll {
|
|||||||
const puiserRessources = rollContext.puiserRessources === true || rollContext.puiserRessources === "true"
|
const puiserRessources = rollContext.puiserRessources === true || rollContext.puiserRessources === "true"
|
||||||
const rollMoonDie = rollContext.rollMoonDie === true || rollContext.rollMoonDie === "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
|
// Puiser dans ses ressources → ignorer tous les malus
|
||||||
const effectiveWoundMalus = puiserRessources ? 0 : woundMalus
|
const effectiveWoundMalus = effectivePuiser ? 0 : woundMalus
|
||||||
const effectiveModifier = puiserRessources ? Math.max(0, modifier) : modifier
|
const effectiveModifier = effectivePuiser ? Math.max(0, modifier) : modifier
|
||||||
const effectiveAspectMod = puiserRessources ? Math.max(0, aspectMod) : aspectMod
|
const effectiveAspectMod = effectivePuiser ? 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 = (!isResistance && useDestin) ? 3 : 2
|
||||||
const totalModifier = skillValue + effectiveWoundMalus + effectiveAspectMod + effectiveModifier
|
const totalModifier = skillValue + effectiveWoundMalus + effectiveAspectMod + effectiveModifier
|
||||||
const formula = useFortune
|
const formula = (!isResistance && useFortune)
|
||||||
? buildFormula(1, totalModifier + 8)
|
? buildFormula(1, totalModifier + 8)
|
||||||
: buildFormula(nbDice, totalModifier)
|
: 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 moonDieResult = null
|
||||||
let moonFace = null
|
let moonFace = null
|
||||||
let moonResultType = null
|
let moonResultType = null
|
||||||
if (rollMoonDie) {
|
if (effectiveMoon) {
|
||||||
const moonRoll = await new Roll("1d8").evaluate()
|
const moonRoll = await new Roll("1d8").evaluate()
|
||||||
moonDieResult = moonRoll.total
|
moonDieResult = moonRoll.total
|
||||||
moonFace = SYSTEM.MOON_DIE_FACES[moonDieResult] ?? null
|
moonFace = SYSTEM.MOON_DIE_FACES[moonDieResult] ?? null
|
||||||
@@ -200,13 +206,14 @@ export class CelestopolRoll extends Roll {
|
|||||||
aspectMod: effectiveAspectMod,
|
aspectMod: effectiveAspectMod,
|
||||||
woundMalus: effectiveWoundMalus,
|
woundMalus: effectiveWoundMalus,
|
||||||
autoSuccess,
|
autoSuccess,
|
||||||
useDestin,
|
isResistance,
|
||||||
useFortune,
|
useDestin: !isResistance && useDestin,
|
||||||
puiserRessources,
|
useFortune: !isResistance && useFortune,
|
||||||
nbDice: useFortune ? 1 : nbDice,
|
puiserRessources: effectivePuiser,
|
||||||
|
nbDice: (!isResistance && useFortune) ? 1 : nbDice,
|
||||||
formula,
|
formula,
|
||||||
rollMode: rollContext.visibility ?? "publicroll",
|
rollMode: rollContext.visibility ?? "publicroll",
|
||||||
rollMoonDie,
|
rollMoonDie: effectiveMoon,
|
||||||
moonDieResult,
|
moonDieResult,
|
||||||
moonFace,
|
moonFace,
|
||||||
moonResultType,
|
moonResultType,
|
||||||
@@ -215,11 +222,22 @@ export class CelestopolRoll extends Roll {
|
|||||||
const roll = new this(formula, {}, rollData)
|
const roll = new this(formula, {}, rollData)
|
||||||
await roll.evaluate()
|
await roll.evaluate()
|
||||||
roll.computeResult()
|
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 })
|
await roll.toMessage({}, { rollMode: rollData.rollMode })
|
||||||
|
|
||||||
// Destin utilisé → vider la jauge (reset à 0)
|
// Destin utilisé → vider la jauge (reset à 0)
|
||||||
const actor = game.actors.get(options.actorId)
|
if (rollData.useDestin && actor) {
|
||||||
if (useDestin && actor) {
|
|
||||||
await actor.update({
|
await actor.update({
|
||||||
"system.destin.lvl": 0,
|
"system.destin.lvl": 0,
|
||||||
"system.destin.d1.checked": false,
|
"system.destin.d1.checked": false,
|
||||||
@@ -234,13 +252,13 @@ export class CelestopolRoll extends Roll {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fortune utilisée → décrémenter de 1 (min 0)
|
// 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
|
const currentFortune = actor.system.attributs.fortune.value ?? 0
|
||||||
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
|
// Puiser dans ses ressources → coche une case de spleen
|
||||||
if (puiserRessources && actor) {
|
if (rollData.puiserRessources && actor) {
|
||||||
const currentSpleen = actor.system.spleen.lvl ?? 0
|
const currentSpleen = actor.system.spleen.lvl ?? 0
|
||||||
if (currentSpleen < 8) {
|
if (currentSpleen < 8) {
|
||||||
const newLvl = currentSpleen + 1
|
const newLvl = currentSpleen + 1
|
||||||
@@ -255,7 +273,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
// 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({
|
||||||
"system.prefs.rollMoonDie": rollMoonDie,
|
"system.prefs.rollMoonDie": rollData.rollMoonDie,
|
||||||
"system.prefs.difficulty": difficulty,
|
"system.prefs.difficulty": difficulty,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -357,6 +375,8 @@ export class CelestopolRoll extends Roll {
|
|||||||
nbDice: this.options.nbDice ?? diceResults.length,
|
nbDice: this.options.nbDice ?? diceResults.length,
|
||||||
woundMalus,
|
woundMalus,
|
||||||
woundLabel,
|
woundLabel,
|
||||||
|
isResistance: this.options.isResistance ?? false,
|
||||||
|
woundTaken: this.options.woundTaken ?? null,
|
||||||
// Dé de lune
|
// Dé de lune
|
||||||
hasMoonDie: moonDieResult !== null,
|
hasMoonDie: moonDieResult !== null,
|
||||||
moonDieResult,
|
moonDieResult,
|
||||||
|
|||||||
@@ -226,4 +226,31 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
fortuneValue: this.attributs.fortune.value,
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
<div class="stat-block">
|
<div class="stat-block">
|
||||||
<div class="stat-header">
|
<div class="stat-header">
|
||||||
<span class="stat-name">{{localize stat.label}}</span>
|
<span class="stat-name">{{localize stat.label}}</span>
|
||||||
<div class="stat-res">
|
<div class="stat-res {{#unless ../isEditMode}}rollable{{/unless}}" data-stat-id="{{statId}}"
|
||||||
|
title="{{localize 'CELESTOPOL.Roll.resistanceClickToRoll'}}">
|
||||||
<label>{{localize "CELESTOPOL.Stat.res"}}</label>
|
<label>{{localize "CELESTOPOL.Stat.res"}}</label>
|
||||||
<span class="stat-res-value">{{lookup (lookup ../system.stats statId) 'res'}}</span>
|
<span class="stat-res-value">{{lookup (lookup ../system.stats statId) 'res'}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -123,5 +123,12 @@
|
|||||||
<span class="result-label">{{localize "CELESTOPOL.Roll.failure"}}</span>
|
<span class="result-label">{{localize "CELESTOPOL.Roll.failure"}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
{{!-- Blessure auto-cochée (test de résistance raté) --}}
|
||||||
|
{{#if woundTaken}}
|
||||||
|
<div class="resistance-wound-notice">
|
||||||
|
<span class="wound-icon">🩹</span>
|
||||||
|
<span>{{localize "CELESTOPOL.Roll.woundTaken"}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{!-- Options non disponibles en test de résistance --}}
|
||||||
|
{{#unless isResistance}}
|
||||||
|
|
||||||
{{!-- Modificateur & Aspect côte à côte --}}
|
{{!-- Modificateur & Aspect côte à côte --}}
|
||||||
<div class="form-two-col">
|
<div class="form-two-col">
|
||||||
<div class="form-row-line">
|
<div class="form-row-line">
|
||||||
@@ -110,6 +113,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{/unless}}{{!-- /isResistance --}}
|
||||||
|
|
||||||
{{!-- Visibilité --}}
|
{{!-- Visibilité --}}
|
||||||
<div class="form-row-line form-visibility">
|
<div class="form-row-line form-visibility">
|
||||||
<label for="visibility">{{localize "CELESTOPOL.Roll.visibility"}}</label>
|
<label for="visibility">{{localize "CELESTOPOL.Roll.visibility"}}</label>
|
||||||
|
|||||||
Reference in New Issue
Block a user