import { MAX_ENDURANCE_FATIGUE, RdDUtility } from "../rdd-utility.js"; import { ReglesOptionnelles } from "../settings/regles-optionnelles.js"; import { STATUSES } from "../settings/status-effects.js"; import { ITEM_TYPES } from "../item.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"; /** * Classe de base pour les acteurs qui peuvent subir des blessures * - créatures * - humanoides */ export class RdDBaseActorSang extends RdDBaseActorReve { getForce() { return Number(this.system.carac.force?.value ?? 0) } getBonusDegat() { return Number(this.system.attributs?.plusdom?.value ?? 0) } getProtectionNaturelle() { return Number(this.system.attributs?.protection?.value ?? 0) } getSConst() { return 0 } getEnduranceMax() { return Math.max(1, Math.min(this.system.sante.endurance.max, MAX_ENDURANCE_FATIGUE)) } getFatigueActuelle() { if (ReglesOptionnelles.isUsing("appliquer-fatigue")) { return Math.max(0, Math.min(this.getFatigueMax(), this.system.sante.fatigue?.value ?? 0)); } return 0; } getFatigueRestante() { return this.getFatigueMax() - this.getFatigueActuelle() } getFatigueMin() { return this.system.sante.endurance.max - this.system.sante.endurance.value } getFatigueMax() { return this.getEnduranceMax() * 2 } malusFatigue() { if (ReglesOptionnelles.isUsing("appliquer-fatigue")) { return RdDUtility.calculMalusFatigue(this.getFatigueActuelle(), this.getEnduranceMax()) } return 0; } /* -------------------------------------------- */ getEncombrementMax() { return Number(this.system.attributs?.encombrement?.value ?? 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 = 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()); 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; } const sante = foundry.utils.duplicate(this.system.sante) let compteur = sante[name]; if (!compteur) { return; } let result = { sonne: false, }; 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 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()) } const perte = compteur.value - result.newValue; result.perte = perte; if (perte > 1) { // Peut-être sonné si 2 points d'endurance perdus d'un coup foundry.utils.mergeObject(result, await this.jetEndurance(result.newValue)); } else if (inc > 0) { await this.setSonne(false); } if (sante.fatigue && inc < 0) { // Each endurance lost -> fatigue lost fatigue = perte; } } 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 (this.isDead()) { await this.setEffect(STATUSES.StatusComma, true); } return result } /* -------------------------------------------- */ _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 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) { // Aggravation encaissement.gravite += 2 if (encaissement.gravite > 2) { encaissement.vie += 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: ` ${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !` }); } return blessure; } 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()) { ChatMessage.create({ content: `Jet de Vie: ${this.name} 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: ${jetDeVie.total} / ${vie}` if (isSuccess) { msgText += "
Réussi, pas de perte de point de vie." } else { msgText += `
Echoué, perte ${perte} point de vie`; await this.santeIncDec("vie", -perte); } if (this.isDead()) { msgText += `
${this.name} est mort !!!!`; } else if (prochainJet > 0) { msgText += `
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) { const jetEndurance = (await RdDDice.roll("1d20")).total; const sonne = jetEndurance == 20 || jetEndurance > (resteEndurance ?? this.system.sante.endurance.value) if (sonne) { await this.setSonne(); } return { jetEndurance, sonne } } async finDeRoundBlessures() { const nbGraves = this.filterItems(it => it.isGrave(), 'blessure').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) { ui.notifications.info(`${this.name} est hors combat, il ne reste donc pas sonné`); return; } await this.setEffect(STATUSES.StatusStunned, sonne); } getSonne() { return this.getEffect(STATUSES.StatusStunned); } 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 } }