export default class RMFRPToolsDiceRoller extends FormApplication { constructor(item, actor) { super(); this.item = foundry.utils.duplicate(item); this.actor = actor; this.itemName = item.name; this.characterBonus = Number(item.system.total_bonus); this.stunnedModifier = actor.getStunnedModifier() this.rollType = [ { value: "one_to_onehundred", text: "1-100", selected: false }, { value: "open_ended", text: "Open-Ended", selected: true }, { value: "high_open_ended", text: "High Open-Ended", selected: false }, { value: "low_open_ended", text: "Low Open-Ended", selected: false } ]; // Process weapon case if (this.item.type == "skill" && this.item.system.category.includes("Weapon")) { let weapon = this.actor.items.find(i => i.type == "weapon" && this.item._id == i.system.skill); if ( !weapon ) { ui.notifications.error("Weapon not found for skill: " + this.item.name); return } this.weapon = weapon this.characterBonus += Number(weapon.system.weapon_bonus); } } static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ["form"], title: "Rolemaster Dice Roller", popOut: true, width: 480, height: 440, template: "systems/fvtt-rolemaster-frp/templates/sheets/apps/app_dice_roller.html" }); } getData() { // Send data to the template return { itemName: this.itemName, characterBonus: this.characterBonus, selectOptions: this.rollType, woundsModifier: this.actor.system.modifiers.woundsModifier, weapon: this?.weapon, targetArmorClass: 1, config: CONFIG.rmfrp, difficulty: 0, combatSituation: 0, lightningModifier: 0, darknessModifier: 0, hitsPerRound: 0, isStunned: this.actor.system.state.stunned, stunnedModifier: this.stunnedModifier }; } activateListeners(html) { super.activateListeners(html); } async _updateObject(event, formData) { console.log("Rolling Dice"); console.log(formData); console.log(event); this.roll(event.submitter?.dataset?.value, formData); } /* -------------------------------------------- */ async showDiceSoNice(roll, rollMode) { if (game.modules.get("dice-so-nice")?.active) { if (game.dice3d) { let whisper = null; let blind = false; rollMode = rollMode ?? game.settings.get("core", "rollMode"); switch (rollMode) { case "blindroll": //GM only blind = true; case "gmroll": //GM + rolling player whisper = this.getUsers(user => user.isGM); break; case "roll": //everybody whisper = this.getUsers(user => user.active); break; case "selfroll": whisper = [game.user.id]; break; } await game.dice3d.showForRoll(roll, game.user, true, whisper, blind); } } } /* -------------------------------------------- */ async roll(rollKey, formData) { let baseRoll = await new Roll("1d100").roll(); await this.showDiceSoNice(baseRoll, game.settings.get("core", "rollMode")) let rollType = this.rollType.find(r => r.value == rollKey)?.text; let rollData = { name: this.itemName, weapon: this?.weapon, rollKey: rollKey, rollType: rollType, difficulty: Number(formData.difficulty), targetArmorClass: Number(formData?.targetArmorClass || 1), combatSituation: Number(formData?.combatSituation || 0), lightningModifier: Number(formData?.lightningModifier || 0), darknessModifier: Number(formData?.darknessModifier || 0), characterBonus: Number(this.characterBonus), woundsModifier: Number(this.actor.system.modifiers.woundsModifier), hitsPerRound: Number(formData.hitsPerRound), isStunned: this.actor.system.state.stunned, stunnedModifier: this.stunnedModifier, rolls: [baseRoll], } if (baseRoll.result == 66) { rollData.content = "You rolled a 66!"; } // Process the for low open ended rolls if (rollKey === "open_ended" || rollKey === "low_open_ended") { if (baseRoll.result < 6) { rollData.lowopen = true let newRoll = await new Roll("-1d100").roll(); await this.showDiceSoNice(newRoll, game.settings.get("core", "rollMode")) rollData.rolls.push(newRoll); while (newRoll.result > 95) { newRoll = await new Roll("-1d100").roll(); await this.showDiceSoNice(newRoll, game.settings.get("core", "rollMode")) rollData.rolls.push(newRoll); } } } // Process the for high open ended rolls if (rollKey === "open_ended" || rollKey === "high_open_ended") { if (baseRoll.result > 95) { rollData.highopen = true let newRoll = await new Roll("1d100").roll(); await this.showDiceSoNice(newRoll, game.settings.get("core", "rollMode")) rollData.rolls.push(newRoll); while (newRoll.result > 95) { newRoll = await new Roll("1d100").roll(); await this.showDiceSoNice(newRoll, game.settings.get("core", "rollMode")) rollData.rolls.push(newRoll); } } } // Compute total of rolls rollData.totalRolls = rollData.rolls.reduce((acc, roll) => Number(acc) + Number(roll.result), 0); rollData.totalFinal = rollData.totalRolls + Number(rollData.combatSituation) + Number(rollData.lightningModifier) + Number(rollData.darknessModifier) + Number(this.actor.system.modifiers.woundsModifier) + Number(formData.difficulty) + Number(rollData.hitsPerRound) + Number(rollData.stunnedModifier) + Number(this.characterBonus); console.log(">>> Roll Data: ", rollData); // Manage weapon table if (this.weapon) { let hasFumble = false if ( rollData.totalRolls <= this.weapon.system.fumble_value ) { hasFumble = true } let attackResult = game.rmfrp.attackTables.getAttackRollResult(this.weapon.system.attack_table, rollData.totalFinal, rollData.targetArmorClass) if ( !attackResult) { ui.notifications.error("Attack table not found: " + this.weapon.system.attack_table); return } if (typeof attackResult == "object") { // Why ????? attackResult = attackResult[String(rollData.targetArmorClass)]; } console.log("Attack Result: ", attackResult); rollData.attackResult = attackResult; // Is it a a critical ? let critical = attackResult.match(/(\d+)(\w)?(\w)?/); if (critical && critical[2]) { if ( critical[2] === "F") { hasFumble = true } else { let criticalTable = this.weapon.system.critical_table if ( !criticalTable ) { ui.notifications.error("Critical table not found for weapon: " + this.weapon.name); return } let criticalResult = await game.rmfrp.attackTables.getCriticalResult(criticalTable, critical[2]); rollData.criticalResult = criticalResult; console.log("Critical Result: ", criticalResult); } } if (hasFumble) { let fumbleResult = await game.rmfrp.attackTables.getFumbleRollResult(this.weapon.system.fumble_table, this.weapon.system.fumble_column) if ( !fumbleResult) { ui.notifications.error("Fumble table not found: " + this.weapon.system.attack_table); return } rollData.fumbleResult = fumbleResult; } } // Define the Chat Message Template let chatTemplate = "systems/fvtt-rolemaster-frp/templates/chat/chat_dice_roll.html"; console.log("Final rollData", rollData) // Pass the Data through to be used in the Chat Message let chatData = rollData // Render the Rolls to the Chat Window renderTemplate(chatTemplate, chatData).then((html) => { let chatOptions = { flavor: rollType, rollMode: game.settings.get("core", "rollMode"), content: html, }; ChatMessage.create(chatOptions); }); } }