3df46b5848
- Extract all inline HTML from JS into 21 Handlebars templates (chat/, dialogs/, ui/) - Split utils.mjs (1507) into barrel + helpers.mjs, combat.mjs, d30.mjs - Split roll.mjs (1632) into barrel + roll-base.mjs, roll-prompt.mjs, roll-combat.mjs, roll-damage.mjs - Split lethal-fantasy.mjs (1426) into bootstrap + chat-reaction.mjs - Fix: missing async on injectDiceTray (free-roll.mjs:29 SyntaxError) - Fix: weapon._id fallback for deserialized chat-message weapon objects - Fix: missing await on rollModifier.evaluate() calls in roll-combat.mjs - Fix: choices→choicesList ReferenceError in utils.mjs - Fix: add 12 missing i18n keys (chooseWeapon, chooseSave, attackRoll, etc.) - Fix: restore sideLabel in bonus-die-select.hbs - Clean: remove dead messageContent param, console.log→log() - Style: barrel files preserve existing import paths
241 lines
8.0 KiB
JavaScript
241 lines
8.0 KiB
JavaScript
/**
|
|
* 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))
|