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 bd1357c..384bc5c 100644 --- a/module/actor/actor-sheet.js +++ b/module/actor/actor-sheet.js @@ -185,6 +185,9 @@ export class BoLActorSheet extends ActorSheet { case "aptitude" : BoLRoll.aptitudeCheck(this.actor, actorData, dataset, event); break; + case "weapon": + 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/module/system/settings.js b/module/system/settings.js index b0c530d..1e061c9 100644 --- a/module/system/settings.js +++ b/module/system/settings.js @@ -9,5 +9,15 @@ export const registerSystemSettings = function() { type: Boolean, onChange: lang => window.location.reload() }); + + game.settings.register("bol", "rollArmor", { + name: "Effectuer des jets pour les armures", + hint: "Effectue un jet de dés pour les armures (valeur fixe si désactivé)", + scope: "world", + config: true, + default: true, + type: Boolean, + onChange: lang => window.location.reload() + }); }; diff --git a/templates/actor/parts/tabs/actor-actions.hbs b/templates/actor/parts/tabs/actor-actions.hbs index b612857..ec968ba 100644 --- a/templates/actor/parts/tabs/actor-actions.hbs +++ b/templates/actor/parts/tabs/actor-actions.hbs @@ -5,3 +5,24 @@
  • Attaque à mains nues

  • +{{#each combat as |combatType id|}} +
      +
    1. +
      {{localize combatType.label}}
      + {{#if protection}}
      {{localize "BOL.ui.protection"}}
      {{/if}} + {{#if blocking}}
      {{localize "BOL.ui.blocking"}}
      {{/if}} + {{#if weapon}}
      {{localize "BOL.ui.damages"}}
      {{/if}} + {{#if ranged}}
      {{localize "BOL.ui.range"}}
      {{else}}
      {{/if}} +
    2. + {{#each combatType.items as |item id|}} +
    3. +
      +

      {{#if ../weapon}}{{/if}}{{item.name}}{{#if ../weapon}}{{/if}}

      + {{#if ../protection}}
      {{item.data.properties.soak.value}}
      {{/if}} + {{#if ../blocking}}
      {{item.data.properties.blocking.malus}}
      {{/if}} + {{#if ../weapon}}
      {{item.data.properties.damage}}
      {{/if}} + {{#if ../ranged}}
      {{item.data.properties.range}}
      {{else}}
      {{/if}} +
    4. + {{/each}} +
    +{{/each}} diff --git a/templates/actor/parts/tabs/actor-combat.hbs b/templates/actor/parts/tabs/actor-combat.hbs index 95a423e..bb71031 100644 --- a/templates/actor/parts/tabs/actor-combat.hbs +++ b/templates/actor/parts/tabs/actor-combat.hbs @@ -8,17 +8,16 @@ -{{#each data.combat as | combat id|}} - {{#if (gt (count combat.items) 0)}} +{{#each combat as |combatType id|}}
    1. -
      {{localize combat.label}}
      +
      {{localize combatType.label}}
      {{#if protection}}
      {{localize "BOL.ui.protection"}}
      {{/if}} {{#if blocking}}
      {{localize "BOL.ui.blocking"}}
      {{/if}} {{#if weapon}}
      {{localize "BOL.ui.damages"}}
      {{/if}} {{#if ranged}}
      {{localize "BOL.ui.range"}}
      {{else}}
      {{/if}}
    2. - {{#each combat.items as |item id|}} + {{#each combatType.items as |item id|}}
    3. {{item.name}}

      @@ -29,5 +28,4 @@
    4. {{/each}}
    - {{/if}} {{/each}} 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 @@ +{{weapon.name}} +

    Dommages de l'arme : {{damage}} +{{!#if hasDescription}} + + + +{{!/if}} \ No newline at end of file diff --git a/templates/dialogs/weapon-roll-dialog.hbs b/templates/dialogs/weapon-roll-dialog.hbs new file mode 100644 index 0000000..c66e99a --- /dev/null +++ b/templates/dialogs/weapon-roll-dialog.hbs @@ -0,0 +1,72 @@ +
    + {{!-- Sheet Header --}} +
    +
    +
    +

    {{localize 'BOL.ui.weaponCheck'}}

    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    {{defence}}
    +
    + {{#if careers.items}} +
    +
    + +
    +
    + +
    +
    + {{/if}} +
    \ No newline at end of file