- Ajout clés i18n CELESTOPOL.Wound.* (anodin, dérisoire, négligeable…) → corrige l'affichage brut des clés dans la fenêtre de jet - Suppression du setting 'autoWounds' (mort-né, le malus est toujours appliqué) - Le malus de blessures est inclus automatiquement dans tous les jets via getWoundMalus() → prepareDerivedData() → blessures.lvl Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
272 lines
10 KiB
JavaScript
272 lines
10 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,
|
|
CelestopolEquipment,
|
|
CelestopolWeapon,
|
|
CelestopolArmure,
|
|
} from "./module/models/_module.mjs"
|
|
import {
|
|
CelestopolActor,
|
|
CelestopolItem,
|
|
CelestopolChatMessage,
|
|
CelestopolRoll,
|
|
} from "./module/documents/_module.mjs"
|
|
import {
|
|
CelestopolCharacterSheet,
|
|
CelestopolNPCSheet,
|
|
CelestopolAnomalySheet,
|
|
CelestopolAspectSheet,
|
|
CelestopolEquipmentSheet,
|
|
CelestopolWeaponSheet,
|
|
CelestopolArmureSheet,
|
|
} 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.equipment = CelestopolEquipment
|
|
CONFIG.Item.dataModels.weapon = CelestopolWeapon
|
|
CONFIG.Item.dataModels.armure = CelestopolArmure
|
|
|
|
// ── 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", foundry.appv1.sheets.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", foundry.appv1.sheets.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, CelestopolEquipmentSheet, {
|
|
types: ["equipment"],
|
|
makeDefault: true,
|
|
label: "CELESTOPOL.Sheet.equipment",
|
|
})
|
|
foundry.documents.collections.Items.registerSheet(SYSTEM_ID, CelestopolWeaponSheet, {
|
|
types: ["weapon"],
|
|
makeDefault: true,
|
|
label: "CELESTOPOL.Sheet.weapon",
|
|
})
|
|
foundry.documents.collections.Items.registerSheet(SYSTEM_ID, CelestopolArmureSheet, {
|
|
types: ["armure"],
|
|
makeDefault: true,
|
|
label: "CELESTOPOL.Sheet.armure",
|
|
})
|
|
|
|
// ── 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)
|
|
}
|
|
|
|
// Migration : supprime les items de types obsolètes (ex: "attribute")
|
|
if (game.user.isGM) {
|
|
_migrateObsoleteItems()
|
|
}
|
|
})
|
|
|
|
/** Supprime les items dont le type n'est plus reconnu par le système. */
|
|
async function _migrateObsoleteItems() {
|
|
const validTypes = new Set(["anomaly", "aspect", "equipment", "weapon", "armure"])
|
|
for (const actor of game.actors) {
|
|
// Utilise _source.items pour trouver les items qui n'ont pas pu s'initialiser
|
|
const toDelete = (actor._source?.items ?? [])
|
|
.filter(i => !validTypes.has(i.type))
|
|
.map(i => i._id)
|
|
if (toDelete.length) {
|
|
console.warn(`${SYSTEM_ID} | Migration: suppression de ${toDelete.length} item(s) obsolète(s) sur ${actor.name}`, toDelete)
|
|
await actor.deleteEmbeddedDocuments("Item", toDelete)
|
|
}
|
|
}
|
|
// Items globaux (hors acteur)
|
|
const globalToDelete = game.items.contents
|
|
.filter(i => !validTypes.has(i.type))
|
|
.map(i => i.id)
|
|
if (globalToDelete.length) {
|
|
console.warn(`${SYSTEM_ID} | Migration: suppression de ${globalToDelete.length} item(s) global(aux) obsolète(s)`, globalToDelete)
|
|
await Item.deleteDocuments(globalToDelete)
|
|
}
|
|
}
|
|
|
|
/* ─── 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, "rollMoonDieByDefault", {
|
|
name: "CELESTOPOL.Setting.rollMoonDieByDefault.name",
|
|
hint: "CELESTOPOL.Setting.rollMoonDieByDefault.hint",
|
|
scope: "world",
|
|
config: true,
|
|
type: Boolean,
|
|
default: false,
|
|
})
|
|
}
|
|
|
|
/* ─── Template preload ───────────────────────────────────────────────────── */
|
|
|
|
function _preloadTemplates() {
|
|
const base = `systems/${SYSTEM_ID}/templates`
|
|
foundry.applications.handlebars.loadTemplates([
|
|
`${base}/character-main.hbs`,
|
|
`${base}/character-competences.hbs`,
|
|
`${base}/character-blessures.hbs`,
|
|
`${base}/character-factions.hbs`,
|
|
`${base}/character-equipement.hbs`,
|
|
`${base}/character-biography.hbs`,
|
|
`${base}/npc-main.hbs`,
|
|
`${base}/npc-competences.hbs`,
|
|
`${base}/npc-blessures.hbs`,
|
|
`${base}/anomaly.hbs`,
|
|
`${base}/aspect.hbs`,
|
|
`${base}/equipment.hbs`,
|
|
`${base}/weapon.hbs`,
|
|
`${base}/armure.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
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ─── Nom par défaut des items à la création ─────────────────────────────── */
|
|
|
|
Hooks.on("preCreateItem", (item, data) => {
|
|
const defaultNames = {
|
|
weapon: () => game.i18n.localize("TYPES.Item.weapon"),
|
|
armure: () => game.i18n.localize("TYPES.Item.armure"),
|
|
anomaly: () => game.i18n.localize("TYPES.Item.anomaly"),
|
|
aspect: () => game.i18n.localize("TYPES.Item.aspect"),
|
|
equipment: () => game.i18n.localize("TYPES.Item.equipment"),
|
|
}
|
|
const fn = defaultNames[item.type]
|
|
if (fn && (!data.name || data.name === "New Item" || data.name === item.type)) {
|
|
item.updateSource({ name: fn() })
|
|
}
|
|
})
|