Enhance and bugfixes for combat #4
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
const rollOptionContent = await renderTemplate(rollOptionTpl, dialogData);
|
||||
let d = new Dialog({
|
||||
title: label,
|
||||
content: rollOptionContent,
|
||||
buttons: {
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("BOL.ui.cancel"),
|
||||
callback: () => {
|
||||
}
|
||||
},
|
||||
submit: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
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 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 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
|
||||
/* -------------------------------------------- */
|
||||
/* 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: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("BOL.ui.cancel"),
|
||||
callback: () => {
|
||||
}
|
||||
},
|
||||
submit: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
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: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("BOL.ui.cancel"),
|
||||
callback: () => {
|
||||
}
|
||||
},
|
||||
submit: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
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: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("BOL.ui.cancel"),
|
||||
callback: () => {
|
||||
}
|
||||
},
|
||||
submit: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
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: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("BOL.ui.cancel"),
|
||||
callback: () => {
|
||||
}
|
||||
},
|
||||
submit: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
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: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("BOL.ui.cancel"),
|
||||
callback: () => {
|
||||
}
|
||||
},
|
||||
submit: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
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 {
|
||||
|
@ -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();
|
||||
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) {
|
||||
|
@ -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": [],
|
||||
|
6
templates/chat/rolls/attack-heroic-card.hbs
Normal file
6
templates/chat/rolls/attack-heroic-card.hbs
Normal file
@ -0,0 +1,6 @@
|
||||
<img class="chat-icon" src="{{attacker.img}}" alt="{{attacker.name}}"/>
|
||||
Jet Héroïque !
|
||||
<button class="damage-increase" data-damage-mode="damage-plus-6" data-attack-id="{{attackId}}">Augmenter les dommages de 6</button>
|
||||
<button class="damage-increase" data-damage-mode="damage-plus-12" data-attack-id="{{attackId}}">Augmenter les dommages de 12 (1 point d'Heroisme)</button>
|
||||
<button class="damage-increase" data-damage-mode="damage-normal" data-attack-id="{{attackId}}">Laisser les dommages inchangés</button>
|
||||
|
@ -3,13 +3,14 @@
|
||||
<ul>
|
||||
<li>
|
||||
{{#if (eq defenseMode "damage-with-armor")}}
|
||||
Protection de l'armure : {{rollArmor.total}}.
|
||||
Protection de l'armure : {{armorProtect}}.
|
||||
{{/if}}
|
||||
{{#if (eq defenseMode "damage-without-armor")}}
|
||||
Aucune protection d'armure !
|
||||
{{/if}}
|
||||
{{#if (eq defenseMode "hero-reduce-damage")}}
|
||||
Un point d'héroisme dépensé, pour une réduction des dommages de {{rollHero.total}}.
|
||||
Protection de l'armure : {{armorProtect}}.
|
||||
Un point d'héroisme dépensé, pour une réduction des dommages supplémentaire de {{rollHero.total}}.
|
||||
{{/if}}
|
||||
{{#if (eq defenseMode "hero-in-extremis")}}
|
||||
Aucun dommage encaissé, grâce à la parade in-extremis avec {{weaponHero.name}}. L'arme a été détruite pendant cette parade !
|
||||
|
Loading…
Reference in New Issue
Block a user