forked from public/foundryvtt-reve-de-dragon
		
	en cas de descente des TMR, suppression des signes draconiques éphémères durant seulement 1 round
		
			
				
	
	
		
			330 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			330 lines
		
	
	
		
			12 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";
 | |
| 
 | |
| /**
 | |
|  * 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()
 | |
|   }
 | |
| 
 | |
|   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) }
 | |
| 
 | |
|   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 = 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 }, { render: true })
 | |
|     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 onCreateItem(item, options, id) {
 | |
|     switch (item.type) {
 | |
|       case ITEM_TYPES.blessure:
 | |
|         await this.changeBleedingState()
 | |
|         break
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async onUpdateItem(item, options, id) {
 | |
|     switch (item.type) {
 | |
|       case ITEM_TYPES.blessure:
 | |
|         await this.changeBleedingState()
 | |
|         break
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async changeBleedingState() {
 | |
|     const bleeding = this.itemTypes[ITEM_TYPES.blessure].find(it => it.isBleeding())
 | |
|     await this.setEffect(STATUSES.StatusBleeding, bleeding ? true : false)
 | |
|   }
 | |
| 
 | |
|   /* -------------------------------------------- */
 | |
|   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: `<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;
 | |
|   }
 | |
| 
 | |
|   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.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) {
 | |
|     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.getAlias()} est hors combat, il ne reste donc pas sonné`);
 | |
|       return;
 | |
|     }
 | |
|     await this.setEffect(STATUSES.StatusStunned, sonne)
 | |
|   }
 | |
| 
 | |
|   isSonne() {
 | |
|     return this.getEffectByStatus(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 }
 | |
| 
 | |
| 
 | |
| }
 |