Combat Tracker + power enhancements

This commit is contained in:
LeRatierBretonnien 2023-04-06 20:15:04 +02:00
parent 5b91041a3f
commit 41fbc094bb
7 changed files with 132 additions and 36 deletions

View File

@ -459,10 +459,10 @@
"BOL.chat.isdead": "{name} est mort !", "BOL.chat.isdead": "{name} est mort !",
"BOL.chat.epitaph": "Que son nom soit honoré sur les champs de batailles de Lémurie !", "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.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.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.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.alchemytitle": "Préparation Alchimique : {name}",
"BOL.chat.alchemypoints": "Points de Création Investis : {pcCostCurrent}", "BOL.chat.alchemypoints": "Points de Création Investis : {pcCostCurrent}",
"BOL.chat.alchemysuccess": "La préparation alchimique a été réalisée avec succès !<br>Créez l'item ou l'effet correspondant dans votre Inventaire.<br>L'avancement dans la préparation a été remis à 0.", "BOL.chat.alchemysuccess": "La préparation alchimique a été réalisée avec succès !<br>Créez l'item ou l'effet correspondant dans votre Inventaire.<br>L'avancement dans la préparation a été remis à 0.",
@ -475,19 +475,19 @@
"BOL.chat.applydamagetotarget": "Appliquer les dommages à la cible", "BOL.chat.applydamagetotarget": "Appliquer les dommages à la cible",
"BOL.chat.fightoption": "Option de combat", "BOL.chat.fightoption": "Option de combat",
"BOL.chat.reroll": "Relancer (1 P. Heroisme)", "BOL.chat.reroll": "Relancer (1 P. Heroisme)",
"BOL.chat.toheroic": "Transformer en succés Héroïque (1 P. Héroisme)", "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)", "BOL.chat.tolegend": "Transformer en succes Légendaire (1 P. Heroisme/Vilainie)",
"BOL.chat.hurttitle": "{name} va encaisser {damageTotal} dégats !", "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.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.witharmor": "Encaisser avec la protection de l'armure",
"BOL.chat.withoutarmor": "Encaisser sans 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.shakeoff": "Juste une égratignure (1 Point d'Héroisme/Vilainie)",
"BOL.chat.splinteredshield": "Parade in Extremis avec {name} (1 Point d'Héroisme)", "BOL.chat.splinteredshield": "Parade in Extremis avec {name} (1 Point d'Héroisme/Vilainie)",
"BOL.chat.damagesummary": "Dégats subis par {name}", "BOL.chat.damagesummary": "Dégats subis par {name}",
"BOL.chat.protectvalue": "Protection de l'armure", "BOL.chat.protectvalue": "Protection de l'armure",
"BOL.chat.noprotectvalue": "Aucune protection d'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.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 a également été dépensé.", "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.finaldamage": "Encaissement final : {finalDamage} dégats !",
"BOL.chat.spell": "Sort", "BOL.chat.spell": "Sort",
"BOL.chat.spellcost": "Cout en Points de Pouvoir", "BOL.chat.spellcost": "Cout en Points de Pouvoir",
@ -525,13 +525,13 @@
"BOL.chat.usedhoroscope": "Horoscope utilisé", "BOL.chat.usedhoroscope": "Horoscope utilisé",
"BOL.chat.horoscopedeleted": "Le(s) Horoscopes utilisé(s) a/ont été supprimé(s) automatiquement.", "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.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.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.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.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.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.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.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", "BOL.chat.criticalbuttonjournal": "Succès Héroïque/Légendaire",

View File

