ChatMessage.create est async, il faut donc de préférence l'appeler avec un await. Des effets secondaires avaient lieu (ordre de messages, updates ultérieurs parfois pas pris en compte)
431 lines
16 KiB
JavaScript
431 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 {
|
|
|
|
|
|
async _preUpdate(changed, options, user) {
|
|
const updatedEndurance = changed?.system?.sante?.endurance
|
|
if (updatedEndurance && options.diff) {
|
|
await this.setEffect(STATUSES.StatusUnconscious, updatedEndurance.value == 0)
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
countBlessures(filter) { return this.itemTypes[ITEM_TYPES.blessure].filter(filter).length }
|
|
isDead() { return this.system.sante.vie.value < -this.getSConst() }
|
|
|
|
/* -------------------------------------------- */
|
|
computeResumeBlessure() {
|
|
function descBlessure(count, name) {
|
|
return count > 0 ? [`${count} ${name}${count > 1 ? "s" : ""}`] : []
|
|
}
|
|
|
|
const nbContusions = this.countBlessures(it => it.isContusion())
|
|
const nbLegeres = this.countBlessures(it => it.isLegere())
|
|
const nbGraves = this.countBlessures(it => it.isGrave())
|
|
const nbCritiques = this.countBlessures(it => it.isCritique())
|
|
return [
|
|
... (nbCritiques > 0 ? ['une CRITIQUE !'] : []),
|
|
...descBlessure(nbGraves, 'grave'),
|
|
...descBlessure(nbLegeres, 'légère'),
|
|
...descBlessure(nbContusions, 'contusion'),
|
|
]
|
|
}
|
|
|
|
blessuresASoigner() { return [] }
|
|
|
|
async computeArmure(attackerRoll) { return this.getProtectionNaturelle() }
|
|
async remiseANeuf() { }
|
|
async ajoutExperience(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)
|
|
const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, isCritique)
|
|
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, isCritique = false) {
|
|
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") {
|
|
if (result.newValue == 0 && inc < 0 && !isCritique) {
|
|
// perte endurance et endurance devient 0 (sauf critique) -> -1 vie
|
|
sante.vie.value--
|
|
result.perteVie = true
|
|
}
|
|
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 })
|
|
|
|
if (perteEndurance > 1) {
|
|
// Peut-être sonné si 2 points d'endurance perdus d'un coup
|
|
foundry.utils.mergeObject(result, await this.jetEndurance(result.newValue));
|
|
} else if (name == "endurance" && inc > 0) {
|
|
await this.setSonne(false)
|
|
}
|
|
|
|
if (this.isDead()) {
|
|
await this.setEffect(STATUSES.StatusComma, true)
|
|
}
|
|
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 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) {
|
|
const toDelete = this.filterItems(filterToDelete, ITEM_TYPES.blessure)
|
|
.map(it => it.id)
|
|
await this.deleteEmbeddedDocuments('Item', toDelete)
|
|
}
|
|
|
|
countBlessures(filter = it => !it.isContusion()) {
|
|
return this.filterItems(filter, ITEM_TYPES.blessure).length
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async jetDeVie() {
|
|
if (this.isDead()) {
|
|
return await 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)
|
|
})
|
|
}
|
|
const jetDeVie = await RdDDice.roll("1d20");
|
|
const sConst = this.getSConst();
|
|
const vie = this.system.sante.vie.value;
|
|
const isCritique = this.countBlessures(it => it.isCritique()) > 0
|
|
const isGrave = this.countBlessures(it => it.isGrave())
|
|
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)'}`
|
|
}
|
|
return await ChatMessage.create({
|
|
content: msgText,
|
|
whisper: ChatUtility.getOwners(this)
|
|
})
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async jetEndurance(resteEndurance = undefined) {
|
|
const jetEndurance = (await RdDDice.roll("1d20")).total;
|
|
const sonne = jetEndurance == 20 || jetEndurance > (resteEndurance ?? this.system.sante.endurance.value)
|
|
if (sonne) {
|
|
await this.setSonne(true)
|
|
}
|
|
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) {
|
|
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)
|
|
}
|
|
|
|
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 }
|
|
|
|
|
|
}
|