Corrections sur factions, aspects, degats et fiches PNJs
This commit is contained in:
@@ -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 2025–2026 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,
|
||||
|
||||
Reference in New Issue
Block a user