diff --git a/lang/en.json b/lang/en.json index 9e34162..c6fbfa6 100644 --- a/lang/en.json +++ b/lang/en.json @@ -92,7 +92,9 @@ "BOL.ui.treasure" : "Trésor", "BOL.ui.vehicles" : "Véhicules/Montures", "BOL.ui.misc" : "Divers", - + "BOL.ui.noWeaponName" : "Unknown Weapon", + "BOL.ui.targetDefence": "Defence", + "BOL.featureCategory.origins": "Origines", "BOL.featureCategory.races": "Races", "BOL.featureCategory.careers": "Carrières", diff --git a/lang/fr.json b/lang/fr.json index b7a7dd7..9a150e9 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -94,6 +94,8 @@ "BOL.ui.misc" : "Divers", "BOL.ui.vehicleProperties" : " Propriétés de véhicule", "BOL.ui.speed" : "Vitesse", + "BOL.ui.noWeaponName" : "Arme Inconnue", + "BOL.ui.targetDefence": "Défense", "BOL.featureCategory.origins": "Origines", "BOL.featureCategory.races": "Races", diff --git a/module/actor/actor-sheet.js b/module/actor/actor-sheet.js index eb30cf3..384bc5c 100644 --- a/module/actor/actor-sheet.js +++ b/module/actor/actor-sheet.js @@ -186,7 +186,7 @@ export class BoLActorSheet extends ActorSheet { BoLRoll.aptitudeCheck(this.actor, actorData, dataset, event); break; case "weapon": - console.log("ROLL WEAPON !!!"); // TODO + BoLRoll.weaponCheck(this.actor, actorData, dataset, event); break; default : break; } diff --git a/module/actor/actor.js b/module/actor/actor.js index c6f617e..3941733 100644 --- a/module/actor/actor.js +++ b/module/actor/actor.js @@ -41,6 +41,9 @@ export class BoLActor extends Actor { get aptitudes() { return Object.values(this.data.data.aptitudes); } + get defenseValue() { + return this.data.data.aptitudes.def.value; + } get resources() { return Object.values(this.data.data.resources); } @@ -84,7 +87,6 @@ export class BoLActor extends Actor { get protections() { return this.armors.concat(this.helms).concat(this.shields) } - get melee() { return this.weapons.filter(i => i.data.properties.melee === true); } diff --git a/module/controllers/bol-rolls.js b/module/controllers/bol-rolls.js index c640a9b..b72a840 100644 --- a/module/controllers/bol-rolls.js +++ b/module/controllers/bol-rolls.js @@ -1,3 +1,5 @@ +import { BoLUtility } from "../system/bol-utility.js"; + export class BoLRoll { static options() { return { classes: ["bol", "dialog"] }; @@ -25,6 +27,40 @@ export class BoLRoll { 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() + if ( !target) { + ui.notifications.warn("No target selected for attack !"); + return; + } + const li = $(event.currentTarget).parents(".item"); + console.log("ITEM", target); + 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: game.actors.get(target.data.actorId), + 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 */ /* -------------------------------------------- */ @@ -58,7 +94,7 @@ export class BoLRoll { const adv = html.find('#adv').val(); const mod = html.find('#mod').val(); let careers = html.find('#career').val(); - const career = (!careers) ? 0 : Math.max(...careers.map(i => parseInt(i))); + 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`); @@ -75,6 +111,59 @@ export class BoLRoll { 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, + defence: attackDef.defender.defenseValue, + }; + 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 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; + 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 = { @@ -104,7 +193,7 @@ export class BoLRoll { const adv = html.find('#adv').val(); const mod = html.find('#mod').val(); let careers = html.find('#career').val(); - const career = (!careers) ? 0 : Math.max(...careers.map(i => parseInt(i))); + 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`); @@ -123,12 +212,13 @@ export class BoLRoll { } export class BoLDefaultRoll { - constructor(label, formula, 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; } @@ -148,6 +238,9 @@ export class BoLDefaultRoll { flags : {msgType : "default"} }); }); + if (this._isSuccess && this._isWeapon) { + + } } _buildChatMessage(actor) { @@ -167,6 +260,75 @@ export class BoLDefaultRoll { } +export class BoLAttackRoll { + 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": 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(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": true}); + 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"} + }); + }); + } + } + + _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); +} + + _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 BoLWeaponRoll { // constructor(actor, label, formula, isCritical, description){ // this._label = label; diff --git a/module/system/bol-utility.js b/module/system/bol-utility.js index da69aa9..09c97f8 100644 --- a/module/system/bol-utility.js +++ b/module/system/bol-utility.js @@ -179,6 +179,30 @@ export class BoLUtility { }); } + /* -------------------------------------------- */ + static getDamageFormula( damageString) { + if (damageString[0] == 'd') {damageString = "1" + damageString} // Help parsing + var myReg = new RegExp('(\\d+)[dD]([\\d]+)([MB]*)?([\\+\\d]*)?', 'g'); + let res = myReg.exec(damageString); + let nbDice = parseInt(res[1]); + let postForm = 'kh'+nbDice; + let modIndex = 3; + if ( res[3]) { + if ( res[3] == 'M') { + postForm = 'kl'+nbDice; + nbDice++; + modIndex = 4; + } + if ( res[3] == 'B') { + postForm = 'kh'+nbDice; + nbDice++; + modIndex = 4; + } + } + let formula = nbDice+"d"+res[2] + postForm + ((res[modIndex]) ? res[modIndex] : ""); + return formula; + } + /* -------------------------------------------- */ static async confirmDelete(actorSheet, li) { let itemId = li.data("item-id"); diff --git a/templates/chat/rolls/damage-roll-card.hbs b/templates/chat/rolls/damage-roll-card.hbs new file mode 100644 index 0000000..4d6cb4c --- /dev/null +++ b/templates/chat/rolls/damage-roll-card.hbs @@ -0,0 +1,7 @@ + +