/** * Lethal Fantasy RPG System * Author: LeRatierBretonnien/Uberwald */ import { SYSTEM } from "./module/config/system.mjs" globalThis.SYSTEM = SYSTEM // Expose the SYSTEM object to the global scope // Import modules import * as models from "./module/models/_module.mjs" import * as documents from "./module/documents/_module.mjs" import * as applications from "./module/applications/_module.mjs" import { handleSocketEvent } from "./module/socket.mjs" import { configureDiceSoNice } from "./module/dice.mjs" import { Macros } from "./module/macros.mjs" import { initControlButtons } from "./module/control-buttons.mjs" import { setupTextEnrichers } from "./module/enrichers.mjs" Hooks.once("init", function () { console.info("Lethal Fantasy RPG | Initializing System") console.info(SYSTEM.ASCII) globalThis.lethalFantasy = game.system game.system.CONST = SYSTEM // Expose the system API game.system.api = { applications, models, documents, } CONFIG.Actor.documentClass = documents.LethalFantasyActor CONFIG.Actor.dataModels = { character: models.LethalFantasyCharacter, opponent: models.LethalFantasyOpponent, } CONFIG.Item.documentClass = documents.LethalFantasyItem CONFIG.Item.dataModels = { path: models.LethalFantasyPath, talent: models.LethalFantasyTalent, weapon: models.LethalFantasyWeapon, armor: models.LethalFantasyArmor, spell: models.LethalFantasySpell, attack: models.LethalFantasyAttack, } // Register sheet application classes Actors.unregisterSheet("core", ActorSheet) Actors.registerSheet("lethalFantasy", applications.LethalFantasyCharacterSheet, { types: ["character"], makeDefault: true }) Actors.registerSheet("lethalFantasy", applications.LethalFantasyOpponentSheet, { types: ["opponent"], makeDefault: true }) Items.unregisterSheet("core", ItemSheet) Items.registerSheet("lethalFantasy", applications.LethalFantasyTalentSheet, { types: ["talent"], makeDefault: true }) Items.registerSheet("lethalFantasy", applications.LethalFantasyPathSheet, { types: ["path"], makeDefault: true }) Items.registerSheet("lethalFantasy", applications.LethalFantasyWeaponSheet, { types: ["weapon"], makeDefault: true }) Items.registerSheet("lethalFantasy", applications.LethalFantasySpellSheet, { types: ["spell"], makeDefault: true }) Items.registerSheet("lethalFantasy", applications.LethalFantasyArmorSheet, { types: ["armor"], makeDefault: true }) Items.registerSheet("lethalFantasy", applications.LethalFantasyAttackSheet, { types: ["attack"], makeDefault: true }) // Other Document Configuration CONFIG.ChatMessage.documentClass = documents.LethalFantasyChatMessage // Dice system configuration CONFIG.Dice.rolls.push(documents.LethalFantasyRoll) // Register system settings game.settings.register("lethalFantasy", "displayOpponentMalus", { name: "TENEBRIS.Setting.displayOpponentMalus", hint: "TENEBRIS.Setting.displayOpponentMalusHint", scope: "world", config: true, type: Boolean, default: true, }) game.settings.register("lethalFantasy", "fortune", { name: "TENEBRIS.Setting.fortune", hint: "TENEBRIS.Setting.fortuneHint", scope: "world", config: true, type: Number, default: 0, requiresReload: true, }) game.settings.register("lethalFantasy", "worldKey", { name: "Unique world key", scope: "world", config: false, type: String, default: "", }) // Activate socket handler game.socket.on(`system.${SYSTEM.id}`, handleSocketEvent) // Pre-localize configuration objects // TODO : encore d'actualité ? // preLocalizeConfig() initControlButtons() setupTextEnrichers() // Gestion des jets de dés depuis les journaux document.addEventListener("click", (event) => { const anchor = event.target.closest("a.ask-roll-journal") if (!anchor) return event.preventDefault() event.stopPropagation() const type = anchor.dataset.rollType const target = anchor.dataset.rollTarget const title = anchor.dataset.rollTitle const avantage = anchor.dataset.rollAvantage applications.LethalFantasyManager.askRollForAll(type, target, title, avantage) }) console.info("CTHULHU TENEBRIS | System Initialized") }) /** * Perform one-time configuration of system configuration objects. */ function preLocalizeConfig() { const localizeConfigObject = (obj, keys) => { for (let o of Object.values(obj)) { for (let k of keys) { o[k] = game.i18n.localize(o[k]) } } } // CONFIG.Dice.rollModes = Object.fromEntries(Object.entries(CONFIG.Dice.rollModes).map(([key, value]) => [key, game.i18n.localize(value)])) // localizeConfigObject(SYSTEM.ACTION.TAG_CATEGORIES, ["label"]) // localizeConfigObject(CONFIG.Dice.rollModes, ["label"]) } Hooks.once("ready", function () { console.info("CTHULHU TENEBRIS | Ready") game.system.applicationFortune = new applications.LethalFantasyFortune() game.system.applicationFortune.render(true) game.system.applicationManager = new applications.LethalFantasyManager() if (game.user.isGM) { game.system.applicationManager.render(true) } if (!SYSTEM.DEV_MODE) { registerWorldCount("lethalFantasy") } _showUserGuide() /** * */ async function _showUserGuide() { if (game.user.isGM) { const newVer = game.system.version const userGuideJournalName = "Guide du système" const userGuideCompendiumLabel = "userguide" let currentVer = "0" let oldUserGuide = game.journal.getName(userGuideJournalName) if (oldUserGuide !== undefined && oldUserGuide !== null && oldUserGuide.getFlag("lethalFantasy", "UserGuideVersion") !== undefined) { currentVer = oldUserGuide.getFlag("lethalFantasy", "UserGuideVersion") } if (newVer === currentVer) { // Up to date return } let newReleasePack = game.packs.find((p) => p.metadata.name === userGuideCompendiumLabel) if (newReleasePack === null || newReleasePack === undefined) { console.info("CTHULHU TENEBRIS | No compendium found for the system guide") return } await newReleasePack.getIndex() let newUserGuide = newReleasePack.index.find((j) => j.name === userGuideJournalName) if (newUserGuide === undefined || newUserGuide === null) { console.info("CTHULHU TENEBRIS | No system guide found in the compendium") return } // Don't delete until we have new release Pack if (oldUserGuide !== null && oldUserGuide !== undefined) { await oldUserGuide.delete() } await game.journal.importFromCompendium(newReleasePack, newUserGuide._id) let newReleaseJournal = game.journal.getName(newUserGuide.name) await newReleaseJournal.setFlag("lethalFantasy", "UserGuideVersion", newVer) // Show journal await newReleaseJournal.sheet.render(true, { sheetMode: "text" }) } } }) Hooks.on("renderChatMessage", (message, html, data) => { const typeMessage = data.message.flags.lethalFantasy?.typeMessage // Message de fortune if (typeMessage === "fortune") { if (game.user.isGM && !data.message.flags.lethalFantasy?.accepted) { html.find(".button").click((event) => applications.LethalFantasyFortune.acceptRequest(event, html, data)) } else { html.find(".button").each((i, btn) => { btn.style.display = "none" }) if (game.user.isGM) { html.find(".fortune-accepted").each((i, btn) => (btn.style.display = "flex")) } } } // Message de demande de jet de dés else if (typeMessage === "askRoll") { // Affichage des boutons de jet de dés uniquement pour les joueurs if (game.user.isGM) { html.find(".ask-roll-dice").each((i, btn) => { btn.style.display = "none" }) } else { html.find(".ask-roll-dice").click((event) => { const btn = $(event.currentTarget) const type = btn.data("type") const value = btn.data("value") const avantage = btn.data("avantage") ?? "=" const character = game.user.character if (type === SYSTEM.ROLL_TYPE.RESOURCE) character.rollResource(value) else if (type === SYSTEM.ROLL_TYPE.SAVE) character.rollSave(value, avantage) }) } } }) Hooks.on("updateSetting", async (setting, update, options, id) => { if (setting.key === "lethalFantasy.fortune") { game.system.applicationFortune.render(true) } }) // Dice-so-nice Ready Hooks.once("diceSoNiceReady", (dice3d) => { configureDiceSoNice(dice3d) }) /** * Create a macro when dropping an entity on the hotbar * Item - open roll dialog * Actor - open actor sheet * Journal - open journal sheet */ Hooks.on("hotbarDrop", (bar, data, slot) => { if (["Actor", "Item", "JournalEntry", "roll", "rollDamage", "rollAttack"].includes(data.type)) { Macros.createLethalFantasyMacro(data, slot) return false } }) /** * Register world usage statistics * @param {string} registerKey */ function registerWorldCount(registerKey) { if (game.user.isGM) { let worldKey = game.settings.get(registerKey, "worldKey") if (worldKey === undefined || worldKey === "") { worldKey = foundry.utils.randomID(32) game.settings.set(registerKey, "worldKey", worldKey) } // Simple API counter const worldData = { register_key: registerKey, world_key: worldKey, foundry_version: `${game.release.generation}.${game.release.build}`, system_name: game.system.id, system_version: game.system.version, } let apiURL = "https://worlds.qawstats.info/worlds-counter" $.ajax({ url: apiURL, type: "POST", data: JSON.stringify(worldData), contentType: "application/json; charset=utf-8", dataType: "json", async: false, }) } }