From 721cb08059b63d0946632f150da0a79f62893f1d Mon Sep 17 00:00:00 2001 From: sladecraven Date: Sat, 1 Jan 2022 23:32:48 +0100 Subject: [PATCH] Enhance and bugfixes for combat --- module/actor/actor.js | 2 +- module/controllers/bol-rolls.js | 653 ++++++++++--------- module/system/bol-utility.js | 87 ++- system.json | 2 +- templates/chat/rolls/attack-heroic-card.hbs | 6 + templates/chat/rolls/defense-result-card.hbs | 5 +- 6 files changed, 425 insertions(+), 330 deletions(-) create mode 100644 templates/chat/rolls/attack-heroic-card.hbs diff --git a/module/actor/actor.js b/module/actor/actor.js index 1805466..c3d98e5 100644 --- a/module/actor/actor.js +++ b/module/actor/actor.js @@ -217,7 +217,7 @@ export class BoLActor extends Actor { for (let protect of protectWorn) { if ( protect.data.subtype == 'helm') { formula += "+1" - } else { + } else if ( protect.data.subtype == 'armor') { formula += "+" + protect.data.properties.soak.formula; } } diff --git a/module/controllers/bol-rolls.js b/module/controllers/bol-rolls.js index b5f11b9..9b6c495 100644 --- a/module/controllers/bol-rolls.js +++ b/module/controllers/bol-rolls.js @@ -1,356 +1,365 @@ import { BoLUtility } from "../system/bol-utility.js"; export class BoLRoll { - static options() { - return { classes: ["bol", "dialog"] }; - } - - static attributeCheck(actor, actorData, dataset, event) { - // const elt = $(event.currentTarget)[0]; - // let key = elt.attributes["data-rolling"].value; - const key = dataset.key; - const adv = dataset.adv; - let attribute = eval(`actor.data.data.attributes.${key}`); - let label = (attribute.label) ? game.i18n.localize(attribute.label) : null; - let description = actor.name + " - " + game.i18n.localize('BOL.ui.attributeCheck') + " - " + game.i18n.localize(attribute.label) ; - return this.attributeRollDialog(actor, actorData, attribute, label, description, adv, 0); - } - - static aptitudeCheck(actor, actorData, dataset, event) { - // const elt = $(event.currentTarget)[0]; - // let key = elt.attributes["data-rolling"].value; - const key = dataset.key; - const adv = dataset.adv; - let aptitude = eval(`actor.data.data.aptitudes.${key}`); - let label = (aptitude.label) ? game.i18n.localize(aptitude.label) : null; - let description = actor.name + " - " + game.i18n.localize('BOL.ui.aptitudeCheck') + " - " + game.i18n.localize(aptitude.label) ; - return this.aptitudeRollDialog(actor, actorData, aptitude, label, description, adv, 0); - } - - static weaponCheck(actor, actorData, dataset, event) { - // const elt = $(event.currentTarget)[0]; - // let key = elt.attributes["data-rolling"].value; - let target = BoLUtility.getTarget() - const li = $(event.currentTarget).parents(".item"); - const weapon = actor.items.get(li.data("item-id")); - if (!weapon) { - ui.notifications.warn("Unable to find weapon !"); - return; - } - let weaponData = weapon.data.data; - let attackDef= { - id:randomID(16), - attacker: actor, - attackerData: actorData, - weapon :weapon, - mod: 0, - target : target, - defender: (target) ? game.actors.get(target.data.actorId) : undefined, - adv :dataset.adv || 0, - attribute : eval(`actor.data.data.attributes.${weaponData.properties.attackAttribute}`), - aptitude : eval(`actor.data.data.aptitudes.${weaponData.properties.attackAptitude}`), - label : (weapon.name) ? weapon.name : game.i18n.localize('BOL.ui.noWeaponName'), - description : actor.name + " - " + game.i18n.localize('BOL.ui.weaponAttack'), - } - console.log("WEAPON!", attackDef, weaponData); - return this.weaponRollDialog(attackDef); + static options() { + return { classes: ["bol", "dialog"] }; } - /* -------------------------------------------- */ - /* ROLL DIALOGS */ - /* -------------------------------------------- */ - static async attributeRollDialog(actor, actorData, attribute, label, description, adv, mod, onEnter = "submit") { - const rollOptionTpl = 'systems/bol/templates/dialogs/attribute-roll-dialog.hbs'; - const dialogData = { - adv:adv, - mod: mod, - attr:attribute, - careers:actorData.features.careers, - boons:actorData.features.boons, - flaws:actorData.features.flaws - }; - - const rollOptionContent = await renderTemplate(rollOptionTpl, dialogData); - let d = new Dialog({ - title: label, - content: rollOptionContent, - buttons: { - cancel: { - icon: '', - label: game.i18n.localize("BOL.ui.cancel"), - callback: () => { - } - }, - submit: { - icon: '', - label: game.i18n.localize("BOL.ui.submit"), - callback: (html) => { - const attr = html.find('#attr').val(); - const adv = html.find('#adv').val(); - const mod = html.find('#mod').val(); - let careers = html.find('#career').val(); - const career = (careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i))); - const isMalus = adv < 0; - const dicePool = (isMalus) ? 2 - parseInt(adv) : 2 + parseInt(adv); - const attrValue = eval(`actor.data.data.attributes.${attr}.value`); - const modifiers = parseInt(attrValue) + parseInt(mod) + parseInt(career); - const formula = (isMalus) ? dicePool + "d6kl2 + " + modifiers : dicePool + "d6kh2 + " + modifiers; - let r = new BoLDefaultRoll(label, formula, description); - r.roll(actor); - } - } - }, - default: onEnter, - close: () => {} - }, this.options()); - return d.render(true); - } + static attributeCheck(actor, actorData, dataset, event) { + // const elt = $(event.currentTarget)[0]; + // let key = elt.attributes["data-rolling"].value; + const key = dataset.key; + const adv = dataset.adv; + let attribute = eval(`actor.data.data.attributes.${key}`); + let label = (attribute.label) ? game.i18n.localize(attribute.label) : null; + let description = actor.name + " - " + game.i18n.localize('BOL.ui.attributeCheck') + " - " + game.i18n.localize(attribute.label); + return this.attributeRollDialog(actor, actorData, attribute, label, description, adv, 0); + } - static async weaponRollDialog( attackDef) { - const rollOptionTpl = 'systems/bol/templates/dialogs/weapon-roll-dialog.hbs'; - const dialogData = { - attr:attackDef.attribute, - adv:attackDef.adv, - mod: attackDef.mod, - apt:attackDef.aptitude, - weapon: attackDef.weapon, - attackId: attackDef.id, - careers: attackDef.attackerData.features.careers, - boons: attackDef.attackerData.features.boons, - flaws: attackDef.attackerData.features.flaws, - }; - if ( attackDef.defender) { - dialogData.defence = attackDef.defender.defenseValue, - dialogData.shieldBlock = 'none' - let shields = attackDef.defender.shields - for( let shield of shields) { - dialogData.shieldBlock = (shield.data.properties.blocking.blockingAll) ? 'blockall' : 'blockone'; - dialogData.shieldAttackMalus = (shield.data.properties.blocking.malus)? shield.data.properties.blocking.malus : 1; - dialogData.applyShieldMalus = false + static aptitudeCheck(actor, actorData, dataset, event) { + // const elt = $(event.currentTarget)[0]; + // let key = elt.attributes["data-rolling"].value; + const key = dataset.key; + const adv = dataset.adv; + let aptitude = eval(`actor.data.data.aptitudes.${key}`); + let label = (aptitude.label) ? game.i18n.localize(aptitude.label) : null; + let description = actor.name + " - " + game.i18n.localize('BOL.ui.aptitudeCheck') + " - " + game.i18n.localize(aptitude.label); + return this.aptitudeRollDialog(actor, actorData, aptitude, label, description, adv, 0); + } + + static weaponCheck(actor, actorData, dataset, event) { + // const elt = $(event.currentTarget)[0]; + // let key = elt.attributes["data-rolling"].value; + let target = BoLUtility.getTarget() + const li = $(event.currentTarget).parents(".item"); + const weapon = actor.items.get(li.data("item-id")); + if (!weapon) { + ui.notifications.warn("Unable to find weapon !"); + return; + } + let weaponData = weapon.data.data; + let attackDef = { + id: randomID(16), + attacker: actor, + attackerData: actorData, + weapon: weapon, + mod: 0, + target: target, + defender: (target) ? game.actors.get(target.data.actorId) : undefined, + adv: dataset.adv || 0, + attribute: eval(`actor.data.data.attributes.${weaponData.properties.attackAttribute}`), + aptitude: eval(`actor.data.data.aptitudes.${weaponData.properties.attackAptitude}`), + label: (weapon.name) ? weapon.name : game.i18n.localize('BOL.ui.noWeaponName'), + description: actor.name + " - " + game.i18n.localize('BOL.ui.weaponAttack'), + } + console.log("WEAPON!", attackDef, weaponData); + return this.weaponRollDialog(attackDef); + } + + /* -------------------------------------------- */ + /* ROLL DIALOGS */ + /* -------------------------------------------- */ + static async attributeRollDialog(actor, actorData, attribute, label, description, adv, mod, onEnter = "submit") { + const rollOptionTpl = 'systems/bol/templates/dialogs/attribute-roll-dialog.hbs'; + const dialogData = { + adv: adv, + mod: mod, + attr: attribute, + careers: actorData.features.careers, + boons: actorData.features.boons, + flaws: actorData.features.flaws + }; + + const rollOptionContent = await renderTemplate(rollOptionTpl, dialogData); + let d = new Dialog({ + title: label, + content: rollOptionContent, + buttons: { + cancel: { + icon: '', + label: game.i18n.localize("BOL.ui.cancel"), + callback: () => { + } + }, + submit: { + icon: '', + label: game.i18n.localize("BOL.ui.submit"), + callback: (html) => { + const attr = html.find('#attr').val(); + const adv = html.find('#adv').val(); + const mod = html.find('#mod').val(); + let careers = html.find('#career').val(); + const career = (careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i))); + const isMalus = adv < 0; + const dicePool = (isMalus) ? 2 - parseInt(adv) : 2 + parseInt(adv); + const attrValue = eval(`actor.data.data.attributes.${attr}.value`); + const modifiers = parseInt(attrValue) + parseInt(mod) + parseInt(career); + const formula = (isMalus) ? dicePool + "d6kl2 + " + modifiers : dicePool + "d6kh2 + " + modifiers; + let r = new BoLDefaultRoll(label, formula, description); + r.roll(actor); + } } + }, + default: onEnter, + close: () => { } + }, this.options()); + return d.render(true); + } + + static async weaponRollDialog(attackDef) { + const rollOptionTpl = 'systems/bol/templates/dialogs/weapon-roll-dialog.hbs'; + const dialogData = { + attr: attackDef.attribute, + adv: attackDef.adv, + mod: attackDef.mod, + apt: attackDef.aptitude, + weapon: attackDef.weapon, + attackId: attackDef.id, + careers: attackDef.attackerData.features.careers, + boons: attackDef.attackerData.features.boons, + flaws: attackDef.attackerData.features.flaws, + }; + if (attackDef.defender) { + dialogData.defence = attackDef.defender.defenseValue, + dialogData.shieldBlock = 'none' + let shields = attackDef.defender.shields + for (let shield of shields) { + dialogData.shieldBlock = (shield.data.properties.blocking.blockingAll) ? 'blockall' : 'blockone'; + dialogData.shieldAttackMalus = (shield.data.properties.blocking.malus) ? shield.data.properties.blocking.malus : 1; + dialogData.applyShieldMalus = false } - const rollOptionContent = await renderTemplate(rollOptionTpl, dialogData); - let d = new Dialog({ - title: attackDef.label, - content: rollOptionContent, - buttons: { - cancel: { - icon: '', - label: game.i18n.localize("BOL.ui.cancel"), - callback: () => { - } - }, - submit: { - icon: '', - label: game.i18n.localize("BOL.ui.submit"), - callback: (html) => { - const attr = html.find('#attr').val(); - const apt = html.find('#apt').val(); - const adv = html.find('#adv').val(); - const mod = html.find('#mod').val() || 0; - - let shieldMalus = 0; - const applyShieldMalus = html.find('#applyShieldMalus').val() || false; - if (applyShieldMalus || dialogData.shieldBlock =='blockall') { - shieldMalus = dialogData.shieldAttackMalus; - } - - let careers = html.find('#career').val(); - const career = (careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i))); - const isMalus = adv < 0; - const dicePool = (isMalus) ? 2 - parseInt(adv) : 2 + parseInt(adv); - const attrValue = eval(`attackDef.attacker.data.data.attributes.${attr}.value`); - const aptValue = eval(`attackDef.attacker.data.data.aptitudes.${apt}.value`); - const modifiers = parseInt(attrValue) + parseInt(aptValue) + parseInt(mod) + parseInt(career) - dialogData.defence - shieldMalus; - const formula = (isMalus) ? dicePool + "d6kl2 + " + modifiers : dicePool + "d6kh2 + " + modifiers; - attackDef.formula = formula; - let r = new BoLAttackRoll(attackDef); - r.roll(); - } - } - }, - default: 'submit', - close: () => {} - }, this.options()); - return d.render(true); } + const rollOptionContent = await renderTemplate(rollOptionTpl, dialogData); + let d = new Dialog({ + title: attackDef.label, + content: rollOptionContent, + buttons: { + cancel: { + icon: '', + label: game.i18n.localize("BOL.ui.cancel"), + callback: () => { + } + }, + submit: { + icon: '', + label: game.i18n.localize("BOL.ui.submit"), + callback: (html) => { + const attr = html.find('#attr').val(); + const apt = html.find('#apt').val(); + const adv = html.find('#adv').val(); + const mod = html.find('#mod').val() || 0; - static async aptitudeRollDialog(actor, actorData, aptitude, label, description, adv, mod, onEnter = "submit") { - const rollOptionTpl = 'systems/bol/templates/dialogs/aptitude-roll-dialog.hbs'; - const dialogData = { - adv:adv, - mod: mod, - apt:aptitude, - careers:actorData.features.careers, - boons:actorData.features.boons, - flaws:actorData.features.flaws - }; - const rollOptionContent = await renderTemplate(rollOptionTpl, dialogData); - let d = new Dialog({ - title: label, - content: rollOptionContent, - buttons: { - cancel: { - icon: '', - label: game.i18n.localize("BOL.ui.cancel"), - callback: () => { - } - }, - submit: { - icon: '', - label: game.i18n.localize("BOL.ui.submit"), - callback: (html) => { - const apt = html.find('#apt').val(); - const adv = html.find('#adv').val(); - const mod = html.find('#mod').val(); - let careers = html.find('#career').val(); - const career = (careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i))); - const isMalus = adv < 0; - const dicePool = (isMalus) ? 2 - parseInt(adv) : 2 + parseInt(adv); - const aptValue = eval(`actor.data.data.aptitudes.${apt}.value`); - const modifiers = parseInt(aptValue) + parseInt(mod) + parseInt(career); - const formula = (isMalus) ? dicePool + "d6kl2 + " + modifiers : dicePool + "d6kh2 + " + modifiers; - let r = new BoLDefaultRoll(label, formula, description); - r.roll(actor); - } - } - }, - default: onEnter, - close: () => {} - }, this.options()); - return d.render(true); - } + let shieldMalus = 0; + const applyShieldMalus = html.find('#applyShieldMalus').val() || false; + if (applyShieldMalus || dialogData.shieldBlock == 'blockall') { + shieldMalus = dialogData.shieldAttackMalus; + } + + let careers = html.find('#career').val(); + const career = (careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i))); + const isMalus = adv < 0; + const dicePool = (isMalus) ? 2 - parseInt(adv) : 2 + parseInt(adv); + const attrValue = eval(`attackDef.attacker.data.data.attributes.${attr}.value`); + const aptValue = eval(`attackDef.attacker.data.data.aptitudes.${apt}.value`); + const modifiers = parseInt(attrValue) + parseInt(aptValue) + parseInt(mod) + parseInt(career) - dialogData.defence - shieldMalus; + const formula = (isMalus) ? dicePool + "d6kl2 + " + modifiers : dicePool + "d6kh2 + " + modifiers; + attackDef.formula = formula; + let r = new BoLAttackRoll(attackDef); + r.roll(); + } + } + }, + default: 'submit', + close: () => { } + }, this.options()); + return d.render(true); + } + + static async aptitudeRollDialog(actor, actorData, aptitude, label, description, adv, mod, onEnter = "submit") { + const rollOptionTpl = 'systems/bol/templates/dialogs/aptitude-roll-dialog.hbs'; + const dialogData = { + adv: adv, + mod: mod, + apt: aptitude, + careers: actorData.features.careers, + boons: actorData.features.boons, + flaws: actorData.features.flaws + }; + const rollOptionContent = await renderTemplate(rollOptionTpl, dialogData); + let d = new Dialog({ + title: label, + content: rollOptionContent, + buttons: { + cancel: { + icon: '', + label: game.i18n.localize("BOL.ui.cancel"), + callback: () => { + } + }, + submit: { + icon: '', + label: game.i18n.localize("BOL.ui.submit"), + callback: (html) => { + const apt = html.find('#apt').val(); + const adv = html.find('#adv').val(); + const mod = html.find('#mod').val(); + let careers = html.find('#career').val(); + const career = (careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i))); + const isMalus = adv < 0; + const dicePool = (isMalus) ? 2 - parseInt(adv) : 2 + parseInt(adv); + const aptValue = eval(`actor.data.data.aptitudes.${apt}.value`); + const modifiers = parseInt(aptValue) + parseInt(mod) + parseInt(career); + const formula = (isMalus) ? dicePool + "d6kl2 + " + modifiers : dicePool + "d6kh2 + " + modifiers; + let r = new BoLDefaultRoll(label, formula, description); + r.roll(actor); + } + } + }, + default: onEnter, + close: () => { } + }, this.options()); + return d.render(true); + } } export class BoLDefaultRoll { - constructor(label, formula, description, isWeapon=false){ - this._label = label; - this._formula = formula; - this._isSuccess = false; - this._isCritical = false; - this._isFumble = false; - this._isWeapon = isWeapon; - this._description = description; - } + constructor(label, formula, description, isWeapon = false) { + this._label = label; + this._formula = formula; + this._isSuccess = false; + this._isCritical = false; + this._isFumble = false; + this._isWeapon = isWeapon; + this._description = description; + } - async roll(actor){ - const r = new Roll(this._formula); - await r.roll({"async": true}); - const activeDice = r.terms[0].results.filter(r => r.active); - const diceTotal = activeDice.map(r => r.result).reduce((a, b) => a + b); - this._isSuccess = (r.total >= 9); - this._isCritical = (diceTotal === 12); - this._isFumble = (diceTotal === 2); - this._buildChatMessage(actor).then(msgFlavor => { - r.toMessage({ - user: game.user.id, - flavor: msgFlavor, - speaker: ChatMessage.getSpeaker({actor: actor}), - flags : {msgType : "default"} - }); - }); - if (this._isSuccess && this._isWeapon) { + async roll(actor) { + const r = new Roll(this._formula); + await r.roll({ "async": true }); + const activeDice = r.terms[0].results.filter(r => r.active); + const diceTotal = activeDice.map(r => r.result).reduce((a, b) => a + b); + this._isSuccess = (r.total >= 9); + this._isCritical = (diceTotal === 12); + this._isFumble = (diceTotal === 2); + this._buildChatMessage(actor).then(msgFlavor => { + r.toMessage({ + user: game.user.id, + flavor: msgFlavor, + speaker: ChatMessage.getSpeaker({ actor: actor }), + flags: { msgType: "default" } + }); + }); + if (this._isSuccess && this._isWeapon) { - } } + } - _buildChatMessage(actor) { - const rollMessageTpl = 'systems/bol/templates/chat/rolls/default-roll-card.hbs'; - const tplData = { - actor : actor, - label : this._label, - isSuccess : this._isSuccess, - isFailure : !this._isSuccess, - isCritical : this._isCritical, - isFumble : this._isFumble, - hasDescription : this._description && this._description.length > 0, - description : this._description - }; - return renderTemplate(rollMessageTpl, tplData); - } + _buildChatMessage(actor) { + const rollMessageTpl = 'systems/bol/templates/chat/rolls/default-roll-card.hbs'; + const tplData = { + actor: actor, + label: this._label, + isSuccess: this._isSuccess, + isFailure: !this._isSuccess, + isCritical: this._isCritical, + isFumble: this._isFumble, + hasDescription: this._description && this._description.length > 0, + description: this._description + }; + return renderTemplate(rollMessageTpl, tplData); + } } export class BoLAttackRoll { - constructor(attackDef){ - this.attackDef = attackDef; - this._isSuccess = false; - this._isCritical = false; - this._isFumble = false; -} + constructor(attackDef) { + this.attackDef = attackDef; + this._isSuccess = false; + this._isCritical = false; + this._isFumble = false; + } - async roll(){ - const r = new Roll(this.attackDef.formula); - await r.roll({"async": false}); - const activeDice = r.terms[0].results.filter(r => r.active); - const diceTotal = activeDice.map(r => r.result).reduce((a, b) => a + b); - this._isSuccess = (r.total >= 9); - this._isCritical = (diceTotal === 12); - this._isFumble = (diceTotal === 2); - this._buildChatMessage(this.attackDef.attacker).then(msgFlavor => { - r.toMessage({ - user: game.user.id, - flavor: msgFlavor, - speaker: ChatMessage.getSpeaker({actor: this.attackDef.attacker}), - flags : {msgType : "default"} - }); + async roll() { + const r = new Roll(this.attackDef.formula); + await r.roll({ "async": false }); + //await BoLUtility.showDiceSoNice(r); + const activeDice = r.terms[0].results.filter(r => r.active); + const diceTotal = activeDice.map(r => r.result).reduce((a, b) => a + b); + this._isSuccess = (r.total >= 9); + this._isCritical = (diceTotal === 12); + this._isFumble = (diceTotal === 2); + this._buildChatMessage(this.attackDef.attacker).then(msgFlavor => { + r.toMessage({ + user: game.user.id, + flavor: msgFlavor, + speaker: ChatMessage.getSpeaker({ actor: this.attackDef.attacker }), + flags: { msgType: "default" } }); + }); - if (this._isSuccess ) { - let attrDamage = this.attackDef.weapon.data.data.properties.damageAttribute; - let weaponFormula = BoLUtility.getDamageFormula(this.attackDef.weapon.data.data.properties.damage) - let damageFormula = weaponFormula + "+" + this.attackDef.attacker.data.data.attributes[attrDamage].value; - this.damageRoll = new Roll(damageFormula); - await this.damageRoll.roll({"async": false}); - // Update attackDef object - this.attackDef.damageFormula = damageFormula; - this.attackDef.damageRoll = this.damageRoll; + if (this._isSuccess) { + let attrDamage = this.attackDef.weapon.data.data.properties.damageAttribute; + let weaponFormula = BoLUtility.getDamageFormula(this.attackDef.weapon.data.data.properties.damage) + let damageFormula = weaponFormula + "+" + this.attackDef.attacker.data.data.attributes[attrDamage].value; + this.damageRoll = new Roll(damageFormula); + await this.damageRoll.roll({ "async": false }); + //await BoLUtility.showDiceSoNice(this.damageRoll); + // Update attackDef object + this.attackDef.damageFormula = damageFormula; + this.attackDef.damageRoll = this.damageRoll; - this._buildDamageChatMessage(this.attackDef.attacker, this.attackDef.weapon, this.damageRoll.total).then(msgFlavor => { - this.damageRoll.toMessage({ - user: game.user.id, - flavor: msgFlavor, - speaker: ChatMessage.getSpeaker({actor: this.attackDef.attacker}), - flags : {msgType : "default"} - }).then( result => { - if (this.attackDef.target) { - // Broadcast to GM or process it directly in case of GM defense - if ( !game.user.isGM) { - game.socket.emit("system.bol", { msg: "msg_attack_success", data: this.attackDef }); - } else { - BoLUtility.processAttackSuccess( this.attackDef); - } - } - }); + this._buildDamageChatMessage(this.attackDef.attacker, this.attackDef.weapon, this.damageRoll.total).then(msgFlavor => { + this.damageRoll.toMessage({ + user: game.user.id, + flavor: msgFlavor, + speaker: ChatMessage.getSpeaker({ actor: this.attackDef.attacker }), + flags: { msgType: "default" } + }); }); - } + if (this._isCritical) { + ChatMessage.create({ + alias: this.attackDef.attacker.name, + whisper: BoLUtility.getWhisperRecipientsAndGMs(this.attackDef.attacker.name), + content: await renderTemplate('systems/bol/templates/chat/rolls/attack-heroic-card.hbs', { + attackId: attackDef.id, + attacker: attackDef.attacker, + defender: attackDef.defender, + defenderWeapons: defenderWeapons, + damageTotal: attackDef.damageRoll.total + }) + }) + } else { + BoLUtility.sendAttackSuccess( this.attackDef); + } } + } _buildDamageChatMessage(actor, weapon, total) { - const rollMessageTpl = 'systems/bol/templates/chat/rolls/damage-roll-card.hbs'; - const tplData = { - actor : actor, - label : this._label, - weapon: weapon, - damage: total, - }; - return renderTemplate(rollMessageTpl, tplData); -} + const rollMessageTpl = 'systems/bol/templates/chat/rolls/damage-roll-card.hbs'; + const tplData = { + actor: actor, + label: this._label, + weapon: weapon, + damage: total, + isCritical: this._isCritical, + }; + return renderTemplate(rollMessageTpl, tplData); + } _buildChatMessage(actor) { - const rollMessageTpl = 'systems/bol/templates/chat/rolls/default-roll-card.hbs'; - const tplData = { - actor : actor, - label : this._label, - isSuccess : this._isSuccess, - isFailure : !this._isSuccess, - isCritical : this._isCritical, - isFumble : this._isFumble, - hasDescription : this._description && this._description.length > 0, - description : this._description - }; - return renderTemplate(rollMessageTpl, tplData); - } + const rollMessageTpl = 'systems/bol/templates/chat/rolls/default-roll-card.hbs'; + const tplData = { + actor: actor, + label: this._label, + isSuccess: this._isSuccess, + isFailure: !this._isSuccess, + isCritical: this._isCritical, + isFumble: this._isFumble, + hasDescription: this._description && this._description.length > 0, + description: this._description + }; + return renderTemplate(rollMessageTpl, tplData); + } } // export class BoLWeaponRoll { diff --git a/module/system/bol-utility.js b/module/system/bol-utility.js index 320bfed..1dcfe97 100644 --- a/module/system/bol-utility.js +++ b/module/system/bol-utility.js @@ -95,9 +95,31 @@ export class BoLUtility { game.socket.emit("system.fvtt-fragged-kingdom", { msg: "msg_gm_chat_message", data: chatGM }); } + /* -------------------------------------------- */ + static sendAttackSuccess(attackDef) { + if (attackDef.target) { + // Broadcast to GM or process it directly in case of GM defense + if (!game.user.isGM) { + game.socket.emit("system.bol", { msg: "msg_attack_success", data: attackDef }); + } else { + BoLUtility.processAttackSuccess(attackDef); + } + } + } + /* -------------------------------------------- */ static async chatListeners(html) { // Damage handling + html.on("click", '.damage-increase', event => { + let attackId = event.currentTarget.attributes['data-attack-id'].value; + let damageMode = event.currentTarget.attributes['data-damage-mode'].value; + if ( game.user.isGM) { + BoLUtility.processDamageIncrease(event, attackId, damageMode) + } else { + game.socket.emit("system.bol", { msg: "msg_damage_increase", data: {event: event, attackId: attackId, damageMode: damageMode} }); + } + }); + html.on("click", '.damage-handling', event => { let attackId = event.currentTarget.attributes['data-attack-id'].value; let defenseMode = event.currentTarget.attributes['data-defense-mode'].value; @@ -111,6 +133,31 @@ export class BoLUtility { }); } + /* -------------------------------------------- */ + static async processDamageIncrease(event, attackId, damageMode ) { + if ( !game.user.isGM) { + return; + } + BoLUtility.removeChatMessageId(BoLUtility.findChatMessageId(event.currentTarget)); + + // Only GM process this + let attackDef = this.attackStore[attackId]; + if (attackDef) { + attackDef.damageMode = damageMode; + if (defenseMode == 'damage-plus-6') { + attackDef.damageRoll.total += 6; + } + if (defenseMode == 'damage-plus-12') { + attackDef.damageRoll.total += 12; + attackDef.defender.subHeroPoints(1); + } + if (defenseMode == 'damage-normal') { + // Do nothing ! + } + BoLUtility.sendAttackSuccess( this.attackDef); + } + } + /* -------------------------------------------- */ static async processDamageHandling(event, attackId, defenseMode, weaponId=-1) { if ( !game.user.isGM) { @@ -120,14 +167,14 @@ export class BoLUtility { // Only GM process this let attackDef = this.attackStore[attackId]; - console.log("DEFENSE2", attackId, defenseMode, weaponId, attackDef); if (attackDef) { attackDef.defenseMode = defenseMode; if (defenseMode == 'damage-with-armor') { - let armorFormula = attackDef.defender.getArmorFormula(); + let armorFormula = attackDef.defender.getArmorFormula(); attackDef.rollArmor = new Roll(armorFormula) attackDef.rollArmor.roll( {async: false} ); - attackDef.finalDamage = attackDef.damageRoll.total - attackDef.rollArmor.total; + attackDef.armorProtect = (attackDef.rollArmor.total<0) ? 0 : attackDef.rollArmor.total; + attackDef.finalDamage = attackDef.damageRoll.total - attackDef.armorProtect; attackDef.finalDamage = (attackDef.finalDamage<0) ? 0 : attackDef.finalDamage; attackDef.defender.sufferDamage(attackDef.finalDamage); } @@ -136,9 +183,13 @@ export class BoLUtility { attackDef.defender.sufferDamage(attackDef.finalDamage); } if (defenseMode == 'hero-reduce-damage') { + let armorFormula = attackDef.defender.getArmorFormula(); + attackDef.rollArmor = new Roll(armorFormula) + attackDef.rollArmor.roll( {async: false} ); + attackDef.armorProtect = (attackDef.rollArmor.total<0) ? 0 : attackDef.rollArmor.total; attackDef.rollHero = new Roll("1d6"); attackDef.rollHero.roll( {async: false} ); - attackDef.finalDamage = attackDef.damageRoll.total - attackDef.rollHero.total; + attackDef.finalDamage = attackDef.damageRoll.total - attackDef.rollHero.total - attackDef.armorProtect; attackDef.finalDamage = (attackDef.finalDamage<0) ? 0 : attackDef.finalDamage; attackDef.defender.sufferDamage(attackDef.finalDamage); attackDef.defender.subHeroPoints(1); @@ -157,6 +208,7 @@ export class BoLUtility { rollArmor: attackDef.rollArmor, rollHero: attackDef.rollHero, weaponHero : attackDef.weaponHero, + armorProtect: attackDef.armorProtect, defender: attackDef.defender, defenseMode: attackDef.defenseMode, finalDamage: attackDef.finalDamage @@ -313,6 +365,9 @@ export class BoLUtility { if (sockmsg.name == "msg_damage_handling") { BoLUtility.processDamageHandling(sockmsg.data.event, sockmsg.data.attackId, sockmsg.data.defenseMode) } + if (sockmsg.name == "msg_damage_increase") { + BoLUtility.processDamageIncrease(sockmsg.data.event, sockmsg.data.attackId, sockmsg.data.damageMode) + } } /* -------------------------------------------- */ @@ -338,6 +393,30 @@ export class BoLUtility { let formula = nbDice + "d" + res[2] + postForm + ((res[modIndex]) ? res[modIndex] : ""); return formula; } + /* -------------------------------------------- */ + static 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 = BoLUtility.getUsers(user => user.isGM); + break; + case "roll": //everybody + whisper = BoLUtility.getUsers(user => user.active); + break; + case "selfroll": + whisper = [game.user.id]; + break; + } + await game.dice3d.showForRoll(roll, game.user, true, whisper, blind); + } + } + } /* -------------------------------------------- */ static async confirmDelete(actorSheet, li) { diff --git a/system.json b/system.json index d5fefc5..611ad13 100644 --- a/system.json +++ b/system.json @@ -7,7 +7,7 @@ "url": "https://github.com/ZigmundKreud/bol", "license": "LICENSE.txt", "flags": {}, - "version": "0.8.9.0", + "version": "0.8.9.1", "minimumCoreVersion": "0.8.6", "compatibleCoreVersion": "9", "scripts": [], diff --git a/templates/chat/rolls/attack-heroic-card.hbs b/templates/chat/rolls/attack-heroic-card.hbs new file mode 100644 index 0000000..27f4c92 --- /dev/null +++ b/templates/chat/rolls/attack-heroic-card.hbs @@ -0,0 +1,6 @@ +{{attacker.name}} +Jet Héroïque ! + + + + diff --git a/templates/chat/rolls/defense-result-card.hbs b/templates/chat/rolls/defense-result-card.hbs index 69807bd..30e08c6 100644 --- a/templates/chat/rolls/defense-result-card.hbs +++ b/templates/chat/rolls/defense-result-card.hbs @@ -3,13 +3,14 @@