@@ -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 ,