@ -85,11 +85,13 @@ export class BoLActor extends Actor {
if (this.type == 'character') { if (this.type == 'character') {
let newVitality = 10 + this.system.attributes.vigor.value + this.system.resources.hp.bonus let newVitality = 10 + this.system.attributes.vigor.value + this.system.resources.hp.bonus
if (this.system.resources.hp.max != newVitality) { 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 let newPower = 10 + this.system.attributes.mind.value + this.system.resources.power.bonus
if (this.system.resources.power.max != newPower) { 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 { } else {
super.prepareDerivedData() super.prepareDerivedData()
if (this.id) {
this.updateResourcesData() this.updateResourcesData()
this.manageHealthState(); this.manageHealthState()
}
} }
} }
@ -109,11 +113,29 @@ export class BoLActor extends Actor {
get details() { get details() {
return this.system.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() { 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() { 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 get armorMalusValue() { // used for Fight Options
for (let armor of this.armors) { for (let armor of this.armors) {
if (armor.system.properties.armorQuality.includes("light")) { if (armor.system.properties.armorQuality?.includes("light")) {
return 1 return 1
} }
if (armor.system.properties.armorQuality.includes("medium")) { if (armor.system.properties.armorQuality?.includes("medium")) {
return 2 return 2
} }
if (armor.system.properties.armorQuality.includes("heavy")) { if (armor.system.properties.armorQuality?.includes("heavy")) {
return 3 return 3
} }
} }
return 0 return 0
} }
get resources() { get resources() {
return Object.values(this.system.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 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) { getInitiativeRank(rollData = undefined, isCombat = false, combatData) {
@ -821,11 +869,12 @@ export class BoLActor extends Actor {
let fvttInit = 4 // Pietaille par defaut let fvttInit = 4 // Pietaille par defaut
if (this.type == 'character') { if (this.type == 'character') {
fvttInit = 5 fvttInit = 5
if (!rollData) {
fvttInit = -1 fvttInit = -1
if (!rollData) {
if (isCombat) { if (isCombat) {
ui.notifications.info(game.i18n.localize("BOL.ui.warninitiative")) if (game.user.isGM ) {
BoLRoll.aptitudeCheck(this, "init", undefined, combatData) game.socket.emit("system.bol", { name: "msg_request_init_roll", data: { actorId: this.id, combatData : combatData } })
}
} }
} else { } else {
if (rollData.isLegendary) { if (rollData.isLegendary) {

View File

@ -284,7 +284,7 @@ export class BoLRoll {
for (let effect of this.rollData.bolApplicableEffects) { for (let effect of this.rollData.bolApplicableEffects) {
if (effect.system.properties.modifier == "1B") { if (effect.system.properties.modifier == "1B") {
this.rollData.bmDice++; this.rollData.bmDice++;
} else if (effect.system.properties.modifier == "1B") { } else if (effect.system.properties.modifier == "2B") {
this.rollData.bmDice += 2; this.rollData.bmDice += 2;
} else if (effect.system.properties.modifier == "1M") { } else if (effect.system.properties.modifier == "1M") {
this.rollData.bmDice--; this.rollData.bmDice--;

View File

@ -34,9 +34,18 @@ export class BoLCombatManager extends Combat {
/************************************************************************************/ /************************************************************************************/
nextRound() { nextRound() {
let combatants = this.combatants.contents 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) { for (let c of combatants) {
let actor = game.actors.get( c.actorId ) //let actor = game.actors.get(c.actorId)
actor.clearRoundModifiers() 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() super.nextRound()
} }

View File

@ -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 // Spell circle to min PP cost
const __circle2minpp = { 0: 0, 1: 2, 2: 6, 3: 11 } const __circle2minpp = { 0: 0, 1: 2, 2: 6, 3: 11 }
@ -26,6 +26,14 @@ export class BoLUtility {
type: Boolean, type: Boolean,
onChange: lang => window.location.reload() 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", { game.settings.register("bol", "dice-formula", {
name: "Formule de dés", name: "Formule de dés",
hint: "Sélectionne la formule de dés (par défaut 2d6)", 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) let message = game.messages.get(messageId)
return message.getFlag("world", "bol-roll-data") 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) { static cleanupButtons(id) {
@ -574,6 +590,9 @@ export class BoLUtility {
if (sockmsg.name == "msg_cleanup_buttons") { if (sockmsg.name == "msg_cleanup_buttons") {
$(`#${sockmsg.data.id}`).hide() // Hide the options roll 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") { if (sockmsg.name == "msg_damage_handling") {
BoLUtility.processDamageHandling(sockmsg.data.attackId, sockmsg.data.defenseMode, sockmsg.data.weaponId, sockmsg.data.msgId) BoLUtility.processDamageHandling(sockmsg.data.attackId, sockmsg.data.defenseMode, sockmsg.data.weaponId, sockmsg.data.msgId)
} }

View File

@ -14,7 +14,7 @@
], ],
"url": "https://www.uberwald.me/gitea/public/bol", "url": "https://www.uberwald.me/gitea/public/bol",
"license": "LICENSE.txt", "license": "LICENSE.txt",
"version": "10.5.13", "version": "10.5.14",
"compatibility": { "compatibility": {
"minimum": "10", "minimum": "10",
"verified": "10" "verified": "10"
@ -202,7 +202,7 @@
], ],
"socket": true, "socket": true,
"manifest": "https://www.uberwald.me/gitea/public/bol/raw/v10/system.json", "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", "background": "systems/bol/ui/page_accueil.webp",
"gridDistance": 1.5, "gridDistance": 1.5,
"gridUnits": "m", "gridUnits": "m",

View File

@ -1,8 +1,18 @@
<div class="attributes flexrow"> <div class="attributes flexrow">
{{#each attributes as |attribute id|}} {{#each attributes as |attribute id|}}
<div class="attribute stat flex1 flex-group-center {{key}}"> <div class="attribute stat flex1 flex-group-center {{key}}">
<label class="stat-label"><a class="rollable" data-roll-type="attribute" data-roll="2d6+@attributes.{{key}}.value" data-adv="0" data-key="{{key}}">{{localize label}}</a></label><br/> <label class="stat-label"><a class="rollable" data-roll-type="attribute" data-roll="2d6+@attributes.{{key}}.value" data-adv="0" data-key="{{key}}">{{localize label}}</a></label>
<input class="stat-value rounded" type="text" name="system.attributes.{{key}}.value" value="{{numberFormat value decimals=0 sign=true}}" data-dtype="Number"/><br/> {{#if attribute.numModifier}}
<label class="stat-value rounded">{{attribute.numModifier}}</label>
{{/if}}
{{#if (count attribute.diceModifier)}}
<label class="stat-value rounded">{{attribute.diceModifier}}</label>
{{/if}}
<br/>
<input class="stat-value rounded" type="text" name="system.attributes.{{key}}.value" value="{{numberFormat value decimals=0 sign=true}}" data-dtype="Number"/>
<br/>
<span class="stat-roll rollable" title="2d6" data-roll-type="attribute" data-roll="2d6+@attributes.{{key}}.value" data-adv="0" data-key="{{key}}"> <span class="stat-roll rollable" title="2d6" data-roll-type="attribute" data-roll="2d6+@attributes.{{key}}.value" data-adv="0" data-key="{{key}}">
<i class="darkgreen fas fa-dice"></i> <i class="darkgreen fas fa-dice"></i>
</span> </span>
@ -24,8 +34,17 @@
<div class="aptitudes flexrow"> <div class="aptitudes flexrow">
{{#each aptitudes as |aptitude id|}} {{#each aptitudes as |aptitude id|}}
<div class="aptitude stat flex1 flex-group-center"> <div class="aptitude stat flex1 flex-group-center">
<label class="stat-label"><a class="rollable" data-roll-type="aptitude" data-roll="2d6+@aptitudes.{{key}}.value" data-adv="0" data-key="{{key}}">{{localize label}}</a></label><br/> <label class="stat-label"><a class="rollable" data-roll-type="aptitude" data-roll="2d6+@aptitudes.{{key}}.value" data-adv="0" data-key="{{key}}">{{localize label}}</a></label>
{{#if aptitude.numModifier}}
<label class="stat-value rounded">{{aptitude.numModifier}}</label>
{{/if}}
{{#if (count aptitude.diceModifier)}}
<label class="stat-value rounded">{{aptitude.diceModifier}}</label>
{{/if}}
<br/>
<input class="stat-value rounded-border" type="text" name="system.aptitudes.{{key}}.value" value="{{numberFormat value decimals=0 sign=true}}" data-dtype="Number"/><br/> <input class="stat-value rounded-border" type="text" name="system.aptitudes.{{key}}.value" value="{{numberFormat value decimals=0 sign=true}}" data-dtype="Number"/><br/>
<span class="stat-roll rollable" title="2d6" data-roll-type="aptitude" data-roll="2d6+@aptitudes.{{key}}.value" data-adv="0" data-key="{{key}}"> <span class="stat-roll rollable" title="2d6" data-roll-type="aptitude" data-roll="2d6+@aptitudes.{{key}}.value" data-adv="0" data-key="{{key}}">
<i class="darkgreen fas fa-dice"></i> <i class="darkgreen fas fa-dice"></i>
</span> </span>