From 41fbc094bb4ee28fee6f2fffdbdbc6ae7ceae93b Mon Sep 17 00:00:00 2001 From: LeRatierBretonnien Date: Thu, 6 Apr 2023 20:15:04 +0200 Subject: [PATCH] Combat Tracker + power enhancements --- lang/fr.json | 20 +++--- module/actor/actor.js | 75 ++++++++++++++++++---- module/controllers/bol-rolls.js | 2 +- module/system/bol-combat.js | 21 ++++-- module/system/bol-utility.js | 21 +++++- system.json | 4 +- templates/actor/parts/tabs/actor-stats.hbs | 25 +++++++- 7 files changed, 132 insertions(+), 36 deletions(-) diff --git a/lang/fr.json b/lang/fr.json index 5d73864..9480606 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -459,10 +459,10 @@ "BOL.chat.isdead": "{name} est mort !", "BOL.chat.epitaph": "Que son nom soit honoré sur les champs de batailles de Lémurie !", "BOL.chat.vitalityzero": "La Vitalité de {name} est {hp} : il va s'écrouler au sol et sombrer dans l'inconscience !", - "BOL.chat.vitalityheroism": "Vous pouvez dépenser 1 Point d'Héroisme pour reprendre vos esprits pendant 1 round.", + "BOL.chat.vitalityheroism": "Vous pouvez dépenser 1 Point d'Héroisme/Vilainie pour reprendre vos esprits pendant 1 round.", "BOL.chat.vitalityheroismhint": "Dans ce cas votre vitalité remonte à son maximum divisé par 2 (arrondi au supérieur).", "BOL.chat.vitalitydying": "La Vitalité de {name} est de {hp} ! Il est mourant ...", - "BOL.chat.vitalitydyingheroism": "Vous pouvez cependant dépenser 1 Point d'Héroisme pour Défier la Mort (cf. page 58).", + "BOL.chat.vitalitydyingheroism": "Vous pouvez cependant dépenser 1 Point d'Héroisme/Vilainie pour Défier la Mort (cf. page 58).", "BOL.chat.alchemytitle": "Préparation Alchimique : {name}", "BOL.chat.alchemypoints": "Points de Création Investis : {pcCostCurrent}", "BOL.chat.alchemysuccess": "La préparation alchimique a été réalisée avec succès !
Créez l'item ou l'effet correspondant dans votre Inventaire.
L'avancement dans la préparation a été remis à 0.", @@ -475,19 +475,19 @@ "BOL.chat.applydamagetotarget": "Appliquer les dommages à la cible", "BOL.chat.fightoption": "Option de combat", "BOL.chat.reroll": "Relancer (1 P. Heroisme)", - "BOL.chat.toheroic": "Transformer en succés Héroïque (1 P. Héroisme)", - "BOL.chat.tolegend": "Transformer en succes Légendaire (1 P. Heroisme)", + "BOL.chat.toheroic": "Transformer en succés Héroïque (1 P. Héroisme/Vilainie)", + "BOL.chat.tolegend": "Transformer en succes Légendaire (1 P. Heroisme/Vilainie)", "BOL.chat.hurttitle": "{name} va encaisser {damageTotal} dégats !", "BOL.chat.armordefault": "C'est une attaque au défaut de l'armure : vous devez encaisser SANS la protection de l'armure !", "BOL.chat.witharmor": "Encaisser avec la protection de l'armure", "BOL.chat.withoutarmor": "Encaisser sans la protection de l'armure", - "BOL.chat.shakeoff": "Juste une égratignure (1 Point d'Héroisme)", - "BOL.chat.splinteredshield": "Parade in Extremis avec {name} (1 Point d'Héroisme)", + "BOL.chat.shakeoff": "Juste une égratignure (1 Point d'Héroisme/Vilainie)", + "BOL.chat.splinteredshield": "Parade in Extremis avec {name} (1 Point d'Héroisme/Vilainie)", "BOL.chat.damagesummary": "Dégats subis par {name}", "BOL.chat.protectvalue": "Protection de l'armure", "BOL.chat.noprotectvalue": "Aucune protection d'armure !", - "BOL.chat.heroreducedamage": "Un point d'héroisme dépensé, pour une réduction des dommages supplémentaire de {total}.", - "BOL.chat.herosplintered": "Aucun dommage encaissé, grâce à la parade in-extremis avec {weaponHero.name}. L'arme a été détruite pendant cette parade ! Un point d'héroisme a également été dépensé.", + "BOL.chat.heroreducedamage": "Un point d'héroisme/vilainie dépensé, pour une réduction des dommages supplémentaire de {total}.", + "BOL.chat.herosplintered": "Aucun dommage encaissé, grâce à la parade in-extremis avec {weaponHero.name}. L'arme a été détruite pendant cette parade ! Un point d'héroisme/vilainie a également été dépensé.", "BOL.chat.finaldamage": "Encaissement final : {finalDamage} dégats !", "BOL.chat.spell": "Sort", "BOL.chat.spellcost": "Cout en Points de Pouvoir", @@ -525,13 +525,13 @@ "BOL.chat.usedhoroscope": "Horoscope utilisé", "BOL.chat.horoscopedeleted": "Le(s) Horoscopes utilisé(s) a/ont été supprimé(s) automatiquement.", "BOL.chat.criticaloptions": "Succès critique !! Vous pouvez faire (1 option au choix) :", - "BOL.chat.criticalcarnage": "Faire un Carnage : vous avez une attaque gratuite supplémentaire. Cette seconde attaque ne peut bénéficier d'un Point d'Héroisme.", + "BOL.chat.criticalcarnage": "Faire un Carnage : vous avez une attaque gratuite supplémentaire. Cette seconde attaque ne peut bénéficier d'un Point d'Héroisme/vilainie.", "BOL.chat.criticalplus6": "Coup Dévastateur : +6 aux dommages (cf bouton ci-dessous).", "BOL.chat.criticalprecise": "Coup Précis : Vous frappez pour diminuer les capacités de votre adversaire. Décrivez ce que vous faites, et si le MJ l'accepte, votre opposant subira un Dé de Malus pour les actions concernées.", "BOL.chat.criticalunarm": "Désarmement : Si votre adversaire a une arme en main, vous le désarmez.", "BOL.chat.criticalrabble": "Massacrer la piétaille : Si vous combattez de la Piétaille, les résultats des dommages indiquent le nombre d'adversaires mis hors de combat.", "BOL.chat.criticalpush": "Renversement : Si la taille le permet, vous poussez votre adversaire au sol, il souffrira d'1 Dé de Malus pour toutes ses actions au round suivant.", - "BOL.chat.criticalup": "Transformer en Légendaire : En dépensant 1 point d'Héroisme, vous pouvez transformer ce Succès Héroïque en Légendaire, qui vous permet de prendre 2 options dans la liste ci-dessus (cf. bouton pour un +12 aux dommages par exemple).", + "BOL.chat.criticalup": "Transformer en Légendaire : En dépensant 1 point d'Héroisme/Vilainie, vous pouvez transformer ce Succès Héroïque en Légendaire, qui vous permet de prendre 2 options dans la liste ci-dessus (cf. bouton pour un +12 aux dommages par exemple).", "BOL.chat.criticalinfo": "C'est un succès Héroïque ou Légendaire ! Choisissez vos options et effets !", "BOL.chat.criticalbuttonjournal": "Succès Héroïque/Légendaire", diff --git a/module/actor/actor.js b/module/actor/actor.js index b9c7a1c..ab54c06 100644 --- a/module/actor/actor.js +++ b/module/actor/actor.js @@ -85,11 +85,13 @@ export class BoLActor extends Actor { if (this.type == 'character') { let newVitality = 10 + this.system.attributes.vigor.value + this.system.resources.hp.bonus if (this.system.resources.hp.max != newVitality) { - this.update({ 'system.resources.hp.max': newVitality }) + let actor = this + setTimeout( function() { actor.update({ 'system.resources.hp.max': newVitality }) }, 800 ) } let newPower = 10 + this.system.attributes.mind.value + this.system.resources.power.bonus if (this.system.resources.power.max != newPower) { - this.update({ 'system.resources.power.max': newPower }) + let actor = this + setTimeout( function() { actor.update({ 'system.resources.power.max': newPower }) }, 800 ) } } } @@ -100,8 +102,10 @@ export class BoLActor extends Actor { } else { super.prepareDerivedData() - this.updateResourcesData() - this.manageHealthState(); + if (this.id) { + this.updateResourcesData() + this.manageHealthState() + } } } @@ -109,11 +113,29 @@ export class BoLActor extends Actor { get details() { return this.system.details } + addEffectModifiers( myList, dataPath) { + for (let attr of myList) { + attr.numModifier = 0 + attr.diceModifier = "" + let effects = this.items.filter( i => i.type === "feature" && i.system.subtype === "boleffect" && i.system.properties.identifier == dataPath+attr.key) + for (let effect of effects) { + if ( Number(effect.system.properties.modifier)) { + attr.numModifier += Number(effect.system.properties.modifier) + } else { + attr.diceModifier += "+"+effect.system.properties.modifier + } + } + } + } get attributes() { - return Object.values(this.system.attributes) + let attrList = duplicate(Object.values(this.system.attributes)) + this.addEffectModifiers(attrList, "system.attributes.") + return attrList } get aptitudes() { - return Object.values(this.system.aptitudes) + let aptList = Object.values(this.system.aptitudes) + this.addEffectModifiers(aptList, "system.aptitudes.") + return aptList } /* -------------------------------------------- */ @@ -241,19 +263,18 @@ export class BoLActor extends Actor { /*-------------------------------------------- */ get armorMalusValue() { // used for Fight Options for (let armor of this.armors) { - if (armor.system.properties.armorQuality.includes("light")) { + if (armor.system.properties.armorQuality?.includes("light")) { return 1 } - if (armor.system.properties.armorQuality.includes("medium")) { + if (armor.system.properties.armorQuality?.includes("medium")) { return 2 } - if (armor.system.properties.armorQuality.includes("heavy")) { + if (armor.system.properties.armorQuality?.includes("heavy")) { return 3 } } return 0 } - get resources() { return Object.values(this.system.resources) } @@ -812,6 +833,33 @@ export class BoLActor extends Actor { } return game.bol.config.creatureSize["medium"].order // Medium size per default } + /*-------------------------------------------- */ + checkNumeric(myObject) { + if ( myObject) { + for (let key in myObject) { + if ( myObject[key].value === null ) { + myObject[key].value = 0 + } + if ( myObject[key].value === NaN ) { + myObject[key].value = 0 + } + } + } + } + + /*-------------------------------------------- */ + _preUpdate(data, options, userId) { + if (data.system?.attributes) { + this.checkNumeric(data.system.attributes) + } + if (data.system?.aptitudes) { + this.checkNumeric(data.system.aptitudes) + } + if (data.system?.resources) { + this.checkNumeric(data.system.resources) + } + super._preUpdate(data, options, userId) + } /*-------------------------------------------- */ getInitiativeRank(rollData = undefined, isCombat = false, combatData) { @@ -821,11 +869,12 @@ export class BoLActor extends Actor { let fvttInit = 4 // Pietaille par defaut if (this.type == 'character') { fvttInit = 5 - if (!rollData) { fvttInit = -1 + if (!rollData) { if (isCombat) { - ui.notifications.info(game.i18n.localize("BOL.ui.warninitiative")) - BoLRoll.aptitudeCheck(this, "init", undefined, combatData) + if (game.user.isGM ) { + game.socket.emit("system.bol", { name: "msg_request_init_roll", data: { actorId: this.id, combatData : combatData } }) + } } } else { if (rollData.isLegendary) { diff --git a/module/controllers/bol-rolls.js b/module/controllers/bol-rolls.js index 1fd4434..7d2a8d3 100644 --- a/module/controllers/bol-rolls.js +++ b/module/controllers/bol-rolls.js @@ -284,7 +284,7 @@ export class BoLRoll { for (let effect of this.rollData.bolApplicableEffects) { if (effect.system.properties.modifier == "1B") { this.rollData.bmDice++; - } else if (effect.system.properties.modifier == "1B") { + } else if (effect.system.properties.modifier == "2B") { this.rollData.bmDice += 2; } else if (effect.system.properties.modifier == "1M") { this.rollData.bmDice--; diff --git a/module/system/bol-combat.js b/module/system/bol-combat.js index a434f6d..f68e925 100644 --- a/module/system/bol-combat.js +++ b/module/system/bol-combat.js @@ -25,7 +25,7 @@ export class BoLCombatManager extends Combat { // calculate initiative for (let cId = 0; cId < ids.length; cId++) { const combatant = this.combatants.get(ids[cId]) - let fvttInit = combatant.actor.getInitiativeRank(false, true, {combatId: this.id, combatantId: combatant.id } ) + let fvttInit = combatant.actor.getInitiativeRank(false, true, { combatId: this.id, combatantId: combatant.id }) fvttInit += (cId / 100) await this.updateEmbeddedDocuments("Combatant", [{ _id: ids[cId], initiative: fvttInit }]); } @@ -34,9 +34,18 @@ export class BoLCombatManager extends Combat { /************************************************************************************/ nextRound() { let combatants = this.combatants.contents + let autoRemoveDead = game.settings.get("bol", "auto-remove-dead") // Optionnal auto-removal of dead char. for (let c of combatants) { - let actor = game.actors.get( c.actorId ) - actor.clearRoundModifiers() + //let actor = game.actors.get(c.actorId) + c.actor.clearRoundModifiers() + let toRemove = [] + if (autoRemoveDead && c.actor.type == "encounter" && (c.actor.system.chartype == "tough" || c.actor.system.chartype == "creature" || c.actor.system.chartype == "base") && c.actor.system.resources.hp.value <= 0) { + toRemove.push( c.id || c._id) + } + //console.log("REM", autoRemoveDead, toRemove, c.actor) + if (toRemove.length>0) { + this.deleteEmbeddedDocuments('Combatant', toRemove) + } } super.nextRound() } @@ -45,12 +54,12 @@ export class BoLCombatManager extends Combat { startCombat() { let combatants = this.combatants.contents for (let c of combatants) { - let actor = game.actors.get( c.actorId ) + let actor = game.actors.get(c.actorId) actor.storeVitaliteCombat() } return super.startCombat() } - + /************************************************************************************/ _onDelete() { let combatants = this.combatants.contents @@ -63,4 +72,4 @@ export class BoLCombatManager extends Combat { } } - + diff --git a/module/system/bol-utility.js b/module/system/bol-utility.js index b94a635..f0b6b67 100644 --- a/module/system/bol-utility.js +++ b/module/system/bol-utility.js @@ -1,4 +1,4 @@ -import { BoLDefaultRoll } from "../controllers/bol-rolls.js"; +import { BoLRoll, BoLDefaultRoll } from "../controllers/bol-rolls.js"; // Spell circle to min PP cost const __circle2minpp = { 0: 0, 1: 2, 2: 6, 3: 11 } @@ -26,6 +26,14 @@ export class BoLUtility { type: Boolean, onChange: lang => window.location.reload() }) + game.settings.register("bol", "auto-remove-dead", { + name: "Enlever les PNJs morts automatiquement", + hint: "Supprime les PNJ (piétaille, créatures, coriaces) automatiquement du combat lorsqu'ils sont à 0 Vitalité ou moins", + scope: "world", + config: true, + default: false, + type: Boolean + }) game.settings.register("bol", "dice-formula", { name: "Formule de dés", hint: "Sélectionne la formule de dés (par défaut 2d6)", @@ -305,6 +313,14 @@ export class BoLUtility { let message = game.messages.get(messageId) return message.getFlag("world", "bol-roll-data") } + /* -------------------------------------------- */ + static requestInitRoll(actorId, combatData ) { + let actor = game.actors.get( actorId ) + if (actor && actor.isOwner) { + ui.notifications.info(game.i18n.localize("BOL.ui.warninitiative")) + BoLRoll.aptitudeCheck(actor, "init", undefined, combatData) + } + } /* -------------------------------------------- */ static cleanupButtons(id) { @@ -574,6 +590,9 @@ export class BoLUtility { if (sockmsg.name == "msg_cleanup_buttons") { $(`#${sockmsg.data.id}`).hide() // Hide the options roll buttons } + if (sockmsg.name == "msg_request_init_roll") { + this.requestInitRoll( sockmsg.data.actorId, sockmsg.data.combatData) + } if (sockmsg.name == "msg_damage_handling") { BoLUtility.processDamageHandling(sockmsg.data.attackId, sockmsg.data.defenseMode, sockmsg.data.weaponId, sockmsg.data.msgId) } diff --git a/system.json b/system.json index 7f89bba..863b2ff 100644 --- a/system.json +++ b/system.json @@ -14,7 +14,7 @@ ], "url": "https://www.uberwald.me/gitea/public/bol", "license": "LICENSE.txt", - "version": "10.5.13", + "version": "10.5.14", "compatibility": { "minimum": "10", "verified": "10" @@ -202,7 +202,7 @@ ], "socket": true, "manifest": "https://www.uberwald.me/gitea/public/bol/raw/v10/system.json", - "download": "https://www.uberwald.me/gitea/public/bol/archive/bol-v10.5.13.zip", + "download": "https://www.uberwald.me/gitea/public/bol/archive/bol-v10.5.14.zip", "background": "systems/bol/ui/page_accueil.webp", "gridDistance": 1.5, "gridUnits": "m", diff --git a/templates/actor/parts/tabs/actor-stats.hbs b/templates/actor/parts/tabs/actor-stats.hbs index fd05479..1a64a28 100644 --- a/templates/actor/parts/tabs/actor-stats.hbs +++ b/templates/actor/parts/tabs/actor-stats.hbs @@ -1,8 +1,18 @@
{{#each attributes as |attribute id|}}
-
-
+ + {{#if attribute.numModifier}} + + {{/if}} + {{#if (count attribute.diceModifier)}} + + {{/if}} +
+ + +
+ @@ -24,8 +34,17 @@
{{#each aptitudes as |aptitude id|}}
-
+ + {{#if aptitude.numModifier}} + + {{/if}} + {{#if (count aptitude.diceModifier)}} + + {{/if}} +
+
+