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/
*/
export { default as CelestopolCharacterSheet } from "./sheets/character-sheet.mjs"
export { default as CelestopolNPCSheet } from "./sheets/npc-sheet.mjs"
export { CelestopolAnomalySheet, CelestopolAspectSheet, CelestopolEquipmentSheet, CelestopolWeaponSheet, CelestopolArmureSheet } from "./sheets/item-sheets.mjs"

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"
const { HandlebarsApplicationMixin } = foundry.applications.api
@@ -46,6 +59,7 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
actor: this.document,
system: this.document.system,
source: this.document.toObject(),
isGM: game.user.isGM,
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isEditable: this.isEditable,

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/
*/
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class CelestopolItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {

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 CelestopolActorSheet from "./base-actor-sheet.mjs"
import { SYSTEM } from "../../config/system.mjs"
@@ -18,6 +31,7 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
depenseXp: CelestopolCharacterSheet.#onDepenseXp,
supprimerXpLog: CelestopolCharacterSheet.#onSupprimerXpLog,
rollMoonDie: CelestopolCharacterSheet.#onRollMoonDie,
manageFactionAspects: CelestopolCharacterSheet.#onManageFactionAspects,
},
}
@@ -58,6 +72,11 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
context.anomalyTypes = SYSTEM.ANOMALY_TYPES
context.factions = SYSTEM.FACTIONS
context.woundLevels = SYSTEM.WOUND_LEVELS
context.selectedPrimaryFactionId = game.celestopol?.normalizeFactionId(this.document.system.faction) || ""
context.legacyPrimaryFactionValue = this.document.system.faction && !context.selectedPrimaryFactionId
? `${this.document.system.faction}`.trim()
: ""
context.primaryFactionLabel = game.celestopol?.getFactionDisplayLabel(this.document.system.faction) || this.document.system.faction
return context
}
@@ -94,6 +113,18 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
case "factions":
context.tab = context.tabs.factions
context.factionAspectSummary = game.celestopol?.getFactionAspectSummary(this.document) ?? null
context.factionLegend = [
{ value: "+4", label: game.i18n.localize("CELESTOPOL.Faction.levelAllies") },
{ value: "+3", label: game.i18n.localize("CELESTOPOL.Faction.levelAmicaux") },
{ value: "+2", label: game.i18n.localize("CELESTOPOL.Faction.levelPartenaires") },
{ value: "+1", label: game.i18n.localize("CELESTOPOL.Faction.levelBienveillants") },
{ value: "0", label: game.i18n.localize("CELESTOPOL.Faction.levelNeutres") },
{ value: "-1", label: game.i18n.localize("CELESTOPOL.Faction.levelMefiants") },
{ value: "-2", label: game.i18n.localize("CELESTOPOL.Faction.levelHostiles") },
{ value: "-3", label: game.i18n.localize("CELESTOPOL.Faction.levelRivaux") },
{ value: "-4", label: game.i18n.localize("CELESTOPOL.Faction.levelEnnemis") },
]
context.factionRows = Object.entries(SYSTEM.FACTIONS).map(([id, fDef]) => {
const val = this.document.system.factions[id]?.value ?? 0
return {
@@ -177,6 +208,7 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
static async #onCreateArmure() {
await this.document.createEmbeddedDocuments("Item", [{
name: game.i18n.localize("TYPES.Item.armure"), type: "armure",
system: { protection: 1, malus: 1 },
}])
}
@@ -199,6 +231,10 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
await anomaly.update({ "system.usesRemaining": anomaly.system.level })
}
static async #onManageFactionAspects() {
await game.celestopol?.manageFactionAspects(this.document)
}
/** Ouvre un dialogue pour dépenser de l'XP. */
static async #onDepenseXp() {
const actor = this.document

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 CelestopolItemSheet from "./base-item-sheet.mjs"
import { SYSTEM } from "../../config/system.mjs"
@@ -97,4 +110,22 @@ export class CelestopolArmureSheet extends CelestopolItemSheet {
this.document.system.description, { async: true })
return ctx
}
_onRender(context, options) {
super._onRender(context, options)
const protectionInput = this.element.querySelector('[name="system.protection"]')
const malusInput = this.element.querySelector('[name="system.malus"]')
const malusValue = this.element.querySelector('[data-armure-malus-value]')
if (!protectionInput || !malusInput || !malusValue) return
const syncMalus = () => {
malusInput.value = protectionInput.value
malusValue.textContent = protectionInput.value
}
syncMalus()
protectionInput.addEventListener("input", syncMalus)
protectionInput.addEventListener("change", syncMalus)
}
}

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 CelestopolActorSheet from "./base-actor-sheet.mjs"
import { SYSTEM } from "../../config/system.mjs"
@@ -9,6 +22,7 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
window: { contentClasses: ["npc-content"] },
actions: {
createAspect: CelestopolNPCSheet.#onCreateAspect,
createEquipment: CelestopolNPCSheet.#onCreateEquipment,
createWeapon: CelestopolNPCSheet.#onCreateWeapon,
createArmure: CelestopolNPCSheet.#onCreateArmure,
rollMoonDie: CelestopolNPCSheet.#onRollMoonDie,
@@ -53,9 +67,10 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
context.antagonisteStats = SYSTEM.ANTAGONISTE_STATS
const sys = this.document.system
context.aspects = this.document.itemTypes.aspect ?? []
context.weapons = this.document.itemTypes.weapon ?? []
context.armures = this.document.itemTypes.armure ?? []
context.aspects = this.document.itemTypes.aspect ?? []
context.weapons = this.document.itemTypes.weapon.sort((a, b) => a.name.localeCompare(b.name))
context.armures = this.document.itemTypes.armure.sort((a, b) => a.name.localeCompare(b.name))
context.equipments = this.document.itemTypes.equipment.sort((a, b) => a.name.localeCompare(b.name))
context.armorMalus = sys.armorMalus ?? 0
// Label effectif de chaque domaine selon le type de PNJ
@@ -119,9 +134,16 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
}])
}
static async #onCreateEquipment() {
await this.document.createEmbeddedDocuments("Item", [{
name: game.i18n.localize("TYPES.Item.equipment"), type: "equipment",
}])
}
static async #onCreateArmure() {
await this.document.createEmbeddedDocuments("Item", [{
name: game.i18n.localize("TYPES.Item.armure"), type: "armure",
system: { protection: 1, malus: 1 },
}])
}

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/
*/
export const SYSTEM_ID = "fvtt-celestopol"
export const ASCII = `
@@ -88,6 +101,52 @@ export const FACTIONS = {
cour: { id: "cour", label: "CELESTOPOL.Faction.cour" },
}
/** Aspects de faction mobilisables au niveau du groupe. */
export const FACTION_ASPECTS = {
bonnesadresses: { id: "bonnesadresses", label: "CELESTOPOL.FactionAspect.bonnesadresses" },
contrebande: { id: "contrebande", label: "CELESTOPOL.FactionAspect.contrebande" },
corruption: { id: "corruption", label: "CELESTOPOL.FactionAspect.corruption" },
diversion: { id: "diversion", label: "CELESTOPOL.FactionAspect.diversion" },
falsification: { id: "falsification", label: "CELESTOPOL.FactionAspect.falsification" },
passedroit: { id: "passedroit", label: "CELESTOPOL.FactionAspect.passedroit" },
renforts: { id: "renforts", label: "CELESTOPOL.FactionAspect.renforts" },
renseignements: { id: "renseignements", label: "CELESTOPOL.FactionAspect.renseignements" },
ressources: { id: "ressources", label: "CELESTOPOL.FactionAspect.ressources" },
surveillance: { id: "surveillance", label: "CELESTOPOL.FactionAspect.surveillance" },
}
/** Tableau p.111 : aspects de faction disponibles selon l'organisation. */
export const FACTION_ASPECTS_BY_FACTION = {
police: [
"diversion", "passedroit", "renforts", "renseignements", "ressources", "surveillance",
],
vorovskoymir: [
"bonnesadresses", "contrebande", "corruption", "diversion", "falsification",
"renforts", "renseignements", "ressources", "surveillance",
],
okhrana: [
"corruption", "diversion", "falsification", "passedroit", "renforts",
"renseignements", "ressources",
],
oto: [
"contrebande", "corruption", "falsification", "renseignements", "surveillance",
],
syndicats: [
"bonnesadresses", "contrebande", "corruption", "falsification", "renseignements", "surveillance",
],
pinkerton: [
"bonnesadresses", "diversion", "falsification", "renforts",
"renseignements", "ressources", "surveillance",
],
cour: [
"bonnesadresses", "contrebande", "diversion", "renforts", "renseignements", "surveillance",
],
lunanovatek: [
"contrebande", "corruption", "falsification", "renforts",
"renseignements", "ressources", "surveillance",
],
}
/** Niveaux de blessures avec leur malus associé. */
export const WOUND_LEVELS = [
{ id: 0, label: "CELESTOPOL.Wound.none", malus: 0, duration: "" },
@@ -209,6 +268,8 @@ export const SYSTEM = {
ANOMALY_TYPES,
ANOMALY_DEFINITIONS,
FACTIONS,
FACTION_ASPECTS,
FACTION_ASPECTS_BY_FACTION,
NPC_TYPES,
ANTAGONISTE_STATS,
WOUND_LEVELS,

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/
*/
export { default as CelestopolActor } from "./actor.mjs"
export { default as CelestopolItem } from "./item.mjs"
export { default as CelestopolChatMessage } from "./chat-message.mjs"

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/
*/
export default class CelestopolActor extends Actor {
/** @override */
getRollData() {

View File

@@ -1 +1,14 @@
/**
* 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/
*/
export default class CelestopolChatMessage extends ChatMessage {}

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/
*/
const SYSTEM_ID = "fvtt-celestopol"
export default class CelestopolCombat extends Combat {

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/
*/
export default class CelestopolItem extends Item {
/** @override */
getRollData() {

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,

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/
*/
export { default as CelestopolCharacter } from "./character.mjs"
export { default as CelestopolNPC } from "./npc.mjs"
export { CelestopolAnomaly, CelestopolAspect, CelestopolEquipment, CelestopolWeapon, CelestopolArmure } from "./items.mjs"

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"
export default class CelestopolCharacter extends foundry.abstract.TypeDataModel {
@@ -165,8 +178,21 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
getArmorMalus() {
if (!this.parent) return 0
return -(this.parent.itemTypes.armure
.filter(a => a.system.equipped && a.system.malus > 0)
.reduce((sum, a) => sum + a.system.malus, 0))
.filter(a => a.system.equipped && (a.system.protection ?? a.system.malus) > 0)
.reduce((sum, a) => sum + (a.system.protection ?? a.system.malus), 0))
}
/**
* Retourne le malus d'armure applicable pour un jet PJ.
* Règle : uniquement sur Mobilité et Effacement si l'armure est équipée.
* @param {string} statId
* @param {string|null} skillId
* @returns {number}
*/
getArmorMalusForRoll(statId, skillId = null) {
if (statId !== "corps") return 0
if (!["mobilite", "effacement"].includes(skillId)) return 0
return this.getArmorMalus()
}
/**
@@ -198,7 +224,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
skillLabel: skill.label,
skillValue: skill.value,
woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalus(),
armorMalus: this.getArmorMalusForRoll(statId, skillId),
woundLevel: this.blessures.lvl,
difficulty: this.prefs.difficulty,
rollMoonDie: this.prefs.rollMoonDie ?? false,
@@ -229,7 +255,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
skillLabel: "CELESTOPOL.Roll.resistanceTest",
skillValue: statData.res,
woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalus(),
armorMalus: 0,
woundLevel: this.blessures.lvl,
isResistance: true,
rollMoonDie: false,
@@ -240,8 +266,8 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
}
/**
* Collecte les tokens PNJs disponibles comme cibles de combat.
* Priorise le combat tracker, sinon les tokens ciblés par l'utilisateur.
* Collecte les cibles de combat sur la scène active.
* Pour un PJ attaquant, seules les cibles PNJ présentes sur la scène sont proposées.
* @returns {Array<{id:string, name:string, corps:number}>}
*/
_getCombatTargets() {
@@ -250,25 +276,13 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
name: actor.name,
corps: actor.system.stats?.corps?.res ?? 0,
})
// Priorité 1 : PNJs dans le combat actif
if (game.combat?.active) {
const list = game.combat.combatants
.filter(c => c.actor?.type === "npc" && c.actorId !== this.parent.id)
.map(c => toEntry(c.actor))
if (list.length) return list
}
// Priorité 2 : Tokens ciblés par le joueur
const targeted = [...(game.user?.targets ?? [])]
.filter(t => t.actor?.type === "npc")
.map(t => toEntry(t.actor))
if (targeted.length) return targeted
// Priorité 3 : Tous les tokens NPC de la scène active
if (canvas?.tokens?.placeables) {
return canvas.tokens.placeables
.filter(t => t.actor?.type === "npc" && t.actor.id !== this.parent.id)
.map(t => toEntry(t.actor))
}
return []
const sceneTokens = canvas?.scene?.isView ? (canvas.tokens?.placeables ?? []) : []
return [...new Map(sceneTokens
.filter(t => t.actor?.type === "npc" && t.actor.id !== this.parent.id)
.map(t => {
const actor = t.actor
return [actor.id, toEntry(actor)]
})).values()]
}
/**
@@ -296,7 +310,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
skillLabel: SYSTEM.SKILLS.corps.echauffouree.label,
skillValue: echauffouree.value,
woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalus(),
armorMalus: this.getArmorMalusForRoll("corps", "echauffouree"),
woundLevel: this.blessures.lvl,
rollMoonDie: this.prefs.rollMoonDie ?? false,
destGaugeFull: this.destin.lvl > 0,
@@ -334,7 +348,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
skillLabel: SYSTEM.SKILLS.corps.mobilite.label,
skillValue: mobilite.value,
woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalus(),
armorMalus: this.getArmorMalusForRoll("corps", "mobilite"),
woundLevel: this.blessures.lvl,
rollMoonDie: this.prefs.rollMoonDie ?? false,
destGaugeFull: this.destin.lvl > 0,

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"
/** Schéma partagé pour les bonus/malus par domaine (utilisé dans anomaly/aspect). */
@@ -83,9 +96,14 @@ export class CelestopolArmure extends foundry.abstract.TypeDataModel {
const reqInt = { required: true, nullable: false, integer: true }
return {
protection: new fields.NumberField({ ...reqInt, initial: 1, min: 1, max: 2 }),
malus: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 2 }),
malus: new fields.NumberField({ ...reqInt, initial: 1, min: 0, max: 2 }),
equipped: new fields.BooleanField({ initial: false }),
description: new fields.HTMLField({ required: true, textSearch: true }),
}
}
prepareDerivedData() {
super.prepareDerivedData()
this.malus = this.protection
}
}

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"
export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
@@ -66,7 +79,20 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
const armures = this.parent?.itemTypes?.armure ?? []
return armures
.filter(a => a.system.equipped)
.reduce((sum, a) => sum + (a.system.malus ? -Math.abs(a.system.malus) : 0), 0)
.reduce((sum, a) => {
const value = a.system.protection ?? a.system.malus
return sum + (value ? -Math.abs(value) : 0)
}, 0)
}
/**
* Retourne le malus d'armure applicable pour un jet PNJ.
* Règle : sur tous les jets de Corps uniquement.
* @param {string} statId
* @returns {number}
*/
getArmorMalusForRoll(statId) {
return statId === "corps" ? this.getArmorMalus() : 0
}
/**
@@ -91,7 +117,7 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
skillLabel,
skillValue: statData.res,
woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalus(),
armorMalus: this.getArmorMalusForRoll(statId),
woundLevel: this.blessures.lvl,
})
}