import { ASCII, SYSTEM, SYSTEM_ID, localizeSystemConfig } from "./module/config/system.mjs" 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 MGNERoll from "./module/documents/roll.mjs" Hooks.once("init", () => { console.info(ASCII) console.info(`${SYSTEM_ID} | Initializing system`) game.mgne = { SYSTEM, applications, documents, models, } CONFIG.Actor.documentClass = documents.MGNEActor CONFIG.Actor.dataModels = { character: models.MGNECharacter, creature: models.MGNECreature, companion: models.MGNECompanion, party: models.MGNEParty, } CONFIG.Combat.documentClass = documents.MGNECombat CONFIG.Item.documentClass = documents.MGNEItem CONFIG.Item.dataModels = { weapon: models.MGNEWeapon, armor: models.MGNEArmor, shield: models.MGNEShield, equipment: models.MGNEEquipment, "resonance-core": models.MGNEResonanceCore, artifact: models.MGNEArtifact, feature: models.MGNEFeature, "creature-trait": models.MGNECreatureTrait, } foundry.applications.sheets.ActorSheetV2 && foundry.documents.collections.Actors.unregisterSheet( "core", foundry.applications.sheets.ActorSheetV2, { types: Object.keys(CONFIG.Actor.dataModels) } ) foundry.appv1?.sheets?.ActorSheet && foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet) foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, applications.MGNECharacterSheet, { types: ["character"], makeDefault: true, label: SYSTEM.actorTypes.character.label }) foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, applications.MGNECreatureSheet, { types: ["creature"], makeDefault: true, label: SYSTEM.actorTypes.creature.label }) foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, applications.MGNECompanionSheet, { types: ["companion"], makeDefault: true, label: SYSTEM.actorTypes.companion.label }) foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, applications.MGNEPartySheet, { types: ["party"], makeDefault: true, label: SYSTEM.actorTypes.party.label }) foundry.applications.sheets.ItemSheetV2 && foundry.documents.collections.Items.unregisterSheet( "core", foundry.applications.sheets.ItemSheetV2, { types: Object.keys(CONFIG.Item.dataModels) } ) foundry.appv1?.sheets?.ItemSheet && foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet) foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNEWeaponSheet, { types: ["weapon"], makeDefault: true, label: SYSTEM.itemTypes.weapon.label }) foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNEArmorSheet, { types: ["armor"], makeDefault: true, label: SYSTEM.itemTypes.armor.label }) foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNEShieldSheet, { types: ["shield"], makeDefault: true, label: SYSTEM.itemTypes.shield.label }) foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNEEquipmentSheet, { types: ["equipment"], makeDefault: true, label: SYSTEM.itemTypes.equipment.label }) foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNEResonanceCoreSheet, { types: ["resonance-core"], makeDefault: true, label: SYSTEM.itemTypes["resonance-core"].label }) foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNEArtifactSheet, { types: ["artifact"], makeDefault: true, label: SYSTEM.itemTypes.artifact.label }) foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNEFeatureSheet, { types: ["feature"], makeDefault: true, label: SYSTEM.itemTypes.feature.label }) foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNECreatureTraitSheet, { types: ["creature-trait"], makeDefault: true, label: SYSTEM.itemTypes["creature-trait"].label }) Handlebars.registerHelper("isEqual", (left, right) => left === right) Handlebars.registerHelper("includes", (collection, value) => { if (!collection) return false if (collection instanceof Set) return collection.has(value) if (Array.isArray(collection)) return collection.includes(value) return false }) }) Hooks.once("setup", () => { localizeSystemConfig() }) Hooks.once("ready", () => { console.info(`${SYSTEM_ID} | Ready`) }) Hooks.on("deleteCombat", (combat) => { if (!game.user.isGM) return const pcActors = [...new Set( combat.combatants .filter(c => c.actor?.hasPlayerOwner) .map(c => c.actor) )] const cores = pcActors.flatMap(actor => actor.items.filter(item => item.type === "resonance-core" && !item.system.burnedOut && item.system.usageDie !== "depleted") ) if (!cores.length) return const lines = cores.map(c => `
  • ${c.parent.name} — ${c.name} (${c.system.usageDie})
  • `).join("") ChatMessage.create({ content: `

    ⚙️ ${game.i18n.localize("MGNE.Notification.RollUsageDiceReminder")}

    `, }) }) Hooks.on("renderCombatTracker", (_app, element) => { const root = element instanceof HTMLElement ? element : element?.[0] if (!root) return const footer = root.querySelector(".combat-controls") if (!footer || footer.querySelector(".mgne-flee-control")) return const button = document.createElement("button") button.type = "button" button.className = "combat-control-lg mgne-flee-control" button.dataset.action = "mgneFlee" button.innerHTML = `${game.i18n.localize("MGNE.Combat.Flee")}` button.disabled = !game.combat button.addEventListener("click", event => { event.preventDefault() game.combat?.rollFlee() }) footer.append(button) }) Hooks.on("renderChatMessageHTML", (message, element) => { const root = element instanceof HTMLElement ? element : element?.[0] if (!root) return // Dice tooltip toggle root.querySelectorAll("[data-action='toggle-dice-tooltip']").forEach(trigger => { trigger.addEventListener("click", () => { const tooltip = trigger.closest(".chat-card-body")?.querySelector(".chat-dice-tooltip") if (!tooltip) return const isHidden = tooltip.hidden tooltip.hidden = !isHidden trigger.classList.toggle("tooltip-open", isHidden) }) }) root.querySelectorAll(".mgne-roll-damage-btn").forEach(btn => { btn.addEventListener("click", async () => { const actorId = btn.dataset.actorId const itemId = btn.dataset.itemId const actor = game.actors.get(actorId) const item = actor?.items.get(itemId) if (!actor || !item) { ui.notifications.warn(game.i18n.localize("MGNE.Notification.ActorOrItemNotFound")) return } await MGNERoll.rollDamage({ actor, item }) }) }) root.querySelectorAll(".mgne-apply-damage-select").forEach(select => { const isAllowed = game.user.isGM || message.isAuthor if (!isAllowed) { select.closest(".chat-apply-actions")?.remove() return } const card = select.closest(".mgne-chat-card") const damageTotal = parseInt(card?.dataset.damageTotal ?? "0", 10) || 0 const damageCritical = card?.dataset.damageCritical === "true" const tokens = canvas.scene?.tokens.contents ?? [] for (const token of tokens) { if (!token.actor) continue const opt = document.createElement("option") opt.value = token.id opt.textContent = token.name select.appendChild(opt) } if (tokens.length === 0) { const opt = document.createElement("option") opt.disabled = true opt.textContent = game.i18n.localize("MGNE.Roll.NoTargetSelected") select.appendChild(opt) } select.addEventListener("change", async event => { const tokenId = event.target.value if (!tokenId) return const token = canvas.scene?.tokens.get(tokenId) const targetActor = token?.actor if (!targetActor) return select.value = "" let finalDamage = damageTotal const targetOmens = targetActor.system?.omens?.current ?? 0 if (targetOmens > 0) { const spendOmen = await foundry.applications.api.DialogV2.wait({ window: { title: game.i18n.localize("MGNE.RollDialog.OmenReduceTitle") }, classes: ["mgne", "roll-dialog"], content: `

    ${game.i18n.format("MGNE.RollDialog.OmenReducePrompt", { name: targetActor.name, omens: targetOmens })}

    `, buttons: [ { label: game.i18n.localize("MGNE.Common.No"), icon: "fa-solid fa-xmark", callback: () => false }, { label: game.i18n.localize("MGNE.RollDialog.OmenReduceButton"), icon: "fa-solid fa-star", callback: () => true }, ], rejectClose: false, }) if (spendOmen) { const reduceRoll = await (new Roll("1d6")).evaluate() // Re-read omens after dialog to avoid overwriting concurrent changes const currentTargetOmens = targetActor.system?.omens?.current ?? 0 await targetActor.update({ "system.omens.current": Math.max(0, currentTargetOmens - 1) }) finalDamage = Math.max(0, damageTotal - reduceRoll.total) const reduceMsg = game.i18n.format("MGNE.Roll.OmenReducedDamage", { name: targetActor.name, reduced: reduceRoll.total, final: finalDamage, }) await ChatMessage.create({ speaker: ChatMessage.getSpeaker({ actor: targetActor }), rolls: [reduceRoll], content: `

    ${reduceMsg}

    `, }) } } await targetActor.applyDamage(finalDamage, { critical: damageCritical, chat: true }) }) }) })