Add fight options

This commit is contained in:
sladecraven 2022-03-10 21:05:53 +01:00
parent eacd32927c
commit 02b3dd5e0f
24 changed files with 447 additions and 227 deletions

View File

@ -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;
}

View File

@ -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,6 +164,7 @@
"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",
@ -162,6 +172,14 @@
"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",

View File

@ -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,6 +157,7 @@
"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",
@ -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,6 +195,7 @@
"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",

View File

@ -43,6 +43,11 @@ export class BoLActorSheet extends ActorSheet {
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;
}
/* -------------------------------------------- */

View File

@ -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");
@ -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
}
};
}
}
/*-------------------------------------------- */

View File

@ -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) {

View File

@ -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()
}
}

View File

@ -139,7 +139,7 @@ export class BoLUtility {
chatGM.whisper = this.getUsers(user => user.isGM);
chatGM.content = "Blind message of " + game.user.name + "<br>" + 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
}
/* -------------------------------------------- */

View File

@ -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 = {

View File

@ -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",
@ -48,6 +49,7 @@ export const preloadHandlebarsTemplates = async function () {
"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/fightoptions-roll-part.hbs",
];
// Load the template parts

7
packs/fightoptions.db Normal file
View File

@ -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":"<p>Vous visez une zone du corps non prot&eacute;g&eacute;e ou&nbsp;un point faible de l&rsquo;armure de votre adversaire.</p>\n<p>Appliquez la valeur de protection fixe de l&rsquo;armure comme malus &agrave; votre jet d&rsquo;attaque (-1 pour une armure l&eacute;g&egrave;re, -2 pour une armure moyenne et -3 pour une armure lourde). Si votre attaque passe malgr&eacute; ce malus, les d&eacute;g&acirc;ts de votre coup ignorent la protection de l&rsquo;armure.</p>\n<p>Si le MJ l&rsquo;accepte, cette option de combat pourra &eacute;galement permettre de trouver le d&eacute;faut de l&rsquo;armure naturelle d&rsquo;une cr&eacute;ature.</p>","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":"<p>Vous choisissez d&rsquo;adopter une attitude prudente, en restant toujours pr&ecirc;t &agrave; parer ou &agrave; esquiver l&rsquo;attaque de votre adversaire. Combattre en posture d&eacute;fensive vous conf&egrave;re un bonus de +1 en d&eacute;fense, mais vous subissez un malus de -1 &agrave; votre jet d&rsquo;attaque.</p>","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":"<p>Vous consacrez votre round &agrave; esquiver, parer et vous prot&eacute;ger des coups. Vous n&rsquo; effectuez pas d&rsquo;attaque durant le round, mais b&eacute;n&eacute;ficiez d&rsquo;un bonus de +2 en d&eacute;fense, qui s&rsquo;ajoute &eacute;ventuellement &agrave; celui que pourrait vous apporter un bouclier ou une arme secondaire de parade.</p>","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":"<p>Vous ne pouvez utiliser que des armes l&eacute;g&egrave;res&nbsp;ou moyennes.</p>\n<p>Vous attaquez avec une arme et parez avec l&rsquo;autre. Vous consid&eacute;rez l&rsquo;arme de parade comme l&rsquo;&eacute;quivalent d&rsquo;un petit bouclier (+1 en d&eacute;fense contre une attaque), mais vous subissez un malus de -1 sur votre jet d&rsquo;attaque avec votre autre arme.</p>","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":"<p>Vous attaquez avec la plus extr&ecirc;me t&eacute;m&eacute;rit&eacute;.</p>\n<p>Vous ne b&eacute;n&eacute;ficiez pas de l&rsquo;&eacute;ventuel bonus d&rsquo;un&nbsp;bouclier ou d&rsquo;une arme secondaire de parade et subissez un malus de -2 &agrave; la d&eacute;fense. En revanche,&nbsp;vous b&eacute;n&eacute;ficiez d&rsquo;un bonus de +2 au jet d&rsquo;attaque.</p>","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":"<p>Vous vous concentrez sur l&rsquo;attaque, au d&eacute;triment&nbsp;de votre d&eacute;fense. Cette option vous conf&egrave;re un bonus de +1 au jet d&rsquo;attaque, mais vous subissez un malus de -1 en d&eacute;fense.</p>","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":"<p>Vous ne pouvez utiliser que des armes l&eacute;g&egrave;res&nbsp;ou moyennes.</p>\n<p><span style=\"font-family: var(--font-primary); font-size: var(--font-size-14);\">Vous attaquez avec vos deux armes. </span><span style=\"font-family: var(--font-primary); font-size: var(--font-size-14);\">Vous n&rsquo;effectuez qu&rsquo;un seul jet d&rsquo;attaque avec un </span>malus de -1, mais vous infligez des d&eacute;g&acirc;ts comme&nbsp;si vous maniez une arme moyenne (si vous utilisez deux armes l&eacute;g&egrave;res) ou une arme lourde (si vous&nbsp;utilisez une arme moyenne et une arme l&eacute;g&egrave;re, ou deux armes moyennes).</p>","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"}

