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.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 !<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.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",

View File

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

View File

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

View File

@ -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,7 +54,7 @@ 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()

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

View File

@ -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",

View File

@ -1,8 +1,18 @@
<div class="attributes flexrow">
{{#each attributes as |attribute id|}}
<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/>
<input class="stat-value rounded" type="text" name="system.attributes.{{key}}.value" value="{{numberFormat value decimals=0 sign=true}}" data-dtype="Number"/><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>
{{#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}}">
<i class="darkgreen fas fa-dice"></i>
</span>
@ -24,8 +34,17 @@
<div class="aptitudes flexrow">
{{#each aptitudes as |aptitude id|}}
<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/>
<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>
</span>