/** * Lethal Fantasy RPG System * Author: LeRatierBretonnien/Uberwald */ import { SYSTEM } from "./module/config/system.mjs" // 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 { LethalFantasyCombatTracker, LethalFantasyCombat } from "./module/applications/combat.mjs" import { Macros } from "./module/macros.mjs" import { setupTextEnrichers } from "./module/enrichers.mjs" import LethalFantasyUtils, { log } from "./module/utils.mjs" // Import chat reaction hooks (renderChatMessageHTML, preCreateChatMessage, defense/attack reactions, resource costing, auto-damage) import "./module/hooks/chat-reaction.mjs" Hooks.once("init", function () { globalThis.SYSTEM = SYSTEM globalThis.pendingDefenses = new Map() console.info("Lethal Fantasy RPG | Initializing System") console.info(SYSTEM.ASCII) game.settings.register(game.system.id, "debug", { name: "Debug logging", scope: "client", config: true, default: false, type: Boolean, }) globalThis.lethalFantasy = game.system globalThis.log = log game.system.CONST = SYSTEM // Expose the system API game.system.api = { applications, models, documents, } CONFIG.ui.combat = LethalFantasyCombatTracker CONFIG.Combat.documentClass = LethalFantasyCombat; CONFIG.Actor.documentClass = documents.LethalFantasyActor CONFIG.Actor.dataModels = { character: models.LethalFantasyCharacter, monster: models.LethalFantasyMonster, } CONFIG.Item.documentClass = documents.LethalFantasyItem CONFIG.Item.dataModels = { skill: models.LethalFantasySkill, gift: models.LethalFantasyGift, weapon: models.LethalFantasyWeapon, armor: models.LethalFantasyArmor, shield: models.LethalFantasyShield, spell: models.LethalFantasySpell, vulnerability: models.LethalFantasyVulnerability, equipment: models.LethalFantasyEquipment, miracle: models.LethalFantasyMiracle } // Register sheet application classes (V2) foundry.documents.collections.Actors.registerSheet("lethalFantasy", applications.LethalFantasyCharacterSheet, { types: ["character"], makeDefault: true }) foundry.documents.collections.Actors.registerSheet("lethalFantasy", applications.LethalFantasyMonsterSheet, { types: ["monster"], makeDefault: true }) foundry.documents.collections.Items.registerSheet("lethalFantasy", applications.LethalFantasySkillSheet, { types: ["skill"], makeDefault: true }) foundry.documents.collections.Items.registerSheet("lethalFantasy", applications.LethalFantasyGiftSheet, { types: ["gift"], makeDefault: true }) foundry.documents.collections.Items.registerSheet("lethalFantasy", applications.LethalFantasyVulnerabilitySheet, { types: ["vulnerability"], makeDefault: true }) foundry.documents.collections.Items.registerSheet("lethalFantasy", applications.LethalFantasyWeaponSheet, { types: ["weapon"], makeDefault: true }) foundry.documents.collections.Items.registerSheet("lethalFantasy", applications.LethalFantasySpellSheet, { types: ["spell"], makeDefault: true }) foundry.documents.collections.Items.registerSheet("lethalFantasy", applications.LethalFantasyArmorSheet, { types: ["armor"], makeDefault: true }) foundry.documents.collections.Items.registerSheet("lethalFantasy", applications.LethalFantasyShieldSheet, { types: ["shield"], makeDefault: true }) foundry.documents.collections.Items.registerSheet("lethalFantasy", applications.LethalFantasyEquipmentSheet, { types: ["equipment"], makeDefault: true }) foundry.documents.collections.Items.registerSheet("lethalFantasy", applications.LethalFantasyMiracleSheet, { types: ["miracle"], makeDefault: true }) // Other Document Configuration CONFIG.ChatMessage.documentClass = documents.LethalFantasyChatMessage // Dice system configuration CONFIG.Dice.rolls.push(documents.LethalFantasyRoll) // Activate socket handler game.socket.on(`system.${SYSTEM.id}`, LethalFantasyUtils.handleSocketEvent) setupTextEnrichers() LethalFantasyUtils.registerHandlebarsHelpers() LethalFantasyUtils.setHookListeners() console.info("LETHAL FANTASY | System Initialized") }) /** * Perform one-time configuration of system configuration objects.f */ 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]) } } } } Hooks.once("ready", function () { console.info("LETHAL FANTASY | Ready") // Initialiser la table des résultats D30 documents.D30Roll.initialize() // Saignement piloté par le combat tracker _registerBleedingHooks() _showUserGuide() /** * */ async function _showUserGuide() { if (game.user.isGM) { const newVer = game.system.version } } }) /** * Saignement piloté par le combat tracker. * Chaque round = 1 seconde → les acteurs qui saignent perdent 1 HP/blessure. * Hors combat, une notification prévient le MJ que des blessures saignent encore. */ function _registerBleedingHooks() { if (!game.user.isGM) return Hooks.on("combatRound", async (combat, previous, current) => { if (previous === current) return const processed = new Set() for (const combatant of combat.combatants) { const actor = combatant.actor if (!actor || processed.has(actor.id)) continue processed.add(actor.id) await _applyBleedingTick(actor) } }) Hooks.on("combatEnd", async (combat) => { const bleeding = _findBleedingActors() if (bleeding.length) { ui.notifications.warn( game.i18n.format("LETHALFANTASY.Notifications.bleedingCombatEnd", { names: bleeding.map(a => a.name).join(", "), }) ) } }) Hooks.on("combatStart", async (combat) => { const bleeding = _findBleedingActors() if (bleeding.length) { ui.notifications.warn( game.i18n.format("LETHALFANTASY.Notifications.bleedingCombatStart", { names: bleeding.map(a => a.name).join(", "), }) ) } }) } /** * Appliquer 1 HP de dégât par blessure active, décrémenter la durée. * @param {import("foundry/common/documents.mjs").Actor} actor */ async function _applyBleedingTick(actor) { if (!actor?.system?.hp?.wounds) return const wounds = foundry.utils.duplicate(actor.system.hp.wounds) let hpLoss = 0 let changed = false for (const wound of wounds) { if (wound.duration > 0 && wound.value > 0) { hpLoss += 1 wound.duration -= 1 if (wound.duration <= 0) { wound.value = 0 wound.description = "" } changed = true } } if (!changed) return const currentHp = actor.system.hp.value ?? 0 await actor.update({ "system.hp.value": currentHp - hpLoss, "system.hp.wounds": wounds, }) } /** * Retourne les acteurs (monde + tokens) qui ont des blessures actives. * @returns {import("foundry/common/documents.mjs").Actor[]} */ function _findBleedingActors() { const actors = [] for (const actor of game.actors.values()) { if (actor?.system?.hp?.wounds?.some(w => w.duration > 0 && w.value > 0)) { actors.push(actor) } } for (const token of canvas.tokens?.placeables ?? []) { if (token.actor && !actors.includes(token.actor)) { if (token.actor?.system?.hp?.wounds?.some(w => w.duration > 0 && w.value > 0)) { actors.push(token.actor) } } } return actors } /** * 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 } }) /* -------------------------------------------- */ /** * Inject the Lethal Fantasy dice tray into the chat sidebar. */ Hooks.on("renderChatLog", (_chatLog, html) => applications.injectDiceTray(_chatLog, html))