import { YggdrasillUtility } from "../yggdrasill-utility.js" const dureeGaldrSD = { "1d5a": 3, "1d10t": 6, "1d10m": 9, "1d10h": 12, "1d5j": 15} const ciblesGaldrSD = { "1": 3, "2_4": 6, "5_9": 9, "10_49": 12, "50plus": 15} const zonesciblesGaldrSD = { "INS10cm3": 3, "INS50cm3": 6, "INS1m3": 9, "INS5m3": 12, "INS10m3": 15} /** * Dialogue de jet de dé pour Yggdrasill - Version DialogV2 */ export class YggdrasillRollDialog { /** * Create and display the roll dialog * @param {YggdrasillActor} actor - The actor making the roll * @param {Object} rollData - Data for the roll * @returns {Promise} */ static async create(actor, rollData) { // Initialiser attackData pour les armes si nécessaire if ((rollData.mode === "armecc" || rollData.mode === "armetir" || rollData.mode === "armedist") && rollData.attackDef) { // Les données sont déjà calculées dans attackDef par getAttaqueData/getTirData // On les copie simplement dans attackData pour le template rollData.attackData = { ...rollData.attackDef } } // Calculer srTotal pour les sorts if (rollData.mode === "galdr") { this._updateGaldrSD(rollData) rollData.srTotal = 14 + (rollData.sdGaldr || 0) } else if (rollData.mode === "sejdr") { rollData.srTotal = 14 + (rollData.bonusdefense || 0) } else if (rollData.mode === "rune") { this._updateRuneData(rollData, actor) } // Préparer le contexte pour le template const context = { ...rollData, img: actor.img, name: actor.name, config: game.system.yggdrasill.config, } // Titre selon le mode let title = "Test" if (rollData.mode === "competence") title = "Test de Compétence" else if (rollData.mode === "carac") title = "Test de Caractéristique" else if (rollData.mode === "attribut") title = "Test d'Attribut" else if (rollData.mode === "armecc") title = "Attaque au Corps à Corps" else if (rollData.mode === "armetir" || rollData.mode === "armedist") title = "Attaque à Distance" else if (rollData.mode === "sejdr") title = "Sort Sejdr" else if (rollData.mode === "galdr") title = "Sort Galdr" else if (rollData.mode === "rune") title = "Rune" // Rendre le template en HTML const content = await foundry.applications.handlebars.renderTemplate( "systems/fvtt-yggdrasill/templates/roll-dialog-generic-new.hbs", context ) // Utiliser DialogV2.wait avec le HTML rendu return foundry.applications.api.DialogV2.wait({ window: { title: title, icon: "fa-solid fa-dice-d20" }, classes: ["yggdrasill-roll-dialog"], position: { width: 600 }, modal: false, content, buttons: [ { action: "roll", label: "Lancer le Test", icon: "fa-solid fa-dice", default: true, callback: (event, button, dialog) => { this._updateRollDataFromForm(rollData, button.form.elements, actor) this._performRoll(rollData) } }, ], actions: {}, render: (event, dialog) => { // Pour Galdr: recalculer srTotal quand durée, cibles ou zone changent if (rollData.mode === "galdr") { $("#dureeGaldr, #nbCibles, #zoneGaldr").on("change", () => { rollData.dureeGaldr = $("#dureeGaldr").val() rollData.nbCibles = $("#nbCibles").val() rollData.zoneGaldr = $("#zoneGaldr").val() this._updateGaldrSD(rollData) rollData.srTotal = 14 + (rollData.sdGaldr || 0) $("#srTotal").text(rollData.srTotal) }) } // Pour Sejdr: recalculer srTotal quand DM change if (rollData.mode === "sejdr") { $("#bonusdefense").on("change", () => { rollData.bonusdefense = parseInt($("#bonusdefense").val()) || 0 rollData.srTotal = 14 + rollData.bonusdefense $("#srTotal").text(rollData.srTotal) }) } // Pour Rune: recalculer srTotal quand support ou puissance changent if (rollData.mode === "rune") { $("#supportRune, #puissanceRune").on("change", () => { rollData.supportRune = $("#supportRune").val() rollData.puissanceRune = parseInt($("#puissanceRune").val()) || 1 this._updateRuneData(rollData, actor) $("#runeDuree").text(rollData.runeDuree) $("#runeDureeVie").text(rollData.runeDureeVie) $("#srTotal").text(rollData.srTotal) }) } }, rejectClose: false, }) } /** * Perform the roll based on mode * @param {Object} rollData - The roll data * @private */ static _performRoll(rollData) { if (rollData.mode === "attribut") { YggdrasillUtility.rollAttribute(rollData) } else { YggdrasillUtility.rollYggdrasill(rollData) } } /** * Mettre à jour rollData avec les valeurs du formulaire * @param {Object} rollData - L'objet rollData à mettre à jour * @param {HTMLFormControlsCollection} formElements - Les éléments du formulaire * @param {YggdrasillActor} actor - L'acteur pour récupérer les données * @private */ static _updateRollDataFromForm(rollData, formElements, actor) { // Caractéristique (pour compétences) if (formElements.caracName) { const caracKey = formElements.caracName.value rollData.caracName = caracKey rollData.selectedCarac = this._findCaracByKey(actor, caracKey) } // Type d'attaque (pour armes) if (formElements.typeAttack) { rollData.attackDef = rollData.attackDef || {} rollData.attackDef.typeAttack = formElements.typeAttack.value this._updateAttackData(rollData, actor) } // Bonus/Malus if (formElements.bonusMalus) { rollData.bonusMalus = parseInt(formElements.bonusMalus.value) || 0 } // Furor if (formElements.furorUsage) { rollData.furorUsage = parseInt(formElements.furorUsage.value) || 0 } // Seuil de Réussite if (formElements.sr) { rollData.sr = parseInt(formElements.sr.value) || 9 } // Galdr options if (formElements.dureeGaldr) { rollData.dureeGaldr = formElements.dureeGaldr.value } if (formElements.nbCibles) { rollData.nbCibles = formElements.nbCibles.value } if (formElements.zoneGaldr) { rollData.zoneGaldr = formElements.zoneGaldr.value } if (rollData.mode === "galdr") { this._updateGaldrSD(rollData) rollData.srTotal = 14 + (rollData.sdGaldr || 0) } // Sejdr options if (formElements.bonusdefense) { rollData.bonusdefense = parseInt(formElements.bonusdefense.value) || 0 } if (rollData.mode === "sejdr") { rollData.srTotal = 14 + (rollData.bonusdefense || 0) } // Rune options if (formElements.runeDuree) { rollData.runeDuree = formElements.runeDuree.value } } /** * Find characteristic by key in actor * @param {YggdrasillActor} actor * @param {String} caracKey * @returns {Object} * @private */ static _findCaracByKey(actor, caracKey) { for (let [categKey, categ] of Object.entries(actor.system.carac)) { for (let [key, carac] of Object.entries(categ.carac)) { if (key === caracKey) { return { ...carac, categName: categKey, caracName: key } } } } return null } /** * Update attack data based on attack type * @param {Object} rollData * @param {YggdrasillActor} actor * @private */ static _updateAttackData(rollData, actor) { const config = game.system.yggdrasill.config const attackType = rollData.attackDef.typeAttack const attackMode = config.attackMode?.[attackType] if (attackMode) { rollData.attackData = rollData.attackData || {} rollData.attackData.categName = attackMode.categName rollData.attackData.caracName = attackMode.caracName rollData.attackData.malus = this._computeValue(attackMode.malus, actor) rollData.attackData.bonusdegats = this._computeValue(attackMode.bonusdegats, actor) rollData.attackData.protection = this._computeValue(attackMode.protection, actor) rollData.attackData.label = attackMode.label rollData.attackData.description = attackMode.description } } /** * Compute value from string formula like "puissance;3" * @param {String|Number} value * @param {YggdrasillActor} actor * @returns {Number} * @private */ static _computeValue(value, actor) { if (typeof value === 'number') return value if (!value || value === "0") return 0 const parts = String(value).split(';') if (parts.length === 2) { const caracName = parts[0] const multiplier = parseInt(parts[1]) || 1 const caracValue = this._getCaracValue(actor, caracName) return caracValue * multiplier } return 0 } /** * Get characteristic value by name * @param {YggdrasillActor} actor * @param {String} caracName * @returns {Number} * @private */ static _getCaracValue(actor, caracName) { for (let categ of Object.values(actor.system.carac)) { if (categ.carac && categ.carac[caracName]) { return categ.carac[caracName].value || 0 } } return 0 } /** * Update rune data based on support and power * @param {Object} rollData * @param {YggdrasillActor} actor * @private */ static _updateRuneData(rollData, actor) { let support = 0 rollData.dureeRune = 6 - (rollData.agiliteCarac?.value || 0) if (rollData.supportRune === "peau") { support = 3 rollData.echelleDuree = "Actions" rollData.echelleDureeVie = "Heures" } else if (rollData.supportRune === "tissu") { support = 6 rollData.echelleDuree = "Tours" rollData.echelleDureeVie = "Jours" } else if (rollData.supportRune === "cuir") { support = 9 rollData.echelleDuree = "Minutes" rollData.echelleDureeVie = "Semaines" } else if (rollData.supportRune === "bois") { support = 12 rollData.echelleDuree = "Heures" rollData.echelleDureeVie = "Mois" } else if (rollData.supportRune === "pierremetal") { support = 15 rollData.echelleDuree = "Jours" rollData.echelleDureeVie = "Années" } rollData.runeDuree = `${rollData.dureeRune} ${rollData.echelleDuree}` rollData.runeDureeVie = `${rollData.competence?.system.niveau || 1} ${rollData.echelleDureeVie}` rollData.srTotal = (rollData.puissanceRune || 1) + (Number(rollData.sort?.system.niveau || 0) * 3) + support } /** * Update Galdr SD based on duration and targets * @param {Object} rollData * @private */ static _updateGaldrSD(rollData) { let sdDuree = Number(dureeGaldrSD[rollData.dureeGaldr]) || 0 let sdVar = 0 if (rollData.sort?.system.voie === "illusion") { sdVar = Number(zonesciblesGaldrSD[rollData.zoneGaldr]) || 0 } else { sdVar = Number(ciblesGaldrSD[rollData.nbCibles]) || 0 } rollData.sdGaldr = sdDuree + sdVar } }