221 lines
8.5 KiB
JavaScript
221 lines
8.5 KiB
JavaScript
/**
|
||
* 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 = `
|
||
<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)
|
||
})
|