Files
fvtt-celestopol/fvtt-celestopol.mjs
LeRatierBretonnier f9ddcdf9da Refonte complète du système Anomalies
- DataModel : renommage value→level (1-4), ajout usesRemaining (0-4), suppression scores/notes
- Config : ajout ANOMALY_DEFINITIONS avec compétences applicables par type (8 anomalies)
- Fiche item anomalie : header avec level/uses visuels (dots), barre de compétences applicables,
  2 onglets Description + Technique/Narratif (suppression onglet Scores)
- Fiche PJ onglet Domaines : bloc anomalie proéminent unique avec:
  - Nom + sous-type + icône
  - Dots niveau (●●○○)
  - Dots usages + bouton Utiliser + bouton Réinitialiser
  - Chips des domaines applicables
- Actions : useAnomaly (décrémente usesRemaining), resetAnomalyUses (reset au niveau)
- Contrainte : max 1 anomalie par personnage (drop + createAnomaly)
- Helpers HBS : lte, gte, lt ajoutés
- i18n : nouvelles clés Anomaly.* (level, usesRemaining, use, resetUses, etc.)
- CSS : .anomaly-block sur fiche PJ, dots animés, .anomaly-uses-row sur fiche item

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 18:15:06 +01:00

230 lines
8.6 KiB
JavaScript

