diff --git a/css/bol.css b/css/bol.css index 8ff330b..bec5f27 100644 --- a/css/bol.css +++ b/css/bol.css @@ -149,6 +149,24 @@ border: none; border-radius: 0; } +.bol select[multiple] { + box-shadow: none; + border: none; + font-size: 12px; +} +.bol select[multiple]:focus option:checked { + background: darkred linear-gradient(0deg, darkred 0%, darkred 100%); + color: white; +} +.bol option:hover, +.bol option:focus, +.bol option:active, +.bol option:checked, +.bol option[selected] { + cursor: pointer; + background: darkred linear-gradient(0deg, darkred 0%, darkred 100%); + color: white; +} .bol label.checkbox { flex: auto; padding: 0; @@ -425,6 +443,13 @@ background: #cd071e; color: #fff; } +.darkred { + color: darkred; +} +.bg-darkred { + background: darkred; + color: #fff; +} .purple { color: purple; } @@ -432,6 +457,30 @@ background: purple; color: #fff; } +.message-header h2.damage { + color: orangered; + font-weight: bold; +} +.message-header h2.critical { + color: green; + font-weight: bold; +} +.message-header h2.fumble { + color: red; + font-weight: bold; +} +.message-header h2.success { + color: darkgreen; + font-weight: bold; +} +.message-header h2.failure { + color: darkred; + font-weight: bold; +} +.message-header h2.roll { + color: darkslategrey; + font-weight: bold; +} .bol.sheet .window-content { height: 100%; padding: 5px; @@ -609,6 +658,13 @@ } .bol.sheet.actor .stat-roll { font-size: 1.5rem; + color: #4b4a44; +} +.bol.sheet.actor .stat-roll.malus { + color: darkred; +} +.bol.sheet.actor .stat-roll.bonus { + color: darkgreen; } .bol.sheet.actor .header-field-label, .bol.sheet.actor .stat-label { @@ -624,6 +680,7 @@ } .bol.sheet.actor .rounded-border { border: 3px solid #4b4a44; + box-shadow: 5px 5px 5px gray; border-radius: 100px; width: 4rem; height: 4rem; @@ -675,23 +732,23 @@ font-size: 12px; line-height: 18px; } +.bol.dialog .sheet-header h3 { + font-family: "Wolfsbane2Expanded", cursive; + font-size: 24px; + color: black; +} .editor, .editor-content { height: 100%; } .rollable { - color: #4b4a44; cursor: pointer; } -.malus { - color: darkred; -} -.bonus { - color: darkgreen; -} .chat-message .chat-icon { - flex: 0 0 64px; + float: right; border: 1px outset lightgray; + box-shadow: 3px 3px 3px black; + margin: 3px; width: 64px; height: 64px; } diff --git a/lang/en.json b/lang/en.json index 83fc175..4f2e123 100644 --- a/lang/en.json +++ b/lang/en.json @@ -38,6 +38,9 @@ "BOL.ui.type": "Type", "BOL.ui.subtype": "Sous-type", "BOL.ui.properties": "Propriétés", + "BOL.ui.attribute" : "Attribut", + "BOL.ui.aptitude" : "Aptitude", + "BOL.ui.advantages" : "Avantages/Désavantages", "BOL.ui.modifiers": "Modificateurs", "BOL.ui.item": "Objet", "BOL.ui.edit": "Editer", @@ -45,12 +48,31 @@ "BOL.ui.equip": "Equiper", "BOL.ui.delete": "Supprimer", "BOL.ui.roll" : "Utiliser", + "BOL.ui.equipment" : "Équipement", + "BOL.ui.weapon" : "Arme", + "BOL.ui.melee" : "Arme de contact", + "BOL.ui.ranged" : "Arme à distance", "BOL.ui.protection" : "Protection", + "BOL.ui.shield" : "Bouclier", "BOL.ui.blocking" : "Blocage", "BOL.ui.range" : "Portée", "BOL.ui.quantity" : "Quantité", "BOL.ui.weight" : "Poids", "BOL.ui.price": "Prix", + "BOL.ui.cancel": "Annuler", + "BOL.ui.submit": "OK", + "BOL.ui.attributeCheck" : "Test d'attribut", + "BOL.ui.aptitudeCheck" : "Test d'aptitude", + "BOL.ui.weaponCheck" : "Jet d'attaque", + "BOL.ui.spellCheck" : "Jet de sort", + "BOL.ui.careers" : "Carrières", + "BOL.ui.boons" : "Avantages", + "BOL.ui.flaws" : "Désavantages", + "BOL.ui.rank" : "Rang", + "BOL.ui.success" : "Succès", + "BOL.ui.failure" : "Échec", + "BOL.ui.fumble" : "Échec critique", + "BOL.ui.critical" : "Succès critique", "BOL.featureCategory.origins": "Origines", "BOL.featureCategory.races": "Races", @@ -144,78 +166,7 @@ "BOL.range.Extreme": "Extrême", "BOL.range.Maximum": "Maximale", - - "Careers": "Carrieres", - "Name": "Nom", - "Boons": "Avantages", - "Flaws": "Desavantages", - "Inventory": "Inventaire", - - "Career": "Carrière", - "Boon": "Avantage", - "Flaw": "Désavantage", - - "d6B": "d6 Bonus", - "d6M": "d6 Malus", - "ranged": "A Distance", - "melee": "Melée", - "spell" : "Sort", - "protection" : "Protection", - "weapon" : "Arme", - "armor" : "Armure", - "helm" : "Casque", - "shield" : "Bouclier", - - "equipable": "Equipable", - "stackable": "Empilable", - "consumable" : "Consommable", - "magical" : "Magique", - - "2H" : "A Deux Mains", - "reloadable" : "A Recharger", - "bow" : "Arc", - "crossbow" : "Arbalète", - "throwing" : "Lancer", - - "rank": "Niveau", - "attribut": "Attribut", - "subtype": "Type", - "Language": "Langue", - "Level": "Niveau", - "Roll": "Lancer", - - "Melee": "Mêlée", - "Ranged": "Tir", - "Quantity": "Quantité", - "Weight":"Poids", - "Damage": "Dommages", - "Two Hands": "A Deux Mains", - "Thrown": "Peut-être lancé", - "Range": "Portée (m.)", - "Reload": "A Recharger", - "Reload Duration": "Durée de rechargement (rounds)" , - "Can be hidden": "Peut être cachée", - "Ignore Shield": "Ignore les boucliers", - "Improvised": "Improvisée", - "Properties": "Propriétés", - "Protection Roll": "Jet de Protection", - "Protection Fixed": "Protection statique (si pas de jet)", - "None": "Aucun", - "Social Malus": "Malus Social", - "Agility Malus":"Malus d'Agilité", - "Initiative Malus": "Malus d'Initiative", - "Power Cost": "Cout en Points de Pouvoir", - "Equipped": "Equipé", - "Attack with": "Attaque avec", - "Range Modifier": "Modificateur de portée", - "Point Blank": "Bout portant", - "Short": "Courte", - "Medium": "Moyenne", - "Long": "Long", - "Very Long": "Très longue", - "Extreme": "Extrême", - "Maximum": "Maximale", - "Defender": "Défenseur", - "Defense score": "Score de défense", - "Modifier": "Modificateur" + "BOL.notification.MacroMultipleTokensSelected": "Vous avez sélectionné plusieurs tokens", + "BOL.notification.MacroNoActorAvailable": "Aucun acteur n'a pu être ciblé", + "BOL.notification.MacroNoTokenSelected": "Vous devez sélectionner un token" } \ No newline at end of file diff --git a/lang/fr.json b/lang/fr.json index 2f47a08..4f2e123 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -38,6 +38,9 @@ "BOL.ui.type": "Type", "BOL.ui.subtype": "Sous-type", "BOL.ui.properties": "Propriétés", + "BOL.ui.attribute" : "Attribut", + "BOL.ui.aptitude" : "Aptitude", + "BOL.ui.advantages" : "Avantages/Désavantages", "BOL.ui.modifiers": "Modificateurs", "BOL.ui.item": "Objet", "BOL.ui.edit": "Editer", @@ -56,6 +59,20 @@ "BOL.ui.quantity" : "Quantité", "BOL.ui.weight" : "Poids", "BOL.ui.price": "Prix", + "BOL.ui.cancel": "Annuler", + "BOL.ui.submit": "OK", + "BOL.ui.attributeCheck" : "Test d'attribut", + "BOL.ui.aptitudeCheck" : "Test d'aptitude", + "BOL.ui.weaponCheck" : "Jet d'attaque", + "BOL.ui.spellCheck" : "Jet de sort", + "BOL.ui.careers" : "Carrières", + "BOL.ui.boons" : "Avantages", + "BOL.ui.flaws" : "Désavantages", + "BOL.ui.rank" : "Rang", + "BOL.ui.success" : "Succès", + "BOL.ui.failure" : "Échec", + "BOL.ui.fumble" : "Échec critique", + "BOL.ui.critical" : "Succès critique", "BOL.featureCategory.origins": "Origines", "BOL.featureCategory.races": "Races", @@ -149,78 +166,7 @@ "BOL.range.Extreme": "Extrême", "BOL.range.Maximum": "Maximale", - - "Careers": "Carrieres", - "Name": "Nom", - "Boons": "Avantages", - "Flaws": "Desavantages", - "Inventory": "Inventaire", - - "Career": "Carrière", - "Boon": "Avantage", - "Flaw": "Désavantage", - - "d6B": "d6 Bonus", - "d6M": "d6 Malus", - "ranged": "A Distance", - "melee": "Melée", - "spell" : "Sort", - "protection" : "Protection", - "weapon" : "Arme", - "armor" : "Armure", - "helm" : "Casque", - "shield" : "Bouclier", - - "equipable": "Equipable", - "stackable": "Empilable", - "consumable" : "Consommable", - "magical" : "Magique", - - "2H" : "A Deux Mains", - "reloadable" : "A Recharger", - "bow" : "Arc", - "crossbow" : "Arbalète", - "throwing" : "Lancer", - - "rank": "Niveau", - "attribut": "Attribut", - "subtype": "Type", - "Language": "Langue", - "Level": "Niveau", - "Roll": "Lancer", - - "Melee": "Mêlée", - "Ranged": "Tir", - "Quantity": "Quantité", - "Weight":"Poids", - "Damage": "Dommages", - "Two Hands": "A Deux Mains", - "Thrown": "Peut-être lancé", - "Range": "Portée (m.)", - "Reload": "A Recharger", - "Reload Duration": "Durée de rechargement (rounds)" , - "Can be hidden": "Peut être cachée", - "Ignore Shield": "Ignore les boucliers", - "Improvised": "Improvisée", - "Properties": "Propriétés", - "Protection Roll": "Jet de Protection", - "Protection Fixed": "Protection statique (si pas de jet)", - "None": "Aucun", - "Social Malus": "Malus Social", - "Agility Malus":"Malus d'Agilité", - "Initiative Malus": "Malus d'Initiative", - "Power Cost": "Cout en Points de Pouvoir", - "Equipped": "Equipé", - "Attack with": "Attaque avec", - "Range Modifier": "Modificateur de portée", - "Point Blank": "Bout portant", - "Short": "Courte", - "Medium": "Moyenne", - "Long": "Long", - "Very Long": "Très longue", - "Extreme": "Extrême", - "Maximum": "Maximale", - "Defender": "Défenseur", - "Defense score": "Score de défense", - "Modifier": "Modificateur" + "BOL.notification.MacroMultipleTokensSelected": "Vous avez sélectionné plusieurs tokens", + "BOL.notification.MacroNoActorAvailable": "Aucun acteur n'a pu être ciblé", + "BOL.notification.MacroNoTokenSelected": "Vous devez sélectionner un token" } \ No newline at end of file diff --git a/module/actor/actor-sheet.js b/module/actor/actor-sheet.js index 2432c98..6c10e70 100644 --- a/module/actor/actor-sheet.js +++ b/module/actor/actor-sheet.js @@ -2,6 +2,8 @@ * Extend the basic ActorSheet with some very simple modifications * @extends {ActorSheet} */ +import {BoLRoll} from "../controllers/bol-rolls.js"; + export class BoLActorSheet extends ActorSheet { /** @override */ @@ -33,18 +35,6 @@ export class BoLActorSheet extends ActorSheet { const item = this.actor.items.get(li.data("itemId")); item.sheet.render(true); }); - 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") ); - }); - // Equip/Unequip item html.find('.item-equip').click(this._onToggleEquip.bind(this)); @@ -57,6 +47,18 @@ 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") ); + // }); } /* -------------------------------------------- */ @@ -121,14 +123,16 @@ export class BoLActorSheet extends ActorSheet { event.preventDefault(); const element = event.currentTarget; const dataset = element.dataset; - - if (dataset.roll) { - let roll = new Roll(dataset.roll, this.actor.data.data); - let label = dataset.label ? `Rolling ${dataset.label}` : ''; - roll.roll().toMessage({ - speaker: ChatMessage.getSpeaker({ actor: this.actor }), - flavor: label - }); + const actorData = this.getData(); + const rollType = dataset.rollType; + switch(rollType) { + case "attribute" : + BoLRoll.attributeCheck(this.actor, actorData, dataset, event); + break; + case "aptitude" : + BoLRoll.aptitudeCheck(this.actor, actorData, dataset, event); + break; + default : break; } } diff --git a/module/actor/actor.js b/module/actor/actor.js index 7b398fe..4314aea 100644 --- a/module/actor/actor.js +++ b/module/actor/actor.js @@ -1,6 +1,3 @@ -import { BoLRollDialog } from "../system/roll-dialog.js"; -import { BoLUtility } from "../system/bol-utility.js"; - /** * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system. * @extends {Actor} @@ -14,9 +11,7 @@ export class BoLActor extends Actor { // console.log(actorData); // const data = actorData.data; // const flags = actorData.flags; - - // Make separate methods for each Actor type (character, npc, etc.) to keep - // things organized. + // Make separate methods for each Actor type (character, npc, etc.) to keep things organized. if (actorData.type === 'character') { this._prepareCharacterData(actorData); } @@ -165,97 +160,6 @@ export class BoLActor extends Actor { }; } /* -------------------------------------------- */ - buildRollData(mode, title) { - return { - mode : mode, - title : title, - actorId: this.id, - actorImg: this.img, - boons : this.boons, - flaws : this.flaws, - d6Bonus: 0, - d6Malus: 0, - rollMode: game.settings.get("core", "rollMode"), - optionsBonusMalus: BoLUtility.buildListOptions(-8, +2), - bonusMalus: 0 - } - } - - saveRollData( rollData) { - this.currentRollData = rollData; - } - - /* -------------------------------------------- */ - async rollAttributeAptitude( attrKey ) { - let attr = this.data.data.attributes[attrKey]; - if ( !attr) { - attr = this.data.data.aptitudes[attrKey]; - } - if (attr) { - let rollData = this.buildRollData("attribute", game.i18n.localize(attr.label)); - rollData.attribute = duplicate(attr); - let rollDialog = await BoLRollDialog.create( this, rollData); - rollDialog.render( true ); - } else { - ui.notifications.warn("Unable to find attribute " + attrKey ); - } - } - - /* -------------------------------------------- */ - async rollCareer( careerId ) { - let career = BoLUtility.data(this.data.items.find( item => item.type == 'feature' && item.id == careerId)); - if (career) { - let rollData = this.buildRollData("career", `${career.name} : ${career.data.rank}`); - rollData.career = career; - rollData.rollAttribute = 'mind'; - rollData.attributes = duplicate(this.data.data.attributes); - let rollDialog = await BoLRollDialog.create( this, rollData); - rollDialog.render( true ); - } else { - ui.notifications.warn("Unable to find career for actor " + this.name + " - Career ID " + careerId); - } - } - - /* -------------------------------------------- */ - async rollWeapon( weaponId ) { - let weapon = BoLUtility.data(this.data.items.find( item => item.type == 'item' && item.id == weaponId)); - if (weapon) { - let target = BoLUtility.getTarget(); - // if ( !target) { - // ui.notifications.warn("You must have a target to attack with a Weapon"); - // return; - // } - let objectDefender = (target) ? BoLUtility.data(game.actors.get(target.data.actorId)) : null; - objectDefender = (objectDefender) ? mergeObject(objectDefender, target.data.actorData) : null; - let rollData = this.buildRollData("weapon", weapon.name); - rollData.weapon = weapon; - rollData.target = target; - rollData.isRanged = BoLUtility.isRangedWeapon( weapon ); - rollData.defender = objectDefender; - rollData.rollAttribute = 'agility'; - rollData.attributes = duplicate(this.data.data.attributes); // For damage bonus - rollData.rangeModifier = 0; - - if ( weapon.data.type == 'melee') { - rollData.aptitude = duplicate(this.data.data.aptitudes.melee); - } else { - rollData.aptitude = duplicate(this.data.data.aptitudes.ranged); - } - console.log("WEAPON ! ", rollData); - let rollDialog = await BoLRollDialog.create( this, rollData); - rollDialog.render( true ); - } else { - ui.notifications.warn("Unable to find weapon for actor " + this.name + " - Weapon ID " + weaponId); - } - } - - - /** - * - * @param {*} item - * @param {*} bypassChecks - * @returns - */ toggleEquipItem(item) { const equipable = item.data.data.properties.equipable; if(equipable){ @@ -264,6 +168,4 @@ export class BoLActor extends Actor { return item.update(itemData); } } - - } \ No newline at end of file diff --git a/module/bol.js b/module/bol.js index 8553d08..15ea9f5 100644 --- a/module/bol.js +++ b/module/bol.js @@ -8,13 +8,15 @@ import {preloadHandlebarsTemplates} from "./system/templates.js"; import {registerHandlebarsHelpers} from "./system/helpers.js"; import {registerSystemSettings} from "./system/settings.js"; import registerHooks from "./system/hooks.js"; -import {DataLoader} from "./system/data.js"; +// import {DataLoader} from "./system/data.js"; +import {Macros} from "./system/macros.js"; Hooks.once('init', async function () { game.bol = { BoLActor, BoLItem, + macros : Macros, config:BOL }; diff --git a/module/controllers/bol-rolls.js b/module/controllers/bol-rolls.js new file mode 100644 index 0000000..0c0122c --- /dev/null +++ b/module/controllers/bol-rolls.js @@ -0,0 +1,207 @@ +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); + } + + /* -------------------------------------------- */ + /* 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.data.features.careers, + boons:actorData.data.features.boons, + flaws:actorData.data.features.flaws + }; + const rollOptionContent = await renderTemplate(rollOptionTpl, dialogData); + let d = new Dialog({ + title: label, + content: rollOptionContent, + buttons: { + cancel: { + icon: '', + label: game.i18n.localize("BOL.ui.cancel"), + callback: () => { + } + }, + submit: { + icon: '', + label: game.i18n.localize("BOL.ui.submit"), + callback: (html) => { + const attr = html.find('#attr').val(); + const adv = html.find('#adv').val(); + const mod = html.find('#mod').val(); + const careers = html.find('#career').val(); + const career = (careers.size >0) ? Math.max(...html.find('#career').val().map(i => parseInt(i))) : 0; + 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 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.data.features.careers, + boons:actorData.data.features.boons, + flaws:actorData.data.features.flaws + }; + const rollOptionContent = await renderTemplate(rollOptionTpl, dialogData); + let d = new Dialog({ + title: label, + content: rollOptionContent, + buttons: { + cancel: { + icon: '', + label: game.i18n.localize("BOL.ui.cancel"), + callback: () => { + } + }, + submit: { + icon: '', + label: game.i18n.localize("BOL.ui.submit"), + callback: (html) => { + const apt = html.find('#apt').val(); + const adv = html.find('#adv').val(); + const mod = html.find('#mod').val(); + const careers = html.find('#career').val(); + const career = (careers.size >0) ? Math.max(...html.find('#career').val().map(i => parseInt(i))) : 0; + 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){ + this._label = label; + this._formula = formula; + this._isSuccess = false; + this._isCritical = false; + this._isFumble = false; + 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"} + }); + }); + } + + _buildChatMessage(actor) { + const rollMessageTpl = 'systems/bol/templates/chat/rolls/default-roll-card.hbs'; + const tplData = { + actor : actor, + label : this._label, + isSuccess : this._isSuccess, + isFailure : !this._isSuccess, + isCritical : this._isCritical, + isFumble : this._isFumble, + hasDescription : this._description && this._description.length > 0, + description : this._description + }; + return renderTemplate(rollMessageTpl, tplData); + } + +} + +// export class BoLWeaponRoll { +// constructor(actor, label, formula, isCritical, description){ +// this._label = label; +// this._formula = formula; +// this._isCritical = isCritical; +// this._description = description; +// } +// +// _buildChatMessage() { +// const rollMessageTpl = 'systems/bol/templates/chat/rolls/weapon-roll-card.hbs'; +// const tplData = { +// label : this._label, +// isCritical : this._isCritical, +// hasDescription : this._description && this._description.length > 0, +// description : this._description +// }; +// return renderTemplate(rollMessageTpl, tplData); +// } +// } + +// export class BoLSpellRoll { +// constructor(actor, label, formula, isCritical, description){ +// this._label = label; +// this._formula = formula; +// this._isCritical = isCritical; +// this._description = description; +// } +// +// _buildChatMessage() { +// const rollMessageTpl = 'systems/bol/templates/chat/rolls/spell-roll-card.hbs'; +// const tplData = { +// label : this._label, +// isCritical : this._isCritical, +// hasDescription : this._description && this._description.length > 0, +// description : this._description +// }; +// return renderTemplate(rollMessageTpl, tplData); +// } +// } diff --git a/module/system/config.js b/module/system/config.js index 54e557b..1bea016 100644 --- a/module/system/config.js +++ b/module/system/config.js @@ -98,8 +98,7 @@ BOL.featureSubtypes = { "race" : "BOL.featureSubtypes.race", "career" : "BOL.featureSubtypes.career", "boon" : "BOL.featureSubtypes.boon", - "flaw" : "BOL.featureSubtypes.flaw", - "language" : "BOL.featureSubtypes.language" + "flaw" : "BOL.featureSubtypes.flaw" } BOL.itemIcons = { diff --git a/module/system/hooks.js b/module/system/hooks.js index f69fc6b..70441be 100644 --- a/module/system/hooks.js +++ b/module/system/hooks.js @@ -1,202 +1,61 @@ export default function registerHooks() { - // Hooks.on("getChatLogEntryContext", (html, options) => { - // let canApplyDamage = li => li.find("h2.damage").length; - // let canApplyHealing = li => li.find("h2.heal").length; - // options.push( - // { - // name: game.i18n.localize("COF.ui.applyDamage"), - // icon: '', - // condition: canApplyDamage, - // callback: li => { - // const dmg = parseInt(li.find(".dice-total").text()); - // Hitpoints.applyToTargets(-dmg); - // } - // }, - // { - // name: game.i18n.localize("COF.ui.applyDamage"), - // icon: '', - // condition: canApplyHealing, - // callback: li => { - // const dmg = parseInt(li.find(".dice-total").text()); - // Hitpoints.applyToTargets(-dmg); - // } - // }, - // { - // name: game.i18n.localize("COF.ui.applyHalfDamage"), - // icon: '', - // condition: canApplyDamage, - // callback: li => { - // const dmg = Math.ceil(parseInt(li.find(".dice-total").text()) / 2); - // Hitpoints.applyToTargets(-dmg); - // } - // }, - // { - // name: game.i18n.localize("COF.ui.applyDoubleDamage"), - // icon: '', - // condition: canApplyDamage, - // callback: li => { - // const dmg = parseInt(li.find(".dice-total").text())*2; - // Hitpoints.applyToTargets(-dmg); - // } - // }, - // { - // name: game.i18n.localize("COF.ui.applyHealing"), - // icon: '', - // condition: canApplyDamage, - // callback: li => { - // const dmg = parseInt(li.find(".dice-total").text()); - // Hitpoints.applyToTargets(dmg); - // } - // }, - // { - // name: game.i18n.localize("COF.ui.applyHealing"), - // icon: '', - // condition: canApplyHealing, - // callback: li => { - // const dmg = parseInt(li.find(".dice-total").text()); - // Hitpoints.applyToTargets(dmg); - // } - // } - // ); - // }); - // - // /** - // * Create a macro when dropping an entity on the hotbar - // * Item - open roll dialog for item - // * Actor - open actor sheet - // * Journal - open journal sheet - // */ - // - // Hooks.on("hotbarDrop", async (bar, data, slot) => { - // // Create item macro if rollable item - weapon, spell, prayer, trait, or skill - // if (data.type == "Item") { - // let item = data.data; - // let command = `let onlyDamage = false;\nlet customLabel = "";\nlet skillDescription = "";\nlet dmgDescription = "";\n\nif (event) {\n if (event.shiftKey) onlyDamage = true;\n}\n\ngame.cof.macros.rollItemMacro("${item._id}", "${item.name}", "${item.type}", 0, 0, 0, onlyDamage, customLabel, skillDescription, dmgDescription);`; - // - // let macro = game.macros.entities.find(m => (m.name === item.name) && (m.command === command)); - // if (!macro) { - // macro = await Macro.create({ - // name: item.name, - // type : "script", - // img: item.img, - // command : command - // }, {displaySheet: false}) - // } - // game.user.assignHotbarMacro(macro, slot); - // } - // // Create a macro to open the actor sheet of the actor dropped on the hotbar - // else if (data.type == "Actor") { - // let actor = game.actors.get(data.id); - // let command = `game.actors.get("${data.id}").sheet.render(true)` - // let macro = game.macros.entities.find(m => (m.name === actor.name) && (m.command === command)); - // if (!macro) { - // macro = await Macro.create({ - // name: actor.data.name, - // type: "script", - // img: actor.data.img, - // command: command - // }, {displaySheet: false}) - // game.user.assignHotbarMacro(macro, slot); - // } - // } - // // Create a macro to open the journal sheet of the journal dropped on the hotbar - // else if (data.type == "JournalEntry") { - // let journal = game.journal.get(data.id); - // let command = `game.journal.get("${data.id}").sheet.render(true)` - // let macro = game.macros.entities.find(m => (m.name === journal.name) && (m.command === command)); - // if (!macro) { - // macro = await Macro.create({ - // name: journal.data.name, - // type: "script", - // img: (journal.data.img) ? journal.data.img : "icons/svg/book.svg", - // command: command - // }, {displaySheet: false}) - // game.user.assignHotbarMacro(macro, slot); - // } - // } - // return false; - // }); - // - // - // /** - // * Intercepte les commandes de chat - // * /stat - Jet de caractéristique - // * /skill stat - Jet de caractéristique - // * /stats - Génère les caractéristiques d'un personnage - // */ - // - // Hooks.on("chatMessage", (html, content, msg) => { - // let regExp; - // regExp = /(\S+)/g; - // let commands = content.match(regExp); - // let command = (commands.length>0 && commands[0].split("/").length > 0) ? commands[0].split("/")[1].trim() : null; - // let arg1 = (commands.length > 1) ? commands[1].trim() : null; - // const actor = game.cof.macros.getSpeakersActor(); - // - // const validCommands = ["for", "str", "dex", "con", "int", "sag", "wis", "cha", "atc", "melee", "atd", "ranged", "atm", "magic"]; - // - // if(command && validCommands.includes(command)) { - // game.cof.macros.rollStatMacro(actor, command, 0, 0, null); - // return false; - // } - // else if(command && command === "skill") { - // if(arg1 && validCommands.includes(arg1)) { - // game.cof.macros.rollStatMacro(actor, arg1, 0, 0, null); - // } else { - // ui.notifications.error("Vous devez préciser la caractéristique à tester, par exemple \"/skill str\"."); - // } - // return false; - // } - // else if(command && command === "stats") { - // CharacterGeneration.statsCommand(); - // return false; - // } - // }); - // - // Hooks.on("renderChatMessage", (message, html, data) => { - // // Affiche ou non les boutons d'application des dommages - // if (game.settings.get("cof", "displayChatDamageButtonsToAll")) { - // html.find(".apply-dmg").click(ev => Hitpoints.onClickChatMessageApplyButton(ev, html, data)); - // } - // else { - // if (game.user.isGM){ - // html.find(".apply-dmg").click(ev => Hitpoints.onClickChatMessageApplyButton(ev, html, data)); - // } - // else { - // html.find(".apply-dmg").each((i, btn) => { - // btn.style.display = "none" - // }); - // } - // } - // }); + /** + * Create a macro when dropping an entity on the hotbar + * Item - open roll dialog for item + * Actor - open actor sheet + * Journal - open journal sheet + */ + Hooks.on("hotbarDrop", async (bar, data, slot) => { + console.log(data.type); + // Create item macro if rollable item - weapon, spell, prayer, trait, or skill + if (data.type == "Item") { + let item = data.data; + console.log(item); + // let command = `let onlyDamage = false;\nlet customLabel = "";\nlet skillDescription = "";\nlet dmgDescription = "";\n\nif (event) {\n if (event.shiftKey) onlyDamage = true;\n}\n\ngame.cof.macros.rollItemMacro("${item._id}", "${item.name}", "${item.type}", 0, 0, 0, onlyDamage, customLabel, skillDescription, dmgDescription);`; - // Hooks.on("preCreateChatMessage", (data, options, user) => { - // console.debug("preCreateChatMessage"); - // // console.log(data,options,user); - // return true; - // }); - // Hooks.on("createChatMessage", (message, options, user) => { - // console.debug("createChatMessage"); - // // console.log(message,options,user); - // return true; - // }); - // Hooks.on("updateChatMessage", (message, update, options, user) => { - // console.debug("updateChatMessage"); - // // console.log(message,update,options,user); - // return true; - // }); - - // Hooks.on("renderItemSheet", (app, html, data) => { - // console.debug("renderItemSheet"); - // return true; - // }); - // Hooks.on("renderChatLog", (app, html, data) => { - // console.debug("renderChatLog"); - // return true; - // }); - // Hooks.on('dropCanvasData', function (canvas, dropData) { - // console.debug("dropCanvasData"); - // return true; - // }); + // let macro = game.macros.entities.find(m => (m.name === item.name) && (m.command === command)); + // if (!macro) { + // macro = await Macro.create({ + // name: item.name, + // type : "script", + // img: item.img, + // command : command + // }, {displaySheet: false}) + // } + // game.user.assignHotbarMacro(macro, slot); + } + // Create a macro to open the actor sheet of the actor dropped on the hotbar + else if (data.type == "Actor") { + let actor = game.actors.get(data.id); + let command = `/*\nPersonnalisez la macro selon vos besoins en suivant les exemples suivants : \ngame.bol.macros.rollMacro('attribute', 'vigor|agility|mind|appeal', adv, mod);\ngame.bol.macros.rollMacro('aptitude', 'init|melee|ranged|def', adv, mod);\n*/\ngame.bol.macros.rollMacro('attribute', 'vigor', 0, 0);`; + let macro = game.macros.entities.find(m => (m.name === actor.name) && (m.command === command)); + if (!macro) { + macro = await Macro.create({ + name: actor.data.name, + type: "script", + img: "icons/svg/dice-target.svg", + command: command + }, {displaySheet: false}) + game.user.assignHotbarMacro(macro, slot); + } + } + // Create a macro to open the journal sheet of the journal dropped on the hotbar + else if (data.type == "JournalEntry") { + let journal = game.journal.get(data.id); + console.log(journal); + let command = `game.journal.get("${data.id}").sheet.render(true)` + let macro = game.macros.entities.find(m => (m.name === journal.name) && (m.command === command)); + if (!macro) { + macro = await Macro.create({ + name: journal.data.name, + type: "script", + img: (journal.data.img) ? journal.data.img : "icons/svg/book.svg", + command: command + }, {displaySheet: false}) + game.user.assignHotbarMacro(macro, slot); + } + } + return false; + }); } diff --git a/module/system/macros.js b/module/system/macros.js new file mode 100644 index 0000000..fa57b71 --- /dev/null +++ b/module/system/macros.js @@ -0,0 +1,52 @@ +import {BoLRoll} from "../controllers/bol-rolls.js"; + +export class Macros { + /** + * @name getSpeakersActor + * @description + * + * @returns + */ + static getSpeakersActor = function(){ + // Vérifie qu'un seul token est sélectionné + const tokens = canvas.tokens.controlled; + if (tokens.length > 1) { + ui.notifications.warn(game.i18n.localize('BOL.notification.MacroMultipleTokensSelected')); + return null; + } + + const speaker = ChatMessage.getSpeaker(); + let actor; + // Si un token est sélectionné, le prendre comme acteur cible + if (speaker.token) actor = game.actors.tokens[speaker.token]; + // Sinon prendre l'acteur par défaut pour l'utilisateur courrant + if (!actor) actor = game.actors.get(speaker.actor); + return actor; + } + + static rollMacro = async function (rollType, key, adv, mod){ + const actor = this.getSpeakersActor(); + // Several tokens selected + if (actor === null) return; + // No token selected + if (actor === undefined) return ui.notifications.error(game.i18n.localize("BOL.notification.MacroNoTokenSelected")); + + const actorData = {}; + actorData.data = { + features : actor.buildFeatures() + }; + + if(rollType === "attribute") { + let attribute = eval(`actor.data.data.attributes.${key}`); + let rollLabel = (attribute.label) ? game.i18n.localize(attribute.label) : null; + let description = actor.name + " - " + game.i18n.localize('BOL.ui.attributeCheck') + " - " + game.i18n.localize(attribute.label) ; + BoLRoll.attributeRollDialog(actor, actorData, attribute, rollLabel, description, adv, mod); + } + else if(rollType === "aptitude") { + let aptitude = eval(`actor.data.data.aptitudes.${key}`); + let rollLabel = (aptitude.label) ? game.i18n.localize(aptitude.label) : null; + let description = actor.name + " - " + game.i18n.localize('BOL.ui.aptitudeCheck') + " - " + game.i18n.localize(aptitude.label) ; + BoLRoll.aptitudeRollDialog(actor, actorData, aptitude, rollLabel, description, adv, mod); + } + } +} diff --git a/module/system/roll-dialog.js b/module/system/roll-dialog.js index c252847..c4c865b 100644 --- a/module/system/roll-dialog.js +++ b/module/system/roll-dialog.js @@ -1,4 +1,4 @@ -import { BoLUtility } from "../system/bol-utility.js"; +import { BoLUtility } from "./bol-utility.js"; export class BoLRollDialog extends Dialog { @@ -47,7 +47,7 @@ export class BoLRollDialog extends Dialog { activateListeners(html) { super.activateListeners(html); - var dialog = this; + let dialog = this; function onLoad() { } $(function () { onLoad(); }); diff --git a/module/system/templates.js b/module/system/templates.js index c7eb41d..81ac0e6 100644 --- a/module/system/templates.js +++ b/module/system/templates.js @@ -16,14 +16,19 @@ export const preloadHandlebarsTemplates = async function () { // ITEMS "systems/bol/templates/item/parts/item-header.hbs", "systems/bol/templates/item/parts/properties/feature-properties.hbs", - "systems/bol/templates/item/parts/properties/equipment-properties.hbs", - "systems/bol/templates/item/parts/properties/protection-properties.hbs", - "systems/bol/templates/item/parts/properties/shield-properties.hbs", - "systems/bol/templates/item/parts/properties/weapon-properties.hbs", - "systems/bol/templates/item/parts/properties/armor-properties.hbs", - "systems/bol/templates/item/parts/properties/melee-properties.hbs", - "systems/bol/templates/item/parts/properties/ranged-properties.hbs", "systems/bol/templates/item/parts/properties/item-properties.hbs", + "systems/bol/templates/item/parts/properties/item/equipment-properties.hbs", + "systems/bol/templates/item/parts/properties/item/protection-properties.hbs", + "systems/bol/templates/item/parts/properties/item/shield-properties.hbs", + "systems/bol/templates/item/parts/properties/item/weapon-properties.hbs", + "systems/bol/templates/item/parts/properties/item/armor-properties.hbs", + "systems/bol/templates/item/parts/properties/item/melee-properties.hbs", + "systems/bol/templates/item/parts/properties/item/ranged-properties.hbs", + "systems/bol/templates/item/parts/properties/feature/career-properties.hbs", + "systems/bol/templates/item/parts/properties/feature/boon-properties.hbs", + "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", // DIALOGS "systems/bol/templates/roll/parts/roll-dialog-modifiers.hbs", "systems/bol/templates/roll/parts/roll-dialog-attribute.hbs" diff --git a/styles/bol.less b/styles/bol.less index 4eda36d..933ae45 100644 --- a/styles/bol.less +++ b/styles/bol.less @@ -3,6 +3,7 @@ @import "global/typography"; @import "global/item-list"; @import "global/colors"; +@import 'global/chat'; @logo-width: 190px; @logo-height: 115px; @@ -30,6 +31,13 @@ } } &.dialog { + .sheet-header{ + h3 { + font-family: @font-tertiary; + font-size: 24px; + color: black; + } + } } } @@ -38,23 +46,16 @@ } .rollable { - color: @colorOlive; + //color: @colorOlive; cursor: pointer; } -.malus { - color: darkred; -} - -.bonus { - color: darkgreen; -} - .chat-message .chat-icon { - flex : 0 0 64px; - border: 1px outset lightgray; - //padding: 2px 6px 2px 2px; - //float: left; + float:right; + border:1px outset lightgray; + box-shadow: 3px 3px 3px black; + margin: 3px; + //flex : 0 0 64px; width: 64px; height: 64px; } diff --git a/styles/components/actor.less b/styles/components/actor.less index e50315d..245f3c4 100644 --- a/styles/components/actor.less +++ b/styles/components/actor.less @@ -115,11 +115,20 @@ .stat-value { font-size: 1.5rem; font-weight: bold; - color: darkred; + color: @c-darkred; } .stat-roll { font-size: 1.5rem; + color: @colorOlive; + + &.malus { + color: @c-darkred; + } + + &.bonus { + color: darkgreen; + } } .header-field-label, @@ -139,6 +148,7 @@ .rounded-border { border: 3px solid @colorOlive; + box-shadow: 5px 5px 5px gray; border-radius: 100px; width: 4rem; height: 4rem; diff --git a/styles/global/chat.less b/styles/global/chat.less new file mode 100644 index 0000000..b8c4fb4 --- /dev/null +++ b/styles/global/chat.less @@ -0,0 +1,26 @@ +.message-header { + h2.damage{ + color: @colorDamage; + font-weight: bold; + } + h2.critical{ + color: @colorCritical; + font-weight: bold; + } + h2.fumble{ + color: @colorFumble; + font-weight: bold; + } + h2.success{ + color: @colorSuccess; + font-weight: bold; + } + h2.failure{ + color: @colorFailure; + font-weight: bold; + } + h2.roll{ + color: @colorRoll; + font-weight: bold; + } +} diff --git a/styles/global/colors.less b/styles/global/colors.less index 1e9f6d0..f4c2dfa 100644 --- a/styles/global/colors.less +++ b/styles/global/colors.less @@ -12,6 +12,7 @@ @c-blue: #009ee0; @c-green: #44a12b; @c-red: #cd071e; +@c-darkred: darkred; @c-purple: purple; @c-darkred: darkred; @c-border:#736953a6; @@ -128,6 +129,14 @@ color: @c-white; } +.darkred { + color: @c-darkred; +} +.bg-darkred { + background: @c-darkred; + color: @c-white; +} + .purple { color: @c-purple; } diff --git a/styles/global/forms.less b/styles/global/forms.less index 9271fb9..d453156 100644 --- a/styles/global/forms.less +++ b/styles/global/forms.less @@ -49,11 +49,6 @@ } select { - //-webkit-appearance: none; - //-moz-appearance: none; - //color: $c-black; - //color: $colorDark; - //font-family: $font-primary; font-size: 14px; text-align: center; text-align-last: center; @@ -61,16 +56,31 @@ width: 100%; border: none; border-radius: 0; - - // height: calc(100% - 2px); - // //border: 1px solid $colorTan; - // background: rgba(0, 0, 0, 0.05); - // border-radius: 0; - // border: none; - // box-shadow: none; - // color: $colorDark; } + select[multiple]{ + box-shadow: none; + border: none; + font-size: 12px; + &:focus { + option:checked { + background: @c-darkred linear-gradient(0deg, @c-darkred 0%, @c-darkred 100%); + color:white; + } + } + } + option:hover, + option:focus, + option:active, + option:checked, + option[selected] { + cursor: pointer; + background: @c-darkred linear-gradient(0deg, @c-darkred 0%, @c-darkred 100%); + color:white; + } + //option:not(:checked) { + // color: black; + //} /* or whatever your default style is */ label.checkbox { flex: auto; diff --git a/template.json b/template.json index d7dfcde..07a9ce2 100644 --- a/template.json +++ b/template.json @@ -7,6 +7,12 @@ "biography": "", "notes": "", "languages": [], + "creation": { + "key" : "creation", + "label" : "BOL.resources.creation", + "value": 0, + "max": 0 + }, "xp": { "key" : "xp", "label" : "BOL.traits.xp", @@ -19,12 +25,6 @@ "base": 0, "value": 0, "bonus": 0 - }, - "creation": { - "key" : "creation", - "label" : "BOL.resources.creation", - "value": 0, - "max": 0 } }, "attributes": { @@ -119,12 +119,6 @@ "label" : "BOL.resources.power", "value": 0, "max": 0 - }, - "villainy" : { - "key" : "villainy", - "label" : "BOL.resources.villainy", - "value": 5, - "max": 5 } } } @@ -154,11 +148,9 @@ "armor" : false, "helm" : false, "shield" : false, - "equipable": false, "consumable" : false, "magical" : false, - "2H" : false, "reloadable" : false, "bow" : false, @@ -170,40 +162,10 @@ "item": { "templates": ["base", "equipment"] }, - "weapon": { - "type": "", - "quantity": 1, - "weight": 0, - "damage": "", - "range": "", - "thrown": false, - "twohands": false, - "reload": false, - "reloadduration": 0, - "canbehidden": false, - "ignoreshield": false, - "improvised": false, - "equipped": false, - "description": "" - }, - "armor": { - "type": "", - "protectionroll": "", - "protectionfixed": 0, - "socialmalus": false, - "agilitymalus": 0, - "initmalus": 0, - "powercost": 0, - "equipped": false, - "description": "" - }, "feature": { "rank": 0, "templates": ["base"], - "properties" : { - "d6B": false, - "d6M": false - } + "properties" : {} } } } diff --git a/templates/actor/actor-sheet.hbs b/templates/actor/actor-sheet.hbs index 4b51cdf..fb17978 100644 --- a/templates/actor/actor-sheet.hbs +++ b/templates/actor/actor-sheet.hbs @@ -2,12 +2,17 @@