feat: implémentation complète du système Célestopol 1922 pour FoundryVTT v13
- DataModels (character, npc, anomaly, aspect, attribute, equipment) - ApplicationV2 sheets (character 5 tabs, npc 3 tabs, 4 item sheets) - DialogV2 pour les jets de dés avec phase de lune - Templates Handlebars complets (fiches PJ/PNJ, items, jet, chat) - Styles LESS → CSS compilé (thème vert foncé / orange CopaseticNF) - i18n fr.json complet (clés CELESTOPOL.*) - Point d'entrée fvtt-celestopol.mjs avec hooks init/ready - Assets : polices CopaseticNF, images UI, icônes items - Mise à jour copilot-instructions.md avec l'architecture réelle Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
4
module/documents/_module.mjs
Normal file
4
module/documents/_module.mjs
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as CelestopolActor } from "./actor.mjs"
|
||||
export { default as CelestopolItem } from "./item.mjs"
|
||||
export { default as CelestopolChatMessage } from "./chat-message.mjs"
|
||||
export { CelestopolRoll } from "./roll.mjs"
|
||||
12
module/documents/actor.mjs
Normal file
12
module/documents/actor.mjs
Normal file
@@ -0,0 +1,12 @@
|
||||
export default class CelestopolActor extends Actor {
|
||||
/** @override */
|
||||
prepareDerivedData() {
|
||||
super.prepareDerivedData()
|
||||
this.system.prepareDerivedData?.()
|
||||
}
|
||||
|
||||
/** @override */
|
||||
getRollData() {
|
||||
return this.toObject(false).system
|
||||
}
|
||||
}
|
||||
7
module/documents/chat-message.mjs
Normal file
7
module/documents/chat-message.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
export default class CelestopolChatMessage extends ChatMessage {
|
||||
/** @override */
|
||||
async getHTML() {
|
||||
const html = await super.getHTML()
|
||||
return html
|
||||
}
|
||||
}
|
||||
11
module/documents/item.mjs
Normal file
11
module/documents/item.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
export default class CelestopolItem extends Item {
|
||||
/** @override */
|
||||
prepareDerivedData() {
|
||||
super.prepareDerivedData()
|
||||
}
|
||||
|
||||
/** @override */
|
||||
getRollData() {
|
||||
return this.toObject(false).system
|
||||
}
|
||||
}
|
||||
170
module/documents/roll.mjs
Normal file
170
module/documents/roll.mjs
Normal file
@@ -0,0 +1,170 @@
|
||||
import { SYSTEM } from "../config/system.mjs"
|
||||
|
||||
/**
|
||||
* Système de dés de Célestopol 1922.
|
||||
*
|
||||
* Le jet de base est : (valeur compétence)d6 comparé à un seuil de difficulté.
|
||||
* Le dé de lune ajoute un bonus selon la phase actuelle.
|
||||
* Destin et Spleen modifient le nombre de dés.
|
||||
*/
|
||||
export class CelestopolRoll extends Roll {
|
||||
static CHAT_TEMPLATE = "systems/fvtt-celestopol/templates/chat-message.hbs"
|
||||
|
||||
get resultType() { return this.options.resultType }
|
||||
get isSuccess() { return this.resultType === "success" }
|
||||
get isFailure() { return this.resultType === "failure" }
|
||||
get actorId() { return this.options.actorId }
|
||||
get actorName() { return this.options.actorName }
|
||||
get actorImage() { return this.options.actorImage }
|
||||
get skillLabel() { return this.options.skillLabel }
|
||||
get difficulty() { return this.options.difficulty }
|
||||
get moonBonus() { return this.options.moonBonus ?? 0 }
|
||||
|
||||
/**
|
||||
* Ouvre le dialogue de configuration du jet via DialogV2 et exécute le jet.
|
||||
* @param {object} options
|
||||
* @returns {Promise<CelestopolRoll|null>}
|
||||
*/
|
||||
static async prompt(options = {}) {
|
||||
const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes)
|
||||
const fieldRollMode = new foundry.data.fields.StringField({
|
||||
choices: rollModes,
|
||||
blank: false,
|
||||
default: "publicroll",
|
||||
})
|
||||
|
||||
const dialogContext = {
|
||||
actorName: options.actorName,
|
||||
skillLabel: options.skillLabel,
|
||||
skillValue: options.skillValue,
|
||||
woundMalus: options.woundMalus ?? 0,
|
||||
difficultyChoices:SYSTEM.DIFFICULTY_CHOICES,
|
||||
moonPhaseChoices: SYSTEM.MOON_DICE_PHASES,
|
||||
defaultDifficulty:options.difficulty ?? "normal",
|
||||
defaultMoonPhase: options.moonPhase ?? "none",
|
||||
rollModes,
|
||||
fieldRollMode,
|
||||
}
|
||||
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-celestopol/templates/roll-dialog.hbs",
|
||||
dialogContext
|
||||
)
|
||||
|
||||
const title = `${game.i18n.localize("CELESTOPOL.Roll.title")} — ${game.i18n.localize(options.skillLabel ?? "")}`
|
||||
|
||||
const rollContext = await foundry.applications.api.DialogV2.wait({
|
||||
window: { title },
|
||||
classes: ["fvtt-celestopol", "roll-dialog"],
|
||||
content,
|
||||
buttons: [
|
||||
{
|
||||
label: game.i18n.localize("CELESTOPOL.Roll.roll"),
|
||||
callback: (event, button) => {
|
||||
return Array.from(button.form.elements).reduce((obj, input) => {
|
||||
if (input.name) obj[input.name] = input.value
|
||||
return obj
|
||||
}, {})
|
||||
},
|
||||
},
|
||||
],
|
||||
rejectClose: false,
|
||||
})
|
||||
|
||||
if (!rollContext) return null
|
||||
|
||||
const difficulty = rollContext.difficulty ?? "normal"
|
||||
const diffConfig = SYSTEM.DIFFICULTY_CHOICES[difficulty] ?? SYSTEM.DIFFICULTY_CHOICES.normal
|
||||
const moonPhase = rollContext.moonPhase ?? "none"
|
||||
const moonConfig = SYSTEM.MOON_DICE_PHASES[moonPhase] ?? SYSTEM.MOON_DICE_PHASES.none
|
||||
const modifier = parseInt(rollContext.modifier ?? 0) || 0
|
||||
const woundMalus = options.woundMalus ?? 0
|
||||
const skillValue = Math.max(0, (options.skillValue ?? 0) + woundMalus)
|
||||
const nbDice = Math.max(1, skillValue)
|
||||
const moonBonus = moonConfig.bonus ?? 0
|
||||
const totalModifier = moonBonus + modifier
|
||||
|
||||
const formula = totalModifier !== 0
|
||||
? `${nbDice}d6 + ${totalModifier}`
|
||||
: `${nbDice}d6`
|
||||
|
||||
const rollData = {
|
||||
...options,
|
||||
difficulty,
|
||||
difficultyValue: diffConfig.value,
|
||||
moonPhase,
|
||||
moonBonus,
|
||||
modifier,
|
||||
formula,
|
||||
rollMode: rollContext.visibility ?? "publicroll",
|
||||
}
|
||||
|
||||
const roll = new this(formula, {}, rollData)
|
||||
await roll.evaluate()
|
||||
roll.computeResult()
|
||||
await roll.toMessage({}, { rollMode: rollData.rollMode })
|
||||
|
||||
// Mémoriser les préférences sur l'acteur
|
||||
const actor = game.actors.get(options.actorId)
|
||||
if (actor) {
|
||||
await actor.update({
|
||||
"system.prefs.moonPhase": moonPhase,
|
||||
"system.prefs.difficulty": difficulty,
|
||||
})
|
||||
}
|
||||
|
||||
return roll
|
||||
}
|
||||
|
||||
/** Détermine succès/échec selon le total vs le seuil. */
|
||||
computeResult() {
|
||||
const threshold = SYSTEM.DIFFICULTY_CHOICES[this.options.difficulty]?.value ?? 0
|
||||
if (threshold === 0) {
|
||||
this.options.resultType = "unknown"
|
||||
} else if (this.total >= threshold) {
|
||||
this.options.resultType = "success"
|
||||
} else {
|
||||
this.options.resultType = "failure"
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async render(chatOptions = {}) {
|
||||
const data = await this._getChatCardData(chatOptions.isPrivate)
|
||||
return foundry.applications.handlebars.renderTemplate(this.constructor.CHAT_TEMPLATE, data)
|
||||
}
|
||||
|
||||
async _getChatCardData(isPrivate) {
|
||||
return {
|
||||
css: [SYSTEM.id, "dice-roll"],
|
||||
cssClass: [SYSTEM.id, "dice-roll"].join(" "),
|
||||
actorId: this.actorId,
|
||||
actingCharName: this.actorName,
|
||||
actingCharImg: this.actorImage,
|
||||
skillLabel: this.skillLabel,
|
||||
formula: this.formula,
|
||||
total: this.total,
|
||||
resultType: this.resultType,
|
||||
isSuccess: this.isSuccess,
|
||||
isFailure: this.isFailure,
|
||||
difficulty: this.options.difficulty,
|
||||
difficultyValue:this.options.difficultyValue,
|
||||
moonPhase: this.options.moonPhase,
|
||||
moonBonus: this.moonBonus,
|
||||
isPrivate,
|
||||
tooltip: isPrivate ? "" : await this.getTooltip(),
|
||||
results: this.dice[0]?.results ?? [],
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async toMessage(messageData = {}, { rollMode, create = true } = {}) {
|
||||
return super.toMessage(
|
||||
{
|
||||
flavor: `<strong>${game.i18n.localize(this.skillLabel ?? "")}</strong>`,
|
||||
...messageData,
|
||||
},
|
||||
{ rollMode }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user