/**
* fvtt-celestopol.mjs — Point d'entrée principal du système Célestopol 1922
* FoundryVTT v13+ / DataModels / ApplicationV2
*/
import { SYSTEM, SYSTEM_ID, ASCII } from "./module/config/system.mjs"
import {
CelestopolCharacter,
CelestopolNPC,
CelestopolAnomaly,
CelestopolAspect,
CelestopolAttribute,
CelestopolEquipment,
} from "./module/models/_module.mjs"
import {
CelestopolActor,
CelestopolItem,
CelestopolChatMessage,
CelestopolRoll,
} from "./module/documents/_module.mjs"
import {
CelestopolCharacterSheet,
CelestopolNPCSheet,
CelestopolAnomalySheet,
CelestopolAspectSheet,
CelestopolAttributeSheet,
CelestopolEquipmentSheet,
} from "./module/applications/_module.mjs"
/* ─── Init hook ──────────────────────────────────────────────────────────── */
Hooks.once("init", () => {
console.log(ASCII)
console.log(`${SYSTEM_ID} | Initializing Célestopol 1922 system`)
// Expose SYSTEM constants in game.system namespace
game.celestopol = { SYSTEM }
// ── DataModels ──────────────────────────────────────────────────────────
CONFIG.Actor.dataModels.character = CelestopolCharacter
CONFIG.Actor.dataModels.npc = CelestopolNPC
CONFIG.Item.dataModels.anomaly = CelestopolAnomaly
CONFIG.Item.dataModels.aspect = CelestopolAspect
CONFIG.Item.dataModels.attribute = CelestopolAttribute
CONFIG.Item.dataModels.equipment = CelestopolEquipment
// ── Document classes ────────────────────────────────────────────────────
CONFIG.Actor.documentClass = CelestopolActor
CONFIG.Item.documentClass = CelestopolItem
CONFIG.ChatMessage.documentClass = CelestopolChatMessage
CONFIG.Dice.rolls.push(CelestopolRoll)
// ── Token display defaults ───────────────────────────────────────────────
CONFIG.Actor.trackableAttributes = {
character: {
bar: ["blessures.lvl"],
value: ["initiative", "anomaly.level"],
},
npc: {
bar: ["blessures.lvl"],
value: ["initiative"],
},
}
// ── Sheets: unregister core, register system sheets ─────────────────────
foundry.applications.sheets.ActorSheetV2.unregisterSheet?.("core", "Actor", { types: ["character", "npc"] })
foundry.documents.collections.Actors.unregisterSheet("core", ActorSheet)
foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, CelestopolCharacterSheet, {
types: ["character"],
makeDefault: true,
label: "CELESTOPOL.Sheet.character",
})
foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, CelestopolNPCSheet, {
types: ["npc"],
makeDefault: true,
label: "CELESTOPOL.Sheet.npc",
})
foundry.documents.collections.Items.unregisterSheet("core", ItemSheet)
foundry.documents.collections.Items.registerSheet(SYSTEM_ID, CelestopolAnomalySheet, {
types: ["anomaly"],
makeDefault: true,
label: "CELESTOPOL.Sheet.anomaly",
})
foundry.documents.collections.Items.registerSheet(SYSTEM_ID, CelestopolAspectSheet, {
types: ["aspect"],
makeDefault: true,
label: "CELESTOPOL.Sheet.aspect",
})
foundry.documents.collections.Items.registerSheet(SYSTEM_ID, CelestopolAttributeSheet, {
types: ["attribute"],
makeDefault: true,
label: "CELESTOPOL.Sheet.attribute",
})
foundry.documents.collections.Items.registerSheet(SYSTEM_ID, CelestopolEquipmentSheet, {
types: ["equipment"],
makeDefault: true,
label: "CELESTOPOL.Sheet.equipment",
})
// ── Handlebars helpers ───────────────────────────────────────────────────
_registerHandlebarsHelpers()
// ── Game settings ────────────────────────────────────────────────────────
_registerSettings()
// ── Pre-load templates ───────────────────────────────────────────────────
_preloadTemplates()
})
/* ─── Ready hook ─────────────────────────────────────────────────────────── */
Hooks.once("ready", () => {
console.log(`${SYSTEM_ID} | System ready`)
// Socket handler for GM-only operations (e.g. wound application)
if (game.socket) {
game.socket.on(`system.${SYSTEM_ID}`, _onSocketMessage)
}
})
/* ─── Handlebars helpers ─────────────────────────────────────────────────── */
function _registerHandlebarsHelpers() {
// Helper : concat strings
Handlebars.registerHelper("concat", (...args) => args.slice(0, -1).join(""))
// Helper : strict equality
Handlebars.registerHelper("eq", (a, b) => a === b)
// Helper : greater than
Handlebars.registerHelper("gt", (a, b) => a > b)
// Helper : less than or equal
Handlebars.registerHelper("lte", (a, b) => a <= b)
// Helper : greater than or equal
Handlebars.registerHelper("gte", (a, b) => a >= b)
// Helper : less than
Handlebars.registerHelper("lt", (a, b) => a < b)
// Helper : logical OR
Handlebars.registerHelper("or", (...args) => args.slice(0, -1).some(Boolean))
// Helper : build array from args (Handlebars doesn't have arrays natively)
Handlebars.registerHelper("array", (...args) => args.slice(0, -1))
// Helper : nested object lookup with dot path or multiple keys
Handlebars.registerHelper("lookup", (obj, ...args) => {
const options = args.pop() // last arg is Handlebars options hash
return args.reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), obj)
})
// Helper : negate a number (abs value helper)
Handlebars.registerHelper("neg", n => -n)
// Helper : absolute value
Handlebars.registerHelper("abs", n => Math.abs(n))
// Helper : add two numbers
Handlebars.registerHelper("add", (a, b) => a + b)
Handlebars.registerHelper("let", function(value, options) {
return options.fn({ value })
})
}
/* ─── Settings ───────────────────────────────────────────────────────────── */
function _registerSettings() {
game.settings.register(SYSTEM_ID, "defaultMoonPhase", {
name: "CELESTOPOL.Setting.defaultMoonPhase.name",
hint: "CELESTOPOL.Setting.defaultMoonPhase.hint",
scope: "world",
config: true,
type: String,
default: "nouvelleLune",
choices: Object.fromEntries(
Object.entries(SYSTEM.MOON_DICE_PHASES).map(([k, v]) => [k, v.label])
),
})
game.settings.register(SYSTEM_ID, "autoWounds", {
name: "CELESTOPOL.Setting.autoWounds.name",
hint: "CELESTOPOL.Setting.autoWounds.hint",
scope: "world",
config: true,
type: Boolean,
default: false,
})
}
/* ─── Template preload ───────────────────────────────────────────────────── */
function _preloadTemplates() {
const base = `systems/${SYSTEM_ID}/templates`
loadTemplates([
`${base}/character-main.hbs`,
`${base}/character-competences.hbs`,
`${base}/character-blessures.hbs`,
`${base}/character-factions.hbs`,
`${base}/character-biography.hbs`,
`${base}/npc-main.hbs`,
`${base}/npc-competences.hbs`,
`${base}/npc-blessures.hbs`,
`${base}/anomaly.hbs`,
`${base}/aspect.hbs`,
`${base}/attribute.hbs`,
`${base}/equipment.hbs`,
`${base}/roll-dialog.hbs`,
`${base}/chat-message.hbs`,
`${base}/partials/item-scores.hbs`,
])
}
/* ─── Socket handler ─────────────────────────────────────────────────────── */
function _onSocketMessage(data) {
if (!game.user.isGM) return
switch (data.type) {
case "applyWound": {
const actor = game.actors.get(data.actorId)
if (actor) actor.update({ "system.blessures.lvl": data.level })
break
}
}
}