View File

@ -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": [],

View File

@ -26,13 +26,17 @@
{{!-- Sheet Body --}}
<section class="sheet-body">
<div class="tab stats" data-group="primary" data-tab="stats">{{>
"systems/bol/templates/actor/parts/tabs/actor-stats.hbs"}}</div>
"systems/bol/templates/actor/parts/tabs/actor-stats.hbs"}}
</div>
<div class="tab actions" data-group="primary" data-tab="actions">
{{> "systems/bol/templates/actor/parts/tabs/actor-actions.hbs"}}
</div>
<div class="tab combat" data-group="primary" data-tab="combat">
{{> "systems/bol/templates/actor/parts/tabs/actor-combat.hbs"}}
</div>
<div class="tab features" data-group="primary" data-tab="features">
{{> "systems/bol/templates/actor/parts/tabs/actor-features.hbs"}}
</div>

View File

@ -6,6 +6,7 @@
{{#if blocking}}<div class="item-field">{{localize "BOL.ui.blocking"}}</div>{{/if}}
{{#if weapon}}<div class="item-field">{{localize "BOL.ui.damages"}}</div>{{/if}}
{{#if ranged}}<div class="item-field">{{localize "BOL.ui.range"}}</div>{{else}}<div class="item-field"></div>{{/if}}
{{#if options}}<div class="item-field">{{localize "BOL.ui.status"}}</div>{{else}}<div class="item-field"></div>{{/if}}
</li>
{{#each combatType.items as |item id|}}
<li class="item flexrow" data-item-id="{{item._id}}">
@ -15,6 +16,15 @@
{{#if ../blocking}}<div class="item-field">{{item.data.properties.blocking.malus}}</div>{{/if}}
{{#if ../weapon}}<div class="item-field"><a class="rollable" data-roll-type="damage">{{item.data.properties.damage}}</a></div>{{/if}}
{{#if ../ranged}}<div class="item-field">{{item.data.properties.range}}</div>{{else}}<div class="item-field"></div>{{/if}}
{{#if ../options}}<div class="item-field">
{{#if item.data.properties.activated}}
<a class="toggle-fight-option">{{localize "BOL.ui.toactivated"}}</a>
{{else}}
<a class="toggle-fight-option">{{localize "BOL.ui.todeactivated"}}</a>
{{/if}}
</div>{{else}}
<div class="item-field"></div>
{{/if}}
</li>
{{/each}}
</ol>

View File

@ -0,0 +1,14 @@
<div>
<img class="chat-icon" src="{{img}}" alt="{{name}}"/>
<h2 class="good"><strong>{{name}}</strong></h2>
</div>
<div class="flexrow">
{{#if state}}
{{name}} active son option de combat {{foName}} pour ce round !
{{else}}
{{name}} désactive son option de combat {{foName}} pour ce round !
{{/if}}
</div>

View File

@ -1,41 +0,0 @@
<div class="chat-message-header flexrow">
<img class="chat-icon" src="{{actorImg}}" alt="{{alias}}"/>
<h4 class=chat-actor-name>{{alias}}</h4>
</div>
<div class="flexrow">
<h3>
{{#if (eq mode "attribute")}}
{{localize attribute.label}}
{{else}}
{{#if (eq mode "career")}}
{{localize "Career"}} : {{career.name}}
{{else}}
{{#if (eq mode "weapon")}}
{{localize "Attack with"}} {{weapon.name}}
{{/if}}
{{/if}}
{{/if}}
</h3>
</div>
<div class="dice-roll">
<div class="dice-result">
<div class="dice-formula">{{formula}}</div>
<div class="dice-formula">{{modifier}}</div>
<div class="dice-tooltip" style="display: none;">
<section class="tooltip-part">
<div class="dice">
<header class="part-header flexrow">
<span class="part-formula">{{formula}}</span>
<span class="part-total">{{finalScore}}</span>
</header>
<ol class="dice-rolls">
<li class="roll die d6 discarded min">1</li>
<li class="roll die d6">2</li>
<li class="roll die d6">3</li>
</ol>
</div>
</section>
</div>
<h4 class="dice-total">{{finalScore}}</h4>
</div>
</div>

View File

@ -1,5 +1,7 @@
<img class="chat-icon" src="{{img}}" alt="{{name}}"/>
<h3><strong>{{name}}</strong></h3>
<div>
<img class="chat-icon" src="{{img}}" alt="{{name}}"/>
<h2 class="bad"><strong>{{name}}</strong></h2>
</div>
<div class="flexrow">

View File

@ -5,4 +5,5 @@
<div id="{{applyId}}">
<button class="chat-damage-apply" data-attack-id="{{id}}">Appliquer les dommages à la cible</button>
</div>
<br>
{{/if}}

View File

@ -1,21 +1,30 @@
<img class="chat-icon" src="{{actor.img}}" alt="{{actor.name}}"/>
{{#if isSuccess}}
{{#if isCritical}}
<h2 class="success critical"><i class="fas fa-check-double"></i>&nbsp;{{localize "BOL.ui.critical"}}...</h2>
{{else}}
<h2 class="success"><i class="fas fa-check"></i>&nbsp;{{localize "BOL.ui.success"}}...</h2>
{{/if}}
{{/if}}
{{#if isFailure}}
{{#if isFumble}}
<h2 class="failure fumble"><i class="fas fa-skull-crossbones"></i>&nbsp;{{localize "BOL.ui.fumble"}}...</h2>
{{else}}
<h2 class="failure"><i class="fas fa-times"></i>&nbsp;{{localize "BOL.ui.failure"}}...</h2>
{{/if}}
{{/if}}
<div>
{{#if isSuccess}}
{{#if isCritical}}
<h2 class="success critical"><i class="fas fa-check-double"></i>&nbsp;{{localize "BOL.ui.critical"}}...
{{else}}
<h2 class="success"><i class="fas fa-check"></i>&nbsp;{{localize "BOL.ui.success"}}...
{{/if}}
{{/if}}
{{#if isFailure}}
{{#if isFumble}}
<h2 class="failure fumble"><i class="fas fa-skull-crossbones"></i>&nbsp;{{localize "BOL.ui.fumble"}}...
{{else}}
<h2 class="failure"><i class="fas fa-times"></i>&nbsp;{{localize "BOL.ui.failure"}}...
{{/if}}
{{/if}}
<img class="chat-icon" src="{{actor.img}}" alt="{{actor.name}}"/>
</h2>
</div>
<h3><strong>{{description}}</strong></h3>
{{#if fightOption}}
<div>
Option de combat : {{fightOption.name}}
</div>
{{/if}}
<div id="{{optionsId}}">
{{#if (and isSuccess weapon)}}
{{> "systems/bol/templates/chat/rolls/attack-damage-card.hbs"}}
@ -33,4 +42,5 @@
{{#if isRealCritical}}
<button class="chat-button button transform-heroic-roll" data-roll-id=="{{rollId}}" data-actor-id="{{actor.id}}">Transformer en succes héroique (1 P. Heroisme)</button>
{{/if}}
<br>
</div>

View File

@ -1,6 +1,10 @@
<img class="chat-icon" src="{{defender.img}}" alt="{{defender.name}}"/>
Va encaisser {{damageTotal}} dégats !
{{#if damagesIgnoresArmor}}
<br>C'est une attaque au défaut de l'armure : vous devez encaisser SANS la protection de l'armure !
{{/if}}
<button class="damage-handling" data-defense-mode="damage-with-armor" data-attack-id="{{attackId}}">Encaisser avec la protection de l'armure</button>
<button class="damage-handling" data-defense-mode="damage-without-armor" data-attack-id="{{attackId}}">Encaisser sans la protection de l'armure</button>

View File

@ -0,0 +1,10 @@
{{#if fightOption}}
<div class="flexrow" style="margin-bottom: 1px;">
<div class="flex1 center bg-darkred">
<label for="fg">{{localize 'BOL.ui.fightOption'}}</label>
</div>
<div class="flex1 center cell">
{{fightOption.name}}
</div>
</div>
{{/if}}

View File

@ -45,6 +45,8 @@
{{> "systems/bol/templates/dialogs/flaws-roll-part.hbs"}}
{{> "systems/bol/templates/dialogs/fightoptions-roll-part.hbs"}}
{{> "systems/bol/templates/dialogs/adv-roll-part.hbs"}}
{{> "systems/bol/templates/dialogs/mod-roll-part.hbs"}}

View File

@ -12,9 +12,9 @@
{{#if (equals data.subtype "career")}}
{{> "systems/bol/templates/item/parts/properties/feature/career-properties.hbs"}}
{{/if}}
{{!#if (equals data.subtype "origin")}}
{{!> "systems/bol/templates/item/parts/properties/feature/origin-properties.hbs"}}
{{!/if}}
{{#if (equals data.subtype "fightoption")}}
{{> "systems/bol/templates/item/parts/properties/feature/fightoption-properties.hbs"}}
{{/if}}
{{!#if (equals data.subtype "race")}}
{{!> "systems/bol/templates/item/parts/properties/feature/race-properties.hbs"}}
{{!/if}}

View File

@ -0,0 +1,19 @@
<h3 class="form-header">{{localize "BOL.ui.fightOption"}}</h3>
<div class="form-group">
<label class="property-label">{{localize "BOL.ui.fightOptionType"}}</label>
<div class="form-fields">
<select name="data.properties.fightoptiontype" data-dtype="String">
{{#select data.properties.fightoptiontype}}
{{#each config.fightOptionTypes as |item id|}}
<option value="{{id}}">{{localize item}}</option>
{{/each}}
{{/select}}
</select>
</div>
</div>
<div class="property flexrow">
<label class="property-label">{{localize "BOL.ui.activated"}}</label>
<input class="field-value" type="checkbox" name="data.properties.activated" {{checked data.properties.activated}}>
</div>