Files
fvtt-chroniques-de-l-etrange/src/system.js
T
uberwald 9617005a5c
Release Creation / build (release) Successful in 1m24s
Finalisation du système
2026-05-06 22:37:40 +02:00

221 lines
8.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 20242026 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 = `
<button type="button" class="cde-chat-btn cde-chat-btn--loksyu">
<i class="fas fa-yin-yang"></i> ${game.i18n.localize("CDE.Loksyu")}
</button>
<button type="button" class="cde-chat-btn cde-chat-btn--tinji">
<i class="fas fa-star"></i> ${game.i18n.localize("CDE.TinJi2")}
</button>
<button type="button" class="cde-chat-btn cde-chat-btn--wheel">
<i class="fas fa-circle-notch"></i> ${game.i18n.localize("CDE.InitiativeWheel")}
</button>
`
// 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)
})