Fix: affichage blessures et pertes

Les pertes et la récupération sont correctement gérées (en ne
faisant qu'un seul render pour le personnage)
This commit is contained in:
2026-03-07 20:37:16 +01:00
parent 9bb9a3b0eb
commit dfc73fb96d
11 changed files with 155 additions and 143 deletions

View File

@@ -5,6 +5,8 @@
- Le malus d'encombrement sur jet d'Agilité avec Natation ou Acrobatie peuvent être changés, et sont correctement arrondis
- L'ajustement de condition est plus visible sur les résultats de jets de dés
- Les blessures graves soignées ne font pas perdre d'endurance par round
- Les blessures et pertes correspondantes (vie, endurance, effets sonné, ...) s'affichent correctement
- la remise à neuf ne provoque pas d'erreur pour certains effets déjà supprimés
## 13.0.31 - Les choix multiples d'Illysis

View File

@@ -454,7 +454,7 @@ export class RdDActor extends RdDBaseActorSang {
blessures
})));
await this.supprimerBlessures(it => it.system.gravite <= 0);
await this.supprimerBlessures(it => it.system.gravite <= 0)
}
/* -------------------------------------------- */
@@ -498,18 +498,18 @@ export class RdDActor extends RdDBaseActorSang {
/* -------------------------------------------- */
async remiseANeuf() {
await this.removeEffects(e => !e.statuses?.has(STATUSES.StatusDemiReve), { render: false })
await this.supprimerBlessures(it => true, { render: false })
await this.update({
'system.sante.endurance.value': this.system.sante.endurance.max,
'system.sante.vie.value': this.system.sante.vie.max,
'system.sante.fatigue.value': 0,
'system.compteurs.ethylisme': { value: 1, nb_doses: 0, jet_moral: false }
})
await this.removeEffects(e => !e.statuses?.has(STATUSES.StatusDemiReve));
await this.supprimerBlessures(it => true);
}, { render: true })
await ChatMessage.create({
whisper: ChatUtility.getOwners(this),
content: 'Remise à neuf de ' + this.name
});
})
}
/* -------------------------------------------- */
@@ -2788,20 +2788,20 @@ export class RdDActor extends RdDBaseActorSang {
const guerisonData = { list: [], pointsConsommes: 0 }
for (let blessure of blessures) {
if (pointsGuerison >= blessure.system.gravite) {
pointsGuerison -= blessure.system.gravite;
guerisonData.list.push(`1 Blessure ${blessure.system.label} (${blessure.system.gravite} points)`);
pointsGuerison -= blessure.system.gravite
guerisonData.list.push(`1 Blessure ${blessure.system.label} (${blessure.system.gravite} points)`)
ids.push(blessure.id)
}
}
if (ids.length > 0) {
await this.supprimerBlessures(it => ids.includes(it.id));
await this.supprimerBlessures(it => ids.includes(it.id), { render: blessures.length != ids.length })
}
if (blessures.length == ids.length) {
let pvManquants = this.system.sante.vie.max - this.system.sante.vie.value;
let pvSoignees = Math.min(pvManquants, Math.floor(pointsGuerison / 2));
pointsGuerison -= pvSoignees * 2;
guerisonData.list.push(pvSoignees + " Points de Vie soignés");
await this.santeIncDec('vie', +pvSoignees, false);
await this.santeIncDec('vie', +pvSoignees, { render: true })
}
guerisonData.pointsConsommes = pointsGuerisonInitial - pointsGuerison;
return guerisonData;

View File

@@ -158,7 +158,7 @@ export class RdDBaseActorReve extends RdDBaseActor {
computeResumeBlessure() { }
countBlessures(filter = it => !it.isContusion()) { return 0 }
async santeIncDec(name, inc, isCritique = false) { }
async santeIncDec(name, inc, options = {}) { }
async finDeRound(options = { terminer: false }) {
await this.finDeRoundSuppressionEffetsTermines(options)
@@ -656,23 +656,18 @@ export class RdDBaseActorReve extends RdDBaseActor {
jet => this.$onEncaissement(jet, show, attackerToken, defenderToken));
}
}
async $onEncaissement(jet, show, attackerToken, defenderToken) {
await this.onAppliquerJetEncaissement(jet, attackerToken);
await this.$afficherEncaissement(jet, show, defenderToken);
}
async onAppliquerJetEncaissement(encaissement, attackerToken) { }
async $afficherEncaissement(encaissement, show, defenderToken) {
async $onEncaissement(encaissement, show, attackerToken, defenderToken) {
await this.onAppliquerJetEncaissement(encaissement, attackerToken);
foundry.utils.mergeObject(encaissement, {
alias: defenderToken?.name ?? this.getAlias(),
hasPlayerOwner: this.hasPlayerOwner,
show: show ?? {}
}, { overwrite: false });
await ChatUtility.createChatWithRollMode(
{
await ChatUtility.createChatWithRollMode({
roll: encaissement.roll,
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.hbs', encaissement)
},

View File

@@ -113,9 +113,33 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
async onAppliquerJetEncaissement(encaissement, attackerToken) {
const santeOrig = foundry.utils.duplicate(this.system.sante);
const blessure = await this.ajouterBlessure(encaissement, attackerToken); // Will update the result table
const perteVie = await this.santeIncDec("vie", -encaissement.vie);
const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, blessure?.isCritique());
const blessure = this.nouvelleBlessure(encaissement.gravite, {
localisation: encaissement.dmg?.loc.label ?? '',
origine: attackerToken?.name ?? ''
})
if (blessure.system.gravite == encaissement.gravite) {
blessure.system.vie = encaissement.vie
blessure.system.endurance = encaissement.endurance
}
else { // aggravation du fait du nombre de blessures
blessure.system.vie = blessure.system.vie
const rollPerteEndurance = new Roll(blessure.system.endurance)
await rollPerteEndurance.evaluate()
blessure.system.endurance =rollPerteEndurance.total
}
const isCritique = blessure.system.gravite >= 6;
if (isCritique){
blessure.system.endurance = this.getEnduranceActuelle()
}
// Will update the result table
if (blessure.system.gravite > 6) {
this.setEffect(STATUSES.StatusComma, true)
encaissement.mort = "à seconde blessure critique"
}
const perteVie = await this.santeIncDec("vie", -encaissement.vie, { render: false })
const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, { isCritique, render: false })
await this.createEmbeddedDocuments('Item', [blessure])
foundry.utils.mergeObject(encaissement, {
resteEndurance: perteEndurance.newValue,
@@ -124,11 +148,11 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
endurance: perteEndurance.perte,
vie: santeOrig.vie.value - perteVie.newValue,
blessure: blessure
});
})
}
/* -------------------------------------------- */
async santeIncDec(name, inc, isCritique = false) {
async santeIncDec(name, inc, options = {}) {
if (name == 'fatigue' && !ReglesOptionnelles.isUsing("appliquer-fatigue")) {
return
}
@@ -142,13 +166,14 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
let minValue = name == "vie" ? -this.getSConst() - 1 : 0;
result.newValue = Math.max(minValue, Math.min(compteur.value + inc, compteur.max));
//console.log("New value ", inc, minValue, result.newValue);
let fatigue = 0;
if (name == "endurance") {
if (result.newValue == 0 && inc < 0 && !isCritique) { // perte endurance et endurance devient 0 (sauf critique) -> -1 vie
if (name == "endurance" && result.newValue == 0 && inc < 0 && !options.isCritique) { // perte endurance et endurance devient 0 (sauf critique) -> -1 vie
sante.vie.value--;
result.perteVie = true;
}
foundry.utils.mergeObject(options, { render: true }, { overwrite: false })
if (name == "endurance") {
result.newValue = Math.max(0, result.newValue);
if (inc > 0) { // le max d'endurance s'applique seulement à la récupération
result.newValue = Math.min(result.newValue, this._computeEnduranceMax())
@@ -164,17 +189,17 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
if (ReglesOptionnelles.isUsing("appliquer-fatigue") && sante.fatigue && fatigue > 0) {
sante.fatigue.value = Math.max(sante.fatigue.value + fatigue, this.getFatigueMin());
}
await this.update({ "system.sante": sante }, { render: true })
await this.update({ "system.sante": sante }, options)
if (perteEndurance > 1) {
// Peut-être sonné si 2 points d'endurance perdus d'un coup
foundry.utils.mergeObject(result, await this.jetEndurance(result.newValue));
foundry.utils.mergeObject(result, await this.jetEndurance(result.newValue, options));
} else if (name == "endurance" && inc > 0) {
await this.setSonne(false);
await this.setSonne(false, options)
}
if (this.isDead()) {
await this.setEffect(STATUSES.StatusComma, true);
await this.setEffect(STATUSES.StatusComma, true, options)
}
return result
}
@@ -306,32 +331,15 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
}
/* -------------------------------------------- */
async ajouterBlessure(encaissement, attackerToken = undefined) {
if (encaissement.gravite < 0) return;
if (encaissement.gravite > 0) {
while (this.countBlessures(it => it.system.gravite == encaissement.gravite) >= RdDItemBlessure.maxBlessures(encaissement.gravite) && encaissement.gravite <= 6) {
nouvelleBlessure(gravite, options = { origine: undefined, localisation: '' }) {
if (gravite < 0) return
if (gravite > 0) {
while (this.countBlessures(it => it.system.gravite == gravite) >= RdDItemBlessure.maxBlessures(gravite) && gravite <= 6) {
// Aggravation
encaissement.gravite += 2
if (encaissement.gravite > 2) {
encaissement.vie += 2;
gravite += 2
}
}
}
const endActuelle = this.getEnduranceActuelle();
const blessure = await RdDItemBlessure.createBlessure(this, encaissement.gravite, encaissement.dmg?.loc.label ?? '', attackerToken);
if (blessure.isCritique()) {
encaissement.endurance = endActuelle
}
if (blessure.isMort()) {
this.setEffect(STATUSES.StatusComma, true);
encaissement.mort = true;
ChatMessage.create({
content: `<img class="chat-icon" src="icons/svg/skull.svg" data-tooltip="charge" />
<strong>${this.getAlias()} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !</strong>`
});
}
return blessure;
return RdDItemBlessure.prepareBlessure(gravite, options);
}
async supprimerBlessure({ gravite }) {
@@ -341,10 +349,10 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
}
}
async supprimerBlessures(filterToDelete) {
async supprimerBlessures(filterToDelete, options = { render: true}) {
const toDelete = this.filterItems(filterToDelete, ITEM_TYPES.blessure)
.map(it => it.id);
await this.deleteEmbeddedDocuments('Item', toDelete);
.map(it => it.id)
await this.deleteEmbeddedDocuments('Item', toDelete, options)
}
countBlessures(filter = it => !it.isContusion()) {
@@ -391,11 +399,11 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
}
/* -------------------------------------------- */
async jetEndurance(resteEndurance = undefined) {
async jetEndurance(resteEndurance = undefined, options) {
const jetEndurance = (await RdDDice.roll("1d20")).total;
const sonne = jetEndurance == 20 || jetEndurance > (resteEndurance ?? this.system.sante.endurance.value)
if (sonne) {
await this.setSonne();
await this.setSonne(true, options)
}
return { jetEndurance, sonne }
}
@@ -409,12 +417,13 @@ export class RdDBaseActorSang extends RdDBaseActorReve {
}
}
async setSonne(sonne = true) {
async setSonne(sonne = true, options = {}) {
if (!game.combat && sonne) {
// TODO: vérifier si comportement toujours valable
ui.notifications.info(`${this.getAlias()} est hors combat, il ne reste donc pas sonné`);
return;
return
}
await this.setEffect(STATUSES.StatusStunned, sonne)
await this.setEffect(STATUSES.StatusStunned, sonne, options)
}
isSonne() {

View File

@@ -285,14 +285,14 @@ export class RdDBaseActor extends Actor {
return this.getEffects().filter(it => it.statuses.has(effectId))
}
async setEffect(effectId, status) {
async setEffect(effectId, status, options = {render: true}) {
if (this.isEffectAllowed(effectId)) {
const effects = this.getEffectsByStatus(effectId)
if (!status && effects.length > 0) {
await this.deleteEmbeddedDocuments('ActiveEffect', effects.map(it => it.id), { render: true })
await this.deleteEmbeddedDocuments('ActiveEffect', effects.map(it => it.id), options)
}
if (status && effects.length == 0) {
await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.prepareActiveEffect(effectId)], { render: true })
await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.prepareActiveEffect(effectId)], options)
}
}
}
@@ -301,13 +301,13 @@ export class RdDBaseActor extends Actor {
this.removeEffects(it => it.id == id)
}
async removeEffects(filter = e => true) {
async removeEffects(filter = e => true, options = {render: true}) {
if (game.user.isGM) {
const ids = this.getEffects(filter)
.filter(it => this.canRemoveEffects(it))
.map(it => it.id)
if (ids.length > 0) {
await this.deleteEmbeddedDocuments('ActiveEffect', ids)
await this.deleteEmbeddedDocuments('ActiveEffect', ids, options)
}
}

View File

@@ -15,13 +15,13 @@ export class RdDCreature extends RdDBaseActorSang {
}
async remiseANeuf() {
await this.removeEffects(e => true);
await this.supprimerBlessures(it => true);
await this.removeEffects(e => true, { render: false })
await this.supprimerBlessures(it => true, { render: false })
await this.update({
'system.sante.endurance.value': this.system.sante.endurance.max,
'system.sante.vie.value': this.system.sante.vie.max,
'system.sante.fatigue.value': 0
});
}, { render: true });
}
async finDeRoundBlessures() {

View File

@@ -86,11 +86,7 @@ export class RdDEntite extends RdDBaseActorReve {
return
}
const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance);
foundry.utils.mergeObject(encaissement, {
resteEndurance: perteEndurance.newValue,
endurance: perteEndurance.perte,
blessure: RdDItemBlessure.prepareBlessure(encaissement.gravite, encaissement.dmg?.loc.label ?? '', attackerToken)
})
foundry.utils.mergeObject(encaissement, { resteEndurance: perteEndurance.newValue, endurance: perteEndurance.perte })
}
isEntiteAccordee(attacker) {

View File

@@ -103,11 +103,10 @@ export class RdDActorExportSheet extends RdDActorSheet {
gravite: this.html.find(event.currentTarget).data('gravite')
})
)
this.html.find('.click-blessure-add').click(async event =>
await this.actor.ajouterBlessure({
gravite: this.html.find(event.currentTarget).data('gravite')
this.html.find('.click-blessure-add').click(async event => {
const blessure = this.actor.nouvelleBlessure(this.html.find(event.currentTarget).data('gravite'))
await actor.createEmbeddedDocuments('Item', [blessure])
})
)
this.html.find('.button-export').click(async event => await
ExportScriptarium.INSTANCE.exportActors([this.actor],
`${this.actor.uuid}-${this.actor.name}`

View File

@@ -19,7 +19,7 @@ const definitionsBlessures = [
{ type: "legere", gravite: 2, endurance: "1d6", vie: 0, label: 'Légère', max: 5, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/blessure.webp" },
{ type: "grave", gravite: 4, endurance: "2d6", vie: -2, label: 'Grave', max: 2, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/blessure.webp" },
{ type: "critique", gravite: 6, endurance: "-100", vie: -4, label: 'Critique', max: 1, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/blessure.webp" },
{ type: "mort", gravite: 8, label: 'Mort', max: 1, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/mort.webp" }
{ type: "mort", gravite: 8, endurance: "-100", vie: 0, label: 'Mort', max: 1, icon: "systems/foundryvtt-reve-de-dragon/icons/sante/mort.webp" }
]
export class RdDItemBlessure extends RdDItem {
@@ -71,13 +71,13 @@ export class RdDItemBlessure extends RdDItem {
return 0
}
static async createBlessure(actor, gravite, localisation = '', attackerToken = undefined) {
const blessure = RdDItemBlessure.prepareBlessure(gravite, localisation, attackerToken);
const blessures = await actor.createEmbeddedDocuments('Item', [blessure])
static async createBlessure(actor, gravite, options = { localisation: '', origine: '', render: true }) {
const blessure = RdDItemBlessure.prepareBlessure(gravite, options);
const blessures = await actor.createEmbeddedDocuments('Item', [blessure], options)
return blessures[0]
}
static prepareBlessure(gravite, localisation, attackerToken) {
static prepareBlessure(gravite, options = { localisation: '', origine: '' }) {
const definition = RdDItemBlessure.getDefinition(gravite);
return {
name: definition.label,
@@ -85,9 +85,11 @@ export class RdDItemBlessure extends RdDItem {
img: definition.icon,
system: {
gravite: gravite,
vie: definition.vie,
endurance: definition.endurance,
difficulte: -gravite,
localisation: localisation,
origine: attackerToken?.name ?? ""
localisation: options.localisation,
origine: options.origine
}
}
}

View File

@@ -693,7 +693,9 @@
"bonus": 0
},
"localisation": "",
"origine": ""
"origine": "",
"vie": 0,
"endurance": "0"
},
"maladie": {
"templates": ["description", "temporel"],

View File

@@ -21,15 +21,16 @@
{{~#unless (eq penetration 0)}} (pénétration de {{penetration}}){{/unless}}
{{~/unless~}}, total: <span class="rdd-roll-echec">{{total}}</span>
<br>
{{alias}}
{{log 'encaissement' this}}
{{#if mort}}vient de mourir
{{else if blessure}}
{{#unless mort}}
{{alias}}
{{#if blessure}}
{{#if (gt blessure.system.gravite 0)}}subit une blessure {{blessure.name}}
{{~else~}}subit une éraflure
{{~/if~}}
{{~else~}}s'en sort sans une égratignure
{{~/if~}}
{{/unless}}
{{~#unless (eq dmg.mortalite 'entiteincarnee')}}
{{#if dmg.loc.label}}
@@ -42,7 +43,7 @@
{{/if}}
{{#if (ne dmg.mortalite 'entiteincarnee')}}
{{#if (gt endurance 1)}}et
{{#if sonne}}est <strong>sonné</strong><img class="chat-inline-icon" src="icons/svg/stoned.svg" data-tooltip="charge" height="16" width="16" /> jusqu'à la fin du prochain round{{else}}n'est pas sonné{{/if}}!
{{#if sonne}}est <strong>sonné</strong><img class="chat-inline-icon" src="icons/svg/stoned.svg" data-tooltip="sonné" height="16" width="16" /> jusqu'à la fin du prochain round{{else}}n'est pas sonné{{/if}}!
{{#if hasPlayerOwner}}Jet d'endurance : {{jetEndurance}} / {{resteEndurance}}{{/if}}
{{/if}}
{{/if}}
@@ -52,4 +53,10 @@
{{else if (eq show.recul 'recul')}}<div>La violence du coup fait reculer {{alias}} de quelques mètres ! Il/elle ne pourra plus attaquer ce round.</div>
{{/if}}
</div>
{{#if mort}}
<div>
<img class="chat-icon" src="icons/svg/skull.svg" data-tooltip="mort" />
<strong>{{alias}} vient de succomber {{mort}}! Que les Dragons gardent son Archétype en paix !</strong>
</div>
{{/if}}
{{/if}}