/** * Chroniques de l'Étrange — Système FoundryVTT * * Chroniques de l'Étrange 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 2024–2026 LeRatierBretonnien * @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/ */ import { ACTOR_TYPES, ITEM_TYPES, MAGICS, SUBTYPES, SYSTEM_ID } from "./config/constants.js" import { registerSettings, migrateIfNeeded, loadWelcomeSceneIfNeeded } from "./config/settings.js" import { preLocalizeConfig } from "./config/localize.js" import { configureRuntime } from "./config/runtime.js" import { CharacterDataModel, NpcDataModel } from "./data/actors/index.js" import { EquipmentDataModel, KungfuDataModel, SpellDataModel, SupernaturalDataModel, WeaponDataModel, ArmorDataModel, SanheiDataModel, IngredientDataModel } from "./data/items/index.js" import { CDEMessage } from "./documents/chat-message.js" import { CDEActor } from "./documents/actor.js" import { CDEItem } from "./documents/item.js" import { registerDice } from "./ui/dice.js" import { registerHandlebarsHelpers } from "./ui/helpers.js" import { preloadPartials } from "./ui/templates.js" import { CDECharacterSheet, CDENpcSheet } from "./ui/sheets/actors/index.js" import { CDEItemSheet, CDEKungfuSheet, CDESpellSheet, CDESupernaturalSheet, CDEWeaponSheet, CDEArmorSheet, CDESanheiSheet, CDEIngredientSheet } from "./ui/sheets/items/index.js" import { CDELoksyuApp } from "./ui/apps/loksyu-app.js" import { CDETinjiApp } from "./ui/apps/tinji-app.js" import { CDEWheelApp } from "./ui/apps/wheel-app.js" import { injectRollActions, refreshAllRollActions } from "./ui/roll-actions.js" import { CDECombat } from "./documents/combat.js" import { showWelcomeMessage, injectWelcomeActions } from "./ui/apps/welcome.js" Hooks.once("i18nInit", preLocalizeConfig) Hooks.once("init", async () => { console.info(`CHRONIQUESDELETRANGE | Initializing ${SYSTEM_ID}`) registerSettings() game.system.CONST = { MAGICS, SUBTYPES } // Expose standalone apps globally for macros game.cde = { CDELoksyuApp, CDETinjiApp, CDEWheelApp } CONFIG.Combat.documentClass = CDECombat CONFIG.Actor.dataModels = { [ACTOR_TYPES.character]: CharacterDataModel, [ACTOR_TYPES.npc]: NpcDataModel, } CONFIG.Item.dataModels = { [ITEM_TYPES.item]: EquipmentDataModel, [ITEM_TYPES.kungfu]: KungfuDataModel, [ITEM_TYPES.spell]: SpellDataModel, [ITEM_TYPES.supernatural]: SupernaturalDataModel, [ITEM_TYPES.weapon]: WeaponDataModel, [ITEM_TYPES.armor]: ArmorDataModel, [ITEM_TYPES.sanhei]: SanheiDataModel, [ITEM_TYPES.ingredient]: IngredientDataModel, } CONFIG.Actor.documentClass = CDEActor CONFIG.Item.documentClass = CDEItem CONFIG.ChatMessage.documentClass = CDEMessage configureRuntime() foundry.applications.apps.DocumentSheetConfig.unregisterSheet(Actor, "core", foundry.appv1.sheets.ActorSheet) foundry.applications.apps.DocumentSheetConfig.unregisterSheet(Item, "core", foundry.appv1.sheets.ItemSheet) foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDECharacterSheet, { types: [ACTOR_TYPES.character], makeDefault: true, label: "CDE Character Sheet (V2)", }) foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDENpcSheet, { types: [ACTOR_TYPES.npc], makeDefault: true, label: "CDE NPC Sheet (V2)", }) foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEItemSheet, { types: [ITEM_TYPES.item], makeDefault: true, label: "CDE Item Sheet (V2)", }) foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEKungfuSheet, { types: [ITEM_TYPES.kungfu], makeDefault: true, label: "CDE KungFu Sheet (V2)", }) foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESpellSheet, { types: [ITEM_TYPES.spell], makeDefault: true, label: "CDE Spell Sheet (V2)", }) foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESupernaturalSheet, { types: [ITEM_TYPES.supernatural], makeDefault: true, label: "CDE Supernatural Sheet (V2)", }) foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEWeaponSheet, { types: [ITEM_TYPES.weapon], makeDefault: true, label: "CDE Weapon Sheet (V2)", }) foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEArmorSheet, { types: [ITEM_TYPES.armor], makeDefault: true, label: "CDE Armor Sheet (V2)", }) foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESanheiSheet, { types: [ITEM_TYPES.sanhei], makeDefault: true, label: "CDE Sanhei Sheet (V2)", }) foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEIngredientSheet, { types: [ITEM_TYPES.ingredient], makeDefault: true, label: "CDE Ingredient Sheet (V2)", }) await preloadPartials() registerHandlebarsHelpers() registerDice() console.info(`CHRONIQUESDELETRANGE | Initialized`) }) Hooks.once("ready", async () => { await migrateIfNeeded() await loadWelcomeSceneIfNeeded() CDEWheelApp.registerHooks() if (game.user.isGM) showWelcomeMessage() }) /** Add Loksyu + Tin Ji quick-access buttons to the chat panel (FoundryVTT v13) */ Hooks.on("renderChatLog", (_app, html) => { const el = html instanceof HTMLElement ? html : (html[0] ?? html) if (!el?.querySelector) return // Avoid double-injection on re-renders if (el.querySelector(".cde-chat-app-buttons")) return const wrapper = document.createElement("div") wrapper.classList.add("cde-chat-app-buttons") wrapper.innerHTML = ` ` // Use event delegation to avoid being swallowed by Foundry's own handlers wrapper.addEventListener("click", (ev) => { if (ev.target.closest(".cde-chat-btn--loksyu")) CDELoksyuApp.open() if (ev.target.closest(".cde-chat-btn--tinji")) CDETinjiApp.open() if (ev.target.closest(".cde-chat-btn--wheel")) CDEWheelApp.open() }) // Insert before the chat form — works on v12 and v13 const anchor = el.querySelector(".chat-form") ?? el.querySelector(".chat-message-form") ?? el.querySelector("form") if (anchor) anchor.parentElement.insertBefore(wrapper, anchor) else el.appendChild(wrapper) }) /** Inject Loksyu / TinJi action buttons into roll-result chat messages */ Hooks.on("renderChatMessageHTML", (message, html) => { injectRollActions(message, html) if (message.flags?.[SYSTEM_ID]?.welcome) injectWelcomeActions(message, html) }) /** Refresh all visible roll-result buttons whenever Loksyu or TinJi settings change */ Hooks.on("updateSetting", setting => { if (!setting.key) return if (setting.key.includes("loksyuData") || setting.key.includes("tinjiData")) { refreshAllRollActions() } }) /** * When an actor's initiative changes (via +/- buttons on the sheet), * sync the corresponding combatant in the active combat. */ Hooks.on("updateActor", (actor, diff) => { if (!foundry.utils.hasProperty(diff, "system.initiative")) return if (!game.combat) return const initiative = actor.system.initiative const combatant = game.combat.combatants.find(c => c.actor?.id === actor.id) if (combatant && combatant.initiative !== initiative) { combatant.update({ initiative }).catch(() => {}) } }) /** * When a combatant's initiative changes (via wheel action buttons), * sync the actor's system.initiative to match. * Uses setTimeout to defer until after Foundry's update chain resolves, * avoiding concurrent #recordPreviousState errors on the combat document. */ Hooks.on("updateCombatant", (combatant, diff) => { if (!("initiative" in diff)) return const initiative = combatant.initiative if (initiative == null) return setTimeout(() => { const actor = combatant.actor if (actor && actor.system?.initiative !== initiative) { actor.update({ "system.initiative": initiative }).catch(() => {}) } }, 0) })