import { MournbladeUtility } from "../mournblade-utility.js" export default class MournbladeEnchantementDialog { static _normalize(str) { return (str ?? "") .toLowerCase() .replace(/œ/g, "oe") .replace(/æ/g, "ae") .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") .replace(/[^a-z0-9]+/g, " ") .trim() } static _findCompetence(actor, ...keywords) { const normKeys = keywords.map(k => MournbladeEnchantementDialog._normalize(k)) return actor.items.find(item => { if (item.type !== "competence") return false const norm = MournbladeEnchantementDialog._normalize(item.name) return normKeys.every(k => norm.includes(k)) }) ?? null } static async create(actor, item) { const normalize = MournbladeEnchantementDialog._normalize.bind(MournbladeEnchantementDialog) const findComp = (...kw) => MournbladeEnchantementDialog._findCompetence(actor, ...kw) const ameDisponible = Math.max(0, (actor.system.ame.currentmax - actor.system.ame.value)) const aspect = actor.system.balance.aspect ?? 0 // Skill lookups const savoirRunesComp = findComp("rune") const hautParlerComp = findComp("haut", "parler") const artisanatComp = findComp("savoir", "artisanat") const claAttr = actor.system.attributs?.clairvoyance // Prerequisite: Rune de la Loi in inventory const hasRuneLoi = actor.items.some(i => { if (i.type !== "rune") return false return normalize(i.name).includes("loi") }) const savoirRunesNiveau = savoirRunesComp ? (savoirRunesComp.system.niveau ?? 0) : null const hautParlerNiveau = hautParlerComp ? (hautParlerComp.system.niveau ?? 0) : null const artisanatNiveau = artisanatComp ? (artisanatComp.system.niveau ?? 0) : null const claValeur = claAttr ? (claAttr.value ?? 0) : 0 // Limit: CLA + Savoir:Runes is capped by min(Haut-Parler, Artisanat) if those skills exist const limiteur = (hautParlerNiveau !== null && artisanatNiveau !== null) ? Math.min(hautParlerNiveau, artisanatNiveau) : (hautParlerNiveau ?? artisanatNiveau ?? null) const context = { actorImg: actor.img, actorName: actor.name, itemImg: item.img, itemName: item.name, itemType: item.type, itemId: item.id, ameDisponible, aspect, hasRuneLoi, savoirRunesNiveau, hautParlerNiveau, artisanatNiveau, claValeur, limiteur, modOptions: Array.from({ length: 21 }, (_, i) => i - 10), enchantementActif: item.system.enchantementLoi?.actif ?? false, enchantementBonus: item.system.enchantementLoi?.bonus ?? 0, enchantementAntiChaos: item.system.enchantementLoi?.antiChaos ?? false, } const prerequisOk = hasRuneLoi const content = await foundry.applications.handlebars.renderTemplate( "systems/fvtt-mournblade/templates/dialog-enchantement.hbs", context ) Hooks.once("renderDialogV2", (_app, html) => { const form = html.querySelector ? html : html[0] MournbladeEnchantementDialog._attachListeners(form, ameDisponible, claValeur, savoirRunesNiveau, limiteur, prerequisOk) }) return foundry.applications.api.DialogV2.wait({ window: { title: `Enchanter : ${item.name}`, icon: "fa-solid fa-star" }, classes: ["mournblade-roll-dialog"], position: { width: 520 }, modal: false, content, buttons: [ { action: "enchanter", label: "Enchanter", icon: "fa-solid fa-star", default: true, callback: (event, button, dialog) => { const elems = button.form.elements const ptsAme = parseInt(elems["ptsAme"]?.value ?? 5) || 5 const antiChaos = elems["antiChaos"]?.value === "true" const modificateur = parseInt(elems["modificateur"]?.value ?? 0) || 0 MournbladeUtility.rollEnchantement({ actor, item, ptsAme, antiChaos, modificateur, savoirRunesComp, hautParlerComp, artisanatComp, claValeur, limiteur, }) } }, ], rejectClose: false, }) } static _attachListeners(html, ameDisponible, claValeur, savoirRunesNiveau, limiteur, prerequisOk = true) { const enchanterBtn = html.querySelector('button[data-action="enchanter"]') if (enchanterBtn) enchanterBtn.disabled = !prerequisOk const diffEl = html.querySelector('#enchant-difficulte') const bonusEl = html.querySelector('#enchant-bonus-preview') const warnAmeEl = html.querySelector('#enchant-ame-warn') const warnLimitEl = html.querySelector('#enchant-limit-warn') const totalEl = html.querySelector('#enchant-total-dice') const recalculate = () => { const ptsAme = parseInt(html.querySelector('[name="ptsAme"]')?.value ?? 5) || 0 const difficulte = ptsAme const bonus = Math.floor(ptsAme / 5) const savoir = savoirRunesNiveau ?? 0 const basePool = claValeur + savoir const effectivePool = limiteur !== null ? Math.min(basePool, limiteur) : basePool if (diffEl) diffEl.textContent = difficulte if (bonusEl) bonusEl.textContent = `+${bonus}` if (totalEl) totalEl.textContent = effectivePool if (warnAmeEl) warnAmeEl.style.display = ptsAme > ameDisponible ? "" : "none" if (warnLimitEl && limiteur !== null) warnLimitEl.style.display = basePool > limiteur ? "" : "none" } const ptsAmeEl = html.querySelector('[name="ptsAme"]') if (ptsAmeEl) { ptsAmeEl.addEventListener('input', recalculate) ptsAmeEl.addEventListener('change', recalculate) } recalculate() } }