diff --git a/changelog.md b/changelog.md index e9a7cf88..cefc6aec 100644 --- a/changelog.md +++ b/changelog.md @@ -20,6 +20,10 @@ - affichage du statut de surprise du défenseur - prise en compte des significatives (demi-surprises, armes disparates, particulière en finesse) + - gestion de l'appel à la chance + - gestion de l'utilisation de la destinée + - gestion du recul depuis le messages + - gestion de l'encaissement depuis le messages - impossible de faire un jet "actif" en surprise totale (attaque, parade, ...) ## 13.0.8 - Le renouveau d'Illysis diff --git a/css/foundryvtt-reve-de-dragon.css b/css/foundryvtt-reve-de-dragon.css index 6956b215..36ac4ced 100644 --- a/css/foundryvtt-reve-de-dragon.css +++ b/css/foundryvtt-reve-de-dragon.css @@ -2682,6 +2682,17 @@ select, max-width: 1rem; max-height: 1rem; } +.system-foundryvtt-reve-de-dragon .chat-card-info { + font-size: 1.1rem; + display: flex; + flex-direction: row; +} +.system-foundryvtt-reve-de-dragon .chat-card-info img { + margin: 0 0.5rem; + max-width: 1rem; + max-height: 1rem; + filter: invert(0.8); +} .system-foundryvtt-reve-de-dragon .chat-card-button { text-shadow: 1px 1px #4d3534; box-shadow: inset 1x 1px #a6827e; diff --git a/less/foundryvtt-reve-de-dragon.less b/less/foundryvtt-reve-de-dragon.less index 815f0bd9..f204ceab 100644 --- a/less/foundryvtt-reve-de-dragon.less +++ b/less/foundryvtt-reve-de-dragon.less @@ -1963,6 +1963,18 @@ max-height: 1rem; } } + .chat-card-info{ + font-size: 1.1rem; + display: flex; + flex-direction: row; + + img { + margin: 0 0.5rem; + max-width: 1rem; + max-height: 1rem; + filter: invert(0.8); + } + } .chat-card-button{ text-shadow: 1px 1px #4d3534; diff --git a/module/actor.js b/module/actor.js index ebdc2e7d..e683cdf5 100644 --- a/module/actor.js +++ b/module/actor.js @@ -506,7 +506,7 @@ export class RdDActor extends RdDBaseActorSang { 'system.sante.fatigue.value': 0, 'system.compteurs.ethylisme': { value: 1, nb_doses: 0, jet_moral: false } }) - await this.removeEffects(e => e.id != STATUSES.StatusDemiReve); + await this.removeEffects(e => !e.statuses?.has(STATUSES.StatusDemiReve)); await this.supprimerBlessures(it => true); await ChatMessage.create({ whisper: ChatUtility.getOwners(this), @@ -2513,29 +2513,27 @@ export class RdDActor extends RdDBaseActorSang { } /* -------------------------------------------- */ - async computeArmure(attackerRoll) { - let dmg = (attackerRoll.dmg.dmgArme ?? 0) + (attackerRoll.dmg.dmgActor ?? 0); - let armeData = attackerRoll.arme; + async computeArmure(dmg) { + let baseDmg = (dmg.dmgArme ?? 0) + (dmg.dmgActor ?? 0); let protection = 0; - const armures = this.items.filter(it => it.type == "armure" && it.system.equipe); - for (const armure of armures) { - protection += await RdDDice.rollTotal(armure.system.protection.toString()); - if (dmg > 0 && attackerRoll.dmg.encaisserSpecial != "noarmure") { - await armure.deteriorerArmure(dmg) - dmg = 0; + if (dmg.encaisserSpecial != "noarmure") { + const armures = this.items.filter(it => it.type == "armure" && it.system.equipe) + + for (const armure of armures) { + protection += await RdDDice.rollTotal(armure.system.protection.toString()); + if (baseDmg > 0 && dmg.encaisserSpecial != "noarmure") { + await armure.deteriorerArmure(baseDmg) + baseDmg = 0; + } + } + protection -= Math.min(dmg.penetration, protection) + protection += this.getProtectionNaturelle(); + // Gestion des cas particuliers sur la fenêtre d'encaissement + if (dmg.encaisserSpecial == "chute") { + protection = Math.min(protection, 2); } } - const penetration = Misc.toInt(armeData?.system.penetration ?? 0); - protection = Math.max(protection - penetration, 0); - protection += this.getProtectionNaturelle(); - // Gestion des cas particuliers sur la fenêtre d'encaissement - if (attackerRoll.dmg.encaisserSpecial == "noarmure") { - protection = 0; - } - if (attackerRoll.dmg.encaisserSpecial == "chute") { - protection = Math.min(protection, 2); - } - console.log("Final protect", protection, attackerRoll); + console.log("Final protect", protection, dmg) return protection; } diff --git a/module/actor/base-actor-reve.js b/module/actor/base-actor-reve.js index 98974363..4199ea53 100644 --- a/module/actor/base-actor-reve.js +++ b/module/actor/base-actor-reve.js @@ -118,8 +118,7 @@ export class RdDBaseActorReve extends RdDBaseActor { .filter(it => it != undefined); } - - async computeArmure(attackerRoll) { return this.getProtectionNaturelle() } + async computeArmure(dmg) { return this.getProtectionNaturelle() } async remiseANeuf() { } async appliquerAjoutExperience(rollData, hideChatMessage = 'show') { } @@ -227,12 +226,12 @@ export class RdDBaseActorReve extends RdDBaseActor { isEffectAllowed(effectId) { return false } getEffects(filter = e => true, forceRequise = undefined) { - const effects = this.getEmbeddedCollection("ActiveEffect").filter(filter) + const effects = this.getEmbeddedCollection("ActiveEffect") + const selected = effects.filter(filter) if (forceRequise && this.isForceInsuffisante(forceRequise)) { - /// TODO - effects.push(StatusEffects.prepareActiveEffect(STATUSES.StatusForceWeak)) + selected.push(StatusEffects.prepareActiveEffect(STATUSES.StatusForceWeak)) } - return effects + return selected } getEffectByStatus(statusId) { @@ -257,7 +256,8 @@ export class RdDBaseActorReve extends RdDBaseActor { async removeEffects(filter = e => true) { if (game.user.isGM) { - const ids = this.getEffects(filter).map(it => it.id); + const effectsToRemove = this.getEffects(filter); + const ids = effectsToRemove.map(it => it.id); await this.deleteEmbeddedDocuments('ActiveEffect', ids); } } @@ -495,29 +495,37 @@ export class RdDBaseActorReve extends RdDBaseActor { /* -------------------------------------------- */ async encaisser() { await RdDEncaisser.encaisser(this) } - async encaisserDommages(rollData, attacker = undefined, show = undefined, attackerToken = undefined, defenderToken = undefined) { + async encaisserDommages(dmg, attacker = undefined, show = undefined, attackerToken = undefined, defenderToken = undefined) { if (attacker && !await attacker.accorder(this, 'avant-encaissement')) { - return; + return } - const armure = await this.computeArmure(rollData); + if (!Misc.isOwnerPlayer(this)) { + return RdDBaseActor.remoteActorCall({ + tokenId: attackerToken?.id ?? this.token?.id, + actorId: this.id, + method: 'encaisserDommages', args: [dmg, attacker, show, attackerToken, defenderToken] + }) + } + + const armure = await this.computeArmure(dmg) if (ReglesOptionnelles.isUsing('validation-encaissement-gr')) { - await this.encaisserDommagesValidationGR(rollData, armure, show, attackerToken, defenderToken); + await this.encaisserDommagesValidationGR(dmg, armure, show, attackerToken, defenderToken); } else { - const jet = await RdDUtility.jetEncaissement(this, rollData, armure, { showDice: SHOW_DICE }); + const jet = await RdDUtility.jetEncaissement(this, dmg, armure, { showDice: SHOW_DICE }); await this.$onEncaissement(jet, show, attackerToken, defenderToken) } } - async encaisserDommagesValidationGR(rollData, armure, show, attackerToken, defenderToken) { + async encaisserDommagesValidationGR(dmg, armure, show, attackerToken, defenderToken) { if (!game.user.isGM) { RdDBaseActor.remoteActorCall({ tokenId: this.token?.id, actorId: this.id, - method: 'encaisserDommagesValidationGR', args: [rollData, armure, show, attackerToken, defenderToken] + method: 'encaisserDommagesValidationGR', args: [dmg, armure, show, attackerToken, defenderToken] }) } else { - DialogValidationEncaissement.validerEncaissement(this, rollData, armure, + DialogValidationEncaissement.validerEncaissement(this, dmg, armure, jet => this.$onEncaissement(jet, show, attackerToken, defenderToken)); } } @@ -554,15 +562,37 @@ export class RdDBaseActorReve extends RdDBaseActor { } } + async encaisserRecul(force, dmgArme = 0) { + const diffRecul = this.getTaille() - force - dmgArme + const rolled = await RdDResolutionTable.roll(10, diffRecul) + if (rolled.isSuccess) { + return 'encaisse' + } + if (rolled.isETotal || (await this.rollEquilibre(diffRecul)).isEchec) { + await this.setEffect(STATUSES.StatusProne, true) + return 'chute' + } + return 'recul' + } + + /* -------------------------------------------- */ + async rollEquilibre(diff) { + // TODO: accrobatie optionnelle sur jet d'équilibre? + if (ReglesOptionnelles.isSet('acrobatie-pour-recul')){ + diff += Math.max(0, this.getCompetence('acrobatie')?.system.niveau ?? 0) + } + return await RdDResolutionTable.roll(this.getAgilite(), diff); + } + /* -------------------------------------------- */ async accorder(entite, when = 'avant-encaissement') { if (when != game.settings.get(SYSTEM_RDD, "accorder-entite-cauchemar") || entite == undefined || !entite.isEntite([ENTITE_INCARNE]) || entite.isEntiteAccordee(this)) { - return true; + return true } - const rolled = await RdDResolutionTable.roll(this.getReveActuel(), - Number(entite.getNiveau())); + const rolled = await RdDResolutionTable.roll(this.getReveActuel(), - Number(entite.getNiveau())) const rollData = { alias: this.getAlias(), rolled: rolled, @@ -571,11 +601,11 @@ export class RdDBaseActorReve extends RdDBaseActor { }; if (rolled.isSuccess) { - await entite.setEntiteReveAccordee(this); + await entite.setEntiteReveAccordee(this) } - await RdDRollResult.displayRollData(rollData, this, 'chat-resultat-accorder-cauchemar.hbs'); - await this.appliquerAjoutExperience(rollData, true); + await RdDRollResult.displayRollData(rollData, this, 'chat-resultat-accorder-cauchemar.hbs') + await this.appliquerAjoutExperience(rollData, true) return rolled.isSuccess; } diff --git a/module/actor/creature.js b/module/actor/creature.js index 6724f38b..95c42364 100644 --- a/module/actor/creature.js +++ b/module/actor/creature.js @@ -1,6 +1,4 @@ -import { Grammar } from "../grammar.js"; import { ITEM_TYPES } from "../constants.js"; -import { LIST_CARAC_AUTRES } from "../rdd-carac.js"; import { RdDBaseActorSang } from "./base-actor-sang.js"; export class RdDCreature extends RdDBaseActorSang { @@ -45,5 +43,4 @@ export class RdDCreature extends RdDBaseActorSang { } return undefined } - } diff --git a/module/dialog-validation-encaissement.js b/module/dialog-validation-encaissement.js index 55daa7a3..825b4c06 100644 --- a/module/dialog-validation-encaissement.js +++ b/module/dialog-validation-encaissement.js @@ -7,18 +7,17 @@ import { RdDUtility } from "./rdd-utility.js"; */ export class DialogValidationEncaissement extends Dialog { - static async validerEncaissement(actor, rollData, armure, onEncaisser) { - const encaissement = await RdDUtility.jetEncaissement(actor, rollData, armure, { showDice: HIDE_DICE }); + static async validerEncaissement(actor, dmg, armure, onEncaisser) { + const encaissement = await RdDUtility.jetEncaissement(actor, dmg, armure, { showDice: HIDE_DICE }); const html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-validation-encaissement.hbs', { actor: actor, - rollData: rollData, encaissement: encaissement }); - new DialogValidationEncaissement(html, actor, rollData, armure, encaissement, onEncaisser).render(true); + new DialogValidationEncaissement(html, actor, dmg, armure, encaissement, onEncaisser).render(true); } /* -------------------------------------------- */ - constructor(html, actor, rollData, armure, encaissement, onEncaisser) { + constructor(html, actor, dmg, armure, encaissement, onEncaisser) { // Common conf let buttons = { "valider": { label: "Valider", callback: html => this.onValider() }, @@ -42,11 +41,11 @@ export class DialogValidationEncaissement extends Dialog { super(dialogConf, dialogOptions); this.actor = actor - this.rollData = rollData; - this.armure = armure; - this.encaissement = encaissement; - this.onEncaisser = onEncaisser; - this.forceDiceResult = {total: encaissement.roll.result }; + this.dmg = dmg + this.armure = armure + this.encaissement = encaissement + this.onEncaisser = onEncaisser + this.forceDiceResult = {total: encaissement.roll.result } } /* -------------------------------------------- */ @@ -55,14 +54,14 @@ export class DialogValidationEncaissement extends Dialog { this.html = html; this.html.find('input.encaissement-roll-result').keyup(async event => { this.forceDiceResult.total = event.currentTarget.value; - this.encaissement = await RdDUtility.jetEncaissement(this.actor, this.rollData, this.armure, { showDice: HIDE_DICE, forceDiceResult: this.forceDiceResult}); + this.encaissement = await RdDUtility.jetEncaissement(this.actor, this.dmg, this.armure, { showDice: HIDE_DICE, forceDiceResult: this.forceDiceResult}); this.html.find('label.encaissement-total').text(this.encaissement.total); this.html.find('label.encaissement-blessure').text(this.encaissement.blessures) }); } async onValider() { - this.encaissement = await RdDUtility.jetEncaissement(this.actor, this.rollData, this.armure, { showDice: SHOW_DICE, forceDiceResult: this.forceDiceResult}); + this.encaissement = await RdDUtility.jetEncaissement(this.actor, this.dmg, this.armure, { showDice: SHOW_DICE, forceDiceResult: this.forceDiceResult}); this.onEncaisser(this.encaissement) } } diff --git a/module/rdd-bonus.js b/module/rdd-bonus.js index 54dbadca..f8a9d969 100644 --- a/module/rdd-bonus.js +++ b/module/rdd-bonus.js @@ -1,5 +1,6 @@ import { RdDItemArme } from "./item/arme.js"; import { RdDPossession } from "./rdd-possession.js"; +import { ReglesOptionnelles } from "./settings/regles-optionnelles.js"; const conditionsTactiques = [ { key: '', label: '', dmg: 0, attaque: 0, parade: 0, esquive: true, isTactique: false }, @@ -35,11 +36,14 @@ export class RdDBonus { /* -------------------------------------------- */ static dmg(rollData, actor, isEntiteIncarnee = false) { + const diff = rollData.diffLibre; const dmgArme = RdDBonus.dmgArme(rollData.arme, rollData.arme?.system.dommagesReels) const forceRequise = rollData.arme ? RdDItemArme.valeurMain(rollData.arme.system.force ?? 0, RdDItemArme.getMainAttaque(rollData.competence)) : 0 let dmg = { total: 0, dmgArme: dmgArme, + diff: diff, + dmgDiffLibre: ReglesOptionnelles.isUsing('degat-ajout-malus-libre') ? Math.abs(diff ?? 0) : 0, penetration: RdDBonus._peneration(rollData), dmgTactique: RdDBonus.dmgBonus(rollData.tactique), dmgParticuliere: RdDBonus._dmgParticuliere(rollData), @@ -51,6 +55,28 @@ export class RdDBonus { dmg.total = dmg.dmgSurprise + dmg.dmgTactique + dmg.dmgArme + dmg.dmgActor + dmg.dmgParticuliere + dmg.dmgForceInsuffisante return dmg; } + + static dmgRollV2(rollData, current) { + const actor = rollData.active.actor + const attaque = current.attaque + const arme = attaque.arme + const dmgArme = RdDBonus.dmgArme(arme, attaque.dommagesArme) + const dmg = { + total: 0, + dmgArme: dmgArme, + penetration: arme.penetration(), + diff: attaque.diff, + dmgTactique: current.tactique?.dmg ?? 0, + dmgParticuliere: 0, // TODO RdDBonus._dmgParticuliere(rollData), + dmgSurprise: rollData.opponent?.surprise?.dmg ?? 0, + mortalite: RdDBonus.mortalite(current.dmg?.mortalite, arme.system.mortalite, rollData.opponent?.actor?.isEntite()), + dmgActor: RdDBonus.bonusDmg(actor, attaque.carac.key, dmgArme, attaque.forceRequise), + dmgForceInsuffisante: Math.min(0, actor.getForce() - attaque.forceRequise), + dmgDiffLibre: ReglesOptionnelles.isUsing('degat-ajout-malus-libre') ? Math.abs(attaque.diff ?? 0) : 0 + } + dmg.total = dmg.dmgSurprise + dmg.dmgTactique + dmg.dmgArme + dmg.dmgActor + dmg.dmgParticuliere + dmg.dmgForceInsuffisante + dmg.dmgDiffLibre + return dmg + } /* -------------------------------------------- */ static description(condition) { diff --git a/module/rdd-combat.js b/module/rdd-combat.js index 08b35d61..de91a987 100644 --- a/module/rdd-combat.js +++ b/module/rdd-combat.js @@ -373,7 +373,7 @@ export class RdDCombat { if (Misc.isOwnerPlayer(defender)) { let attackerRoll = msg.attackerRoll; let attacker = msg.attackerId ? game.actors.get(msg.attackerId) : undefined; - defender.encaisserDommages(attackerRoll, attacker, msg.attackerToken); + defender.encaisserDommages(attackerRoll.dmg, attacker, msg.attackerToken); const rddCombat = RdDCombat.rddCombatForAttackerAndDefender(msg.attackerId, msg.attackerToken.id, msg.defenderToken.id); rddCombat?.removeChatMessageActionsPasseArme(attackerRoll.passeArme); } @@ -982,21 +982,24 @@ export class RdDCombat { }) } - async doRollDefense(rollData) { + async doRollDefense(rollData, callbacks = []) { await RollDialog.create(rollData, { onRollDone: (dialog) => { if (!OptionsAvancees.isUsing(ROLL_DIALOG_V2_TEST)) dialog.close() }, customChatMessage: true, - callbacks: [async (roll) => { - this.removeChatMessageActionsPasseArme(roll.passeArme); - // defense: esquive / arme de parade / competence de défense - if (!RdDCombat.isParticuliere(roll)) { - await roll.active.actor.incDecItemUse(roll.current[PART_DEFENSE].defense?.id); - } - await this._onDefense(roll); - }] + callbacks: [ + async (roll) => { + this.removeChatMessageActionsPasseArme(roll.passeArme); + // defense: esquive / arme de parade / competence de défense + if (!RdDCombat.isParticuliere(roll)) { + await roll.active.actor.incDecItemUse(roll.current[PART_DEFENSE].defense?.id); + } + await this._onDefense(roll); + }, + ...callbacks + ] }) } @@ -1027,12 +1030,10 @@ export class RdDCombat { } async _onDefense(rollData) { - console.log("RdDCombat._onDefense >>>", rollData) const isEsquive = rollData.current[PART_DEFENSE].isEsquive const isParade = !isEsquive if (RdDCombat.isReussite(rollData)) { if (isParade) { - await this.computeRecul(rollData) await this.computeDeteriorationArme(rollData) } @@ -1040,11 +1041,6 @@ export class RdDCombat { await this._onDefenseParticuliere(rollData, isEsquive) } } - else { - //await this._sendMessageDefense(rollData.attackerRoll, rollData, { defense: true }) - } - - // TODO: modify chat message this.removeChatMessageActionsPasseArme(rollData.passeArme) } @@ -1269,44 +1265,14 @@ export class RdDCombat { } const attackerRoll = defenderRoll.attackerRoll; if (this._isForceOuCharge(attackerRoll, defenderRoll.v2)) { - const impact = this._computeImpactRecul(attackerRoll); - const rollRecul = await RdDResolutionTable.roll(10, impact) - defenderRoll.show.recul = await this.gererRecul(rollRecul, impact) + defenderRoll.show.recul = this.defender.encaisserRecul(this.attacker.getForce(), attackerRoll.dmg.dmgArme) } } - async gererRecul(rolled, impact) { - if (rolled.isSuccess) { - return 'encaisse' - } - if (rolled.isETotal || this._isReculCauseChute(impact)) { - - await this.defender.setEffect(STATUSES.StatusProne, true) - return 'chute' - } - return 'recul' - } - - /* -------------------------------------------- */ - async _isReculCauseChute(impact) { - const agilite = this.defender.getAgilite() - const chute = await RdDResolutionTable.rollData({ caracValue: agilite, finalLevel: impact }) - return chute.rolled.isEchec - } - _isForceOuCharge(attaque, isRollV2 = false /* TODO: delete roll V1 */) { return attaque.particuliere == 'force' || 'charge' == (isRollV2 ? attaque.tactique?.key : attaque.tactique) } - /* -------------------------------------------- */ - _computeImpactRecul(attackerRoll) { - const taille = this.defender.getTaille() - const force = this.attacker.getForce() - const dommages = attackerRoll.dmg /* TODO: delete roll V1 */ - ? attackerRoll.dmg.dmgArme - : attackerRoll.arme.system.dommagesReels ?? attaque.arme.system.dommages; - return taille - (force + dommages); - } /* -------------------------------------------- */ async encaisser(attackerRoll, defenderRoll) { @@ -1316,12 +1282,16 @@ export class RdDCombat { this._onEchecTotal(defenderRoll); } + await this.doRollEncaissement(attackerRoll, defenderRoll); + this.removeChatMessageActionsPasseArme(attackerRoll.passeArme); + } + + async doRollEncaissement(attackerRoll, defenderRoll) { if (Misc.isOwnerPlayer(this.defender)) { attackerRoll.attackerId = this.attackerId; attackerRoll.defenderTokenId = this.defenderToken.id; - await this.computeRecul(defenderRoll); - await this.defender.encaisserDommages(attackerRoll, this.attacker, defenderRoll?.show, this.attackerToken, this.defenderToken); + await this.defender.encaisserDommages(attackerRoll.dmg, this.attacker, defenderRoll?.show, this.attackerToken, this.defenderToken); } else { // envoi à un GM: les joueurs n'ont pas le droit de modifier les personnages qu'ils ne possèdent pas game.socket.emit(SYSTEM_SOCKET_ID, { @@ -1332,9 +1302,8 @@ export class RdDCombat { attackerToken: this.attackerToken, defenderToken: this.defenderToken } - }); + }) } - this.removeChatMessageActionsPasseArme(attackerRoll.passeArme); } /* -------------------------------------------- */ diff --git a/module/rdd-roll-encaisser.js b/module/rdd-roll-encaisser.js index 9b5728bc..e11b3a23 100644 --- a/module/rdd-roll-encaisser.js +++ b/module/rdd-roll-encaisser.js @@ -66,12 +66,11 @@ export class RdDEncaisser extends Dialog { /* -------------------------------------------- */ performEncaisser(mortalite) { this.actor.encaisserDommages({ - dmg: { - total: Number(this.modifier), - ajustement: Number(this.modifier), - encaisserSpecial: this.encaisserSpecial, - mortalite: mortalite - } + total: Number(this.modifier), + ajustement: Number(this.modifier), + encaisserSpecial: this.encaisserSpecial, + mortalite: mortalite, + penetration: 0 }) } } diff --git a/module/rdd-roll.js b/module/rdd-roll.js index 58a1fec0..7ae8900d 100644 --- a/module/rdd-roll.js +++ b/module/rdd-roll.js @@ -9,6 +9,7 @@ import { RdDResolutionTable } from "./rdd-resolution-table.js"; import { ReglesOptionnelles } from "./settings/regles-optionnelles.js"; import { Grammar } from "./grammar.js"; import { ACTOR_TYPES } from "./constants.js"; +import { RdDUtility } from "./rdd-utility.js"; /** * Extend the base Dialog entity to select roll parameters diff --git a/module/rdd-utility.js b/module/rdd-utility.js index 69b802b5..05c2f61e 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -610,35 +610,33 @@ export class RdDUtility { /* -------------------------------------------- */ static async getLocalisation(type = 'personnage') { - let result = await RdDDice.rollTotal("1d20"); - let txt = "" + const loc = { result: await RdDDice.rollTotal("1d20")}; if (type == 'personnage') { - if (result <= 3) txt = "Jambe, genou, pied, jarret"; - else if (result <= 7) txt = "Hanche, cuisse, fesse"; - else if (result <= 9) txt = "Ventre, reins"; - else if (result <= 12) txt = "Poitrine, dos"; - else if (result <= 14) txt = "Avant-bras, main, coude"; - else if (result <= 18) txt = "Epaule, bras, omoplate"; - else if (result == 19) txt = "Tête"; - else if (result == 20) txt = "Tête (visage)"; + if (loc.result <= 3) loc.txt = "Jambe, genou, pied, jarret"; + else if (loc.result <= 7) loc.txt = "Hanche, cuisse, fesse"; + else if (loc.result <= 9) loc.txt = "Ventre, reins"; + else if (loc.result <= 12) loc.txt = "Poitrine, dos"; + else if (loc.result <= 14) loc.txt = "Avant-bras, main, coude"; + else if (loc.result <= 18) loc.txt = "Epaule, bras, omoplate"; + else if (loc.result == 19) loc.txt = "Tête"; + else if (loc.result == 20) loc.txt = "Tête (visage)"; } else { - if (result <= 7) txt = "Jambes/Pattes"; - else if (result <= 18) txt = "Corps"; - else if (result <= 20) txt = "Tête"; + if (loc.result <= 7) loc.txt = "Jambes/Pattes"; + else if (loc.result <= 18) loc.txt = "Corps"; + else if (loc.result <= 20) loc.txt = "Tête"; } - - return { result: result, label: txt }; + return loc } /* -------------------------------------------- */ - static async jetEncaissement(actor, rollData, armure, options = { showDice: HIDE_DICE }) { - const diff = Math.abs(rollData.diffLibre); - let formula = RdDUtility.formuleEncaissement(diff, options) + static async jetEncaissement(actor, dmg, armure, options = { showDice: HIDE_DICE }) { + const diff = Math.abs(dmg.diff) + const formula = RdDUtility.formuleEncaissement(diff, options) const roll = await RdDDice.roll(formula, options); RdDUtility.remplaceDeMinParDifficulte(roll, diff, options); - return await RdDUtility.prepareEncaissement(actor, rollData, roll, armure); + return await RdDUtility.prepareEncaissement(actor, dmg, roll, armure); } static remplaceDeMinParDifficulte(roll, diff, options) { @@ -661,7 +659,7 @@ export class RdDUtility { } } - static formuleEncaissement(diff, options) { + static formuleEncaissement(diff) { // Chaque dé fait au minimum la difficulté libre if (ReglesOptionnelles.isUsing('degat-minimum-malus-libre')) { return `2d10min${diff}` @@ -670,25 +668,22 @@ export class RdDUtility { } /* -------------------------------------------- */ - static async prepareEncaissement(actor, rollData, roll, armure) { - // La difficulté d'ataque s'ajoute aux dégâts - const bonusDegatsDiffLibre = ReglesOptionnelles.isUsing('degat-ajout-malus-libre') ? Math.abs(rollData.diffLibre ?? 0) : 0 - const jetTotal = roll.total + rollData.dmg.total - armure + bonusDegatsDiffLibre - const encaissement = RdDUtility._selectEncaissement(jetTotal, rollData.dmg.mortalite); + static async prepareEncaissement(actor, dmg, roll, armure) { + const jetTotal = roll.total + dmg.total - armure + const encaissement = RdDUtility._selectEncaissement(jetTotal, dmg.mortalite); const over20 = Math.max(jetTotal - 20, 0); - encaissement.dmg = rollData.dmg + encaissement.dmg = dmg if (ReglesOptionnelles.isUsing('localisation-aleatoire')) { - encaissement.dmg.loc = rollData.dmg.loc ?? await RdDUtility.getLocalisation(actor.type) + encaissement.dmg.loc = dmg.loc ?? await RdDUtility.getLocalisation(actor.type) encaissement.dmg.loc.label = encaissement.dmg.loc.label ?? 'Corps;' } else { encaissement.dmg.loc = { label: '' } } - encaissement.dmg.bonusDegatsDiffLibre = bonusDegatsDiffLibre - encaissement.roll = roll; - encaissement.armure = armure; - encaissement.penetration = rollData.arme?.system.penetration ?? 0; - encaissement.total = jetTotal; + encaissement.roll = roll + encaissement.armure = armure + encaissement.penetration = dmg.penetration + encaissement.total = jetTotal encaissement.vie = await RdDUtility._evaluatePerte(encaissement.vie, over20); encaissement.endurance = await RdDUtility._evaluatePerte(encaissement.endurance, over20); return encaissement; diff --git a/module/roll/chat-roll-result.mjs b/module/roll/chat-roll-result.mjs index 7eedd29e..7c22ad21 100644 --- a/module/roll/chat-roll-result.mjs +++ b/module/roll/chat-roll-result.mjs @@ -37,10 +37,12 @@ export default class ChatRollResult { } prepareDisplay(roll) { + roll.done = roll.done || {} roll.show = roll.show || {} roll.show.chance = this.isAppelChancePossible(roll) roll.show.encaissement = this.isShowEncaissement(roll) - roll.show.recul = this.isShowReculChoc(roll) + roll.show.recul = this.getReculChoc(roll) + } isAppelChancePossible(roll) { @@ -54,11 +56,22 @@ export default class ChatRollResult { roll.attackerRoll?.dmg.mortalite != 'empoignade' } - isShowReculChoc(roll) { + getReculChoc(roll, defender = roll.active.actor, attacker = roll.opponent.actor) { const attaque = roll.attackerRoll - return attaque && + if (attaque && (roll.rolled.isEchec || !roll.current.defense.isEsquive) && - (attaque.particuliere == 'force' || 'charge' == attaque.tactique?.key) + (attaque.particuliere == 'force' || 'charge' == attaque.tactique?.key)) { + const taille = defender.system.carac.taille.value + const impact = attacker.system.carac.force.value + roll.attackerRoll?.dmg.dmgArme + return { + raison: 'charge' == attaque.tactique?.key ? 'charge' : 'particulière en force', + taille: taille, + impact: impact, + chances: RdDResolutionTable.computeChances(10, taille-impact).norm, + diff: taille - impact + } + } + return undefined } async buildRollHtml(roll) { @@ -69,6 +82,28 @@ export default class ChatRollResult { async chatListeners(html) { $(html).on("click", '.appel-chance', event => this.onClickAppelChance(event)) $(html).on("click", '.appel-destinee', event => this.onClickAppelDestinee(event)) + $(html).on("click", '.encaissement', event => this.onClickEncaissement(event)) + $(html).on("click", '.resister-recul', event => this.onClickRecul(event)) + + } + + getCombat(roll) { + switch (roll.type.current) { + case ROLL_TYPE_DEFENSE: + return RdDCombat.rddCombatForAttackerAndDefender(roll.ids.opponentId, roll.ids.opponentTokenId, roll.ids.actorTokenId) + case ROLL_TYPE_ATTAQUE: + return RdDCombat.rddCombatForAttackerAndDefender(roll.ids.actorId, roll.ids.actorTokenId, roll.ids.opponentId) + } + return undefined + } + + async updateChatMessage(chatMessage, savedRoll) { + ChatUtility.setMessageData(chatMessage, 'rollData', savedRoll) + const copy = foundry.utils.duplicate(savedRoll) + RollDialog.loadRollData(copy) + this.prepareDisplay(copy) + chatMessage.update({ content: await this.buildRollHtml(copy) }) + chatMessage.render(true) } onClickAppelChance(event) { @@ -81,32 +116,23 @@ export default class ChatRollResult { event.preventDefault() } - getCombat(roll) { - switch (roll.type.current) { - case ROLL_TYPE_DEFENSE: - return RdDCombat.rddCombatForAttackerAndDefender(roll.ids.opponentId, roll.ids.opponentTokenId, roll.ids.actorId) - case ROLL_TYPE_ATTAQUE: - return RdDCombat.rddCombatForAttackerAndDefender(roll.ids.actorId, roll.ids.actorTokenId, roll.ids.opponentId) - } - return undefined - } - onAppelChanceSuccess(savedRoll, chatMessage) { const reRoll = foundry.utils.duplicate(savedRoll) reRoll.type.retry = true + const callbacks = [r => ChatUtility.removeChatMessageId(chatMessage.id)] // TODO: annuler les effets switch (reRoll.type.current) { case ROLL_TYPE_DEFENSE: - this.getCombat(reRoll)?.doRollDefense(reRoll) + this.getCombat(reRoll)?.doRollDefense(reRoll, callbacks) break case ROLL_TYPE_ATTAQUE: - this.getCombat(reRoll)?.doRollAttaque(reRoll) + // TODO + this.getCombat(reRoll)?.doRollAttaque(reRoll, callbacks) break default: { - RollDialog.create(reRoll) + RollDialog.create(reRoll, { callbacks: callbacks }) } } - ChatUtility.removeChatMessageId(chatMessage.id) } async onAppelChanceEchec(savedRoll, chatMessage) { @@ -114,15 +140,6 @@ export default class ChatRollResult { await this.updateChatMessage(chatMessage, savedRoll) } - async updateChatMessage(chatMessage, savedRoll) { - ChatUtility.setMessageData(chatMessage, 'rollData', savedRoll) - const copy = foundry.utils.duplicate(savedRoll) - RollDialog.loadRollData(copy) - this.prepareDisplay(copy) - chatMessage.update({ content: await this.buildRollHtml(copy) }) - chatMessage.render(true) - } - onClickAppelDestinee(event) { const chatMessage = ChatUtility.getChatMessage(event) const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') @@ -132,10 +149,32 @@ export default class ChatRollResult { const reRoll = foundry.utils.duplicate(savedRoll) reRoll.type.retry = true RdDResolutionTable.significativeRequise(reRoll.rolled) - await this.updateChatMessage(chatMessage, savedRoll) + await this.updateChatMessage(chatMessage, reRoll) }) - event.preventDefault() } + async onClickEncaissement(event) { + const chatMessage = ChatUtility.getChatMessage(event) + const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') + const attaque = savedRoll.attackerRoll + const defender = game.actors.get(savedRoll.ids.actorId) + const attacker = game.actors.get(savedRoll.ids.opponentId) + const defenderToken = savedRoll.ids.actorTokenId ? canvas.tokens.get(savedRoll.ids.actorTokenId) : undefined + const attackerToken = savedRoll.ids.opponentTokenId ? canvas.tokens.get(savedRoll.ids.opponentTokenId) : undefined + await defender?.encaisserDommages(attaque.dmg, attacker, undefined, attackerToken, defenderToken) + + savedRoll.done.encaissement = true + await this.updateChatMessage(chatMessage, savedRoll) + } + + async onClickRecul(event) { + const chatMessage = ChatUtility.getChatMessage(event) + const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') + const defender = game.actors.get(savedRoll.ids.actorId) + const attacker = game.actors.get(savedRoll.ids.opponentId) + savedRoll.done.recul = await defender.encaisserRecul(attacker.getForce(), savedRoll.attackerRoll.dmg.dmgArme) + // const reculChoc = this.getReculChoc(savedRoll, defender, attacker) + await this.updateChatMessage(chatMessage, savedRoll) + } } \ No newline at end of file diff --git a/module/roll/roll-dialog.mjs b/module/roll/roll-dialog.mjs index 0e9df824..2ecc326e 100644 --- a/module/roll/roll-dialog.mjs +++ b/module/roll/roll-dialog.mjs @@ -288,6 +288,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2 target.attackerRoll = rollData.attackerRoll target.rolled = rollData.rolled target.result = rollData.result + target.done = target.done ?? {} return target } diff --git a/module/roll/roll-part-attaque.mjs b/module/roll/roll-part-attaque.mjs index 22e25877..ca2f41e5 100644 --- a/module/roll/roll-part-attaque.mjs +++ b/module/roll/roll-part-attaque.mjs @@ -1,4 +1,5 @@ import { RdDBonus } from "../rdd-bonus.js" +import { ReglesOptionnelles } from "../settings/regles-optionnelles.js" import { ROLL_TYPE_ATTAQUE } from "./roll-constants.mjs" import { PART_CARAC } from "./roll-part-carac.mjs" import { PART_COMP } from "./roll-part-comp.mjs" @@ -49,28 +50,7 @@ export class RollPartAttaque extends RollPartSelect { prepareContext(rollData) { const current = this.getCurrent(rollData) - current.dmg = this.$dmgRollV2(rollData, current) - } - - $dmgRollV2(rollData, current) { - const actor = rollData.active.actor - const attaque = current.attaque - const arme = attaque.arme - const dmgArme = RdDBonus.dmgArme(arme, attaque.dommagesArme) - - const dmg = { - total: 0, - dmgArme: dmgArme, - penetration: arme.penetration(), - dmgTactique: current.tactique?.dmg ?? 0, - dmgParticuliere: 0, // TODO RdDBonus._dmgParticuliere(rollData), - dmgSurprise: rollData.opponent?.surprise?.dmg ?? 0, - mortalite: RdDBonus.mortalite(current.dmg?.mortalite, arme.system.mortalite, rollData.opponent?.actor?.isEntite()), - dmgActor: RdDBonus.bonusDmg(actor, attaque.carac.key, dmgArme, attaque.forceRequise), - dmgForceInsuffisante: Math.min(0, actor.getForce() - attaque.forceRequise) - } - dmg.total = dmg.dmgSurprise + dmg.dmgTactique + dmg.dmgArme + dmg.dmgActor + dmg.dmgParticuliere + dmg.dmgForceInsuffisante - return dmg + current.dmg = RdDBonus.dmgRollV2(rollData, current) } getAjustements(rollData) { diff --git a/module/settings/regles-optionnelles.js b/module/settings/regles-optionnelles.js index ce759ca9..1207c66b 100644 --- a/module/settings/regles-optionnelles.js +++ b/module/settings/regles-optionnelles.js @@ -13,6 +13,7 @@ const listeReglesOptionnelles = [ { group: 'Règles de combat', name: 'localisation-aleatoire', descr: "Proposer une localisation aléatoire des blessures" }, { group: 'Règles de combat', name: 'recul', descr: "Appliquer le recul en cas de particulière en force ou de charge" }, + { group: 'Règles de combat', name: 'acrobatie-pour-recul', descr: "L'acrobatie aide à ne pas chuter en cas de recul" , default: false }, { group: 'Règles de combat', name: 'resistanceArmeParade', descr: "Faire le jet de résistance des armes lors de parades pouvant les endommager" }, { group: 'Règles de combat', name: 'deteriorationArmure', descr: "Tenir compte de la détérioration des armures" }, { group: 'Règles de combat', name: 'defenseurDesarme', descr: "Le défenseur peut être désarmé en parant une particulière en force ou une charge avec une arme autre qu'un bouclier" }, diff --git a/templates/dialog-validation-encaissement.hbs b/templates/dialog-validation-encaissement.hbs index c8076f84..07dc680c 100644 --- a/templates/dialog-validation-encaissement.hbs +++ b/templates/dialog-validation-encaissement.hbs @@ -37,8 +37,8 @@ {{#if encaissement.dmg.dmgSurprise}}
+dom surprise: {{plusMoins encaissement.dmg.dmgSurprise}}
{{/if}} - {{#if encaissement.dmg.bonusDegatsDiffLibre}} -
+dom attaque: {{plusMoins encaissement.dmg.bonusDegatsDiffLibre}}
+ {{#if encaissement.dmg.dmgDiffLibre}} +
+dom attaque: {{plusMoins encaissement.dmg.dmgDiffLibre}}
{{/if}} diff --git a/templates/roll/result/partial-encaissement.hbs b/templates/roll/result/partial-encaissement.hbs index 3d59faad..8adbcb60 100644 --- a/templates/roll/result/partial-encaissement.hbs +++ b/templates/roll/result/partial-encaissement.hbs @@ -1,12 +1,15 @@ {{#if show.encaissement}} - - Encaisser à {{plusMoins attackerRoll.dmg.total}} - {{#if (eq attackerRoll.dmg.mortalite 'non-mortel')~}}(non-mortel){{/if}} - + {{#if done.encaissement}} + + + {{active.name}} a encaissé + + {{else}} + + Encaisser à {{plusMoins attackerRoll.dmg.total}} + {{#if (eq attackerRoll.dmg.mortalite 'non-mortel')~}}(non-mortel){{/if}} + + {{/if}} {{/if}} diff --git a/templates/roll/result/partial-recul-choc.hbs b/templates/roll/result/partial-recul-choc.hbs index 5471d1bb..1cfbd90a 100644 --- a/templates/roll/result/partial-recul-choc.hbs +++ b/templates/roll/result/partial-recul-choc.hbs @@ -1,11 +1,18 @@ {{#if show.recul}} - - Résister au recul - -{{/if}} + {{#if (eq done.recul 'recul')}} + + + {{active.name}} recule sous l'impact + + {{else if (eq done.recul 'chute')}} + + + {{active.name}} recule et chute sous l'impact + + {{else}} + + Résister au recul + 10 à {{plusMoins show.recul.diff}} = {{show.recul.chances}}% + + {{/if}} +{{/if}} \ No newline at end of file