Corrections sur factions, aspects, degats et fiches PNJs

This commit is contained in:
2026-04-11 15:02:46 +02:00
parent 36516c3b08
commit 3358dea306
44 changed files with 2308 additions and 148 deletions

View File

@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
import { SYSTEM } from "../config/system.mjs"
/** Construit la formule de jet à partir du nombre de dés et du modificateur total. */
@@ -29,6 +42,42 @@ export class CelestopolRoll extends Roll {
get skillLabel() { return this.options.skillLabel }
get difficulty() { return this.options.difficulty }
/**
* Convertit le niveau de dégâts d'une arme en nombre de blessures de base.
* Règle : une attaque réussie inflige toujours 1 blessure, plus le bonus de dégâts.
* @param {string|number|null} weaponDegats
* @returns {number|null}
*/
static getIncomingWounds(weaponDegats) {
const raw = `${weaponDegats ?? "0"}`
const bonus = Number.parseInt(raw, 10)
if (!Number.isFinite(bonus)) return null
return Math.max(0, 1 + bonus)
}
/**
* Retourne la protection totale de l'armure équipée pour un acteur.
* @param {Actor|null} actor
* @returns {number}
*/
static getActorArmorProtection(actor) {
if (!actor) return 0
if (typeof actor.system?.getArmorMalus === "function") {
return Math.abs(actor.system.getArmorMalus())
}
const derivedArmorMalus = actor.system?.armorMalus
if (Number.isFinite(derivedArmorMalus)) {
return Math.abs(derivedArmorMalus)
}
const armures = actor.itemTypes?.armure ?? []
return armures
.filter(a => a.system.equipped)
.reduce((sum, a) => sum + Math.abs(a.system.protection ?? a.system.malus ?? 0), 0)
}
/**
* Ouvre le dialogue de configuration du jet via DialogV2 et exécute le jet.
* @param {object} options
@@ -71,6 +120,8 @@ export class CelestopolRoll extends Roll {
value: m.value,
label: game.i18n.localize(m.label),
}))
const factionAspectChoices = game.celestopol?.getFactionAspectSummary(options.actorId ? game.actors.get(options.actorId) : null)
?.availableAspectChoices ?? []
const dialogContext = {
actorName: options.actorName,
@@ -89,6 +140,7 @@ export class CelestopolRoll extends Roll {
aspectChoices,
situationChoices,
rangedModChoices,
factionAspectChoices,
availableTargets,
fortuneValue,
armorMalus,
@@ -123,7 +175,7 @@ export class CelestopolRoll extends Roll {
function applyTargetSelection() {
if (!targetSelect) return
const selectedOption = targetSelect.options[targetSelect.selectedIndex]
const val = parseFloat(targetSelect.value)
const val = parseFloat(selectedOption?.dataset.corps ?? "")
const corpsPnjInput = wrap.querySelector('#corpsPnj')
if (targetSelect.value && !isNaN(val)) {
// Cible sélectionnée : masquer la valeur, afficher le nom
@@ -155,6 +207,8 @@ export class CelestopolRoll extends Roll {
const autoSucc = rawMod === "auto"
const modifier = autoSucc ? 0 : (parseInt(rawMod ?? 0) || 0)
const aspectMod = parseInt(wrap.querySelector('#aspectModifier')?.value ?? 0) || 0
const selectedFactionAspect = wrap.querySelector('#factionAspectId')?.selectedOptions?.[0]
const factionAspectBonus = parseInt(selectedFactionAspect?.dataset.value ?? 0) || 0
const situMod = parseInt(wrap.querySelector('#situationMod')?.value ?? 0) || 0
const rangedMod = parseInt(wrap.querySelector('#rangedMod')?.value ?? 0) || 0
const useDestin = wrap.querySelector('#useDestin')?.checked
@@ -180,7 +234,7 @@ export class CelestopolRoll extends Roll {
const effSit = puiser ? Math.max(0, situMod) : situMod
const effArmor = puiser ? 0 : armorMalus
const effRanged = puiser ? Math.max(0, rangedMod) : rangedMod
const totalMod = skillValue + effWound + effMod + effAspect + effSit + effArmor + effRanged
const totalMod = skillValue + effWound + effMod + effAspect + factionAspectBonus + effSit + effArmor + effRanged
let formula
if (autoSucc) {
@@ -198,7 +252,7 @@ export class CelestopolRoll extends Roll {
if (previewEl) previewEl.textContent = formula
}
wrap.querySelectorAll('#modifier, #aspectModifier, #situationMod, #rangedMod, #useDestin, #useFortune, #puiserRessources, #corpsPnj')
wrap.querySelectorAll('#modifier, #aspectModifier, #factionAspectId, #situationMod, #rangedMod, #useDestin, #useFortune, #puiserRessources, #corpsPnj')
.forEach(el => {
el.addEventListener('change', update)
el.addEventListener('input', update)
@@ -233,13 +287,23 @@ export class CelestopolRoll extends Roll {
const autoSuccess = rollContext.modifier === "auto"
const modifier = autoSuccess ? 0 : (parseInt(rollContext.modifier ?? 0) || 0)
const aspectMod = parseInt(rollContext.aspectModifier ?? 0) || 0
const factionAspectId = typeof rollContext.factionAspectId === "string" ? rollContext.factionAspectId : ""
const selectedFactionAspect = factionAspectChoices.find(choice => choice.id === factionAspectId) ?? null
const factionAspectBonus = selectedFactionAspect?.value ?? 0
const factionAspectLabel = selectedFactionAspect?.label ?? ""
const situationMod = parseInt(rollContext.situationMod ?? 0) || 0
const rangedMod = isRangedAttack ? (parseInt(rollContext.rangedMod ?? 0) || 0) : 0
const isOpposition = !isCombat && !isResistance && (rollContext.isOpposition === true || rollContext.isOpposition === "true")
const isOpposition = !isCombat && (rollContext.isOpposition === true || rollContext.isOpposition === "true")
const useDestin = destGaugeFull && (rollContext.useDestin === true || rollContext.useDestin === "true")
const useFortune = fortuneValue > 0 && (rollContext.useFortune === true || rollContext.useFortune === "true")
const puiserRessources = rollContext.puiserRessources === true || rollContext.puiserRessources === "true"
const rollMoonDie = rollContext.rollMoonDie === true || rollContext.rollMoonDie === "true"
const selectedCombatTargetId = typeof rollContext.targetSelect === "string" ? rollContext.targetSelect : ""
const selectedCombatTarget = selectedCombatTargetId
? availableTargets.find(t => t.id === selectedCombatTargetId) ?? null
: null
const targetActorId = selectedCombatTarget?.id || ""
const targetActorName = selectedCombatTarget?.name || ""
// En résistance : forcer puiser=false, lune=false, fortune=false, destin=false
const effectivePuiser = isResistance ? false : puiserRessources
@@ -255,7 +319,7 @@ export class CelestopolRoll extends Roll {
// Fortune : 1d8 + 8 ; Destin : 3d8 ; sinon : 2d8
const nbDice = (!isResistance && useDestin) ? 3 : 2
const totalModifier = skillValue + effectiveWoundMalus + effectiveAspectMod + effectiveModifier + effectiveSituationMod + effectiveArmorMalus + effectiveRangedMod
const totalModifier = skillValue + effectiveWoundMalus + effectiveAspectMod + effectiveModifier + factionAspectBonus + effectiveSituationMod + effectiveArmorMalus + effectiveRangedMod
const formula = (!isResistance && useFortune)
? buildFormula(1, totalModifier + 8)
: buildFormula(nbDice, totalModifier)
@@ -277,6 +341,9 @@ export class CelestopolRoll extends Roll {
difficultyValue: diffConfig.value,
modifier: effectiveModifier,
aspectMod: effectiveAspectMod,
factionAspectId,
factionAspectLabel,
factionAspectBonus,
situationMod: effectiveSituationMod,
woundMalus: effectiveWoundMalus,
autoSuccess,
@@ -287,6 +354,9 @@ export class CelestopolRoll extends Roll {
weaponType,
weaponName,
weaponDegats,
targetActorId,
targetActorName,
availableTargets,
rangedMod: effectiveRangedMod,
useDestin: !isResistance && useDestin,
useFortune: !isResistance && useFortune,
@@ -410,8 +480,10 @@ export class CelestopolRoll extends Roll {
: 11
const margin = this.options.margin
const woundMalus = this.options.woundMalus ?? 0
const armorMalus = this.options.armorMalus ?? 0
const skillValue = this.options.skillValue ?? 0
const woundLevelId = this.options.woundLevel ?? 0
const weaponDegats = `${this.options.weaponDegats ?? "0"}`
const woundLabel = woundLevelId > 0
? game.i18n.localize(SYSTEM.WOUND_LEVELS[woundLevelId]?.label ?? "")
: null
@@ -430,6 +502,22 @@ export class CelestopolRoll extends Roll {
}
const isOpposition = this.options.isOpposition ?? false
const isWeaponHit = (this.options.isCombat ?? false) && !(this.options.isRangedDefense ?? false) && this.isSuccess
const incomingWounds = isWeaponHit ? this.constructor.getIncomingWounds(weaponDegats) : null
const hasVariableDamage = isWeaponHit && incomingWounds === null
const targetActorId = this.options.targetActorId ?? ""
const targetActorName = this.options.targetActorName ?? ""
const availableTargets = (this.options.availableTargets ?? []).map(target => ({
...target,
selected: target.id === targetActorId,
}))
const selectedTargetActor = targetActorId ? game.actors.get(targetActorId) : null
const selectedTargetProtection = selectedTargetActor
? this.constructor.getActorArmorProtection(selectedTargetActor)
: null
const selectedTargetAppliedWounds = (incomingWounds !== null && selectedTargetActor)
? Math.max(0, incomingWounds - selectedTargetProtection)
: null
// Libellé de difficulté : en combat "Corps PNJ : N", en opposition "vs ?", sinon "Seuil : 11"
const difficultyLabel = this.options.isCombat
@@ -464,22 +552,35 @@ export class CelestopolRoll extends Roll {
modifier: this.options.modifier ?? 0,
autoSuccess: this.options.autoSuccess ?? false,
aspectMod: this.options.aspectMod ?? 0,
factionAspectLabel: this.options.factionAspectLabel ?? "",
factionAspectBonus: this.options.factionAspectBonus ?? 0,
skillValue,
useDestin: this.options.useDestin ?? false,
useFortune: this.options.useFortune ?? false,
puiserRessources: this.options.puiserRessources ?? false,
nbDice: this.options.nbDice ?? diceResults.length,
woundMalus,
armorMalus,
woundLabel,
isResistance: this.options.isResistance ?? false,
isCombat: this.options.isCombat ?? false,
weaponName: this.options.weaponName ?? null,
weaponDegats: this.options.weaponDegats ?? null,
weaponDegats,
weaponType: this.options.weaponType ?? null,
isRangedDefense: this.options.isRangedDefense ?? false,
woundTaken: this.options.woundTaken ?? null,
situationMod: this.options.situationMod ?? 0,
rangedMod: this.options.rangedMod ?? 0,
hasDamageSummary: isWeaponHit,
incomingWounds,
incomingWoundsDisplay: incomingWounds ?? "1 + X",
hasVariableDamage,
canApplyWeaponDamage: incomingWounds !== null,
targetActorId,
targetActorName,
selectedTargetProtection,
selectedTargetAppliedWounds,
availableTargets,
// Dé de lune
hasMoonDie: moonDieResult !== null,
moonDieResult,