diff --git a/css/bol.css b/css/bol.css
index 1306fc2..f2be19b 100644
--- a/css/bol.css
+++ b/css/bol.css
@@ -617,6 +617,14 @@ a:hover {
color: darkred;
font-weight: bold;
}
+h2.good {
+ color: darkgreen;
+ font-weight: bold;
+}
+h2.bad {
+ color: darkred;
+ font-weight: bold;
+}
.message-header h2.roll {
color: darkslategrey;
font-weight: bold;
@@ -935,3 +943,6 @@ body.system-bol img#logo {
width: 64px;
height: 64px;
}
+.dialog-button {
+ max-height: 2rem;
+}
\ No newline at end of file
diff --git a/lang/en.json b/lang/en.json
index fc15c9d..dd94b73 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -140,6 +140,14 @@
"BOL.ui.rangeModifiers": "Range modifier",
"BOL.ui.money": "Bougette",
"BOL.ui.moneyTitle": "Gold & Treasure",
+ "BOL.ui.fightOption": "Fight Options",
+ "BOL.ui.none": "None",
+ "BOL.ui.fightOptionType": "Fight Options types",
+ "BOL.ui.activated": "Activated",
+ "BOL.ui.deactivated": "Deactivated",
+ "BOL.ui.status": "Status",
+ "BOL.ui.toactivated": "Active (>Désactiver)",
+ "BOL.ui.todeactivated": "Inactive (>Activer)",
"BOL.featureCategory.origins": "Origines",
"BOL.featureCategory.races": "Races",
@@ -147,6 +155,7 @@
"BOL.featureCategory.boons": "Avantages",
"BOL.featureCategory.flaws": "Désavantages",
"BOL.featureCategory.languages": "Langages",
+ "BOL.featureCategory.fightoptions": "Fight Options",
"BOL.featureSubtypes.origin": "Origine",
"BOL.featureSubtypes.race": "Race",
@@ -155,13 +164,22 @@
"BOL.featureSubtypes.flaw": "Désavantage",
"BOL.featureSubtypes.language": "Langage",
"BOL.featureSubtypes.gods": "Faith & Gods",
-
+ "BOL.featureSubtypes.fightOption": "Combat Option",
+
"BOL.bougette.nomoney": "Nothing",
"BOL.bougette.tolive": "To live",
"BOL.bougette.easylife": "Easy Life",
"BOL.bougette.luxury" : "Luxury life",
"BOL.bougette.rich": "Rich!",
+ "BOL.fightOptionTypes.armor": "Attaque au défaut d'armure",
+ "BOL.fightOptionTypes.intrepid": "Attaque intrépide",
+ "BOL.fightOptionTypes.twoweaponsdef": "Combat à 2 armes (Défense)",
+ "BOL.fightOptionTypes.twoweaponsatt": "Combat à 2 armes (Attaque)",
+ "BOL.fightOptionTypes.fulldefense": "Défense totale",
+ "BOL.fightOptionTypes.defense": "Posture défensive",
+ "BOL.fightOptionTypes.attack": "Posture offensive",
+
"BOL.itemCategory.object": "Objet",
"BOL.itemCategory.equipment": "Équipement",
"BOL.itemCategory.consumable": "Consommable",
@@ -175,6 +193,7 @@
"BOL.combatCategory.shields": "Boucliers",
"BOL.combatCategory.melee": "Armes de contact",
"BOL.combatCategory.ranged": "Armes à distance",
+ "BOL.combatCategory.fightOptions": "Fight options",
"BOL.equipmentCategory.weapon": "Arme",
"BOL.equipmentCategory.armor": "Armure",
diff --git a/lang/fr.json b/lang/fr.json
index 8e9ee15..ee4e34b 100644
--- a/lang/fr.json
+++ b/lang/fr.json
@@ -142,6 +142,14 @@
"BOL.ui.rangeModifiers": "Mod. de Portée",
"BOL.ui.money": "Bougette",
"BOL.ui.moneyTitle": "Or et Piecettes",
+ "BOL.ui.fightOption": "Options de Combat",
+ "BOL.ui.none": "Aucune",
+ "BOL.ui.fightOptionType": "Types d'options de Combat",
+ "BOL.ui.activated": "Active",
+ "BOL.ui.deactivated": "Inactive",
+ "BOL.ui.toactivated": "Active (>Désactiver)",
+ "BOL.ui.todeactivated": "Inactive (>Activer)",
+ "BOL.ui.status": "Statut",
"BOL.featureCategory.origins": "Origines",
"BOL.featureCategory.races": "Races",
@@ -149,7 +157,8 @@
"BOL.featureCategory.boons": "Avantages",
"BOL.featureCategory.flaws": "Désavantages",
"BOL.featureCategory.languages": "Langues",
-
+ "BOL.featureCategory.fightoptions": "Options de Combat",
+
"BOL.bougette.nomoney": "A sec",
"BOL.bougette.tolive": "De quoi vivre",
"BOL.bougette.easylife": "A l'aise",
@@ -163,6 +172,15 @@
"BOL.featureSubtypes.flaw": "Désavantage",
"BOL.featureSubtypes.language": "Langue",
"BOL.featureSubtypes.gods": "Dieux & Foi",
+ "BOL.featureSubtypes.fightOption": "Option de Combat",
+
+ "BOL.fightOptionTypes.armor": "Attaque au défaut d'armure",
+ "BOL.fightOptionTypes.intrepid": "Attaque intrépide",
+ "BOL.fightOptionTypes.twoweaponsdef": "Combat à 2 armes (Défense)",
+ "BOL.fightOptionTypes.twoweaponsatt": "Combat à 2 armes (Attaque)",
+ "BOL.fightOptionTypes.fulldefense": "Défense totale",
+ "BOL.fightOptionTypes.defense": "Posture défensive",
+ "BOL.fightOptionTypes.attack": "Posture offensive",
"BOL.itemCategory.object": "Objet",
"BOL.itemCategory.equipment": "Équipement",
@@ -177,7 +195,8 @@
"BOL.combatCategory.shields": "Boucliers",
"BOL.combatCategory.melee": "Armes de contact",
"BOL.combatCategory.ranged": "Armes à distance",
-
+ "BOL.combatCategory.fightOptions": "Options de combat",
+
"BOL.equipmentCategory.weapon": "Arme",
"BOL.equipmentCategory.armor": "Armure",
"BOL.equipmentCategory.protection": "Protection",
diff --git a/module/actor/actor-sheet.js b/module/actor/actor-sheet.js
index 33dd183..e9d3597 100644
--- a/module/actor/actor-sheet.js
+++ b/module/actor/actor-sheet.js
@@ -42,7 +42,12 @@ export class BoLActorSheet extends ActorSheet {
html.find('.create_item').click(ev => {
this.actor.createEmbeddedDocuments('Item', [{ name: "Nouvel Equipement", type: "item" }], { renderSheet: true });
});
-
+
+ html.find(".toggle-fight-option").click((ev) => {
+ const li = $(ev.currentTarget).parents(".item")
+ this.actor.toggleFightOption( li.data("itemId") )
+ })
+
html.find(".inc-dec-btns-alchemy").click((ev) => {
const li = $(ev.currentTarget).parents(".item");
this.actor.spendAlchemyPoint( li.data("itemId"), 1)
@@ -97,17 +102,6 @@ export class BoLActorSheet extends ActorSheet {
// Rollable abilities.
html.find('.rollable').click(this._onRoll.bind(this));
- // html.find('.roll-attribute').click(ev => {
- // this.actor.rollAttributeAptitude( $(ev.currentTarget).data("attr-key") );
- // });
- // html.find('.roll-career').click(ev => {
- // const li = $(ev.currentTarget).parents(".item");
- // this.actor.rollCareer( li.data("itemId") );
- // });
- // html.find('.roll-weapon').click(ev => {
- // const li = $(ev.currentTarget).parents(".item");
- // this.actor.rollWeapon( li.data("itemId") );
- // });
}
/* -------------------------------------------- */
@@ -131,11 +125,11 @@ export class BoLActorSheet extends ActorSheet {
formData.alchemy = this.actor.alchemy
formData.containers = this.actor.containers
formData.treasure = this.actor.treasure
- formData.treasure = this.actor.treasure
- formData.treasure = this.actor.alchemyrecipe
- formData.vehicles = this.actor.vehicles;
- formData.ammos = this.actor.ammos;
- formData.misc = this.actor.misc;
+ formData.alchemyrecipe = this.actor.alchemyrecipe
+ formData.vehicles = this.actor.vehicles
+ formData.fightoptions = this.actor.fightoptions
+ formData.ammos = this.actor.ammos
+ formData.misc = this.actor.misc
formData.combat = this.actor.buildCombat()
formData.features = this.actor.buildFeatures()
formData.isGM = game.user.isGM
@@ -148,9 +142,9 @@ export class BoLActorSheet extends ActorSheet {
formData.isAlchemist = this.actor.isAlchemist()
formData.isPriest = this.actor.isPriest()
- formData.isGM= game.user.isGM
+ formData.isGM = game.user.isGM
- console.log("ACTORDATA", formData);
+ console.log("ACTORDATA", formData)
return formData;
}
/* -------------------------------------------- */
diff --git a/module/actor/actor.js b/module/actor/actor.js
index 663835f..8f7b4b0 100644
--- a/module/actor/actor.js
+++ b/module/actor/actor.js
@@ -22,11 +22,6 @@ export class BoLActor extends Actor {
super.prepareData();
}
- /* -------------------------------------------- */
- //_onUpdate(changed, options, user) {
- //
- //}
-
/* -------------------------------------------- */
updateResourcesData( ) {
if ( this.type == 'character') {
@@ -50,22 +45,92 @@ export class BoLActor extends Actor {
/* -------------------------------------------- */
get itemData(){
- return Array.from(this.data.items.values()).map(i => i.data);
+ return Array.from(this.data.items.values()).map(i => i.data)
}
get details() {
- return this.data.data.details;
+ return this.data.data.details
}
get attributes() {
- return Object.values(this.data.data.attributes);
+ return Object.values(this.data.data.attributes)
}
get aptitudes() {
- return Object.values(this.data.data.aptitudes);
+ return Object.values(this.data.data.aptitudes)
}
+ /* -------------------------------------------- */
get defenseValue() {
- return this.data.data.aptitudes.def.value;
+ let defMod = 0
+ let fo = this.getActiveFightOption()
+ if (fo && fo.data.properties.fightoptiontype == "intrepid" ) {
+ defMod += -2
+ }
+ if (fo && fo.data.properties.fightoptiontype == "fulldefense" ) {
+ defMod += 2
+ }
+ if (fo && fo.data.properties.fightoptiontype == "twoweaponsdef" && !fo.data.properties.used) {
+ defMod += 1
+ this.updateEmbeddedDocuments("Item", [ {_id: fo._id, 'data.properties.used': true}] )
+ }
+ if (fo && fo.data.properties.fightoptiontype == "defense" ) {
+ defMod += 1
+ }
+ if (fo && fo.data.properties.fightoptiontype == "attack" ) {
+ defMod += -1
+ }
+ return this.data.data.aptitudes.def.value + defMod
}
+
+ /* -------------------------------------------- */
+ getActiveFightOption( ) {
+ let it = this.itemData.find(i => i.type === "feature" && i.data.subtype === "fightoption" && i.data.properties.activated)
+ if (it) {
+ return duplicate(it)
+ }
+ return undefined
+ }
+
+ /* -------------------------------------------- */
+ async toggleFightOption( itemId) {
+ let fightOption = this.data.items.get(itemId)
+ let state
+ let updates = []
+
+ if ( fightOption) {
+ fightOption = duplicate(fightOption)
+ if (fightOption.data.properties.activated) {
+ state = false
+ } else {
+ state = true
+ }
+ updates.push( {_id: fightOption._id, 'data.properties.activated': state} ) // Update the selected one
+ await this.updateEmbeddedDocuments("Item", updates) // Apply all changes
+ // Then notify
+ ChatMessage.create({
+ alias: this.name,
+ whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name),
+ content: await renderTemplate('systems/bol/templates/chat/chat-activate-fight-option.hbs', { name: this.name, img: fightOption.img, foName: fightOption.name, state: state} )
+ })
+
+ }
+ }
+
+ /* -------------------------------------------- */
+ get armorMalusValue() { // used for Fight Options
+ for(let armor of this.armors) {
+ if (armor.data.properties.armorQuality.includes("light")) {
+ return 1
+ }
+ if (armor.data.properties.armorQuality.includes("medium")) {
+ return 2
+ }
+ if (armor.data.properties.armorQuality.includes("heavy")) {
+ return 3
+ }
+ }
+ return 0
+ }
+
get resources() {
- return Object.values(this.data.data.resources);
+ return Object.values(this.data.data.resources)
}
get boons() {
return this.itemData.filter(i => i.type === "feature" && i.data.subtype === "boon");
@@ -83,13 +148,16 @@ export class BoLActor extends Actor {
return this.itemData.filter(i => i.type === "feature" && i.data.subtype === "race");
}
get languages() {
- return this.itemData.filter(i => i.type === "feature" && i.data.subtype === "language");
+ return this.itemData.filter(i => i.type === "feature" && i.data.subtype === "language")
+ }
+ get fightoptions() {
+ return this.itemData.filter(i => i.type === "feature" && i.data.subtype === "fightoption")
}
get features() {
- return this.itemData.filter(i => i.type === "feature");
+ return this.itemData.filter(i => i.type === "feature")
}
get equipment() {
- return this.itemData.filter(i => i.type === "item");
+ return this.itemData.filter(i => i.type === "item")
}
get armors() {
return this.itemData.filter(i => i.type === "item" && i.data.category === "equipment" && i.data.subtype === "armor");
@@ -100,7 +168,7 @@ export class BoLActor extends Actor {
get shields() {
return this.itemData.filter(i => i.type === "item" && i.data.category === "equipment" && i.data.subtype === "shield");
}
-
+
get weapons() {
return this.itemData.filter(i => i.type === "item" && i.data.category === "equipment" && i.data.subtype === "weapon");
}
@@ -267,9 +335,15 @@ export class BoLActor extends Actor {
"label": "BOL.featureCategory.languages",
"ranked": false,
"items": this.languages
+ },
+ "fightoptions": {
+ "label": "BOL.featureCategory.fightoptions",
+ "ranked": false,
+ "items": this.fightoptions
}
- };
+ }
}
+
buildCombat(){
return {
"melee" : {
@@ -278,6 +352,7 @@ export class BoLActor extends Actor {
"protection" : false,
"blocking" : false,
"ranged" : false,
+ "options": false,
"items" : this.melee
},
"ranged" : {
@@ -286,6 +361,7 @@ export class BoLActor extends Actor {
"protection" : false,
"blocking" : false,
"ranged" : true,
+ "options": false,
"items" : this.ranged
},
"protections" : {
@@ -294,6 +370,7 @@ export class BoLActor extends Actor {
"protection" : true,
"blocking" : false,
"ranged" : false,
+ "options": false,
"items" : this.protections
},
"shields" : {
@@ -302,9 +379,19 @@ export class BoLActor extends Actor {
"protection" : false,
"blocking" : true,
"ranged" : false,
+ "options": false,
"items" : this.shields
+ },
+ "fightoptions" : {
+ "label" : "BOL.combatCategory.fightOptions",
+ "weapon" : false,
+ "protection" : false,
+ "blocking" : false,
+ "ranged" : false,
+ "options": true,
+ "items" : this.fightoptions
}
- };
+ }
}
/*-------------------------------------------- */
diff --git a/module/controllers/bol-rolls.js b/module/controllers/bol-rolls.js
index ea14062..c631b13 100644
--- a/module/controllers/bol-rolls.js
+++ b/module/controllers/bol-rolls.js
@@ -3,39 +3,47 @@ import { BoLUtility } from "../system/bol-utility.js";
const __adv2dice = { ["1B"]: 3, ["2B"]: 4, ["2"]: 2, ["1M"]: 3, ["2M"]: 4 }
const _apt2attr = { init: "mind", melee: "agility", ranged: "agility", def: "vigor" }
+/* -------------------------------------------- */
export class BoLRoll {
+
+ /* -------------------------------------------- */
static options() {
return { classes: ["bol", "dialog"], width: 480, height: 540 };
}
+ /* -------------------------------------------- */
static convertToAdv(adv) {
if (adv == 0) return "2"
return Math.abs(adv) + (adv < 0) ? 'M' : 'B';
}
+ /* -------------------------------------------- */
static getDefaultAttribute(key) {
return _apt2attr[key]
}
+
/* -------------------------------------------- */
static attributeCheck(actor, actorData, dataset, event) {
- 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.displayRollDialog(
- {
- mode: "attribute",
- actor: actor,
- actorData: actorData,
- attribute: attribute,
- attrValue: attribute.value,
- aptValue: 0,
- label: label,
- careerBonus: 0,
- description: description,
- adv: this.convertToAdv(adv),
- mod: 0
- });
+ 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)
+
+ let rollData = {
+ mode: "attribute",
+ actor: actor,
+ actorData: actorData,
+ attribute: attribute,
+ attrValue: attribute.value,
+ aptValue: 0,
+ label: label,
+ careerBonus: 0,
+ description: description,
+ adv: this.convertToAdv(adv),
+ mod: 0
+ }
+ return this.displayRollDialog( rollData )
}
/* -------------------------------------------- */
@@ -69,24 +77,31 @@ export class BoLRoll {
/* -------------------------------------------- */
static weaponCheck(actor, actorData, dataset, event) {
let target = BoLUtility.getTarget()
- const li = $(event.currentTarget).parents(".item");
- const weapon = actor.items.get(li.data("item-id"));
+ const li = $(event.currentTarget).parents(".item")
+ const weapon = actor.items.get(li.data("item-id"))
if (!weapon) {
- ui.notifications.warn("Unable to find weapon !");
+ ui.notifications.warn("Unable to find weapon !")
return;
}
let weaponData = weapon.data.data
let attribute = eval(`actor.data.data.attributes.${weaponData.properties.attackAttribute}`)
let aptitude = eval(`actor.data.data.aptitudes.${weaponData.properties.attackAptitude}`)
- console.debug("WEAPON!", weaponData)
- let attackDef = {
+ // Manage specific case
+ let fightOption= actor.getActiveFightOption()
+ if ( fightOption && fightOption.data.fightoptiontype == "fulldefense") {
+ ui.notifications.warn(`{{actor.name}} est en Défense Totale ! Il ne peut pas attaquer ce round.`)
+ return
+ }
+ // Build the roll structure
+ let rolldata = {
mode: "weapon",
actor: actor,
actorData: actorData,
weapon: weapon,
isRanged: weaponData.properties.ranged || weaponData.properties.throwing,
target: target,
+ fightOption: fightOption,
careerBonus: 0,
defender: (target) ? game.actors.get(target.data.actorId) : undefined,
attribute: attribute,
@@ -98,7 +113,7 @@ export class BoLRoll {
label: (weapon.name) ? weapon.name : game.i18n.localize('BOL.ui.noWeaponName'),
description: actor.name + " - " + game.i18n.localize('BOL.ui.weaponAttack'),
}
- return this.displayRollDialog(attackDef);
+ return this.displayRollDialog(rolldata)
}
/* -------------------------------------------- */
@@ -179,7 +194,36 @@ export class BoLRoll {
}
$('#roll-modifier').val( this.rollData.attrValue + "+" + this.rollData.aptValue + "+" + this.rollData.careerBonus + "+" + this.rollData.mod + "+" +
- this.rollData.modRanged + "+" + this.rollData.weaponModifier + "-" + this.rollData.defence + "+" + this.rollData.shieldMalus )
+ this.rollData.modRanged + "+" + this.rollData.weaponModifier + "-" + this.rollData.defence + "-" + this.rollData.modArmorMalus + "-" +
+ this.rollData.shieldMalus + "+" + this.rollData.attackModifier )
+ }
+
+ /* -------------------------------------------- */
+ static preProcessFightOption( rollData) {
+ rollData.damagesIgnoresArmor = false // Always reset flags
+ rollData.modArmorMalus = 0
+ rollData.attackModifier = 0
+
+ let fgItem = rollData.fightOption
+ if (fgItem ) {
+ console.log(fgItem)
+ if (fgItem.data.properties.fightoptiontype == "armordefault") {
+ rollData.modArmorMalus = rollData.armorMalus // Activate the armor malus
+ rollData.damagesIgnoresArmor = true
+ }
+ if (fgItem.data.properties.fightoptiontype == "intrepid") {
+ rollData.attackModifier += 2
+ }
+ if (fgItem.data.properties.fightoptiontype == "defense") {
+ rollData.attackModifier += -1
+ }
+ if (fgItem.data.properties.fightoptiontype == "attack") {
+ rollData.attackModifier += 1
+ }
+ if (fgItem.data.properties.fightoptiontype == "twoweaponsdef" || fgItem.data.properties.fightoptiontype == "twoweaponsatt") {
+ rollData.attackModifier += -1
+ }
+ }
}
/* -------------------------------------------- */
@@ -250,15 +294,37 @@ export class BoLRoll {
})
}
+ /* -------------------------------------------- */
+ static preProcessWeapon( rollData) {
+ if (rollData.mode == "weapon") {
+ rollData.weaponModifier = rollData.weapon.data.data.properties.attackModifiers ?? 0;
+ rollData.attackBonusDice = rollData.weapon.data.data.properties.attackBonusDice
+ if (rollData.defender) { // If target is selected
+ rollData.defence = rollData.defender.defenseValue
+ rollData.armorMalus = rollData.defender.armorMalusValue
+ rollData.shieldBlock = 'none'
+ let shields = rollData.defender.shields
+ for (let shield of shields) {
+ rollData.shieldBlock = (shield.data.properties.blocking.blockingAll) ? 'blockall' : 'blockone';
+ rollData.shieldAttackMalus = (shield.data.properties.blocking.malus) ? shield.data.properties.blocking.malus : 1;
+ rollData.applyShieldMalus = false
+ }
+ }
+ }
+ }
+
/* ROLL DIALOGS */
/* -------------------------------------------- */
static async displayRollDialog(rollData, onEnter = "submit") {
+ // initialize default flags/values
const rollOptionTpl = `systems/bol/templates/dialogs/${rollData.mode}-roll-dialog.hbs`
rollData.careers = rollData.actorData.features.careers
rollData.boons = rollData.actor.bonusBoons
rollData.flaws = rollData.actor.malusFlaws
rollData.defence = 0
+ rollData.attackModifier = 0 // Used for fight options
+ rollData.modArmorMalus = 0 // Used for fight options
rollData.bDice = 0
rollData.mDice = 0
rollData.nbBoons = 0
@@ -273,31 +339,17 @@ export class BoLRoll {
rollData.modRanged = rollData.modRanged ?? 0
rollData.mod = rollData.mod ?? 0
rollData.id = randomID(16)
-
- // Weapon mode specific management
rollData.weaponModifier = 0
rollData.attackBonusDice = false
-
- // Saves
+ rollData.armorMalus = 0
+ // Specific stuff
+ this.preProcessWeapon(rollData)
+ this.preProcessFightOption(rollData)
+ // Save
this.rollData = rollData
console.log("ROLLDATA", rollData)
- if (rollData.mode == "weapon") {
- rollData.weaponModifier = rollData.weapon.data.data.properties.attackModifiers ?? 0;
- rollData.attackBonusDice = rollData.weapon.data.data.properties.attackBonusDice
- if (rollData.defender) { // If target is selected
- rollData.defence = rollData.defender.defenseValue
- rollData.shieldBlock = 'none'
- let shields = rollData.defender.shields
- //console.log("Shields", shields)
- for (let shield of shields) {
- rollData.shieldBlock = (shield.data.properties.blocking.blockingAll) ? 'blockall' : 'blockone';
- rollData.shieldAttackMalus = (shield.data.properties.blocking.malus) ? shield.data.properties.blocking.malus : 1;
- rollData.applyShieldMalus = false
- }
- }
- }
-
+ // Then display+process the dialog
const rollOptionContent = await renderTemplate(rollOptionTpl, rollData);
let d = new Dialog({
title: rollData.label,
@@ -325,7 +377,7 @@ export class BoLRoll {
const isMalus = rollData.mDice > 0
rollData.nbDice += (rollData.attackBonusDice) ? 1 : 0
- const modifiers = rollData.attrValue + rollData.aptValue + rollData.careerBonus + rollData.mod + rollData.weaponModifier - rollData.defence + rollData.shieldMalus
+ const modifiers = rollData.attrValue + rollData.aptValue + rollData.careerBonus + rollData.mod + rollData.weaponModifier - rollData.defence - rollData.modArmorMalus + rollData.shieldMalus + rollData.attackModifier
const formula = (isMalus) ? rollData.nbDice + "d6kl2 + " + modifiers : rollData.nbDice + "d6kh2 + " + modifiers
rollData.formula = formula
rollData.modifiers = modifiers
@@ -365,15 +417,16 @@ export class BoLDefaultRoll {
async roll() {
- const r = new Roll(this.rollData.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);
+ const r = new Roll(this.rollData.formula)
+ // console.log("Roll formula", this.rollData.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.rollData.roll = r
- this.rollData.isSuccess = (r.total >= 9);
+ this.rollData.isSuccess = (r.total >= 9)
this.rollData.isCritical = (diceTotal === 12)
this.rollData.isRealCritical = (diceTotal === 12)
- this.rollData.isFumble = (diceTotal === 2);
+ this.rollData.isFumble = (diceTotal === 2)
this.rollData.isFailure = !this.rollData.isSuccess
if (this.rollData.reroll == undefined) {
this.rollData.reroll = this.rollData.actor.heroReroll()
@@ -456,10 +509,10 @@ export class BoLDefaultRoll {
bonusDmg = 12
}
let attrDamageValue = this.getDamageAttributeValue(this.rollData.weapon.data.data.properties.damageAttribute)
- let weaponFormula = BoLUtility.getDamageFormula(this.rollData.weapon.data.data)
+ let weaponFormula = BoLUtility.getDamageFormula(this.rollData.weapon.data.data, this.rollData.fightOption )
let damageFormula = weaponFormula + "+" + bonusDmg + "+" + attrDamageValue
- console.log("DAMAGE !!!", damageFormula, attrDamageValue)
+ console.log("DAMAGE !!!", damageFormula, attrDamageValue, this.rollData)
//console.log("Formula", weaponFormula, damageFormula, this.rollData.weapon.data.data.properties.damage)
this.rollData.damageFormula = damageFormula
@@ -474,7 +527,7 @@ export class BoLDefaultRoll {
_buildDamageChatMessage(rollData) {
const rollMessageTpl = 'systems/bol/templates/chat/rolls/damage-roll-card.hbs';
- return renderTemplate(rollMessageTpl, rollData);
+ return renderTemplate(rollMessageTpl, rollData)
}
_buildChatMessage(rollData) {
diff --git a/module/system/bol-combat.js b/module/system/bol-combat.js
index d8bf799..2d7600e 100644
--- a/module/system/bol-combat.js
+++ b/module/system/bol-combat.js
@@ -48,7 +48,18 @@ export class BoLCombatManager extends Combat {
fvttInit += (cId / 100)
await this.updateEmbeddedDocuments("Combatant", [{ _id: ids[cId], initiative: fvttInit }]);
}
- console.log("TODO : Compute init for actor");
}
+
+ /************************************************************************************/
+ nextRound() {
+ let combatants = this.combatants.contents
+ for (let c of combatants) {
+ let actor = game.actors.get( c.data.actorId )
+ //actor.clearRoundModifiers()
+ }
+ super.nextRound()
+ }
+
+
}
diff --git a/module/system/bol-utility.js b/module/system/bol-utility.js
index 788fdad..0b7a5e9 100644
--- a/module/system/bol-utility.js
+++ b/module/system/bol-utility.js
@@ -139,7 +139,7 @@ export class BoLUtility {
chatGM.whisper = this.getUsers(user => user.isGM);
chatGM.content = "Blind message of " + game.user.name + "
" + chatOptions.content;
console.log("blindMessageToGM", chatGM);
- game.socket.emit("system.fvtt-fragged-kingdom", { msg: "msg_gm_chat_message", data: chatGM });
+ game.socket.emit("system.bol", { msg: "msg_gm_chat_message", data: chatGM });
}
/* -------------------------------------------- */
@@ -335,53 +335,6 @@ export class BoLUtility {
return undefined;
}
- /* -------------------------------------------- */
- static async rollBoL(rollData) {
-
- // Dice bonus/malus selection
- let nbDice = 2;
- let d6BM = 0;
- let mode = "";
- if (rollData.d6Malus > rollData.d6Bonus) {
- d6BM = rollData.d6Malus - rollData.d6Bonus;
- mode = "kl2";
- }
- if (rollData.d6Bonus > rollData.d6Malus) {
- d6BM = rollData.d6Bonus - rollData.d6Malus;
- mode = "kh2";
- }
- nbDice += d6BM;
-
- // Final modifier
- let modifier = Number(rollData.bonusMalus);
- if (rollData.mode == 'career') {
- modifier += Number(rollData.attributes[rollData.rollAttribute].value) + Number(rollData.career.data.rank);
- } else if (rollData.mode == 'attribute') {
- modifier += rollData.attribute.value;
- } else if (rollData.mode == 'weapon') {
- modifier += Number(rollData.attributes[rollData.rollAttribute].value) + Number(rollData.aptitude.value) + Number(rollData.rangeModifier);
- modifier -= rollData.defender.data.aptitudes.def.value;
- }
-
- let formula = nbDice + "d6" + mode + "+" + modifier;
-
- console.log("Going to roll ", formula, rollData.attributes, rollData.rollAttribute);
- let myRoll = new Roll(formula).roll({ async: false });
- await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"));
- rollData.roll = myRoll;
- rollData.formula = formula;
- rollData.modifier = modifier;
- rollData.nbDice = nbDice;
- rollData.finalScore = myRoll.total;
-
- let actor = game.actors.get(rollData.actorId);
- actor.saveRollData(rollData);
-
- this.createChatWithRollMode(rollData.alias, {
- content: await renderTemplate(`systems/bol/templates/chat/chat-generic-result.hbs`, rollData)
- });
- }
-
/* -------------------------------------------- */
static async processAttackSuccess(attackDef) {
if (!game.user.isGM) { // Only GM process this
@@ -399,9 +352,10 @@ export class BoLUtility {
attacker: attackDef.attacker,
defender: attackDef.defender,
defenderWeapons: defenderWeapons,
- damageTotal: attackDef.damageRoll.total
+ damageTotal: attackDef.damageRoll.total,
+ damagesIgnoresArmor: attackDef.damagesIgnoresArmor,
})
- });
+ })
}
/* -------------------------------------------- */
@@ -423,7 +377,8 @@ export class BoLUtility {
}
/* -------------------------------------------- */
- static getDamageFormula(weaponData) {
+ static getDamageFormula(weaponData, fightOption) {
+ let upgradeDamage = (fightOption && fightOption.data.properties.fightoptiontype == "twoweaponsatt")
let damageString = weaponData.properties.damage
let modifier = weaponData.properties.damageModifiers ?? 0
let multiplier = weaponData.properties.damageMultiplier ?? 1
@@ -435,26 +390,33 @@ export class BoLUtility {
let formula = damageString
if (damageString.includes("d") || damageString.includes("D")) {
- 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;
+ 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
+ // Upgrade damage if needed
+ if ( upgradeDamage && ( !res[3] || res[3]=="") ) {
+ res[3] = "B" // Upgrade to bonus
+ }
if (res[3]) {
+ if ( upgradeDamage && res[3] == 'M') {
+ res[3] = "" // Disable lamlus for upgradeDamage
+ }
if (res[3] == 'M') {
- postForm = 'kl' + nbDice;
- nbDice++;
- modIndex = 4;
+ postForm = 'kl' + nbDice
+ nbDice++
+ modIndex = 4
}
if (res[3] == 'B') {
- postForm = 'kh' + nbDice;
- nbDice++;
- modIndex = 4;
+ postForm = 'kh' + nbDice
+ nbDice++
+ modIndex = 4
}
}
- formula = "(" + nbDice + "d" + res[2] + reroll + postForm + "+" + modifier + ") *" + multiplier;
+ formula = "(" + nbDice + "d" + res[2] + reroll + postForm + "+" + modifier + ") *" + multiplier
}
- return formula;
+ return formula
}
/* -------------------------------------------- */
diff --git a/module/system/config.js b/module/system/config.js
index 663590a..cbb6dc5 100644
--- a/module/system/config.js
+++ b/module/system/config.js
@@ -254,7 +254,18 @@ BOL.featureSubtypes = {
"boon" : "BOL.featureSubtypes.boon",
"flaw" : "BOL.featureSubtypes.flaw",
"language" : "BOL.featureSubtypes.language",
- "godsfaith" : "BOL.featureSubtypes.gods"
+ "godsfaith" : "BOL.featureSubtypes.gods",
+ "fightoption" : "BOL.featureSubtypes.fightOption"
+}
+
+BOL.fightOptionTypes = {
+ "armordefault": "BOL.fightOptionTypes.armor",
+ "intrepid": "BOL.fightOptionTypes.intrepid",
+ "twoweaponsdef": "BOL.fightOptionTypes.twoweaponsdef",
+ "twoweaponsatt": "BOL.fightOptionTypes.twoweaponsatt",
+ "fulldefense": "BOL.fightOptionTypes.fulldefense",
+ "defense": "BOL.fightOptionTypes.defense",
+ "attack": "BOL.fightOptionTypes.attack",
}
BOL.itemIcons = {
diff --git a/module/system/templates.js b/module/system/templates.js
index 10ad3df..f3619a7 100644
--- a/module/system/templates.js
+++ b/module/system/templates.js
@@ -33,6 +33,7 @@ export const preloadHandlebarsTemplates = async function () {
"systems/bol/templates/item/parts/properties/feature/flaw-properties.hbs",
"systems/bol/templates/item/parts/properties/feature/origin-properties.hbs",
"systems/bol/templates/item/parts/properties/feature/race-properties.hbs",
+ "systems/bol/templates/item/parts/properties/feature/fightoption-properties.hbs",
// DIALOGS
"systems/bol/templates/chat/rolls/attack-damage-card.hbs",
@@ -47,7 +48,8 @@ export const preloadHandlebarsTemplates = async function () {
"systems/bol/templates/dialogs/career-roll-part.hbs",
"systems/bol/templates/dialogs/boons-roll-part.hbs",
"systems/bol/templates/dialogs/flaws-roll-part.hbs",
- "systems/bol/templates/dialogs/total-roll-part.hbs",
+ "systems/bol/templates/dialogs/total-roll-part.hbs",
+ "systems/bol/templates/dialogs/fightoptions-roll-part.hbs",
];
// Load the template parts
diff --git a/packs/fightoptions.db b/packs/fightoptions.db
new file mode 100644
index 0000000..ec8a92b
--- /dev/null
+++ b/packs/fightoptions.db
@@ -0,0 +1,7 @@
+{"_id":"4VsEmcj4YpdAaaZY","name":"Attaque au Défaut d'armure","type":"feature","img":"icons/skills/melee/weapons-crossed-poleaxes-white.webp","data":{"category":null,"subtype":"fightoption","description":"
Vous visez une zone du corps non protégée ou un point faible de l’armure de votre adversaire.
\nAppliquez la valeur de protection fixe de l’armure comme malus à votre jet d’attaque (-1 pour une armure légère, -2 pour une armure moyenne et -3 pour une armure lourde). Si votre attaque passe malgré ce malus, les dégâts de votre coup ignorent la protection de l’armure.
\nSi le MJ l’accepte, cette option de combat pourra également permettre de trouver le défaut de l’armure naturelle d’une créature.
","properties":{"ismalusdice":false,"isbonusdice":false,"fightoptiontype":"armordefault","activated":false},"rank":0,"fightoptiontype":"armordefault"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{"core":{"sourceId":"Item.3nzfQvMvkK4ujRqI"}}} +{"name":"Posture Défensive","type":"feature","img":"icons/skills/melee/weapons-crossed-poleaxes-white.webp","data":{"category":null,"subtype":"fightoption","description":"Vous choisissez d’adopter une attitude prudente, en restant toujours prêt à parer ou à esquiver l’attaque de votre adversaire. Combattre en posture défensive vous confère un bonus de +1 en défense, mais vous subissez un malus de -1 à votre jet d’attaque.
","properties":{"ismalusdice":false,"isbonusdice":false,"fightoptiontype":"defense","activated":false},"rank":0,"fightoptiontype":"defense"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{"core":{"sourceId":"Item.CS1fCtHTxp5v1krr"}},"_id":"FQPqaB86ZkRzsKwG"} +{"name":"Défense Totale","type":"feature","img":"icons/skills/melee/weapons-crossed-poleaxes-white.webp","data":{"category":null,"subtype":"fightoption","description":"Vous consacrez votre round à esquiver, parer et vous protéger des coups. Vous n’ effectuez pas d’attaque durant le round, mais bénéficiez d’un bonus de +2 en défense, qui s’ajoute éventuellement à celui que pourrait vous apporter un bouclier ou une arme secondaire de parade.
","properties":{"ismalusdice":false,"isbonusdice":false,"fightoptiontype":"fulldefense","activated":false},"rank":0,"fightoptiontype":"fulldefense"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{"core":{"sourceId":"Item.t8v7isBpnFzAbmUI"}},"_id":"JRboSn5RuGILpH0B"} +{"name":"Combat à Deux Armes (Défensif)","type":"feature","img":"icons/skills/melee/weapons-crossed-poleaxes-white.webp","data":{"category":null,"subtype":"fightoption","description":"Vous ne pouvez utiliser que des armes légères ou moyennes.
\nVous attaquez avec une arme et parez avec l’autre. Vous considérez l’arme de parade comme l’équivalent d’un petit bouclier (+1 en défense contre une attaque), mais vous subissez un malus de -1 sur votre jet d’attaque avec votre autre arme.
","properties":{"ismalusdice":false,"isbonusdice":false,"fightoptiontype":"twoweaponsdef","activated":false},"rank":0,"fightoptiontype":"twoweapons"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{"core":{"sourceId":"Item.lyMbLMPnFk0oXaSr"}},"_id":"JtU8EmKuda0M4Onv"} +{"_id":"a3Ev9xm8aM9kAmi3","name":"Attaque Intrépide","type":"feature","img":"icons/skills/melee/weapons-crossed-poleaxes-white.webp","data":{"category":null,"subtype":"fightoption","description":"Vous attaquez avec la plus extrême témérité.
\nVous ne bénéficiez pas de l’éventuel bonus d’un bouclier ou d’une arme secondaire de parade et subissez un malus de -2 à la défense. En revanche, vous bénéficiez d’un bonus de +2 au jet d’attaque.
","properties":{"ismalusdice":false,"isbonusdice":false,"fightoptiontype":"intrepid","activated":false},"rank":0,"fightoptiontype":"intrepid"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{"core":{"sourceId":"Item.589BS9KBGnUazrFA"}}} +{"name":"Posture Offensive","type":"feature","img":"icons/skills/melee/weapons-crossed-poleaxes-white.webp","data":{"category":null,"subtype":"fightoption","description":"Vous vous concentrez sur l’attaque, au détriment de votre défense. Cette option vous confère un bonus de +1 au jet d’attaque, mais vous subissez un malus de -1 en défense.
","properties":{"ismalusdice":false,"isbonusdice":false,"fightoptiontype":"attack","activated":false},"rank":0,"fightoptiontype":"attack"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{"core":{"sourceId":"Item.BF7F5WvL1pbWVHNq"}},"_id":"hgUHJP6JFxbeiRQL"} +{"name":"Combat à Deux Armes (Offensif)","type":"feature","img":"icons/skills/melee/weapons-crossed-poleaxes-white.webp","data":{"category":null,"subtype":"fightoption","description":"Vous ne pouvez utiliser que des armes légères ou moyennes.
\nVous attaquez avec vos deux armes. Vous n’effectuez qu’un seul jet d’attaque avec un malus de -1, mais vous infligez des dégâts comme si vous maniez une arme moyenne (si vous utilisez deux armes légères) ou une arme lourde (si vous utilisez une arme moyenne et une arme légère, ou deux armes moyennes).
","properties":{"ismalusdice":false,"isbonusdice":false,"fightoptiontype":"twoweaponsatt","activated":false},"rank":0,"fightoptiontype":"twoweapons"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"kQghu0tL1dft5xLu":3},"flags":{"core":{"sourceId":"Item.lyMbLMPnFk0oXaSr"}},"_id":"wM4ZIVSSKApgzEmN"} diff --git a/system.json b/system.json index c3d5a22..d6765ca 100644 --- a/system.json +++ b/system.json @@ -7,8 +7,8 @@ "url": "https://github.com/ZigmundKreud/bol", "license": "LICENSE.txt", "flags": {}, - "version": "1.1.0", - "templateVersion": 21, + "version": "1.2.0", + "templateVersion": 22, "minimumCoreVersion": "0.8.6", "compatibleCoreVersion": "9", "scripts": [], @@ -135,6 +135,15 @@ "system": "bol", "entity": "Item", "private": false + }, + { + "label": "Options de Combat", + "type": "Item", + "name": "fightoptions", + "path": "packs/fightoptions.db", + "system": "bol", + "entity": "Item", + "private": false } ], "system": [], diff --git a/templates/actor/actor-sheet.hbs b/templates/actor/actor-sheet.hbs index ccd3564..1378ccc 100644 --- a/templates/actor/actor-sheet.hbs +++ b/templates/actor/actor-sheet.hbs @@ -26,13 +26,17 @@ {{!-- Sheet Body --}}