Les pertes et la récupération sont correctement gérées (en ne faisant qu'un seul render pour le personnage)
444 lines
16 KiB
JavaScript
444 lines
16 KiB
JavaScript
import { RdDUtility } from "../rdd-utility.js";
|
|
import { ReglesOptionnelles } from "../settings/regles-optionnelles.js";
|
|
import { STATUSES } from "../settings/status-effects.js";
|
|
import { ITEM_TYPES } from "../constants.js";
|
|
import { RdDBaseActorReve } from "./base-actor-reve.js";
|
|
import { RdDDice } from "../rdd-dice.js";
|
|
import { RdDItemBlessure } from "../item/blessure.js";
|
|
import { ChatUtility } from "../chat-utility.js";
|
|
import { Misc } from "../misc.js";
|
|
import { RdDBaseActor } from "./base-actor.js";
|
|
import { CARACS } from "../rdd-carac.js";
|
|
|
|
/**
|
|
* Classe de base pour les acteurs qui peuvent subir des blessures
|
|
* - créatures
|
|
* - humanoides
|
|
*/
|
|
export class RdDBaseActorSang extends RdDBaseActorReve {
|
|
|
|
prepareActorData() {
|
|
this.system.sante.vie.max = Math.ceil((this.getTaille() + this.getConstitution()) / 2)
|
|
this.system.sante.vie.value = Math.min(this.system.sante.vie.value, this.system.sante.vie.max)
|
|
super.prepareActorData()
|
|
this.system.attributs.encombrement.value = this.getEncombrementMax()
|
|
}
|
|
|
|
getCarac() {
|
|
const carac = super.getCarac()
|
|
foundry.utils.mergeObject(carac, this.getCaracChanceActuelle())
|
|
foundry.utils.mergeObject(carac, this.getCaracVie())
|
|
return carac
|
|
}
|
|
|
|
getForce() { return Misc.toInt(this.system.carac.force?.value) }
|
|
getConstitution() { return Misc.toInt(this.system.carac.constitution?.value) }
|
|
getVolonte() { return Misc.toInt(this.system.carac.volonte?.value) }
|
|
|
|
getCaracVie() { return { [CARACS.VIE]: { label: "Vie", value: this.getVieMax(), type: "number" } } }
|
|
getVieMax() { return Misc.toInt(this.system.sante.vie?.max) }
|
|
getEnduranceMax() { return Math.max(1, this.getTaille() + this.getConstitution()) }
|
|
getFatigueMax() { return this.getEnduranceMax() * 2 }
|
|
|
|
getProtectionNaturelle() { return Misc.toInt(this.system.attributs?.protection?.value) }
|
|
|
|
getFatigueActuelle() {
|
|
if (ReglesOptionnelles.isUsing("appliquer-fatigue")) {
|
|
return Math.max(0, Math.min(this.getFatigueMax(), Misc.toInt(this.system.sante.fatigue?.value)))
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
isCumulFatigueCauseSommeil(cumulFatigue) {
|
|
return ReglesOptionnelles.isUsing("appliquer-fatigue")
|
|
? (this.getFatigueRestante() <= cumulFatigue)
|
|
: (this.getEnduranceActuelle() <= cumulFatigue)
|
|
}
|
|
getFatigueRestante() { return this.getFatigueMax() - this.getFatigueActuelle() }
|
|
getFatigueMin() { return this.system.sante.endurance.max - this.system.sante.endurance.value }
|
|
|
|
malusFatigue() {
|
|
if (ReglesOptionnelles.isUsing("appliquer-fatigue")) {
|
|
return RdDUtility.calculMalusFatigue(this.getFatigueActuelle(), this.getEnduranceMax())
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
isSurenc() { return this.computeMalusSurEncombrement() < 0 }
|
|
|
|
computeMalusSurEncombrement() {
|
|
return Math.min(0, Math.floor(this.getEncombrementMax() - this.encTotal));
|
|
}
|
|
|
|
isDead() { return this.system.sante.vie.value < -this.getSConst() }
|
|
|
|
nbBlessuresLegeres() { return this.itemTypes[ITEM_TYPES.blessure].filter(it => it.isLegere()).length }
|
|
nbBlessuresGraves() { return this.itemTypes[ITEM_TYPES.blessure].filter(it => it.isGrave()).length }
|
|
nbBlessuresCritiques() { return this.itemTypes[ITEM_TYPES.blessure].filter(it => it.isCritique()).length }
|
|
|
|
/* -------------------------------------------- */
|
|
computeResumeBlessure() {
|
|
const nbLegeres = this.nbBlessuresLegeres()
|
|
const nbGraves = this.nbBlessuresGraves()
|
|
const nbCritiques = this.nbBlessuresCritiques()
|
|
|
|
if (nbLegeres + nbGraves + nbCritiques == 0) {
|
|
return "Aucune blessure";
|
|
}
|
|
let resume = "Blessures:";
|
|
if (nbLegeres > 0) {
|
|
resume += " " + nbLegeres + " légère" + (nbLegeres > 1 ? "s" : "");
|
|
}
|
|
if (nbGraves > 0) {
|
|
if (nbLegeres > 0)
|
|
resume += ",";
|
|
resume += " " + nbGraves + " grave" + (nbGraves > 1 ? "s" : "");
|
|
}
|
|
if (nbCritiques > 0) {
|
|
if (nbGraves > 0 || nbLegeres > 0)
|
|
resume += ",";
|
|
resume += " une CRITIQUE !";
|
|
}
|
|
return resume;
|
|
}
|
|
|
|
blessuresASoigner() { return [] }
|
|
|
|
async computeArmure(attackerRoll) { return this.getProtectionNaturelle() }
|
|
async remiseANeuf() { }
|
|
async appliquerAjoutExperience(rollData, hideChatMessage = 'show') { }
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
async onAppliquerJetEncaissement(encaissement, attackerToken) {
|
|
const santeOrig = foundry.utils.duplicate(this.system.sante);
|
|
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,
|
|
sonne: perteEndurance.sonne,
|
|
jetEndurance: perteEndurance.jetEndurance,
|
|
endurance: perteEndurance.perte,
|
|
vie: santeOrig.vie.value - perteVie.newValue,
|
|
blessure: blessure
|
|
})
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async santeIncDec(name, inc, options = {}) {
|
|
if (name == 'fatigue' && !ReglesOptionnelles.isUsing("appliquer-fatigue")) {
|
|
return
|
|
}
|
|
if (!this.system.sante[name]) {
|
|
return
|
|
}
|
|
const sante = foundry.utils.duplicate(this.system.sante)
|
|
const compteur = sante[name]
|
|
const result = { sonne: false }
|
|
let perteEndurance = 0
|
|
let minValue = name == "vie" ? -this.getSConst() - 1 : 0;
|
|
|
|
result.newValue = Math.max(minValue, Math.min(compteur.value + inc, compteur.max));
|
|
let fatigue = 0;
|
|
|
|
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())
|
|
}
|
|
perteEndurance = compteur.value - result.newValue;
|
|
result.perte = perteEndurance
|
|
if (sante.fatigue && inc < 0) { // Each endurance lost -> fatigue lost
|
|
fatigue = perteEndurance;
|
|
}
|
|
}
|
|
compteur.value = result.newValue;
|
|
// If endurance lost, then the same amount of fatigue cannot be recovered
|
|
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 }, 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, options));
|
|
} else if (name == "endurance" && inc > 0) {
|
|
await this.setSonne(false, options)
|
|
}
|
|
|
|
if (this.isDead()) {
|
|
await this.setEffect(STATUSES.StatusComma, true, options)
|
|
}
|
|
return result
|
|
}
|
|
|
|
async callbackPremiersSoins(blessureId, rollData, soigneurId) {
|
|
await this.onRollTachePremiersSoins(blessureId,
|
|
rollData.v2 ? rollData.current.tache.tache : rollData.tache,
|
|
rollData.rolled.isETotal,
|
|
soigneurId)
|
|
}
|
|
async onRollTachePremiersSoins(blessureId, tache, isETotal, soigneurId) {
|
|
if (!this.isOwner) {
|
|
return RdDBaseActor.remoteActorCall({
|
|
tokenId: this.token?.id,
|
|
actorId: this.id,
|
|
method: 'onRollTachePremiersSoins', args: [blessureId, tache, isETotal, soigneurId]
|
|
})
|
|
}
|
|
|
|
const blessure = this.getItem(blessureId, 'blessure')
|
|
if (blessure && !blessure.system.premierssoins.done) {
|
|
if (isETotal) {
|
|
await blessure.update({
|
|
'system.difficulte': blessure.system.difficulte - 1,
|
|
'system.premierssoins.tache': Math.max(0, tache.system.points_de_tache_courant)
|
|
})
|
|
}
|
|
else {
|
|
const bonus = tache.system.points_de_tache_courant - tache.system.points_de_tache
|
|
await blessure.update({
|
|
'system.premierssoins': {
|
|
done: (bonus >= 0),
|
|
bonus: Math.max(0, bonus),
|
|
tache: Math.max(0, tache.system.points_de_tache_courant)
|
|
}
|
|
})
|
|
if (bonus >= 0 && soigneurId) {
|
|
const soigneur = game.actors.get(soigneurId)
|
|
await soigneur.deleteEmbeddedDocuments('Item', [tache.id], { render: true })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async onRollSoinsComplets(blessureId, rollData) {
|
|
if (!this.isOwner) {
|
|
return RdDBaseActor.remoteActorCall({
|
|
tokenId: this.token?.id,
|
|
actorId: this.id,
|
|
method: 'onRollSoinsComplets', args: [blessureId, rollData]
|
|
})
|
|
}
|
|
const blessure = this.getItem(blessureId, 'blessure')
|
|
if (blessure && blessure.system.premierssoins.done && !blessure.system.soinscomplets.done) {
|
|
// TODO: update de la blessure: passer par le MJ!
|
|
if (rollData.rolled.isETotal) {
|
|
await blessure.setSoinsBlessure({
|
|
difficulte: blessure.system.difficulte - 1,
|
|
premierssoins: { done: false, bonus: 0 }, soinscomplets: { done: false, bonus: 0 },
|
|
})
|
|
}
|
|
else {
|
|
// soins complets finis
|
|
await blessure.setSoinsBlessure({
|
|
soinscomplets: { done: true, bonus: Math.max(0, rollData.rolled.ptTache) },
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_computeEnduranceMax() {
|
|
const diffVie = this.system.sante.vie.max - this.system.sante.vie.value;
|
|
const maxEndVie = this.system.sante.endurance.max - (diffVie * 2);
|
|
const nbGraves = this.countBlessures(it => it.isGrave()) > 0
|
|
const nbCritiques = this.countBlessures(it => it.isCritique()) > 0
|
|
const maxEndGraves = Math.floor(this.system.sante.endurance.max / (2 * nbGraves));
|
|
const maxEndCritiques = nbCritiques > 0 ? 1 : this.system.sante.endurance.max;
|
|
return Math.max(0, Math.min(maxEndVie, maxEndGraves, maxEndCritiques));
|
|
}
|
|
|
|
async onUpdateActor(update, options, actorId) {
|
|
const updatedEndurance = update?.system?.sante?.endurance
|
|
if (updatedEndurance && options.diff) {
|
|
await this.setEffect(STATUSES.StatusUnconscious, updatedEndurance.value == 0)
|
|
}
|
|
await super.onUpdateActor(update, options, actorId)
|
|
}
|
|
|
|
async onCreateItem(item, options, id) {
|
|
await this.changeItemEffects(item)
|
|
await super.onCreateItem(item, options, id)
|
|
}
|
|
|
|
async onUpdateItem(item, updates, options, id) {
|
|
await this.changeItemEffects(item);
|
|
await super.onUpdateItem(item, updates, options, id)
|
|
}
|
|
|
|
async onDeleteItem(item, options, id) {
|
|
await this.changeItemEffects(item);
|
|
await super.onDeleteItem(item, options, id)
|
|
}
|
|
|
|
async changeItemEffects(item) {
|
|
switch (item.type) {
|
|
case ITEM_TYPES.blessure:
|
|
await this.changeStateBleeding();
|
|
break;
|
|
case ITEM_TYPES.maladie:
|
|
case ITEM_TYPES.poison:
|
|
await this.changeStateMalade();
|
|
break;
|
|
}
|
|
}
|
|
|
|
async changeStateBleeding() {
|
|
const bleeding = this.itemTypes[ITEM_TYPES.blessure].find(it => it.isBleeding())
|
|
await this.setEffect(STATUSES.StatusBleeding, bleeding ? true : false)
|
|
}
|
|
|
|
async changeStateMalade() {
|
|
const maladiePoisons = this.getMaladiesPoisons()
|
|
await this.setEffect(STATUSES.StatusMalade, maladiePoisons.length > 0)
|
|
}
|
|
|
|
getMaladiesPoisons() {
|
|
return this.items.filter(it => [ITEM_TYPES.maladie, ITEM_TYPES.poison].includes(it.type))
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
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
|
|
gravite += 2
|
|
}
|
|
}
|
|
return RdDItemBlessure.prepareBlessure(gravite, options);
|
|
}
|
|
|
|
async supprimerBlessure({ gravite }) {
|
|
const toDelete = this.itemTypes[ITEM_TYPES.blessure].find(it => it.system.gravite == gravite)?.id
|
|
if (toDelete) {
|
|
await this.deleteEmbeddedDocuments('Item', [toDelete]);
|
|
}
|
|
}
|
|
|
|
async supprimerBlessures(filterToDelete, options = { render: true}) {
|
|
const toDelete = this.filterItems(filterToDelete, ITEM_TYPES.blessure)
|
|
.map(it => it.id)
|
|
await this.deleteEmbeddedDocuments('Item', toDelete, options)
|
|
}
|
|
|
|
countBlessures(filter = it => !it.isContusion()) {
|
|
return this.filterItems(filter, ITEM_TYPES.blessure).length
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async jetDeVie() {
|
|
if (this.isDead()) {
|
|
ChatMessage.create({
|
|
content: `Jet de Vie: ${this.getAlias()} est déjà mort, ce n'est pas la peine d'en rajouter !!!!!`,
|
|
whisper: ChatUtility.getOwners(this)
|
|
})
|
|
return
|
|
}
|
|
const jetDeVie = await RdDDice.roll("1d20");
|
|
|
|
const sConst = this.getSConst();
|
|
const vie = this.system.sante.vie.value;
|
|
const isCritique = this.nbBlessuresCritiques() > 0;
|
|
const isGrave = this.nbBlessuresGraves();
|
|
const isEchecTotal = jetDeVie.total == 20;
|
|
const isSuccess = jetDeVie.total == 1 || jetDeVie.total <= vie;
|
|
const perte = isSuccess ? 0 : 1 + (isEchecTotal ? vie + sConst : 0)
|
|
const prochainJet = (jetDeVie.total == 1 && vie > 0 ? 20 : 1) * (isCritique ? 1 : isGrave > 0 ? sConst : 0)
|
|
|
|
let msgText = `Jet de Vie: <strong>${jetDeVie.total} / ${vie}</strong>`
|
|
if (isSuccess) {
|
|
msgText += "<br>Réussi, pas de perte de point de vie."
|
|
} else {
|
|
msgText += `<br>Echoué, perte ${perte} point de vie`;
|
|
await this.santeIncDec("vie", -perte);
|
|
}
|
|
if (this.isDead()) {
|
|
msgText += `<br><strong>${this.getAlias()} est mort !!!!</strong>`;
|
|
}
|
|
else if (prochainJet > 0) {
|
|
msgText += `<br>Prochain jet de vie dans ${prochainJet} ${isCritique ? 'round' : 'minute'}${prochainJet > 1 ? 's' : ''} ${isCritique ? '(état critique)' : '(état grave)'}`
|
|
}
|
|
ChatMessage.create({
|
|
content: msgText,
|
|
whisper: ChatUtility.getOwners(this)
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
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(true, options)
|
|
}
|
|
return { jetEndurance, sonne }
|
|
}
|
|
|
|
async finDeRoundBlessures() {
|
|
const nbGraves = this.itemTypes[ITEM_TYPES.blessure]
|
|
.filter(it => it.isGrave() && !it.system.premierssoins.done).length;
|
|
if (nbGraves > 0) {
|
|
// Gestion blessure graves : -1 pt endurance par blessure grave
|
|
await this.santeIncDec("endurance", -nbGraves);
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
await this.setEffect(STATUSES.StatusStunned, sonne, options)
|
|
}
|
|
|
|
isSonne() {
|
|
return this.getEffectsByStatus(STATUSES.StatusStunned).length > 0
|
|
}
|
|
|
|
isEffectAllowed(effectId) { return true }
|
|
|
|
/* -------------------------------------------- */
|
|
async computeEtatGeneral() { this.system.compteurs.etat.value = this.malusVie() + this.malusFatigue() + this.malusEthylisme() }
|
|
getEtatGeneral(options = { ethylisme: false }) { return this.system.compteurs.etat.value }
|
|
|
|
malusVie() { return Math.min(this.system.sante.vie.value - this.system.sante.vie.max, 0) }
|
|
malusEthylisme() { return 0 }
|
|
|
|
|
|
}
|