Files
fvtt-chroniques-de-l-etrange/src/system.js
T
uberwald 34b7e32d08
Release Creation / build (release) Successful in 1m7s
Fix timeout usage
2026-06-10 16:30:07 +02:00

232 lines
9.2 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.log(
"%c╔══════════════════════════════════════════════════════════╗\n" +
"%c║ Chroniques de l'Étrange — FoundryVTT ║\n" +
"%c║ Système de jeu par Antre-Monde Éditions ║\n" +
"%c║ Made by Uberwald - https://www.ubwerwald.me ║\n" +
"%c╚══════════════════════════════════════════════════════════╝",
"color: #d4af37; font-weight: bold;",
"color: #e2e8f4;",
"color: #7d94b8;",
"color: #5a7a9a;",
"color: #d4af37; font-weight: bold;",
)
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)
})