Fin de gestion encaissement/recul V2

This commit is contained in:
2025-09-28 14:41:03 +02:00
parent faca73b0a1
commit bbcd6ad363
19 changed files with 291 additions and 219 deletions

View File

@@ -20,6 +20,10 @@
- affichage du statut de surprise du défenseur - affichage du statut de surprise du défenseur
- prise en compte des significatives (demi-surprises, armes disparates, - prise en compte des significatives (demi-surprises, armes disparates,
particulière en finesse) 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, ...) - impossible de faire un jet "actif" en surprise totale (attaque, parade, ...)
## 13.0.8 - Le renouveau d'Illysis ## 13.0.8 - Le renouveau d'Illysis

View File

@@ -2682,6 +2682,17 @@ select,
max-width: 1rem; max-width: 1rem;
max-height: 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 { .system-foundryvtt-reve-de-dragon .chat-card-button {
text-shadow: 1px 1px #4d3534; text-shadow: 1px 1px #4d3534;
box-shadow: inset 1x 1px #a6827e; box-shadow: inset 1x 1px #a6827e;

View File

@@ -1963,6 +1963,18 @@
max-height: 1rem; 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{ .chat-card-button{
text-shadow: 1px 1px #4d3534; text-shadow: 1px 1px #4d3534;

View File

@@ -506,7 +506,7 @@ export class RdDActor extends RdDBaseActorSang {
'system.sante.fatigue.value': 0, 'system.sante.fatigue.value': 0,
'system.compteurs.ethylisme': { value: 1, nb_doses: 0, jet_moral: false } '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 this.supprimerBlessures(it => true);
await ChatMessage.create({ await ChatMessage.create({
whisper: ChatUtility.getOwners(this), whisper: ChatUtility.getOwners(this),
@@ -2513,29 +2513,27 @@ export class RdDActor extends RdDBaseActorSang {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async computeArmure(attackerRoll) { async computeArmure(dmg) {
let dmg = (attackerRoll.dmg.dmgArme ?? 0) + (attackerRoll.dmg.dmgActor ?? 0); let baseDmg = (dmg.dmgArme ?? 0) + (dmg.dmgActor ?? 0);
let armeData = attackerRoll.arme;
let protection = 0; let protection = 0;
const armures = this.items.filter(it => it.type == "armure" && it.system.equipe); if (dmg.encaisserSpecial != "noarmure") {
for (const armure of armures) { const armures = this.items.filter(it => it.type == "armure" && it.system.equipe)
protection += await RdDDice.rollTotal(armure.system.protection.toString());
if (dmg > 0 && attackerRoll.dmg.encaisserSpecial != "noarmure") { for (const armure of armures) {
await armure.deteriorerArmure(dmg) protection += await RdDDice.rollTotal(armure.system.protection.toString());
dmg = 0; 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); console.log("Final protect", protection, dmg)
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);
return protection; return protection;
} }

View File

@@ -118,8 +118,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
.filter(it => it != undefined); .filter(it => it != undefined);
} }
async computeArmure(dmg) { return this.getProtectionNaturelle() }
async computeArmure(attackerRoll) { return this.getProtectionNaturelle() }
async remiseANeuf() { } async remiseANeuf() { }
async appliquerAjoutExperience(rollData, hideChatMessage = 'show') { } async appliquerAjoutExperience(rollData, hideChatMessage = 'show') { }
@@ -227,12 +226,12 @@ export class RdDBaseActorReve extends RdDBaseActor {
isEffectAllowed(effectId) { return false } isEffectAllowed(effectId) { return false }
getEffects(filter = e => true, forceRequise = undefined) { 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)) { if (forceRequise && this.isForceInsuffisante(forceRequise)) {
/// TODO selected.push(StatusEffects.prepareActiveEffect(STATUSES.StatusForceWeak))
effects.push(StatusEffects.prepareActiveEffect(STATUSES.StatusForceWeak))
} }
return effects return selected
} }
getEffectByStatus(statusId) { getEffectByStatus(statusId) {
@@ -257,7 +256,8 @@ export class RdDBaseActorReve extends RdDBaseActor {
async removeEffects(filter = e => true) { async removeEffects(filter = e => true) {
if (game.user.isGM) { 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); await this.deleteEmbeddedDocuments('ActiveEffect', ids);
} }
} }
@@ -495,29 +495,37 @@ export class RdDBaseActorReve extends RdDBaseActor {
/* -------------------------------------------- */ /* -------------------------------------------- */
async encaisser() { await RdDEncaisser.encaisser(this) } 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')) { 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')) { if (ReglesOptionnelles.isUsing('validation-encaissement-gr')) {
await this.encaisserDommagesValidationGR(rollData, armure, show, attackerToken, defenderToken); await this.encaisserDommagesValidationGR(dmg, armure, show, attackerToken, defenderToken);
} }
else { 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) 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) { if (!game.user.isGM) {
RdDBaseActor.remoteActorCall({ RdDBaseActor.remoteActorCall({
tokenId: this.token?.id, tokenId: this.token?.id,
actorId: this.id, actorId: this.id,
method: 'encaisserDommagesValidationGR', args: [rollData, armure, show, attackerToken, defenderToken] method: 'encaisserDommagesValidationGR', args: [dmg, armure, show, attackerToken, defenderToken]
}) })
} else { } else {
DialogValidationEncaissement.validerEncaissement(this, rollData, armure, DialogValidationEncaissement.validerEncaissement(this, dmg, armure,
jet => this.$onEncaissement(jet, show, attackerToken, defenderToken)); 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') { async accorder(entite, when = 'avant-encaissement') {
if (when != game.settings.get(SYSTEM_RDD, "accorder-entite-cauchemar") if (when != game.settings.get(SYSTEM_RDD, "accorder-entite-cauchemar")
|| entite == undefined || entite == undefined
|| !entite.isEntite([ENTITE_INCARNE]) || !entite.isEntite([ENTITE_INCARNE])
|| entite.isEntiteAccordee(this)) { || 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 = { const rollData = {
alias: this.getAlias(), alias: this.getAlias(),
rolled: rolled, rolled: rolled,
@@ -571,11 +601,11 @@ export class RdDBaseActorReve extends RdDBaseActor {
}; };
if (rolled.isSuccess) { if (rolled.isSuccess) {
await entite.setEntiteReveAccordee(this); await entite.setEntiteReveAccordee(this)
} }
await RdDRollResult.displayRollData(rollData, this, 'chat-resultat-accorder-cauchemar.hbs'); await RdDRollResult.displayRollData(rollData, this, 'chat-resultat-accorder-cauchemar.hbs')
await this.appliquerAjoutExperience(rollData, true); await this.appliquerAjoutExperience(rollData, true)
return rolled.isSuccess; return rolled.isSuccess;
} }

View File

@@ -1,6 +1,4 @@
import { Grammar } from "../grammar.js";
import { ITEM_TYPES } from "../constants.js"; import { ITEM_TYPES } from "../constants.js";
import { LIST_CARAC_AUTRES } from "../rdd-carac.js";
import { RdDBaseActorSang } from "./base-actor-sang.js"; import { RdDBaseActorSang } from "./base-actor-sang.js";
export class RdDCreature extends RdDBaseActorSang { export class RdDCreature extends RdDBaseActorSang {
@@ -45,5 +43,4 @@ export class RdDCreature extends RdDBaseActorSang {
} }
return undefined return undefined
} }
} }

View File

@@ -7,18 +7,17 @@ import { RdDUtility } from "./rdd-utility.js";
*/ */
export class DialogValidationEncaissement extends Dialog { export class DialogValidationEncaissement extends Dialog {
static async validerEncaissement(actor, rollData, armure, onEncaisser) { static async validerEncaissement(actor, dmg, armure, onEncaisser) {
const encaissement = await RdDUtility.jetEncaissement(actor, rollData, armure, { showDice: HIDE_DICE }); 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', { const html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-validation-encaissement.hbs', {
actor: actor, actor: actor,
rollData: rollData,
encaissement: encaissement 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 // Common conf
let buttons = { let buttons = {
"valider": { label: "Valider", callback: html => this.onValider() }, "valider": { label: "Valider", callback: html => this.onValider() },
@@ -42,11 +41,11 @@ export class DialogValidationEncaissement extends Dialog {
super(dialogConf, dialogOptions); super(dialogConf, dialogOptions);
this.actor = actor this.actor = actor
this.rollData = rollData; this.dmg = dmg
this.armure = armure; this.armure = armure
this.encaissement = encaissement; this.encaissement = encaissement
this.onEncaisser = onEncaisser; this.onEncaisser = onEncaisser
this.forceDiceResult = {total: encaissement.roll.result }; this.forceDiceResult = {total: encaissement.roll.result }
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -55,14 +54,14 @@ export class DialogValidationEncaissement extends Dialog {
this.html = html; this.html = html;
this.html.find('input.encaissement-roll-result').keyup(async event => { this.html.find('input.encaissement-roll-result').keyup(async event => {
this.forceDiceResult.total = event.currentTarget.value; 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-total').text(this.encaissement.total);
this.html.find('label.encaissement-blessure').text(this.encaissement.blessures) this.html.find('label.encaissement-blessure').text(this.encaissement.blessures)
}); });
} }
async onValider() { 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) this.onEncaisser(this.encaissement)
} }
} }

View File

@@ -1,5 +1,6 @@
import { RdDItemArme } from "./item/arme.js"; import { RdDItemArme } from "./item/arme.js";
import { RdDPossession } from "./rdd-possession.js"; import { RdDPossession } from "./rdd-possession.js";
import { ReglesOptionnelles } from "./settings/regles-optionnelles.js";
const conditionsTactiques = [ const conditionsTactiques = [
{ key: '', label: '', dmg: 0, attaque: 0, parade: 0, esquive: true, isTactique: false }, { 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) { static dmg(rollData, actor, isEntiteIncarnee = false) {
const diff = rollData.diffLibre;
const dmgArme = RdDBonus.dmgArme(rollData.arme, rollData.arme?.system.dommagesReels) 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 const forceRequise = rollData.arme ? RdDItemArme.valeurMain(rollData.arme.system.force ?? 0, RdDItemArme.getMainAttaque(rollData.competence)) : 0
let dmg = { let dmg = {
total: 0, total: 0,
dmgArme: dmgArme, dmgArme: dmgArme,
diff: diff,
dmgDiffLibre: ReglesOptionnelles.isUsing('degat-ajout-malus-libre') ? Math.abs(diff ?? 0) : 0,
penetration: RdDBonus._peneration(rollData), penetration: RdDBonus._peneration(rollData),
dmgTactique: RdDBonus.dmgBonus(rollData.tactique), dmgTactique: RdDBonus.dmgBonus(rollData.tactique),
dmgParticuliere: RdDBonus._dmgParticuliere(rollData), 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 dmg.total = dmg.dmgSurprise + dmg.dmgTactique + dmg.dmgArme + dmg.dmgActor + dmg.dmgParticuliere + dmg.dmgForceInsuffisante
return dmg; 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) { static description(condition) {

View File

@@ -373,7 +373,7 @@ export class RdDCombat {
if (Misc.isOwnerPlayer(defender)) { if (Misc.isOwnerPlayer(defender)) {
let attackerRoll = msg.attackerRoll; let attackerRoll = msg.attackerRoll;
let attacker = msg.attackerId ? game.actors.get(msg.attackerId) : undefined; 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); const rddCombat = RdDCombat.rddCombatForAttackerAndDefender(msg.attackerId, msg.attackerToken.id, msg.defenderToken.id);
rddCombat?.removeChatMessageActionsPasseArme(attackerRoll.passeArme); rddCombat?.removeChatMessageActionsPasseArme(attackerRoll.passeArme);
} }
@@ -982,21 +982,24 @@ export class RdDCombat {
}) })
} }
async doRollDefense(rollData) { async doRollDefense(rollData, callbacks = []) {
await RollDialog.create(rollData, { await RollDialog.create(rollData, {
onRollDone: (dialog) => { onRollDone: (dialog) => {
if (!OptionsAvancees.isUsing(ROLL_DIALOG_V2_TEST)) if (!OptionsAvancees.isUsing(ROLL_DIALOG_V2_TEST))
dialog.close() dialog.close()
}, },
customChatMessage: true, customChatMessage: true,
callbacks: [async (roll) => { callbacks: [
this.removeChatMessageActionsPasseArme(roll.passeArme); async (roll) => {
// defense: esquive / arme de parade / competence de défense this.removeChatMessageActionsPasseArme(roll.passeArme);
if (!RdDCombat.isParticuliere(roll)) { // defense: esquive / arme de parade / competence de défense
await roll.active.actor.incDecItemUse(roll.current[PART_DEFENSE].defense?.id); if (!RdDCombat.isParticuliere(roll)) {
} await roll.active.actor.incDecItemUse(roll.current[PART_DEFENSE].defense?.id);
await this._onDefense(roll); }
}] await this._onDefense(roll);
},
...callbacks
]
}) })
} }
@@ -1027,12 +1030,10 @@ export class RdDCombat {
} }
async _onDefense(rollData) { async _onDefense(rollData) {
console.log("RdDCombat._onDefense >>>", rollData)
const isEsquive = rollData.current[PART_DEFENSE].isEsquive const isEsquive = rollData.current[PART_DEFENSE].isEsquive
const isParade = !isEsquive const isParade = !isEsquive
if (RdDCombat.isReussite(rollData)) { if (RdDCombat.isReussite(rollData)) {
if (isParade) { if (isParade) {
await this.computeRecul(rollData)
await this.computeDeteriorationArme(rollData) await this.computeDeteriorationArme(rollData)
} }
@@ -1040,11 +1041,6 @@ export class RdDCombat {
await this._onDefenseParticuliere(rollData, isEsquive) await this._onDefenseParticuliere(rollData, isEsquive)
} }
} }
else {
//await this._sendMessageDefense(rollData.attackerRoll, rollData, { defense: true })
}
// TODO: modify chat message
this.removeChatMessageActionsPasseArme(rollData.passeArme) this.removeChatMessageActionsPasseArme(rollData.passeArme)
} }
@@ -1269,44 +1265,14 @@ export class RdDCombat {
} }
const attackerRoll = defenderRoll.attackerRoll; const attackerRoll = defenderRoll.attackerRoll;
if (this._isForceOuCharge(attackerRoll, defenderRoll.v2)) { if (this._isForceOuCharge(attackerRoll, defenderRoll.v2)) {
const impact = this._computeImpactRecul(attackerRoll); defenderRoll.show.recul = this.defender.encaisserRecul(this.attacker.getForce(), attackerRoll.dmg.dmgArme)
const rollRecul = await RdDResolutionTable.roll(10, impact)
defenderRoll.show.recul = await this.gererRecul(rollRecul, impact)
} }
} }
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 */) { _isForceOuCharge(attaque, isRollV2 = false /* TODO: delete roll V1 */) {
return attaque.particuliere == 'force' || 'charge' == (isRollV2 ? attaque.tactique?.key : attaque.tactique) 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) { async encaisser(attackerRoll, defenderRoll) {
@@ -1316,12 +1282,16 @@ export class RdDCombat {
this._onEchecTotal(defenderRoll); this._onEchecTotal(defenderRoll);
} }
await this.doRollEncaissement(attackerRoll, defenderRoll);
this.removeChatMessageActionsPasseArme(attackerRoll.passeArme);
}
async doRollEncaissement(attackerRoll, defenderRoll) {
if (Misc.isOwnerPlayer(this.defender)) { if (Misc.isOwnerPlayer(this.defender)) {
attackerRoll.attackerId = this.attackerId; attackerRoll.attackerId = this.attackerId;
attackerRoll.defenderTokenId = this.defenderToken.id; attackerRoll.defenderTokenId = this.defenderToken.id;
await this.computeRecul(defenderRoll); 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 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, { game.socket.emit(SYSTEM_SOCKET_ID, {
@@ -1332,9 +1302,8 @@ export class RdDCombat {
attackerToken: this.attackerToken, attackerToken: this.attackerToken,
defenderToken: this.defenderToken defenderToken: this.defenderToken
} }
}); })
} }
this.removeChatMessageActionsPasseArme(attackerRoll.passeArme);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */

View File

@@ -66,12 +66,11 @@ export class RdDEncaisser extends Dialog {
/* -------------------------------------------- */ /* -------------------------------------------- */
performEncaisser(mortalite) { performEncaisser(mortalite) {
this.actor.encaisserDommages({ this.actor.encaisserDommages({
dmg: { total: Number(this.modifier),
total: Number(this.modifier), ajustement: Number(this.modifier),
ajustement: Number(this.modifier), encaisserSpecial: this.encaisserSpecial,
encaisserSpecial: this.encaisserSpecial, mortalite: mortalite,
mortalite: mortalite penetration: 0
}
}) })
} }
} }

View File

@@ -9,6 +9,7 @@ import { RdDResolutionTable } from "./rdd-resolution-table.js";
import { ReglesOptionnelles } from "./settings/regles-optionnelles.js"; import { ReglesOptionnelles } from "./settings/regles-optionnelles.js";
import { Grammar } from "./grammar.js"; import { Grammar } from "./grammar.js";
import { ACTOR_TYPES } from "./constants.js"; import { ACTOR_TYPES } from "./constants.js";
import { RdDUtility } from "./rdd-utility.js";
/** /**
* Extend the base Dialog entity to select roll parameters * Extend the base Dialog entity to select roll parameters

View File

@@ -610,35 +610,33 @@ export class RdDUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async getLocalisation(type = 'personnage') { static async getLocalisation(type = 'personnage') {
let result = await RdDDice.rollTotal("1d20"); const loc = { result: await RdDDice.rollTotal("1d20")};
let txt = ""
if (type == 'personnage') { if (type == 'personnage') {
if (result <= 3) txt = "Jambe, genou, pied, jarret"; if (loc.result <= 3) loc.txt = "Jambe, genou, pied, jarret";
else if (result <= 7) txt = "Hanche, cuisse, fesse"; else if (loc.result <= 7) loc.txt = "Hanche, cuisse, fesse";
else if (result <= 9) txt = "Ventre, reins"; else if (loc.result <= 9) loc.txt = "Ventre, reins";
else if (result <= 12) txt = "Poitrine, dos"; else if (loc.result <= 12) loc.txt = "Poitrine, dos";
else if (result <= 14) txt = "Avant-bras, main, coude"; else if (loc.result <= 14) loc.txt = "Avant-bras, main, coude";
else if (result <= 18) txt = "Epaule, bras, omoplate"; else if (loc.result <= 18) loc.txt = "Epaule, bras, omoplate";
else if (result == 19) txt = "Tête"; else if (loc.result == 19) loc.txt = "Tête";
else if (result == 20) txt = "Tête (visage)"; else if (loc.result == 20) loc.txt = "Tête (visage)";
} else { } else {
if (result <= 7) txt = "Jambes/Pattes"; if (loc.result <= 7) loc.txt = "Jambes/Pattes";
else if (result <= 18) txt = "Corps"; else if (loc.result <= 18) loc.txt = "Corps";
else if (result <= 20) txt = "Tête"; else if (loc.result <= 20) loc.txt = "Tête";
} }
return loc
return { result: result, label: txt };
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static async jetEncaissement(actor, rollData, armure, options = { showDice: HIDE_DICE }) { static async jetEncaissement(actor, dmg, armure, options = { showDice: HIDE_DICE }) {
const diff = Math.abs(rollData.diffLibre); const diff = Math.abs(dmg.diff)
let formula = RdDUtility.formuleEncaissement(diff, options) const formula = RdDUtility.formuleEncaissement(diff, options)
const roll = await RdDDice.roll(formula, options); const roll = await RdDDice.roll(formula, options);
RdDUtility.remplaceDeMinParDifficulte(roll, diff, 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) { 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 // Chaque dé fait au minimum la difficulté libre
if (ReglesOptionnelles.isUsing('degat-minimum-malus-libre')) { if (ReglesOptionnelles.isUsing('degat-minimum-malus-libre')) {
return `2d10min${diff}` return `2d10min${diff}`
@@ -670,25 +668,22 @@ export class RdDUtility {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static async prepareEncaissement(actor, rollData, roll, armure) { static async prepareEncaissement(actor, dmg, roll, armure) {
// La difficulté d'ataque s'ajoute aux dégâts const jetTotal = roll.total + dmg.total - armure
const bonusDegatsDiffLibre = ReglesOptionnelles.isUsing('degat-ajout-malus-libre') ? Math.abs(rollData.diffLibre ?? 0) : 0 const encaissement = RdDUtility._selectEncaissement(jetTotal, dmg.mortalite);
const jetTotal = roll.total + rollData.dmg.total - armure + bonusDegatsDiffLibre
const encaissement = RdDUtility._selectEncaissement(jetTotal, rollData.dmg.mortalite);
const over20 = Math.max(jetTotal - 20, 0); const over20 = Math.max(jetTotal - 20, 0);
encaissement.dmg = rollData.dmg encaissement.dmg = dmg
if (ReglesOptionnelles.isUsing('localisation-aleatoire')) { 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;' encaissement.dmg.loc.label = encaissement.dmg.loc.label ?? 'Corps;'
} }
else { else {
encaissement.dmg.loc = { label: '' } encaissement.dmg.loc = { label: '' }
} }
encaissement.dmg.bonusDegatsDiffLibre = bonusDegatsDiffLibre encaissement.roll = roll
encaissement.roll = roll; encaissement.armure = armure
encaissement.armure = armure; encaissement.penetration = dmg.penetration
encaissement.penetration = rollData.arme?.system.penetration ?? 0; encaissement.total = jetTotal
encaissement.total = jetTotal;
encaissement.vie = await RdDUtility._evaluatePerte(encaissement.vie, over20); encaissement.vie = await RdDUtility._evaluatePerte(encaissement.vie, over20);
encaissement.endurance = await RdDUtility._evaluatePerte(encaissement.endurance, over20); encaissement.endurance = await RdDUtility._evaluatePerte(encaissement.endurance, over20);
return encaissement; return encaissement;

View File

@@ -37,10 +37,12 @@ export default class ChatRollResult {
} }
prepareDisplay(roll) { prepareDisplay(roll) {
roll.done = roll.done || {}
roll.show = roll.show || {} roll.show = roll.show || {}
roll.show.chance = this.isAppelChancePossible(roll) roll.show.chance = this.isAppelChancePossible(roll)
roll.show.encaissement = this.isShowEncaissement(roll) roll.show.encaissement = this.isShowEncaissement(roll)
roll.show.recul = this.isShowReculChoc(roll) roll.show.recul = this.getReculChoc(roll)
} }
isAppelChancePossible(roll) { isAppelChancePossible(roll) {
@@ -54,11 +56,22 @@ export default class ChatRollResult {
roll.attackerRoll?.dmg.mortalite != 'empoignade' roll.attackerRoll?.dmg.mortalite != 'empoignade'
} }
isShowReculChoc(roll) { getReculChoc(roll, defender = roll.active.actor, attacker = roll.opponent.actor) {
const attaque = roll.attackerRoll const attaque = roll.attackerRoll
return attaque && if (attaque &&
(roll.rolled.isEchec || !roll.current.defense.isEsquive) && (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) { async buildRollHtml(roll) {
@@ -69,6 +82,28 @@ export default class ChatRollResult {
async chatListeners(html) { async chatListeners(html) {
$(html).on("click", '.appel-chance', event => this.onClickAppelChance(event)) $(html).on("click", '.appel-chance', event => this.onClickAppelChance(event))
$(html).on("click", '.appel-destinee', event => this.onClickAppelDestinee(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) { onClickAppelChance(event) {
@@ -81,32 +116,23 @@ export default class ChatRollResult {
event.preventDefault() 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) { onAppelChanceSuccess(savedRoll, chatMessage) {
const reRoll = foundry.utils.duplicate(savedRoll) const reRoll = foundry.utils.duplicate(savedRoll)
reRoll.type.retry = true reRoll.type.retry = true
const callbacks = [r => ChatUtility.removeChatMessageId(chatMessage.id)]
// TODO: annuler les effets // TODO: annuler les effets
switch (reRoll.type.current) { switch (reRoll.type.current) {
case ROLL_TYPE_DEFENSE: case ROLL_TYPE_DEFENSE:
this.getCombat(reRoll)?.doRollDefense(reRoll) this.getCombat(reRoll)?.doRollDefense(reRoll, callbacks)
break break
case ROLL_TYPE_ATTAQUE: case ROLL_TYPE_ATTAQUE:
this.getCombat(reRoll)?.doRollAttaque(reRoll) // TODO
this.getCombat(reRoll)?.doRollAttaque(reRoll, callbacks)
break break
default: { default: {
RollDialog.create(reRoll) RollDialog.create(reRoll, { callbacks: callbacks })
} }
} }
ChatUtility.removeChatMessageId(chatMessage.id)
} }
async onAppelChanceEchec(savedRoll, chatMessage) { async onAppelChanceEchec(savedRoll, chatMessage) {
@@ -114,15 +140,6 @@ export default class ChatRollResult {
await this.updateChatMessage(chatMessage, savedRoll) 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) { onClickAppelDestinee(event) {
const chatMessage = ChatUtility.getChatMessage(event) const chatMessage = ChatUtility.getChatMessage(event)
const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData') const savedRoll = ChatUtility.getMessageData(chatMessage, 'rollData')
@@ -132,10 +149,32 @@ export default class ChatRollResult {
const reRoll = foundry.utils.duplicate(savedRoll) const reRoll = foundry.utils.duplicate(savedRoll)
reRoll.type.retry = true reRoll.type.retry = true
RdDResolutionTable.significativeRequise(reRoll.rolled) 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)
}
} }

View File

@@ -288,6 +288,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2
target.attackerRoll = rollData.attackerRoll target.attackerRoll = rollData.attackerRoll
target.rolled = rollData.rolled target.rolled = rollData.rolled
target.result = rollData.result target.result = rollData.result
target.done = target.done ?? {}
return target return target
} }

View File

@@ -1,4 +1,5 @@
import { RdDBonus } from "../rdd-bonus.js" import { RdDBonus } from "../rdd-bonus.js"
import { ReglesOptionnelles } from "../settings/regles-optionnelles.js"
import { ROLL_TYPE_ATTAQUE } from "./roll-constants.mjs" import { ROLL_TYPE_ATTAQUE } from "./roll-constants.mjs"
import { PART_CARAC } from "./roll-part-carac.mjs" import { PART_CARAC } from "./roll-part-carac.mjs"
import { PART_COMP } from "./roll-part-comp.mjs" import { PART_COMP } from "./roll-part-comp.mjs"
@@ -49,28 +50,7 @@ export class RollPartAttaque extends RollPartSelect {
prepareContext(rollData) { prepareContext(rollData) {
const current = this.getCurrent(rollData) const current = this.getCurrent(rollData)
current.dmg = this.$dmgRollV2(rollData, current) current.dmg = RdDBonus.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
} }
getAjustements(rollData) { getAjustements(rollData) {

View File

@@ -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: '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: '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: '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: '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" }, { 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" },

View File

@@ -37,8 +37,8 @@
{{#if encaissement.dmg.dmgSurprise}} {{#if encaissement.dmg.dmgSurprise}}
<div>+dom surprise: {{plusMoins encaissement.dmg.dmgSurprise}}</div> <div>+dom surprise: {{plusMoins encaissement.dmg.dmgSurprise}}</div>
{{/if}} {{/if}}
{{#if encaissement.dmg.bonusDegatsDiffLibre}} {{#if encaissement.dmg.dmgDiffLibre}}
<div>+dom attaque: {{plusMoins encaissement.dmg.bonusDegatsDiffLibre}}</div> <div>+dom attaque: {{plusMoins encaissement.dmg.dmgDiffLibre}}</div>
{{/if}} {{/if}}
</div> </div>
</span> </span>

View File

@@ -1,12 +1,15 @@
{{#if show.encaissement}} {{#if show.encaissement}}
<a class='chat-card-button button-encaisser' {{#if done.encaissement}}
{{!-- data-actorId='{{ids.actorId}}' <span class='chat-card-info'>
data-actorTokenId='{{ids.actorTokenId}}' <img src="systems/foundryvtt-reve-de-dragon/assets/ui/encaisser.svg"/>
data-opponentId='{{ids.opponentId}}' {{active.name}} a encaissé
data-opponentTokenId='{{ids.opponentTokenId}}' --}} </span>
data-tooltip="Encaisser à {{plusMoins attackerRoll.dmg.total}} {{#if (eq attackerRoll.dmg.mortalite 'non-mortel')~}}(non-mortel){{/if}}" {{else}}
> <a class='chat-card-button encaissement'
<img src="systems/foundryvtt-reve-de-dragon/assets/ui/encaisser.svg"/> Encaisser à {{plusMoins attackerRoll.dmg.total}} data-tooltip="Encaisser à {{plusMoins attackerRoll.dmg.total}} {{#if (eq attackerRoll.dmg.mortalite 'non-mortel')~}}(non-mortel){{/if}}"
{{#if (eq attackerRoll.dmg.mortalite 'non-mortel')~}}(non-mortel){{/if}} >
</a> <img src="systems/foundryvtt-reve-de-dragon/assets/ui/encaisser.svg"/> Encaisser à {{plusMoins attackerRoll.dmg.total}}
{{#if (eq attackerRoll.dmg.mortalite 'non-mortel')~}}(non-mortel){{/if}}
</a>
{{/if}}
{{/if}} {{/if}}

View File

@@ -1,11 +1,18 @@
{{#if show.recul}} {{#if show.recul}}
<a class='chat-card-button resister-recul-button' {{#if (eq done.recul 'recul')}}
data-actorId='{{ids.actorId}}' <span class='chat-card-info'>
data-actorTokenId='{{ids.actorTokenId}}' <img src="systems/foundryvtt-reve-de-dragon/assets/ui/recul.svg"/>
data-opponentId='{{ids.opponentId}}' {{active.name}} recule sous l'impact
data-opponentTokenId='{{ids.opponentTokenId}}' </span>
data-tooltip="Résister au recul" {{else if (eq done.recul 'chute')}}
> <span class='chat-card-info'>
<img src="systems/foundryvtt-reve-de-dragon/assets/ui/recul.svg"/> Résister au recul <img src="icons/svg/falling.svg"/>
</a> {{active.name}} recule et chute sous l'impact
{{/if}} </span>
{{else}}
<a class='chat-card-button resister-recul' data-tooltip="Résister au recul ({{show.recul.raison}}, taille {{show.recul.taille}}, impact {{show.recul.impact}})">
<img src="systems/foundryvtt-reve-de-dragon/assets/ui/recul.svg"/> Résister au recul
10 à {{plusMoins show.recul.diff}} = {{show.recul.chances}}%
</a>
{{/if}}
{{/if}}