import { SYSTEM } from "../config/system.mjs" export default class HellbornRoll extends Roll { /** * The HTML template path used to render dice checks of this type * @type {string} */ static CHAT_TEMPLATE = "systems/fvtt-hellborn/templates/chat-message.hbs" get type() { return this.options.type } get isDamage() { return this.type === ROLL_TYPE.DAMAGE } get target() { return this.options.target } get value() { return this.options.value } get actorId() { return this.options.actorId } get actorName() { return this.options.actorName } get actorImage() { return this.options.actorImage } get help() { return this.options.help } get resultType() { return this.options.resultType } get isFailure() { return this.resultType === "failure" } get hasTarget() { return this.options.hasTarget } get realDamage() { return this.options.realDamage } get weapon() { return this.options.weapon } static updateFullFormula(options) { let fullFormula fullFormula = `3D6 + ${options.nbAdvantages}D6kh - ${options.nbDisadvantages}D6kh + ${options.rollItem.value}` $('#roll-dialog-full-formula').text(fullFormula) options.fullFormula = fullFormula } /** * Prompt the user with a dialog to configure and execute a roll. * * @param {Object} options Configuration options for the roll. * @param {string} options.rollType The type of roll being performed. * @param {string} options.rollTarget The target of the roll. * @param {string} options.actorId The ID of the actor performing the roll. * @param {string} options.actorName The name of the actor performing the roll. * @param {string} options.actorImage The image of the actor performing the roll. * @param {boolean} options.hasTarget Whether the roll has a target. * @param {Object} options.data Additional data for the roll. * * @returns {Promise} The roll result or null if the dialog was cancelled. */ static async prompt(options = {}) { let formula = `3D6 + 0D6KH - 0D6KH + ${options?.rollItem?.value}` switch (options.rollType) { case "stat": break case "damage": { let formula = options.rollItem.system.damage let damageRoll = new Roll(formula) await damageRoll.evaluate() await damageRoll.toMessage({ flavor: `${options.rollItem.name} - Damage Roll - ${options.rollItem.system.damageType}`, }); return } case "weapon": { let actor = game.actors.get(options.actorId) options.weapon = foundry.utils.duplicate(options.rollItem) let statKey = "skin" if (options.weapon.system.weaponType === "melee") { if ( options.weapon.system.properties.toLowerCase().match("heavy") || options.weapon.system.properties.toLowerCase().match("oversized")) { statKey = "flesh" } } options.rollItem = actor.system.stats[statKey] } break default: break } const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes) const fieldRollMode = new foundry.data.fields.StringField({ choices: rollModes, blank: false, default: "public", }) options.formula = formula options.nbAdvantages = "0" options.nbDisadvantages = "0" let dialogContext = { actorId: options.actorId, actorName: options.actorName, rollType: options.rollType, rollItem: foundry.utils.duplicate(options.rollItem), // Object only, no class weapon: options?.weapon, formula: formula, fullFormula: formula, rollModes, fieldRollMode, choiceAdvantages: SYSTEM.CHOICE_ADVANTAGES_DISADVANTAGES, choiceDisadvantages: SYSTEM.CHOICE_ADVANTAGES_DISADVANTAGES, hasTarget: options.hasTarget, difficulty: "0", nbAdvantages: "0", nbDisadvantages: "0", } const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-hellborn/templates/roll-dialog.hbs", dialogContext) const title = HellbornRoll.createTitle(options.rollType, options.rollTarget) const label = game.i18n.localize("HELLBORN.Roll.roll") const rollContext = await foundry.applications.api.DialogV2.wait({ window: { title: title }, classes: ["fvtt-hellborn"], content, buttons: [ { label: label, callback: (event, button, dialog) => { const output = Array.from(button.form.elements).reduce((obj, input) => { if (input.name) obj[input.name] = input.value return obj }, {}) return output }, }, ], actions: { }, rejectClose: false, // Click on Close button will not launch an error render: (event, dialog) => { $(".roll-stat-advantages").change(event => { options.nbAdvantages = Number(event.target.value) HellbornRoll.updateFullFormula(options) }) $(".roll-stat-disadvantages").change(event => { options.nbDisadvantages = Number(event.target.value) HellbornRoll.updateFullFormula(options) }) $(".select-combat-option").change(event => { console.log(event) let field = $(event.target).data("field") let modifier = SYSTEM.ATTACK_MODIFIERS[field] if (event.target.checked) { options.numericModifier += modifier } else { options.numericModifier -= modifier } HellbornRoll.updateFullFormula(options) }) } }) // If the user cancels the dialog, exit if (rollContext === null) return let rollData = foundry.utils.mergeObject(foundry.utils.duplicate(options), rollContext) rollData.rollMode = rollContext.visibility rollData.targetScore = 8 if (Hooks.call("fvtt-hellborn.preRoll", options, rollData) === false) return options.nbAdvantages = Number(options.nbAdvantages) options.nbDisadvantages = Number(options.nbDisadvantages) let diceFormula = `3D6 + ${options.nbAdvantages}D6kh - ${options.nbDisadvantages}D6kh + ${options.rollItem.value}` const roll = new this(diceFormula, options.data, rollData) await roll.evaluate() console.log("Roll", rollData, roll) options.difficulty = (rollData.difficulty === "unknown") ? 0 : (Number(rollData.difficulty) || 0) roll.displayRollResult(roll, options, rollData, roll) if (Hooks.call("fvtt-hellborn.Roll", options, rollData, roll) === false) return return roll } displayRollResult(formula, options, rollData, roll) { let resultType = "failure" if (options.difficulty === 0) { resultType = "unknown" } else if (this.total >= options.difficulty) { resultType = "success" } // Compute the result quality this.options.satanicSuccess = false this.options.fiendishFailure = false this.options.rollData = foundry.utils.duplicate(rollData) if (resultType === "success") { let nb6 = roll.terms[0].results.filter(r => r.result === 6).length nb6 += roll.terms[3].total === 6 ? 1 : 0 this.options.satanicSuccess = nb6 >= 3 if (this.options.satanicSuccess) { resultType = "success" } } if (resultType === "failure") { let nb1 = roll.terms[0].results.filter(r => r.result === 1).length nb1 += roll.terms[5].total === 1 ? 1 : 0 this.options.fiendishFailure = nb1 >= 3 if (this.options.fiendishFailure) { resultType = "failure" } } this.options.resultType = resultType this.options.isSuccess = resultType === "success" this.options.isFailure = resultType === "failure" } /** * Creates a title based on the given type. * * @param {string} type The type of the roll. * @param {string} target The target of the roll. * @returns {string} The generated title. */ static createTitle(type, target) { switch (type) { case "stat": return `${game.i18n.localize("HELLBORN.Label.titleStat")}` case "weapon": return `${game.i18n.localize("HELLBORN.Label.titleWeapon")}` default: return game.i18n.localize("HELLBORN.Label.titleStandard") } } /** @override */ async render(chatOptions = {}) { let chatData = await this._getChatCardData(chatOptions.isPrivate) return await foundry.applications.handlebars.renderTemplate(this.constructor.CHAT_TEMPLATE, chatData) } /** * Generates the data required for rendering a roll chat card. * * @param {boolean} isPrivate Indicates if the chat card is private. * @returns {Promise} A promise that resolves to an object containing the chat card data. * @property {Array} css - CSS classes for the chat card. * @property {Object} data - The data associated with the roll. * @property {number} diceTotal - The total value of the dice rolled. * @property {boolean} isGM - Indicates if the user is a Game Master. * @property {string} formula - The formula used for the roll. * @property {number} total - The total result of the roll. * @property {boolean} isFailure - Indicates if the roll is a failure. * @property {string} actorId - The ID of the actor performing the roll. * @property {string} actingCharName - The name of the character performing the roll. * @property {string} actingCharImg - The image of the character performing the roll. * @property {string} resultType - The type of result (e.g., success, failure). * @property {boolean} hasTarget - Indicates if the roll has a target. * @property {string} targetName - The name of the target. * @property {number} targetArmor - The armor value of the target. * @property {number} realDamage - The real damage dealt. * @property {boolean} isPrivate - Indicates if the chat card is private. * @property {string} cssClass - The combined CSS classes as a single string. * @property {string} tooltip - The tooltip text for the chat card. */ async _getChatCardData(isPrivate) { let cardData = foundry.utils.duplicate(this.options) cardData.css = [SYSTEM.id, "dice-roll"] cardData.data = this.data cardData.diceTotal = this.dice.reduce((t, d) => t + d.total, 0) cardData.isGM = game.user.isGM cardData.formula = this.formula cardData.fullFormula = this.options.fullFormula cardData.numericModifier = this.options.numericModifier cardData.total = this.total cardData.actorId = this.actorId cardData.actingCharName = this.actorName cardData.actingCharImg = this.actorImage cardData.resultType = this.resultType cardData.hasTarget = this.hasTarget cardData.targetName = this.targetName cardData.targetArmor = this.targetArmor cardData.realDamage = this.realDamage cardData.isPrivate = isPrivate cardData.weapon = this.weapon cardData.cssClass = cardData.css.join(" ") cardData.tooltip = isPrivate ? "" : await this.getTooltip() return cardData } /** * Converts the roll result to a chat message. * * @param {Object} [messageData={}] Additional data to include in the message. * @param {Object} options Options for message creation. * @param {string} options.rollMode The mode of the roll (e.g., public, private). * @param {boolean} [options.create=true] Whether to create the message. * @returns {Promise} - A promise that resolves when the message is created. */ async toMessage(messageData = {}, { rollMode, create = true } = {}) { super.toMessage( { isFailure: this.resultType === "failure", actingCharName: this.actorName, actingCharImg: this.actorImage, hasTarget: this.hasTarget, realDamage: this.realDamage, ...messageData, }, { rollMode: rollMode }, ) } }